Repository: software-mansion/TypeGPU
Pull request #1359 (2025-06-11 16:02:08)

feat: std.log and std.log2


Patch:
diff --git a/packages/typegpu/src/data/vectorOps.ts b/packages/typegpu/src/data/vectorOps.ts
index 5a3b5f4b..51101b7f 100644
--- a/packages/typegpu/src/data/vectorOps.ts
+++ b/packages/typegpu/src/data/vectorOps.ts
@@ -1058,6 +1058,28 @@ export const VectorOps = {
     vec4h: unary4h((val) => 2 ** val),
   } as Record<VecKind, <T extends vBase>(v: T) => T>,
 
+  log: {
+    vec2f: unary2f(Math.log),
+    vec2h: unary2h(Math.log),
+
+    vec3f: unary3f(Math.log),
+    vec3h: unary3h(Math.log),
+
+    vec4f: unary4f(Math.log),
+    vec4h: unary4h(Math.log),
+  } as Record<VecKind, <T extends vBase>(v: T) => T>,
+
+  log2: {
+    vec2f: unary2f(Math.log2),
+    vec2h: unary2h(Math.log2),
+
+    vec3f: unary3f(Math.log2),
+    vec3h: unary3h(Math.log2),
+
+    vec4f: unary4f(Math.log2),
+    vec4h: unary4h(Math.log2),
+  } as Record<VecKind, <T extends vBase>(v: T) => T>,
+
   fract: {
     vec2f: unary2f((value) => value - Math.floor(value)),
     vec2h: unary2h((value) => value - Math.floor(value)),
diff --git a/packages/typegpu/src/std/index.ts b/packages/typegpu/src/std/index.ts
index 0d869b2c..f7c6c084 100644
--- a/packages/typegpu/src/std/index.ts
+++ b/packages/typegpu/src/std/index.ts
@@ -25,6 +25,8 @@ export {
   floor,
   fract,
   length,
+  log,
+  log2,
   max,
   min,
   mix,
diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts
index 9ca95c45..3dee75cf 100644
--- a/packages/typegpu/src/std/numeric.ts
+++ b/packages/typegpu/src/std/numeric.ts
@@ -463,6 +463,40 @@ export const length = createDualImpl(
   'length',
 );
 
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#log-builtin
+ */
+export const log = createDualImpl(
+  // CPU implementation
+  <T extends AnyFloatVecInstance | number>(value: T): T => {
+    if (typeof value === 'number') {
+      return Math.log(value) as T;
+    }
+    return VectorOps.log[value.kind](value) as T;
+  },
+  // GPU implementation
+  (value) => snip(`log(${value.value})`, value.dataType),
+  'log',
+);
+
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#log2-builtin
+ */
+export const log2 = createDualImpl(
+  // CPU implementation
+  <T extends AnyFloatVecInstance | number>(value: T): T => {
+    if (typeof value === 'number') {
+      return Math.log2(value) as T;
+    }
+    return VectorOps.log2[value.kind](value) as T;
+  },
+  // GPU implementation
+  (value) => snip(`log2(${value.value})`, value.dataType),
+  'log2',
+);
+
 /**
  * @privateRemarks
  * https://www.w3.org/TR/WGSL/#max-float-builtin


Test patch:
diff --git a/packages/typegpu/tests/std/numeric/log.test.ts b/packages/typegpu/tests/std/numeric/log.test.ts
new file mode 100644
index 00000000..b39205f8
--- /dev/null
+++ b/packages/typegpu/tests/std/numeric/log.test.ts
@@ -0,0 +1,40 @@
+import { describe, expect, it } from 'vitest';
+import { vec2f, vec3f, vec4f } from '../../../src/data/index.ts';
+import { isCloseTo, log } from '../../../src/std/index.ts';
+
+describe('log', () => {
+  it('computes natural logarithm of numeric value', () => {
+    expect(log(1)).toBeCloseTo(0);
+    expect(log(Math.E)).toBeCloseTo(1);
+    expect(log(Math.E * Math.E)).toBeCloseTo(2);
+    expect(log(0.5)).toBeCloseTo(Math.log(0.5));
+  });
+
+  it('computes natural logarithm for vec2f', () => {
+    const input = vec2f(1, Math.E);
+    const expected = vec2f(0, 1);
+    expect(isCloseTo(log(input), expected)).toBe(true);
+
+    const input2 = vec2f(Math.E * Math.E, 0.5);
+    const expected2 = vec2f(2, Math.log(0.5));
+    expect(isCloseTo(log(input2), expected2)).toBe(true);
+  });
+
+  it('computes natural logarithm for vec3f', () => {
+    const input = vec3f(1, Math.E, Math.E * Math.E);
+    const expected = vec3f(0, 1, 2);
+    expect(isCloseTo(log(input), expected)).toBe(true);
+  });
+
+  it('computes natural logarithm for vec4f', () => {
+    const input = vec4f(1, Math.E, Math.E * Math.E, 0.5);
+    const expected = vec4f(0, 1, 2, Math.log(0.5));
+    expect(isCloseTo(log(input), expected)).toBe(true);
+  });
+
+  it('handles edge cases correctly', () => {
+    expect(log(0)).toBe(Number.NEGATIVE_INFINITY);
+    expect(log(Number.POSITIVE_INFINITY)).toBe(Number.POSITIVE_INFINITY);
+    expect(Number.isNaN(log(-1))).toBe(true);
+  });
+});
diff --git a/packages/typegpu/tests/std/numeric/log2.test.ts b/packages/typegpu/tests/std/numeric/log2.test.ts
new file mode 100644
index 00000000..ffef6476
--- /dev/null
+++ b/packages/typegpu/tests/std/numeric/log2.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, it } from 'vitest';
+import { vec2f, vec3f, vec4f } from '../../../src/data/index.ts';
+import { isCloseTo, log2 } from '../../../src/std/index.ts';
+
+describe('log2', () => {
+  it('computes base-2 logarithm of numeric value', () => {
+    expect(log2(1)).toBeCloseTo(0);
+    expect(log2(2)).toBeCloseTo(1);
+    expect(log2(4)).toBeCloseTo(2);
+    expect(log2(8)).toBeCloseTo(3);
+    expect(log2(16)).toBeCloseTo(4);
+    expect(log2(0.5)).toBeCloseTo(-1);
+    expect(log2(0.25)).toBeCloseTo(-2);
+  });
+
+  it('computes base-2 logarithm for vec2f', () => {
+    const input = vec2f(2, 8);
+    const expected = vec2f(1, 3);
+    expect(isCloseTo(log2(input), expected)).toBe(true);
+  });
+
+  it('computes base-2 logarithm for vec3f', () => {
+    const input = vec3f(2, 4, 16);
+    const expected = vec3f(1, 2, 4);
+    expect(isCloseTo(log2(input), expected)).toBe(true);
+  });
+
+  it('computes base-2 logarithm for vec4f', () => {
+    const input = vec4f(1, 2, 0.5, 0.25);
+    const expected = vec4f(0, 1, -1, -2);
+    expect(isCloseTo(log2(input), expected)).toBe(true);
+  });
+
+  it('handles edge cases correctly', () => {
+    expect(log2(0)).toBe(Number.NEGATIVE_INFINITY);
+    expect(log2(Number.POSITIVE_INFINITY)).toBe(Number.POSITIVE_INFINITY);
+    expect(Number.isNaN(log2(-1))).toBe(true);
+  });
+});