Skip to content

Commit

Permalink
Implement wgpuAdapterGetLimits, add test that limit requests are appl…
Browse files Browse the repository at this point in the history
…ied (#21799)

- Implement the wgpuAdapterGetLimits entry point.
- Add a test which verifies that limit requests are correctly passed through to
  requestDevice(), by requesting all of the adapter's max limits, then ensuring
  that the device has the same limits. This verifies that wgpuAdapterGetLimits
  and wgpuDeviceGetLimits work, and that limit requests are applied (though it
  can only verify limits for which the current system supports a value better
  than the default).

Fixes #21798
  • Loading branch information
pkomon-tgm committed May 1, 2024
1 parent c54609c commit 849342f
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 46 deletions.
100 changes: 54 additions & 46 deletions src/library_webgpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,54 @@ var LibraryWebGPU = {
return desc;
},

fillLimitStruct: (limits, supportedLimitsOutPtr) => {
var limitsOutPtr = supportedLimitsOutPtr + {{{ C_STRUCTS.WGPUSupportedLimits.limits }}};

function setLimitValueU32(name, limitOffset) {
var limitValue = limits[name];
{{{ makeSetValue('limitsOutPtr', 'limitOffset', 'limitValue', 'i32') }}};
}
function setLimitValueU64(name, limitOffset) {
var limitValue = limits[name];
{{{ makeSetValue('limitsOutPtr', 'limitOffset', 'limitValue', 'i64') }}};
}

setLimitValueU32('maxTextureDimension1D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension1D }}});
setLimitValueU32('maxTextureDimension2D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension2D }}});
setLimitValueU32('maxTextureDimension3D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension3D }}});
setLimitValueU32('maxTextureArrayLayers', {{{ C_STRUCTS.WGPULimits.maxTextureArrayLayers }}});
setLimitValueU32('maxBindGroups', {{{ C_STRUCTS.WGPULimits.maxBindGroups }}});
setLimitValueU32('maxBindGroupsPlusVertexBuffers', {{{ C_STRUCTS.WGPULimits.maxBindGroupsPlusVertexBuffers }}});
setLimitValueU32('maxBindingsPerBindGroup', {{{ C_STRUCTS.WGPULimits.maxBindingsPerBindGroup }}});
setLimitValueU32('maxDynamicUniformBuffersPerPipelineLayout', {{{ C_STRUCTS.WGPULimits.maxDynamicUniformBuffersPerPipelineLayout }}});
setLimitValueU32('maxDynamicStorageBuffersPerPipelineLayout', {{{ C_STRUCTS.WGPULimits.maxDynamicStorageBuffersPerPipelineLayout }}});
setLimitValueU32('maxSampledTexturesPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxSampledTexturesPerShaderStage }}});
setLimitValueU32('maxSamplersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxSamplersPerShaderStage }}});
setLimitValueU32('maxStorageBuffersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxStorageBuffersPerShaderStage }}});
setLimitValueU32('maxStorageTexturesPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxStorageTexturesPerShaderStage }}});
setLimitValueU32('maxUniformBuffersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxUniformBuffersPerShaderStage }}});
setLimitValueU32('minUniformBufferOffsetAlignment', {{{ C_STRUCTS.WGPULimits.minUniformBufferOffsetAlignment }}});
setLimitValueU32('minStorageBufferOffsetAlignment', {{{ C_STRUCTS.WGPULimits.minStorageBufferOffsetAlignment }}});

setLimitValueU64('maxUniformBufferBindingSize', {{{ C_STRUCTS.WGPULimits.maxUniformBufferBindingSize }}});
setLimitValueU64('maxStorageBufferBindingSize', {{{ C_STRUCTS.WGPULimits.maxStorageBufferBindingSize }}});

setLimitValueU32('maxVertexBuffers', {{{ C_STRUCTS.WGPULimits.maxVertexBuffers }}});
setLimitValueU32('maxBufferSize', {{{ C_STRUCTS.WGPULimits.maxBufferSize }}});
setLimitValueU32('maxVertexAttributes', {{{ C_STRUCTS.WGPULimits.maxVertexAttributes }}});
setLimitValueU32('maxVertexBufferArrayStride', {{{ C_STRUCTS.WGPULimits.maxVertexBufferArrayStride }}});
setLimitValueU32('maxInterStageShaderComponents', {{{ C_STRUCTS.WGPULimits.maxInterStageShaderComponents }}});
setLimitValueU32('maxInterStageShaderVariables', {{{ C_STRUCTS.WGPULimits.maxInterStageShaderVariables }}});
setLimitValueU32('maxColorAttachments', {{{ C_STRUCTS.WGPULimits.maxColorAttachments }}});
setLimitValueU32('maxColorAttachmentBytesPerSample', {{{ C_STRUCTS.WGPULimits.maxColorAttachmentBytesPerSample }}});
setLimitValueU32('maxComputeWorkgroupStorageSize', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupStorageSize }}});
setLimitValueU32('maxComputeInvocationsPerWorkgroup', {{{ C_STRUCTS.WGPULimits.maxComputeInvocationsPerWorkgroup }}});
setLimitValueU32('maxComputeWorkgroupSizeX', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeX }}});
setLimitValueU32('maxComputeWorkgroupSizeY', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeY }}});
setLimitValueU32('maxComputeWorkgroupSizeZ', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeZ }}});
setLimitValueU32('maxComputeWorkgroupsPerDimension', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupsPerDimension }}});
},

