Passing all the rain pass configs through a giant struct into the rain shader. I've included the compute shader fields, because I have hope that the compute pass can use the same shader module.

This commit is contained in:
Rezmason
2021-10-30 12:53:26 -07:00
parent 1516f82554
commit cc75938fcb
4 changed files with 168 additions and 55 deletions

View File

@@ -4,12 +4,10 @@ WebGPU
First decent rainRender First decent rainRender
Port render pass 100% Port render pass 100%
For now, use noise instead of the data texture For now, use noise instead of the data texture
Get all the uniforms in
Categorize them into buffers
Port compute pass 100% Port compute pass 100%
Compute entry point can live in the same wgsl file, I think Compute entry point can live in the same wgsl file, I think
More uniforms! Categorize!
use textureLoad for texel access in render pipeline use textureLoad for texel access in render pipeline
Reorder the config fields
Render target Render target
Resize accordingly Resize accordingly
Put in its own module Put in its own module

View File

@@ -2,14 +2,25 @@ import std140 from "./std140.js";
import { getCanvasSize, loadTexture, makeUniformBuffer } from "./utils.js"; import { getCanvasSize, loadTexture, makeUniformBuffer } from "./utils.js";
const { mat4, vec3 } = glMatrix; const { mat4, vec3 } = glMatrix;
export default async (canvas, config) => { const rippleTypes = {
console.log(config); box: 0,
circle: 1,
};
const cycleStyles = {
cycleFasterWhenDimmed: 0,
cycleRandomly: 1,
};
const numVerticesPerQuad = 2 * 3;
export default async (canvas, config) => {
const adapter = await navigator.gpu.requestAdapter(); const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice(); const device = await adapter.requestDevice();
const canvasContext = canvas.getContext("webgpu"); const canvasContext = canvas.getContext("webgpu");
const presentationFormat = canvasContext.getPreferredFormat(adapter); const presentationFormat = canvasContext.getPreferredFormat(adapter);
const queue = device.queue;
console.table(device.limits);
const canvasConfig = { const canvasConfig = {
device, device,
@@ -19,39 +30,94 @@ export default async (canvas, config) => {
canvasContext.configure(canvasConfig); canvasContext.configure(canvasConfig);
const renderPassConfig = { const msdfTexturePromise = loadTexture(device, config.glyphTexURL);
colorAttachments: [ const rainRenderShaderPromise = fetch("shaders/wgsl/rainRenderPass.wgsl").then((response) => response.text());
{
view: canvasContext.getCurrentTexture().createView(),
loadValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: "store",
},
],
};
const NUM_VERTICES_PER_QUAD = 6; // The volumetric mode multiplies the number of columns
// to reach the desired density, and then overlaps them
const volumetric = config.volumetric;
const density = volumetric && config.effect !== "none" ? config.density : 1;
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
const numColumns = config.numColumns; // The volumetric mode requires us to create a grid of quads,
const numRows = config.numColumns; // rather than a single quad for our geometry
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
const numQuads = numQuadRows * numQuadColumns;
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
const msdfSampler = device.createSampler({ // Various effect-related values
magFilter: "linear", const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
minFilter: "linear", const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0;
}); const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
const showComputationTexture = config.effect === "none";
const msdfTexture = await loadTexture(device, config.glyphTexURL); const configData = [
// common
{ name: "animationSpeed", type: "f32", value: config.animationSpeed },
{ name: "glyphHeightToWidth", type: "f32", value: config.glyphHeightToWidth },
{ name: "resurrectingCodeRatio", type: "f32", value: config.resurrectingCodeRatio },
{ name: "numColumns", type: "i32", value: numColumns },
{ name: "numRows", type: "i32", value: numRows },
{ name: "showComputationTexture", type: "i32", value: showComputationTexture },
const configStructLayout = std140(["i32", "i32", "f32"]); // compute
const configBuffer = makeUniformBuffer(device, configStructLayout, [numColumns, numRows, config.glyphHeightToWidth]); { name: "brightnessThreshold", type: "f32", value: config.brightnessThreshold },
{ name: "brightnessOverride", type: "f32", value: config.brightnessOverride },
{ name: "brightnessDecay", type: "f32", value: config.brightnessDecay },
{ name: "cursorEffectThreshold", type: "f32", value: config.cursorEffectThreshold },
{ name: "cycleSpeed", type: "f32", value: config.cycleSpeed },
{ name: "cycleFrameSkip", type: "i32", value: config.cycleFrameSkip },
{ name: "fallSpeed", type: "f32", value: config.fallSpeed },
{ name: "hasSun", type: "i32", value: config.hasSun },
{ name: "hasThunder", type: "i32", value: config.hasThunder },
{ name: "raindropLength", type: "f32", value: config.raindropLength },
{ name: "rippleScale", type: "f32", value: config.rippleScale },
{ name: "rippleSpeed", type: "f32", value: config.rippleSpeed },
{ name: "rippleThickness", type: "f32", value: config.rippleThickness },
{ name: "cycleStyle", type: "i32", value: cycleStyle },
{ name: "rippleType", type: "i32", value: rippleType },
const msdfStructLayout = std140(["i32", "i32"]); // render
const msdfBuffer = makeUniformBuffer(device, msdfStructLayout, [config.glyphTextureColumns, config.glyphSequenceLength]); { name: "forwardSpeed", type: "f32", value: config.forwardSpeed },
{ name: "glyphVerticalSpacing", type: "f32", value: config.glyphVerticalSpacing },
{ name: "glyphEdgeCrop", type: "f32", value: config.glyphEdgeCrop },
{ name: "isPolar", type: "i32", value: config.isPolar },
{ name: "density", type: "f32", value: density },
{ name: "numQuadColumns", type: "i32", value: numQuadColumns },
{ name: "numQuadRows", type: "i32", value: numQuadRows },
{ name: "quadSize", type: "f32", value: quadSize },
{ name: "slantScale", type: "f32", value: slantScale },
{ name: "slantVec", type: "vec2<f32>", value: slantVec },
{ name: "volumetric", type: "i32", value: volumetric },
];
console.table(configData);
const timeStructLayout = std140(["f32", "i32"]); const configLayout = std140(configData.map((field) => field.type));
const timeBuffer = makeUniformBuffer(device, timeStructLayout); const configBuffer = makeUniformBuffer(
device,
configLayout,
configData.map((field) => field.value)
);
const sceneStructLayout = std140(["vec2<f32>", "mat4x4<f32>", "mat4x4<f32>"]); const msdfData = [
const sceneBuffer = makeUniformBuffer(device, sceneStructLayout); { name: "glyphSequenceLength", type: "i32", value: config.glyphSequenceLength },
{ name: "glyphTextureColumns", type: "i32", value: config.glyphTextureColumns },
];
console.table(msdfData);
const msdfLayout = std140(msdfData.map((field) => field.type));
const msdfBuffer = makeUniformBuffer(
device,
msdfLayout,
msdfData.map((field) => field.value)
);
const timeLayout = std140(["f32", "i32"]);
const timeBuffer = makeUniformBuffer(device, timeLayout);
const sceneLayout = std140(["vec2<f32>", "mat4x4<f32>", "mat4x4<f32>"]);
const sceneBuffer = makeUniformBuffer(device, sceneLayout);
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));
@@ -62,11 +128,16 @@ export default async (canvas, config) => {
const aspectRatio = canvasSize[0] / canvasSize[1]; const aspectRatio = canvasSize[0] / canvasSize[1];
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
queue.writeBuffer(sceneBuffer, 0, sceneStructLayout.build([screenSize, camera, transform])); device.queue.writeBuffer(sceneBuffer, 0, sceneLayout.build([screenSize, camera, transform]));
}; };
updateCameraBuffer(); updateCameraBuffer();
const [rainRenderShader] = await Promise.all(["shaders/wgsl/rainRenderPass.wgsl"].map(async (path) => (await fetch(path)).text())); const msdfSampler = device.createSampler({
magFilter: "linear",
minFilter: "linear",
});
const [msdfTexture, rainRenderShader] = await Promise.all([msdfTexturePromise, rainRenderShaderPromise]);
const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader }); const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader });
@@ -96,8 +167,6 @@ export default async (canvas, config) => {
}, },
}); });
console.log(device.limits);
const bindGroup = device.createBindGroup({ const bindGroup = device.createBindGroup({
layout: rainRenderPipeline.getBindGroupLayout(0), layout: rainRenderPipeline.getBindGroupLayout(0),
entries: [configBuffer, msdfBuffer, msdfSampler, msdfTexture.createView(), timeBuffer, sceneBuffer] entries: [configBuffer, msdfBuffer, msdfSampler, msdfTexture.createView(), timeBuffer, sceneBuffer]
@@ -114,10 +183,19 @@ export default async (canvas, config) => {
bundleEncoder.setPipeline(rainRenderPipeline); bundleEncoder.setPipeline(rainRenderPipeline);
bundleEncoder.setBindGroup(0, bindGroup); bundleEncoder.setBindGroup(0, bindGroup);
const numQuads = numColumns * numRows; bundleEncoder.draw(numVerticesPerQuad * numQuads, 1, 0, 0);
bundleEncoder.draw(NUM_VERTICES_PER_QUAD * numQuads, 1, 0, 0);
const renderBundles = [bundleEncoder.finish()]; const renderBundles = [bundleEncoder.finish()];
const renderPassConfig = {
colorAttachments: [
{
view: canvasContext.getCurrentTexture().createView(),
loadValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: "store",
},
],
};
let frame = 0; let frame = 0;
const renderLoop = (now) => { const renderLoop = (now) => {
@@ -131,7 +209,7 @@ export default async (canvas, config) => {
updateCameraBuffer(); updateCameraBuffer();
} }
queue.writeBuffer(timeBuffer, 0, timeStructLayout.build([now / 1000, frame])); device.queue.writeBuffer(timeBuffer, 0, timeLayout.build([now / 1000, frame]));
frame++; frame++;
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
@@ -141,7 +219,7 @@ export default async (canvas, config) => {
renderPass.executeBundles(renderBundles); renderPass.executeBundles(renderBundles);
renderPass.endPass(); renderPass.endPass();
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
queue.submit([commandBuffer]); device.queue.submit([commandBuffer]);
requestAnimationFrame(renderLoop); requestAnimationFrame(renderLoop);
}; };

View File

@@ -68,7 +68,7 @@ const buildStruct = (fields, values, buffer) => {
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
const view = views[fields[i].baseType]; const view = views[fields[i].baseType];
const value = values[i]; const value = values[i];
const array = value[Symbol.iterator] == null ? [value] : value; const array = value[Symbol.iterator] == null ? [Number(value)] : value;
view.set(array, fields[i].byteOffset); view.set(array, fields[i].byteOffset);
} }
return buffer; return buffer;

View File

@@ -1,19 +1,53 @@
let NUM_VERTICES_PER_QUAD:i32 = 6; let NUM_VERTICES_PER_QUAD:i32 = 6; // 2 * 3
let PI:f32 = 3.14159265359; let PI:f32 = 3.14159265359;
let TWO_PI:f32 = 6.28318530718; let TWO_PI:f32 = 6.28318530718;
let SQRT_2:f32 = 1.4142135623730951; let SQRT_2:f32 = 1.4142135623730951;
let SQRT_5:f32 = 2.23606797749979; let SQRT_5:f32 = 2.23606797749979;
[[block]] struct Config { [[block]] struct Config {
numColumns: i32; // common
numRows: i32; animationSpeed : f32;
glyphHeightToWidth: f32; glyphHeightToWidth : f32;
resurrectingCodeRatio : f32;
numColumns : i32;
numRows : i32;
showComputationTexture : i32;
// compute
brightnessThreshold : f32;
brightnessOverride : f32;
brightnessDecay : f32;
cursorEffectThreshold : f32;
cycleSpeed : f32;
cycleFrameSkip : i32;
fallSpeed : f32;
hasSun : i32;
hasThunder : i32;
raindropLength : f32;
rippleScale : f32;
rippleSpeed : f32;
rippleThickness : f32;
cycleStyle : i32;
rippleType : i32;
// render
forwardSpeed : f32;
glyphVerticalSpacing : f32;
glyphEdgeCrop : f32;
isPolar : i32;
density : f32;
numQuadColumns : i32;
numQuadRows : i32;
quadSize : f32;
slantScale : f32;
slantVec : vec2<f32>;
volumetric : i32;
}; };
[[group(0), binding(0)]] var<uniform> config:Config; [[group(0), binding(0)]] var<uniform> config:Config;
[[block]] struct MSDF { [[block]] struct MSDF {
glyphTextureColumns: i32;
glyphSequenceLength: i32; glyphSequenceLength: i32;
glyphTextureColumns: i32;
}; };
[[group(0), binding(1)]] var<uniform> msdf:MSDF; [[group(0), binding(1)]] var<uniform> msdf:MSDF;
[[group(0), binding(2)]] var msdfSampler: sampler; [[group(0), binding(2)]] var msdfSampler: sampler;
@@ -60,6 +94,9 @@ struct VertexOutput {
[[stage(vertex)]] fn vertMain([[builtin(vertex_index)]] VertexIndex:u32) -> VertexOutput { [[stage(vertex)]] fn vertMain([[builtin(vertex_index)]] VertexIndex:u32) -> VertexOutput {
var timePlaceholder = time.seconds;
var i = i32(VertexIndex); var i = i32(VertexIndex);
var quadIndex = i / NUM_VERTICES_PER_QUAD; var quadIndex = i / NUM_VERTICES_PER_QUAD;
@@ -68,16 +105,16 @@ struct VertexOutput {
f32(((i + 1) % NUM_VERTICES_PER_QUAD / 3)) f32(((i + 1) % NUM_VERTICES_PER_QUAD / 3))
); );
var cellPosition = vec2<i32>( var quadPosition = vec2<i32>(
quadIndex % config.numColumns, quadIndex % config.numQuadColumns,
quadIndex / config.numColumns quadIndex / config.numQuadColumns
); );
var position = cornerPosition; var position = cornerPosition;
position = position + vec2<f32>(cellPosition); position = position + vec2<f32>(quadPosition);
position = position / vec2<f32>( position = position / vec2<f32>(
f32(config.numColumns), f32(config.numQuadColumns),
f32(config.numRows) f32(config.numQuadRows)
); );
position = 1.0 - position * 2.0; position = 1.0 - position * 2.0;
@@ -86,8 +123,8 @@ struct VertexOutput {
var depth:f32 = 0.0; var depth:f32 = 0.0;
// depth = -0.5 // depth = -0.5
// + sin(time.seconds * 2.0 + f32(cellPosition.x) / f32(config.numColumns) * 10.0) * 0.2 // + sin(time.seconds * 2.0 + f32(quadPosition.x) / f32(config.numQuadColumns) * 10.0) * 0.2
// + sin(time.seconds * 2.0 + f32(cellPosition.y) / f32(config.numColumns) * 10.0) * 0.2; // + sin(time.seconds * 2.0 + f32(quadPosition.y) / f32(config.numQuadRows) * 10.0) * 0.2;
var pos:vec4<f32> = vec4<f32>(position, depth, 1.0); var pos:vec4<f32> = vec4<f32>(position, depth, 1.0);
pos.x = pos.x / config.glyphHeightToWidth; pos.x = pos.x / config.glyphHeightToWidth;
@@ -103,8 +140,8 @@ struct VertexOutput {
[[stage(fragment)]] fn fragMain([[location(0)]] UV:vec2<f32>) -> [[location(0)]] vec4<f32> { [[stage(fragment)]] fn fragMain([[location(0)]] UV:vec2<f32>) -> [[location(0)]] vec4<f32> {
var color:vec4<f32> = textureSample(msdfTexture, msdfSampler, UV / f32(msdf.glyphTextureColumns)); var color:vec4<f32> = textureSample(msdfTexture, msdfSampler, UV / f32(msdf.glyphTextureColumns));
// color.b = color.b * (sin(time.seconds * TWO_PI) * 0.5 + 0.5);
color.b = color.b * f32(time.frames / 60 % 2); color = vec4<f32>(UV, 0.5, 1.0);
return color; return color;
} }