Skip to content

Commit cab0ad7

Browse files
authored
fix: Add missing textureLoad overloads and fix wrong types (#2261)
1 parent f60b4f7 commit cab0ad7

2 files changed

Lines changed: 257 additions & 7 deletions

File tree

packages/typegpu/src/std/texture.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { stitch } from '../core/resolve/stitch.ts';
22
import {
3+
isWgslExternalTexture,
34
isWgslTexture,
45
type WgslExternalTexture,
56
type WgslStorageTexture,
67
type WgslTexture,
78
} from '../data/texture.ts';
8-
import type { TexelData } from '../core/texture/texture.ts';
99
import { dualImpl, MissingCpuImplError } from '../core/function/dualImpl.ts';
1010
import { f32, i32, u32 } from '../data/numeric.ts';
1111
import { vec2u, vec3u, vec4f, vec4i, vec4u } from '../data/vector.ts';
@@ -39,14 +39,14 @@ import type {
3939
textureDepth2dArray,
4040
textureDepthCube,
4141
textureDepthCubeArray,
42+
textureDepthMultisampled2d,
4243
textureExternal,
4344
textureMultisampled2d,
4445
textureStorage1d,
4546
textureStorage2d,
4647
textureStorage2dArray,
4748
textureStorage3d,
4849
} from '../data/texture.ts';
49-
5050
import type { comparisonSampler, sampler } from '../data/sampler.ts';
5151

5252
function sampleCpu<T extends texture1d>(texture: T, sampler: sampler, coords: number): v4f;
@@ -338,6 +338,23 @@ function textureLoadCpu<T extends textureMultisampled2d>(
338338
coords: v2i | v2u,
339339
sampleIndex: number,
340340
): PrimitiveToLoadedType[T[typeof $internal]['type']];
341+
function textureLoadCpu<T extends textureDepth2d>(
342+
texture: T,
343+
coords: v2i | v2u,
344+
level: number,
345+
): number;
346+
function textureLoadCpu<T extends textureDepth2dArray>(
347+
texture: T,
348+
coords: v2i | v2u,
349+
arrayIndex: number,
350+
level: number,
351+
): number;
352+
function textureLoadCpu<T extends textureDepthMultisampled2d>(
353+
texture: T,
354+
coords: v2i | v2u,
355+
sampleIndex: number,
356+
): number;
357+
function textureLoadCpu<T extends textureExternal>(texture: T, coords: v2i | v2u): v4f;
341358
function textureLoadCpu<T extends textureStorage1d>(
342359
texture: T,
343360
coords: number,
@@ -356,10 +373,10 @@ function textureLoadCpu<T extends textureStorage3d>(
356373
coords: v3i | v3u,
357374
): TexelFormatToInstanceType<T[typeof $internal][0]>;
358375
function textureLoadCpu(
359-
_texture: WgslTexture | WgslStorageTexture,
376+
_texture: WgslTexture | WgslStorageTexture | WgslExternalTexture,
360377
_coords: number | v2i | v2u | v3i | v3u,
361378
_levelOrArrayIndex?: number,
362-
): TexelData {
379+
): v4f | v4i | v4u | number {
363380
throw new MissingCpuImplError(
364381
'`textureLoad` relies on GPU resources and cannot be executed outside of a draw call',
365382
);
@@ -370,7 +387,7 @@ export const textureLoad = dualImpl({
370387
normalImpl: textureLoadCpu,
371388
codegenImpl: (_ctx, args) => stitch`textureLoad(${args})`,
372389
signature: (...args) => {
373-
const texture = args[0] as WgslTexture | WgslStorageTexture;
390+
const texture = args[0] as WgslTexture | WgslStorageTexture | WgslExternalTexture;
374391
if (isWgslTexture(texture)) {
375392
const isDepth = texture.type.startsWith('texture_depth');
376393
const sampleType = texture.sampleType;
@@ -385,6 +402,12 @@ export const textureLoad = dualImpl({
385402
: vec4i,
386403
};
387404
}
405+
if (isWgslExternalTexture(texture)) {
406+
return {
407+
argTypes: args,
408+
returnType: vec4f,
409+
};
410+
}
388411
const format = texture.format;
389412
const dataType = getTextureFormatInfo(format).vectorType;
390413
return {
@@ -418,8 +441,8 @@ function textureStoreCpu<T extends textureStorage3d>(
418441
function textureStoreCpu(
419442
_texture: WgslStorageTexture,
420443
_coords: number | v2i | v2u | v3i | v3u,
421-
_arrayIndexOrValue?: number | TexelData,
422-
_maybeValue?: TexelData,
444+
_arrayIndexOrValue?: number | v4f | v4i | v4u,
445+
_maybeValue?: v4f | v4i | v4u,
423446
): void {
424447
throw new MissingCpuImplError(
425448
'`textureStore` relies on GPU resources and cannot be executed outside of a draw call',
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { describe, expect, expectTypeOf } from 'vitest';
2+
import { it } from '../../utils/extendedIt.ts';
3+
import { textureLoad } from '../../../src/std/texture.ts';
4+
import tgpu from '../../../src/index.js';
5+
import * as d from '../../../src/data/index.ts';
6+
import { bindGroupLayout } from '../../../src/tgpuBindGroupLayout.ts';
7+
import { resolve } from '../../../src/core/resolve/tgpuResolve.ts';
8+
9+
// we need this since all other usages will be removed by plugin
10+
expectTypeOf(() => {});
11+
12+
describe('textureLoad', () => {
13+
it('Has correct signatures for sampled and depth textures', () => {
14+
const testLayout = bindGroupLayout({
15+
tex1d: { texture: d.texture1d() },
16+
tex2d: { texture: d.texture2d() },
17+
tex2d_u32: { texture: d.texture2d(d.u32) },
18+
tex2d_i32: { texture: d.texture2d(d.i32) },
19+
tex2d_array: { texture: d.texture2dArray() },
20+
tex3d: { texture: d.texture3d() },
21+
texms2d: { texture: d.textureMultisampled2d() },
22+
texdepth2d: { texture: d.textureDepth2d() },
23+
texdepth2d_array: { texture: d.textureDepth2dArray() },
24+
texdepthms2d: { texture: d.textureDepthMultisampled2d() },
25+
});
26+
27+
const testFn = tgpu.fn([])(() => {
28+
const coord1d = d.i32(0);
29+
const coord2d = d.vec2i(0, 0);
30+
const coord3d = d.vec3i(0, 0, 0);
31+
const level = d.i32(0);
32+
const arrayIndex = d.i32(0);
33+
const sampleIndex = d.i32(0);
34+
35+
const load1d = textureLoad(testLayout.$.tex1d, coord1d, level);
36+
const load2d = textureLoad(testLayout.$.tex2d, coord2d, level);
37+
const load2d_u32 = textureLoad(testLayout.$.tex2d_u32, coord2d, level);
38+
const load2d_i32 = textureLoad(testLayout.$.tex2d_i32, coord2d, level);
39+
const load2d_array = textureLoad(testLayout.$.tex2d_array, coord2d, arrayIndex, level);
40+
const load3d = textureLoad(testLayout.$.tex3d, coord3d, level);
41+
const loadms2d = textureLoad(testLayout.$.texms2d, coord2d, sampleIndex);
42+
const loaddepth2d = textureLoad(testLayout.$.texdepth2d, coord2d, level);
43+
const loaddepth2d_array = textureLoad(
44+
testLayout.$.texdepth2d_array,
45+
coord2d,
46+
arrayIndex,
47+
level,
48+
);
49+
const loaddepthms2d = textureLoad(testLayout.$.texdepthms2d, coord2d, sampleIndex);
50+
51+
if (false) {
52+
expectTypeOf(load1d).toEqualTypeOf<d.v4f>();
53+
expectTypeOf(load2d).toEqualTypeOf<d.v4f>();
54+
expectTypeOf(load2d_u32).toEqualTypeOf<d.v4u>();
55+
expectTypeOf(load2d_i32).toEqualTypeOf<d.v4i>();
56+
expectTypeOf(load2d_array).toEqualTypeOf<d.v4f>();
57+
expectTypeOf(load3d).toEqualTypeOf<d.v4f>();
58+
expectTypeOf(loadms2d).toEqualTypeOf<d.v4f>();
59+
expectTypeOf(loaddepth2d).toEqualTypeOf<number>();
60+
expectTypeOf(loaddepth2d_array).toEqualTypeOf<number>();
61+
expectTypeOf(loaddepthms2d).toEqualTypeOf<number>();
62+
}
63+
});
64+
65+
expect(resolve([testFn])).toMatchInlineSnapshot(`
66+
"@group(0) @binding(0) var tex1d: texture_1d<f32>;
67+
68+
@group(0) @binding(1) var tex2d: texture_2d<f32>;
69+
70+
@group(0) @binding(2) var tex2d_u32: texture_2d<u32>;
71+
72+
@group(0) @binding(3) var tex2d_i32: texture_2d<i32>;
73+
74+
@group(0) @binding(4) var tex2d_array: texture_2d_array<f32>;
75+
76+
@group(0) @binding(5) var tex3d: texture_3d<f32>;
77+
78+
@group(0) @binding(6) var texms2d: texture_multisampled_2d<f32>;
79+
80+
@group(0) @binding(7) var texdepth2d: texture_depth_2d;
81+
82+
@group(0) @binding(8) var texdepth2d_array: texture_depth_2d_array;
83+
84+
@group(0) @binding(9) var texdepthms2d: texture_depth_multisampled_2d;
85+
86+
fn testFn() {
87+
const coord1d = 0i;
88+
var coord2d = vec2i();
89+
var coord3d = vec3i();
90+
const level = 0i;
91+
const arrayIndex = 0i;
92+
const sampleIndex = 0i;
93+
var load1d = textureLoad(tex1d, coord1d, level);
94+
var load2d = textureLoad(tex2d, coord2d, level);
95+
var load2d_u32 = textureLoad(tex2d_u32, coord2d, level);
96+
var load2d_i32 = textureLoad(tex2d_i32, coord2d, level);
97+
var load2d_array = textureLoad(tex2d_array, coord2d, arrayIndex, level);
98+
var load3d = textureLoad(tex3d, coord3d, level);
99+
var loadms2d = textureLoad(texms2d, coord2d, sampleIndex);
100+
let loaddepth2d = textureLoad(texdepth2d, coord2d, level);
101+
let loaddepth2d_array = textureLoad(texdepth2d_array, coord2d, arrayIndex, level);
102+
let loaddepthms2d = textureLoad(texdepthms2d, coord2d, sampleIndex);
103+
}"
104+
`);
105+
});
106+
107+
it('Has correct signatures for storage textures', () => {
108+
const testLayout = bindGroupLayout({
109+
store1d: { storageTexture: d.textureStorage1d('rgba32float', 'read-only') },
110+
store2d: { storageTexture: d.textureStorage2d('rgba32float', 'read-only') },
111+
store2d_uint: { storageTexture: d.textureStorage2d('rgba32uint', 'read-only') },
112+
store2d_sint: { storageTexture: d.textureStorage2d('rgba32sint', 'read-only') },
113+
store2d_array: { storageTexture: d.textureStorage2dArray('rgba32float', 'read-only') },
114+
store3d: { storageTexture: d.textureStorage3d('rgba32float', 'read-only') },
115+
});
116+
117+
const testFn = tgpu.fn([])(() => {
118+
const coord1d = d.i32(0);
119+
const coord2d = d.vec2i(0, 0);
120+
const coord3d = d.vec3i(0, 0, 0);
121+
const arrayIndex = d.i32(0);
122+
123+
const loadStore1d = textureLoad(testLayout.$.store1d, coord1d);
124+
const loadStore2d = textureLoad(testLayout.$.store2d, coord2d);
125+
const loadStore2d_uint = textureLoad(testLayout.$.store2d_uint, coord2d);
126+
const loadStore2d_sint = textureLoad(testLayout.$.store2d_sint, coord2d);
127+
const loadStore2d_array = textureLoad(testLayout.$.store2d_array, coord2d, arrayIndex);
128+
const loadStore3d = textureLoad(testLayout.$.store3d, coord3d);
129+
130+
if (false) {
131+
expectTypeOf(loadStore1d).toEqualTypeOf<d.v4f>();
132+
expectTypeOf(loadStore2d).toEqualTypeOf<d.v4f>();
133+
expectTypeOf(loadStore2d_uint).toEqualTypeOf<d.v4u>();
134+
expectTypeOf(loadStore2d_sint).toEqualTypeOf<d.v4i>();
135+
expectTypeOf(loadStore2d_array).toEqualTypeOf<d.v4f>();
136+
expectTypeOf(loadStore3d).toEqualTypeOf<d.v4f>();
137+
}
138+
});
139+
140+
expect(resolve([testFn])).toMatchInlineSnapshot(`
141+
"@group(0) @binding(0) var store1d: texture_storage_1d<rgba32float, read>;
142+
143+
@group(0) @binding(1) var store2d: texture_storage_2d<rgba32float, read>;
144+
145+
@group(0) @binding(2) var store2d_uint: texture_storage_2d<rgba32uint, read>;
146+
147+
@group(0) @binding(3) var store2d_sint: texture_storage_2d<rgba32sint, read>;
148+
149+
@group(0) @binding(4) var store2d_array: texture_storage_2d_array<rgba32float, read>;
150+
151+
@group(0) @binding(5) var store3d: texture_storage_3d<rgba32float, read>;
152+
153+
fn testFn() {
154+
const coord1d = 0i;
155+
var coord2d = vec2i();
156+
var coord3d = vec3i();
157+
const arrayIndex = 0i;
158+
var loadStore1d = textureLoad(store1d, coord1d);
159+
var loadStore2d = textureLoad(store2d, coord2d);
160+
var loadStore2d_uint = textureLoad(store2d_uint, coord2d);
161+
var loadStore2d_sint = textureLoad(store2d_sint, coord2d);
162+
var loadStore2d_array = textureLoad(store2d_array, coord2d, arrayIndex);
163+
var loadStore3d = textureLoad(store3d, coord3d);
164+
}"
165+
`);
166+
});
167+
168+
it('Has correct signatures for external textures', () => {
169+
const testLayout = bindGroupLayout({
170+
texExternal: { externalTexture: d.textureExternal() },
171+
});
172+
173+
const testFn = tgpu.fn([])(() => {
174+
const coord2d = d.vec2i(0, 0);
175+
176+
const loadExternal = textureLoad(testLayout.$.texExternal, coord2d);
177+
178+
if (false) {
179+
expectTypeOf(loadExternal).toEqualTypeOf<d.v4f>();
180+
}
181+
});
182+
183+
expect(resolve([testFn])).toMatchInlineSnapshot(`
184+
"@group(0) @binding(0) var texExternal: texture_external;
185+
186+
fn testFn() {
187+
var coord2d = vec2i();
188+
var loadExternal = textureLoad(texExternal, coord2d);
189+
}"
190+
`);
191+
});
192+
193+
it('does not allow for raw schemas to be passed in', ({ root }) => {
194+
const someTexture = root['~unstable']
195+
.createTexture({
196+
size: [256, 256],
197+
format: 'rgba8unorm',
198+
})
199+
.$usage('sampled');
200+
const sampledView = someTexture.createView(d.texture2d());
201+
202+
const someLayout = bindGroupLayout({
203+
tex2d: { texture: d.texture2d() },
204+
});
205+
206+
// Valid: view from a created texture
207+
const _validFn = tgpu.fn(
208+
[],
209+
d.vec4f,
210+
)(() => textureLoad(sampledView.$, d.vec2i(0, 0), d.i32(0)));
211+
212+
// Valid: view from a layout binding
213+
const _validFn2 = tgpu.fn(
214+
[],
215+
d.vec4f,
216+
)(() => textureLoad(someLayout.$.tex2d, d.vec2i(0, 0), d.i32(0)));
217+
218+
// @ts-expect-error — raw schema must not be accepted
219+
const _invalidFn = tgpu.fn(
220+
[],
221+
d.vec4f,
222+
)(() =>
223+
// @ts-expect-error
224+
textureLoad(d.texture2d(), d.vec2i(0, 0), 0),
225+
);
226+
});
227+
});

0 commit comments

Comments
 (0)