Skip to content

Conversation

@SenorBlanco
Copy link
Collaborator

@SenorBlanco SenorBlanco commented Mar 13, 2025

These samples required changes beyond requesting compatibility mode.

a-buffer

  • requires storage buffers in fragment stage (issue 19)
  • depth texture binding cannot be used in textureLoad() (issue 16)
    • bind as texture_2d<f32> (and unfilterable-float sampleType) instead
  • interpolate(flat) not supported (issue 17)
    • use interpolate(flat, either) instead

bitonicSort

  • requires storage buffers in fragment stage (issue 19)

cubemap

  • textureBindingViewDimension must be specified for cubemap sampling (issue 1)

deferredRendering

  • depth texture binding cannot be used in textureLoad() (issue 16)
    • bind as texture_2d<f32> (and unfilterable-float sampleType) instead
  • requires storage buffers in fragment stage (issue 19)

reversedZ

  • depth texture binding cannot be used in textureLoad() (issue 16)
    • bind as texture_2d<f32> (and unfilterable-float sampleType) instead

samplerParameters

  • requires storage buffers in vertex stage (issue 18)

skinnedMesh

  • requires storage buffers in vertex stage (issue 18)

textRenderingMsdf

  • requires storage buffers in vertex stage (issue 18)
  • requires storage buffers in fragment stage (issue 19)

videoUploading

  • no additional changes required (missed this one in the first pass)

wireframe

  • requires storage buffers in vertex stage (issue 18)

@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch from 48ef20c to a6c3e5f Compare March 13, 2025 20:26
These samples required changes beyond requesting compatibility mode.

a-buffer
* requires storage buffers in fragment stage (issue 19)
* depth texture binding cannot be used in textureLoad() (issue 16)
  * bind as texture_2d<f32> (and unfilterable-float sampleType) instead
* interpolate(flat) not supported (issue 17)
  * use interpolate(flat, either) instead

bitonicSort
* requires storage buffers in fragment stage (issue 19)

cubemap
* textureBindingViewDimension must be specified for cubemap sampling (issue 1)

deferredRendering
* depth texture binding cannot be used in textureLoad() (issue 16)
  * bind as texture_2d<f32> (and unfilterable-float sampleType) instead
* requires storage buffers in fragment stage (issue 19)

reversedZ
* depth texture binding cannot be used in textureLoad() (issue 16)
  * bind as texture_2d<f32> (and unfilterable-float sampleType) instead

samplerParameters
* requires storage buffers in vertex stage (issue 18)

skinnedMesh
* requires storage buffers in vertex stage (issue 18)

textRenderingMsdf
* requires storage buffers in vertex stage (issue 18)
* requires storage buffers in fragment stage (issue 19)

videoUploading
* no additional changes required (missed this one in the first pass)

wireframe
* requires storage buffers in vertex stage (issue 18)
@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch from a6c3e5f to a5044b8 Compare March 13, 2025 20:30
Copy link
Collaborator

@greggman greggman left a comment

Choose a reason for hiding this comment

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

See comments. The pattern in the PR would be okay in say 6 months but it doesn't work today.