// Map from enum string back to enum number, for callbacks.
Int_BufferMapState: {
'unmapped': 0,
Expand Down Expand Up @@ -768,50 +816,7 @@ var LibraryWebGPU = {

wgpuDeviceGetLimits: (deviceId, limitsOutPtr) => {
var device = WebGPU.mgrDevice.objects[deviceId].object;
var limitsPtr = {{{ C_STRUCTS.WGPUSupportedLimits.limits }}};
function setLimitValueU32(name, limitOffset) {
var limitValue = device.limits[name];
{{{ makeSetValue('limitsOutPtr', 'limitsPtr + limitOffset', 'limitValue', 'i32') }}};
}
function setLimitValueU64(name, limitOffset) {
var limitValue = device.limits[name];
{{{ makeSetValue('limitsOutPtr', 'limitsPtr + limitOffset', 'limitValue', 'i64') }}};
}

setLimitValueU32('maxTextureDimension1D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension1D }}});
setLimitValueU32('maxTextureDimension2D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension2D }}});
setLimitValueU32('maxTextureDimension3D', {{{ C_STRUCTS.WGPULimits.maxTextureDimension3D }}});
setLimitValueU32('maxTextureArrayLayers', {{{ C_STRUCTS.WGPULimits.maxTextureArrayLayers }}});
setLimitValueU32('maxBindGroups', {{{ C_STRUCTS.WGPULimits.maxBindGroups }}});
setLimitValueU32('maxBindGroupsPlusVertexBuffers', {{{ C_STRUCTS.WGPULimits.maxBindGroupsPlusVertexBuffers }}});
setLimitValueU32('maxBindingsPerBindGroup', {{{ C_STRUCTS.WGPULimits.maxBindingsPerBindGroup }}});
setLimitValueU32('maxDynamicUniformBuffersPerPipelineLayout', {{{ C_STRUCTS.WGPULimits.maxDynamicUniformBuffersPerPipelineLayout }}});
setLimitValueU32('maxDynamicStorageBuffersPerPipelineLayout', {{{ C_STRUCTS.WGPULimits.maxDynamicStorageBuffersPerPipelineLayout }}});
setLimitValueU32('maxSampledTexturesPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxSampledTexturesPerShaderStage }}});
setLimitValueU32('maxSamplersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxSamplersPerShaderStage }}});
setLimitValueU32('maxStorageBuffersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxStorageBuffersPerShaderStage }}});
setLimitValueU32('maxStorageTexturesPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxStorageTexturesPerShaderStage }}});
setLimitValueU32('maxUniformBuffersPerShaderStage', {{{ C_STRUCTS.WGPULimits.maxUniformBuffersPerShaderStage }}});
setLimitValueU32('minUniformBufferOffsetAlignment', {{{ C_STRUCTS.WGPULimits.minUniformBufferOffsetAlignment }}});
setLimitValueU32('minStorageBufferOffsetAlignment', {{{ C_STRUCTS.WGPULimits.minStorageBufferOffsetAlignment }}});

setLimitValueU64('maxUniformBufferBindingSize', {{{ C_STRUCTS.WGPULimits.maxUniformBufferBindingSize }}});
setLimitValueU64('maxStorageBufferBindingSize', {{{ C_STRUCTS.WGPULimits.maxStorageBufferBindingSize }}});

setLimitValueU32('maxVertexBuffers', {{{ C_STRUCTS.WGPULimits.maxVertexBuffers }}});
setLimitValueU32('maxBufferSize', {{{ C_STRUCTS.WGPULimits.maxBufferSize }}});
setLimitValueU32('maxVertexAttributes', {{{ C_STRUCTS.WGPULimits.maxVertexAttributes }}});
setLimitValueU32('maxVertexBufferArrayStride', {{{ C_STRUCTS.WGPULimits.maxVertexBufferArrayStride }}});
setLimitValueU32('maxInterStageShaderComponents', {{{ C_STRUCTS.WGPULimits.maxInterStageShaderComponents }}});
setLimitValueU32('maxInterStageShaderVariables', {{{ C_STRUCTS.WGPULimits.maxInterStageShaderVariables }}});
setLimitValueU32('maxColorAttachments', {{{ C_STRUCTS.WGPULimits.maxColorAttachments }}});
setLimitValueU32('maxColorAttachmentBytesPerSample', {{{ C_STRUCTS.WGPULimits.maxColorAttachmentBytesPerSample }}});
setLimitValueU32('maxComputeWorkgroupStorageSize', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupStorageSize }}});
setLimitValueU32('maxComputeInvocationsPerWorkgroup', {{{ C_STRUCTS.WGPULimits.maxComputeInvocationsPerWorkgroup }}});
setLimitValueU32('maxComputeWorkgroupSizeX', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeX }}});
setLimitValueU32('maxComputeWorkgroupSizeY', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeY }}});
setLimitValueU32('maxComputeWorkgroupSizeZ', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeZ }}});
setLimitValueU32('maxComputeWorkgroupsPerDimension', {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupsPerDimension }}});
WebGPU.fillLimitStruct(device.limits, limitsOutPtr);
return 1;
},

