Add validation, storage, and querying of limits

Adds a way to store the limits on the Adapter and the
Device. For now, adapter limits are always the default
limits, and device limits are stored but not used.

This CL also adds usage of an ErrorObjectIdResolver and
Provider in the WGPUDeviceProperties serialization and
deserialization helpers. Serializing/deserializing this
struct should never have objects.

Bug: dawn:685
Change-Id: I1479b4407b0f9ec9f9b2bff62cad7caa693c99d7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/63983
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Austin Eng 2021-09-13 18:49:09 +00:00 committed by Dawn LUCI CQ
parent caf6e8b3f0
commit 02fbf168e0
17 changed files with 375 additions and 4 deletions

View File

@ -883,7 +883,8 @@
{"name": "multi planar formats", "type": "bool", "default": "false"},
{"name": "depth clamping", "type": "bool", "default": "false"},
{"name": "invalid extension", "type": "bool", "default": "false"},
{"name": "dawn internal usages", "type": "bool", "default": "false"}
{"name": "dawn internal usages", "type": "bool", "default": "false"},
{"name": "limits", "type": "limits"}
]
},
"double": {
@ -897,6 +898,38 @@
{"name": "userdata", "type": "void", "annotation": "*"}
]
},
"limits": {
"category": "structure",
"extensible": true,
"members": [
{"name": "max texture dimension 1D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max texture dimension 2D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max texture dimension 3D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max texture array layers", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max bind groups", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max dynamic uniform buffers per pipeline layout", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max dynamic storage buffers per pipeline layout", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max sampled textures per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max samplers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max storage buffers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max storage textures per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max uniform buffers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max uniform buffer binding size", "type": "uint64_t", "default": "WGPU_LIMIT_U64_UNDEFINED"},
{"name": "max storage buffer binding size", "type": "uint64_t", "default": "WGPU_LIMIT_U64_UNDEFINED"},
{"name": "min uniform buffer offset alignment", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "min storage buffer offset alignment", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max vertex buffers", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max vertex attributes", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max vertex buffer array stride", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max inter stage shader components", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute workgroup storage size", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute invocations per workgroup", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute workgroup size x", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute workgroup size y", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute workgroup size z", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"},
{"name": "max compute workgroups per dimension", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}
]
},
"logging callback": {
"category": "callback",
"args": [

View File

@ -711,7 +711,8 @@ namespace dawn_wire {
WireResult result = serializeBuffer.Next(&transfer);
ASSERT(result == WireResult::Success);
result = WGPUDevicePropertiesSerialize(*deviceProperties, transfer, &serializeBuffer);
ErrorObjectIdProvider provider;
result = WGPUDevicePropertiesSerialize(*deviceProperties, transfer, &serializeBuffer, provider);
ASSERT(result == WireResult::Success);
}
@ -724,8 +725,9 @@ namespace dawn_wire {
return false;
}
ErrorObjectIdResolver resolver;
return WGPUDevicePropertiesDeserialize(deviceProperties, transfer, &deserializeBuffer,
nullptr) == WireResult::Success;
nullptr, resolver) == WireResult::Success;
}
} // namespace dawn_wire

View File

@ -77,6 +77,8 @@
// TODO(crbug.com/dawn/520): Remove WGPU_STRIDE_UNDEFINED in favor of WGPU_COPY_STRIDE_UNDEFINED.
#define WGPU_STRIDE_UNDEFINED (0xffffffffUL)
#define WGPU_COPY_STRIDE_UNDEFINED (0xffffffffUL)
#define WGPU_LIMIT_U32_UNDEFINED (0xffffffffUL)
#define WGPU_LIMIT_U64_UNDEFINED (0xffffffffffffffffULL)
typedef uint32_t WGPUFlags;

View File

@ -23,6 +23,8 @@ namespace wgpu {
// TODO(crbug.com/520): Remove kStrideUndefined in favor of kCopyStrideUndefined.
static constexpr uint32_t kStrideUndefined = WGPU_STRIDE_UNDEFINED;
static constexpr uint32_t kCopyStrideUndefined = WGPU_COPY_STRIDE_UNDEFINED;
static constexpr uint32_t kLimitU32Undefined = WGPU_LIMIT_U32_UNDEFINED;
static constexpr uint64_t kLimitU64Undefined = WGPU_LIMIT_U64_UNDEFINED;
{% for type in by_category["enum"] %}
enum class {{as_cppType(type.name)}} : uint32_t {

View File

@ -65,7 +65,7 @@ static constexpr float kLodMax = 1000.0;
static constexpr uint32_t kMaxTextureDimension1D = 8192u;
static constexpr uint32_t kMaxTextureDimension2D = 8192u;
static constexpr uint32_t kMaxTextureDimension3D = 2048u;
static constexpr uint32_t kMaxTextureArrayLayers = 2048u;
static constexpr uint32_t kMaxTextureArrayLayers = 256u;
static constexpr uint32_t kMaxTexture2DMipLevels = 14u;
static_assert(1 << (kMaxTexture2DMipLevels - 1) == kMaxTextureDimension2D,
"kMaxTexture2DMipLevels and kMaxTextureDimension2D size mismatch");

View File

@ -20,6 +20,8 @@ namespace dawn_native {
AdapterBase::AdapterBase(InstanceBase* instance, wgpu::BackendType backend)
: mInstance(instance), mBackend(backend) {
mLimits.v1.nextInChain = nullptr;
GetDefaultLimits(&mLimits.v1);
mSupportedExtensions.EnableExtension(Extension::DawnInternalUsages);
}
@ -65,9 +67,24 @@ namespace dawn_native {
WGPUDeviceProperties adapterProperties = {};
mSupportedExtensions.InitializeDeviceProperties(&adapterProperties);
// This is OK for now because there are no limit extension structs.
// If we add additional structs, the caller will need to provide memory
// to store them (ex. by calling GetLimits directly instead). Currently,
// we keep this function as it's only used internally in Chromium to
// send the adapter properties across the wire.
GetLimits(reinterpret_cast<wgpu::Limits*>(&adapterProperties.limits));
return adapterProperties;
}
bool AdapterBase::GetLimits(wgpu::Limits* limits) const {
ASSERT(limits != nullptr);
if (limits->nextInChain != nullptr) {
return false;
}
*limits = mLimits.v1;
return true;
}
DeviceBase* AdapterBase::CreateDevice(const DeviceDescriptor* descriptor) {
DeviceBase* result = nullptr;
@ -104,6 +121,15 @@ namespace dawn_native {
}
}
if (descriptor != nullptr && descriptor->requiredLimits != nullptr) {
DAWN_TRY(ValidateLimits(
mLimits.v1, *reinterpret_cast<const wgpu::Limits*>(descriptor->requiredLimits)));
if (descriptor->requiredLimits->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("Unsupported limit extension struct");
}
}
DAWN_TRY_ASSIGN(*result, CreateDeviceImpl(descriptor));
return {};
}

View File

@ -19,6 +19,7 @@
#include "dawn_native/Error.h"
#include "dawn_native/Extensions.h"
#include "dawn_native/Limits.h"
#include "dawn_native/dawn_platform.h"
#include <string>
@ -51,6 +52,8 @@ namespace dawn_native {
const std::vector<const char*>& requestedExtensions) const;
WGPUDeviceProperties GetAdapterProperties() const;
bool GetLimits(wgpu::Limits* limits) const;
virtual bool SupportsExternalImages() const = 0;
protected:
@ -67,6 +70,7 @@ namespace dawn_native {
virtual MaybeError ResetInternalDeviceForTestingImpl();
InstanceBase* mInstance = nullptr;
wgpu::BackendType mBackend;
CombinedLimits mLimits;
};
} // namespace dawn_native

View File

@ -239,6 +239,8 @@ source_set("dawn_native_sources") {
"Instance.h",
"IntegerTypes.h",
"InternalPipelineStore.h",
"Limits.cpp",
"Limits.h",
"ObjectBase.cpp",
"ObjectBase.h",
"ObjectContentHasher.cpp",

View File

@ -105,6 +105,8 @@ target_sources(dawn_native PRIVATE
"Instance.h"
"InternalPipelineStore.h"
"IntegerTypes.h"
"Limits.cpp"
"Limits.h"
"ObjectBase.cpp"
"ObjectBase.h"
"PassResourceUsage.h"

View File

@ -106,6 +106,10 @@ namespace dawn_native {
return mImpl->GetAdapterProperties();
}
bool Adapter::GetLimits(WGPULimits* limits) const {
return mImpl->GetLimits(reinterpret_cast<wgpu::Limits*>(limits));
}
bool Adapter::SupportsExternalImages() const {
return mImpl->SupportsExternalImages();
}

View File

@ -178,6 +178,13 @@ namespace dawn_native {
ApplyExtensions(descriptor);
}
if (descriptor != nullptr && descriptor->requiredLimits != nullptr) {
mLimits.v1 = ReifyDefaultLimits(
*reinterpret_cast<const wgpu::Limits*>(descriptor->requiredLimits));
} else {
GetDefaultLimits(&mLimits.v1);
}
mFormatTable = BuildFormatTable(this);
SetDefaultToggles();
if ((adapter->GetBackendType() == wgpu::BackendType::Metal ||

View File

@ -20,6 +20,7 @@
#include "dawn_native/Extensions.h"
#include "dawn_native/Format.h"
#include "dawn_native/Forward.h"
#include "dawn_native/Limits.h"
#include "dawn_native/ObjectBase.h"
#include "dawn_native/Toggles.h"
@ -447,6 +448,7 @@ namespace dawn_native {
size_t mLazyClearCountForTesting = 0;
std::atomic_uint64_t mNextPipelineCompatibilityToken;
CombinedLimits mLimits;
ExtensionsSet mEnabledExtensions;
std::unique_ptr<InternalPipelineStore> mInternalPipelineStore;

129
src/dawn_native/Limits.cpp Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_native/Limits.h"
#include "common/Assert.h"
#define LIMITS(X) \
X(Higher, maxTextureDimension1D, 8192) \
X(Higher, maxTextureDimension2D, 8192) \
X(Higher, maxTextureDimension3D, 2048) \
X(Higher, maxTextureArrayLayers, 256) \
X(Higher, maxBindGroups, 4) \
X(Higher, maxDynamicUniformBuffersPerPipelineLayout, 8) \
X(Higher, maxDynamicStorageBuffersPerPipelineLayout, 4) \
X(Higher, maxSampledTexturesPerShaderStage, 16) \
X(Higher, maxSamplersPerShaderStage, 16) \
X(Higher, maxStorageBuffersPerShaderStage, 8) \
X(Higher, maxStorageTexturesPerShaderStage, 4) \
X(Higher, maxUniformBuffersPerShaderStage, 12) \
X(Higher, maxUniformBufferBindingSize, 16384) \
X(Higher, maxStorageBufferBindingSize, 134217728) \
X(Lower, minUniformBufferOffsetAlignment, 256) \
X(Lower, minStorageBufferOffsetAlignment, 256) \
X(Higher, maxVertexBuffers, 8) \
X(Higher, maxVertexAttributes, 16) \
X(Higher, maxVertexBufferArrayStride, 2048) \
X(Higher, maxInterStageShaderComponents, 60) \
X(Higher, maxComputeWorkgroupStorageSize, 16352) \
X(Higher, maxComputeInvocationsPerWorkgroup, 256) \
X(Higher, maxComputeWorkgroupSizeX, 256) \
X(Higher, maxComputeWorkgroupSizeY, 256) \
X(Higher, maxComputeWorkgroupSizeZ, 64) \
X(Higher, maxComputeWorkgroupsPerDimension, 65535)
namespace dawn_native {
namespace {
enum class LimitBetterDirection {
Lower,
Higher,
};
template <LimitBetterDirection Better>
struct CheckLimit;
template <>
struct CheckLimit<LimitBetterDirection::Lower> {
template <typename T>
static MaybeError Invoke(T supported, T required) {
if (required < supported) {
return DAWN_VALIDATION_ERROR("requiredLimit lower than supported limit");
}
return {};
}
};
template <>
struct CheckLimit<LimitBetterDirection::Higher> {
template <typename T>
static MaybeError Invoke(T supported, T required) {
if (required > supported) {
return DAWN_VALIDATION_ERROR("requiredLimit greater than supported limit");
}
return {};
}
};
template <typename T>
bool IsLimitUndefined(T value) {
static_assert(sizeof(T) != sizeof(T), "IsLimitUndefined not implemented for this type");
return false;
}
template <>
bool IsLimitUndefined<uint32_t>(uint32_t value) {
return value == wgpu::kLimitU32Undefined;
}
template <>
bool IsLimitUndefined<uint64_t>(uint64_t value) {
return value == wgpu::kLimitU64Undefined;
}
} // namespace
void GetDefaultLimits(wgpu::Limits* limits) {
ASSERT(limits != nullptr);
#define X(Better, limitName, defaultValue) limits->limitName = defaultValue;
LIMITS(X)
#undef X
}
wgpu::Limits ReifyDefaultLimits(const wgpu::Limits& limits) {
wgpu::Limits out;
#define X(Better, limitName, defaultValue) \
if (!IsLimitUndefined(limits.limitName)) { \
out.limitName = limits.limitName; \
} else { \
out.limitName = defaultValue; \
}
LIMITS(X)
#undef X
return out;
}
MaybeError ValidateLimits(const wgpu::Limits& supportedLimits,
const wgpu::Limits& requiredLimits) {
#define X(Better, limitName, defaultValue) \
if (!IsLimitUndefined(requiredLimits.limitName)) { \
DAWN_TRY(CheckLimit<LimitBetterDirection::Better>::Invoke(supportedLimits.limitName, \
requiredLimits.limitName)); \
}
LIMITS(X)
#undef X
return {};
}
} // namespace dawn_native

40
src/dawn_native/Limits.h Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef DAWNNATIVE_LIMITS_H_
#define DAWNNATIVE_LIMITS_H_
#include "dawn_native/Error.h"
#include "dawn_native/dawn_platform.h"
namespace dawn_native {
struct CombinedLimits {
wgpu::Limits v1;
};
// Populate |limits| with the default limits.
void GetDefaultLimits(wgpu::Limits* limits);
// Returns a copy of |limits| where all undefined values are replaced
// with their defaults.
wgpu::Limits ReifyDefaultLimits(const wgpu::Limits& limits);
// Validate that |requiredLimits| are no better than |supportedLimits|.
MaybeError ValidateLimits(const wgpu::Limits& supportedLimits,
const wgpu::Limits& requiredLimits);
} // namespace dawn_native
#endif // DAWNNATIVE_LIMITS_H_

View File

@ -66,6 +66,8 @@ namespace dawn_native {
std::vector<const char*> requiredExtensions;
std::vector<const char*> forceEnabledToggles;
std::vector<const char*> forceDisabledToggles;
const WGPULimits* requiredLimits = nullptr;
};
// A struct to record the information of a toggle. A toggle is a code path in Dawn device that
@ -108,6 +110,7 @@ namespace dawn_native {
std::vector<const char*> GetSupportedExtensions() const;
WGPUDeviceProperties GetAdapterProperties() const;
bool GetLimits(WGPULimits* limits) const;
// Check that the Adapter is able to support importing external images. This is necessary
// to implement the swapchain and interop APIs in Chromium.

View File

@ -214,6 +214,7 @@ test("dawn_unittests") {
"unittests/validation/RenderBundleValidationTests.cpp",
"unittests/validation/RenderPassDescriptorValidationTests.cpp",
"unittests/validation/RenderPipelineValidationTests.cpp",
"unittests/validation/RequestDeviceValidationTests.cpp",
"unittests/validation/ResourceUsageTrackingTests.cpp",
"unittests/validation/SamplerValidationTests.cpp",
"unittests/validation/ShaderModuleValidationTests.cpp",

View File

@ -0,0 +1,112 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tests/unittests/validation/ValidationTest.h"
class RequestDeviceValidationTest : public ValidationTest {
protected:
void SetUp() {
DAWN_SKIP_TEST_IF(UsesWire());
ValidationTest::SetUp();
}
static void ExpectRequestDeviceSuccess(WGPURequestDeviceStatus status,
WGPUDevice cDevice,
const char* message,
void* userdata) {
wgpu::Device device = wgpu::Device::Acquire(cDevice);
EXPECT_EQ(status, WGPURequestDeviceStatus_Success);
EXPECT_NE(device, nullptr);
EXPECT_STREQ(message, nullptr);
}
static void ExpectRequestDeviceError(WGPURequestDeviceStatus status,
WGPUDevice cDevice,
const char* message,
void* userdata) {
wgpu::Device device = wgpu::Device::Acquire(cDevice);
EXPECT_EQ(status, WGPURequestDeviceStatus_Error);
EXPECT_EQ(device, nullptr);
EXPECT_STRNE(message, nullptr);
}
};
// Test that requesting a device without specifying limits is valid.
TEST_F(RequestDeviceValidationTest, NoRequiredLimits) {
dawn_native::DeviceDescriptor descriptor;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
}
// Test that requesting a device with the default limits is valid.
TEST_F(RequestDeviceValidationTest, DefaultLimits) {
wgpu::Limits limits = {};
dawn_native::DeviceDescriptor descriptor;
descriptor.requiredLimits = reinterpret_cast<const WGPULimits*>(&limits);
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
}
// Test that requesting a device where a required limit is above the maximum value.
TEST_F(RequestDeviceValidationTest, HigherIsBetter) {
wgpu::Limits limits = {};
dawn_native::DeviceDescriptor descriptor;
descriptor.requiredLimits = reinterpret_cast<const WGPULimits*>(&limits);
wgpu::Limits supportedLimits;
EXPECT_TRUE(adapter.GetLimits(reinterpret_cast<WGPULimits*>(&supportedLimits)));
// Test below the max.
limits.maxBindGroups = supportedLimits.maxBindGroups - 1;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
// Test the max.
limits.maxBindGroups = supportedLimits.maxBindGroups;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
// Test above the max.
limits.maxBindGroups = supportedLimits.maxBindGroups + 1;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceError, nullptr);
}
// Test that requesting a device where a required limit is below the minimum value.
TEST_F(RequestDeviceValidationTest, LowerIsBetter) {
wgpu::Limits limits = {};
dawn_native::DeviceDescriptor descriptor;
descriptor.requiredLimits = reinterpret_cast<const WGPULimits*>(&limits);
wgpu::Limits supportedLimits;
EXPECT_TRUE(adapter.GetLimits(reinterpret_cast<WGPULimits*>(&supportedLimits)));
// Test below the min.
limits.minUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment / 2;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceError, nullptr);
// Test the min.
limits.minUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
// Test above the min.
limits.minUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment * 2;
adapter.RequestDevice(&descriptor, ExpectRequestDeviceSuccess, nullptr);
}
// Test that it is an error to request limits with an invalid chained struct
TEST_F(RequestDeviceValidationTest, InvalidChainedStruct) {
wgpu::PrimitiveDepthClampingState depthClamp = {};
wgpu::Limits limits = {};
limits.nextInChain = &depthClamp;
dawn_native::DeviceDescriptor descriptor;
descriptor.requiredLimits = reinterpret_cast<const WGPULimits*>(&limits);
adapter.RequestDevice(&descriptor, ExpectRequestDeviceError, nullptr);
}