From 6b23a19e97eb5d251f8e9217ed5c03b4dc45fa76 Mon Sep 17 00:00:00 2001 From: Marcin Hawryluk <70582973+mhawryluk@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:59:25 +0100 Subject: [PATCH 1/7] docs: Contribution guide (#854) --- CONTRIBUTING.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..cfcd0521 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +## Reporting Issues and Suggesting Features +If you encounter what you believe is a bug, please [report an issue](https://github.com/software-mansion/TypeGPU/issues/new). For suggesting new features, you should first [start a discussion](https://github.com/software-mansion/TypeGPU/discussions/new/choose) if one does not already exist. Feel free to also join and comment on [existing discussions](https://github.com/software-mansion/TypeGPU/discussions). + + +## Development + +To contribute by resolving an open issue or developing a new feature, please adhere to the following workflow: + +1. Fork this repository. +2. Create a new feature branch from the `main` branch. +3. Ensure that the `pnpm dev` script is running at all times during development. +4. Stage your changes and commit them. We recommend following the [Conventional Commit Specification](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. +5. Make sure all the tests pass when running `pnpm test`. +6. Submit the PR for review. + +After your pull request is submitted, we will review it at as soon as possible. We may suggest changes or request additional improvements, so please enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) on your PR. + + +## Release Checklist + +1. Create new branch, update version string in package.json and in openInStackBlitz.ts, run `pnpm install` +2. Take the built package from code sandbox CI and test the changes on StackBlitz (optional) +3. Merge to main +4. Prepare the package for publishing +```bash +cd packages/typegpu +pnpm prepare-package +cd dist +pnpm publish -—dry-run # (if alpha, --tag alpha) +``` +5. If everything looks okay, then `pnpm publish` (if alpha, `--tag alpha`) +6. Rebase *release* branch on *main* +7. Generate and edit release notes on GitHub \ No newline at end of file From 37176b08e2ee12b5704efc301a4a05cdc8044919 Mon Sep 17 00:00:00 2001 From: Marcin Hawryluk <70582973+mhawryluk@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:03:22 +0100 Subject: [PATCH 2/7] impr: Update entry function parameters (#857) --- .../rendering/box-raytracing/index.ts | 10 +-- .../examples/simple/increment-tgsl/index.ts | 2 +- .../examples/simulation/boids-next/index.ts | 9 ++- .../examples/simulation/confetti/index.ts | 12 ++-- .../fluid-double-buffering/index.ts | 19 +++--- .../simulation/fluid-with-atomics/index.ts | 20 +++--- packages/typegpu/src/builtin.ts | 27 +++++++- packages/typegpu/src/core/function/fnTypes.ts | 2 +- .../src/core/function/tgpuComputeFn.ts | 38 +++++++---- .../src/core/function/tgpuFragmentFn.ts | 67 ++++++++++++------- .../typegpu/src/core/function/tgpuVertexFn.ts | 35 +++++++--- packages/typegpu/src/core/root/rootTypes.ts | 5 +- packages/typegpu/src/data/attributes.ts | 10 ++- .../typegpu/tests/computePipeline.test.ts | 22 +++++- packages/typegpu/tests/rawFn.test.ts | 18 ++--- packages/typegpu/tests/renderPipeline.test.ts | 66 +++++++++++++----- packages/typegpu/tests/resolve.test.ts | 4 +- packages/typegpu/tests/tgslFn.test.ts | 51 ++++++++------ 18 files changed, 277 insertions(+), 140 deletions(-) diff --git a/apps/typegpu-docs/src/content/examples/rendering/box-raytracing/index.ts b/apps/typegpu-docs/src/content/examples/rendering/box-raytracing/index.ts index e647e960..afa98a0b 100644 --- a/apps/typegpu-docs/src/content/examples/rendering/box-raytracing/index.ts +++ b/apps/typegpu-docs/src/content/examples/rendering/box-raytracing/index.ts @@ -179,10 +179,10 @@ const getBoxIntersection = tgpu['~unstable'] .$name('box_intersection'); const vertexFunction = tgpu['~unstable'] - .vertexFn( - { vertexIndex: d.builtin.vertexIndex }, - { outPos: d.builtin.position }, - ) + .vertexFn({ + in: { vertexIndex: d.builtin.vertexIndex }, + out: { outPos: d.builtin.position }, + }) .does(/* wgsl */ `(input: VertexInput) -> VertexOutput { var pos = array( vec2( 1, 1), @@ -203,7 +203,7 @@ const boxSizeAccessor = tgpu['~unstable'].accessor(d.u32); const canvasDimsAccessor = tgpu['~unstable'].accessor(CanvasDimsStruct); const fragmentFunction = tgpu['~unstable'] - .fragmentFn({ position: d.builtin.position }, d.vec4f) + .fragmentFn({ in: { position: d.builtin.position }, out: d.vec4f }) .does(/* wgsl */ `(input: FragmentInput) -> @location(0) vec4f { let minDim = f32(min(canvasDims.width, canvasDims.height)); diff --git a/apps/typegpu-docs/src/content/examples/simple/increment-tgsl/index.ts b/apps/typegpu-docs/src/content/examples/simple/increment-tgsl/index.ts index f5e28180..b0eab5dd 100644 --- a/apps/typegpu-docs/src/content/examples/simple/increment-tgsl/index.ts +++ b/apps/typegpu-docs/src/content/examples/simple/increment-tgsl/index.ts @@ -11,7 +11,7 @@ const counterBuffer = root const counter = counterBuffer.as('mutable'); const increment = tgpu['~unstable'] - .computeFn({ num: d.builtin.numWorkgroups }, { workgroupSize: [1] }) + .computeFn({ in: { num: d.builtin.numWorkgroups }, workgroupSize: [1] }) .does((input) => { const tmp = counter.value.x; counter.value.x = counter.value.y; diff --git a/apps/typegpu-docs/src/content/examples/simulation/boids-next/index.ts b/apps/typegpu-docs/src/content/examples/simulation/boids-next/index.ts index 26c67899..36df97ca 100644 --- a/apps/typegpu-docs/src/content/examples/simulation/boids-next/index.ts +++ b/apps/typegpu-docs/src/content/examples/simulation/boids-next/index.ts @@ -41,7 +41,10 @@ const VertexOutput = { }; const mainVert = tgpu['~unstable'] - .vertexFn({ v: d.vec2f, center: d.vec2f, velocity: d.vec2f }, VertexOutput) + .vertexFn({ + in: { v: d.vec2f, center: d.vec2f, velocity: d.vec2f }, + out: VertexOutput, + }) .does(/* wgsl */ `(input: VertexInput) -> VertexOutput { let angle = getRotationFromVelocity(input.velocity); let rotated = rotate(input.v, angle); @@ -64,7 +67,7 @@ const mainVert = tgpu['~unstable'] }); const mainFrag = tgpu['~unstable'] - .fragmentFn(VertexOutput, d.vec4f) + .fragmentFn({ in: VertexOutput, out: d.vec4f }) .does(/* wgsl */ ` (input: FragmentInput) -> @location(0) vec4f { return input.color; @@ -209,7 +212,7 @@ const computeBindGroupLayout = tgpu const { currentTrianglePos, nextTrianglePos } = computeBindGroupLayout.bound; const mainCompute = tgpu['~unstable'] - .computeFn({ gid: d.builtin.globalInvocationId }, { workgroupSize: [1] }) + .computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: [1] }) .does(/* wgsl */ `(input: ComputeInput) { let index = input.gid.x; var instanceInfo = currentTrianglePos[index]; diff --git a/apps/typegpu-docs/src/content/examples/simulation/confetti/index.ts b/apps/typegpu-docs/src/content/examples/simulation/confetti/index.ts index c0f1ab74..eca14981 100644 --- a/apps/typegpu-docs/src/content/examples/simulation/confetti/index.ts +++ b/apps/typegpu-docs/src/content/examples/simulation/confetti/index.ts @@ -99,16 +99,16 @@ const rotate = tgpu['~unstable'].fn([d.vec2f, d.f32], d.vec2f).does(/* wgsl */ ` `); const mainVert = tgpu['~unstable'] - .vertexFn( - { + .vertexFn({ + in: { tilt: d.f32, angle: d.f32, color: d.vec4f, center: d.vec2f, index: d.builtin.vertexIndex, }, - VertexOutput, - ) + out: VertexOutput, + }) .does( /* wgsl */ `(input: VertexInput) -> VertexOutput { let width = input.tilt; @@ -136,14 +136,14 @@ const mainVert = tgpu['~unstable'] }); const mainFrag = tgpu['~unstable'] - .fragmentFn(VertexOutput, d.vec4f) + .fragmentFn({ in: VertexOutput, out: d.vec4f }) .does(/* wgsl */ ` (input: FragmentInput) -> @location(0) vec4f { return input.color; }`); const mainCompute = tgpu['~unstable'] - .computeFn({ gid: d.builtin.globalInvocationId }, { workgroupSize: [1] }) + .computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: [1] }) .does( /* wgsl */ `(input: ComputeInput) { let index = input.gid.x; diff --git a/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts index a9a1b890..2e036c37 100644 --- a/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts @@ -236,7 +236,7 @@ const computeVelocity = tgpu['~unstable'] .$uses({ getCell, isValidFlowOut, isValidCoord, rand01 }); const mainInitWorld = tgpu['~unstable'] - .computeFn({ gid: d.builtin.globalInvocationId }, { workgroupSize: [1] }) + .computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: [1] }) .does((input) => { const x = d.i32(input.gid.x); const y = d.i32(input.gid.y); @@ -258,7 +258,7 @@ const mainInitWorld = tgpu['~unstable'] }); const mainMoveObstacles = tgpu['~unstable'] - .computeFn({}, { workgroupSize: [1] }) + .computeFn({ workgroupSize: [1] }) .does(/* wgsl */ `() { for (var obs_idx = 0; obs_idx < MAX_OBSTACLES; obs_idx += 1) { let obs = prevObstacleReadonly[obs_idx]; @@ -403,7 +403,10 @@ const getMinimumInFlow = tgpu['~unstable'] }); const mainCompute = tgpu['~unstable'] - .computeFn({ gid: d.builtin.globalInvocationId }, { workgroupSize: [8, 8] }) + .computeFn({ + in: { gid: d.builtin.globalInvocationId }, + workgroupSize: [8, 8], + }) .does((input) => { const x = d.i32(input.gid.x); const y = d.i32(input.gid.y); @@ -464,10 +467,10 @@ let boxY = 0.2; let leftWallX = 0; const vertexMain = tgpu['~unstable'] - .vertexFn( - { idx: d.builtin.vertexIndex }, - { pos: d.builtin.position, uv: d.vec2f }, - ) + .vertexFn({ + in: { idx: d.builtin.vertexIndex }, + out: { pos: d.builtin.position, uv: d.vec2f }, + }) .does(/* wgsl */ `(input: VertexInput) -> VertexOut { var pos = array( vec2(1, 1), // top-right @@ -490,7 +493,7 @@ const vertexMain = tgpu['~unstable'] }`); const fragmentMain = tgpu['~unstable'] - .fragmentFn({ uv: d.vec2f }, d.vec4f) + .fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f }) .does((input) => { const x = d.i32(input.uv.x * d.f32(gridSizeUniform.value)); const y = d.i32(input.uv.y * d.f32(gridSizeUniform.value)); diff --git a/apps/typegpu-docs/src/content/examples/simulation/fluid-with-atomics/index.ts b/apps/typegpu-docs/src/content/examples/simulation/fluid-with-atomics/index.ts index 31248fb7..d9e3d5c5 100644 --- a/apps/typegpu-docs/src/content/examples/simulation/fluid-with-atomics/index.ts +++ b/apps/typegpu-docs/src/content/examples/simulation/fluid-with-atomics/index.ts @@ -281,14 +281,14 @@ const decideWaterLevel = tgpu['~unstable'].fn([d.u32, d.u32]).does((x, y) => { }); const vertex = tgpu['~unstable'] - .vertexFn( - { + .vertexFn({ + in: { squareData: d.vec2f, currentStateData: d.u32, idx: d.builtin.instanceIndex, }, - { pos: d.builtin.position, cell: d.f32 }, - ) + out: { pos: d.builtin.position, cell: d.f32 }, + }) .does((input) => { const w = sizeUniform.value.x; const h = sizeUniform.value.y; @@ -319,7 +319,7 @@ const vertex = tgpu['~unstable'] }); const fragment = tgpu['~unstable'] - .fragmentFn({ cell: d.f32 }, d.location(0, d.vec4f)) + .fragmentFn({ in: { cell: d.f32 }, out: d.location(0, d.vec4f) }) .does((input) => { if (input.cell === -1) { return d.vec4f(0.5, 0.5, 0.5, 1); @@ -361,12 +361,10 @@ function resetGameData() { drawCanvasData = []; const compute = tgpu['~unstable'] - .computeFn( - { gid: d.builtin.globalInvocationId }, - { - workgroupSize: [options.workgroupSize, options.workgroupSize], - }, - ) + .computeFn({ + in: { gid: d.builtin.globalInvocationId }, + workgroupSize: [options.workgroupSize, options.workgroupSize], + }) .does((input) => { decideWaterLevel(input.gid.x, input.gid.y); }); diff --git a/packages/typegpu/src/builtin.ts b/packages/typegpu/src/builtin.ts index 054554a9..e19727d3 100644 --- a/packages/typegpu/src/builtin.ts +++ b/packages/typegpu/src/builtin.ts @@ -45,6 +45,11 @@ export type BuiltinNumWorkgroups = Decorated< Vec3u, [Builtin<'num_workgroups'>] >; +export type BuiltinSubgroupInvocationId = Decorated< + U32, + [Builtin<'subgroup_invocation_id'>] +>; +export type BuiltinSubgroupSize = Decorated]>; export const builtin = { vertexIndex: attribute(u32, { @@ -99,6 +104,14 @@ export const builtin = { type: '@builtin', value: 'num_workgroups', }) as BuiltinNumWorkgroups, + subgroupInvocationId: attribute(u32, { + type: '@builtin', + value: 'subgroup_invocation_id', + }) as BuiltinSubgroupInvocationId, + subgroupSize: attribute(u32, { + type: '@builtin', + value: 'subgroup_size', + }) as BuiltinSubgroupSize, } as const; export type AnyBuiltin = (typeof builtin)[keyof typeof builtin]; @@ -107,7 +120,19 @@ export type AnyComputeBuiltin = | BuiltinLocalInvocationIndex | BuiltinGlobalInvocationId | BuiltinWorkgroupId - | BuiltinNumWorkgroups; + | BuiltinNumWorkgroups + | BuiltinSubgroupInvocationId + | BuiltinSubgroupSize; +export type AnyVertexInputBuiltin = BuiltinVertexIndex | BuiltinInstanceIndex; +export type AnyVertexOutputBuiltin = BuiltinClipDistances | BuiltinPosition; +export type AnyFragmentInputBuiltin = + | BuiltinPosition + | BuiltinFrontFacing + | BuiltinSampleIndex + | BuiltinSampleMask + | BuiltinSubgroupInvocationId + | BuiltinSubgroupSize; +export type AnyFragmentOutputBuiltin = BuiltinFragDepth | BuiltinSampleMask; export type OmitBuiltins = S extends AnyBuiltin ? never diff --git a/packages/typegpu/src/core/function/fnTypes.ts b/packages/typegpu/src/core/function/fnTypes.ts index e3ed55a5..e0d3b80b 100644 --- a/packages/typegpu/src/core/function/fnTypes.ts +++ b/packages/typegpu/src/core/function/fnTypes.ts @@ -48,7 +48,7 @@ export type Implementation< Return = unknown, > = string | ((...args: Args) => Return); -type BaseIOData = +export type BaseIOData = | F32 | F16 | I32 diff --git a/packages/typegpu/src/core/function/tgpuComputeFn.ts b/packages/typegpu/src/core/function/tgpuComputeFn.ts index 70afa47a..f396c709 100644 --- a/packages/typegpu/src/core/function/tgpuComputeFn.ts +++ b/packages/typegpu/src/core/function/tgpuComputeFn.ts @@ -51,32 +51,46 @@ export interface ComputeFnOptions { workgroupSize: number[]; } +export function computeFn(options: { + workgroupSize: number[]; + // biome-ignore lint/complexity/noBannedTypes: it's fine +}): TgpuComputeFnShell<{}>; + +export function computeFn< + ComputeIn extends Record, +>(options: { + in: ComputeIn; + workgroupSize: number[]; +}): TgpuComputeFnShell; + /** * Creates a shell of a typed entry function for the compute shader stage. Any function * that implements this shell can perform general-purpose computation. * - * @param workgroupSize + * @param options.in + * Record with builtins used by the compute shader. + * @param options.workgroupSize * Size of blocks that the thread grid will be divided into (up to 3 dimensions). */ -export function computeFn>( - argTypes: ComputeIn, - options: ComputeFnOptions, -): TgpuComputeFnShell { - const { workgroupSize } = options; - +export function computeFn< + ComputeIn extends Record, +>(options: { + in?: ComputeIn; + workgroupSize: number[]; +}): TgpuComputeFnShell { return { - argTypes: [createStructFromIO(argTypes)], + argTypes: [createStructFromIO(options.in ?? {})], returnType: undefined, workgroupSize: [ - workgroupSize[0] ?? 1, - workgroupSize[1] ?? 1, - workgroupSize[2] ?? 1, + options.workgroupSize[0] ?? 1, + options.workgroupSize[1] ?? 1, + options.workgroupSize[2] ?? 1, ], does(implementation) { return createComputeFn( this, - workgroupSize, + options.workgroupSize, implementation as Implementation, ); }, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index ab7f4fb3..226eefb2 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -1,8 +1,9 @@ import type { - BuiltinFragDepth, - BuiltinSampleMask, + AnyFragmentInputBuiltin, + AnyFragmentOutputBuiltin, OmitBuiltins, } from '../../builtin'; +import type { AnyAttribute } from '../../data/attributes'; import type { AnyWgslStruct } from '../../data/struct'; import type { Decorated, Location, Vec4f } from '../../data/wgslTypes'; import { type TgpuNamable, isNamable } from '../../namable'; @@ -10,7 +11,7 @@ import type { GenerationCtx } from '../../smol/wgslGenerator'; import type { Labelled, ResolutionCtx, SelfResolvable } from '../../types'; import { addReturnTypeToExternals } from '../resolve/externals'; import { createFnCore } from './fnCore'; -import type { IOLayout, IORecord, Implementation, InferIO } from './fnTypes'; +import type { BaseIOData, IORecord, Implementation, InferIO } from './fnTypes'; import { type IOLayoutToSchema, createOutputType, @@ -24,20 +25,22 @@ import { export type FragmentOutConstrained = | Vec4f | Decorated]> - | BuiltinSampleMask - | BuiltinFragDepth + | AnyFragmentOutputBuiltin | IORecord< - | Vec4f - | Decorated]> - | BuiltinSampleMask - | BuiltinFragDepth + Vec4f | Decorated]> | AnyFragmentOutputBuiltin >; +export type FragmentInConstrained = IORecord< + | BaseIOData + | Decorated[]> + | AnyFragmentInputBuiltin +>; + /** * Describes a fragment entry function signature (its arguments and return type) */ export interface TgpuFragmentFnShell< - FragmentIn extends IOLayout, + FragmentIn extends FragmentInConstrained, FragmentOut extends FragmentOutConstrained, > { readonly argTypes: [AnyWgslStruct]; @@ -63,7 +66,7 @@ export interface TgpuFragmentFnShell< } export interface TgpuFragmentFn< - Varying extends IOLayout = IOLayout, + Varying extends FragmentInConstrained = FragmentInConstrained, Output extends FragmentOutConstrained = FragmentOutConstrained, > extends TgpuNamable { readonly shell: TgpuFragmentFnShell; @@ -72,31 +75,45 @@ export interface TgpuFragmentFn< $uses(dependencyMap: Record): this; } +export function fragmentFn< + FragmentOut extends FragmentOutConstrained, +>(options: { + out: FragmentOut; + // biome-ignore lint/complexity/noBannedTypes: it's fine +}): TgpuFragmentFnShell<{}, FragmentOut>; + +export function fragmentFn< + FragmentIn extends FragmentInConstrained, + FragmentOut extends FragmentOutConstrained, +>(options: { + in: FragmentIn; + out: FragmentOut; +}): TgpuFragmentFnShell; + /** * Creates a shell of a typed entry function for the fragment shader stage. Any function * that implements this shell can run for each fragment (pixel), allowing the inner code * to process information received from the vertex shader stage and builtins to determine * the final color of the pixel (many pixels in case of multiple targets). * - * @param inputType - * Values computed in the vertex stage and builtins to be made available to functions that implement this shell. - * @param outputType - * A `vec4f`, signaling this function outputs a color for one target, or a struct/array containing - * colors for multiple targets. + * @param options.in + * Values computed in the vertex stage and builtins to be made available to functions that implement this shell. + * @param options.out + * A `vec4f`, signaling this function outputs a color for one target, or a record containing colors for multiple targets. */ export function fragmentFn< // Not allowing single-value input, as using objects here is more // readable, and refactoring to use a builtin argument is too much hassle. - FragmentIn extends IORecord, + FragmentIn extends FragmentInConstrained, FragmentOut extends FragmentOutConstrained, ->( - inputType: FragmentIn, - outputType: FragmentOut, -): TgpuFragmentFnShell { +>(options: { + in?: FragmentIn; + out: FragmentOut; +}): TgpuFragmentFnShell { return { - argTypes: [createStructFromIO(inputType)], - targets: outputType, - returnType: createOutputType(outputType) as FragmentOut, + argTypes: [createStructFromIO(options.in ?? {})], + targets: options.out, + returnType: createOutputType(options.out) as FragmentOut, does(implementation) { // biome-ignore lint/suspicious/noExplicitAny: @@ -110,7 +127,7 @@ export function fragmentFn< // -------------- function createFragmentFn( - shell: TgpuFragmentFnShell, + shell: TgpuFragmentFnShell, implementation: Implementation, ): TgpuFragmentFn { type This = TgpuFragmentFn & Labelled & SelfResolvable; diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index 22da1bf1..80a862d4 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -56,15 +56,30 @@ export interface TgpuVertexFn< $uses(dependencyMap: Record): this; } +export function vertexFn(options: { + out: VertexOut; + // biome-ignore lint/complexity/noBannedTypes: it's fine +}): TgpuVertexFnShell<{}, VertexOut>; + +export function vertexFn< + VertexIn extends IORecord, + // Not allowing single-value output, as it is better practice + // to properly label what the vertex shader is outputting. + VertexOut extends IORecord, +>(options: { + in: VertexIn; + out: VertexOut; +}): TgpuVertexFnShell; + /** * Creates a shell of a typed entry function for the vertex shader stage. Any function * that implements this shell can run for each vertex, allowing the inner code to process * attributes and determine the final position of the vertex. * - * @param inputType + * @param options.in * Vertex attributes and builtins to be made available to functions that implement this shell. - * @param outputType - * A struct type containing the final position of the vertex, and any information + * @param options.out + * A record containing the final position of the vertex, and any information * passed onto the fragment shader stage. */ export function vertexFn< @@ -72,14 +87,14 @@ export function vertexFn< // Not allowing single-value output, as it is better practice // to properly label what the vertex shader is outputting. VertexOut extends IORecord, ->( - inputType: VertexIn, - outputType: VertexOut, -): TgpuVertexFnShell { +>(options: { + in?: VertexIn; + out: VertexOut; +}): TgpuVertexFnShell { return { - attributes: [inputType], - returnType: createOutputType(outputType) as unknown as VertexOut, - argTypes: [createStructFromIO(inputType)], + attributes: [options.in ?? ({} as VertexIn)], + returnType: createOutputType(options.out) as unknown as VertexOut, + argTypes: [createStructFromIO(options.in ?? {})], does(implementation) { // biome-ignore lint/suspicious/noExplicitAny: diff --git a/packages/typegpu/src/core/root/rootTypes.ts b/packages/typegpu/src/core/root/rootTypes.ts index 7cf734e2..a0342b58 100644 --- a/packages/typegpu/src/core/root/rootTypes.ts +++ b/packages/typegpu/src/core/root/rootTypes.ts @@ -24,6 +24,7 @@ import type { IOLayout, IORecord } from '../function/fnTypes'; import type { TgpuComputeFn } from '../function/tgpuComputeFn'; import type { TgpuFn } from '../function/tgpuFn'; import type { + FragmentInConstrained, FragmentOutConstrained, TgpuFragmentFn, } from '../function/tgpuFragmentFn'; @@ -48,7 +49,7 @@ export interface WithCompute { export type ValidateFragmentIn< VertexOut extends IORecord, - FragmentIn extends IORecord, + FragmentIn extends FragmentInConstrained, FragmentOut extends FragmentOutConstrained, > = FragmentIn extends Partial ? VertexOut extends FragmentIn @@ -76,7 +77,7 @@ export type ValidateFragmentIn< export interface WithVertex { withFragment< - FragmentIn extends IORecord, + FragmentIn extends FragmentInConstrained, FragmentOut extends FragmentOutConstrained, >( ...args: ValidateFragmentIn diff --git a/packages/typegpu/src/data/attributes.ts b/packages/typegpu/src/data/attributes.ts index e6baf357..39c6364e 100644 --- a/packages/typegpu/src/data/attributes.ts +++ b/packages/typegpu/src/data/attributes.ts @@ -50,16 +50,20 @@ export const builtinNames = [ 'global_invocation_id', 'workgroup_id', 'num_workgroups', + 'subgroup_invocation_id', + 'subgroup_size', ] as const; export type BuiltinName = (typeof builtinNames)[number]; -export type AnyAttribute = +export type AnyAttribute< + AllowedBuiltins extends Builtin = Builtin, +> = | Align | Size | Location - | Builtin - | Interpolate; + | Interpolate + | AllowedBuiltins; export type ExtractAttributes = T extends { readonly attribs: unknown[]; diff --git a/packages/typegpu/tests/computePipeline.test.ts b/packages/typegpu/tests/computePipeline.test.ts index ee82c3b0..8cef39c6 100644 --- a/packages/typegpu/tests/computePipeline.test.ts +++ b/packages/typegpu/tests/computePipeline.test.ts @@ -1,12 +1,16 @@ import { describe, expect, expectTypeOf } from 'vitest'; -import tgpu, { MissingBindGroupsError, type TgpuComputePipeline } from '../src'; +import tgpu, { + MissingBindGroupsError, + type TgpuComputeFnShell, + type TgpuComputePipeline, +} from '../src'; import * as d from '../src/data'; import { it } from './utils/extendedIt'; describe('TgpuComputePipeline', () => { it('can be created with a compute entry function', ({ root, device }) => { const entryFn = tgpu['~unstable'] - .computeFn({}, { workgroupSize: [32] }) + .computeFn({ workgroupSize: [32] }) .does(() => { // do something }) @@ -36,7 +40,7 @@ describe('TgpuComputePipeline', () => { .$name('example-layout'); const entryFn = tgpu['~unstable'] - .computeFn({}, { workgroupSize: [1] }) + .computeFn({ workgroupSize: [1] }) .does(() => { layout.bound.alpha; // Using an entry of the layout }) @@ -54,4 +58,16 @@ describe('TgpuComputePipeline', () => { `[Error: Missing bind groups for layouts: 'example-layout'. Please provide it using pipeline.with(layout, bindGroup).(...)]`, ); }); + + it('allows to omit input in entry function shell', () => { + expectTypeOf( + tgpu['~unstable'].computeFn({ in: {}, workgroupSize: [1] }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + + expectTypeOf( + tgpu['~unstable'].computeFn({ workgroupSize: [1] }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + }); }); diff --git a/packages/typegpu/tests/rawFn.test.ts b/packages/typegpu/tests/rawFn.test.ts index 2a33059b..bda93356 100644 --- a/packages/typegpu/tests/rawFn.test.ts +++ b/packages/typegpu/tests/rawFn.test.ts @@ -175,10 +175,10 @@ describe('tgpu.fn with raw string WGSL implementation', () => { it('adds output struct definition when resolving vertex functions', () => { const vertexFunction = tgpu['~unstable'] - .vertexFn( - { vertexIndex: d.builtin.vertexIndex }, - { outPos: d.builtin.position }, - ) + .vertexFn({ + in: { vertexIndex: d.builtin.vertexIndex }, + out: { outPos: d.builtin.position }, + }) .does(/* wgsl */ `(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { var pos = array( vec2( 1, 1), @@ -210,10 +210,10 @@ struct vertex_fn_Output { it('adds output struct definition when resolving fragment functions', () => { const fragmentFunction = tgpu['~unstable'] - .fragmentFn( - { position: d.builtin.position }, - { a: d.vec4f, b: d.builtin.fragDepth }, - ) + .fragmentFn({ + in: { position: d.builtin.position }, + out: { a: d.vec4f, b: d.builtin.fragDepth }, + }) .does(/* wgsl */ `(@builtin(position) position: vec4f) -> Output { var out: Output; out.a = vec4f(1.0); @@ -238,7 +238,7 @@ struct fragment_Output { it('properly handles fragment functions with a single output argument', () => { const fragmentFunction = tgpu['~unstable'] - .fragmentFn({ position: d.builtin.position }, d.vec4f) + .fragmentFn({ in: { position: d.builtin.position }, out: d.vec4f }) .does(/* wgsl */ `(input: FragmentIn) -> @location(0) vec4f { return vec4f(1.0f); }`) diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 7adce4f9..77296363 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -1,21 +1,27 @@ -import { describe, expect } from 'vitest'; -import tgpu, { MissingBindGroupsError } from '../src'; +import { describe, expect, expectTypeOf } from 'vitest'; +import tgpu, { + MissingBindGroupsError, + type TgpuFragmentFnShell, + type TgpuVertexFnShell, +} from '../src'; import * as d from '../src/data'; import { it } from './utils/extendedIt'; describe('Inter-Stage Variables', () => { describe('Empty vertex output', () => { - const emptyVert = tgpu['~unstable'].vertexFn({}, {}).does(''); + const emptyVert = tgpu['~unstable'].vertexFn({ out: {} }).does(''); const emptyVertWithBuiltin = tgpu['~unstable'] - .vertexFn({}, { pos: d.builtin.vertexIndex }) + .vertexFn({ out: { pos: d.builtin.vertexIndex } }) .does(''); it('allows fragment functions to use a subset of the vertex output', ({ root, }) => { - const emptyFragment = tgpu['~unstable'].fragmentFn({}, {}).does(''); + const emptyFragment = tgpu['~unstable'] + .fragmentFn({ in: {}, out: {} }) + .does(''); const emptyFragmentWithBuiltin = tgpu['~unstable'] - .fragmentFn({ pos: d.builtin.position }, {}) + .fragmentFn({ in: { pos: d.builtin.position }, out: {} }) .does(''); // Using none of none @@ -53,7 +59,7 @@ describe('Inter-Stage Variables', () => { root, }) => { const fragment = tgpu['~unstable'] - .fragmentFn({ a: d.vec3f, c: d.f32 }, {}) + .fragmentFn({ in: { a: d.vec3f, c: d.f32 }, out: {} }) .does(''); root @@ -66,21 +72,25 @@ describe('Inter-Stage Variables', () => { describe('Non-empty vertex output', () => { const vert = tgpu['~unstable'] - .vertexFn({}, { a: d.vec3f, b: d.vec2f }) + .vertexFn({ out: { a: d.vec3f, b: d.vec2f } }) .does(''); const vertWithBuiltin = tgpu['~unstable'] - .vertexFn({}, { a: d.vec3f, b: d.vec2f, pos: d.builtin.position }) + .vertexFn({ + out: { a: d.vec3f, b: d.vec2f, pos: d.builtin.position }, + }) .does(''); it('allows fragment functions to use a subset of the vertex output', ({ root, }) => { - const emptyFragment = tgpu['~unstable'].fragmentFn({}, {}).does(''); + const emptyFragment = tgpu['~unstable'] + .fragmentFn({ in: {}, out: {} }) + .does(''); const emptyFragmentWithBuiltin = tgpu['~unstable'] - .fragmentFn({ pos: d.builtin.frontFacing }, {}) + .fragmentFn({ in: { pos: d.builtin.frontFacing }, out: {} }) .does(''); const fullFragment = tgpu['~unstable'] - .fragmentFn({ a: d.vec3f, b: d.vec2f }, d.vec4f) + .fragmentFn({ in: { a: d.vec3f, b: d.vec2f }, out: d.vec4f }) .does(''); // Using none @@ -125,7 +135,7 @@ describe('Inter-Stage Variables', () => { root, }) => { const fragment = tgpu['~unstable'] - .fragmentFn({ a: d.vec3f, c: d.f32 }, {}) + .fragmentFn({ in: { a: d.vec3f, c: d.f32 }, out: {} }) .does(''); // @ts-expect-error: Missing from vertex output @@ -136,7 +146,7 @@ describe('Inter-Stage Variables', () => { root, }) => { const fragment = tgpu['~unstable'] - .fragmentFn({ a: d.vec3f, b: d.f32 }, {}) + .fragmentFn({ in: { a: d.vec3f, b: d.f32 }, out: {} }) .does(''); // @ts-expect-error: Mismatched vertex output @@ -152,11 +162,13 @@ describe('Inter-Stage Variables', () => { .$name('example-layout'); const vertexFn = utgpu - .vertexFn({}, {}) + .vertexFn({ out: {} }) .does('() { layout.bound.alpha; }') .$uses({ layout }); - const fragmentFn = utgpu.fragmentFn({}, { out: d.vec4f }).does('() {}'); + const fragmentFn = utgpu + .fragmentFn({ out: { out: d.vec4f } }) + .does('() {}'); const pipeline = root .withVertex(vertexFn, {}) @@ -173,4 +185,26 @@ describe('Inter-Stage Variables', () => { `[Error: Missing bind groups for layouts: 'example-layout'. Please provide it using pipeline.with(layout, bindGroup).(...)]`, ); }); + + it('allows to omit input in entry function shell', () => { + expectTypeOf( + tgpu['~unstable'].vertexFn({ in: {}, out: {} }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + + expectTypeOf( + tgpu['~unstable'].vertexFn({ out: {} }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + + expectTypeOf( + tgpu['~unstable'].fragmentFn({ in: {}, out: {} }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + + expectTypeOf( + tgpu['~unstable'].fragmentFn({ out: {} }), + // biome-ignore lint/complexity/noBannedTypes: it's fine + ).toEqualTypeOf>(); + }); }); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 61331b43..fa528cf2 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -42,12 +42,12 @@ describe('tgpu resolve', () => { } as unknown as TgpuBufferReadonly; const fragment1 = tgpu['~unstable'] - .fragmentFn({}, d.vec4f) + .fragmentFn({ out: d.vec4f }) .does(() => d.vec4f(0, intensity.value, 0, 1)) .$name('fragment1'); const fragment2 = tgpu['~unstable'] - .fragmentFn({}, d.vec4f) + .fragmentFn({ out: d.vec4f }) .does(() => d.vec4f(intensity.value, 0, 0, 1)) .$name('fragment2'); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index b05fe9ba..7fc4e6c1 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -166,17 +166,17 @@ describe('TGSL tgpu.fn function', () => { it('resolves vertexFn', () => { const vertexFn = tgpu['~unstable'] - .vertexFn( - { + .vertexFn({ + in: { vi: builtin.vertexIndex, ii: builtin.instanceIndex, color: vec4f, }, - { + out: { pos: builtin.position, uv: vec2f, }, - ) + }) .does((input) => { const vi = input.vi; const ii = input.ii; @@ -215,7 +215,10 @@ describe('TGSL tgpu.fn function', () => { it('resolves computeFn', () => { const computeFn = tgpu['~unstable'] - .computeFn({ gid: builtin.globalInvocationId }, { workgroupSize: [24] }) + .computeFn({ + in: { gid: builtin.globalInvocationId }, + workgroupSize: [24], + }) .does((input) => { const index = input.gid.x; const iterationF = f32(0); @@ -244,30 +247,34 @@ describe('TGSL tgpu.fn function', () => { }); it('rejects invalid arguments for computeFn', () => { - tgpu['~unstable'] - // @ts-expect-error - .computeFn({ vid: builtin.vertexIndex }, { workgroupSize: [24] }) - .does(() => {}); - - tgpu['~unstable'] - .computeFn( - // @ts-expect-error - { gid: builtin.globalInvocationId, random: f32 }, - { workgroupSize: [24] }, - ) - .does(() => {}); + const u = tgpu['~unstable']; + + // @ts-expect-error + u.computeFn({ in: { vid: builtin.vertexIndex }, workgroupSize: [24] }).does( + () => {}, + ); + + // @ts-expect-error + u.computeFn({ + in: { gid: builtin.globalInvocationId, random: f32 }, + workgroupSize: [24], + }).does(() => {}); }); it('resolves fragmentFn', () => { const fragmentFn = tgpu['~unstable'] - .fragmentFn( - { pos: builtin.position, uv: vec2f, sampleMask: builtin.sampleMask }, - { + .fragmentFn({ + in: { + pos: builtin.position, + uv: vec2f, + sampleMask: builtin.sampleMask, + }, + out: { sampleMask: builtin.sampleMask, fragDepth: builtin.fragDepth, out: location(0, vec4f), }, - ) + }) .does((input) => { const pos = input.pos; const out = { @@ -315,7 +322,7 @@ describe('TGSL tgpu.fn function', () => { it('resolves fragmentFn with a single output', () => { const fragmentFn = tgpu['~unstable'] - .fragmentFn({ pos: builtin.position }, vec4f) + .fragmentFn({ in: { pos: builtin.position }, out: vec4f }) .does((input) => { return input.pos; }) From 996493f6b3a1cd06733b4dccbabe731ca0749337 Mon Sep 17 00:00:00 2001 From: Marcin Hawryluk <70582973+mhawryluk@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:07:24 +0100 Subject: [PATCH 3/7] docs: Update Repository structure in Readme (#866) --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3aacb805..1b06eab4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,16 @@ Since 2012 [Software Mansion](https://swmansion.com) is a software agency with e ## Repository structure **Packages**: -- [packages/typegpu](/packages/typegpu) - the core library. +- [packages/typegpu](/packages/typegpu) - The core library. +- [packages/unplugin-typegpu](/packages/unplugin-typegpu) - Build plugins for TypeGPU. +- [packages/tgpu-gen](/packages/tgpu-gen) - CLI tool for automatic TypeGPU code generation. +- [packages/tgpu-jit](/packages/tgpu-jit) - Just-In-Time transpiler for TypeGPU. +- [packages/tinyest](/packages/tinyest) - Type definitions for a JS embeddable syntax tree. +- [packages/tinyest-for-wgsl](/packages/tinyest-for-wgsl) - Transforms JavaScript into its *tinyest* form, to be used in generating equivalent (or close to) WGSL code. +- [packages/tgpu-wgsl-parser](/packages/tgpu-wgsl-parser) - WGSL code parser. +- ~~[packages/rollup-plugin](/packages/rollup-plugin) - Rollup plugin for TypeGPU.~~ (replaced by *unplugin-typegpu*) + +- [packages/tgpu-dev-cli](/packages/tgpu-dev-cli) - Development tools for packages in the monorepo. **Apps**: -- [apps/typegpu-docs](/apps/typegpu-docs) - the documentation webpage. +- [apps/typegpu-docs](/apps/typegpu-docs) - The documentation, examples and benchmarks webpage. From b6185ddf917d25c1540ce350c2f598851f231af8 Mon Sep 17 00:00:00 2001 From: deluksic Date: Fri, 14 Feb 2025 09:48:27 +0100 Subject: [PATCH 4/7] refactor: Vector implementation without using Proxy (#779) --- .../scripts/generateSwizzleFunctions.ts | 40 + packages/typegpu/src/data/matrix.ts | 3 +- packages/typegpu/src/data/vector.ts | 583 +-------------- packages/typegpu/src/data/vectorImpl.ts | 681 ++++++++++++++++++ packages/typegpu/src/data/vectorOps.ts | 2 +- packages/typegpu/src/data/wgslTypes.ts | 2 + packages/typegpu/src/std/numeric.ts | 2 +- 7 files changed, 762 insertions(+), 551 deletions(-) create mode 100644 packages/typegpu/scripts/generateSwizzleFunctions.ts create mode 100644 packages/typegpu/src/data/vectorImpl.ts diff --git a/packages/typegpu/scripts/generateSwizzleFunctions.ts b/packages/typegpu/scripts/generateSwizzleFunctions.ts new file mode 100644 index 00000000..72c8c63f --- /dev/null +++ b/packages/typegpu/scripts/generateSwizzleFunctions.ts @@ -0,0 +1,40 @@ +/** + * Prints the swizzling getters to be manually added to the VecBase abstract class. + */ +printSwizzlingFor('xyzw'); + +/** + * Yields combinations of letters from `components` of given `length`. + * + * @example + * vectorComponentCombinations('xyz', 2) // xx, xy, xz, yx, yy ... + */ +function* vectorComponentCombinations( + components: string, + length: number, +): Generator { + if (length > 1) { + for (const str of vectorComponentCombinations(components, length - 1)) { + for (const component of components) { + yield str + component; + } + } + } else { + yield* components; + } +} + +function printSwizzlingFor(components: string) { + const componentIndex: Record = { x: 0, y: 1, z: 2, w: 3 }; + for (const count of [2, 3, 4] as const) { + const vecClassName = `_Vec${count}`; + for (const swizzle of vectorComponentCombinations(components, count)) { + const implementation = ` get ${swizzle}() { return new this.${vecClassName}(${[ + ...swizzle, + ] + .map((s) => `this[${componentIndex[s]}]`) + .join(', ')}); }`; + console.log(implementation); + } + } +} diff --git a/packages/typegpu/src/data/matrix.ts b/packages/typegpu/src/data/matrix.ts index 1ad700cc..aedf6bed 100644 --- a/packages/typegpu/src/data/matrix.ts +++ b/packages/typegpu/src/data/matrix.ts @@ -1,10 +1,11 @@ import { inGPUMode } from '../gpuMode'; import type { SelfResolvable } from '../types'; -import { type VecKind, vec2f, vec3f, vec4f } from './vector'; +import { vec2f, vec3f, vec4f } from './vector'; import type { Mat2x2f, Mat3x3f, Mat4x4f, + VecKind, m2x2f, m3x3f, m4x4f, diff --git a/packages/typegpu/src/data/vector.ts b/packages/typegpu/src/data/vector.ts index 09b80d55..b0b654d2 100644 --- a/packages/typegpu/src/data/vector.ts +++ b/packages/typegpu/src/data/vector.ts @@ -1,5 +1,19 @@ import { inGPUMode } from '../gpuMode'; -import type { SelfResolvable } from '../types'; +import { + Vec2fImpl, + Vec2hImpl, + Vec2iImpl, + Vec2uImpl, + Vec3fImpl, + Vec3hImpl, + Vec3iImpl, + Vec3uImpl, + Vec4fImpl, + Vec4hImpl, + Vec4iImpl, + Vec4uImpl, + type VecBase, +} from './vectorImpl'; import type { Vec2f, Vec2h, @@ -13,496 +27,45 @@ import type { Vec4h, Vec4i, Vec4u, - v2f, - v2h, - v2i, - v2u, - v3f, - v3h, - v3i, - v3u, - v4f, - v4h, - v4i, - v4u, } from './wgslTypes'; // -------------- // Implementation // -------------- -interface VecSchemaOptions { - type: TType; - length: number; - make: (...args: number[]) => TValue; - makeFromScalar: (value: number) => TValue; -} - type VecSchemaBase = { readonly type: string; readonly '~repr': TValue; }; -function makeVecSchema( - options: VecSchemaOptions, +function makeVecSchema( + VecImpl: new (...args: number[]) => VecBase, ): VecSchemaBase & ((...args: number[]) => TValue) { - const VecSchema: VecSchemaBase = { - /** Type-token, not available at runtime */ - '~repr': undefined as unknown as TValue, - type: options.type, - }; + const { kind: type, length: componentCount } = new VecImpl(); const construct = (...args: number[]): TValue => { const values = args; // TODO: Allow users to pass in vectors that fill part of the values. if (inGPUMode()) { - return `${VecSchema.type}(${values.join(', ')})` as unknown as TValue; - } - - if (values.length <= 1) { - return options.makeFromScalar(values[0] ?? 0); + return `${type}(${values.join(', ')})` as unknown as TValue; } - if (values.length === options.length) { - return options.make(...values); + if (values.length <= 1 || values.length === componentCount) { + return new VecImpl(...values) as TValue; } throw new Error( - `'${options.type}' constructor called with invalid number of arguments.`, + `'${type}' constructor called with invalid number of arguments.`, ); }; - return Object.assign(construct, VecSchema); -} - -abstract class vec2Impl implements SelfResolvable { - public readonly length = 2; - abstract readonly kind: `vec2${'f' | 'u' | 'i' | 'h'}`; - - [n: number]: number; - - constructor( - public x: number, - public y: number, - ) {} - - *[Symbol.iterator]() { - yield this.x; - yield this.y; - } - - get [0]() { - return this.x; - } - - get [1]() { - return this.y; - } - - set [0](value: number) { - this.x = value; - } - - set [1](value: number) { - this.y = value; - } - - '~resolve'(): string { - return `${this.kind}(${this.x}, ${this.y})`; - } - - toString() { - return this['~resolve'](); - } -} - -class vec2fImpl extends vec2Impl { - readonly kind = 'vec2f'; - - make2(x: number, y: number): v2f { - return new vec2fImpl(x, y) as unknown as v2f; - } - - make3(x: number, y: number, z: number): v3f { - return new vec3fImpl(x, y, z) as unknown as v3f; - } - - make4(x: number, y: number, z: number, w: number): v4f { - return new vec4fImpl(x, y, z, w) as unknown as v4f; - } -} - -class vec2hImpl extends vec2Impl { - readonly kind = 'vec2h'; - - make2(x: number, y: number): v2h { - return new vec2hImpl(x, y) as unknown as v2h; - } - - make3(x: number, y: number, z: number): v3h { - return new vec3hImpl(x, y, z) as unknown as v3h; - } - - make4(x: number, y: number, z: number, w: number): v4h { - return new vec4hImpl(x, y, z, w) as unknown as v4h; - } -} - -class vec2iImpl extends vec2Impl { - readonly kind = 'vec2i'; - - make2(x: number, y: number): v2i { - return new vec2iImpl(x, y) as unknown as v2i; - } - - make3(x: number, y: number, z: number): v3i { - return new vec3iImpl(x, y, z) as unknown as v3i; - } - - make4(x: number, y: number, z: number, w: number): v4i { - return new vec4iImpl(x, y, z, w) as unknown as v4i; - } -} - -class vec2uImpl extends vec2Impl { - readonly kind = 'vec2u'; - - make2(x: number, y: number): v2u { - return new vec2uImpl(x, y) as unknown as v2u; - } - - make3(x: number, y: number, z: number): v3u { - return new vec3uImpl(x, y, z) as unknown as v3u; - } - - make4(x: number, y: number, z: number, w: number): v4u { - return new vec4uImpl(x, y, z, w) as unknown as v4u; - } -} - -abstract class vec3Impl implements SelfResolvable { - public readonly length = 3; - abstract readonly kind: `vec3${'f' | 'u' | 'i' | 'h'}`; - [n: number]: number; - - constructor( - public x: number, - public y: number, - public z: number, - ) {} - - *[Symbol.iterator]() { - yield this.x; - yield this.y; - yield this.z; - } - - get [0]() { - return this.x; - } - - get [1]() { - return this.y; - } - - get [2]() { - return this.z; - } - - set [0](value: number) { - this.x = value; - } - - set [1](value: number) { - this.y = value; - } - - set [2](value: number) { - this.z = value; - } - - '~resolve'(): string { - return `${this.kind}(${this.x}, ${this.y}, ${this.z})`; - } - - toString() { - return this['~resolve'](); - } -} - -class vec3fImpl extends vec3Impl { - readonly kind = 'vec3f'; - - make2(x: number, y: number): v2f { - return new vec2fImpl(x, y) as unknown as v2f; - } - - make3(x: number, y: number, z: number): v3f { - return new vec3fImpl(x, y, z) as unknown as v3f; - } - - make4(x: number, y: number, z: number, w: number): v4f { - return new vec4fImpl(x, y, z, w) as unknown as v4f; - } -} - -class vec3hImpl extends vec3Impl { - readonly kind = 'vec3h'; - - make2(x: number, y: number): v2h { - return new vec2hImpl(x, y) as unknown as v2h; - } - - make3(x: number, y: number, z: number): v3h { - return new vec3hImpl(x, y, z) as unknown as v3h; - } - - make4(x: number, y: number, z: number, w: number): v4h { - return new vec4hImpl(x, y, z, w) as unknown as v4h; - } -} - -class vec3iImpl extends vec3Impl { - readonly kind = 'vec3i'; - - make2(x: number, y: number): v2i { - return new vec2iImpl(x, y) as unknown as v2i; - } - - make3(x: number, y: number, z: number): v3i { - return new vec3iImpl(x, y, z) as unknown as v3i; - } - - make4(x: number, y: number, z: number, w: number): v4i { - return new vec4iImpl(x, y, z, w) as unknown as v4i; - } -} - -class vec3uImpl extends vec3Impl { - readonly kind = 'vec3u'; - - make2(x: number, y: number): v2u { - return new vec2uImpl(x, y) as unknown as v2u; - } - - make3(x: number, y: number, z: number): v3u { - return new vec3uImpl(x, y, z) as unknown as v3u; - } - - make4(x: number, y: number, z: number, w: number): v4u { - return new vec4uImpl(x, y, z, w) as unknown as v4u; - } -} - -abstract class vec4Impl implements SelfResolvable { - public readonly length = 4; - abstract readonly kind: `vec4${'f' | 'u' | 'i' | 'h'}`; - [n: number]: number; - - constructor( - public x: number, - public y: number, - public z: number, - public w: number, - ) {} - - *[Symbol.iterator]() { - yield this.x; - yield this.y; - yield this.z; - yield this.w; - } - - get [0]() { - return this.x; - } - - get [1]() { - return this.y; - } - - get [2]() { - return this.z; - } - - get [3]() { - return this.w; - } - - set [0](value: number) { - this.x = value; - } - - set [1](value: number) { - this.y = value; - } - - set [2](value: number) { - this.z = value; - } - - set [3](value: number) { - this.w = value; - } - - '~resolve'(): string { - return `${this.kind}(${this.x}, ${this.y}, ${this.z}, ${this.w})`; - } - - toString() { - return this['~resolve'](); - } + return Object.assign(construct, { type, '~repr': undefined as TValue }); } -class vec4fImpl extends vec4Impl { - readonly kind = 'vec4f'; - - make2(x: number, y: number): v2f { - return new vec2fImpl(x, y) as unknown as v2f; - } - - make3(x: number, y: number, z: number): v3f { - return new vec3fImpl(x, y, z) as unknown as v3f; - } - - make4(x: number, y: number, z: number, w: number): v4f { - return new vec4fImpl(x, y, z, w) as unknown as v4f; - } -} - -class vec4hImpl extends vec4Impl { - readonly kind = 'vec4h'; - - make2(x: number, y: number): v2h { - return new vec2hImpl(x, y) as unknown as v2h; - } - - make3(x: number, y: number, z: number): v3h { - return new vec3hImpl(x, y, z) as unknown as v3h; - } - - make4(x: number, y: number, z: number, w: number): v4h { - return new vec4hImpl(x, y, z, w) as unknown as v4h; - } -} - -class vec4iImpl extends vec4Impl { - readonly kind = 'vec4i'; - - make2(x: number, y: number): v2i { - return new vec2iImpl(x, y) as unknown as v2i; - } - - make3(x: number, y: number, z: number): v3i { - return new vec3iImpl(x, y, z) as unknown as v3i; - } - - make4(x: number, y: number, z: number, w: number): v4i { - return new vec4iImpl(x, y, z, w) as unknown as v4i; - } -} - -class vec4uImpl extends vec4Impl { - readonly kind = 'vec4u'; - - make2(x: number, y: number): v2u { - return new vec2uImpl(x, y) as unknown as v2u; - } - - make3(x: number, y: number, z: number): v3u { - return new vec3uImpl(x, y, z) as unknown as v3u; - } - - make4(x: number, y: number, z: number, w: number): v4u { - return new vec4uImpl(x, y, z, w) as unknown as v4u; - } -} - -const vecProxyHandler: ProxyHandler<{ kind: VecKind }> = { - get: (target, prop) => { - if (typeof prop === 'symbol' || !Number.isNaN(Number.parseInt(prop))) { - return Reflect.get(target, prop); - } - - const targetAsVec4 = target as unknown as vec4uImpl; - const values = new Array(prop.length) as number[]; - - let idx = 0; - for (const char of prop as string) { - switch (char) { - case 'x': - values[idx] = targetAsVec4.x; - break; - case 'y': - values[idx] = targetAsVec4.y; - break; - case 'z': - values[idx] = targetAsVec4.z; - break; - case 'w': - values[idx] = targetAsVec4.w; - break; - default: - return Reflect.get(targetAsVec4, prop); - } - idx++; - } - - if (prop.length === 4) { - return new Proxy( - targetAsVec4.make4( - values[0] as number, - values[1] as number, - values[2] as number, - values[3] as number, - ), - vecProxyHandler, - ); - } - - if (prop.length === 3) { - return new Proxy( - targetAsVec4.make3( - values[0] as number, - values[1] as number, - values[2] as number, - ), - vecProxyHandler, - ); - } - - if (prop.length === 2) { - return new Proxy( - targetAsVec4.make2(values[0] as number, values[1] as number), - vecProxyHandler, - ); - } - - return Reflect.get(target, prop); - }, -}; - // ---------- // Public API // ---------- -/** - * Type encompassing all available kinds of vector. - */ -export type VecKind = - | 'vec2f' - | 'vec2i' - | 'vec2u' - | 'vec2h' - | 'vec3f' - | 'vec3i' - | 'vec3u' - | 'vec3h' - | 'vec4f' - | 'vec4i' - | 'vec4u' - | 'vec4h'; - /** * * Schema representing vec2f - a vector with 2 elements of type f32. @@ -516,13 +79,7 @@ export type VecKind = * @example * const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1); */ -export const vec2f = makeVecSchema({ - type: 'vec2f', - length: 2, - make: (x: number, y: number) => - new Proxy(new vec2fImpl(x, y), vecProxyHandler) as v2f, - makeFromScalar: (x) => new Proxy(new vec2fImpl(x, x), vecProxyHandler) as v2f, -}) as Vec2f; +export const vec2f = makeVecSchema(Vec2fImpl) as Vec2f; /** * @@ -537,13 +94,7 @@ export const vec2f = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec2h, d.vec2h(0, 1)); // buffer holding a d.vec2h value, with an initial value of vec2h(0, 1); */ -export const vec2h = makeVecSchema({ - type: 'vec2h', - length: 2, - make: (x: number, y: number) => - new Proxy(new vec2hImpl(x, y), vecProxyHandler) as v2h, - makeFromScalar: (x) => new Proxy(new vec2hImpl(x, x), vecProxyHandler) as v2h, -}) as Vec2h; +export const vec2h = makeVecSchema(Vec2hImpl) as Vec2h; /** * @@ -558,13 +109,7 @@ export const vec2h = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec2i, d.vec2i(0, 1)); // buffer holding a d.vec2i value, with an initial value of vec2i(0, 1); */ -export const vec2i = makeVecSchema({ - type: 'vec2i', - length: 2, - make: (x: number, y: number) => - new Proxy(new vec2iImpl(x, y), vecProxyHandler) as v2i, - makeFromScalar: (x) => new Proxy(new vec2iImpl(x, x), vecProxyHandler) as v2i, -}) as Vec2i; +export const vec2i = makeVecSchema(Vec2iImpl) as Vec2i; /** * @@ -579,13 +124,7 @@ export const vec2i = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec2u, d.vec2u(0, 1)); // buffer holding a d.vec2u value, with an initial value of vec2u(0, 1); */ -export const vec2u = makeVecSchema({ - type: 'vec2u', - length: 2, - make: (x: number, y: number) => - new Proxy(new vec2uImpl(x, y), vecProxyHandler) as v2u, - makeFromScalar: (x) => new Proxy(new vec2uImpl(x, x), vecProxyHandler) as v2u, -}) as Vec2u; +export const vec2u = makeVecSchema(Vec2uImpl) as Vec2u; /** * @@ -600,13 +139,7 @@ export const vec2u = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2); */ -export const vec3f = makeVecSchema({ - type: 'vec3f', - length: 3, - make: (x, y, z) => new Proxy(new vec3fImpl(x, y, z), vecProxyHandler) as v3f, - makeFromScalar: (x) => - new Proxy(new vec3fImpl(x, x, x), vecProxyHandler) as v3f, -}) as Vec3f; +export const vec3f = makeVecSchema(Vec3fImpl) as Vec3f; /** * @@ -621,13 +154,7 @@ export const vec3f = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec3h, d.vec3h(0, 1, 2)); // buffer holding a d.vec3h value, with an initial value of vec3h(0, 1, 2); */ -export const vec3h = makeVecSchema({ - type: 'vec3h', - length: 3, - make: (x, y, z) => new Proxy(new vec3hImpl(x, y, z), vecProxyHandler) as v3h, - makeFromScalar: (x) => - new Proxy(new vec3hImpl(x, x, x), vecProxyHandler) as v3h, -}) as Vec3h; +export const vec3h = makeVecSchema(Vec3hImpl) as Vec3h; /** * @@ -642,13 +169,7 @@ export const vec3h = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec3i, d.vec3i(0, 1, 2)); // buffer holding a d.vec3i value, with an initial value of vec3i(0, 1, 2); */ -export const vec3i = makeVecSchema({ - type: 'vec3i', - length: 3, - make: (x, y, z) => new Proxy(new vec3iImpl(x, y, z), vecProxyHandler) as v3i, - makeFromScalar: (x) => - new Proxy(new vec3iImpl(x, x, x), vecProxyHandler) as v3i, -}) as Vec3i; +export const vec3i = makeVecSchema(Vec3iImpl) as Vec3i; /** * @@ -663,13 +184,7 @@ export const vec3i = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec3u, d.vec3u(0, 1, 2)); // buffer holding a d.vec3u value, with an initial value of vec3u(0, 1, 2); */ -export const vec3u = makeVecSchema({ - type: 'vec3u', - length: 3, - make: (x, y, z) => new Proxy(new vec3uImpl(x, y, z), vecProxyHandler) as v3u, - makeFromScalar: (x) => - new Proxy(new vec3uImpl(x, x, x), vecProxyHandler) as v3u, -}) as Vec3u; +export const vec3u = makeVecSchema(Vec3uImpl) as Vec3u; /** * @@ -684,14 +199,7 @@ export const vec3u = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec4f, d.vec4f(0, 1, 2, 3)); // buffer holding a d.vec4f value, with an initial value of vec4f(0, 1, 2, 3); */ -export const vec4f = makeVecSchema({ - type: 'vec4f', - length: 4, - make: (x, y, z, w) => - new Proxy(new vec4fImpl(x, y, z, w), vecProxyHandler) as v4f, - makeFromScalar: (x) => - new Proxy(new vec4fImpl(x, x, x, x), vecProxyHandler) as v4f, -}) as Vec4f; +export const vec4f = makeVecSchema(Vec4fImpl) as Vec4f; /** * @@ -706,14 +214,7 @@ export const vec4f = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec4h, d.vec4h(0, 1, 2, 3)); // buffer holding a d.vec4h value, with an initial value of vec4h(0, 1, 2, 3); */ -export const vec4h = makeVecSchema({ - type: 'vec4h', - length: 4, - make: (x, y, z, w) => - new Proxy(new vec4hImpl(x, y, z, w), vecProxyHandler) as v4h, - makeFromScalar: (x) => - new Proxy(new vec4hImpl(x, x, x, x), vecProxyHandler) as v4h, -}) as Vec4h; +export const vec4h = makeVecSchema(Vec4hImpl) as Vec4h; /** * @@ -728,14 +229,7 @@ export const vec4h = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec4i, d.vec4i(0, 1, 2, 3)); // buffer holding a d.vec4i value, with an initial value of vec4i(0, 1, 2, 3); */ -export const vec4i = makeVecSchema({ - type: 'vec4i', - length: 4, - make: (x, y, z, w) => - new Proxy(new vec4iImpl(x, y, z, w), vecProxyHandler) as v4i, - makeFromScalar: (x) => - new Proxy(new vec4iImpl(x, x, x, x), vecProxyHandler) as v4i, -}) as Vec4i; +export const vec4i = makeVecSchema(Vec4iImpl) as Vec4i; /** * @@ -750,11 +244,4 @@ export const vec4i = makeVecSchema({ * @example * const buffer = root.createBuffer(d.vec4u, d.vec4u(0, 1, 2, 3)); // buffer holding a d.vec4u value, with an initial value of vec4u(0, 1, 2, 3); */ -export const vec4u = makeVecSchema({ - length: 4, - type: 'vec4u', - make: (x, y, z, w) => - new Proxy(new vec4uImpl(x, y, z, w), vecProxyHandler) as v4u, - makeFromScalar: (x) => - new Proxy(new vec4uImpl(x, x, x, x), vecProxyHandler) as v4u, -}) as Vec4u; +export const vec4u = makeVecSchema(Vec4uImpl) as Vec4u; diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts new file mode 100644 index 00000000..39dc5f8e --- /dev/null +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -0,0 +1,681 @@ +import type { SelfResolvable } from '../types'; +import type { VecKind } from './wgslTypes'; + +// biome-ignore format: swizzles should not expand +export abstract class VecBase extends Array implements SelfResolvable { + abstract get kind(): VecKind; + + abstract get _Vec2(): new ( + x: number, + y: number, + ) => Vec2; + abstract get _Vec3(): new ( + x: number, + y: number, + z: number, + ) => Vec3; + abstract get _Vec4(): new ( + x: number, + y: number, + z: number, + w: number, + ) => Vec4; + + '~resolve'(): string { + return `${this.kind}(${this.join(', ')})`; + } + + toString() { + return this['~resolve'](); + } + + get xx() { return new this._Vec2(this[0], this[0]); } + get xy() { return new this._Vec2(this[0], this[1]); } + get xz() { return new this._Vec2(this[0], this[2]); } + get xw() { return new this._Vec2(this[0], this[3]); } + get yx() { return new this._Vec2(this[1], this[0]); } + get yy() { return new this._Vec2(this[1], this[1]); } + get yz() { return new this._Vec2(this[1], this[2]); } + get yw() { return new this._Vec2(this[1], this[3]); } + get zx() { return new this._Vec2(this[2], this[0]); } + get zy() { return new this._Vec2(this[2], this[1]); } + get zz() { return new this._Vec2(this[2], this[2]); } + get zw() { return new this._Vec2(this[2], this[3]); } + get wx() { return new this._Vec2(this[3], this[0]); } + get wy() { return new this._Vec2(this[3], this[1]); } + get wz() { return new this._Vec2(this[3], this[2]); } + get ww() { return new this._Vec2(this[3], this[3]); } + get xxx() { return new this._Vec3(this[0], this[0], this[0]); } + get xxy() { return new this._Vec3(this[0], this[0], this[1]); } + get xxz() { return new this._Vec3(this[0], this[0], this[2]); } + get xxw() { return new this._Vec3(this[0], this[0], this[3]); } + get xyx() { return new this._Vec3(this[0], this[1], this[0]); } + get xyy() { return new this._Vec3(this[0], this[1], this[1]); } + get xyz() { return new this._Vec3(this[0], this[1], this[2]); } + get xyw() { return new this._Vec3(this[0], this[1], this[3]); } + get xzx() { return new this._Vec3(this[0], this[2], this[0]); } + get xzy() { return new this._Vec3(this[0], this[2], this[1]); } + get xzz() { return new this._Vec3(this[0], this[2], this[2]); } + get xzw() { return new this._Vec3(this[0], this[2], this[3]); } + get xwx() { return new this._Vec3(this[0], this[3], this[0]); } + get xwy() { return new this._Vec3(this[0], this[3], this[1]); } + get xwz() { return new this._Vec3(this[0], this[3], this[2]); } + get xww() { return new this._Vec3(this[0], this[3], this[3]); } + get yxx() { return new this._Vec3(this[1], this[0], this[0]); } + get yxy() { return new this._Vec3(this[1], this[0], this[1]); } + get yxz() { return new this._Vec3(this[1], this[0], this[2]); } + get yxw() { return new this._Vec3(this[1], this[0], this[3]); } + get yyx() { return new this._Vec3(this[1], this[1], this[0]); } + get yyy() { return new this._Vec3(this[1], this[1], this[1]); } + get yyz() { return new this._Vec3(this[1], this[1], this[2]); } + get yyw() { return new this._Vec3(this[1], this[1], this[3]); } + get yzx() { return new this._Vec3(this[1], this[2], this[0]); } + get yzy() { return new this._Vec3(this[1], this[2], this[1]); } + get yzz() { return new this._Vec3(this[1], this[2], this[2]); } + get yzw() { return new this._Vec3(this[1], this[2], this[3]); } + get ywx() { return new this._Vec3(this[1], this[3], this[0]); } + get ywy() { return new this._Vec3(this[1], this[3], this[1]); } + get ywz() { return new this._Vec3(this[1], this[3], this[2]); } + get yww() { return new this._Vec3(this[1], this[3], this[3]); } + get zxx() { return new this._Vec3(this[2], this[0], this[0]); } + get zxy() { return new this._Vec3(this[2], this[0], this[1]); } + get zxz() { return new this._Vec3(this[2], this[0], this[2]); } + get zxw() { return new this._Vec3(this[2], this[0], this[3]); } + get zyx() { return new this._Vec3(this[2], this[1], this[0]); } + get zyy() { return new this._Vec3(this[2], this[1], this[1]); } + get zyz() { return new this._Vec3(this[2], this[1], this[2]); } + get zyw() { return new this._Vec3(this[2], this[1], this[3]); } + get zzx() { return new this._Vec3(this[2], this[2], this[0]); } + get zzy() { return new this._Vec3(this[2], this[2], this[1]); } + get zzz() { return new this._Vec3(this[2], this[2], this[2]); } + get zzw() { return new this._Vec3(this[2], this[2], this[3]); } + get zwx() { return new this._Vec3(this[2], this[3], this[0]); } + get zwy() { return new this._Vec3(this[2], this[3], this[1]); } + get zwz() { return new this._Vec3(this[2], this[3], this[2]); } + get zww() { return new this._Vec3(this[2], this[3], this[3]); } + get wxx() { return new this._Vec3(this[3], this[0], this[0]); } + get wxy() { return new this._Vec3(this[3], this[0], this[1]); } + get wxz() { return new this._Vec3(this[3], this[0], this[2]); } + get wxw() { return new this._Vec3(this[3], this[0], this[3]); } + get wyx() { return new this._Vec3(this[3], this[1], this[0]); } + get wyy() { return new this._Vec3(this[3], this[1], this[1]); } + get wyz() { return new this._Vec3(this[3], this[1], this[2]); } + get wyw() { return new this._Vec3(this[3], this[1], this[3]); } + get wzx() { return new this._Vec3(this[3], this[2], this[0]); } + get wzy() { return new this._Vec3(this[3], this[2], this[1]); } + get wzz() { return new this._Vec3(this[3], this[2], this[2]); } + get wzw() { return new this._Vec3(this[3], this[2], this[3]); } + get wwx() { return new this._Vec3(this[3], this[3], this[0]); } + get wwy() { return new this._Vec3(this[3], this[3], this[1]); } + get wwz() { return new this._Vec3(this[3], this[3], this[2]); } + get www() { return new this._Vec3(this[3], this[3], this[3]); } + get xxxx() { return new this._Vec4(this[0], this[0], this[0], this[0]); } + get xxxy() { return new this._Vec4(this[0], this[0], this[0], this[1]); } + get xxxz() { return new this._Vec4(this[0], this[0], this[0], this[2]); } + get xxxw() { return new this._Vec4(this[0], this[0], this[0], this[3]); } + get xxyx() { return new this._Vec4(this[0], this[0], this[1], this[0]); } + get xxyy() { return new this._Vec4(this[0], this[0], this[1], this[1]); } + get xxyz() { return new this._Vec4(this[0], this[0], this[1], this[2]); } + get xxyw() { return new this._Vec4(this[0], this[0], this[1], this[3]); } + get xxzx() { return new this._Vec4(this[0], this[0], this[2], this[0]); } + get xxzy() { return new this._Vec4(this[0], this[0], this[2], this[1]); } + get xxzz() { return new this._Vec4(this[0], this[0], this[2], this[2]); } + get xxzw() { return new this._Vec4(this[0], this[0], this[2], this[3]); } + get xxwx() { return new this._Vec4(this[0], this[0], this[3], this[0]); } + get xxwy() { return new this._Vec4(this[0], this[0], this[3], this[1]); } + get xxwz() { return new this._Vec4(this[0], this[0], this[3], this[2]); } + get xxww() { return new this._Vec4(this[0], this[0], this[3], this[3]); } + get xyxx() { return new this._Vec4(this[0], this[1], this[0], this[0]); } + get xyxy() { return new this._Vec4(this[0], this[1], this[0], this[1]); } + get xyxz() { return new this._Vec4(this[0], this[1], this[0], this[2]); } + get xyxw() { return new this._Vec4(this[0], this[1], this[0], this[3]); } + get xyyx() { return new this._Vec4(this[0], this[1], this[1], this[0]); } + get xyyy() { return new this._Vec4(this[0], this[1], this[1], this[1]); } + get xyyz() { return new this._Vec4(this[0], this[1], this[1], this[2]); } + get xyyw() { return new this._Vec4(this[0], this[1], this[1], this[3]); } + get xyzx() { return new this._Vec4(this[0], this[1], this[2], this[0]); } + get xyzy() { return new this._Vec4(this[0], this[1], this[2], this[1]); } + get xyzz() { return new this._Vec4(this[0], this[1], this[2], this[2]); } + get xyzw() { return new this._Vec4(this[0], this[1], this[2], this[3]); } + get xywx() { return new this._Vec4(this[0], this[1], this[3], this[0]); } + get xywy() { return new this._Vec4(this[0], this[1], this[3], this[1]); } + get xywz() { return new this._Vec4(this[0], this[1], this[3], this[2]); } + get xyww() { return new this._Vec4(this[0], this[1], this[3], this[3]); } + get xzxx() { return new this._Vec4(this[0], this[2], this[0], this[0]); } + get xzxy() { return new this._Vec4(this[0], this[2], this[0], this[1]); } + get xzxz() { return new this._Vec4(this[0], this[2], this[0], this[2]); } + get xzxw() { return new this._Vec4(this[0], this[2], this[0], this[3]); } + get xzyx() { return new this._Vec4(this[0], this[2], this[1], this[0]); } + get xzyy() { return new this._Vec4(this[0], this[2], this[1], this[1]); } + get xzyz() { return new this._Vec4(this[0], this[2], this[1], this[2]); } + get xzyw() { return new this._Vec4(this[0], this[2], this[1], this[3]); } + get xzzx() { return new this._Vec4(this[0], this[2], this[2], this[0]); } + get xzzy() { return new this._Vec4(this[0], this[2], this[2], this[1]); } + get xzzz() { return new this._Vec4(this[0], this[2], this[2], this[2]); } + get xzzw() { return new this._Vec4(this[0], this[2], this[2], this[3]); } + get xzwx() { return new this._Vec4(this[0], this[2], this[3], this[0]); } + get xzwy() { return new this._Vec4(this[0], this[2], this[3], this[1]); } + get xzwz() { return new this._Vec4(this[0], this[2], this[3], this[2]); } + get xzww() { return new this._Vec4(this[0], this[2], this[3], this[3]); } + get xwxx() { return new this._Vec4(this[0], this[3], this[0], this[0]); } + get xwxy() { return new this._Vec4(this[0], this[3], this[0], this[1]); } + get xwxz() { return new this._Vec4(this[0], this[3], this[0], this[2]); } + get xwxw() { return new this._Vec4(this[0], this[3], this[0], this[3]); } + get xwyx() { return new this._Vec4(this[0], this[3], this[1], this[0]); } + get xwyy() { return new this._Vec4(this[0], this[3], this[1], this[1]); } + get xwyz() { return new this._Vec4(this[0], this[3], this[1], this[2]); } + get xwyw() { return new this._Vec4(this[0], this[3], this[1], this[3]); } + get xwzx() { return new this._Vec4(this[0], this[3], this[2], this[0]); } + get xwzy() { return new this._Vec4(this[0], this[3], this[2], this[1]); } + get xwzz() { return new this._Vec4(this[0], this[3], this[2], this[2]); } + get xwzw() { return new this._Vec4(this[0], this[3], this[2], this[3]); } + get xwwx() { return new this._Vec4(this[0], this[3], this[3], this[0]); } + get xwwy() { return new this._Vec4(this[0], this[3], this[3], this[1]); } + get xwwz() { return new this._Vec4(this[0], this[3], this[3], this[2]); } + get xwww() { return new this._Vec4(this[0], this[3], this[3], this[3]); } + get yxxx() { return new this._Vec4(this[1], this[0], this[0], this[0]); } + get yxxy() { return new this._Vec4(this[1], this[0], this[0], this[1]); } + get yxxz() { return new this._Vec4(this[1], this[0], this[0], this[2]); } + get yxxw() { return new this._Vec4(this[1], this[0], this[0], this[3]); } + get yxyx() { return new this._Vec4(this[1], this[0], this[1], this[0]); } + get yxyy() { return new this._Vec4(this[1], this[0], this[1], this[1]); } + get yxyz() { return new this._Vec4(this[1], this[0], this[1], this[2]); } + get yxyw() { return new this._Vec4(this[1], this[0], this[1], this[3]); } + get yxzx() { return new this._Vec4(this[1], this[0], this[2], this[0]); } + get yxzy() { return new this._Vec4(this[1], this[0], this[2], this[1]); } + get yxzz() { return new this._Vec4(this[1], this[0], this[2], this[2]); } + get yxzw() { return new this._Vec4(this[1], this[0], this[2], this[3]); } + get yxwx() { return new this._Vec4(this[1], this[0], this[3], this[0]); } + get yxwy() { return new this._Vec4(this[1], this[0], this[3], this[1]); } + get yxwz() { return new this._Vec4(this[1], this[0], this[3], this[2]); } + get yxww() { return new this._Vec4(this[1], this[0], this[3], this[3]); } + get yyxx() { return new this._Vec4(this[1], this[1], this[0], this[0]); } + get yyxy() { return new this._Vec4(this[1], this[1], this[0], this[1]); } + get yyxz() { return new this._Vec4(this[1], this[1], this[0], this[2]); } + get yyxw() { return new this._Vec4(this[1], this[1], this[0], this[3]); } + get yyyx() { return new this._Vec4(this[1], this[1], this[1], this[0]); } + get yyyy() { return new this._Vec4(this[1], this[1], this[1], this[1]); } + get yyyz() { return new this._Vec4(this[1], this[1], this[1], this[2]); } + get yyyw() { return new this._Vec4(this[1], this[1], this[1], this[3]); } + get yyzx() { return new this._Vec4(this[1], this[1], this[2], this[0]); } + get yyzy() { return new this._Vec4(this[1], this[1], this[2], this[1]); } + get yyzz() { return new this._Vec4(this[1], this[1], this[2], this[2]); } + get yyzw() { return new this._Vec4(this[1], this[1], this[2], this[3]); } + get yywx() { return new this._Vec4(this[1], this[1], this[3], this[0]); } + get yywy() { return new this._Vec4(this[1], this[1], this[3], this[1]); } + get yywz() { return new this._Vec4(this[1], this[1], this[3], this[2]); } + get yyww() { return new this._Vec4(this[1], this[1], this[3], this[3]); } + get yzxx() { return new this._Vec4(this[1], this[2], this[0], this[0]); } + get yzxy() { return new this._Vec4(this[1], this[2], this[0], this[1]); } + get yzxz() { return new this._Vec4(this[1], this[2], this[0], this[2]); } + get yzxw() { return new this._Vec4(this[1], this[2], this[0], this[3]); } + get yzyx() { return new this._Vec4(this[1], this[2], this[1], this[0]); } + get yzyy() { return new this._Vec4(this[1], this[2], this[1], this[1]); } + get yzyz() { return new this._Vec4(this[1], this[2], this[1], this[2]); } + get yzyw() { return new this._Vec4(this[1], this[2], this[1], this[3]); } + get yzzx() { return new this._Vec4(this[1], this[2], this[2], this[0]); } + get yzzy() { return new this._Vec4(this[1], this[2], this[2], this[1]); } + get yzzz() { return new this._Vec4(this[1], this[2], this[2], this[2]); } + get yzzw() { return new this._Vec4(this[1], this[2], this[2], this[3]); } + get yzwx() { return new this._Vec4(this[1], this[2], this[3], this[0]); } + get yzwy() { return new this._Vec4(this[1], this[2], this[3], this[1]); } + get yzwz() { return new this._Vec4(this[1], this[2], this[3], this[2]); } + get yzww() { return new this._Vec4(this[1], this[2], this[3], this[3]); } + get ywxx() { return new this._Vec4(this[1], this[3], this[0], this[0]); } + get ywxy() { return new this._Vec4(this[1], this[3], this[0], this[1]); } + get ywxz() { return new this._Vec4(this[1], this[3], this[0], this[2]); } + get ywxw() { return new this._Vec4(this[1], this[3], this[0], this[3]); } + get ywyx() { return new this._Vec4(this[1], this[3], this[1], this[0]); } + get ywyy() { return new this._Vec4(this[1], this[3], this[1], this[1]); } + get ywyz() { return new this._Vec4(this[1], this[3], this[1], this[2]); } + get ywyw() { return new this._Vec4(this[1], this[3], this[1], this[3]); } + get ywzx() { return new this._Vec4(this[1], this[3], this[2], this[0]); } + get ywzy() { return new this._Vec4(this[1], this[3], this[2], this[1]); } + get ywzz() { return new this._Vec4(this[1], this[3], this[2], this[2]); } + get ywzw() { return new this._Vec4(this[1], this[3], this[2], this[3]); } + get ywwx() { return new this._Vec4(this[1], this[3], this[3], this[0]); } + get ywwy() { return new this._Vec4(this[1], this[3], this[3], this[1]); } + get ywwz() { return new this._Vec4(this[1], this[3], this[3], this[2]); } + get ywww() { return new this._Vec4(this[1], this[3], this[3], this[3]); } + get zxxx() { return new this._Vec4(this[2], this[0], this[0], this[0]); } + get zxxy() { return new this._Vec4(this[2], this[0], this[0], this[1]); } + get zxxz() { return new this._Vec4(this[2], this[0], this[0], this[2]); } + get zxxw() { return new this._Vec4(this[2], this[0], this[0], this[3]); } + get zxyx() { return new this._Vec4(this[2], this[0], this[1], this[0]); } + get zxyy() { return new this._Vec4(this[2], this[0], this[1], this[1]); } + get zxyz() { return new this._Vec4(this[2], this[0], this[1], this[2]); } + get zxyw() { return new this._Vec4(this[2], this[0], this[1], this[3]); } + get zxzx() { return new this._Vec4(this[2], this[0], this[2], this[0]); } + get zxzy() { return new this._Vec4(this[2], this[0], this[2], this[1]); } + get zxzz() { return new this._Vec4(this[2], this[0], this[2], this[2]); } + get zxzw() { return new this._Vec4(this[2], this[0], this[2], this[3]); } + get zxwx() { return new this._Vec4(this[2], this[0], this[3], this[0]); } + get zxwy() { return new this._Vec4(this[2], this[0], this[3], this[1]); } + get zxwz() { return new this._Vec4(this[2], this[0], this[3], this[2]); } + get zxww() { return new this._Vec4(this[2], this[0], this[3], this[3]); } + get zyxx() { return new this._Vec4(this[2], this[1], this[0], this[0]); } + get zyxy() { return new this._Vec4(this[2], this[1], this[0], this[1]); } + get zyxz() { return new this._Vec4(this[2], this[1], this[0], this[2]); } + get zyxw() { return new this._Vec4(this[2], this[1], this[0], this[3]); } + get zyyx() { return new this._Vec4(this[2], this[1], this[1], this[0]); } + get zyyy() { return new this._Vec4(this[2], this[1], this[1], this[1]); } + get zyyz() { return new this._Vec4(this[2], this[1], this[1], this[2]); } + get zyyw() { return new this._Vec4(this[2], this[1], this[1], this[3]); } + get zyzx() { return new this._Vec4(this[2], this[1], this[2], this[0]); } + get zyzy() { return new this._Vec4(this[2], this[1], this[2], this[1]); } + get zyzz() { return new this._Vec4(this[2], this[1], this[2], this[2]); } + get zyzw() { return new this._Vec4(this[2], this[1], this[2], this[3]); } + get zywx() { return new this._Vec4(this[2], this[1], this[3], this[0]); } + get zywy() { return new this._Vec4(this[2], this[1], this[3], this[1]); } + get zywz() { return new this._Vec4(this[2], this[1], this[3], this[2]); } + get zyww() { return new this._Vec4(this[2], this[1], this[3], this[3]); } + get zzxx() { return new this._Vec4(this[2], this[2], this[0], this[0]); } + get zzxy() { return new this._Vec4(this[2], this[2], this[0], this[1]); } + get zzxz() { return new this._Vec4(this[2], this[2], this[0], this[2]); } + get zzxw() { return new this._Vec4(this[2], this[2], this[0], this[3]); } + get zzyx() { return new this._Vec4(this[2], this[2], this[1], this[0]); } + get zzyy() { return new this._Vec4(this[2], this[2], this[1], this[1]); } + get zzyz() { return new this._Vec4(this[2], this[2], this[1], this[2]); } + get zzyw() { return new this._Vec4(this[2], this[2], this[1], this[3]); } + get zzzx() { return new this._Vec4(this[2], this[2], this[2], this[0]); } + get zzzy() { return new this._Vec4(this[2], this[2], this[2], this[1]); } + get zzzz() { return new this._Vec4(this[2], this[2], this[2], this[2]); } + get zzzw() { return new this._Vec4(this[2], this[2], this[2], this[3]); } + get zzwx() { return new this._Vec4(this[2], this[2], this[3], this[0]); } + get zzwy() { return new this._Vec4(this[2], this[2], this[3], this[1]); } + get zzwz() { return new this._Vec4(this[2], this[2], this[3], this[2]); } + get zzww() { return new this._Vec4(this[2], this[2], this[3], this[3]); } + get zwxx() { return new this._Vec4(this[2], this[3], this[0], this[0]); } + get zwxy() { return new this._Vec4(this[2], this[3], this[0], this[1]); } + get zwxz() { return new this._Vec4(this[2], this[3], this[0], this[2]); } + get zwxw() { return new this._Vec4(this[2], this[3], this[0], this[3]); } + get zwyx() { return new this._Vec4(this[2], this[3], this[1], this[0]); } + get zwyy() { return new this._Vec4(this[2], this[3], this[1], this[1]); } + get zwyz() { return new this._Vec4(this[2], this[3], this[1], this[2]); } + get zwyw() { return new this._Vec4(this[2], this[3], this[1], this[3]); } + get zwzx() { return new this._Vec4(this[2], this[3], this[2], this[0]); } + get zwzy() { return new this._Vec4(this[2], this[3], this[2], this[1]); } + get zwzz() { return new this._Vec4(this[2], this[3], this[2], this[2]); } + get zwzw() { return new this._Vec4(this[2], this[3], this[2], this[3]); } + get zwwx() { return new this._Vec4(this[2], this[3], this[3], this[0]); } + get zwwy() { return new this._Vec4(this[2], this[3], this[3], this[1]); } + get zwwz() { return new this._Vec4(this[2], this[3], this[3], this[2]); } + get zwww() { return new this._Vec4(this[2], this[3], this[3], this[3]); } + get wxxx() { return new this._Vec4(this[3], this[0], this[0], this[0]); } + get wxxy() { return new this._Vec4(this[3], this[0], this[0], this[1]); } + get wxxz() { return new this._Vec4(this[3], this[0], this[0], this[2]); } + get wxxw() { return new this._Vec4(this[3], this[0], this[0], this[3]); } + get wxyx() { return new this._Vec4(this[3], this[0], this[1], this[0]); } + get wxyy() { return new this._Vec4(this[3], this[0], this[1], this[1]); } + get wxyz() { return new this._Vec4(this[3], this[0], this[1], this[2]); } + get wxyw() { return new this._Vec4(this[3], this[0], this[1], this[3]); } + get wxzx() { return new this._Vec4(this[3], this[0], this[2], this[0]); } + get wxzy() { return new this._Vec4(this[3], this[0], this[2], this[1]); } + get wxzz() { return new this._Vec4(this[3], this[0], this[2], this[2]); } + get wxzw() { return new this._Vec4(this[3], this[0], this[2], this[3]); } + get wxwx() { return new this._Vec4(this[3], this[0], this[3], this[0]); } + get wxwy() { return new this._Vec4(this[3], this[0], this[3], this[1]); } + get wxwz() { return new this._Vec4(this[3], this[0], this[3], this[2]); } + get wxww() { return new this._Vec4(this[3], this[0], this[3], this[3]); } + get wyxx() { return new this._Vec4(this[3], this[1], this[0], this[0]); } + get wyxy() { return new this._Vec4(this[3], this[1], this[0], this[1]); } + get wyxz() { return new this._Vec4(this[3], this[1], this[0], this[2]); } + get wyxw() { return new this._Vec4(this[3], this[1], this[0], this[3]); } + get wyyx() { return new this._Vec4(this[3], this[1], this[1], this[0]); } + get wyyy() { return new this._Vec4(this[3], this[1], this[1], this[1]); } + get wyyz() { return new this._Vec4(this[3], this[1], this[1], this[2]); } + get wyyw() { return new this._Vec4(this[3], this[1], this[1], this[3]); } + get wyzx() { return new this._Vec4(this[3], this[1], this[2], this[0]); } + get wyzy() { return new this._Vec4(this[3], this[1], this[2], this[1]); } + get wyzz() { return new this._Vec4(this[3], this[1], this[2], this[2]); } + get wyzw() { return new this._Vec4(this[3], this[1], this[2], this[3]); } + get wywx() { return new this._Vec4(this[3], this[1], this[3], this[0]); } + get wywy() { return new this._Vec4(this[3], this[1], this[3], this[1]); } + get wywz() { return new this._Vec4(this[3], this[1], this[3], this[2]); } + get wyww() { return new this._Vec4(this[3], this[1], this[3], this[3]); } + get wzxx() { return new this._Vec4(this[3], this[2], this[0], this[0]); } + get wzxy() { return new this._Vec4(this[3], this[2], this[0], this[1]); } + get wzxz() { return new this._Vec4(this[3], this[2], this[0], this[2]); } + get wzxw() { return new this._Vec4(this[3], this[2], this[0], this[3]); } + get wzyx() { return new this._Vec4(this[3], this[2], this[1], this[0]); } + get wzyy() { return new this._Vec4(this[3], this[2], this[1], this[1]); } + get wzyz() { return new this._Vec4(this[3], this[2], this[1], this[2]); } + get wzyw() { return new this._Vec4(this[3], this[2], this[1], this[3]); } + get wzzx() { return new this._Vec4(this[3], this[2], this[2], this[0]); } + get wzzy() { return new this._Vec4(this[3], this[2], this[2], this[1]); } + get wzzz() { return new this._Vec4(this[3], this[2], this[2], this[2]); } + get wzzw() { return new this._Vec4(this[3], this[2], this[2], this[3]); } + get wzwx() { return new this._Vec4(this[3], this[2], this[3], this[0]); } + get wzwy() { return new this._Vec4(this[3], this[2], this[3], this[1]); } + get wzwz() { return new this._Vec4(this[3], this[2], this[3], this[2]); } + get wzww() { return new this._Vec4(this[3], this[2], this[3], this[3]); } + get wwxx() { return new this._Vec4(this[3], this[3], this[0], this[0]); } + get wwxy() { return new this._Vec4(this[3], this[3], this[0], this[1]); } + get wwxz() { return new this._Vec4(this[3], this[3], this[0], this[2]); } + get wwxw() { return new this._Vec4(this[3], this[3], this[0], this[3]); } + get wwyx() { return new this._Vec4(this[3], this[3], this[1], this[0]); } + get wwyy() { return new this._Vec4(this[3], this[3], this[1], this[1]); } + get wwyz() { return new this._Vec4(this[3], this[3], this[1], this[2]); } + get wwyw() { return new this._Vec4(this[3], this[3], this[1], this[3]); } + get wwzx() { return new this._Vec4(this[3], this[3], this[2], this[0]); } + get wwzy() { return new this._Vec4(this[3], this[3], this[2], this[1]); } + get wwzz() { return new this._Vec4(this[3], this[3], this[2], this[2]); } + get wwzw() { return new this._Vec4(this[3], this[3], this[2], this[3]); } + get wwwx() { return new this._Vec4(this[3], this[3], this[3], this[0]); } + get wwwy() { return new this._Vec4(this[3], this[3], this[3], this[1]); } + get wwwz() { return new this._Vec4(this[3], this[3], this[3], this[2]); } + get wwww() { return new this._Vec4(this[3], this[3], this[3], this[3]); } +} + +type Tuple2 = [number, number]; +type Tuple3 = [number, number, number]; +type Tuple4 = [number, number, number, number]; + +abstract class Vec2 extends VecBase implements Tuple2 { + declare readonly length = 2; + + 0: number; + 1: number; + + constructor(x?: number, y?: number) { + super(2); + this[0] = x ?? 0; + this[1] = y ?? x ?? 0; + } + + get x() { + return this[0]; + } + + get y() { + return this[1]; + } + + set x(value: number) { + this[0] = value; + } + + set y(value: number) { + this[1] = value; + } +} + +abstract class Vec3 extends VecBase implements Tuple3 { + declare readonly length = 3; + + 0: number; + 1: number; + 2: number; + + constructor(x?: number, y?: number, z?: number) { + super(3); + this[0] = x ?? 0; + this[1] = y ?? x ?? 0; + this[2] = z ?? x ?? 0; + } + + get x() { + return this[0]; + } + + get y() { + return this[1]; + } + + get z() { + return this[2]; + } + + set x(value: number) { + this[0] = value; + } + + set y(value: number) { + this[1] = value; + } + + set z(value: number) { + this[2] = value; + } +} + +abstract class Vec4 extends VecBase implements Tuple4 { + declare readonly length = 4; + + 0: number; + 1: number; + 2: number; + 3: number; + + constructor(x?: number, y?: number, z?: number, w?: number) { + super(4); + this[0] = x ?? 0; + this[1] = y ?? x ?? 0; + this[2] = z ?? x ?? 0; + this[3] = w ?? x ?? 0; + } + + get x() { + return this[0]; + } + + get y() { + return this[1]; + } + + get z() { + return this[2]; + } + + get w() { + return this[3]; + } + + set x(value: number) { + this[0] = value; + } + + set y(value: number) { + this[1] = value; + } + + set z(value: number) { + this[2] = value; + } + + set w(value: number) { + this[3] = value; + } +} + +export class Vec2fImpl extends Vec2 { + get kind() { + return 'vec2f' as const; + } + + get _Vec2() { + return Vec2fImpl; + } + get _Vec3() { + return Vec3fImpl; + } + get _Vec4() { + return Vec4fImpl; + } +} + +export class Vec2hImpl extends Vec2 { + get kind() { + return 'vec2h' as const; + } + + get _Vec2() { + return Vec2hImpl; + } + get _Vec3() { + return Vec3hImpl; + } + get _Vec4() { + return Vec4hImpl; + } +} + +export class Vec2iImpl extends Vec2 { + get kind() { + return 'vec2i' as const; + } + + get _Vec2() { + return Vec2iImpl; + } + get _Vec3() { + return Vec3iImpl; + } + get _Vec4() { + return Vec4iImpl; + } +} + +export class Vec2uImpl extends Vec2 { + get kind() { + return 'vec2u' as const; + } + + get _Vec2() { + return Vec2uImpl; + } + get _Vec3() { + return Vec3uImpl; + } + get _Vec4() { + return Vec4uImpl; + } +} + +export class Vec3fImpl extends Vec3 { + get kind() { + return 'vec3f' as const; + } + + get _Vec2() { + return Vec2fImpl; + } + get _Vec3() { + return Vec3fImpl; + } + get _Vec4() { + return Vec4fImpl; + } +} + +export class Vec3hImpl extends Vec3 { + get kind() { + return 'vec3h' as const; + } + + get _Vec2() { + return Vec2hImpl; + } + get _Vec3() { + return Vec3hImpl; + } + get _Vec4() { + return Vec4hImpl; + } +} + +export class Vec3iImpl extends Vec3 { + get kind() { + return 'vec3i' as const; + } + + get _Vec2() { + return Vec2iImpl; + } + get _Vec3() { + return Vec3iImpl; + } + get _Vec4() { + return Vec4iImpl; + } +} + +export class Vec3uImpl extends Vec3 { + get kind() { + return 'vec3u' as const; + } + + get _Vec2() { + return Vec2uImpl; + } + get _Vec3() { + return Vec3uImpl; + } + get _Vec4() { + return Vec4uImpl; + } +} + +export class Vec4fImpl extends Vec4 { + get kind() { + return 'vec4f' as const; + } + + get _Vec2() { + return Vec2fImpl; + } + get _Vec3() { + return Vec3fImpl; + } + get _Vec4() { + return Vec4fImpl; + } +} + +export class Vec4hImpl extends Vec4 { + get kind() { + return 'vec4h' as const; + } + + get _Vec2() { + return Vec2hImpl; + } + get _Vec3() { + return Vec3hImpl; + } + get _Vec4() { + return Vec4hImpl; + } +} + +export class Vec4iImpl extends Vec4 { + get kind() { + return 'vec4i' as const; + } + + get _Vec2() { + return Vec2iImpl; + } + get _Vec3() { + return Vec3iImpl; + } + get _Vec4() { + return Vec4iImpl; + } +} + +export class Vec4uImpl extends Vec4 { + get kind() { + return 'vec4u' as const; + } + + get _Vec2() { + return Vec2uImpl; + } + get _Vec3() { + return Vec3uImpl; + } + get _Vec4() { + return Vec4uImpl; + } +} diff --git a/packages/typegpu/src/data/vectorOps.ts b/packages/typegpu/src/data/vectorOps.ts index f4d154b9..df385846 100644 --- a/packages/typegpu/src/data/vectorOps.ts +++ b/packages/typegpu/src/data/vectorOps.ts @@ -1,6 +1,5 @@ import { mat2x2f, mat3x3f, mat4x4f } from './matrix'; import { - type VecKind, vec2f, vec2h, vec2i, @@ -15,6 +14,7 @@ import { vec4u, } from './vector'; import type * as wgsl from './wgslTypes'; +import type { VecKind } from './wgslTypes'; type vBase = { kind: VecKind }; type v2 = wgsl.v2f | wgsl.v2h | wgsl.v2i | wgsl.v2u; diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 8dc335fa..397eff92 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -488,6 +488,8 @@ export type AnyVecInstance = | v4i | v4u; +export type VecKind = AnyVecInstance['kind']; + export interface matBase extends NumberArrayView { readonly columns: readonly TColumn[]; } diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index 6eca855e..57806214 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -1,7 +1,7 @@ -import type { VecKind } from '../data/vector'; import { VectorOps } from '../data/vectorOps'; import type { AnyMatInstance, + VecKind, v2f, v2h, v3f, From 448f91125767c5d257b896891f7087061f73930b Mon Sep 17 00:00:00 2001 From: reczkok Date: Mon, 3 Feb 2025 14:04:01 +0100 Subject: [PATCH 5/7] initial draft of compiled IO --- packages/typegpu/src/core/buffer/buffer.ts | 16 +- packages/typegpu/src/data/compiledIO.ts | 199 +++++++++++++++ packages/typegpu/src/data/partialIO.ts | 7 +- packages/typegpu/src/data/wgslTypes.ts | 30 +++ packages/typegpu/tests/compiledIO.test.ts | 275 +++++++++++++++++++++ 5 files changed, 524 insertions(+), 3 deletions(-) create mode 100644 packages/typegpu/src/data/compiledIO.ts create mode 100644 packages/typegpu/tests/compiledIO.test.ts diff --git a/packages/typegpu/src/core/buffer/buffer.ts b/packages/typegpu/src/core/buffer/buffer.ts index d6dad1ad..97728a3d 100644 --- a/packages/typegpu/src/core/buffer/buffer.ts +++ b/packages/typegpu/src/core/buffer/buffer.ts @@ -1,5 +1,9 @@ import { BufferReader, BufferWriter } from 'typed-binary'; import { isWgslData } from '../../data'; +import { + EVAL_ALLOWED_IN_ENV, + getCompiledWriterForSchema, +} from '../../data/compiledIO'; import { readData, writeData } from '../../data/dataIO'; import type { AnyData } from '../../data/dataTypes'; import { getWriteInstructions } from '../../data/partialIO'; @@ -241,6 +245,11 @@ class TgpuBufferImpl implements TgpuBuffer { if (gpuBuffer.mapState === 'mapped') { const mapped = gpuBuffer.getMappedRange(); + if (EVAL_ALLOWED_IN_ENV) { + const writer = getCompiledWriterForSchema(this.dataType); + writer(new DataView(mapped), 0, data); + return; + } writeData(new BufferWriter(mapped), this.dataType, data); return; } @@ -251,7 +260,12 @@ class TgpuBufferImpl implements TgpuBuffer { this._group.flush(); const hostBuffer = new ArrayBuffer(size); - writeData(new BufferWriter(hostBuffer), this.dataType, data); + if (EVAL_ALLOWED_IN_ENV) { + const writer = getCompiledWriterForSchema(this.dataType); + writer(new DataView(hostBuffer), 0, data); + } else { + writeData(new BufferWriter(hostBuffer), this.dataType, data); + } device.queue.writeBuffer(gpuBuffer, 0, hostBuffer, 0, size); } diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts new file mode 100644 index 00000000..f7a67680 --- /dev/null +++ b/packages/typegpu/src/data/compiledIO.ts @@ -0,0 +1,199 @@ +import { roundUp } from '../mathUtils'; +import type { Infer } from '../shared/repr'; +import { alignmentOf } from './alignmentOf'; +import { isDisarray, isUnstruct } from './dataTypes'; +import { offsetsForProps } from './offests'; +import { sizeOf } from './sizeOf'; +import { + isVec, + isVec2, + isVec3, + isVec4, + isWgslArray, + isWgslStruct, +} from './wgslTypes'; +import type * as wgsl from './wgslTypes'; + +export let EVAL_ALLOWED_IN_ENV: boolean; + +try { + new Function('return true'); + EVAL_ALLOWED_IN_ENV = true; +} catch { + EVAL_ALLOWED_IN_ENV = false; +} + +export interface CompiledWriteInstructions { + primitive: 'u32' | 'i32' | 'f32'; + offset: number; + path: string[]; +} + +const typeToPrimitive = { + u32: 'u32', + vec2u: 'u32', + vec3u: 'u32', + vec4u: 'u32', + + i32: 'i32', + vec2i: 'i32', + vec3i: 'i32', + vec4i: 'i32', + + f32: 'f32', + vec2f: 'f32', + vec3f: 'f32', + vec4f: 'f32', + + vec2h: 'f32', + vec3h: 'f32', + vec4h: 'f32', +} as const; + +const primitiveToWriteFunction = { + u32: 'setUint32', + i32: 'setInt32', + f32: 'setFloat32', +} as const; + +export function createCompileInstructions( + schema: TData, +) { + const segments: CompiledWriteInstructions[] = []; + + function gather( + node: T, + offset: number, + path: string[], + ) { + if (isWgslStruct(node) || isUnstruct(node)) { + const propOffsets = offsetsForProps(node); + + const sortedProps = Object.entries(propOffsets).sort( + (a, b) => a[1].offset - b[1].offset, // Sort by offset + ); + + for (const [key, propOffset] of sortedProps) { + const subSchema = node.propTypes[key]; + if (!subSchema) continue; + + gather(subSchema, offset + propOffset.offset, [...path, key]); + } + return; + } + + if (isWgslArray(node) || isDisarray(node)) { + const arrSchema = node as wgsl.WgslArray; + const elementSize = roundUp( + sizeOf(arrSchema.elementType), + alignmentOf(arrSchema.elementType), + ); + + for (let i = 0; i < arrSchema.elementCount; i++) { + const newPath = [...path]; + const last = newPath.pop(); + if (last !== undefined) { + newPath.push(`${last}[${i}]`); + } else { + newPath.push(`[${i}]`); + } + gather(arrSchema.elementType, offset + i * elementSize, newPath); + } + return; + } + + if (isVec(node)) { + const primitive = typeToPrimitive[node.type]; + if (isVec2(node)) { + segments.push({ primitive, offset, path: [...path, 'x'] }); + segments.push({ primitive, offset: offset + 4, path: [...path, 'y'] }); + } + if (isVec3(node)) { + segments.push({ primitive, offset, path: [...path, 'x'] }); + segments.push({ primitive, offset: offset + 4, path: [...path, 'y'] }); + segments.push({ primitive, offset: offset + 8, path: [...path, 'z'] }); + } + if (isVec4(node)) { + segments.push({ primitive, offset, path: [...path, 'x'] }); + segments.push({ primitive, offset: offset + 4, path: [...path, 'y'] }); + segments.push({ primitive, offset: offset + 8, path: [...path, 'z'] }); + segments.push({ primitive, offset: offset + 12, path: [...path, 'w'] }); + } + + return; + } + + const primitive = + typeToPrimitive[node.type as keyof typeof typeToPrimitive]; + segments.push({ primitive, offset, path }); + } + + gather(schema, 0, []); + + return segments; +} + +function buildAccessor(path: string[]) { + const rootIsArray = path[0]?.startsWith('['); + + if (rootIsArray) { + const index = path.shift(); + return `value${index}${path.map((p) => `.${p}`).join('')}`; + } + + return path.length === 0 ? 'value' : `value.${path.join('.')}`; +} + +const compiledWriters = new Map< + wgsl.BaseWgslData, + ( + output: DataView, + offset: number, + value: unknown, + endianness?: boolean, + ) => void +>(); + +export function getCompiledWriterForSchema( + schema: T, +): ( + output: DataView, + offset: number, + value: Infer, + endianness?: boolean, +) => void { + if (compiledWriters.has(schema)) { + return compiledWriters.get(schema) as ( + output: DataView, + offset: number, + value: Infer, + endianness?: boolean, + ) => void; + } + + const instructions = createCompileInstructions(schema); + + const body = instructions + .map( + ({ primitive, offset, path }) => + `output.${primitiveToWriteFunction[primitive]}(offset+${offset}, ${buildAccessor(path)}, endianness)`, + ) + .join('\n'); + + const fn = new Function( + 'output', + 'offset', + 'value', + 'endianness=true', + body, + ) as ( + output: DataView, + offset: number, + value: Infer | unknown, + endianness?: boolean, + ) => void; + + compiledWriters.set(schema, fn); + + return fn; +} diff --git a/packages/typegpu/src/data/partialIO.ts b/packages/typegpu/src/data/partialIO.ts index 41280367..877138d8 100644 --- a/packages/typegpu/src/data/partialIO.ts +++ b/packages/typegpu/src/data/partialIO.ts @@ -60,8 +60,11 @@ export function getWriteInstructions( ); } } - } else if (isWgslArray(node) || isDisarray(node)) { - const arrSchema = node; + return; + } + + if (isWgslArray(node) || isDisarray(node)) { + const arrSchema = node as wgsl.WgslArray; const elementSize = roundUp( sizeOf(arrSchema.elementType), alignmentOf(arrSchema.elementType), diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 397eff92..04a48d9c 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -965,6 +965,36 @@ export type AnyWgslData = // #endregion +export function isVec2(value: unknown): value is Vec2f | Vec2h | Vec2i | Vec2u { + return (value as AnyWgslData)?.type.startsWith('vec2'); +} + +export function isVec3(value: unknown): value is Vec3f | Vec3h | Vec3i | Vec3u { + return (value as AnyWgslData)?.type.startsWith('vec3'); +} + +export function isVec4(value: unknown): value is Vec4f | Vec4h | Vec4i | Vec4u { + return (value as AnyWgslData)?.type.startsWith('vec4'); +} + +export function isVec( + value: unknown, +): value is + | Vec2f + | Vec2h + | Vec2i + | Vec2u + | Vec3f + | Vec3h + | Vec3i + | Vec3u + | Vec4f + | Vec4h + | Vec4i + | Vec4u { + return isVec2(value) || isVec3(value) || isVec4(value); +} + export function isWgslData(value: unknown): value is AnyWgslData { return wgslTypeLiterals.includes((value as AnyWgslData)?.type); } diff --git a/packages/typegpu/tests/compiledIO.test.ts b/packages/typegpu/tests/compiledIO.test.ts new file mode 100644 index 00000000..fd64f2ba --- /dev/null +++ b/packages/typegpu/tests/compiledIO.test.ts @@ -0,0 +1,275 @@ +import { describe, expect } from 'vitest'; +import * as d from '../src/data'; +import { + type CompiledWriteInstructions, + createCompileInstructions, + getCompiledWriterForSchema, +} from '../src/data/compiledIO'; +import { sizeOf } from '../src/data/sizeOf'; +import type { NTuple } from '../src/shared/utilityTypes'; +import { it } from './utils/extendedIt'; + +describe('createCompileInstructions', () => { + it('should compile a writer for a struct', () => { + const struct = d.struct({ + a: d.u32, + b: d.vec3f, + }); + + const instructions = createCompileInstructions(struct); + expect(instructions).toHaveLength(4); + + const [f1, f2x, f2y, f2z] = instructions as NTuple< + CompiledWriteInstructions, + 4 + >; + + expect(f1.offset).toBe(0); + expect(f1.primitive).toBe('u32'); + expect(f1.path).toStrictEqual(['a']); + + expect(f2x.offset).toBe(16); + expect(f2x.primitive).toBe('f32'); + expect(f2x.path).toStrictEqual(['b', 'x']); + + expect(f2y.offset).toBe(20); + expect(f2y.primitive).toBe('f32'); + expect(f2y.path).toStrictEqual(['b', 'y']); + + expect(f2z.offset).toBe(24); + expect(f2z.primitive).toBe('f32'); + expect(f2z.path).toStrictEqual(['b', 'z']); + }); + + it('should compile a writer for a struct with an array', () => { + const struct = d.struct({ + a: d.u32, + b: d.arrayOf(d.vec3f, 2), + c: d.arrayOf(d.u32, 3), + }); + + const instructions = createCompileInstructions(struct); + expect(instructions).toHaveLength(10); + + const [f1, f2x, f2y, f2z, f3x, f3y, f3z, f4, f5, f6] = + instructions as NTuple; + + expect(f1.offset).toBe(0); + expect(f1.primitive).toBe('u32'); + expect(f1.path).toStrictEqual(['a']); + + expect(f2x.offset).toBe(16); + expect(f2x.primitive).toBe('f32'); + expect(f2x.path).toStrictEqual(['b[0]', 'x']); + + expect(f2y.offset).toBe(20); + expect(f2y.primitive).toBe('f32'); + expect(f2y.path).toStrictEqual(['b[0]', 'y']); + + expect(f2z.offset).toBe(24); + expect(f2z.primitive).toBe('f32'); + expect(f2z.path).toStrictEqual(['b[0]', 'z']); + + expect(f3x.offset).toBe(32); + expect(f3x.primitive).toBe('f32'); + expect(f3x.path).toStrictEqual(['b[1]', 'x']); + + expect(f3y.offset).toBe(36); + expect(f3y.primitive).toBe('f32'); + expect(f3y.path).toStrictEqual(['b[1]', 'y']); + + expect(f3z.offset).toBe(40); + expect(f3z.primitive).toBe('f32'); + expect(f3z.path).toStrictEqual(['b[1]', 'z']); + + expect(f4.offset).toBe(48); + expect(f4.primitive).toBe('u32'); + expect(f4.path).toStrictEqual(['c[0]']); + + expect(f5.offset).toBe(52); + expect(f5.primitive).toBe('u32'); + expect(f5.path).toStrictEqual(['c[1]']); + + expect(f6.offset).toBe(56); + expect(f6.primitive).toBe('u32'); + expect(f6.path).toStrictEqual(['c[2]']); + }); + + it('should compile a writer for a struct with nested structs', () => { + const struct = d.struct({ + a: d.u32, + b: d.struct({ + d: d.vec3f, + }), + c: d.arrayOf(d.struct({ d: d.u32 }), 3), + }); + + const instructions = createCompileInstructions(struct); + expect(instructions).toHaveLength(7); + + const [f1, f2x, f2y, f2z, f3, f4, f5] = instructions as NTuple< + CompiledWriteInstructions, + 7 + >; + + expect(f1.offset).toBe(0); + expect(f1.primitive).toBe('u32'); + expect(f1.path).toStrictEqual(['a']); + + expect(f2x.offset).toBe(16); + expect(f2x.primitive).toBe('f32'); + expect(f2x.path).toStrictEqual(['b', 'd', 'x']); + + expect(f2y.offset).toBe(20); + expect(f2y.primitive).toBe('f32'); + expect(f2y.path).toStrictEqual(['b', 'd', 'y']); + + expect(f2z.offset).toBe(24); + expect(f2z.primitive).toBe('f32'); + expect(f2z.path).toStrictEqual(['b', 'd', 'z']); + + expect(f3.offset).toBe(32); + expect(f3.primitive).toBe('u32'); + expect(f3.path).toStrictEqual(['c[0]', 'd']); + + expect(f4.offset).toBe(36); + expect(f4.primitive).toBe('u32'); + expect(f4.path).toStrictEqual(['c[1]', 'd']); + + expect(f5.offset).toBe(40); + expect(f5.primitive).toBe('u32'); + expect(f5.path).toStrictEqual(['c[2]', 'd']); + }); + + it('should compile a writer for an array', () => { + const array = d.arrayOf(d.vec3f, 5); + + const instructions = createCompileInstructions(array); + + expect(instructions).toHaveLength(15); + + for (let i = 0; i < 5; i++) { + expect((instructions[i * 3] as CompiledWriteInstructions).offset).toBe( + i * 16, + ); + expect((instructions[i * 3] as CompiledWriteInstructions).primitive).toBe( + 'f32', + ); + expect( + (instructions[i * 3] as CompiledWriteInstructions).path, + ).toStrictEqual([`[${i}]`, 'x']); + + expect( + (instructions[i * 3 + 1] as CompiledWriteInstructions).offset, + ).toBe(i * 16 + 4); + expect( + (instructions[i * 3 + 1] as CompiledWriteInstructions).primitive, + ).toBe('f32'); + expect( + (instructions[i * 3 + 1] as CompiledWriteInstructions).path, + ).toStrictEqual([`[${i}]`, 'y']); + + expect( + (instructions[i * 3 + 2] as CompiledWriteInstructions).offset, + ).toBe(i * 16 + 8); + expect( + (instructions[i * 3 + 2] as CompiledWriteInstructions).primitive, + ).toBe('f32'); + expect( + (instructions[i * 3 + 2] as CompiledWriteInstructions).path, + ).toStrictEqual([`[${i}]`, 'z']); + } + }); +}); + +describe('createCompileInstructions', () => { + it('should compile a writer for a struct', () => { + const struct = d.struct({ + a: d.u32, + b: d.vec3f, + }); + + const writer = getCompiledWriterForSchema(struct); + const arr = new ArrayBuffer(sizeOf(struct)); + const dataView = new DataView(arr); + + writer(dataView, 0, { a: 1, b: d.vec3f(1, 2, 3) }); + + expect(new Uint32Array(arr, 0, 1)[0]).toBe(1); + expect([...new Float32Array(arr, 16, 3)]).toEqual([1, 2, 3]); + }); + + it('should compile a writer for a struct with an array', () => { + const struct = d.struct({ + a: d.u32, + b: d.arrayOf(d.vec3f, 2), + c: d.arrayOf(d.u32, 3), + }); + + const writer = getCompiledWriterForSchema(struct); + + const arr = new ArrayBuffer(sizeOf(struct)); + const dataView = new DataView(arr); + + writer(dataView, 0, { + a: 1, + b: [d.vec3f(1, 2, 3), d.vec3f(4, 5, 6)], + c: [1, 2, 3], + }); + + expect(new Uint32Array(arr, 0, 1)[0]).toBe(1); + expect([...new Float32Array(arr, 16, 3)]).toEqual([1, 2, 3]); + expect([...new Float32Array(arr, 32, 3)]).toEqual([4, 5, 6]); + expect([...new Uint32Array(arr, 48, 3)]).toEqual([1, 2, 3]); + }); + + it('should compile a writer for a struct with nested structs', () => { + const struct = d.struct({ + a: d.u32, + b: d.struct({ + d: d.vec3f, + }), + c: d.arrayOf(d.struct({ d: d.u32 }), 3), + }); + + const writer = getCompiledWriterForSchema(struct); + + const arr = new ArrayBuffer(sizeOf(struct)); + const dataView = new DataView(arr); + + writer(dataView, 0, { + a: 1, + b: { d: d.vec3f(1, 2, 3) }, + c: [{ d: 1 }, { d: 2 }, { d: 3 }], + }); + + expect(new Uint32Array(arr, 0, 1)[0]).toBe(1); + expect([...new Float32Array(arr, 16, 3)]).toEqual([1, 2, 3]); + expect([...new Uint32Array(arr, 32, 3)]).toEqual([1, 2, 3]); + }); + + it('should compile a writer for an array', () => { + const array = d.arrayOf(d.vec3f, 5); + + const writer = getCompiledWriterForSchema(array); + + const arr = new ArrayBuffer(sizeOf(array)); + const dataView = new DataView(arr); + + writer(dataView, 0, [ + d.vec3f(0, 1, 2), + d.vec3f(3, 4, 5), + d.vec3f(6, 7, 8), + d.vec3f(9, 10, 11), + d.vec3f(12, 13, 14), + ]); + + for (let i = 0; i < 5; i++) { + expect([...new Float32Array(arr, i * 16, 3)]).toEqual([ + i * 3, + i * 3 + 1, + i * 3 + 2, + ]); + } + }); +}); From 389611e3829e84cbb53fcb7843e7e2186e3ade1f Mon Sep 17 00:00:00 2001 From: reczkok Date: Thu, 6 Feb 2025 14:47:13 +0100 Subject: [PATCH 6/7] change benchmark and update write logic (draft) --- .../src/pages/benchmark/benchmark-app.tsx | 123 +++++++----------- packages/typegpu/src/core/buffer/buffer.ts | 11 +- packages/typegpu/src/data/compiledIO.ts | 22 ++-- 3 files changed, 68 insertions(+), 88 deletions(-) diff --git a/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx b/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx index bc301f7a..57673795 100644 --- a/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx +++ b/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx @@ -3,6 +3,7 @@ import { atom } from 'jotai/vanilla'; import { CirclePlus } from 'lucide-react'; import { Suspense, useMemo } from 'react'; import { Bench } from 'tinybench'; +import type { TgpuBuffer, TgpuRoot } from 'typegpu'; import { importTypeGPU, importTypeGPUData } from './modules.js'; import { ParameterSetRow } from './parameter-set-row.js'; import { @@ -25,23 +26,41 @@ async function runBench(params: BenchParameterSet): Promise { const bench = new Bench({ name: stringifyLocator('typegpu', params.typegpu), time: 1000, - }); + setup: async () => { + root = await tgpu.init(); - const amountOfBoids = 10000; + buffer = root.createBuffer(BoidArray); + vectorlessBuffer = root.createBuffer(VectorlessBoidArray); + }, + teardown: () => { + root.destroy(); + }, + }); - bench - .add('mass boid transfer', async () => { - const root = await tgpu.init(); + const amountOfBoids = 5096; - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); + const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3f, + }); + const VectorlessBoid = d.struct({ + posX: d.f32, + posY: d.f32, + posZ: d.f32, + velX: d.f32, + velY: d.f32, + velZ: d.f32, + }); - const BoidArray = d.arrayOf(Boid, amountOfBoids); + const BoidArray = d.arrayOf(Boid, amountOfBoids); + const VectorlessBoidArray = d.arrayOf(VectorlessBoid, amountOfBoids); - const buffer = root.createBuffer(BoidArray); + let root: TgpuRoot; + let buffer: TgpuBuffer; + let vectorlessBuffer: TgpuBuffer; + bench + .add('mass boid transfer', async () => { buffer.write( Array.from({ length: amountOfBoids }).map(() => ({ pos: d.vec3f(1, 2, 3), @@ -49,20 +68,23 @@ async function runBench(params: BenchParameterSet): Promise { })), ); - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }) - .add('mass boid transfer (manual reference)', async () => { - const root = await tgpu.init(); - - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); - - const BoidArray = d.arrayOf(Boid, amountOfBoids); - - const buffer = root.createBuffer(BoidArray); + .add('mass boid transfer (vectorless)', async () => { + vectorlessBuffer.write( + Array.from({ length: amountOfBoids }).map(() => ({ + posX: 1, + posY: 2, + posZ: 3, + velX: 4, + velY: 5, + velZ: 6, + })), + ); + await root.device.queue.onSubmittedWorkDone(); + }) + .add('mass boid transfer (manual reference)', async () => { const data = new ArrayBuffer(d.sizeOf(BoidArray)); const fView = new Float32Array(data); @@ -77,21 +99,9 @@ async function runBench(params: BenchParameterSet): Promise { } root.device.queue.writeBuffer(root.unwrap(buffer), 0, data); - - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }) .add('mass boid transfer (partial write)', async () => { - const root = await tgpu.init(); - - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); - - const BoidArray = d.arrayOf(Boid, amountOfBoids); - - const buffer = root.createBuffer(BoidArray); - const randomBoid = Math.floor(Math.random() * amountOfBoids); buffer.writePartial([ @@ -101,22 +111,11 @@ async function runBench(params: BenchParameterSet): Promise { }, ]); - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }) .add( 'mass boid transfer (partial write 20% of the buffer - not contiguous)', async () => { - const root = await tgpu.init(); - - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); - - const BoidArray = d.arrayOf(Boid, amountOfBoids); - - const buffer = root.createBuffer(BoidArray); - const writes = Array.from({ length: amountOfBoids }) .map((_, i) => i) .filter((i) => i % 5 === 0) @@ -127,23 +126,12 @@ async function runBench(params: BenchParameterSet): Promise { buffer.writePartial(writes); - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }, ) .add( 'mass boid transfer (partial write 20% of the buffer, contiguous)', async () => { - const root = await tgpu.init(); - - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); - - const BoidArray = d.arrayOf(Boid, amountOfBoids); - - const buffer = root.createBuffer(BoidArray); - const writes = Array.from({ length: amountOfBoids / 5 }) .map((_, i) => i) .map((i) => ({ @@ -153,23 +141,12 @@ async function runBench(params: BenchParameterSet): Promise { buffer.writePartial(writes); - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }, ) .add( 'mass boid transfer (partial write 100% of the buffer - contiguous (duh))', async () => { - const root = await tgpu.init(); - - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3f, - }); - - const BoidArray = d.arrayOf(Boid, amountOfBoids); - - const buffer = root.createBuffer(BoidArray); - const writes = Array.from({ length: amountOfBoids }) .map((_, i) => i) .map((i) => ({ @@ -179,7 +156,7 @@ async function runBench(params: BenchParameterSet): Promise { buffer.writePartial(writes); - root.destroy(); + await root.device.queue.onSubmittedWorkDone(); }, ); diff --git a/packages/typegpu/src/core/buffer/buffer.ts b/packages/typegpu/src/core/buffer/buffer.ts index 97728a3d..1b11904c 100644 --- a/packages/typegpu/src/core/buffer/buffer.ts +++ b/packages/typegpu/src/core/buffer/buffer.ts @@ -135,6 +135,7 @@ class TgpuBufferImpl implements TgpuBuffer { private _buffer: GPUBuffer | null = null; private _ownBuffer: boolean; private _destroyed = false; + private _hostBuffer: ArrayBuffer | undefined; private _label: string | undefined; readonly initial: Infer | undefined; @@ -255,18 +256,20 @@ class TgpuBufferImpl implements TgpuBuffer { } const size = sizeOf(this.dataType); + if (!this._hostBuffer) { + this._hostBuffer = new ArrayBuffer(size); + } // Flushing any commands yet to be encoded. this._group.flush(); - const hostBuffer = new ArrayBuffer(size); if (EVAL_ALLOWED_IN_ENV) { const writer = getCompiledWriterForSchema(this.dataType); - writer(new DataView(hostBuffer), 0, data); + writer(new DataView(this._hostBuffer), 0, data); } else { - writeData(new BufferWriter(hostBuffer), this.dataType, data); + writeData(new BufferWriter(this._hostBuffer), this.dataType, data); } - device.queue.writeBuffer(gpuBuffer, 0, hostBuffer, 0, size); + device.queue.writeBuffer(gpuBuffer, 0, this._hostBuffer, 0, size); } public writePartial(data: InferPartial): void { diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index f7a67680..23e5506c 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -2,7 +2,7 @@ import { roundUp } from '../mathUtils'; import type { Infer } from '../shared/repr'; import { alignmentOf } from './alignmentOf'; import { isDisarray, isUnstruct } from './dataTypes'; -import { offsetsForProps } from './offests'; +import { offsetsForProps } from './offsets'; import { sizeOf } from './sizeOf'; import { isVec, @@ -23,6 +23,16 @@ try { EVAL_ALLOWED_IN_ENV = false; } +const compiledWriters = new WeakMap< + wgsl.BaseWgslData, + ( + output: DataView, + offset: number, + value: unknown, + endianness?: boolean, + ) => void +>(); + export interface CompiledWriteInstructions { primitive: 'u32' | 'i32' | 'f32'; offset: number; @@ -144,16 +154,6 @@ function buildAccessor(path: string[]) { return path.length === 0 ? 'value' : `value.${path.join('.')}`; } -const compiledWriters = new Map< - wgsl.BaseWgslData, - ( - output: DataView, - offset: number, - value: unknown, - endianness?: boolean, - ) => void ->(); - export function getCompiledWriterForSchema( schema: T, ): ( From c3f3849f6cd8a42ed03766c7f55908b5dc797fb9 Mon Sep 17 00:00:00 2001 From: reczkok Date: Fri, 14 Feb 2025 10:34:29 +0100 Subject: [PATCH 7/7] post rebase fixes --- packages/typegpu/src/data/compiledIO.ts | 8 ++--- packages/typegpu/tests/compiledIO.test.ts | 37 ++++++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index 23e5506c..a9eb99ea 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -24,7 +24,7 @@ try { } const compiledWriters = new WeakMap< - wgsl.BaseWgslData, + wgsl.BaseData, ( output: DataView, offset: number, @@ -66,12 +66,12 @@ const primitiveToWriteFunction = { f32: 'setFloat32', } as const; -export function createCompileInstructions( +export function createCompileInstructions( schema: TData, ) { const segments: CompiledWriteInstructions[] = []; - function gather( + function gather( node: T, offset: number, path: string[], @@ -154,7 +154,7 @@ function buildAccessor(path: string[]) { return path.length === 0 ? 'value' : `value.${path.join('.')}`; } -export function getCompiledWriterForSchema( +export function getCompiledWriterForSchema( schema: T, ): ( output: DataView, diff --git a/packages/typegpu/tests/compiledIO.test.ts b/packages/typegpu/tests/compiledIO.test.ts index fd64f2ba..8fc037bc 100644 --- a/packages/typegpu/tests/compiledIO.test.ts +++ b/packages/typegpu/tests/compiledIO.test.ts @@ -6,7 +6,6 @@ import { getCompiledWriterForSchema, } from '../src/data/compiledIO'; import { sizeOf } from '../src/data/sizeOf'; -import type { NTuple } from '../src/shared/utilityTypes'; import { it } from './utils/extendedIt'; describe('createCompileInstructions', () => { @@ -19,10 +18,11 @@ describe('createCompileInstructions', () => { const instructions = createCompileInstructions(struct); expect(instructions).toHaveLength(4); - const [f1, f2x, f2y, f2z] = instructions as NTuple< - CompiledWriteInstructions, - 4 - >; + const [f1, f2x, f2y, f2z] = instructions; + + if (!f1 || !f2x || !f2y || !f2z) { + throw new Error('Invalid instructions'); + } expect(f1.offset).toBe(0); expect(f1.primitive).toBe('u32'); @@ -51,8 +51,22 @@ describe('createCompileInstructions', () => { const instructions = createCompileInstructions(struct); expect(instructions).toHaveLength(10); - const [f1, f2x, f2y, f2z, f3x, f3y, f3z, f4, f5, f6] = - instructions as NTuple; + const [f1, f2x, f2y, f2z, f3x, f3y, f3z, f4, f5, f6] = instructions; + + if ( + !f1 || + !f2x || + !f2y || + !f2z || + !f3x || + !f3y || + !f3z || + !f4 || + !f5 || + !f6 + ) { + throw new Error('Invalid instructions'); + } expect(f1.offset).toBe(0); expect(f1.primitive).toBe('u32'); @@ -107,10 +121,11 @@ describe('createCompileInstructions', () => { const instructions = createCompileInstructions(struct); expect(instructions).toHaveLength(7); - const [f1, f2x, f2y, f2z, f3, f4, f5] = instructions as NTuple< - CompiledWriteInstructions, - 7 - >; + const [f1, f2x, f2y, f2z, f3, f4, f5] = instructions; + + if (!f1 || !f2x || !f2y || !f2z || !f3 || !f4 || !f5) { + throw new Error('Invalid instructions'); + } expect(f1.offset).toBe(0); expect(f1.primitive).toBe('u32');