We have ping-pong! And all the rain pass's shaders can coexist in one module! And I met and received ample help from @kainino0x and others on the WebGPU Matrix chat!

This commit is contained in:
Rezmason
2021-11-01 00:28:40 -07:00
parent 6f58882851
commit 1c1b1e4f03
3 changed files with 63 additions and 35 deletions

View File

@@ -3,12 +3,6 @@ TODO:
WebGPU WebGPU
First decent rainRender First decent rainRender
Port compute pass 100% Port compute pass 100%
Compute entry point can live in the same wgsl file, I think
ping-pong buffers
Bind both ping-pong buffers in render pass
The frame integer can be used by the render code to reference the correct one
Reorder the config fields
Reconsider how the bind groups are organized?
Render target Render target
Resize accordingly Resize accordingly
Put in its own module Put in its own module

View File

@@ -31,17 +31,18 @@ export default async (canvas, config) => {
canvasContext.configure(canvasConfig); canvasContext.configure(canvasConfig);
const msdfTexturePromise = loadTexture(device, config.glyphTexURL); const msdfTexturePromise = loadTexture(device, config.glyphTexURL);
const rainRenderShaderPromise = fetch("shaders/wgsl/rainRenderPass.wgsl").then((response) => response.text()); const rainShaderPromise = fetch("shaders/wgsl/rainPass.wgsl").then((response) => response.text());
// The volumetric mode multiplies the number of columns // The volumetric mode multiplies the number of columns
// to reach the desired density, and then overlaps them // to reach the desired density, and then overlaps them
const volumetric = config.volumetric; const volumetric = config.volumetric;
const density = volumetric && config.effect !== "none" ? config.density : 1; const density = volumetric && config.effect !== "none" ? config.density : 1;
const gridSize = [config.numColumns * density, config.numColumns]; const gridSize = [config.numColumns * density, config.numColumns];
const numCells = gridSize[0] * gridSize[1];
// The volumetric mode requires us to create a grid of quads, // The volumetric mode requires us to create a grid of quads,
// rather than a single quad for our geometry // rather than a single quad for our geometry
const numQuads = volumetric ? gridSize[0] * gridSize[1] : 1; const numQuads = volumetric ? numCells : 1;
// Various effect-related values // Various effect-related values
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1; const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
@@ -102,6 +103,13 @@ export default async (canvas, config) => {
const sceneLayout = std140(["vec2<f32>", "mat4x4<f32>", "mat4x4<f32>"]); const sceneLayout = std140(["vec2<f32>", "mat4x4<f32>", "mat4x4<f32>"]);
const sceneBuffer = makeUniformBuffer(device, sceneLayout); const sceneBuffer = makeUniformBuffer(device, sceneLayout);
const cellBufferDescriptor = {
size: numCells * std140(["vec4<f32>"]).size, // TODO: Is this correct?
usage: GPUBufferUsage.STORAGE,
};
const cellsPingBuffer = device.createBuffer(cellBufferDescriptor);
const cellsPongBuffer = device.createBuffer(cellBufferDescriptor);
const transform = mat4.create(); const transform = mat4.create();
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
const camera = mat4.create(); const camera = mat4.create();
@@ -120,13 +128,13 @@ export default async (canvas, config) => {
minFilter: "linear", minFilter: "linear",
}); });
const [msdfTexture, rainRenderShader] = await Promise.all([msdfTexturePromise, rainRenderShaderPromise]); const [msdfTexture, rainShader] = await Promise.all([msdfTexturePromise, rainShaderPromise]);
const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader }); const rainShaderModule = device.createShaderModule({ code: rainShader });
const rainComputePipeline = device.createComputePipeline({ const rainComputePipeline = device.createComputePipeline({
compute: { compute: {
module: rainRenderShaderModule, module: rainShaderModule,
entryPoint: "computeMain", entryPoint: "computeMain",
}, },
}); });
@@ -139,11 +147,11 @@ export default async (canvas, config) => {
const rainRenderPipeline = device.createRenderPipeline({ const rainRenderPipeline = device.createRenderPipeline({
vertex: { vertex: {
module: rainRenderShaderModule, module: rainShaderModule,
entryPoint: "vertMain", entryPoint: "vertMain",
}, },
fragment: { fragment: {
module: rainRenderShaderModule, module: rainShaderModule,
entryPoint: "fragMain", entryPoint: "fragMain",
targets: [ targets: [
{ {
@@ -157,9 +165,9 @@ export default async (canvas, config) => {
}, },
}); });
const renderBindGroup = device.createBindGroup({ const computeBindGroup = device.createBindGroup({
layout: rainRenderPipeline.getBindGroupLayout(0), layout: rainComputePipeline.getBindGroupLayout(0),
entries: [configBuffer, timeBuffer, sceneBuffer, msdfSampler, msdfTexture.createView()] entries: [configBuffer, timeBuffer, cellsPingBuffer, cellsPongBuffer]
.map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource)) .map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource))
.map((resource, binding) => ({ .map((resource, binding) => ({
binding, binding,
@@ -167,9 +175,9 @@ export default async (canvas, config) => {
})), })),
}); });
const computeBindGroup = device.createBindGroup({ const renderBindGroup = device.createBindGroup({
layout: rainComputePipeline.getBindGroupLayout(0), layout: rainRenderPipeline.getBindGroupLayout(0),
entries: [configBuffer, timeBuffer] entries: [configBuffer, timeBuffer, sceneBuffer, msdfSampler, msdfTexture.createView(), cellsPingBuffer, cellsPongBuffer]
.map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource)) .map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource))
.map((resource, binding) => ({ .map((resource, binding) => ({
binding, binding,
@@ -212,8 +220,6 @@ export default async (canvas, config) => {
device.queue.writeBuffer(timeBuffer, 0, timeLayout.build([now / 1000, frame])); device.queue.writeBuffer(timeBuffer, 0, timeLayout.build([now / 1000, frame]));
frame++; frame++;
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
const encoder = device.createCommandEncoder(); const encoder = device.createCommandEncoder();
const computePass = encoder.beginComputePass(); const computePass = encoder.beginComputePass();
@@ -222,6 +228,7 @@ export default async (canvas, config) => {
computePass.dispatch(...gridSize, 1); computePass.dispatch(...gridSize, 1);
computePass.endPass(); computePass.endPass();
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
const renderPass = encoder.beginRenderPass(renderPassConfig); const renderPass = encoder.beginRenderPass(renderPassConfig);
renderPass.executeBundles(renderBundles); renderPass.executeBundles(renderBundles);
renderPass.endPass(); renderPass.endPass();

View File

@@ -1,11 +1,3 @@
let NUM_VERTICES_PER_QUAD : i32 = 6; // 2 * 3
let PI : f32 = 3.14159265359;
let TWO_PI : f32 = 6.28318530718;
let SQRT_2 : f32 = 1.4142135623730951;
let SQRT_5 : f32 = 2.23606797749979;
// Bound resources
[[block]] struct Config { [[block]] struct Config {
// common // common
animationSpeed : f32; animationSpeed : f32;
@@ -43,25 +35,36 @@ let SQRT_5 : f32 = 2.23606797749979;
slantVec : vec2<f32>; slantVec : vec2<f32>;
volumetric : i32; volumetric : i32;
}; };
[[group(0), binding(0)]] var<uniform> config : Config;
[[block]] struct Time { [[block]] struct Time {
seconds : f32; seconds : f32;
frames : i32; frames : i32;
}; };
[[group(0), binding(1)]] var<uniform> time : Time;
[[block]] struct Scene { [[block]] struct Scene {
screenSize : vec2<f32>; screenSize : vec2<f32>;
camera : mat4x4<f32>; camera : mat4x4<f32>;
transform : mat4x4<f32>; transform : mat4x4<f32>;
}; };
[[group(0), binding(2)]] var<uniform> scene : Scene;
[[block]] struct CellData {
cells: array<vec4<f32>>;
};
// Shared bindings
[[group(0), binding(0)]] var<uniform> config : Config;
[[group(0), binding(1)]] var<uniform> time : Time;
// Compute bindings
[[group(0), binding(2)]] var<storage, read_write> cellsPing_RW : CellData;
[[group(0), binding(3)]] var<storage, read_write> cellsPong_RW : CellData;
// Render bindings
[[group(0), binding(2)]] var<uniform> scene : Scene;
[[group(0), binding(3)]] var msdfSampler : sampler; [[group(0), binding(3)]] var msdfSampler : sampler;
[[group(0), binding(4)]] var msdfTexture : texture_2d<f32>; [[group(0), binding(4)]] var msdfTexture : texture_2d<f32>;
[[group(0), binding(5)]] var<storage, read> cellsPing_RO : CellData;
[[group(0), binding(6)]] var<storage, read> cellsPong_RO : CellData;
// Shader params // Shader params
@@ -84,6 +87,14 @@ struct FragOutput {
[[location(0)]] color : vec4<f32>; [[location(0)]] color : vec4<f32>;
}; };
// Constants
let NUM_VERTICES_PER_QUAD : i32 = 6; // 2 * 3
let PI : f32 = 3.14159265359;
let TWO_PI : f32 = 6.28318530718;
let SQRT_2 : f32 = 1.4142135623730951;
let SQRT_5 : f32 = 2.23606797749979;
// Helper functions for generating randomness, borrowed from elsewhere // Helper functions for generating randomness, borrowed from elsewhere
fn randomFloat( uv : vec2<f32> ) -> f32 { fn randomFloat( uv : vec2<f32> ) -> f32 {
@@ -106,8 +117,16 @@ fn wobble(x : f32) -> f32 {
// Compute shader // Compute shader
[[stage(compute), workgroup_size(1, 1, 1)]] fn computeMain(input : ComputeInput) { [[stage(compute), workgroup_size(1, 1, 1)]] fn computeMain(input : ComputeInput) {
var animationSpeed = config.animationSpeed; // TODO: remove
var hasSun = bool(config.hasSun); // TODO: remove var hasSun = bool(config.hasSun); // TODO: remove
var seconds = time.seconds; // TODO: remove var seconds = time.seconds; // TODO: remove
var row = i32(input.id.y);
var column = i32(input.id.x);
var i = row * i32(config.gridSize.x);
cellsPing_RW.cells[i] = vec4<f32>((1.0 + time.seconds * 0.1) % 1.0, 0.0, 0.0, 0.0);
cellsPong_RW.cells[i] = vec4<f32>((0.5 + time.seconds * 0.1) % 1.0, 0.5, 0.0, 0.0);
} }
// Vertex shader // Vertex shader
@@ -139,7 +158,12 @@ fn wobble(x : f32) -> f32 {
var uv = (quadPosition + quadCorner) / quadGridSize; var uv = (quadPosition + quadCorner) / quadGridSize;
// Retrieve the quad's glyph data // Retrieve the quad's glyph data
var vGlyph = vec4<f32>(1.0, 0.0, randomFloat(vec2<f32>(quadPosition.x, 1.0)), 0.0); // TODO : texture2D(state, quadPosition / quadGridSize); var vGlyph: vec4<f32>;
if ((time.frames / 100) % 2 == 0) {
vGlyph = cellsPing_RO.cells[quadIndex];
} else {
vGlyph = cellsPong_RO.cells[quadIndex];
}
// Calculate the quad's depth // Calculate the quad's depth
var quadDepth = 0.0; var quadDepth = 0.0;
@@ -197,6 +221,9 @@ fn getSymbolUV(glyphCycle : f32) -> vec2<f32> {
[[stage(fragment)]] fn fragMain(input : VertOutput) -> FragOutput { [[stage(fragment)]] fn fragMain(input : VertOutput) -> FragOutput {
var firstCellA = cellsPing_RO.cells[0]; // TODO: remove
var firstCellB = cellsPong_RO.cells[0]; // TODO: remove
var volumetric = bool(config.volumetric); var volumetric = bool(config.volumetric);
var uv = input.uv; var uv = input.uv;