Expand Down Expand Up @@ -2537,8 +2542,9 @@ var LibraryWebGPU = {
},

wgpuAdapterGetLimits: (adapterId, limitsOutPtr) => {
abort('TODO: wgpuAdapterGetLimits unimplemented');
return 0;
var adapter = WebGPU.mgrAdapter.get(adapterId);
WebGPU.fillLimitStruct(adapter.limits, limitsOutPtr);
return 1;
},

wgpuAdapterHasFeature: (adapterId, featureEnumValue) => {
Expand Down Expand Up @@ -2599,11 +2605,13 @@ var LibraryWebGPU = {
setLimitU64IfDefined("maxUniformBufferBindingSize", {{{ C_STRUCTS.WGPULimits.maxUniformBufferBindingSize }}});
setLimitU64IfDefined("maxStorageBufferBindingSize", {{{ C_STRUCTS.WGPULimits.maxStorageBufferBindingSize }}});
setLimitU32IfDefined("maxVertexBuffers", {{{ C_STRUCTS.WGPULimits.maxVertexBuffers }}});
setLimitU32IfDefined("maxBufferSize", {{{ C_STRUCTS.WGPULimits.maxBufferSize }}});
setLimitU32IfDefined("maxVertexAttributes", {{{ C_STRUCTS.WGPULimits.maxVertexAttributes }}});
setLimitU32IfDefined("maxVertexBufferArrayStride", {{{ C_STRUCTS.WGPULimits.maxVertexBufferArrayStride }}});
setLimitU32IfDefined("maxInterStageShaderComponents", {{{ C_STRUCTS.WGPULimits.maxInterStageShaderComponents }}});
setLimitU32IfDefined("maxInterStageShaderVariables", {{{ C_STRUCTS.WGPULimits.maxInterStageShaderVariables }}});
setLimitU32IfDefined("maxColorAttachments", {{{ C_STRUCTS.WGPULimits.maxColorAttachments }}});
setLimitU32IfDefined("maxColorAttachmentBytesPerSample", {{{ C_STRUCTS.WGPULimits.maxColorAttachmentBytesPerSample }}});
setLimitU32IfDefined("maxComputeWorkgroupStorageSize", {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupStorageSize }}});
setLimitU32IfDefined("maxComputeInvocationsPerWorkgroup", {{{ C_STRUCTS.WGPULimits.maxComputeInvocationsPerWorkgroup }}});
setLimitU32IfDefined("maxComputeWorkgroupSizeX", {{{ C_STRUCTS.WGPULimits.maxComputeWorkgroupSizeX }}});
Expand Down
4 changes: 4 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4522,6 +4522,10 @@ def test_webgl_simple_extensions(self, simple_enable_extensions, webgl_version):
def test_webgpu_basic_rendering(self, args):
self.btest_exit('webgpu_basic_rendering.cpp', args=['-sUSE_WEBGPU'] + args)

@requires_graphics_hardware
def test_webgpu_required_limits(self):
self.btest_exit('webgpu_required_limits.c', args=['-sUSE_WEBGPU', '-sASYNCIFY'])

# TODO(#19645): Extend this test to proxied WebGPU when it's re-enabled.
@requires_graphics_hardware
def test_webgpu_basic_rendering_pthreads(self):
Expand Down
95 changes: 95 additions & 0 deletions test/webgpu_required_limits.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include <assert.h>
#include <emscripten.h>
#include <stdio.h>
#include <webgpu/webgpu.h>

WGPUSupportedLimits adapter_supported_limits = {
0,
};

