Skip to content
Merged
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
14 changes: 10 additions & 4 deletions sample/a-buffer/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mat4, vec3 } from 'wgpu-matrix';
import { GUI } from 'dat.gui';

import { quitIfWebGPUNotAvailable } from '../util';
import { quitIfWebGPUNotAvailable, quitIfLimitLessThan } from '../util';
import { mesh } from '../../meshes/teapot';

import opaqueWGSL from './opaque.wgsl';
Expand All @@ -13,8 +13,14 @@ function roundUp(n: number, k: number): number {
}

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxStorageBuffersInFragmentStage', 2, limits);
const device = await adapter?.requestDevice({
Copy link
Collaborator

@greggman greggman Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code will just throw if maxStorageBuffersInFragmentStage < 2 and the user gets a blank screen. Given we surface other errors I think it would be good to surface something, which is why I suggested something like

if (maxStorageBuffersInFragmentStage < 2) {
  fail('maxStorageBuffersInFragmentStage < 2); // export from util.ts
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. I thought the samples were checking for requestDevice() failure, but I see they're not.

I've implemented better limit checking. LMK what you think.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestDevice never fails (never turns null/undefined) so "if called correctly" there's nothing to check

requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const context = canvas.getContext('webgpu') as GPUCanvasContext;
Expand Down Expand Up @@ -185,7 +191,7 @@ const translucentBindGroupLayout = device.createBindGroupLayout({
{
binding: 3,
visibility: GPUShaderStage.FRAGMENT,
texture: { sampleType: 'depth' },
texture: { sampleType: 'unfilterable-float' },
},
{
binding: 4,
Expand Down
4 changes: 2 additions & 2 deletions sample/a-buffer/opaque.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct Uniforms {

struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) @interpolate(flat) instance: u32
@location(0) @interpolate(flat, either) instance: u32
};

@vertex
Expand All @@ -30,7 +30,7 @@ fn main_vs(@location(0) position: vec4f, @builtin(instance_index) instance: u32)
}

@fragment
fn main_fs(@location(0) @interpolate(flat) instance: u32) -> @location(0) vec4f {
fn main_fs(@location(0) @interpolate(flat, either) instance: u32) -> @location(0) vec4f {
const colors = array<vec3f,6>(
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
Expand Down
8 changes: 4 additions & 4 deletions sample/a-buffer/translucent.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ struct LinkedList {
@binding(0) @group(0) var<uniform> uniforms: Uniforms;
@binding(1) @group(0) var<storage, read_write> heads: Heads;
@binding(2) @group(0) var<storage, read_write> linkedList: LinkedList;
@binding(3) @group(0) var opaqueDepthTexture: texture_depth_2d;
@binding(3) @group(0) var opaqueDepthTexture: texture_2d<f32>;
@binding(4) @group(0) var<uniform> sliceInfo: SliceInfo;

struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) @interpolate(flat) instance: u32
@location(0) @interpolate(flat, either) instance: u32
};

@vertex
Expand All @@ -56,7 +56,7 @@ fn main_vs(@location(0) position: vec4f, @builtin(instance_index) instance: u32)
}

@fragment
fn main_fs(@builtin(position) position: vec4f, @location(0) @interpolate(flat) instance: u32) {
fn main_fs(@builtin(position) position: vec4f, @location(0) @interpolate(flat, either) instance: u32) {
const colors = array<vec3f,6>(
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
Expand All @@ -67,7 +67,7 @@ fn main_fs(@builtin(position) position: vec4f, @location(0) @interpolate(flat) i
);

let fragCoords = vec2i(position.xy);
let opaqueDepth = textureLoad(opaqueDepthTexture, fragCoords, 0);
let opaqueDepth = textureLoad(opaqueDepthTexture, fragCoords, 0).x;

// reject fragments behind opaque objects
if position.z >= opaqueDepth {
Expand Down
24 changes: 16 additions & 8 deletions sample/bitonicSort/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { GUI } from 'dat.gui';
import fullscreenTexturedQuad from '../../shaders/fullscreenTexturedQuad.wgsl';
import { quitIfAdapterNotAvailable, quitIfWebGPUNotAvailable } from '../util';
import {
quitIfAdapterNotAvailable,
quitIfWebGPUNotAvailable,
quitIfLimitLessThan,
} from '../util';

type BindGroupBindingLayout =
| GPUBufferBindingLayout
Expand Down Expand Up @@ -112,18 +116,22 @@ export const SampleInitFactoryWebGPU = async (
callback: SampleInitCallback3D
): Promise<SampleInit> => {
const init = async ({ canvas, gui, stats }) => {
const adapter = await navigator.gpu?.requestAdapter();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
quitIfAdapterNotAvailable(adapter);

const timestampQueryAvailable = adapter.features.has('timestamp-query');
let device: GPUDevice;
let features = [];
const limits: Record<string, GPUSize32> = {};
if (timestampQueryAvailable) {
device = await adapter.requestDevice({
requiredFeatures: ['timestamp-query'],
});
} else {
device = await adapter.requestDevice();
features = ['timestamp-query'];
}
quitIfLimitLessThan(adapter, 'maxStorageBuffersInFragmentStage', 1, limits);
const device = await adapter.requestDevice({
requiredFeatures: features,
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const context = canvas.getContext('webgpu') as GPUCanvasContext;
Expand Down
22 changes: 17 additions & 5 deletions sample/cornell/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,36 @@ import Radiosity from './radiosity';
import Rasterizer from './rasterizer';
import Tonemapper from './tonemapper';
import Raytracer from './raytracer';
import { quitIfAdapterNotAvailable, quitIfWebGPUNotAvailable } from '../util';
import {
quitIfAdapterNotAvailable,
quitIfWebGPUNotAvailable,
quitIfLimitLessThan,
} from '../util';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;

const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
const requiredFeatures: GPUFeatureName[] =
const features: GPUFeatureName[] =
presentationFormat === 'bgra8unorm' ? ['bgra8unorm-storage'] : [];
const adapter = await navigator.gpu?.requestAdapter();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
quitIfAdapterNotAvailable(adapter);

for (const feature of requiredFeatures) {
for (const feature of features) {
if (!adapter.features.has(feature)) {
throw new Error(
`sample requires ${feature}, but is not supported by the adapter`
);
}
}
const device = await adapter?.requestDevice({ requiredFeatures });
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxComputeWorkgroupSizeX', 256, limits);
quitIfLimitLessThan(adapter, 'maxComputeInvocationsPerWorkgroup', 256, limits);
const device = await adapter?.requestDevice({
requiredFeatures: features,
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const params: {
Expand Down
2 changes: 1 addition & 1 deletion sample/cornell/rasterizer.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct VertexOut {
@builtin(position) position : vec4f,
@location(0) uv : vec2f,
@location(1) emissive : vec3f,
@interpolate(flat)
@interpolate(flat, either)
@location(2) quad : u32,
}

Expand Down
5 changes: 4 additions & 1 deletion sample/cubemap/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import sampleCubemapWGSL from './sampleCubemap.frag.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const device = await adapter?.requestDevice();
quitIfWebGPUNotAvailable(adapter, device);

Expand Down Expand Up @@ -119,6 +121,7 @@ let cubemapTexture: GPUTexture;

cubemapTexture = device.createTexture({
dimension: '2d',
textureBindingViewDimension: 'cube',
// Create a 2d array texture.
// Assume each image has the same size.
size: [imageBitmaps[0].width, imageBitmaps[0].height, 6],
Expand Down
4 changes: 2 additions & 2 deletions sample/deferredRendering/fragmentDeferredRendering.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
@group(0) @binding(2) var gBufferDepth: texture_2d<f32>;

struct LightData {
position : vec4f,
Expand Down Expand Up @@ -40,7 +40,7 @@ fn main(
gBufferDepth,
vec2i(floor(coord.xy)),
0
);
).x;

// Don't light the sky.
if (depth >= 1.0) {
Expand Down
4 changes: 2 additions & 2 deletions sample/deferredRendering/fragmentGBuffersDebugView.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@group(0) @binding(0) var gBufferNormal: texture_2d<f32>;
@group(0) @binding(1) var gBufferAlbedo: texture_2d<f32>;
@group(0) @binding(2) var gBufferDepth: texture_depth_2d;
@group(0) @binding(2) var gBufferDepth: texture_2d<f32>;

override canvasSizeWidth: f32;
override canvasSizeHeight: f32;
Expand All @@ -16,7 +16,7 @@ fn main(
gBufferDepth,
vec2i(floor(coord.xy)),
0
);
).x;
// remap depth into something a bit more visible
let depth = (1.0 - rawDepth) * 50.0;
result = vec4(depth);
Expand Down
14 changes: 10 additions & 4 deletions sample/deferredRendering/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ import fragmentWriteGBuffers from './fragmentWriteGBuffers.wgsl';
import vertexTextureQuad from './vertexTextureQuad.wgsl';
import fragmentGBuffersDebugView from './fragmentGBuffersDebugView.wgsl';
import fragmentDeferredRendering from './fragmentDeferredRendering.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';
import { quitIfWebGPUNotAvailable, quitIfLimitLessThan } from '../util';

const kMaxNumLights = 1024;
const lightExtentMin = vec3.fromValues(-50, -30, -50);
const lightExtentMax = vec3.fromValues(50, 50, 50);

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxStorageBuffersInFragmentStage', 1, limits);
const device = await adapter?.requestDevice({
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const context = canvas.getContext('webgpu') as GPUCanvasContext;
Expand Down Expand Up @@ -165,7 +171,7 @@ const gBufferTexturesBindGroupLayout = device.createBindGroupLayout({
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
texture: {
sampleType: 'depth',
sampleType: 'unfilterable-float',
},
},
],
Expand Down
4 changes: 2 additions & 2 deletions sample/reversedZ/fragmentPrecisionErrorPass.wgsl
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@group(1) @binding(0) var depthTexture: texture_depth_2d;
@group(1) @binding(0) var depthTexture: texture_2d<f32>;

@fragment
fn main(
@builtin(position) coord: vec4f,
@location(0) clipPos: vec4f
) -> @location(0) vec4f {
let depthValue = textureLoad(depthTexture, vec2i(floor(coord.xy)), 0);
let depthValue = textureLoad(depthTexture, vec2i(floor(coord.xy)), 0).x;
let v : f32 = abs(clipPos.z / clipPos.w - depthValue) * 2000000.0;
return vec4f(v, v, v, 1.0);
}
4 changes: 2 additions & 2 deletions sample/reversedZ/fragmentTextureQuad.wgsl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@group(0) @binding(0) var depthTexture: texture_depth_2d;
@group(0) @binding(0) var depthTexture: texture_2d<f32>;

@fragment
fn main(
@builtin(position) coord : vec4f
) -> @location(0) vec4f {
let depthValue = textureLoad(depthTexture, vec2i(floor(coord.xy)), 0);
let depthValue = textureLoad(depthTexture, vec2i(floor(coord.xy)), 0).x;
return vec4f(depthValue, depthValue, depthValue, 1.0);
}
6 changes: 4 additions & 2 deletions sample/reversedZ/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ const depthClearValues = {
};

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const device = await adapter?.requestDevice();
quitIfWebGPUNotAvailable(adapter, device);

Expand Down Expand Up @@ -98,7 +100,7 @@ const depthTextureBindGroupLayout = device.createBindGroupLayout({
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
texture: {
sampleType: 'depth',
sampleType: 'unfilterable-float',
},
},
],
Expand Down
12 changes: 9 additions & 3 deletions sample/samplerParameters/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GUI } from 'dat.gui';

import texturedSquareWGSL from './texturedSquare.wgsl';
import showTextureWGSL from './showTexture.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';
import { quitIfWebGPUNotAvailable, quitIfLimitLessThan } from '../util';

const kMatrices: Readonly<Float32Array> = new Float32Array([
// Row 1: Scale by 2
Expand All @@ -28,8 +28,14 @@ const kMatrices: Readonly<Float32Array> = new Float32Array([
]);

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxStorageBuffersInVertexStage', 1, limits);
const device = await adapter?.requestDevice({
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

//
Expand Down
12 changes: 9 additions & 3 deletions sample/skinnedMesh/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
createSkinnedGridRenderPipeline,
} from './gridUtils';
import { gridIndices } from './gridData';
import { quitIfWebGPUNotAvailable } from '../util';
import { quitIfWebGPUNotAvailable, quitIfLimitLessThan } from '../util';

const MAT4X4_BYTES = 64;

Expand Down Expand Up @@ -95,8 +95,14 @@ const getRotation = (mat: Mat4): Quat => {

//Normal setup
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxStorageBuffersInVertexStage', 2, limits);
const device = await adapter?.requestDevice({
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const context = canvas.getContext('webgpu') as GPUCanvasContext;
Expand Down
13 changes: 10 additions & 3 deletions sample/textRenderingMsdf/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ import { MsdfTextRenderer } from './msdfText';

import basicVertWGSL from '../../shaders/basic.vert.wgsl';
import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';
import { quitIfWebGPUNotAvailable, quitIfLimitLessThan } from '../util';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const limits: Record<string, GPUSize32> = {};
quitIfLimitLessThan(adapter, 'maxStorageBuffersInFragmentStage', 1, limits);
quitIfLimitLessThan(adapter, 'maxStorageBuffersInVertexStage', 2, limits);
const device = await adapter?.requestDevice({
requiredLimits: limits,
});
quitIfWebGPUNotAvailable(adapter, device);

const context = canvas.getContext('webgpu') as GPUCanvasContext;
Expand Down
4 changes: 3 additions & 1 deletion sample/timestampQuery/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import PerfCounter from './PerfCounter';
import TimestampQueryManager from './TimestampQueryManager';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu?.requestAdapter();
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});

// The use of timestamps require a dedicated adapter feature:
// The adapter may or may not support timestamp queries. If not, we simply
Expand Down
Loading