Skip to content

feat: Add cubemap reflection example, *breaking* - remove .populate method #1017

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 53 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c9d0a66
temp
reczkok Mar 6, 2025
fc11c42
progress
reczkok Mar 10, 2025
9f89870
more work
reczkok Mar 10, 2025
de3e346
yes
reczkok Mar 10, 2025
d963420
split files
reczkok Mar 13, 2025
ddd8b74
Fix bind groups for tgpu samplers
reczkok Mar 13, 2025
70f89f2
Add cubemap rendering and optimize icosphere generation
reczkok Mar 13, 2025
f7b5ed0
Use more efficient vertex data formats, add stuff
reczkok Mar 13, 2025
a774649
Fix type reference for cached offsets map key
reczkok Mar 13, 2025
e2ef26e
type fixes
reczkok Mar 13, 2025
f91ce69
Revert some unnecesary changes
reczkok Mar 18, 2025
c75a764
Rename Resource type to Snippet
reczkok Mar 20, 2025
a276929
Add slider controls for icosphere subdivisions and normals
reczkok Mar 24, 2025
2d32c32
Refactor icosphere subdivision to use iteration not recursion
reczkok Mar 27, 2025
7d4a47c
Fix WGSL type check for property access
reczkok Mar 27, 2025
4b79245
gpu time
reczkok Mar 27, 2025
4e53f48
Add a failsafe
reczkok Mar 27, 2025
bce34bc
Add texture sampling function to std and update shaders to TGSL
reczkok Mar 31, 2025
9ec9260
change name:
reczkok Mar 31, 2025
2c56716
Add vector slider control for material properties
reczkok Mar 31, 2025
2bcba7a
Refactor VectorSlider layout and slider rendering
reczkok Apr 1, 2025
14184c8
Refactor cube vertices and remove storage flag
reczkok Apr 1, 2025
f88745d
Organize code in index
reczkok Apr 1, 2025
ef0083f
Clean up IcosphereGenerator buffer management
reczkok Apr 1, 2025
7a5ad66
Fixes, help and attribution, enhancements
reczkok Apr 1, 2025
1023417
Self-nitpick
reczkok Apr 1, 2025
48e1343
Update attribution text formatting
reczkok Apr 1, 2025
b28fc34
Rename cubemapTest images and update paths
reczkok Apr 1, 2025
57fba58
Thanks mr robot
reczkok Apr 1, 2025
e9657a8
Rename Resource to Snippet (some more)
reczkok Apr 1, 2025
91a6ecf
Does-less conversion
reczkok Apr 1, 2025
22fc0af
Use the new awesome API
reczkok Apr 1, 2025
da28aef
Review fixes and more cubemaps :D
reczkok Apr 2, 2025
9d136ae
fix cubemap loading and attempt to add stackblitz vector slider
reczkok Apr 2, 2025
bf34ab4
How did that even happen
reczkok Apr 2, 2025
e38a93c
Fix texture type expectation in bindGroup test
reczkok Apr 2, 2025
b4f94da
Fix vector slider state management
reczkok Apr 2, 2025
16d442d
Refactor VectorSlider to use number arrays
reczkok Apr 3, 2025
111ee42
Merge branch 'main' into feat/game-of-disco
reczkok Apr 3, 2025
eae4685
Remove .js extension from imports
reczkok Apr 3, 2025
84f56a5
Make stackblitz types happy
reczkok Apr 3, 2025
07ddb52
Convert initial colors to arrays
reczkok Apr 3, 2025
7cac646
Refactor vector slider implementation in StackBlitz component
reczkok Apr 3, 2025
2750713
Review fixes and refactoring
reczkok Apr 7, 2025
d8a4b2d
Merge branch 'main' into feat/game-of-disco
reczkok Apr 7, 2025
72f2110
apply review fixes
reczkok Apr 8, 2025
778215f
Add animation loop cleanup to prevent post-destruction calls
reczkok Apr 8, 2025
34a609e
Merge branch 'main' into feat/game-of-disco
reczkok Apr 8, 2025
77f1a58
Merge branch 'main' into feat/game-of-disco
reczkok Apr 9, 2025
a162fe0
Update function parameter style in helpers and add missing Prettify
reczkok Apr 9, 2025
45b0677
Remove Prettify wrap from memIdent type property
reczkok Apr 9, 2025
199b2ab
Remove deprecated populate method and add back prettify
reczkok Apr 10, 2025
6ef1460
Merge branch 'main' into feat/game-of-disco
reczkok Apr 10, 2025
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions apps/typegpu-docs/src/components/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Select } from './design/Select.tsx';
import { Slider } from './design/Slider.tsx';
import { TextArea } from './design/TextArea.tsx';
import { Toggle } from './design/Toggle.tsx';
import { VectorSlider } from './design/VectorSlider.tsx';
import { openInStackBlitz } from './stackblitz/openInStackBlitz.ts';

