Compat-ify samples (more difficult).#495
Conversation
48ef20c to
a6c3e5f
Compare
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)
a6c3e5f to
a5044b8
Compare
greggman
left a comment
There was a problem hiding this comment.
See comments. The pattern in the PR would be okay in say 6 months but it doesn't work today.
| const adapter = await navigator.gpu?.requestAdapter({ | ||
| featureLevel: 'compatibility', | ||
| }); | ||
| const device = await adapter?.requestDevice({ | ||
| requiredLimits: { maxStorageBuffersInFragmentStage: 2 }, | ||
| }); |
There was a problem hiding this comment.
@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
| 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 }, | |
| ); |
There was a problem hiding this comment.
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;
}There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
0fedee7 to
02db960
Compare
| if ('maxStorageBuffersInFragmentStage' in adapter.limits) { | ||
| limits = { maxStorageBuffersInFragmentStage: 2 }; | ||
| } | ||
| const device = await adapter?.requestDevice({ |
There was a problem hiding this comment.
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
}There was a problem hiding this comment.
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.
There was a problem hiding this comment.
requestDevice never fails (never turns null/undefined) so "if called correctly" there's nothing to check
fbba277 to
378e878
Compare
| if (limit in adapter.limits) { | ||
| const limitKey = limit as keyof GPUSupportedLimits; | ||
| const limitValue = adapter.limits[limitKey] as number; | ||
| if (limitValue < requiredValue) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Renamed QuitIfLimitNotMet() -> QuitIfLimitLessThan(). Someone else can make the GreaterThan flavour if desired.
2c2744e to
c9770d4
Compare
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.
c9770d4 to
85eb241
Compare
These samples required changes beyond requesting compatibility mode.
a-buffer
textureLoad()(issue 16)texture_2d<f32>(andunfilterable-floatsampleType) insteadinterpolate(flat)not supported (issue 17)interpolate(flat, either)insteadbitonicSort
cubemap
textureBindingViewDimensionmust be specified for cubemap sampling (issue 1)deferredRendering
textureLoad()(issue 16)texture_2d<f32>(andunfilterable-floatsampleType) insteadreversedZ
textureLoad()(issue 16)texture_2d<f32>(andunfilterable-floatsampleType) insteadsamplerParameters
skinnedMesh
textRenderingMsdf
videoUploading
wireframe