diff --git a/TODO.txt b/TODO.txt index b0dd5d5..a2e4878 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,17 @@ TODO: Seems like bloom size and resolution impact the REGL and WebGPU bloom implementations differently + Ossify the REGL bloom + Load all the GLSL from files + Move high pass into WebGPU bloom + +Live config update roadmap + +Write an explanation of the rain pass (and include images) + Compute + Volumetric quads + Fullscreen quad and spacial mapping + MSDFs Audio system Toggle (or number representing frequency) @@ -37,12 +48,6 @@ WebGPU Improve loop support -Write an explanation of the rain pass (and include images) - Compute - Volumetric quads - Fullscreen quad and spacial mapping - MSDFs - Idea: Build a UI Replace versions with presets Simple changes update the values diff --git a/js/regl/mirrorPass.js b/js/regl/mirrorPass.js index f38f890..16af2e1 100644 --- a/js/regl/mirrorPass.js +++ b/js/regl/mirrorPass.js @@ -1,4 +1,4 @@ -import { loadImage, loadText, makePassFBO, makePass } from "./utils.js"; +import { loadText, makePassFBO, makePass } from "./utils.js"; let start; const numClicks = 5; diff --git a/js/regl/quiltPass.js b/js/regl/quiltPass.js index e8fd05a..fcef01c 100644 --- a/js/regl/quiltPass.js +++ b/js/regl/quiltPass.js @@ -1,4 +1,4 @@ -import { loadImage, loadText, makePassFBO, makePass } from "./utils.js"; +import { loadText, makePassFBO, makePass } from "./utils.js"; // Multiplies the rendered rain and bloom by a loaded in image diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index 1e0f04d..0a49363 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -134,8 +134,8 @@ export default ({ regl, config, lkg }) => { // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen const glyphMSDF = loadImage(regl, config.glyphMSDFURL); const glintMSDF = loadImage(regl, config.glintMSDFURL); - const baseTexture = loadImage(regl, config.baseTextureURL); - const glintTexture = loadImage(regl, config.glintTextureURL); + const baseTexture = loadImage(regl, config.baseTextureURL, true); + const glintTexture = loadImage(regl, config.glintTextureURL, true); const rainPassVert = loadText("shaders/glsl/rainPass.vert.glsl"); const rainPassFrag = loadText("shaders/glsl/rainPass.frag.glsl"); const output = makePassFBO(regl, config.useHalfFloat); diff --git a/js/regl/utils.js b/js/regl/utils.js index f1491c2..efbb284 100644 --- a/js/regl/utils.js +++ b/js/regl/utils.js @@ -1,11 +1,10 @@ -const makePassTexture = (regl, halfFloat, mipmap) => +const makePassTexture = (regl, halfFloat) => regl.texture({ width: 1, height: 1, type: halfFloat ? "half float" : "uint8", wrap: "clamp", - minFilter: "mipmap", - min: mipmap ? "mipmap" : "linear", + min: "linear", mag: "linear", }); @@ -26,7 +25,9 @@ const makeDoubleBuffer = (regl, props) => { }; }; -const loadImage = (regl, url) => { +const isPowerOfTwo = (x) => Math.log2(x) % 1 == 0; + +const loadImage = (regl, url, mipmap) => { let texture = regl.texture([[0]]); let loaded = false; return { @@ -55,10 +56,17 @@ const loadImage = (regl, url) => { data.src = url; await data.decode(); loaded = true; + if (mipmap) { + if (!isPowerOfTwo(data.width) || !isPowerOfTwo(data.height)) { + console.warn(`Can't mipmap a non-power-of-two image: ${url}`); + } + mipmap = false; + } texture = regl.texture({ data, mag: "linear", - min: "linear", + min: mipmap ? "mipmap" : "linear", + flipY: true, }); } })(), diff --git a/js/webgpu/utils.js b/js/webgpu/utils.js index f10fe8a..f0b63d7 100644 --- a/js/webgpu/utils.js +++ b/js/webgpu/utils.js @@ -1,7 +1,12 @@ -/* -// TODO: switch back to this impl once it doesn't break on FF Nightly - const loadTexture = async (device, url) => { + if (url == null) { + return device.createTexture({ + size: [1, 1, 1], + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + const response = await fetch(url); const data = await response.blob(); const source = await createImageBitmap(data); @@ -13,42 +18,7 @@ const loadTexture = async (device, url) => { usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, }); - device.queue.copyExternalImageToTexture({ source }, { texture }, size); - - return texture; -}; -*/ - -const loadTexture = async (device, url) => { - if (url == null) { - return device.createTexture({ - size: [1, 1, 1], - format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, - }); - } - - const image = new Image(); - image.crossOrigin = "Anonymous"; - image.src = url; - await image.decode(); - const { width, height } = image; - const size = [width, height, 1]; - - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - ctx.drawImage(image, 0, 0); - const source = ctx.getImageData(0, 0, width, height).data; - - const texture = device.createTexture({ - size, - format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, - }); - - device.queue.writeTexture({ texture }, source, { bytesPerRow: 4 * width }, size); + device.queue.copyExternalImageToTexture({ source, flipY: true }, { texture }, size); return texture; }; diff --git a/shaders/glsl/rainPass.frag.glsl b/shaders/glsl/rainPass.frag.glsl index 197b19e..10b828c 100644 --- a/shaders/glsl/rainPass.frag.glsl +++ b/shaders/glsl/rainPass.frag.glsl @@ -102,13 +102,13 @@ vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth, vec2 uv) { vec2 getSymbolUV(float index) { float symbolX = modI(index, glyphTextureGridSize.x); float symbolY = (index - symbolX) / glyphTextureGridSize.x; + symbolY = glyphTextureGridSize.y - symbolY - 1.; return vec2(symbolX, symbolY); } vec2 getSymbol(vec2 uv, float index) { // resolve UV to cropped position of glyph in MSDF texture uv = fract(uv * vec2(numColumns, numRows)); - uv.y = 1.0 - uv.y; // y-flip uv -= 0.5; uv *= clamp(1. - glyphEdgeCrop, 0., 1.); uv += 0.5; diff --git a/shaders/wgsl/imagePass.wgsl b/shaders/wgsl/imagePass.wgsl index b2e3bb9..9b72a84 100644 --- a/shaders/wgsl/imagePass.wgsl +++ b/shaders/wgsl/imagePass.wgsl @@ -33,7 +33,7 @@ fn getBrightness(uv : vec2) -> vec4 { var uv = vec2(coord) / vec2(screenSize); - var bgColor = textureSampleLevel( backgroundTex, linearSampler, uv, 0.0 ).rgb; + var bgColor = textureSampleLevel( backgroundTex, linearSampler, vec2(uv.x, 1.0 - uv.y), 0.0 ).rgb; // Combine the texture and bloom, then blow it out to reveal more of the image var brightness = getBrightness(uv); diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 5cc3d13..43c8c6a 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -352,11 +352,10 @@ fn computeEffect (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scre // Vertex shader -// Firefox Nightly (that is to say, Naga) currently has a bug that mixes up these values from ones in the uniforms. -// var quadCorners : array, NUM_VERTICES_PER_QUAD> = array, NUM_VERTICES_PER_QUAD>( -// vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0), -// vec2(1.0, 1.0), vec2(0.0, 1.0), vec2(1.0, 0.0) -// ); +var quadCorners : array, NUM_VERTICES_PER_QUAD> = array, NUM_VERTICES_PER_QUAD>( + vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), + vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) +); @vertex fn vertMain(input : VertInput) -> VertOutput { @@ -368,8 +367,7 @@ fn computeEffect (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scre var i = i32(input.index); var quadIndex = i / NUM_VERTICES_PER_QUAD; - // var quadCorner = quadCorners[i % NUM_VERTICES_PER_QUAD]; - var quadCorner = vec2(f32(i % 2), f32((i + 1) % 6 / 3)); + var quadCorner = quadCorners[i % NUM_VERTICES_PER_QUAD]; var quadPosition = vec2( f32(quadIndex % i32(quadGridSize.x)), @@ -487,13 +485,13 @@ fn getBrightness(raindrop : vec4, effect : vec4, uv : vec2, quadD fn getSymbolUV(symbol : i32) -> vec2 { var symbolX = symbol % config.glyphTextureGridSize.x; var symbolY = symbol / config.glyphTextureGridSize.x; + symbolY = config.glyphTextureGridSize.y - symbolY - 1; return vec2(f32(symbolX), f32(symbolY)); } fn getSymbol(cellUV : vec2, index : i32) -> vec2 { // resolve UV to cropped position of glyph in MSDF texture var uv = fract(cellUV * config.gridSize); - uv.y = 1.0 - uv.y; // y-flip uv -= 0.5; uv *= clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0); uv += 0.5; diff --git a/shaders/wgsl/stripePass.wgsl b/shaders/wgsl/stripePass.wgsl index 5bd9ed2..6ef82d2 100644 --- a/shaders/wgsl/stripePass.wgsl +++ b/shaders/wgsl/stripePass.wgsl @@ -51,7 +51,7 @@ fn getBrightness(uv : vec2) -> vec4 { var uv = vec2(coord) / vec2(screenSize); - var color = textureSampleLevel( stripeTex, linearSampler, uv, 0.0 ).rgb; + var color = textureSampleLevel( stripeTex, linearSampler, vec2(uv.x, 1.0 - uv.y), 0.0 ).rgb; var brightness = getBrightness(uv);