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>,
count?: number,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name count in this error overload differs from elementCount used in the two productive overloads below and in the JSDoc @param. Consider renaming to elementCount for consistency.

): '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 on lines 60 to +65

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can arrays hold location? From location docs:

Must only be applied to an entry point function parameter, entry point return type, or member of a structure type.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current implementation of vertexLayout, d.location in an array isn’t treated like a decorator the way it is for a struct member.

In vertexLayout the behaviour of d.location is setting the pipeline’s shaderLocation attribute.

This can be misleading, and maybe a better approach would be to make decorators single-purpose. We could find another way to pass shaderLocation to the vertex layout.

What do you think?

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 on lines +569 to +574

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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change defeats the purpose of this test (either remove it or repurpose)


const buffer = root.createBuffer(validSchema);

Expand Down
Loading