Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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>,
count?: number,
Comment thread
vende11s marked this conversation as resolved.
Outdated
): '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
27 changes: 27 additions & 0 deletions packages/typegpu/tests/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,33 @@ 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.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
2 changes: 1 addition & 1 deletion packages/typegpu/tests/buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ 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 validSchema = d.size(1024, d.arrayOf(d.u32, 32));
Comment thread
vende11s marked this conversation as resolved.
Outdated

const buffer = root.createBuffer(validSchema);

Expand Down
Loading