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