Skip to content

Commit

Permalink
Add 2 more example to constants to entry points are indpendent. (#169)
Browse files Browse the repository at this point in the history
I'm not sure where to put this basic fact that entry points
are independent. Maybe it belongs in its own article about
shader modules and pipelines as the 2nd article after
fundamentals? It feels mostly like trivia to me ... until
it's not.
  • Loading branch information
greggman authored Sep 18, 2024
1 parent 4e8f7bb commit 86b5b01
Show file tree
Hide file tree
Showing 3 changed files with 486 additions and 6 deletions.
178 changes: 172 additions & 6 deletions webgpu/lessons/webgpu-constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,178 @@ The difference is, pipeline overridable constants can be applied AFTER
the shader module has been created which makes them technically faster
to apply then creating a new shader module. Creating a pipeline is
not a fast operation though so it's not clear how much time this saves
on the overall process of creating a pipeline. I'd suspect the more
complex the shader the more time it saves.
on the overall process of creating a pipeline. It's possible though,
that the WebGPU implementation can use information from the first time
you created a pipeline with certain constants so that the next time
you create it with different constants, much less work is done.

In any case, it is one way to get some small amount of data into a shader.

It is **not** common to use pipeline overridable constants to pass in a color.
That example was used because it's easy to understand and to show the results.
It *might* be useful for an iteration count, the size of an array (for
example the number of lights), etc...
## entry points are independently evaluated

It's also important to remember that entry points are evaluated in
isolation as was partially covered in
[the article on inter-stage variables](webgpu-inter-stage-variables.html#a-builtin-position).

It's as though the code passed to `createShaderModule` was striped
of everything not relevant to the current entry point. Pipeline
override constants are applied, then, the shader for that entry point is
created.

Let's expand our example above. We'll change the shader so both the vertex
and fragment stages use the constants. We'll pass the vertex stage's value
to the fragment stage. We'll then draw every other vertical strip of 50
pixels with one value or the other.

```wgsl
+struct VOut {
+ @builtin(position) pos: vec4f,
+ @location(0) color: vec4f,
+}
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
-) -> @builtin(position) vec4f {
+) -> VOut {
let pos = array(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
- return vec4f(pos[vertexIndex], 0.0, 1.0);
+ return VOut(
+ vec4f(pos[vertexIndex], 0.0, 1.0),
+ vec4f(red, green, blue, 1),
+ );
}
override red = 0.0;
override green = 0.0;
override blue = 0.0;
-@fragment fn fs() -> @location(0) vec4f {
- return vec4f(red, green, blue, 1.0);
+@fragment fn fs(v: VOut) -> @location(0) vec4f {
+ let colorFromVertexShader = v.color;
+ let colorFromFragmentShader = vec4f(red, green, blue, 1.0);
+ // select one color or the other every 50 pixels
+ return select(
+ colorFromVertexShader,
+ colorFromFragmentShader,
+ v.pos.x % 100.0 > 50.0);
}
```

Now we'll pass different constants into the each entry point

```js
const pipeline = device.createRenderPipeline({
label: 'our hardcoded triangle pipeline',
layout: 'auto',
vertex: {
module,
+ constants: {
+ red: 1,
+ green: 1,
+ blue: 0,
+ },
},
fragment: {
module,
targets: [{ format: presentationFormat }],
constants: {
red: 1,
green: 0.5,
blue: 1,
},
},
});
```

The result shows the constants were different in each stage

{{{example url="../webgpu-constants-override-set-entry-points.html"}}}

Again, functionally, the fact that we used one shader module with one WGSL `code`
is just a convenience. The code above is functionally equivalent to

```js
const vertexModule = device.createShaderModule({
code: `
struct VOut {
@builtin(position) pos: vec4f,
@location(0) color: vec4f,
}
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> VOut {
let pos = array(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
return VOut(
vec4f(pos[vertexIndex], 0.0, 1.0),
vec4f(red, green, blue, 1),
);
}
override red = 0.0;
override green = 0.0;
override blue = 0.0;
`,
});

const fragmentModule = device.createShaderModule({
code: `
struct VOut {
@builtin(position) pos: vec4f,
@location(0) color: vec4f,
}
override red = 0.0;
override green = 0.0;
override blue = 0.0;
@fragment fn fs(v: VOut) -> @location(0) vec4f {
let colorFromVertexShader = v.color;
let colorFromFragmentShader = vec4f(red, green, blue, 1.0);
// select one color or the other every 50 pixels
return select(
colorFromVertexShader,
colorFromFragmentShader,
v.pos.x % 100.0 > 50.0);
}
`,
});

const pipeline = device.createRenderPipeline({
label: 'our hardcoded triangle pipeline',
layout: 'auto',
vertex: {
* module: vertexModule,
constants: {
red: 1,
green: 1,
blue: 0,
},
},
fragment: {
* module: fragmentModule,
targets: [{ format: presentationFormat }],
constants: {
red: 1,
green: 0.5,
blue: 1,
},
},
});
```

{{{example url="../webgpu-constants-override-separate-modules.html"}}}

Note: It is **not** common to use pipeline overridable constants to pass in a color.
We used a color because it's easy to understand and to show the results.
163 changes: 163 additions & 0 deletions webgpu/webgpu-constants-override-separate-modules.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGPU Simple Triangle with Canvas CSS</title>
<style>
@import url(resources/webgpu-lesson.css);
html, body {
margin: 0; /* remove the default margin */
height: 100%; /* make the html,body fill the page */
}
canvas {
display: block; /* make the canvas act like a block */
width: 100%; /* make the canvas fill its container */
height: 100%;
}
</style>
</head>
<body>
<canvas></canvas>
</body>
<script type="module">
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail('need a browser that supports WebGPU');
return;
}

// Get a WebGPU context from the canvas and configure it
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
});

