Skip to content

Commit a5293bb

Browse files
author
Martin Valigursky
committed
Support for fragmentOutputTypes for WebGPU
1 parent bb9980d commit a5293bb

File tree

7 files changed

+185
-17
lines changed

7 files changed

+185
-17
lines changed

examples/src/examples/shaders/integer-textures.example.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ const sandShader = pc.ShaderUtils.createShader(device, {
130130
uniqueName: 'SandShader',
131131
attributes: { aPosition: pc.SEMANTIC_POSITION },
132132
vertexChunk: 'quadVS',
133-
fragmentGLSL: files['sandSimulation.frag'],
133+
fragmentGLSL: files['sandSimulation.glsl.frag'],
134+
fragmentWGSL: files['sandSimulation.wgsl.frag'],
134135
// Note that we are changing the shader output type to 'uint'
135136
// This means we only have to return a single integer value from the shader,
136137
// whereas the default is to return a vec4. This option allows you to pass
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Texture (unsigned‐integer, fetch‐only)
2+
var sourceTexture: texture_2d<u32>;
3+
4+
// Uniforms (auto‐buffered, accessed as uniform.<name>)
5+
uniform mousePosition: vec2f;
6+
uniform mouseButton: u32;
7+
uniform passNum: u32;
8+
uniform brush: u32;
9+
uniform randomVal: f32;
10+
uniform brushRadius: f32;
11+
12+
// Interpolated varying (from vertex shader)
13+
varying uv0: vec2f;
14+
15+
// Particle element constants
16+
const AIR: u32 = 0u;
17+
const SAND: u32 = 1u;
18+
const ORANGESAND: u32 = 2u;
19+
const GRAYSAND: u32 = 3u;
20+
const WALL: u32 = 4u;
21+
22+
// Helper: check bounds in integer texel space
23+
fn isInBounds(c: vec2i, size: vec2i) -> bool {
24+
return (c.x > 0 && c.x < size.x - 1) &&
25+
(c.y > 0 && c.y < size.y - 1);
26+
}
27+
28+
// Particle representation
29+
struct Particle {
30+
element: u32,
31+
movedThisFrame: bool,
32+
shade: u32,
33+
waterMass: u32 // unused here
34+
};
35+
36+
// Pseudo‐random generator
37+
fn rand(pos: vec2f, val: f32) -> f32 {
38+
return fract(pos.x * pos.y * val * 1000.0);
39+
}
40+
41+
// Pack a Particle into a single u32
42+
fn pack(p: Particle) -> u32 {
43+
var packed: u32 = 0u;
44+
packed |= (p.element & 0x7u);
45+
packed |= u32(p.movedThisFrame) << 3;
46+
packed |= ((p.shade & 0xFu) << 4);
47+
return packed;
48+
}
49+
50+
// Unpack a u32 into a Particle
51+
fn unpack(packed: u32) -> Particle {
52+
var pt: Particle;
53+
pt.element = packed & 0x7u;
54+
pt.movedThisFrame = ((packed >> 3) & 0x1u) != 0u;
55+
pt.shade = (packed >> 4) & 0xFu;
56+
pt.waterMass = 0u;
57+
return pt;
58+
}
59+
60+
// Fetch and decode a particle from the texture
61+
fn getParticle(coord: vec2i) -> Particle {
62+
let texel: vec4<u32> = textureLoad(sourceTexture, coord, 0);
63+
return unpack(texel.x);
64+
}
65+
66+
@fragment
67+
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
68+
var output: FragmentOutput;
69+
70+
// Determine integer texture size & sample coordinate
71+
let dims: vec2u = textureDimensions(sourceTexture);
72+
let size: vec2i = vec2i(dims);
73+
let coord: vec2i = vec2i(input.uv0 * vec2f(size));
74+
75+
// Out‐of‐bounds → write “wall”
76+
if (!isInBounds(coord, size)) {
77+
output.color = WALL;
78+
return output;
79+
}
80+
81+
// Mouse interaction
82+
let d: f32 = distance(uniform.mousePosition, input.uv0);
83+
let dir: i32 = i32(uniform.passNum % 3u) - 1;
84+
85+
let current = getParticle(coord);
86+
var nextState = current;
87+
88+
if (uniform.mouseButton == 1u && d < uniform.brushRadius) {
89+
nextState.element = uniform.brush;
90+
nextState.movedThisFrame = true;
91+
nextState.shade = u32(rand(input.uv0, uniform.randomVal * f32(uniform.passNum)) * 15.0);
92+
} else if (uniform.mouseButton == 2u && d < uniform.brushRadius) {
93+
nextState.element = AIR;
94+
nextState.movedThisFrame = false;
95+
nextState.shade = u32(rand(input.uv0, uniform.randomVal * f32(uniform.passNum)) * 15.0);
96+
}
97+
98+
// Gravity / flow logic
99+
let base: Particle = Particle(
100+
current.element,
101+
false,
102+
current.shade,
103+
0u
104+
);
105+
106+
if (base.element == AIR) {
107+
let above = getParticle(coord + vec2i(dir, -1));
108+
if (above.element != AIR && above.element != WALL) {
109+
nextState = above;
110+
nextState.movedThisFrame = true;
111+
}
112+
} else if (base.element != WALL) {
113+
let below = getParticle(coord + vec2i(-dir, 1));
114+
if (below.element == AIR && !below.movedThisFrame) {
115+
nextState = below;
116+
nextState.movedThisFrame = false;
117+
}
118+
}
119+
120+
// Write packed result back into the red channel
121+
let packedResult: u32 = pack(nextState);
122+
output.color = packedResult;
123+
return output;
124+
}

src/platform/graphics/constants.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,27 @@ export const typedArrayToType = {
21342134
export const typedArrayIndexFormats = [Uint8Array, Uint16Array, Uint32Array];
21352135
export const typedArrayIndexFormatsByteSize = [1, 2, 4];
21362136

2137+
// map of primitive GLSL types to their corresponding WGSL types
2138+
export const primitiveGlslToWgslTypeMap = new Map([
2139+
// floating-point
2140+
['float', 'f32'],
2141+
['vec2', 'vec2f'],
2142+
['vec3', 'vec3f'],
2143+
['vec4', 'vec4f'],
2144+
2145+
// signed integer
2146+
['int', 'i32'],
2147+
['ivec2', 'vec2i'],
2148+
['ivec3', 'vec3i'],
2149+
['ivec4', 'vec4i'],
2150+
2151+
// unsigned integer
2152+
['uint', 'u32'],
2153+
['uvec2', 'vec2u'],
2154+
['uvec3', 'vec3u'],
2155+
['uvec4', 'vec4u']
2156+
]);
2157+
21372158
/**
21382159
* Map of engine semantics into location on device in range 0..15 (note - semantics mapping to the
21392160
* same location cannot be used at the same time) organized in a way that ATTR0-ATTR7 do not

src/platform/graphics/shader-definition-utils.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
SEMANTIC_TEXCOORD3, SEMANTIC_TEXCOORD4, SEMANTIC_TEXCOORD5, SEMANTIC_TEXCOORD6, SEMANTIC_TEXCOORD7,
55
SEMANTIC_COLOR, SEMANTIC_BLENDINDICES, SEMANTIC_BLENDWEIGHT,
66
SHADERLANGUAGE_WGSL,
7-
SHADERLANGUAGE_GLSL
7+
SHADERLANGUAGE_GLSL,
8+
primitiveGlslToWgslTypeMap
89
} from './constants.js';
910
import gles3FS from './shader-chunks/frag/gles3.js';
1011
import gles3VS from './shader-chunks/vert/gles3.js';
@@ -81,6 +82,15 @@ class ShaderDefinitionUtils {
8182
Debug.assert(!options.fragmentDefines || options.fragmentDefines instanceof Map);
8283
Debug.assert(!options.fragmentIncludes || options.fragmentIncludes instanceof Map);
8384

85+
// Normalize fragmentOutputTypes to an array
86+
const normalizedOutputTypes = (options) => {
87+
let fragmentOutputTypes = options.fragmentOutputTypes ?? 'vec4';
88+
if (!Array.isArray(fragmentOutputTypes)) {
89+
fragmentOutputTypes = [fragmentOutputTypes];
90+
}
91+
return fragmentOutputTypes;
92+
};
93+
8494
const getDefines = (gpu, gl2, isVertex, options) => {
8595

8696
const deviceIntro = device.isWebGPU ? gpu : gl2;
@@ -90,11 +100,7 @@ class ShaderDefinitionUtils {
90100

91101
// Define the fragment shader output type, vec4 by default
92102
if (!isVertex) {
93-
// Normalize fragmentOutputTypes to an array
94-
let fragmentOutputTypes = options.fragmentOutputTypes ?? 'vec4';
95-
if (!Array.isArray(fragmentOutputTypes)) {
96-
fragmentOutputTypes = [fragmentOutputTypes];
97-
}
103+
const fragmentOutputTypes = normalizedOutputTypes(options);
98104

99105
for (let i = 0; i < device.maxColorAttachments; i++) {
100106
attachmentsDefine += `#define COLOR_ATTACHMENT_${i}\n`;
@@ -106,6 +112,26 @@ class ShaderDefinitionUtils {
106112
return attachmentsDefine + deviceIntro;
107113
};
108114

115+
const getDefinesWgsl = (isVertex, options) => {
116+
117+
let attachmentsDefine = '';
118+
119+
// Define the fragment shader output type, vec4 by default
120+
if (!isVertex) {
121+
const fragmentOutputTypes = normalizedOutputTypes(options);
122+
123+
// create alias for each output type
124+
for (let i = 0; i < device.maxColorAttachments; i++) {
125+
const glslOutType = fragmentOutputTypes[i] ?? 'vec4';
126+
const wgslOutType = primitiveGlslToWgslTypeMap.get(glslOutType);
127+
Debug.assert(wgslOutType, `Unknown output type translation: ${glslOutType} -> ${wgslOutType}`);
128+
attachmentsDefine += `alias pcOutType${i} = ${wgslOutType};\n`;
129+
}
130+
}
131+
132+
return attachmentsDefine;
133+
};
134+
109135
const name = options.name ?? 'Untitled';
110136
let vertCode;
111137
let fragCode;
@@ -117,13 +143,15 @@ class ShaderDefinitionUtils {
117143
if (wgsl) {
118144

119145
vertCode = `
146+
${getDefinesWgsl(true, options)}
120147
${wgslVS}
121148
${sharedWGSL}
122149
${vertexDefinesCode}
123150
${options.vertexCode}
124151
`;
125152

126153
fragCode = `
154+
${getDefinesWgsl(false, options)}
127155
${wgslFS}
128156
${sharedWGSL}
129157
${fragmentDefinesCode}

src/platform/graphics/webgpu/webgpu-shader-processor-wgsl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ class WebgpuShaderProcessorWGSL {
777777
let structCode = 'struct FragmentOutput {\n';
778778

779779
for (let i = 0; i < numRenderTargets; i++) {
780-
structCode += ` @location(${i}) color${i > 0 ? i : ''} : vec4f,\n`;
780+
structCode += ` @location(${i}) color${i > 0 ? i : ''} : pcOutType${i},\n`;
781781
}
782782

783783
// find if the src contains `.fragDepth =`, ignoring whitespace before = sign

src/scene/shader-lib/programs/lit-shader.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
SEMANTIC_BLENDINDICES, SEMANTIC_BLENDWEIGHT, SEMANTIC_COLOR, SEMANTIC_NORMAL, SEMANTIC_POSITION, SEMANTIC_TANGENT,
44
SEMANTIC_TEXCOORD0, SEMANTIC_TEXCOORD1,
55
SHADERLANGUAGE_GLSL,
6-
SHADERLANGUAGE_WGSL
6+
SHADERLANGUAGE_WGSL,
7+
primitiveGlslToWgslTypeMap
78
} from '../../../platform/graphics/constants.js';
89
import {
910
LIGHTSHAPE_PUNCTUAL,
@@ -35,13 +36,6 @@ const builtinAttributes = {
3536
vertex_boneIndices: SEMANTIC_BLENDINDICES
3637
};
3738

38-
export const varyingsWGSLTypes = new Map([
39-
['vec4', 'vec4f'],
40-
['vec3', 'vec3f'],
41-
['vec2', 'vec2f'],
42-
['float', 'f32']
43-
]);
44-
4539
class LitShader {
4640
/**
4741
* Shader code representing varyings.
@@ -330,7 +324,7 @@ class LitShader {
330324
varyings.forEach((type, name) => {
331325
this.varyingsCode += `#define VARYING_${name.toUpperCase()}\n`;
332326
this.varyingsCode += this.shaderLanguage === SHADERLANGUAGE_WGSL ?
333-
`varying ${name}: ${varyingsWGSLTypes.get(type)};\n` :
327+
`varying ${name}: ${primitiveGlslToWgslTypeMap.get(type)};\n` :
334328
`varying ${type} ${name};\n`;
335329
});
336330

0 commit comments

Comments
 (0)