diff --git a/apps/typegpu-docs/public/plums.png b/apps/typegpu-docs/public/plums.png new file mode 100644 index 0000000000..55218190da Binary files /dev/null and b/apps/typegpu-docs/public/plums.png differ diff --git a/apps/typegpu-docs/src/examples/common/createDarkModeAccess.ts b/apps/typegpu-docs/src/examples/common/createDarkModeAccess.ts new file mode 100644 index 0000000000..fc9c4d94d8 --- /dev/null +++ b/apps/typegpu-docs/src/examples/common/createDarkModeAccess.ts @@ -0,0 +1,49 @@ +import { d, tgpu, type TgpuRoot } from 'typegpu'; + +export interface DarkModeAccessOptions { + onThemeChange?: (darkMode: boolean) => void; +} + +function isDarkMode(): boolean { + return document.documentElement.dataset.theme === 'dark'; +} + +export function createDarkModeAccess(root: TgpuRoot, options: DarkModeAccessOptions = {}) { + let value = isDarkMode(); + const darkModeUniform = root.createUniform(d.u32, value ? 1 : 0); + + function isDarkModeGPU(): boolean { + 'use gpu'; + return darkModeUniform.$ === 1; + } + + const darkModeAccess = tgpu.accessor(d.bool, isDarkModeGPU); + + const observer = new MutationObserver(() => { + const nextValue = isDarkMode(); + if (nextValue === value) { + return; + } + + value = nextValue; + darkModeUniform.write(value ? 1 : 0); + options.onThemeChange?.(value); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'], + }); + + return { + get $() { + return darkModeAccess.$; + }, + get value() { + return value; + }, + detach() { + observer.disconnect(); + }, + }; +} diff --git a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts index 890d30e9cd..daaa217d52 100644 --- a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts @@ -6,9 +6,9 @@ import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); const canvas = document.querySelector('canvas') as HTMLCanvasElement; -const context = root.configureContext({ canvas }); +const context = root.configureContext({ canvas, alphaMode: 'premultiplied' }); -const response = await fetch('/TypeGPU/plums.jpg'); +const response = await fetch('/TypeGPU/plums.png'); const imageBitmap = await createImageBitmap(await response.blob()); const [srcWidth, srcHeight] = [imageBitmap.width, imageBitmap.height]; @@ -52,11 +52,12 @@ const sampler = root.createSampler({ const ioLayout = tgpu.bindGroupLayout({ flip: { uniform: d.u32 }, + premultiplyAlpha: { uniform: d.u32 }, inTexture: { texture: d.texture2d(d.f32) }, outTexture: { storageTexture: d.textureStorage2d('rgba8unorm') }, }); -const tileData = tgpu.workgroupVar(d.arrayOf(d.arrayOf(d.vec3f, 128), 4)); +const tileData = tgpu.workgroupVar(d.arrayOf(d.arrayOf(d.vec4f, 128), 4)); const computeFn = tgpu.computeFn({ in: { @@ -65,27 +66,32 @@ const computeFn = tgpu.computeFn({ }, workgroupSize: [32, 1, 1], })(({ wid, lid }) => { + 'use gpu'; const settings = settingsUniform.$; const filterOffset = d.i32((settings.filterDim - 1) / 2); const dims = d.vec2i(std.textureDimensions(ioLayout.$.inTexture)); - const baseIndex = d - .vec2i(wid.xy.mul(d.vec2u(settings.blockDim, 4)).add(lid.xy.mul(d.vec2u(4, 1)))) - .sub(d.vec2i(filterOffset, 0)); + const baseIndex = + d.vec2i(wid.xy * d.vec2u(settings.blockDim, 4) + lid.xy * d.vec2u(4, 1)) - + d.vec2i(filterOffset, 0); // Load a tile of pixels into shared memory for (const r of tgpu.unroll(std.range(4))) { for (const c of tgpu.unroll(std.range(4))) { - let loadIndex = baseIndex.add(d.vec2i(c, r)); + let loadIndex = baseIndex + d.vec2i(c, r); if (ioLayout.$.flip !== 0) { loadIndex = loadIndex.yx; } - tileData.$[r][lid.x * 4 + d.u32(c)] = std.textureSampleLevel( + const sample = std.textureSampleLevel( ioLayout.$.inTexture, sampler.$, - d.vec2f(d.vec2f(loadIndex).add(d.vec2f(0.5)).div(d.vec2f(dims))), + d.vec2f((d.vec2f(loadIndex) + d.vec2f(0.5)) / d.vec2f(dims)), 0, - ).rgb; + ); + tileData.$[r][lid.x * 4 + d.u32(c)] = + ioLayout.$.premultiplyAlpha !== 0 + ? d.vec4f(sample.rgb * sample.a, sample.a) + : d.vec4f(sample); } } @@ -94,7 +100,7 @@ const computeFn = tgpu.computeFn({ // Apply the horizontal blur filter and write to the output texture for (const r of tgpu.unroll(std.range(4))) { for (const c of tgpu.unroll(std.range(4))) { - let writeIndex = baseIndex.add(d.vec2i(c, r)); + let writeIndex = baseIndex + d.vec2i(c, r); if (ioLayout.$.flip !== 0) { writeIndex = writeIndex.yx; } @@ -105,12 +111,12 @@ const computeFn = tgpu.computeFn({ center < 128 - filterOffset && std.all(std.lt(writeIndex, dims)) ) { - let acc = d.vec3f(); + let acc = d.vec4f(); for (let f = 0; f < settings.filterDim; f++) { const i = center + f - filterOffset; - acc = acc.add(tileData.$[r][i].mul(1 / settings.filterDim)); + acc += tileData.$[r][i] / settings.filterDim; } - std.textureStore(ioLayout.$.outTexture, writeIndex, d.vec4f(acc, 1)); + std.textureStore(ioLayout.$.outTexture, writeIndex, acc); } } } @@ -119,7 +125,10 @@ const computeFn = tgpu.computeFn({ const renderFragment = tgpu.fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, -})((input) => std.textureSample(renderView.$, sampler.$, input.uv)); +})((input) => { + 'use gpu'; + return std.textureSample(renderView.$, sampler.$, input.uv); +}); const zeroBuffer = root.createBuffer(d.u32, 0).$usage('uniform'); const oneBuffer = root.createBuffer(d.u32, 1).$usage('uniform'); @@ -127,16 +136,19 @@ const oneBuffer = root.createBuffer(d.u32, 1).$usage('uniform'); const ioBindGroups = [ root.createBindGroup(ioLayout, { flip: zeroBuffer, + premultiplyAlpha: oneBuffer, inTexture: imageTexture, outTexture: textures[0], }), root.createBindGroup(ioLayout, { flip: oneBuffer, + premultiplyAlpha: zeroBuffer, inTexture: textures[0], outTexture: textures[1], }), root.createBindGroup(ioLayout, { flip: zeroBuffer, + premultiplyAlpha: zeroBuffer, inTexture: textures[1], outTexture: textures[0], }), diff --git a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts index 7e7b5597bb..b1742e19b7 100644 --- a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts +++ b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts @@ -1,14 +1,17 @@ import { sdRoundedBox2d } from '@typegpu/sdf'; import { tgpu, common, d, std } from 'typegpu'; +import { createDarkModeAccess } from '../../common/createDarkModeAccess.ts'; import { defineControls } from '../../common/defineControls.ts'; +import { hexToRgb } from '@typegpu/color'; const root = await tgpu.init(); const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas, alphaMode: 'premultiplied' }); const mousePosUniform = root.createUniform(d.vec2f, d.vec2f(0.5, 0.5)); +const darkModeAccess = createDarkModeAccess(root); -const response = await fetch('/TypeGPU/plums.jpg'); +const response = await fetch('/TypeGPU/plums.png'); const imageBitmap = await createImageBitmap(await response.blob()); const imageTexture = root @@ -68,43 +71,82 @@ const TintParams = d.struct({ strength: d.f32, }); -const calculateWeights = (sdfDist: number, start: number, end: number, featherUV: number) => { +function calculateWeights(sdfDist: number, start: number, end: number, featherUV: number) { 'use gpu'; const inside = 1 - std.smoothstep(start - featherUV, start + featherUV, sdfDist); const outside = std.smoothstep(end - featherUV, end + featherUV, sdfDist); const ring = std.max(0, 1 - inside - outside); return Weights({ inside, ring, outside }); -}; +} -const applyTint = (color: d.v3f, tint: d.Infer) => { +function applyTint(color: d.v4f, tint: d.Infer): d.v4f { 'use gpu'; - return std.mix(d.vec4f(color, 1), d.vec4f(tint.color, 1), tint.strength); -}; + return d.vec4f(std.mix(color.rgb, tint.color, tint.strength), color.a); +} + +function getBackground() { + 'use gpu'; + if (darkModeAccess.$) { + return hexToRgb('#171a25'); + } + return hexToRgb('#ffffff'); +} + +function sampleBiasWithBackground( + tex: d.texture2d, + sampler: d.sampler, + uv: d.v2f, + bias: number, +): d.v4f { + 'use gpu'; + const sample = std.textureSampleBias(tex, sampler, uv, bias); + return d.vec4f(std.mix(getBackground(), sample.rgb, sample.a), 1); +} -const sampleWithChromaticAberration = ( +function sampleLevelWithBackground( + tex: d.texture2d, + sampler: d.sampler, + uv: d.v2f, + level: number, +): d.v4f { + 'use gpu'; + const sample = std.textureSampleLevel(tex, sampler, uv, level); + return d.vec4f(std.mix(getBackground(), sample.rgb, sample.a), 1); +} + +function sampleWithChromaticAberration( tex: d.texture2d, sampler: d.sampler, uv: d.v2f, offset: number, dir: d.v2f, blur: number, -) => { +) { 'use gpu'; - const samples = d.arrayOf(d.vec3f, 3)(); - for (const i of tgpu.unroll(std.range(3))) { + const masks = [d.vec3f(1, 0, 0), d.vec3f(0, 1, 0), d.vec3f(0, 0, 1)]; + let result = d.vec4f(); + for (const i of tgpu.unroll(std.range(masks.length))) { const channelOffset = dir * (d.f32(i) - 1) * offset; - samples[i] = std.textureSampleBias(tex, sampler, uv - channelOffset, blur).rgb; + const sample = sampleBiasWithBackground(tex, sampler, uv - channelOffset, blur); + result += d.vec4f(sample.rgb * masks[i], sample.a); } - return d.vec3f(samples[0].x, samples[1].y, samples[2].z); -}; + + return result; +} + +function premultiplyAlpha(sample: d.v4f): d.v4f { + 'use gpu'; + return d.vec4f(sample.rgb, 1) * sample.a; +} const fragmentShader = tgpu.fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { - const posInBoxSpace = uv.sub(mousePosUniform.$); + 'use gpu'; + const posInBoxSpace = uv - mousePosUniform.$; const sdfDist = sdRoundedBox2d(posInBoxSpace, paramsUniform.$.rectDims, paramsUniform.$.radius); - const dir = std.normalize(posInBoxSpace.mul(paramsUniform.$.rectDims.yx)); + const dir = std.normalize(posInBoxSpace * paramsUniform.$.rectDims.yx); const normalizedDist = (sdfDist - paramsUniform.$.start) / (paramsUniform.$.end - paramsUniform.$.start); @@ -112,29 +154,26 @@ const fragmentShader = tgpu.fragmentFn({ const featherUV = paramsUniform.$.edgeFeather / std.max(texDim.x, texDim.y); const weights = calculateWeights(sdfDist, paramsUniform.$.start, paramsUniform.$.end, featherUV); - const blurSample = std.textureSampleBias(sampledView.$, sampler.$, uv, paramsUniform.$.blur); + const blurSample = sampleBiasWithBackground(sampledView.$, sampler.$, uv, paramsUniform.$.blur); const refractedSample = sampleWithChromaticAberration( sampledView.$, sampler.$, - uv.add(dir.mul(paramsUniform.$.refractionStrength * normalizedDist)), + uv + dir * (paramsUniform.$.refractionStrength * normalizedDist), paramsUniform.$.chromaticStrength * normalizedDist, dir, paramsUniform.$.blur * paramsUniform.$.edgeBlurMultiplier, ); - const normalSample = std.textureSampleLevel(sampledView.$, sampler.$, uv, 0); + const normalSample = sampleLevelWithBackground(sampledView.$, sampler.$, uv, d.f32(0)); const tint = TintParams({ color: paramsUniform.$.tintColor, strength: paramsUniform.$.tintStrength, }); - const tintedBlur = applyTint(blurSample.rgb, tint); + const tintedBlur = applyTint(blurSample, tint); const tintedRing = applyTint(refractedSample, tint); - return tintedBlur - .mul(weights.inside) - .add(tintedRing.mul(weights.ring)) - .add(normalSample.mul(weights.outside)); + return tintedBlur * weights.inside + tintedRing * weights.ring + normalSample * weights.outside; }); const pipeline = root.createRenderPipeline({ @@ -305,5 +344,6 @@ export function onCleanup() { cancelAnimationFrame(frameId); } window.removeEventListener('mousemove', handleMouseMove); + darkModeAccess.detach(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts index 5bc17e56ab..f33d9d66fc 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts @@ -86,7 +86,7 @@ let brushState: BrushState = { }; // Load and create background texture -const response = await fetch('/TypeGPU/plums.jpg'); +const response = await fetch('/TypeGPU/plums.png'); const plums = await createImageBitmap(await response.blob(), { resizeWidth: p.N, resizeHeight: p.N, diff --git a/apps/typegpu-docs/src/examples/tests/texture-test/index.ts b/apps/typegpu-docs/src/examples/tests/texture-test/index.ts index b0aee7e728..6753e8e86b 100644 --- a/apps/typegpu-docs/src/examples/tests/texture-test/index.ts +++ b/apps/typegpu-docs/src/examples/tests/texture-test/index.ts @@ -6,7 +6,7 @@ const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas }); const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); -const imageBitmap = await createImageBitmap(await (await fetch('/TypeGPU/plums.jpg')).blob()); +const imageBitmap = await createImageBitmap(await (await fetch('/TypeGPU/plums.png')).blob()); const testFormats = [ 'rgba8unorm', diff --git a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts index 4db6c4623d..f982900fee 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/blur.test.ts @@ -32,15 +32,17 @@ describe('blur example', () => { @group(0) @binding(0) var settingsUniform: Settings; - @group(1) @binding(1) var inTexture: texture_2d; + @group(1) @binding(2) var inTexture: texture_2d; @group(1) @binding(0) var flip: u32; - var tileData: array, 4>; - @group(0) @binding(1) var sampler_1: sampler; - @group(1) @binding(2) var outTexture: texture_storage_2d; + var tileData: array, 4>; + + @group(1) @binding(1) var premultiplyAlpha: u32; + + @group(1) @binding(3) var outTexture: texture_storage_2d; @compute @workgroup_size(32, 1, 1) fn computeFn(@builtin(workgroup_id) wid: vec3u, @builtin(local_invocation_id) lid: vec3u) { let settings = (&settingsUniform); @@ -54,7 +56,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[0i][((lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[0i][((lid.x * 4u) + 0u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #0 / #1 { @@ -62,7 +65,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[0i][((lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[0i][((lid.x * 4u) + 1u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #0 / #2 { @@ -70,7 +74,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[0i][((lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[0i][((lid.x * 4u) + 2u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #0 / #3 { @@ -78,7 +83,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[0i][((lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[0i][((lid.x * 4u) + 3u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // --- // unrolled iteration #1 @@ -88,7 +94,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[1i][((lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[1i][((lid.x * 4u) + 0u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #1 / #1 { @@ -96,7 +103,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[1i][((lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[1i][((lid.x * 4u) + 1u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #1 / #2 { @@ -104,7 +112,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[1i][((lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[1i][((lid.x * 4u) + 2u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #1 / #3 { @@ -112,7 +121,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[1i][((lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[1i][((lid.x * 4u) + 3u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // --- // unrolled iteration #2 @@ -122,7 +132,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[2i][((lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[2i][((lid.x * 4u) + 0u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #2 / #1 { @@ -130,7 +141,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[2i][((lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[2i][((lid.x * 4u) + 1u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #2 / #2 { @@ -138,7 +150,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[2i][((lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[2i][((lid.x * 4u) + 2u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #2 / #3 { @@ -146,7 +159,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[2i][((lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[2i][((lid.x * 4u) + 3u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // --- // unrolled iteration #3 @@ -156,7 +170,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[3i][((lid.x * 4u) + 0u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[3i][((lid.x * 4u) + 0u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #3 / #1 { @@ -164,7 +179,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[3i][((lid.x * 4u) + 1u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[3i][((lid.x * 4u) + 1u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #3 / #2 { @@ -172,7 +188,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[3i][((lid.x * 4u) + 2u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[3i][((lid.x * 4u) + 2u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // unrolled iteration #3 / #3 { @@ -180,7 +197,8 @@ describe('blur example', () => { if ((flip != 0u)) { loadIndex = loadIndex.yx; } - tileData[3i][((lid.x * 4u) + 3u)] = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).rgb; + let sample = textureSampleLevel(inTexture, sampler_1, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0); + tileData[3i][((lid.x * 4u) + 3u)] = select(sample, vec4f((sample.rgb * sample.a), sample.a), (premultiplyAlpha != 0u)); } // --- // --- @@ -194,12 +212,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 0i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[0i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[0i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #0 / #1 @@ -210,12 +228,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 1i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[0i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[0i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #0 / #2 @@ -226,12 +244,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 2i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[0i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[0i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #0 / #3 @@ -242,12 +260,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 3i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[0i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[0i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // --- @@ -260,12 +278,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 0i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[1i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[1i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #1 / #1 @@ -276,12 +294,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 1i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[1i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[1i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #1 / #2 @@ -292,12 +310,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 2i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[1i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[1i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #1 / #3 @@ -308,12 +326,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 3i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[1i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[1i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // --- @@ -326,12 +344,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 0i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[2i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[2i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #2 / #1 @@ -342,12 +360,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 1i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[2i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[2i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #2 / #2 @@ -358,12 +376,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 2i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[2i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[2i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #2 / #3 @@ -374,12 +392,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 3i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[2i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[2i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // --- @@ -392,12 +410,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 0i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[3i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[3i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #3 / #1 @@ -408,12 +426,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 1i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[3i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[3i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #3 / #2 @@ -424,12 +442,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 2i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[3i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[3i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // unrolled iteration #3 / #3 @@ -440,12 +458,12 @@ describe('blur example', () => { } let center = (i32((4u * lid.x)) + 3i); if ((((center >= filterOffset) && (center < (128i - filterOffset))) && all((writeIndex < dims)))) { - var acc = vec3f(); + var acc = vec4f(); for (var f = 0; (f < (*settings).filterDim); f++) { let i = ((center + f) - filterOffset); - acc = (acc + (tileData[3i][i] * (1f / f32((*settings).filterDim)))); + acc += (tileData[3i][i] / f32((*settings).filterDim)); } - textureStore(outTexture, writeIndex, vec4f(acc, 1f)); + textureStore(outTexture, writeIndex, acc); } } // --- diff --git a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts index 6d5100c366..d619dec968 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/liquid-glass.test.ts @@ -104,6 +104,16 @@ describe('liquid-glass example', () => { @group(0) @binding(3) var sampler_1: sampler; + @group(0) @binding(4) var themeUniform: u32; + + fn applyPageBackground(color: vec4f) -> vec4f { + let darkBackground = vec3f(0.09019608050584793, 0.10196078568696976, 0.14509804546833038); + let distanceFromWhite = distance(color.rgb, vec3f(1)); + let foregroundOpacity = smoothstep(0f, 0.1f, distanceFromWhite); + let colorOnDark = mix(darkBackground, color.rgb, foregroundOpacity); + return vec4f(select(color.rgb, colorOnDark, (themeUniform == 1u)), color.a); + } + fn sampleWithChromaticAberration(tex: texture_2d, sampler_2: sampler, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { var samples = array(); // unrolled iteration #0 @@ -146,12 +156,12 @@ describe('liquid-glass example', () => { let texDim = textureDimensions(sampledView, 0); let featherUV = (paramsUniform.edgeFeather / f32(max(texDim.x, texDim.y))); let weights = calculateWeights(sdfDist, paramsUniform.start, paramsUniform.end, featherUV); - let blurSample = textureSampleBias(sampledView, sampler_1, _arg_0.uv, paramsUniform.blur); - let refractedSample = sampleWithChromaticAberration(sampledView, sampler_1, (_arg_0.uv + (dir * (paramsUniform.refractionStrength * normalizedDist))), (paramsUniform.chromaticStrength * normalizedDist), dir, (paramsUniform.blur * paramsUniform.edgeBlurMultiplier)); - let normalSample = textureSampleLevel(sampledView, sampler_1, _arg_0.uv, 0); + let blurSample = applyPageBackground(textureSampleBias(sampledView, sampler_1, _arg_0.uv, paramsUniform.blur)); + let refractedSample = applyPageBackground(vec4f(sampleWithChromaticAberration(sampledView, sampler_1, (_arg_0.uv + (dir * (paramsUniform.refractionStrength * normalizedDist))), (paramsUniform.chromaticStrength * normalizedDist), dir, (paramsUniform.blur * paramsUniform.edgeBlurMultiplier)), 1f)); + let normalSample = applyPageBackground(textureSampleLevel(sampledView, sampler_1, _arg_0.uv, 0)); let tint = TintParams(paramsUniform.tintColor, paramsUniform.tintStrength); let tintedBlur = applyTint(blurSample.rgb, tint); - let tintedRing = applyTint(refractedSample, tint); + let tintedRing = applyTint(refractedSample.rgb, tint); return (((tintedBlur * weights.inside) + (tintedRing * weights.ring)) + (normalSample * weights.outside)); }" `);