Comment on lines 16 to 21
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const device = await adapter?.requestDevice({
requiredLimits: { maxStorageBuffersInFragmentStage: 2 },
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

@kainino0x pointed out this will fail on current chrome/firefox/safari

To make it work requires only adding it IF maxStorageBuffersInFragmentStage exists on adapter.limits because otherwise it's an unknown limit and will fail device creation.

There's a bunch of solutions - they all have trade offs.

Here's one

Suggested change
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const device = await adapter?.requestDevice({
requiredLimits: { maxStorageBuffersInFragmentStage: 2 },
});
const adapter = await navigator.gpu?.requestAdapter({
featureLevel: 'compatibility',
});
const { maxStorageBuffersInFragmentStage } = adapter.limits;
// Note: maxStorageBuffersInFragmentStage may be undefined in old browsers
// but that's ok as it will fail this check and old browser only support core
// which always has enough storage buffers in fragment shaders.
if (maxStorageBuffersInFragmentStage < 2) {
fail('maxStorageBuffersInFragmentStage < 2'); // export from util.ts
}
const device = await adapter?.requestDevice({
requiredLimits: { maxStorageBuffersInFragmentStage },
);

Copy link
Collaborator

@greggman greggman Mar 13, 2025

Choose a reason for hiding this comment

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

Note: I'm not sure that example is acceptable nor not since it's not requesting 2, it's requesting max. The more I have to deal with features and limits the more think you should just always get max limits. It's much easier to deal with.

const adapter = await navigator.gpu?.requestAdapter({
  featureLevel: 'compatibility',
});
const device = await adapter.requestDevice({
  requiredFeatures: adapter.features,
  requiredLimits: objLikeToObj(adapter.limits), // need a helper ATM
});
if (device.limits.maxStorageBuffersInFragmentStage < 2) {
  fail('I can has no running');
}

It's way simpler than the summersault of checking for in adapter and applying in device but it's not the intent of the spec. 😕

function objLikeToObj(objLike) {
  const obj = {};
  for (const key of objLike) {
    obj[key] = objLike[key];
  }
  return obj;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks.

const { maxStorageBuffersInFragmentStage } = adapter.limits;

gave me:

(!) [plugin typescript] sample/a-buffer/main.ts (20:9): @rollup/plugin-typescript TS2339: Property 'maxStorageBuffersInFragmentStage' does not exist on type 'GPUSupportedLimits'.
/home/senorblanco/src/webgpu-samples/sample/a-buffer/main.ts:20:9

I used

if ('maxStorageBuffersInFragmentStage' in adapter.limits) {

instead. LMK if that's kosher.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note: I'm not sure that example is acceptable nor not since it's not requesting 2, it's requesting max. The more I have to deal with features and limits the more think you should just always get max limits. It's much easier to deal with.

It is easier, but I think it's dangerous, in that it turns WebGPU into OpenGL: you could write some code that runs fine on your machine (with above-average limits), but won't run elsewhere (with default limits). I'd prefer to request the minimum that the sample actually needs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

const { maxStorageBuffersInFragmentStage } = adapter.limits;

It gets an error because the types need to be updated

npm install --save-dev @webgpu/types@latest

This will prevent breakage on WebGPU implementations that don't
support those limits yet.
@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch from 0fedee7 to 02db960 Compare March 13, 2025 23:35
if ('maxStorageBuffersInFragmentStage' in adapter.limits) {
limits = { maxStorageBuffersInFragmentStage: 2 };
}
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

@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch 3 times, most recently from fbba277 to 378e878 Compare March 17, 2025 19:06
@SenorBlanco SenorBlanco requested a review from greggman March 17, 2025 19:08
Copy link
Collaborator

@greggman greggman left a comment

Choose a reason for hiding this comment

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

lgtm! one nit.

if (limit in adapter.limits) {
const limitKey = limit as keyof GPUSupportedLimits;
const limitValue = adapter.limits[limitKey] as number;
if (limitValue < requiredValue) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: some limits are minXXX instead of maxXXX. Up to you if you want if it now or wait for someone to stumble on it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renamed QuitIfLimitNotMet() -> QuitIfLimitLessThan(). Someone else can make the GreaterThan flavour if desired.

@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch 5 times, most recently from 2c2744e to c9770d4 Compare March 17, 2025 20:21
Implement a "quitIfLimitNotMet()" helper that tests for the existence
of a limit in adapter.limits, checks that it meets a certain value,
and errors if not. If successful, it will add the limit to a
GPURequiredLimits object.
@SenorBlanco SenorBlanco force-pushed the enable-compat-difficult branch from c9770d4 to 85eb241 Compare March 17, 2025 20:22
@SenorBlanco SenorBlanco merged commit 50fdb54 into webgpu:main Mar 17, 2025
1 check passed
@SenorBlanco SenorBlanco deleted the enable-compat-difficult branch March 21, 2025 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants