Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ const ArrayPartialSchema = d.arrayOf(d.f32);
const array = ArrayPartialSchema(2)([1.2, 19.29]);
// ^?
```
:::caution
The only decoration allowed on array element types is `d.location`. Decorators like `d.align` and `d.size` cannot be applied directly - wrap them in a struct instead, e.g. `d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)`.
:::

## Textures

Expand Down
36 changes: 32 additions & 4 deletions packages/typegpu/src/data/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ import { comptime, type TgpuComptime } from '../core/function/comptime.ts';
import { $internal } from '../shared/symbols.ts';
import { schemaCallWrapper } from './schemaCallWrapper.ts';
import { sizeOf } from './sizeOf.ts';
import type { AnyWgslData, WgslArray } from './wgslTypes.ts';
import type { AnyWgslData, Decorated, Location, WgslArray } from './wgslTypes.ts';
import { isDecorated, isLocationAttrib } from './wgslTypes.ts';

// ----------
// Public API
// ----------

type ForbiddenDecoratedArrayElement<T> =
T extends Decorated<infer _, infer Attribs>
? Attribs[number] extends Location
? never
: T
: never;

interface WgslArrayConstructor {
/**
* @deprecated Error: Arrays cannot hold decorated types other than @location.
* Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).
*/
<TElement extends AnyWgslData>(
elementType: ForbiddenDecoratedArrayElement<TElement>,
elementCount?: number,
): 'Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).';

<TElement extends AnyWgslData>(
elementType: TElement,
): (elementCount: number) => WgslArray<TElement>;
Expand All @@ -20,6 +37,10 @@ interface WgslArrayConstructor {
* Creates an array schema that can be used to construct gpu buffers.
* Describes arrays with fixed-size length, storing elements of the same type.
*
* The only decoration allowed on element types is `d.location`. Decorators like
* `d.align` and `d.size` cannot be applied directly — wrap them in a struct instead,
* e.g. `d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)`.
*
* @example
* const LENGTH = 3;
* const array = d.arrayOf(d.u32, LENGTH);
Expand All @@ -31,16 +52,23 @@ interface WgslArrayConstructor {
*
* @param elementType The type of elements in the array.
* @param elementCount The number of elements in the array.
* @throws If `elementType` is decorated with anything other than `d.location`.
*/
export const arrayOf: TgpuComptime<WgslArrayConstructor> = comptime(((
elementType,
elementCount,
elementType: AnyWgslData,
elementCount?: number,
) => {
if (isDecorated(elementType) && !elementType.attribs.every(isLocationAttrib)) {
throw new Error(
'Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).',
);
}
Comment thread
vende11s marked this conversation as resolved.

if (elementCount === undefined) {
return comptime((count: number) => cpu_arrayOf(elementType, count));
}
return cpu_arrayOf(elementType, elementCount);
}) as WgslArrayConstructor).$name('arrayOf');
}) as unknown as WgslArrayConstructor).$name('arrayOf');

// --------------
// Implementation
Expand Down
28 changes: 28 additions & 0 deletions packages/typegpu/tests/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,34 @@ describe('array', () => {
- fn*:main(): Value [1, 2, 3] is not resolvable]
`);
});

it('allows @location-decorated element types', () => {
const located = d.arrayOf(d.location(0, d.u32), 4);
Comment thread
vende11s marked this conversation as resolved.
expect(located.elementCount).toBe(4);
expectTypeOf(located).toEqualTypeOf<d.WgslArray<d.Decorated<d.U32, [d.Location<0>]>>>();
});

it('should allow calling arrayOf inside generic helpers', () => {
function arrayOf32<T extends d.AnyWgslData>(schema: T) {
return d.arrayOf(schema, 32);
}

arrayOf32(d.f32);
});
Comment thread
aleksanderkatan marked this conversation as resolved.

it('throws when a non-location decorated element type is passed', () => {
expect(() => d.arrayOf(d.align(16, d.u32), 4)).toThrowErrorMatchingInlineSnapshot(
`[Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).]`,
);
expect(() => d.arrayOf(d.align(16, d.u32))).toThrow();
expect(() => d.arrayOf(d.size(16, d.u32), 3)).toThrow();
expect(() => d.arrayOf(d.location(0, d.align(16, d.u32)), 3)).toThrow();
Comment thread
vende11s marked this conversation as resolved.

const aligned = () => d.arrayOf(d.align(16, d.u32), 4);
expectTypeOf(
aligned,
).returns.toEqualTypeOf<'Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).'>();
});
});

describe('array.length', () => {
Expand Down
13 changes: 0 additions & 13 deletions packages/typegpu/tests/buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,19 +859,6 @@ describe('TgpuBuffer', () => {
]
>();
});

it('should ignore decorated types when determining validity usage', ({ root }) => {
const validSchema = d.size(1024, d.arrayOf(d.align(16, d.u32), 32));

const buffer = root.createBuffer(validSchema);

expectTypeOf<Parameters<typeof buffer.$usage>>().toEqualTypeOf<
[
'index' | 'storage' | 'uniform' | 'vertex' | 'indirect',
...('index' | 'storage' | 'uniform' | 'vertex' | 'indirect')[],
]
>();
});
});

describe('TgpuBuffer (InferInput)', () => {
Expand Down
Loading