Improve validation errors, Part 1
Begin's using Abseil's string formatting and new error context tracking to dramatically improve the usefulness of validation messages. In addition to putting in place several utilities to enable better messages this change also updates the BindGroup buffers bindings validation messages as a test for the new mechanisms. Bug: dawn:563 Change-Id: Ie5bf5ffb24a9013cebd67745dc4172dfbc901e9a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64201 Commit-Queue: Brandon Jones <bajones@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
a04663c13d
commit
ba66295033
|
@ -35,6 +35,7 @@ Developer documentation:
|
|||
- [Testing Dawn](docs/testing.md)
|
||||
- [Debugging Dawn](docs/debugging.md)
|
||||
- [Dawn's infrastructure](docs/infra.md)
|
||||
- [Dawn errors](docs/errors.md)
|
||||
|
||||
User documentation: (TODO, figure out what overlaps with the webgpu.h docs)
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
# Dawn Errors
|
||||
|
||||
Dawn produces errors for several reasons. The most common is validation errors, indicatin that a
|
||||
given descriptor, configuration, state, or action is not valid according to the WebGPU spec. Errors
|
||||
can also be produced during exceptional circumstances such as the system running out of GPU memory
|
||||
or the device being lost.
|
||||
|
||||
The messages attached to these errors will frequently be one of the primary tools developers use to
|
||||
debug problems their applications, so it is important that the messages Dawn returns are useful.
|
||||
|
||||
Following the guidelines in document will help ensure that Dawn's errors are clear, informative, and
|
||||
consistent.
|
||||
|
||||
## Returning Errors
|
||||
|
||||
Since errors are expected to be an exceptional case, it's important that code that produces an error
|
||||
doesn't adversely impact the performance of the error-free path. The best way to ensure that is to
|
||||
make sure that all errors are returned from within an `if` statement that uses the `DAWN_UNLIKELY()`
|
||||
macro to indicate that the expression is not expected to evaluate to true. For example:
|
||||
|
||||
```C++
|
||||
if (DAWN_UNLIKELY(offset > buffer.size)) {
|
||||
return DAWN_VALIDATION_ERROR("Offset (%u) is larger than the size (%u) of %s."
|
||||
offset, buffer.size, buffer);
|
||||
}
|
||||
```
|
||||
|
||||
To simplify producing validation errors, it's strongly suggested that the `DAWN_INVALID_IF()` macro
|
||||
is used, which will wrap the expression in the `DAWN_UNLIKELY()` macro for you:
|
||||
|
||||
```C++
|
||||
// This is equivalent to the previous example.
|
||||
DAWN_INVALID_IF(offset > buffer.size, "Offset (%u) is larger than the size (%u) of %s."
|
||||
offset, buffer.size, buffer);
|
||||
```
|
||||
|
||||
// TODO: Cover `MaybeError`, `ResultOrError<T>`, `DAWN_TRY(_ASSIGN)`, `DAWN_TRY_CONTEXT`, etc...
|
||||
|
||||
## Error message formatting
|
||||
|
||||
Errors returned from `DAWN_INVALID_IF()` or `DAWN_VALIDATION_ERROR()` should follow these guidelines:
|
||||
|
||||
**Write error messages as complete sentences. (First word capitalized, ends with a period, etc.)**
|
||||
* Example: `Command encoding has already finished.`
|
||||
* Instead of: `encoder finished`
|
||||
|
||||
**When possible any values mentioned should be immediately followed in parentheses by the given value.**
|
||||
* Example: `("Array stride (%u) is not...", stride)`
|
||||
* Output: `Array stride (16) is not...`
|
||||
|
||||
**When possible any object or descriptors should be represented by the object formatted as a string.**
|
||||
* Example: `("The %s size (%s) is...", buffer, buffer.size)`
|
||||
* Output: `The [Buffer] size (512) is...` or `The [Buffer "Label"] size (512) is...`
|
||||
|
||||
**Enum and bitmask values should be formatted as strings rather than integers or hex values.**
|
||||
* Example: `("The %s format (%s) is...", texture, texture.format)`
|
||||
* Output: `The [Texture "Label"] format (TextureFormat::RGBA8Unorm) is...`
|
||||
|
||||
**When possible state both the given value and the expected value or limit.**
|
||||
* Example: `("Offset (%u) is larger than the size (%u) of %s.", offset, buffer.size, buffer)`
|
||||
* Output: `Offset (256) is larger than the size (144) of [Buffer "Label"].`
|
||||
|
||||
**State errors in terms of what failed, rather than how to satisfy the rule.**
|
||||
* Example: `Binding size (3) is less than the minimum binding size (32).`
|
||||
* Instead of: `Binding size (3) must not be less than the minimum binding size (32).`
|
||||
|
||||
**Don't repeat information given in context.**
|
||||
* See next section for details
|
||||
|
||||
## Error Context
|
||||
|
||||
When calling functions that perform validation consider if calling `DAWN_TRY_CONTEXT()` rather than
|
||||
`DAWN_TRY()` is appropriate. Context messages, when provided, will be appended to any validation
|
||||
errors as a type of human readable "callstack". An error with context messages appears will be
|
||||
formatted as:
|
||||
|
||||
```
|
||||
<Primary error message.>
|
||||
- While <context message lvl 2>
|
||||
- While <context message lvl 1>
|
||||
- While <context message lvl 0>
|
||||
```
|
||||
|
||||
For example, if a validation error occurs while validating the creation of a BindGroup, the message
|
||||
may be:
|
||||
|
||||
```
|
||||
Binding size (256) is larger than the size (80) of [Buffer "View Matrix"].
|
||||
- While validating entries[1] as a Buffer
|
||||
- While validating [BindGroupDescriptor "Frame Bind Group"] against [BindGroupLayout]
|
||||
- While calling CreateBindGroup
|
||||
```
|
||||
|
||||
// TODO: Guidelines about when to include context
|
||||
|
||||
## Context message formatting
|
||||
|
||||
Context messages should follow these guidelines:
|
||||
|
||||
**Begin with the action being taken, starting with a lower case. `- While ` will be appended by Dawn.**
|
||||
* Example: `("validating primitive state")`
|
||||
* Output: `- While validating primitive state`
|
||||
|
||||
**When looping through arrays, indicate the array name and index.**
|
||||
* Example: `("validating buffers[%u]", i)`
|
||||
* Output: `- While validating buffers[2]`
|
||||
|
||||
**Indicate which descriptors or objects are being examined in as high-level a context as possible.**
|
||||
* Example: `("validating % against %", descriptor, descriptor->layout)`
|
||||
* Output: `- While validating [BindGroupDescriptor "Label"] against [BindGroupLayout]`
|
||||
|
||||
**When possible, indicate the function call being made as the top-level context.**
|
||||
* Example: `("calling CreatePipelineLayout")`
|
||||
* Output: `- While calling CreatePipelineLayout`
|
|
@ -837,6 +837,14 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
FileRender('dawn_native/ChainUtils.cpp',
|
||||
'src/dawn_native/ChainUtils_autogen.cpp',
|
||||
frontend_params))
|
||||
renders.append(
|
||||
FileRender('dawn_native/webgpu_absl_format.h',
|
||||
'src/dawn_native/webgpu_absl_format_autogen.h',
|
||||
frontend_params))
|
||||
renders.append(
|
||||
FileRender('dawn_native/webgpu_absl_format.cpp',
|
||||
'src/dawn_native/webgpu_absl_format_autogen.cpp',
|
||||
frontend_params))
|
||||
|
||||
if 'dawn_wire' in targets:
|
||||
additional_params = compute_wire_params(params_dawn, wire_json)
|
||||
|
|
|
@ -26,15 +26,6 @@
|
|||
|
||||
namespace dawn_native {
|
||||
|
||||
// Type aliases to make all frontend types appear as if they have "Base" at the end when some
|
||||
// of them are actually pure-frontend and don't have the Base.
|
||||
using CommandEncoderBase = CommandEncoder;
|
||||
using ComputePassEncoderBase = ComputePassEncoder;
|
||||
using FenceBase = Fence;
|
||||
using RenderPassEncoderBase = RenderPassEncoder;
|
||||
using RenderBundleEncoderBase = RenderBundleEncoder;
|
||||
using SurfaceBase = Surface;
|
||||
|
||||
namespace {
|
||||
|
||||
{% for type in by_category["object"] %}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
//* 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/webgpu_absl_format_autogen.h"
|
||||
|
||||
{% set skip_types = ["texture view", "instance", "surface"] %}
|
||||
|
||||
{% for type in by_category["object"] %}
|
||||
{% if type.name.canonical_case() not in skip_types %}
|
||||
#include "dawn_native/{{type.name.CamelCase()}}.h"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
namespace dawn_native {
|
||||
|
||||
//
|
||||
// Objects
|
||||
//
|
||||
|
||||
// TODO(dawn:563) Detect the type of ObjectBase references and use the right formatter.
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const ObjectBase* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("[Object");
|
||||
const std::string& label = value->GetLabel();
|
||||
if (!label.empty()) {
|
||||
s->Append(absl::StrFormat(" \"%s\"", label));
|
||||
}
|
||||
s->Append("]");
|
||||
return {true};
|
||||
}
|
||||
|
||||
{% for type in by_category["object"] %}
|
||||
{% if type.name.canonical_case() not in skip_types %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const {{as_frontendType(type)}} value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("[{{as_cppType(type.name)}}");
|
||||
const std::string& label = value->GetLabel();
|
||||
if (!label.empty()) {
|
||||
s->Append(absl::StrFormat(" \"%s\"", label));
|
||||
}
|
||||
s->Append("]");
|
||||
return {true};
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// Special case for textureViews, since frequently the texture will be the
|
||||
// thing that's labeled.
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const TextureViewBase* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("[TextureView");
|
||||
const std::string& label = value->GetLabel();
|
||||
if (!label.empty()) {
|
||||
s->Append(absl::StrFormat(" \"%s\"", label));
|
||||
}
|
||||
const std::string& textureLabel = value->GetTexture()->GetLabel();
|
||||
if (!textureLabel.empty()) {
|
||||
s->Append(absl::StrFormat(" of Texture \"%s\"", textureLabel));
|
||||
}
|
||||
s->Append("]");
|
||||
return {true};
|
||||
}
|
||||
|
||||
//
|
||||
// Descriptors
|
||||
//
|
||||
|
||||
{% for type in by_category["structure"] %}
|
||||
{% for member in type.members %}
|
||||
{% if member.name.canonical_case() == "label" %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const {{as_cppType(type.name)}}* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("[{{as_cppType(type.name)}}");
|
||||
if (value->label != nullptr) {
|
||||
s->Append(absl::StrFormat(" \"%s\"", value->label));
|
||||
}
|
||||
s->Append("]");
|
||||
return {true};
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
namespace wgpu {
|
||||
|
||||
//
|
||||
// Enums
|
||||
//
|
||||
|
||||
{% for type in by_category["enum"] %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert({{as_cppType(type.name)}} value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("{{as_cppType(type.name)}}::");
|
||||
switch (value) {
|
||||
{% for value in type.values %}
|
||||
case {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}:
|
||||
s->Append("{{as_cppEnum(value.name)}}");
|
||||
break;
|
||||
{% endfor %}
|
||||
default:
|
||||
s->Append(absl::StrFormat("%x", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value)));
|
||||
}
|
||||
return {true};
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
//
|
||||
// Bitmasks
|
||||
//
|
||||
|
||||
{% for type in by_category["bitmask"] %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert({{as_cppType(type.name)}} value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
s->Append("{{as_cppType(type.name)}}::");
|
||||
if (!static_cast<bool>(value)) {
|
||||
{% for value in type.values if value.value == 0 %}
|
||||
// 0 is often explicitly declared as None.
|
||||
s->Append("{{as_cppEnum(value.name)}}");
|
||||
{% else %}
|
||||
s->Append(absl::StrFormat("{{as_cppType(type.name)}}::%x", 0));
|
||||
{% endfor %}
|
||||
return {true};
|
||||
}
|
||||
|
||||
bool moreThanOneBit = !HasZeroOrOneBits(value);
|
||||
if (moreThanOneBit) {
|
||||
s->Append("(");
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
{% for value in type.values if value.value != 0 %}
|
||||
if (value & {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}) {
|
||||
if (!first) {
|
||||
s->Append("|");
|
||||
}
|
||||
first = false;
|
||||
s->Append("{{as_cppEnum(value.name)}}");
|
||||
value &= ~{{as_cppType(type.name)}}::{{as_cppEnum(value.name)}};
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
if (static_cast<bool>(value)) {
|
||||
if (!first) {
|
||||
s->Append("|");
|
||||
}
|
||||
s->Append(absl::StrFormat("{{as_cppType(type.name)}}::%x", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value)));
|
||||
}
|
||||
|
||||
if (moreThanOneBit) {
|
||||
s->Append(")");
|
||||
}
|
||||
|
||||
return {true};
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
} // namespace wgpu
|
|
@ -0,0 +1,91 @@
|
|||
//* 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 WEBGPU_ABSL_FORMAT_H_
|
||||
#define WEBGPU_ABSL_FORMAT_H_
|
||||
|
||||
#include "dawn_native/dawn_platform.h"
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
namespace dawn_native {
|
||||
|
||||
{% set skip_types = ["instance", "surface"] %}
|
||||
{% set pure_frontend_types = ["command encoder", "compute pass encoder", "render pass encoder", "render bundle encoder"] %}
|
||||
|
||||
//
|
||||
// Objects
|
||||
//
|
||||
|
||||
class ObjectBase;
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const ObjectBase* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s);
|
||||
|
||||
{% for type in by_category["object"] %}
|
||||
{% set Base = "" if type.name.canonical_case() in pure_frontend_types else "Base" %}
|
||||
{% if type.name.canonical_case() not in skip_types %}
|
||||
class {{type.name.CamelCase()}}{{Base}};
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const {{type.name.CamelCase()}}{{Base}}* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s);
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
//
|
||||
// Descriptors
|
||||
//
|
||||
|
||||
// Only includes structures that have a 'label' member.
|
||||
{% for type in by_category["structure"] %}
|
||||
{% for member in type.members %}
|
||||
{% if member.name.canonical_case() == "label" %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert(const {{as_cppType(type.name)}}* value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s);
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
namespace wgpu {
|
||||
|
||||
//
|
||||
// Enums
|
||||
//
|
||||
|
||||
{% for type in by_category["enum"] %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert({{as_cppType(type.name)}} value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s);
|
||||
{% endfor %}
|
||||
|
||||
//
|
||||
// Bitmasks
|
||||
//
|
||||
|
||||
{% for type in by_category["bitmask"] %}
|
||||
absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
|
||||
AbslFormatConvert({{as_cppType(type.name)}} value,
|
||||
const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s);
|
||||
{% endfor %}
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // WEBGPU_ABSL_FORMAT_H_
|
|
@ -109,6 +109,8 @@ dawn_json_generator("dawn_native_utils_gen") {
|
|||
"src/dawn_native/wgpu_structs_autogen.cpp",
|
||||
"src/dawn_native/ValidationUtils_autogen.h",
|
||||
"src/dawn_native/ValidationUtils_autogen.cpp",
|
||||
"src/dawn_native/webgpu_absl_format_autogen.h",
|
||||
"src/dawn_native/webgpu_absl_format_autogen.cpp",
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -153,7 +155,6 @@ source_set("dawn_native_headers") {
|
|||
# except those that define exported symbols.
|
||||
source_set("dawn_native_sources") {
|
||||
deps = [
|
||||
":dawn_abseil",
|
||||
":dawn_native_headers",
|
||||
":dawn_native_utils_gen",
|
||||
"${dawn_root}/src/common",
|
||||
|
@ -173,7 +174,10 @@ source_set("dawn_native_sources") {
|
|||
# Dependencies that are needed to compile dawn_native entry points in
|
||||
# FooBackend.cpp need to be public deps so they are propagated to the
|
||||
# dawn_native target
|
||||
public_deps = [ "${dawn_root}/src/dawn_platform" ]
|
||||
public_deps = [
|
||||
":dawn_abseil",
|
||||
"${dawn_root}/src/dawn_platform",
|
||||
]
|
||||
|
||||
sources = get_target_outputs(":dawn_native_utils_gen")
|
||||
sources += [
|
||||
|
|
|
@ -69,50 +69,41 @@ namespace dawn_native {
|
|||
uint64_t bufferSize = entry.buffer->GetSize();
|
||||
|
||||
// Handle wgpu::WholeSize, avoiding overflows.
|
||||
if (entry.offset > bufferSize) {
|
||||
return DAWN_VALIDATION_ERROR("Buffer binding doesn't fit in the buffer");
|
||||
}
|
||||
DAWN_INVALID_IF(entry.offset > bufferSize,
|
||||
"Binding offset (%u) is larger than the size (%u) of %s.", entry.offset,
|
||||
bufferSize, entry.buffer);
|
||||
|
||||
uint64_t bindingSize =
|
||||
(entry.size == wgpu::kWholeSize) ? bufferSize - entry.offset : entry.size;
|
||||
|
||||
if (bindingSize > bufferSize) {
|
||||
return DAWN_VALIDATION_ERROR("Buffer binding size larger than the buffer");
|
||||
}
|
||||
DAWN_INVALID_IF(bindingSize > bufferSize,
|
||||
"Binding size (%u) is larger than the size (%u) of %s.", bindingSize,
|
||||
bufferSize, entry.buffer);
|
||||
|
||||
if (bindingSize == 0) {
|
||||
return DAWN_VALIDATION_ERROR("Buffer binding size cannot be zero.");
|
||||
}
|
||||
DAWN_INVALID_IF(bindingSize == 0, "Binding size is zero");
|
||||
|
||||
// Note that no overflow can happen because we already checked that
|
||||
// bufferSize >= bindingSize
|
||||
if (entry.offset > bufferSize - bindingSize) {
|
||||
return DAWN_VALIDATION_ERROR("Buffer binding doesn't fit in the buffer");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
entry.offset > bufferSize - bindingSize,
|
||||
"Binding range (offset: %u, size: %u) doesn't fit in the size (%u) of %s.",
|
||||
entry.offset, bufferSize, bindingSize, entry.buffer);
|
||||
|
||||
if (!IsAligned(entry.offset, requiredBindingAlignment)) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Buffer offset for bind group needs to satisfy the minimum alignment");
|
||||
}
|
||||
DAWN_INVALID_IF(!IsAligned(entry.offset, requiredBindingAlignment),
|
||||
"Offset (%u) does not satisfy the minimum %s alignment (%u).",
|
||||
entry.offset, bindingInfo.buffer.type, requiredBindingAlignment);
|
||||
|
||||
if (!(entry.buffer->GetUsage() & requiredUsage)) {
|
||||
return DAWN_VALIDATION_ERROR("buffer binding usage mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(!(entry.buffer->GetUsage() & requiredUsage),
|
||||
"Binding usage (%s) of %s doesn't match expected usage (%s).",
|
||||
entry.buffer->GetUsage(), entry.buffer, requiredUsage);
|
||||
|
||||
if (bindingSize < bindingInfo.buffer.minBindingSize) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Binding size smaller than minimum buffer size: binding " +
|
||||
std::to_string(entry.binding) + " given " + std::to_string(bindingSize) +
|
||||
" bytes, required " + std::to_string(bindingInfo.buffer.minBindingSize) +
|
||||
" bytes");
|
||||
}
|
||||
DAWN_INVALID_IF(bindingSize < bindingInfo.buffer.minBindingSize,
|
||||
"Binding size (%u) is smaller than the minimum binding size (%u).",
|
||||
bindingSize, bindingInfo.buffer.minBindingSize);
|
||||
|
||||
if (bindingSize > maxBindingSize) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Binding size bigger than maximum uniform buffer binding size: binding " +
|
||||
std::to_string(entry.binding) + " given " + std::to_string(bindingSize) +
|
||||
" bytes, maximum is " + std::to_string(kMaxUniformBufferBindingSize) +
|
||||
" bytes");
|
||||
}
|
||||
DAWN_INVALID_IF(bindingSize > maxBindingSize,
|
||||
"Binding size (%u) is larger than the maximum binding size (%u).",
|
||||
bindingSize, maxBindingSize);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -129,9 +120,8 @@ namespace dawn_native {
|
|||
TextureViewBase* view = entry.textureView;
|
||||
|
||||
Aspect aspect = view->GetAspects();
|
||||
if (!HasOneBit(aspect)) {
|
||||
return DAWN_VALIDATION_ERROR("Texture view must select a single aspect");
|
||||
}
|
||||
// TODO(dawn:563): Format Aspects
|
||||
DAWN_INVALID_IF(!HasOneBit(aspect), "Multiple aspects selected in %s.", view);
|
||||
|
||||
TextureBase* texture = view->GetTexture();
|
||||
switch (bindingInfo.bindingType) {
|
||||
|
@ -141,36 +131,46 @@ namespace dawn_native {
|
|||
SampleTypeBit requiredType =
|
||||
SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType);
|
||||
|
||||
if (!(texture->GetUsage() & wgpu::TextureUsage::TextureBinding)) {
|
||||
return DAWN_VALIDATION_ERROR("Texture binding usage mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
!(texture->GetUsage() & wgpu::TextureUsage::TextureBinding),
|
||||
"Usage (%s) of %s doesn't include TextureUsage::TextureBinding.",
|
||||
texture->GetUsage(), texture);
|
||||
|
||||
if (texture->IsMultisampledTexture() != bindingInfo.texture.multisampled) {
|
||||
return DAWN_VALIDATION_ERROR("Texture multisampling mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
texture->IsMultisampledTexture() != bindingInfo.texture.multisampled,
|
||||
"Sample count (%u) of %s doesn't match expectation (multisampled: %d).",
|
||||
texture->GetSampleCount(), texture, bindingInfo.texture.multisampled);
|
||||
|
||||
if ((supportedTypes & requiredType) == 0) {
|
||||
return DAWN_VALIDATION_ERROR("Texture component type usage mismatch");
|
||||
}
|
||||
// TODO(dawn:563): Improve error message.
|
||||
DAWN_INVALID_IF((supportedTypes & requiredType) == 0,
|
||||
"Texture component type usage mismatch.");
|
||||
|
||||
if (entry.textureView->GetDimension() != bindingInfo.texture.viewDimension) {
|
||||
return DAWN_VALIDATION_ERROR("Texture view dimension mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
entry.textureView->GetDimension() != bindingInfo.texture.viewDimension,
|
||||
"Dimension (%s) of %s doesn't match the expected dimension (%s).",
|
||||
entry.textureView->GetDimension(), entry.textureView,
|
||||
bindingInfo.texture.viewDimension);
|
||||
break;
|
||||
}
|
||||
case BindingInfoType::StorageTexture: {
|
||||
if (!(texture->GetUsage() & wgpu::TextureUsage::StorageBinding)) {
|
||||
return DAWN_VALIDATION_ERROR("Storage Texture binding usage mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
!(texture->GetUsage() & wgpu::TextureUsage::StorageBinding),
|
||||
"Usage (%s) of %s doesn't include TextureUsage::StorageBinding.",
|
||||
texture->GetUsage(), texture);
|
||||
|
||||
ASSERT(!texture->IsMultisampledTexture());
|
||||
|
||||
if (texture->GetFormat().format != bindingInfo.storageTexture.format) {
|
||||
return DAWN_VALIDATION_ERROR("Storage texture format mismatch");
|
||||
}
|
||||
if (entry.textureView->GetDimension() !=
|
||||
bindingInfo.storageTexture.viewDimension) {
|
||||
return DAWN_VALIDATION_ERROR("Storage texture view dimension mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
texture->GetFormat().format != bindingInfo.storageTexture.format,
|
||||
"Format (%s) of %s expected to be (%s).", texture->GetFormat().format,
|
||||
texture, bindingInfo.storageTexture.format);
|
||||
|
||||
DAWN_INVALID_IF(
|
||||
entry.textureView->GetDimension() !=
|
||||
bindingInfo.storageTexture.viewDimension,
|
||||
"Dimension (%s) of %s doesn't match the expected dimension (%s).",
|
||||
entry.textureView->GetDimension(), entry.textureView,
|
||||
bindingInfo.storageTexture.viewDimension);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -194,25 +194,25 @@ namespace dawn_native {
|
|||
|
||||
switch (bindingInfo.sampler.type) {
|
||||
case wgpu::SamplerBindingType::NonFiltering:
|
||||
if (entry.sampler->IsFiltering()) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Filtering sampler is incompatible with non-filtering sampler "
|
||||
"binding.");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
entry.sampler->IsFiltering(),
|
||||
"Filtering sampler %s is incompatible with non-filtering sampler "
|
||||
"binding.",
|
||||
entry.sampler);
|
||||
DAWN_FALLTHROUGH;
|
||||
case wgpu::SamplerBindingType::Filtering:
|
||||
if (entry.sampler->IsComparison()) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Comparison sampler is incompatible with non-comparison sampler "
|
||||
"binding.");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
entry.sampler->IsComparison(),
|
||||
"Comparison sampler %s is incompatible with non-comparison sampler "
|
||||
"binding.",
|
||||
entry.sampler);
|
||||
break;
|
||||
case wgpu::SamplerBindingType::Comparison:
|
||||
if (!entry.sampler->IsComparison()) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"Non-comparison sampler is imcompatible with comparison sampler "
|
||||
"binding.");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
!entry.sampler->IsComparison(),
|
||||
"Non-comparison sampler %s is imcompatible with comparison sampler "
|
||||
"binding.",
|
||||
entry.sampler);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
|
@ -251,9 +251,11 @@ namespace dawn_native {
|
|||
|
||||
DAWN_TRY(device->ValidateObject(descriptor->layout));
|
||||
|
||||
if (BindingIndex(descriptor->entryCount) != descriptor->layout->GetBindingCount()) {
|
||||
return DAWN_VALIDATION_ERROR("numBindings mismatch");
|
||||
}
|
||||
DAWN_INVALID_IF(
|
||||
BindingIndex(descriptor->entryCount) != descriptor->layout->GetBindingCount(),
|
||||
"Number of entries (%u) did not match the number of entries (%u) specified in %s",
|
||||
descriptor->entryCount, static_cast<uint32_t>(descriptor->layout->GetBindingCount()),
|
||||
descriptor->layout);
|
||||
|
||||
const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
|
||||
ASSERT(bindingMap.size() <= kMaxBindingsPerPipelineLayout);
|
||||
|
@ -263,15 +265,17 @@ namespace dawn_native {
|
|||
const BindGroupEntry& entry = descriptor->entries[i];
|
||||
|
||||
const auto& it = bindingMap.find(BindingNumber(entry.binding));
|
||||
if (it == bindingMap.end()) {
|
||||
return DAWN_VALIDATION_ERROR("setting non-existent binding");
|
||||
}
|
||||
DAWN_INVALID_IF(it == bindingMap.end(),
|
||||
"In entries[%u], binding index %u not present in the bind group layout",
|
||||
i, entry.binding);
|
||||
|
||||
BindingIndex bindingIndex = it->second;
|
||||
ASSERT(bindingIndex < descriptor->layout->GetBindingCount());
|
||||
|
||||
if (bindingsSet[bindingIndex]) {
|
||||
return DAWN_VALIDATION_ERROR("binding set twice");
|
||||
}
|
||||
DAWN_INVALID_IF(bindingsSet[bindingIndex],
|
||||
"In entries[%u], binding index %u already used by a previous entry", i,
|
||||
entry.binding);
|
||||
|
||||
bindingsSet.set(bindingIndex);
|
||||
|
||||
const BindingInfo& bindingInfo = descriptor->layout->GetBindingInfo(bindingIndex);
|
||||
|
@ -279,17 +283,21 @@ namespace dawn_native {
|
|||
// Perform binding-type specific validation.
|
||||
switch (bindingInfo.bindingType) {
|
||||
case BindingInfoType::Buffer:
|
||||
DAWN_TRY(ValidateBufferBinding(device, entry, bindingInfo));
|
||||
DAWN_TRY_CONTEXT(ValidateBufferBinding(device, entry, bindingInfo),
|
||||
"validating entries[%u] as a Buffer", i);
|
||||
break;
|
||||
case BindingInfoType::Texture:
|
||||
case BindingInfoType::StorageTexture:
|
||||
DAWN_TRY(ValidateTextureBinding(device, entry, bindingInfo));
|
||||
DAWN_TRY_CONTEXT(ValidateTextureBinding(device, entry, bindingInfo),
|
||||
"validating entries[%u] as a Texture", i);
|
||||
break;
|
||||
case BindingInfoType::Sampler:
|
||||
DAWN_TRY(ValidateSamplerBinding(device, entry, bindingInfo));
|
||||
DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, bindingInfo),
|
||||
"validating entries[%u] as a Sampler", i);
|
||||
break;
|
||||
case BindingInfoType::ExternalTexture:
|
||||
DAWN_TRY(ValidateExternalTextureBinding(device, entry, bindingInfo));
|
||||
DAWN_TRY_CONTEXT(ValidateExternalTextureBinding(device, entry, bindingInfo),
|
||||
"validating entries[%u] as an ExternalTexture", i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,9 @@ namespace dawn_native {
|
|||
uint64_t mDebugGroupStackSize = 0;
|
||||
};
|
||||
|
||||
// For the benefit of template generation.
|
||||
using CommandEncoderBase = CommandEncoder;
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_COMMANDENCODER_H_
|
||||
|
|
|
@ -66,6 +66,9 @@ namespace dawn_native {
|
|||
Ref<CommandEncoder> mCommandEncoder;
|
||||
};
|
||||
|
||||
// For the benefit of template generation.
|
||||
using ComputePassEncoderBase = ComputePassEncoder;
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_COMPUTEPASSENCODER_H_
|
||||
|
|
|
@ -396,9 +396,17 @@ namespace dawn_native {
|
|||
ASSERT(error != nullptr);
|
||||
std::ostringstream ss;
|
||||
ss << error->GetMessage();
|
||||
for (const auto& callsite : error->GetBacktrace()) {
|
||||
ss << "\n at " << callsite.function << " (" << callsite.file << ":" << callsite.line
|
||||
<< ")";
|
||||
|
||||
const std::vector<std::string>& contexts = error->GetContexts();
|
||||
if (!contexts.empty()) {
|
||||
for (auto context : contexts) {
|
||||
ss << "\n - While " << context;
|
||||
}
|
||||
} else {
|
||||
for (const auto& callsite : error->GetBacktrace()) {
|
||||
ss << "\n at " << callsite.function << " (" << callsite.file << ":"
|
||||
<< callsite.line << ")";
|
||||
}
|
||||
}
|
||||
HandleError(error->GetType(), ss.str().c_str());
|
||||
}
|
||||
|
@ -463,12 +471,12 @@ namespace dawn_native {
|
|||
|
||||
MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const {
|
||||
ASSERT(object != nullptr);
|
||||
if (DAWN_UNLIKELY(object->GetDevice() != this)) {
|
||||
return DAWN_VALIDATION_ERROR("Object from a different device.");
|
||||
}
|
||||
if (DAWN_UNLIKELY(object->IsError())) {
|
||||
return DAWN_VALIDATION_ERROR("Object is an error.");
|
||||
}
|
||||
DAWN_INVALID_IF(object->GetDevice() != this,
|
||||
"%s is associated with %s, and cannot be used with %s.", object,
|
||||
object->GetDevice(), this);
|
||||
|
||||
DAWN_INVALID_IF(object->IsError(), "%s is an error.", object);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -1128,7 +1136,8 @@ namespace dawn_native {
|
|||
const BindGroupDescriptor* descriptor) {
|
||||
DAWN_TRY(ValidateIsAlive());
|
||||
if (IsValidationEnabled()) {
|
||||
DAWN_TRY(ValidateBindGroupDescriptor(this, descriptor));
|
||||
DAWN_TRY_CONTEXT(ValidateBindGroupDescriptor(this, descriptor),
|
||||
"validating %s against %s", descriptor, descriptor->layout);
|
||||
}
|
||||
return CreateBindGroupImpl(descriptor);
|
||||
}
|
||||
|
@ -1606,4 +1615,16 @@ namespace dawn_native {
|
|||
return PipelineCompatibilityToken(mNextPipelineCompatibilityToken++);
|
||||
}
|
||||
|
||||
const std::string& DeviceBase::GetLabel() const {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
void DeviceBase::APISetLabel(const char* label) {
|
||||
mLabel = label;
|
||||
SetLabelImpl();
|
||||
}
|
||||
|
||||
void DeviceBase::SetLabelImpl() {
|
||||
}
|
||||
|
||||
} // namespace dawn_native
|
||||
|
|
|
@ -308,6 +308,9 @@ namespace dawn_native {
|
|||
|
||||
PipelineCompatibilityToken GetNextPipelineCompatibilityToken();
|
||||
|
||||
const std::string& GetLabel() const;
|
||||
void APISetLabel(const char* label);
|
||||
|
||||
protected:
|
||||
void SetToggle(Toggle toggle, bool isEnabled);
|
||||
void ForceSetToggle(Toggle toggle, bool isEnabled);
|
||||
|
@ -351,6 +354,7 @@ namespace dawn_native {
|
|||
virtual ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
|
||||
TextureBase* texture,
|
||||
const TextureViewDescriptor* descriptor) = 0;
|
||||
virtual void SetLabelImpl();
|
||||
|
||||
virtual MaybeError TickImpl() = 0;
|
||||
void FlushCallbackTaskQueue();
|
||||
|
@ -463,6 +467,7 @@ namespace dawn_native {
|
|||
|
||||
std::unique_ptr<CallbackTaskManager> mCallbackTaskManager;
|
||||
std::unique_ptr<dawn_platform::WorkerTaskPool> mWorkerTaskPool;
|
||||
std::string mLabel;
|
||||
};
|
||||
|
||||
} // namespace dawn_native
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
#ifndef DAWNNATIVE_ERROR_H_
|
||||
#define DAWNNATIVE_ERROR_H_
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "common/Result.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/webgpu_absl_format_autogen.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -74,6 +76,18 @@ namespace dawn_native {
|
|||
|
||||
#define DAWN_VALIDATION_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::Validation, MESSAGE)
|
||||
|
||||
// TODO(dawn:563): Rename to DAWN_VALIDATION_ERROR once all message format strings have been
|
||||
// converted to constexpr.
|
||||
#define DAWN_FORMAT_VALIDATION_ERROR(...) \
|
||||
DAWN_MAKE_ERROR(InternalErrorType::Validation, absl::StrFormat(__VA_ARGS__))
|
||||
|
||||
#define DAWN_INVALID_IF(EXPR, ...) \
|
||||
if (DAWN_UNLIKELY(EXPR)) { \
|
||||
return DAWN_MAKE_ERROR(InternalErrorType::Validation, absl::StrFormat(__VA_ARGS__)); \
|
||||
} \
|
||||
for (;;) \
|
||||
break
|
||||
|
||||
// DAWN_DEVICE_LOST_ERROR means that there was a real unrecoverable native device lost error.
|
||||
// We can't even do a graceful shutdown because the Device is gone.
|
||||
#define DAWN_DEVICE_LOST_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::DeviceLost, MESSAGE)
|
||||
|
@ -99,18 +113,24 @@ namespace dawn_native {
|
|||
// the current function.
|
||||
#define DAWN_TRY(EXPR) DAWN_TRY_WITH_CLEANUP(EXPR, {})
|
||||
|
||||
#define DAWN_TRY_WITH_CLEANUP(EXPR, BODY) \
|
||||
{ \
|
||||
auto DAWN_LOCAL_VAR = EXPR; \
|
||||
if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) { \
|
||||
{BODY} /* comment to force the formatter to insert a newline */ \
|
||||
std::unique_ptr<::dawn_native::ErrorData> \
|
||||
error = DAWN_LOCAL_VAR.AcquireError(); \
|
||||
error->AppendBacktrace(__FILE__, __func__, __LINE__); \
|
||||
return {std::move(error)}; \
|
||||
} \
|
||||
} \
|
||||
for (;;) \
|
||||
#define DAWN_TRY_CONTEXT(EXPR, ...) \
|
||||
DAWN_TRY_WITH_CLEANUP(EXPR, { \
|
||||
if (error->GetType() == InternalErrorType::Validation) { \
|
||||
error->AppendContext(absl::StrFormat(__VA_ARGS__)); \
|
||||
} \
|
||||
})
|
||||
|
||||
#define DAWN_TRY_WITH_CLEANUP(EXPR, BODY) \
|
||||
{ \
|
||||
auto DAWN_LOCAL_VAR = EXPR; \
|
||||
if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) { \
|
||||
std::unique_ptr<::dawn_native::ErrorData> error = DAWN_LOCAL_VAR.AcquireError(); \
|
||||
{BODY} /* comment to force the formatter to insert a newline */ \
|
||||
error->AppendBacktrace(__FILE__, __func__, __LINE__); \
|
||||
return {std::move(error)}; \
|
||||
} \
|
||||
} \
|
||||
for (;;) \
|
||||
break
|
||||
|
||||
// DAWN_TRY_ASSIGN is the same as DAWN_TRY for ResultOrError and assigns the success value, if
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "dawn_native/ErrorData.h"
|
||||
|
||||
#include "dawn_native/Error.h"
|
||||
#include "dawn_native/ObjectBase.h"
|
||||
#include "dawn_native/dawn_platform.h"
|
||||
|
||||
namespace dawn_native {
|
||||
|
@ -42,6 +43,10 @@ namespace dawn_native {
|
|||
mBacktrace.push_back(std::move(record));
|
||||
}
|
||||
|
||||
void ErrorData::AppendContext(std::string context) {
|
||||
mContexts.push_back(std::move(context));
|
||||
}
|
||||
|
||||
InternalErrorType ErrorData::GetType() const {
|
||||
return mType;
|
||||
}
|
||||
|
@ -54,4 +59,8 @@ namespace dawn_native {
|
|||
return mBacktrace;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& ErrorData::GetContexts() const {
|
||||
return mContexts;
|
||||
}
|
||||
|
||||
} // namespace dawn_native
|
||||
|
|
|
@ -48,15 +48,18 @@ namespace dawn_native {
|
|||
int line;
|
||||
};
|
||||
void AppendBacktrace(const char* file, const char* function, int line);
|
||||
void AppendContext(std::string context);
|
||||
|
||||
InternalErrorType GetType() const;
|
||||
const std::string& GetMessage() const;
|
||||
const std::vector<BacktraceRecord>& GetBacktrace() const;
|
||||
const std::vector<std::string>& GetContexts() const;
|
||||
|
||||
private:
|
||||
InternalErrorType mType;
|
||||
std::string mMessage;
|
||||
std::vector<BacktraceRecord> mBacktrace;
|
||||
std::vector<std::string> mContexts;
|
||||
};
|
||||
|
||||
} // namespace dawn_native
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace dawn_native {
|
|||
class CommandEncoder;
|
||||
class ComputePassEncoder;
|
||||
class ExternalTextureBase;
|
||||
class Fence;
|
||||
class InstanceBase;
|
||||
class PipelineBase;
|
||||
class PipelineLayoutBase;
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace dawn_native {
|
|||
: RefCounted(kNotErrorPayload), mDevice(device) {
|
||||
}
|
||||
|
||||
const std::string& ObjectBase::GetLabel() {
|
||||
const std::string& ObjectBase::GetLabel() const {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace dawn_native {
|
|||
ObjectBase(DeviceBase* device, ErrorTag tag);
|
||||
|
||||
DeviceBase* GetDevice() const;
|
||||
const std::string& GetLabel();
|
||||
const std::string& GetLabel() const;
|
||||
bool IsError() const;
|
||||
|
||||
// Dawn API
|
||||
|
|
|
@ -45,6 +45,10 @@ namespace dawn_native {
|
|||
|
||||
EncodingContext mBundleEncodingContext;
|
||||
};
|
||||
|
||||
// For the benefit of template generation.
|
||||
using RenderBundleEncoderBase = RenderBundleEncoder;
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_RENDERBUNDLEENCODER_H_
|
||||
|
|
|
@ -77,6 +77,9 @@ namespace dawn_native {
|
|||
bool mOcclusionQueryActive = false;
|
||||
};
|
||||
|
||||
// For the benefit of template generation.
|
||||
using RenderPassEncoderBase = RenderPassEncoder;
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_RENDERPASSENCODER_H_
|
||||
|
|
|
@ -100,6 +100,9 @@ namespace dawn_native {
|
|||
uint32_t mXWindow = 0;
|
||||
};
|
||||
|
||||
// For the benefit of template generation.
|
||||
using SurfaceBase = Surface;
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_SURFACE_H_
|
||||
|
|
Loading…
Reference in New Issue