const vertexModule = device.createShaderModule({
code: `
struct VOut {
@builtin(position) pos: vec4f,
@location(0) color: vec4f,
}
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> VOut {
let pos = array(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
return VOut(
vec4f(pos[vertexIndex], 0.0, 1.0),
vec4f(red, green, blue, 1),
);
}
override red = 0.0;
override green = 0.0;
override blue = 0.0;
`,
});

const fragmentModule = device.createShaderModule({
code: `
struct VOut {
@builtin(position) pos: vec4f,
@location(0) color: vec4f,
}
override red = 0.0;
override green = 0.0;
override blue = 0.0;
@fragment fn fs(v: VOut) -> @location(0) vec4f {
let colorFromVertexShader = v.color;
let colorFromFragmentShader = vec4f(red, green, blue, 1.0);
// select one color or the other every 50 pixels
return select(
colorFromVertexShader,
colorFromFragmentShader,
v.pos.x % 100.0 > 50.0);
}
`,
});

const pipeline = device.createRenderPipeline({
label: 'our hardcoded triangle pipeline',
layout: 'auto',
vertex: {
module: vertexModule,
constants: {
red: 1,
green: 1,
blue: 0,
},
},
fragment: {
module: fragmentModule,
targets: [{ format: presentationFormat }],
constants: {
red: 1,
green: 0.5,
blue: 1,
},
},
});

const renderPassDescriptor = {
label: 'our basic canvas renderPass',
colorAttachments: [
{
// view: <- to be filled out when we render
clearValue: [0.3, 0.3, 0.3, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
};

function render() {
// Get the current texture from the canvas context and
// set it as the texture to render to.
renderPassDescriptor.colorAttachments[0].view =
context.getCurrentTexture().createView();

const encoder = device.createCommandEncoder({ label: 'our encoder' });
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.draw(3); // call our vertex shader 3 times
pass.end();

const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
}

const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
const width = entry.contentBoxSize[0].inlineSize;
const height = entry.contentBoxSize[0].blockSize;
canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
// re-render
render();
}
});
observer.observe(canvas);
}

function fail(msg) {
// eslint-disable-next-line no-alert
alert(msg);
}

main();
</script>
</html>
Loading

0 comments on commit 86b5b01

Please sign in to comment.