void assertLimitsCompatible(WGPULimits required_limits,
WGPULimits supported_limits) {
#define ASSERT_LIMITS_COMPATIBLE(limitName) \
assert(required_limits.limitName == supported_limits.limitName)
ASSERT_LIMITS_COMPATIBLE(maxTextureDimension1D);
ASSERT_LIMITS_COMPATIBLE(maxTextureDimension2D);
ASSERT_LIMITS_COMPATIBLE(maxTextureDimension3D);
ASSERT_LIMITS_COMPATIBLE(maxTextureArrayLayers);
ASSERT_LIMITS_COMPATIBLE(maxBindGroups);
ASSERT_LIMITS_COMPATIBLE(maxBindGroupsPlusVertexBuffers);
ASSERT_LIMITS_COMPATIBLE(maxBindingsPerBindGroup);
ASSERT_LIMITS_COMPATIBLE(maxDynamicUniformBuffersPerPipelineLayout);
ASSERT_LIMITS_COMPATIBLE(maxDynamicStorageBuffersPerPipelineLayout);
ASSERT_LIMITS_COMPATIBLE(maxSampledTexturesPerShaderStage);
ASSERT_LIMITS_COMPATIBLE(maxSamplersPerShaderStage);
ASSERT_LIMITS_COMPATIBLE(maxStorageBuffersPerShaderStage);
ASSERT_LIMITS_COMPATIBLE(maxStorageTexturesPerShaderStage);
ASSERT_LIMITS_COMPATIBLE(maxUniformBuffersPerShaderStage);
ASSERT_LIMITS_COMPATIBLE(minUniformBufferOffsetAlignment);
ASSERT_LIMITS_COMPATIBLE(minStorageBufferOffsetAlignment);
ASSERT_LIMITS_COMPATIBLE(maxUniformBufferBindingSize);
ASSERT_LIMITS_COMPATIBLE(maxStorageBufferBindingSize);
ASSERT_LIMITS_COMPATIBLE(maxVertexBuffers);
ASSERT_LIMITS_COMPATIBLE(maxBufferSize);
ASSERT_LIMITS_COMPATIBLE(maxVertexAttributes);
ASSERT_LIMITS_COMPATIBLE(maxVertexBufferArrayStride);
ASSERT_LIMITS_COMPATIBLE(maxInterStageShaderComponents);
ASSERT_LIMITS_COMPATIBLE(maxInterStageShaderVariables);
ASSERT_LIMITS_COMPATIBLE(maxColorAttachments);
ASSERT_LIMITS_COMPATIBLE(maxColorAttachmentBytesPerSample);
ASSERT_LIMITS_COMPATIBLE(maxComputeWorkgroupStorageSize);
ASSERT_LIMITS_COMPATIBLE(maxComputeInvocationsPerWorkgroup);
ASSERT_LIMITS_COMPATIBLE(maxComputeWorkgroupSizeX);
ASSERT_LIMITS_COMPATIBLE(maxComputeWorkgroupSizeY);
ASSERT_LIMITS_COMPATIBLE(maxComputeWorkgroupSizeZ);
ASSERT_LIMITS_COMPATIBLE(maxComputeWorkgroupsPerDimension);
#undef ASSERT_LIMITS_COMPATIBLE
}

void on_device_request_ended(WGPURequestDeviceStatus status,
WGPUDevice device,
char const* message,
void* userdata) {
assert(status == WGPURequestDeviceStatus_Success);

WGPUSupportedLimits device_supported_limits;
wgpuDeviceGetLimits(device, &device_supported_limits);

// verify that the obtained device fullfils required limits
assertLimitsCompatible(adapter_supported_limits.limits,
device_supported_limits.limits);
}

void on_adapter_request_ended(WGPURequestAdapterStatus status,
WGPUAdapter adapter,
char const* message,
void* userdata) {
if (status == WGPURequestAdapterStatus_Unavailable) {
printf("WebGPU unavailable; exiting cleanly\n");
exit(0);
}

assert(status == WGPURequestAdapterStatus_Success);

wgpuAdapterGetLimits(adapter, &adapter_supported_limits);

// for device limits, require the limits supported by adapter
WGPURequiredLimits device_required_limits = {0,};
device_required_limits.limits = adapter_supported_limits.limits;

WGPUDeviceDescriptor device_desc = {0,};
device_desc.requiredFeatureCount = 0;
device_desc.requiredLimits = &device_required_limits;
wgpuAdapterRequestDevice(adapter, &device_desc, on_device_request_ended, NULL);
}

int main() {
const WGPUInstance instance = wgpuCreateInstance(NULL);

WGPURequestAdapterOptions adapter_options = {0,};
wgpuInstanceRequestAdapter(instance, &adapter_options, on_adapter_request_ended, NULL);

// This code is returned when the runtime exits unless something else sets
// it, like exit(0).
return 99;
}

0 comments on commit 849342f

Please sign in to comment.