Skip to content

Commit

Permalink
constants
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Nov 4, 2023
1 parent 4e7c400 commit a208b5b
Show file tree
Hide file tree
Showing 4 changed files with 390 additions and 1 deletion.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"noborder",
"noinvertdark",
"nonull",
"overridable",
"rasterizes",
"rdparty",
"rgba",
Expand Down
131 changes: 130 additions & 1 deletion webgpu/lessons/webgpu-constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,134 @@ Title: WebGPU Shader Constants
Description: The fundamentals of WebGPU
TOC: Constants

TBD
I'm not sure this topic deserves to considered an input to the shader.
but, from one point of view it is so lets cover it.

Constants, or more formally, *pipeline-overridable constants* are a type
of constant you declare in your shader but you can change when you use
that shader to create a pipeline.

A simple example would be something like this

```wgsl
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);
}
```

Using this fragment shader with the vertex shader from [the article on fundamentals](webgpu-fundamentals.html)

```wgsl
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
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);
}
```

Now if we use this shader as is we'll get a black triangle

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

But, we can change those constants, or "override" them when we specify the pipeline.

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

And now we get a pinkish color.

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

Pipeline overridable constants can only be scalar values so boolean (true/false),
integers, floating point numbers. They can not be vectors or matrices.

If you don't specify a value in the shader then you **must** supply one in
the pipeline. You can also give them a numeric id and then refer to them
by their id.

Example:

```wgsl
override red: f32; // Must be specified in the pipeline
@id(123) override green = 0.0; // May be specified by 'green' or by 123
override blue = 0.0;
@fragment fn fs() -> @location(0) vec4f {
return vec4f(red, green, blue, 1.0);
}
```

You might ask, what is the point? I can just as easily do this when I
create the WGSL. For example

```js
const red = 0.5;
const blue = 0.7;
const green = 1.0;

const code = `
const red = ${red};
const green = ${green};
const blue = ${blue};
@fragment fn fs() -> @location(0) vec4f {
return vec4f(red, green, blue, 1.0);
}
`;
```

Or even more directly

```js
const red = 0.5;
const blue = 0.7;
const green = 1.0;

const code = `
@fragment fn fs() -> @location(0) vec4f {
return vec4f(${red}, ${green}, ${blue}, 1.0);
}
`;
```

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.

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 would instead be useful for an iteration count, the size of an array (for
example the number of lights), etc...
132 changes: 132 additions & 0 deletions webgpu/webgpu-constants-override.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<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 module = device.createShaderModule({
label: 'our color via constants triangle shaders',
code: `
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
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);
}
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);
}
`,
});

const pipeline = device.createRenderPipeline({
label: 'our hardcoded triangle pipeline',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
},
fragment: {
module,
entryPoint: 'fs',
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 a208b5b

Please sign in to comment.