From 142276fd7e2c330395d27f45bfd444a114b00326 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Thu, 19 Sep 2024 15:16:40 -0700 Subject: [PATCH] Add alphaToCoverage sample --- sample/alphaToCoverage/index.html | 29 +++ sample/alphaToCoverage/main.ts | 184 ++++++++++++++++++ sample/alphaToCoverage/meta.ts | 11 ++ .../renderWithAlphaToCoverage.wgsl | 26 +++ .../showMultisampleTexture.wgsl | 58 ++++++ sample/reversedZ/index.html | 2 +- sample/samplerParameters/index.html | 2 +- src/samples.ts | 4 +- 8 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 sample/alphaToCoverage/index.html create mode 100644 sample/alphaToCoverage/main.ts create mode 100644 sample/alphaToCoverage/meta.ts create mode 100644 sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl create mode 100644 sample/alphaToCoverage/showMultisampleTexture.wgsl diff --git a/sample/alphaToCoverage/index.html b/sample/alphaToCoverage/index.html new file mode 100644 index 00000000..36939aef --- /dev/null +++ b/sample/alphaToCoverage/index.html @@ -0,0 +1,29 @@ + + + + + + webgpu-samples: alphaToCoverage + + + + + + + + diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts new file mode 100644 index 00000000..e571fdc9 --- /dev/null +++ b/sample/alphaToCoverage/main.ts @@ -0,0 +1,184 @@ +import { GUI } from 'dat.gui'; + +import showMultisampleTextureWGSL from './showMultisampleTexture.wgsl'; +import renderWithAlphaToCoverageWGSL from './renderWithAlphaToCoverage.wgsl'; +import { quitIfWebGPUNotAvailable } from '../util'; + +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const adapter = await navigator.gpu?.requestAdapter(); +const device = await adapter?.requestDevice(); +quitIfWebGPUNotAvailable(adapter, device); + +// +// GUI controls +// + +const kInitConfig = { + width: 8, + height: 8, + alpha: 0, + pause: false, +}; +const config = { ...kInitConfig }; +const updateAlpha = () => { + if (!config.pause) { + config.alpha = ((performance.now() / 10000) % 1.2) - 0.1; + gui.updateDisplay(); + } + + const data = new Float32Array([config.alpha]); + device.queue.writeBuffer(bufConfig, 0, data); +}; + +const gui = new GUI(); +{ + const buttons = { + initial() { + Object.assign(config, kInitConfig); + gui.updateDisplay(); + }, + }; + const presets = gui.addFolder('Presets'); + presets.open(); + presets.add(buttons, 'initial').name('reset to initial'); + + const settings = gui.addFolder('Settings'); + settings.open(); + settings.add(config, 'width', 1, 16, 1); + settings.add(config, 'height', 1, 16, 1); + settings.add(config, 'alpha', -0.1, 1.1, 0.01); + settings.add(config, 'pause', false); +} + +// +// Canvas setup +// + +const devicePixelRatio = window.devicePixelRatio; +canvas.width = canvas.clientWidth * devicePixelRatio; +canvas.height = canvas.clientHeight * devicePixelRatio; +const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + +const context = canvas.getContext('webgpu') as GPUCanvasContext; +context.configure({ + device, + format: presentationFormat, + alphaMode: 'premultiplied', +}); + +// +// Config buffer +// + +const bufConfig = device.createBuffer({ + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, + size: 128, +}); + +// +// Pipeline to render to a multisampled texture using alpha-to-coverage +// + +const renderWithAlphaToCoverageModule = device.createShaderModule({ + code: renderWithAlphaToCoverageWGSL, +}); +const renderWithAlphaToCoveragePipeline = device.createRenderPipeline({ + label: 'renderWithAlphaToCoveragePipeline', + layout: 'auto', + vertex: { module: renderWithAlphaToCoverageModule }, + fragment: { + module: renderWithAlphaToCoverageModule, + targets: [{ format: 'rgba16float' }], + }, + 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 +// + +const showMultisampleTextureModule = device.createShaderModule({ + code: showMultisampleTextureWGSL, +}); +const showMultisampleTexturePipeline = device.createRenderPipeline({ + label: 'showMultisampleTexturePipeline', + layout: 'auto', + vertex: { module: showMultisampleTextureModule }, + fragment: { + module: showMultisampleTextureModule, + targets: [{ format: presentationFormat }], + }, + primitive: { topology: 'triangle-list' }, +}); +const showMultisampleTextureBGL = + showMultisampleTexturePipeline.getBindGroupLayout(0); + +function frame() { + updateAlpha(); + + const multisampleTexture = device.createTexture({ + format: 'rgba16float', + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + size: [config.width, config.height], + sampleCount: 4, + }); + const multisampleTextureView = multisampleTexture.createView(); + + const showMultisampleTextureBG = device.createBindGroup({ + layout: showMultisampleTextureBGL, + entries: [{ binding: 0, resource: multisampleTextureView }], + }); + + const commandEncoder = device.createCommandEncoder(); + // renderWithAlphaToCoverage pass + { + const pass = commandEncoder.beginRenderPass({ + label: 'renderWithAlphaToCoverage pass', + colorAttachments: [ + { + view: multisampleTextureView, + clearValue: [0, 0, 0, 1], // will be overwritten + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + pass.setPipeline(renderWithAlphaToCoveragePipeline); + pass.setBindGroup(0, renderWithAlphaToCoverageBG); + pass.draw(6); + pass.end(); + } + // showMultisampleTexture pass + { + const pass = commandEncoder.beginRenderPass({ + label: 'showMulitsampleTexture pass', + colorAttachments: [ + { + view: context.getCurrentTexture().createView(), + clearValue: [1, 0, 1, 1], // error color, will be overwritten + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + pass.setPipeline(showMultisampleTexturePipeline); + pass.setBindGroup(0, showMultisampleTextureBG); + pass.draw(6); + pass.end(); + } + device.queue.submit([commandEncoder.finish()]); + + multisampleTexture.destroy(); + + requestAnimationFrame(frame); +} + +requestAnimationFrame(frame); diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts new file mode 100644 index 00000000..f41498af --- /dev/null +++ b/sample/alphaToCoverage/meta.ts @@ -0,0 +1,11 @@ +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.', + filename: __DIRNAME__, + sources: [ + { path: 'main.ts' }, + { path: './renderWithAlphaToCoverage.wgsl' }, + { path: './showMultisampleTexture.wgsl' }, + ], +}; diff --git a/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl new file mode 100644 index 00000000..d0791268 --- /dev/null +++ b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl @@ -0,0 +1,26 @@ +struct Config { + alpha: f32, +}; +@group(0) @binding(0) var config: Config; + +struct Varying { + @builtin(position) pos: vec4f, +} + +@vertex +fn vmain( + @builtin(vertex_index) vertex_index: u32, +) -> 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)); +} + +@fragment +fn fmain(vary: Varying) -> @location(0) vec4f { + return vec4f(1, 1, 1, config.alpha); +} + diff --git a/sample/alphaToCoverage/showMultisampleTexture.wgsl b/sample/alphaToCoverage/showMultisampleTexture.wgsl new file mode 100644 index 00000000..b7195a98 --- /dev/null +++ b/sample/alphaToCoverage/showMultisampleTexture.wgsl @@ -0,0 +1,58 @@ +@group(0) @binding(0) var tex: texture_multisampled_2d; + +struct Varying { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, +} + +const kMipLevels = 4; +const baseMipSize: u32 = 16; + +const kSquare = array( + vec2f(0, 0), vec2f(0, 1), vec2f(1, 0), + vec2f(1, 0), vec2f(0, 1), vec2f(1, 1), +); + +@vertex +fn vmain( + @builtin(vertex_index) vertex_index: u32, +) -> Varying { + + let uv = kSquare[vertex_index]; + let pos = vec4(uv * vec2(2, -2) + vec2(-1, 1), 0.0, 1.0); + + return Varying(pos, uv); +} + +const kSampleCount = 4; +const kSamplePositions: array = array( + vec2f(0.375, 0.125), + vec2f(0.875, 0.375), + vec2f(0.125, 0.625), + vec2f(0.625, 0.875), +); + +@fragment +fn fmain(vary: Varying) -> @location(0) vec4f { + let dim = textureDimensions(tex); + let dimMax = max(dim.x, dim.y); + + let xy = vary.uv * f32(dimMax); + 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); + } + + // 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 val = textureLoad(tex, xyInt, sampleIndex).rgb; + return vec4f(val, 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); +} diff --git a/sample/reversedZ/index.html b/sample/reversedZ/index.html index 652897ad..03395943 100644 --- a/sample/reversedZ/index.html +++ b/sample/reversedZ/index.html @@ -3,7 +3,7 @@ - webgpu-samples: samplerParameters + webgpu-samples: reversedZ