function ToggleRow({
Expand Down Expand Up @@ -90,6 +91,42 @@ function SliderRow({
);
}

function VectorSliderRow({
label,
initial,
min,
max,
step,
onChange,
}: {
label: string;
initial?: number[];
min: number[];
max: number[];
step: number[];
onChange: (value: number[]) => void;
}) {
const [value, setValue] = useState<number[]>(initial ?? min);
const runWithCatch = useSetAtom(runWithCatchAtom);

return (
<>
<div className="text-sm">{label}</div>

<VectorSlider
min={min}
max={max}
step={step}
value={value}
onChange={(newValue) => {
setValue(newValue);
runWithCatch(() => onChange(newValue));
}}
/>
</>
);
}

function TextAreaRow({
label,
initial,
Expand Down Expand Up @@ -183,6 +220,16 @@ function paramToControlRow(param: ExampleControlParam) {
step={param.step}
initial={param.initial}
/>
) : 'onVectorSliderChange' in param ? (
<VectorSliderRow
key={param.label}
label={param.label}
onChange={param.onVectorSliderChange}
min={param.min}
max={param.max}
step={param.step}
initial={param.initial}
/>
) : 'onButtonClick' in param ? (
<ButtonRow
key={param.label}
Expand Down
49 changes: 49 additions & 0 deletions apps/typegpu-docs/src/components/design/VectorSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as RadixSlider from '@radix-ui/react-slider';

type Props = {
min: number[];
max: number[];
step: number[];
value: number[];
onChange: (value: number[]) => void;
};

export function VectorSlider({ min, max, step, value, onChange }: Props) {
const handleComponentChange = (index: number, newValue: number) => {
onChange([...value.slice(0, index), newValue, ...value.slice(index + 1)]);
};

const renderSlider = (index: number) => (
<div key={index} className="flex items-center flex-1">
<RadixSlider.Root
value={[value[index]]}
min={min[index]}
max={max[index]}
step={step[index]}
onValueChange={(values) => handleComponentChange(index, values[0])}
className="bg-grayscale-20 h-10 rounded-[0.25rem] relative flex overflow-hidden flex-1"
>
<RadixSlider.Track className="flex-1 h-full">
<RadixSlider.Range className="absolute h-full bg-gradient-to-br from-gradient-purple to-gradient-blue" />
</RadixSlider.Track>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-center">
{value[index].toFixed(2)}
</div>
</RadixSlider.Root>
</div>
);

const containerClass = 'flex w-full';
const rowClass = 'flex flex-row gap-4 w-full';

return value.length === 4 ? (
<div className={`${containerClass} flex-col gap-2`}>
<div className={rowClass}>{[0, 1].map(renderSlider)}</div>
<div className={rowClass}>{[2, 3].map(renderSlider)}</div>
</div>
) : (
<div className={`${containerClass} flex-row gap-1`}>
{value.map((_, index) => renderSlider(index))}
</div>
);
}
53 changes: 52 additions & 1 deletion apps/typegpu-docs/src/components/stackblitz/stackBlitzIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ for (const controls of Object.values(example)) {
params.onSelectChange(select.value);
}

if ('onVectorSliderChange' in params) {
const sliderContainer = document.createElement('div');
sliderContainer.style.display = 'flex';
sliderContainer.style.flexDirection = 'column';
sliderContainer.style.gap = '0.2rem';

const currentValues = params.initial
? [...params.initial]
: [...params.min];
const length = params.min.length;
const labels = ['x', 'y', 'z', 'w'];

for (let i = 0; i < length; i++) {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.gap = '0.2rem';

const labelSpan = document.createElement('span');
labelSpan.innerText = labels[i];

const slider = document.createElement('input');
slider.type = 'range';
slider.min = `${params.min[i]}`;
slider.max = `${params.max[i]}`;
slider.step = `${params.step[i] ?? 0.1}`;
slider.value = `${currentValues[i]}`;

slider.addEventListener('input', () => {
currentValues[i] = Number.parseFloat(slider.value);
params.onVectorSliderChange(currentValues);
});

row.appendChild(labelSpan);
row.appendChild(slider);
sliderContainer.appendChild(row);
}

params.onVectorSliderChange(currentValues);
controlRow.appendChild(sliderContainer);
}

if ('onToggleChange' in params) {
const toggle = document.createElement('input');
toggle.type = 'checkbox';
Expand Down Expand Up @@ -169,6 +211,14 @@ type SliderControlParam = {
step?: number;
};

type VectorSliderControlParam = {
onVectorSliderChange: (newValue: number[]) => void;
initial?: number[];
min: number[];
max: number[];
step: number[];
};

type ButtonControlParam = {
onButtonClick: (() => void) | (() => Promise<void>);
};
Expand All @@ -183,4 +233,5 @@ type ExampleControlParam =
| ToggleControlParam
| SliderControlParam
| ButtonControlParam
| TextAreaControlParam;
| TextAreaControlParam
| VectorSliderControlParam;
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { TgpuRoot } from 'typegpu';
import * as d from 'typegpu/data';
import { CubeVertex } from './dataTypes.ts';

function vert(
position: [number, number, number, number],
uv: [number, number],
) {
return CubeVertex({
position: d.vec4f(...position),
uv: d.vec2f(...uv),
});
}

export const cubeVertices: d.Infer<typeof CubeVertex>[] = [
// Bottom face
vert([-1, -1, -1, 1], [0, 0]),
vert([1, -1, -1, 1], [1, 0]),
vert([1, -1, 1, 1], [1, 1]),
vert([1, -1, 1, 1], [1, 1]),
vert([-1, -1, 1, 1], [0, 1]),
vert([-1, -1, -1, 1], [0, 0]),

// Right face
vert([1, 1, 1, 1], [0, 1]),
vert([1, -1, 1, 1], [1, 1]),
vert([1, -1, -1, 1], [1, 0]),
vert([1, 1, -1, 1], [0, 0]),
vert([1, 1, 1, 1], [0, 1]),
vert([1, -1, -1, 1], [1, 0]),

// Top face
vert([-1, 1, 1, 1], [0, 1]),
vert([1, 1, 1, 1], [1, 1]),
vert([1, 1, -1, 1], [1, 0]),
vert([-1, 1, -1, 1], [0, 0]),
vert([-1, 1, 1, 1], [0, 1]),
vert([1, 1, -1, 1], [1, 0]),

// Left face
vert([-1, -1, 1, 1], [0, 1]),
vert([-1, 1, 1, 1], [1, 1]),
vert([-1, 1, -1, 1], [1, 0]),
vert([-1, -1, -1, 1], [0, 0]),
vert([-1, -1, 1, 1], [0, 1]),
vert([-1, 1, -1, 1], [1, 0]),

// Front face
vert([1, 1, 1, 1], [0, 1]),
vert([-1, 1, 1, 1], [1, 1]),
vert([-1, -1, 1, 1], [1, 0]),
vert([-1, -1, 1, 1], [1, 0]),
vert([1, -1, 1, 1], [0, 0]),
vert([1, 1, 1, 1], [0, 1]),

// Back face
vert([1, -1, -1, 1], [0, 1]),
vert([-1, -1, -1, 1], [1, 1]),
vert([-1, 1, -1, 1], [1, 0]),
vert([1, 1, -1, 1], [0, 0]),
vert([1, -1, -1, 1], [0, 1]),
vert([-1, 1, -1, 1], [1, 0]),
];

export type CubemapNames = 'campsite' | 'beach' | 'chapel' | 'city';
function getCubemapUrls(name: CubemapNames) {
return ['posx', 'negx', 'posy', 'negy', 'posz', 'negz'].map(
(side) => `assets/cubemap-reflection/${name}/${side}.jpg`,
);
}

export async function loadCubemap(root: TgpuRoot, chosenCubemap: CubemapNames) {
const size = 2048;
const texture = root['~unstable']
.createTexture({
dimension: '2d',
size: [size, size, 6],
format: 'rgba8unorm',
})
.$usage('sampled', 'render');

await Promise.all(
getCubemapUrls(chosenCubemap).map(async (url, i) => {
const response = await fetch(url);
const blob = await response.blob();
const imageBitmap = await createImageBitmap(blob);

root.device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture: root.unwrap(texture), mipLevel: 0, origin: [0, 0, i] },
[size, size],
);
}),
);

return texture;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as d from 'typegpu/data';

export const Camera = d.struct({
view: d.mat4x4f,
projection: d.mat4x4f,
position: d.vec4f,
});

export const Vertex = d.unstruct({
position: d.float16x4,
normal: d.float16x4,
});

export const ComputeVertex = d.struct({
position: d.vec2u, // four packed 16-bit floats
normal: d.vec2u, // four packed 16-bit floats
});

export const CubeVertex = d.struct({
position: d.vec4f,
uv: d.vec2f,
});

export const DirectionalLight = d.struct({
direction: d.vec3f,
color: d.vec3f,
intensity: d.f32,
});

export const Material = d.struct({
ambient: d.vec3f,
diffuse: d.vec3f,
specular: d.vec3f,
shininess: d.f32,
reflectivity: d.f32,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

export const unpackVec2u = tgpu['~unstable'].fn(
[d.vec2u],
d.vec4f,
)((packed) => {
const xy = std.unpack2x16float(packed.x);
const zw = std.unpack2x16float(packed.y);
return d.vec4f(xy, zw);
});

export const packVec2u = tgpu['~unstable'].fn(
[d.vec4f],
d.vec2u,
)((toPack) => {
const xy = std.pack2x16float(toPack.xy);
const zw = std.pack2x16float(toPack.zw);
return d.vec2u(xy, zw);
});

export const getNormal = tgpu['~unstable'].fn(
{
v1: d.vec4f,
v2: d.vec4f,
v3: d.vec4f,
smoothNormals: d.u32,
vertexPos: d.vec4f,
},
d.vec4f,
)(({ v1, v2, v3, smoothNormals, vertexPos }) => {
'kernel & js';
if (smoothNormals === 1) {
return vertexPos;
}
const edge1 = std.sub(v2.xyz, v1.xyz);
const edge2 = std.sub(v3.xyz, v1.xyz);
return std.normalize(d.vec4f(std.cross(edge1, edge2), 0));
});

export const calculateMidpoint = tgpu['~unstable'].fn(
[d.vec4f, d.vec4f],
d.vec4f,
)((v1, v2) => {
return d.vec4f(std.mul(0.5, std.add(v1.xyz, v2.xyz)), 1);
});
Loading