Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/typegpu-docs/public/plums.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions apps/typegpu-docs/src/examples/common/createDarkModeAccess.ts
Original file line number Diff line number Diff line change
@@ -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();
},
};
}
42 changes: 27 additions & 15 deletions apps/typegpu-docs/src/examples/image-processing/blur/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -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: {
Expand All @@ -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);
}
}

Expand All @@ -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;
}
Expand All @@ -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);
}
}
}
Expand All @@ -119,24 +125,30 @@ 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');

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],
}),
Expand Down
86 changes: 63 additions & 23 deletions apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -68,73 +71,109 @@
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<typeof TintParams>) => {
function applyTint(color: d.v4f, tint: d.Infer<typeof TintParams>): 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<d.F32>,
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<d.F32>,
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<d.F32>,
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 {

Check warning on line 137 in apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts

View workflow job for this annotation

GitHub Actions / build-and-test

eslint(no-unused-vars)

Function 'premultiplyAlpha' is declared but never used.

Check warning on line 137 in apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts

View workflow job for this annotation

GitHub Actions / build-and-test

eslint(no-unused-vars)

Function 'premultiplyAlpha' is declared but never used.
'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);

const texDim = std.textureDimensions(sampledView.$, 0);
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({
Expand Down Expand Up @@ -305,5 +344,6 @@
cancelAnimationFrame(frameId);
}
window.removeEventListener('mousemove', handleMouseMove);
darkModeAccess.detach();
root.destroy();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion apps/typegpu-docs/src/examples/tests/texture-test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading
Loading