From dccfdb96d9bb30694510c3247ea1bd5ed023018a Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 20 Sep 2024 14:09:52 -0700 Subject: [PATCH 1/7] alphaToCoverage: show resolve result; draw twice with different colors/alphas This demonstrates how different alpha-to-coverage algorithms produce different blending results. --- sample/alphaToCoverage/main.ts | 91 +++++++++++++------ sample/alphaToCoverage/meta.ts | 14 ++- .../renderWithAlphaToCoverage.wgsl | 12 +-- .../showMultisampleTexture.wgsl | 29 +++++- 4 files changed, 105 insertions(+), 41 deletions(-) diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts index 5a8dd324..96381fed 100644 --- a/sample/alphaToCoverage/main.ts +++ b/sample/alphaToCoverage/main.ts @@ -13,18 +13,31 @@ quitIfWebGPUNotAvailable(adapter, device); // GUI controls // -const kAlphaSteps = 64; - const kInitConfig = { width: 8, height: 8, - alpha: 4, + color1: 0x0000ff, + alpha1: 127, + color2: 0xff0000, + alpha2: 16, pause: false, }; const config = { ...kInitConfig }; const updateConfig = () => { - const data = new Float32Array([config.alpha / kAlphaSteps]); - device.queue.writeBuffer(bufConfig, 0, data); + // Update the colors in the (instance-step-mode) vertex buffer + const data = new Uint8Array([ + // instance 0 color + (config.color1 >> 16) & 0xff, // R + (config.color1 >> 8) & 0xff, // G + (config.color1 >> 0) & 0xff, // B + config.alpha1, + // instance 1 color + (config.color2 >> 16) & 0xff, // R + (config.color2 >> 8) & 0xff, // G + (config.color2 >> 0) & 0xff, // B + config.alpha2, + ]); + device.queue.writeBuffer(bufInstanceColors, 0, data); }; const gui = new GUI(); @@ -41,12 +54,16 @@ const gui = new GUI(); settings.add(config, 'width', 1, 16, 1); settings.add(config, 'height', 1, 16, 1); - const alphaPanel = gui.addFolder('Alpha'); - alphaPanel.open(); - alphaPanel - .add(config, 'alpha', -2, kAlphaSteps + 2, 1) - .name(`alpha (of ${kAlphaSteps})`); - alphaPanel.add(config, 'pause', false); + const draw1Panel = gui.addFolder('Draw 1'); + draw1Panel.open(); + draw1Panel.addColor(config, 'color1').name('color'); + draw1Panel.add(config, 'alpha1', 0, 255).name('alpha'); + + const draw2Panel = gui.addFolder('Draw 2'); + draw2Panel.open(); + draw2Panel.addColor(config, 'color2').name('color'); + draw2Panel.add(config, 'alpha2', 0, 255).name('alpha'); + draw2Panel.add(config, 'pause', false); gui.add(buttons, 'initial').name('reset to initial'); } @@ -74,8 +91,8 @@ context.configure({ // Config buffer // -const bufConfig = device.createBuffer({ - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, +const bufInstanceColors = device.createBuffer({ + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX, size: 128, }); @@ -89,21 +106,23 @@ const renderWithAlphaToCoverageModule = device.createShaderModule({ const renderWithAlphaToCoveragePipeline = device.createRenderPipeline({ label: 'renderWithAlphaToCoveragePipeline', layout: 'auto', - vertex: { module: renderWithAlphaToCoverageModule }, + vertex: { + module: renderWithAlphaToCoverageModule, + buffers: [ + { + stepMode: 'instance', + arrayStride: 4, + attributes: [{ shaderLocation: 0, format: 'unorm8x4', offset: 0 }], + }, + ], + }, fragment: { module: renderWithAlphaToCoverageModule, - targets: [{ format: 'rgba16float' }], + targets: [{ format: 'rgba8unorm' }], }, multisample: { count: 4, alphaToCoverageEnabled: true }, primitive: { topology: 'triangle-list' }, }); -const renderWithAlphaToCoverageBGL = - renderWithAlphaToCoveragePipeline.getBindGroupLayout(0); - -const renderWithAlphaToCoverageBG = device.createBindGroup({ - layout: renderWithAlphaToCoverageBGL, - entries: [{ binding: 0, resource: { buffer: bufConfig } }], -}); // // "Debug" view of the actual texture contents @@ -129,16 +148,26 @@ function render() { updateConfig(); const multisampleTexture = device.createTexture({ - format: 'rgba16float', + format: 'rgba8unorm', usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, size: [config.width, config.height], sampleCount: 4, }); const multisampleTextureView = multisampleTexture.createView(); + const resolveTexture = device.createTexture({ + format: 'rgba8unorm', + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + size: [config.width, config.height], + }); + const resolveTextureView = resolveTexture.createView(); + const showMultisampleTextureBG = device.createBindGroup({ layout: showMultisampleTextureBGL, - entries: [{ binding: 0, resource: multisampleTextureView }], + entries: [ + { binding: 0, resource: multisampleTextureView }, + { binding: 1, resource: resolveTextureView }, + ], }); const commandEncoder = device.createCommandEncoder(); @@ -149,21 +178,22 @@ function render() { colorAttachments: [ { view: multisampleTextureView, - clearValue: [0, 0, 0, 1], // will be overwritten + resolveTarget: resolveTextureView, + clearValue: [0, 0, 0, 1], // black background loadOp: 'clear', storeOp: 'store', }, ], }); pass.setPipeline(renderWithAlphaToCoveragePipeline); - pass.setBindGroup(0, renderWithAlphaToCoverageBG); - pass.draw(6); + pass.setVertexBuffer(0, bufInstanceColors); + pass.draw(6, 2); pass.end(); } // showMultisampleTexture pass { const pass = commandEncoder.beginRenderPass({ - label: 'showMulitsampleTexture pass', + label: 'showMultisampleTexture pass', colorAttachments: [ { view: context.getCurrentTexture().createView(), @@ -185,7 +215,10 @@ function render() { function frame() { if (!config.pause) { - config.alpha = ((performance.now() / 10000) % 1) * (kAlphaSteps + 4) - 2; + // scrub alpha2 over 15 seconds + let alpha = ((performance.now() / 15000) % 1) * (255 + 20) - 10; + alpha = Math.max(0, Math.min(alpha, 255)); + config.alpha2 = alpha; gui.updateDisplay(); } updateCanvasSize(); diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts index f41498af..af406078 100644 --- a/sample/alphaToCoverage/meta.ts +++ b/sample/alphaToCoverage/meta.ts @@ -1,7 +1,19 @@ export default { name: 'Alpha-to-Coverage', description: - 'Visualizes how alpha-to-coverage translates alpha values into sample coverage on your device. This varies per device; for example, not all devices guarantee that once a sample pops in, it will stay; some devices repeat at 2x2 pixels, others at 4x4; etc. The circles show the 4 samples of each pixel; the background checkerboard shows where the pixels are.', + ` +Visualizes how alpha-to-coverage translates alpha values into sample +coverage on your device. It draws two full-screen quads into a 4-sample +texture, with the configured color and alpha values. Then, it visualizes the +contents of that 4-sample texture: the circles show the 4 samples of each +pixel; the background checkerboard shows where the pixels are. + +The algorithm that converts alpha to a coverage sample mask varies per +device. This results in different proportions between the black background, +the first draw, and the second draw. + +Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices guarantee that once a sample "pops in", it will stay there at higher alpha values. Some devices "move" samples around at certain alpha thresholds even without increasing the total sample count. + `, filename: __DIRNAME__, sources: [ { path: 'main.ts' }, diff --git a/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl index d0791268..352f4043 100644 --- a/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl +++ b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl @@ -1,26 +1,24 @@ -struct Config { - alpha: f32, -}; -@group(0) @binding(0) var config: Config; - struct Varying { @builtin(position) pos: vec4f, + // Color from instance-step-mode vertex buffer + @location(0) color: vec4f, } @vertex fn vmain( @builtin(vertex_index) vertex_index: u32, + @location(0) color: vec4f, ) -> Varying { var square = array( vec2f(-1, -1), vec2f(-1, 1), vec2f( 1, -1), vec2f( 1, -1), vec2f(-1, 1), vec2f( 1, 1), ); - return Varying(vec4(square[vertex_index], 0, 1)); + return Varying(vec4(square[vertex_index], 0, 1), color); } @fragment fn fmain(vary: Varying) -> @location(0) vec4f { - return vec4f(1, 1, 1, config.alpha); + return vary.color; } diff --git a/sample/alphaToCoverage/showMultisampleTexture.wgsl b/sample/alphaToCoverage/showMultisampleTexture.wgsl index b7195a98..eae754d4 100644 --- a/sample/alphaToCoverage/showMultisampleTexture.wgsl +++ b/sample/alphaToCoverage/showMultisampleTexture.wgsl @@ -1,4 +1,5 @@ @group(0) @binding(0) var tex: texture_multisampled_2d; +@group(0) @binding(1) var resolved: texture_2d; struct Varying { @builtin(position) pos: vec4f, @@ -24,6 +25,7 @@ fn vmain( return Varying(pos, uv); } +// Standard sample positions for 4xMSAA (assuming the device conforms to spec) const kSampleCount = 4; const kSamplePositions: array = array( vec2f(0.375, 0.125), @@ -32,6 +34,12 @@ const kSamplePositions: array = array( vec2f(0.625, 0.875), ); +// Compute dimensions for drawing a nice-looking visualization +const kSampleDistanceFromCloseEdge = 0.125; // from the standard sample positions +const kGridEdgeHalfWidth = 0.025; +const kSampleInnerRadius = kSampleDistanceFromCloseEdge - kGridEdgeHalfWidth; +const kSampleOuterRadius = kSampleDistanceFromCloseEdge + kGridEdgeHalfWidth; + @fragment fn fmain(vary: Varying) -> @location(0) vec4f { let dim = textureDimensions(tex); @@ -45,14 +53,27 @@ fn fmain(vary: Varying) -> @location(0) vec4f { return vec4f(0, 0, 0, 1); } - // check if we're close to a sample, and return it + // Check if we're close to a sample, and return it for (var sampleIndex = 0; sampleIndex < kSampleCount; sampleIndex += 1) { - if distance(xyFrac, kSamplePositions[sampleIndex]) < 0.1 { + let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]); + if distanceFromSample < kSampleInnerRadius { + // Draw a circle for the sample value let val = textureLoad(tex, xyInt, sampleIndex).rgb; return vec4f(val, 1); + } else if distanceFromSample < kSampleOuterRadius { + // Draw a ring around the circle + return vec4f(0, 0, 0, 1); } + + } + + // If close to a grid edge, render the grid + let distanceToGridEdge = abs((xyFrac + 0.5) % 1 - 0.5); + if min(distanceToGridEdge.x, distanceToGridEdge.y) < kGridEdgeHalfWidth { + return vec4f(0, 0, 0, 1); } - // if not close to a sample, render background checkerboard - return vec4f(f32(xyInt.x % 4 + 1) / 8, 0, f32(xyInt.y % 4 + 1) / 8, 1); + // Otherwise, show the multisample-resolved result as the background + let val = textureLoad(resolved, xyInt, /*level*/ 0).rgb; + return vec4f(val, 1); } From 035a56fc184c2d217d236a9093dc88a680e99609 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 20 Sep 2024 14:58:09 -0700 Subject: [PATCH 2/7] lint fix --- sample/alphaToCoverage/meta.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts index af406078..751d5b03 100644 --- a/sample/alphaToCoverage/meta.ts +++ b/sample/alphaToCoverage/meta.ts @@ -1,7 +1,6 @@ export default { name: 'Alpha-to-Coverage', - description: - ` + description: ` Visualizes how alpha-to-coverage translates alpha values into sample coverage on your device. It draws two full-screen quads into a 4-sample texture, with the configured color and alpha values. Then, it visualizes the @@ -13,7 +12,7 @@ device. This results in different proportions between the black background, the first draw, and the second draw. Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices guarantee that once a sample "pops in", it will stay there at higher alpha values. Some devices "move" samples around at certain alpha thresholds even without increasing the total sample count. - `, +`, filename: __DIRNAME__, sources: [ { path: 'main.ts' }, From 986913ec2ac4e725ac95b08a16756182e1c4239b Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 20 Sep 2024 15:28:24 -0700 Subject: [PATCH 3/7] add checkbox to hide resolved color --- sample/alphaToCoverage/main.ts | 58 +++++++++++++++++++++++----------- sample/alphaToCoverage/meta.ts | 5 ++- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts index 96381fed..a0d2782f 100644 --- a/sample/alphaToCoverage/main.ts +++ b/sample/alphaToCoverage/main.ts @@ -16,6 +16,7 @@ quitIfWebGPUNotAvailable(adapter, device); const kInitConfig = { width: 8, height: 8, + showResolvedColor: true, color1: 0x0000ff, alpha1: 127, color2: 0xff0000, @@ -23,24 +24,9 @@ const kInitConfig = { pause: false, }; const config = { ...kInitConfig }; -const updateConfig = () => { - // Update the colors in the (instance-step-mode) vertex buffer - const data = new Uint8Array([ - // instance 0 color - (config.color1 >> 16) & 0xff, // R - (config.color1 >> 8) & 0xff, // G - (config.color1 >> 0) & 0xff, // B - config.alpha1, - // instance 1 color - (config.color2 >> 16) & 0xff, // R - (config.color2 >> 8) & 0xff, // G - (config.color2 >> 0) & 0xff, // B - config.alpha2, - ]); - device.queue.writeBuffer(bufInstanceColors, 0, data); -}; const gui = new GUI(); +gui.width = 300; { const buttons = { initial() { @@ -53,6 +39,7 @@ const gui = new GUI(); settings.open(); settings.add(config, 'width', 1, 16, 1); settings.add(config, 'height', 1, 16, 1); + settings.add(config, 'showResolvedColor', true); const draw1Panel = gui.addFolder('Draw 1'); draw1Panel.open(); @@ -93,9 +80,26 @@ context.configure({ const bufInstanceColors = device.createBuffer({ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX, - size: 128, + size: 8, }); +function writeConfigToGPU() { + // Update the colors in the (instance-step-mode) vertex buffer + const data = new Uint8Array([ + // instance 0 color + (config.color1 >> 16) & 0xff, // R + (config.color1 >> 8) & 0xff, // G + (config.color1 >> 0) & 0xff, // B + config.alpha1, + // instance 1 color + (config.color2 >> 16) & 0xff, // R + (config.color2 >> 8) & 0xff, // G + (config.color2 >> 0) & 0xff, // B + config.alpha2, + ]); + device.queue.writeBuffer(bufInstanceColors, 0, data); +} + // // Pipeline to render to a multisampled texture using alpha-to-coverage // @@ -145,7 +149,7 @@ const showMultisampleTextureBGL = showMultisampleTexturePipeline.getBindGroupLayout(0); function render() { - updateConfig(); + writeConfigToGPU(); const multisampleTexture = device.createTexture({ format: 'rgba8unorm', @@ -171,6 +175,20 @@ function render() { }); const commandEncoder = device.createCommandEncoder(); + // clear resolveTextureView to gray if it won't be used + if (!config.showResolvedColor) { + const pass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: resolveTextureView, + clearValue: [0.3, 0.3, 0.3, 1], + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + pass.end(); + } // renderWithAlphaToCoverage pass { const pass = commandEncoder.beginRenderPass({ @@ -178,7 +196,9 @@ function render() { colorAttachments: [ { view: multisampleTextureView, - resolveTarget: resolveTextureView, + resolveTarget: config.showResolvedColor + ? resolveTextureView + : undefined, clearValue: [0, 0, 0, 1], // black background loadOp: 'clear', storeOp: 'store', diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts index 751d5b03..b5f3bc1e 100644 --- a/sample/alphaToCoverage/meta.ts +++ b/sample/alphaToCoverage/meta.ts @@ -11,7 +11,10 @@ The algorithm that converts alpha to a coverage sample mask varies per device. This results in different proportions between the black background, the first draw, and the second draw. -Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices guarantee that once a sample "pops in", it will stay there at higher alpha values. Some devices "move" samples around at certain alpha thresholds even without increasing the total sample count. +Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices +guarantee that once a sample "pops in", it will stay there at higher alpha +values. Some devices "move" samples around at certain alpha thresholds even +without increasing the total sample count. `, filename: __DIRNAME__, sources: [ From 8ad51e2b83e63750aea90cc23cd9d4c9e3f31b98 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 20 Sep 2024 15:32:00 -0700 Subject: [PATCH 4/7] add link to article on alpha-to-coverage --- sample/alphaToCoverage/meta.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts index b5f3bc1e..1922e1cd 100644 --- a/sample/alphaToCoverage/meta.ts +++ b/sample/alphaToCoverage/meta.ts @@ -15,6 +15,9 @@ Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices guarantee that once a sample "pops in", it will stay there at higher alpha values. Some devices "move" samples around at certain alpha thresholds even without increasing the total sample count. + +More info on alpha-to-coverage: + `, filename: __DIRNAME__, sources: [ From 3bc94b70f7f7c4ebca19293b6d1bf1f28e32c071 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 20 Sep 2024 21:10:53 -0700 Subject: [PATCH 5/7] avoid recreating texture every frame --- sample/alphaToCoverage/main.ts | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts index a0d2782f..2a65a3d5 100644 --- a/sample/alphaToCoverage/main.ts +++ b/sample/alphaToCoverage/main.ts @@ -75,7 +75,7 @@ context.configure({ }); // -// Config buffer +// GPU state controlled by the config gui // const bufInstanceColors = device.createBuffer({ @@ -83,7 +83,29 @@ const bufInstanceColors = device.createBuffer({ size: 8, }); -function writeConfigToGPU() { +let multisampleTexture, multisampleTextureView; +function resetMultisampleTexture() { + if ( + !multisampleTexture || + multisampleTexture.width !== config.width || + multisampleTexture.height !== config.height + ) { + if (multisampleTexture) { + multisampleTexture.destroy(); + } + console.log('recreate'); + multisampleTexture = device.createTexture({ + format: 'rgba8unorm', + usage: + GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + size: [config.width, config.height], + sampleCount: 4, + }); + multisampleTextureView = multisampleTexture.createView(); + } +} + +function applyConfig() { // Update the colors in the (instance-step-mode) vertex buffer const data = new Uint8Array([ // instance 0 color @@ -98,6 +120,8 @@ function writeConfigToGPU() { config.alpha2, ]); device.queue.writeBuffer(bufInstanceColors, 0, data); + + resetMultisampleTexture(); } // @@ -149,15 +173,7 @@ const showMultisampleTextureBGL = showMultisampleTexturePipeline.getBindGroupLayout(0); function render() { - writeConfigToGPU(); - - const multisampleTexture = device.createTexture({ - format: 'rgba8unorm', - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, - size: [config.width, config.height], - sampleCount: 4, - }); - const multisampleTextureView = multisampleTexture.createView(); + applyConfig(); const resolveTexture = device.createTexture({ format: 'rgba8unorm', @@ -229,8 +245,6 @@ function render() { pass.end(); } device.queue.submit([commandEncoder.finish()]); - - multisampleTexture.destroy(); } function frame() { From ef1efc7413c52c94e78367fef49db8d7fa549eca Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Sat, 21 Sep 2024 15:53:05 -0700 Subject: [PATCH 6/7] more size options, fix another redundantly allocated texture --- sample/alphaToCoverage/main.ts | 40 ++++++++++--------- .../showMultisampleTexture.wgsl | 36 ++++++++--------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts index 2a65a3d5..0ff37ccd 100644 --- a/sample/alphaToCoverage/main.ts +++ b/sample/alphaToCoverage/main.ts @@ -14,8 +14,7 @@ quitIfWebGPUNotAvailable(adapter, device); // const kInitConfig = { - width: 8, - height: 8, + sizeLog2: 3, showResolvedColor: true, color1: 0x0000ff, alpha1: 127, @@ -37,8 +36,7 @@ gui.width = 300; const settings = gui.addFolder('Settings'); settings.open(); - settings.add(config, 'width', 1, 16, 1); - settings.add(config, 'height', 1, 16, 1); + settings.add(config, 'sizeLog2', 0, 8, 1).name('size = 2**'); settings.add(config, 'showResolvedColor', true); const draw1Panel = gui.addFolder('Draw 1'); @@ -83,25 +81,36 @@ const bufInstanceColors = device.createBuffer({ size: 8, }); -let multisampleTexture, multisampleTextureView; +let multisampleTexture: GPUTexture, multisampleTextureView: GPUTextureView; +let resolveTexture: GPUTexture, resolveTextureView: GPUTextureView; +let lastSize = 0; function resetMultisampleTexture() { - if ( - !multisampleTexture || - multisampleTexture.width !== config.width || - multisampleTexture.height !== config.height - ) { + const size = 2 ** config.sizeLog2; + if (lastSize !== size) { if (multisampleTexture) { multisampleTexture.destroy(); } - console.log('recreate'); multisampleTexture = device.createTexture({ format: 'rgba8unorm', usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, - size: [config.width, config.height], + size: [size, size], sampleCount: 4, }); multisampleTextureView = multisampleTexture.createView(); + + if (resolveTexture) { + resolveTexture.destroy(); + } + resolveTexture = device.createTexture({ + format: 'rgba8unorm', + usage: + GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + size: [size, size], + }); + resolveTextureView = resolveTexture.createView(); + + lastSize = size; } } @@ -175,13 +184,6 @@ const showMultisampleTextureBGL = function render() { applyConfig(); - const resolveTexture = device.createTexture({ - format: 'rgba8unorm', - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, - size: [config.width, config.height], - }); - const resolveTextureView = resolveTexture.createView(); - const showMultisampleTextureBG = device.createBindGroup({ layout: showMultisampleTextureBGL, entries: [ diff --git a/sample/alphaToCoverage/showMultisampleTexture.wgsl b/sample/alphaToCoverage/showMultisampleTexture.wgsl index eae754d4..3de18a8f 100644 --- a/sample/alphaToCoverage/showMultisampleTexture.wgsl +++ b/sample/alphaToCoverage/showMultisampleTexture.wgsl @@ -49,28 +49,26 @@ fn fmain(vary: Varying) -> @location(0) vec4f { let xyInt = vec2u(xy); let xyFrac = xy % vec2f(1, 1); - if xyInt.x >= dim.x || xyInt.y >= dim.y { - return vec4f(0, 0, 0, 1); - } + // Show the visualization only if the resolution is large enough to see it + if (dpdx(xy.x) < kGridEdgeHalfWidth) & (dpdy(xy.y) < kGridEdgeHalfWidth) { + // Check if we're close to a sample; if so, visualize the sample value + for (var sampleIndex = 0; sampleIndex < kSampleCount; sampleIndex += 1) { + let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]); + if distanceFromSample < kSampleInnerRadius { + // Draw a circle for the sample value + let val = textureLoad(tex, xyInt, sampleIndex).rgb; + return vec4f(val, 1); + } else if distanceFromSample < kSampleOuterRadius { + // Draw a ring around the circle + return vec4f(0, 0, 0, 1); + } + } - // Check if we're close to a sample, and return it - for (var sampleIndex = 0; sampleIndex < kSampleCount; sampleIndex += 1) { - let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]); - if distanceFromSample < kSampleInnerRadius { - // Draw a circle for the sample value - let val = textureLoad(tex, xyInt, sampleIndex).rgb; - return vec4f(val, 1); - } else if distanceFromSample < kSampleOuterRadius { - // Draw a ring around the circle + // If close to a grid edge, render the grid + let distanceToGridEdge = abs((xyFrac + 0.5) % 1 - 0.5); + if min(distanceToGridEdge.x, distanceToGridEdge.y) < kGridEdgeHalfWidth { return vec4f(0, 0, 0, 1); } - - } - - // If close to a grid edge, render the grid - let distanceToGridEdge = abs((xyFrac + 0.5) % 1 - 0.5); - if min(distanceToGridEdge.x, distanceToGridEdge.y) < kGridEdgeHalfWidth { - return vec4f(0, 0, 0, 1); } // Otherwise, show the multisample-resolved result as the background From 90236b847915116b03c27dfde3e32b001e2bf342 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Sat, 21 Sep 2024 16:08:38 -0700 Subject: [PATCH 7/7] improve description --- sample/alphaToCoverage/meta.ts | 27 +++++++++---------- .../showMultisampleTexture.wgsl | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts index 1922e1cd..8cb35bc7 100644 --- a/sample/alphaToCoverage/meta.ts +++ b/sample/alphaToCoverage/meta.ts @@ -1,23 +1,20 @@ export default { name: 'Alpha-to-Coverage', description: ` -Visualizes how alpha-to-coverage translates alpha values into sample -coverage on your device. It draws two full-screen quads into a 4-sample -texture, with the configured color and alpha values. Then, it visualizes the -contents of that 4-sample texture: the circles show the 4 samples of each -pixel; the background checkerboard shows where the pixels are. - -The algorithm that converts alpha to a coverage sample mask varies per -device. This results in different proportions between the black background, -the first draw, and the second draw. +Alpha-to-coverage is an alternative to alpha testing and alpha blending. See: + -Examples: Some devices use 1x1 patterns, others 2x2, others 4x4. Not all devices -guarantee that once a sample "pops in", it will stay there at higher alpha -values. Some devices "move" samples around at certain alpha thresholds even -without increasing the total sample count. +This sample visualizes how alpha-to-coverage translates alpha values into sample +coverage on your device. It draws two full-screen quads into a 4-sample +texture, each with the configured color and alpha value. Then, it visualizes the +contents of the resulting 4-sample texture: the circles show the 4 samples of +each texel; the background shows the "resolved" results (average of 4 samples). -More info on alpha-to-coverage: - +The algorithm that converts alpha to a coverage sample mask varies per device. +This results in different average "blending" proportions between the black +background, the first draw, and the second draw. +Device differences include different tile sizes (e.g. 1x1, 2x2, or 4x4), +"moving" samples (or not) around with in the tile as alpha increases, etc. `, filename: __DIRNAME__, sources: [ diff --git a/sample/alphaToCoverage/showMultisampleTexture.wgsl b/sample/alphaToCoverage/showMultisampleTexture.wgsl index 3de18a8f..99b22a03 100644 --- a/sample/alphaToCoverage/showMultisampleTexture.wgsl +++ b/sample/alphaToCoverage/showMultisampleTexture.wgsl @@ -50,7 +50,7 @@ fn fmain(vary: Varying) -> @location(0) vec4f { let xyFrac = xy % vec2f(1, 1); // Show the visualization only if the resolution is large enough to see it - if (dpdx(xy.x) < kGridEdgeHalfWidth) & (dpdy(xy.y) < kGridEdgeHalfWidth) { + if (dpdx(xy.x) < kGridEdgeHalfWidth * 2) & (dpdy(xy.y) < kGridEdgeHalfWidth * 2) { // Check if we're close to a sample; if so, visualize the sample value for (var sampleIndex = 0; sampleIndex < kSampleCount; sampleIndex += 1) { let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]);