mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-13 10:51:35 +00:00
Make CommandBufferValidation use Error.h
This commit is contained in:
parent
52f2383bb8
commit
021c9504d0
@ -123,11 +123,24 @@ namespace {{namespace}} {
|
|||||||
//* can be skipped in the NonValidatingEntryPoints.
|
//* can be skipped in the NonValidatingEntryPoints.
|
||||||
{% if suffix in methodsWithExtraValidation %}
|
{% if suffix in methodsWithExtraValidation %}
|
||||||
if (valid) {
|
if (valid) {
|
||||||
valid = self->Validate{{method.name.CamelCase()}}(
|
MaybeError error = self->Validate{{method.name.CamelCase()}}(
|
||||||
{%- for arg in method.arguments -%}
|
{%- for arg in method.arguments -%}
|
||||||
{% if not loop.first %}, {% endif %}{{as_varName(arg.name)}}
|
{% if not loop.first %}, {% endif %}{{as_varName(arg.name)}}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
);
|
);
|
||||||
|
//* Builders want to handle error themselves, unpack the error and make
|
||||||
|
//* the builder handle it.
|
||||||
|
{% if type.is_builder %}
|
||||||
|
if (error.IsError()) {
|
||||||
|
ErrorData* errorData = error.AcquireError();
|
||||||
|
self->HandleError(errorData->GetMessage().c_str());
|
||||||
|
delete errorData;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
{% else %}
|
||||||
|
//* Non-builder errors are handled by the device
|
||||||
|
valid = !self->GetDevice()->ConsumedError(std::move(error));
|
||||||
|
{% endif %}
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -32,12 +32,10 @@ namespace backend {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool ValidateCopyLocationFitsInTexture(CommandBufferBuilder* builder,
|
MaybeError ValidateCopyLocationFitsInTexture(const TextureCopyLocation& location) {
|
||||||
const TextureCopyLocation& location) {
|
|
||||||
const TextureBase* texture = location.texture.Get();
|
const TextureBase* texture = location.texture.Get();
|
||||||
if (location.level >= texture->GetNumMipLevels()) {
|
if (location.level >= texture->GetNumMipLevels()) {
|
||||||
builder->HandleError("Copy mip-level out of range");
|
NXT_RETURN_ERROR("Copy mip-level out of range");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
|
// All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
|
||||||
@ -47,18 +45,16 @@ namespace backend {
|
|||||||
(static_cast<uint64_t>(texture->GetWidth()) >> level) ||
|
(static_cast<uint64_t>(texture->GetWidth()) >> level) ||
|
||||||
uint64_t(location.y) + uint64_t(location.height) >
|
uint64_t(location.y) + uint64_t(location.height) >
|
||||||
(static_cast<uint64_t>(texture->GetHeight()) >> level)) {
|
(static_cast<uint64_t>(texture->GetHeight()) >> level)) {
|
||||||
builder->HandleError("Copy would touch outside of the texture");
|
NXT_RETURN_ERROR("Copy would touch outside of the texture");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(cwallez@chromium.org): Check the depth bound differently for 2D arrays and 3D
|
// TODO(cwallez@chromium.org): Check the depth bound differently for 2D arrays and 3D
|
||||||
// textures
|
// textures
|
||||||
if (location.z != 0 || location.depth != 1) {
|
if (location.z != 0 || location.depth != 1) {
|
||||||
builder->HandleError("No support for z != 0 and depth != 1 for now");
|
NXT_RETURN_ERROR("No support for z != 0 and depth != 1 for now");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FitsInBuffer(const BufferBase* buffer, uint32_t offset, uint32_t size) {
|
bool FitsInBuffer(const BufferBase* buffer, uint32_t offset, uint32_t size) {
|
||||||
@ -66,38 +62,33 @@ namespace backend {
|
|||||||
return offset <= bufferSize && (size <= (bufferSize - offset));
|
return offset <= bufferSize && (size <= (bufferSize - offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateCopySizeFitsInBuffer(CommandBufferBuilder* builder,
|
MaybeError ValidateCopySizeFitsInBuffer(const BufferCopyLocation& location,
|
||||||
const BufferCopyLocation& location,
|
uint32_t dataSize) {
|
||||||
uint32_t dataSize) {
|
|
||||||
if (!FitsInBuffer(location.buffer.Get(), location.offset, dataSize)) {
|
if (!FitsInBuffer(location.buffer.Get(), location.offset, dataSize)) {
|
||||||
builder->HandleError("Copy would overflow the buffer");
|
NXT_RETURN_ERROR("Copy would overflow the buffer");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateTexelBufferOffset(CommandBufferBuilder* builder,
|
MaybeError ValidateTexelBufferOffset(TextureBase* texture,
|
||||||
TextureBase* texture,
|
const BufferCopyLocation& location) {
|
||||||
const BufferCopyLocation& location) {
|
|
||||||
uint32_t texelSize =
|
uint32_t texelSize =
|
||||||
static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
|
static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
|
||||||
if (location.offset % texelSize != 0) {
|
if (location.offset % texelSize != 0) {
|
||||||
builder->HandleError("Buffer offset must be a multiple of the texel size");
|
NXT_RETURN_ERROR("Buffer offset must be a multiple of the texel size");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComputeTextureCopyBufferSize(CommandBufferBuilder*,
|
MaybeError ComputeTextureCopyBufferSize(const TextureCopyLocation& location,
|
||||||
const TextureCopyLocation& location,
|
uint32_t rowPitch,
|
||||||
uint32_t rowPitch,
|
uint32_t* bufferSize) {
|
||||||
uint32_t* bufferSize) {
|
|
||||||
// TODO(cwallez@chromium.org): check for overflows
|
// TODO(cwallez@chromium.org): check for overflows
|
||||||
*bufferSize = (rowPitch * (location.height - 1) + location.width) * location.depth;
|
*bufferSize = (rowPitch * (location.height - 1) + location.width) * location.depth;
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ComputeDefaultRowPitch(TextureBase* texture, uint32_t width) {
|
uint32_t ComputeDefaultRowPitch(TextureBase* texture, uint32_t width) {
|
||||||
@ -105,45 +96,35 @@ namespace backend {
|
|||||||
return texelSize * width;
|
return texelSize * width;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateRowPitch(CommandBufferBuilder* builder,
|
MaybeError ValidateRowPitch(const TextureCopyLocation& location, uint32_t rowPitch) {
|
||||||
const TextureCopyLocation& location,
|
|
||||||
uint32_t rowPitch) {
|
|
||||||
if (rowPitch % kTextureRowPitchAlignment != 0) {
|
if (rowPitch % kTextureRowPitchAlignment != 0) {
|
||||||
builder->HandleError("Row pitch must be a multiple of 256");
|
NXT_RETURN_ERROR("Row pitch must be a multiple of 256");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t texelSize = TextureFormatPixelSize(location.texture.Get()->GetFormat());
|
uint32_t texelSize = TextureFormatPixelSize(location.texture.Get()->GetFormat());
|
||||||
if (rowPitch < location.width * texelSize) {
|
if (rowPitch < location.width * texelSize) {
|
||||||
builder->HandleError("Row pitch must not be less than the number of bytes per row");
|
NXT_RETURN_ERROR("Row pitch must not be less than the number of bytes per row");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateCanUseAs(CommandBufferBuilder* builder,
|
MaybeError ValidateCanUseAs(BufferBase* buffer, nxt::BufferUsageBit usage) {
|
||||||
BufferBase* buffer,
|
|
||||||
nxt::BufferUsageBit usage) {
|
|
||||||
ASSERT(HasZeroOrOneBits(usage));
|
ASSERT(HasZeroOrOneBits(usage));
|
||||||
if (!(buffer->GetAllowedUsage() & usage)) {
|
if (!(buffer->GetAllowedUsage() & usage)) {
|
||||||
builder->HandleError("buffer doesn't have the required usage.");
|
NXT_RETURN_ERROR("buffer doesn't have the required usage.");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateCanUseAs(CommandBufferBuilder* builder,
|
MaybeError ValidateCanUseAs(TextureBase* texture, nxt::TextureUsageBit usage) {
|
||||||
TextureBase* texture,
|
|
||||||
nxt::TextureUsageBit usage) {
|
|
||||||
ASSERT(HasZeroOrOneBits(usage));
|
ASSERT(HasZeroOrOneBits(usage));
|
||||||
if (!(texture->GetAllowedUsage() & usage)) {
|
if (!(texture->GetAllowedUsage() & usage)) {
|
||||||
builder->HandleError("texture doesn't have the required usage.");
|
NXT_RETURN_ERROR("texture doesn't have the required usage.");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PassType {
|
enum class PassType {
|
||||||
@ -184,10 +165,10 @@ namespace backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Performs the per-pass usage validation checks
|
// Performs the per-pass usage validation checks
|
||||||
bool AreUsagesValid(PassType pass) const {
|
MaybeError ValidateUsages(PassType pass) const {
|
||||||
// Storage resources cannot be used twice in the same compute pass
|
// Storage resources cannot be used twice in the same compute pass
|
||||||
if (pass == PassType::Compute && mStorageUsedMultipleTimes) {
|
if (pass == PassType::Compute && mStorageUsedMultipleTimes) {
|
||||||
return false;
|
NXT_RETURN_ERROR("Storage resource used multiple times in compute pass");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffers can only be used as single-write or multiple read.
|
// Buffers can only be used as single-write or multiple read.
|
||||||
@ -196,14 +177,15 @@ namespace backend {
|
|||||||
nxt::BufferUsageBit usage = it.second;
|
nxt::BufferUsageBit usage = it.second;
|
||||||
|
|
||||||
if (usage & ~buffer->GetAllowedUsage()) {
|
if (usage & ~buffer->GetAllowedUsage()) {
|
||||||
return false;
|
NXT_RETURN_ERROR("Buffer missing usage for the pass");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readOnly = (usage & kReadOnlyBufferUsages) == usage;
|
bool readOnly = (usage & kReadOnlyBufferUsages) == usage;
|
||||||
bool singleUse = nxt::HasZeroOrOneBits(usage);
|
bool singleUse = nxt::HasZeroOrOneBits(usage);
|
||||||
|
|
||||||
if (!readOnly && !singleUse) {
|
if (!readOnly && !singleUse) {
|
||||||
return false;
|
NXT_RETURN_ERROR(
|
||||||
|
"Buffer used as writeable usage and another usage in pass");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,17 +196,17 @@ namespace backend {
|
|||||||
nxt::TextureUsageBit usage = it.second;
|
nxt::TextureUsageBit usage = it.second;
|
||||||
|
|
||||||
if (usage & ~texture->GetAllowedUsage()) {
|
if (usage & ~texture->GetAllowedUsage()) {
|
||||||
return false;
|
NXT_RETURN_ERROR("Texture missing usage for the pass");
|
||||||
}
|
}
|
||||||
|
|
||||||
// For textures the only read-only usage in a pass is Sampled, so checking the
|
// For textures the only read-only usage in a pass is Sampled, so checking the
|
||||||
// usage constraint simplifies to checking a single usage bit is set.
|
// usage constraint simplifies to checking a single usage bit is set.
|
||||||
if (!nxt::HasZeroOrOneBits(it.second)) {
|
if (!nxt::HasZeroOrOneBits(it.second)) {
|
||||||
return false;
|
NXT_RETURN_ERROR("Texture used with more than one usage in pass");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the per-pass usage for use by backends for APIs with explicit barriers.
|
// Returns the per-pass usage for use by backends for APIs with explicit barriers.
|
||||||
@ -297,7 +279,7 @@ namespace backend {
|
|||||||
// CommandBufferBuilder
|
// CommandBufferBuilder
|
||||||
|
|
||||||
CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device)
|
CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device)
|
||||||
: Builder(device), mState(std::make_unique<CommandBufferStateTracker>(this)) {
|
: Builder(device), mState(std::make_unique<CommandBufferStateTracker>()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandBufferBuilder::~CommandBufferBuilder() {
|
CommandBufferBuilder::~CommandBufferBuilder() {
|
||||||
@ -333,7 +315,7 @@ namespace backend {
|
|||||||
|
|
||||||
// Implementation of the command buffer validation that can be precomputed before submit
|
// Implementation of the command buffer validation that can be precomputed before submit
|
||||||
|
|
||||||
bool CommandBufferBuilder::ValidateGetResult() {
|
MaybeError CommandBufferBuilder::ValidateGetResult() {
|
||||||
MoveToIterator();
|
MoveToIterator();
|
||||||
mIterator.Reset();
|
mIterator.Reset();
|
||||||
|
|
||||||
@ -342,78 +324,73 @@ namespace backend {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case Command::BeginComputePass: {
|
case Command::BeginComputePass: {
|
||||||
mIterator.NextCommand<BeginComputePassCmd>();
|
mIterator.NextCommand<BeginComputePassCmd>();
|
||||||
if (!ValidateComputePass()) {
|
NXT_TRY(ValidateComputePass());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::BeginRenderPass: {
|
case Command::BeginRenderPass: {
|
||||||
BeginRenderPassCmd* cmd = mIterator.NextCommand<BeginRenderPassCmd>();
|
BeginRenderPassCmd* cmd = mIterator.NextCommand<BeginRenderPassCmd>();
|
||||||
if (!ValidateRenderPass(cmd->info.Get())) {
|
NXT_TRY(ValidateRenderPass(cmd->info.Get()));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::CopyBufferToBuffer: {
|
case Command::CopyBufferToBuffer: {
|
||||||
CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
|
CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
|
||||||
if (!ValidateCopySizeFitsInBuffer(this, copy->source, copy->size) ||
|
|
||||||
!ValidateCopySizeFitsInBuffer(this, copy->destination, copy->size) ||
|
NXT_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size));
|
||||||
!ValidateCanUseAs(this, copy->source.buffer.Get(),
|
NXT_TRY(ValidateCopySizeFitsInBuffer(copy->destination, copy->size));
|
||||||
nxt::BufferUsageBit::TransferSrc) ||
|
|
||||||
!ValidateCanUseAs(this, copy->destination.buffer.Get(),
|
NXT_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
|
||||||
nxt::BufferUsageBit::TransferDst)) {
|
nxt::BufferUsageBit::TransferSrc));
|
||||||
return false;
|
NXT_TRY(ValidateCanUseAs(copy->destination.buffer.Get(),
|
||||||
}
|
nxt::BufferUsageBit::TransferDst));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::CopyBufferToTexture: {
|
case Command::CopyBufferToTexture: {
|
||||||
CopyBufferToTextureCmd* copy = mIterator.NextCommand<CopyBufferToTextureCmd>();
|
CopyBufferToTextureCmd* copy = mIterator.NextCommand<CopyBufferToTextureCmd>();
|
||||||
|
|
||||||
uint32_t bufferCopySize = 0;
|
uint32_t bufferCopySize = 0;
|
||||||
if (!ValidateRowPitch(this, copy->destination, copy->rowPitch) ||
|
NXT_TRY(ValidateRowPitch(copy->destination, copy->rowPitch));
|
||||||
!ComputeTextureCopyBufferSize(this, copy->destination, copy->rowPitch,
|
NXT_TRY(ComputeTextureCopyBufferSize(copy->destination, copy->rowPitch,
|
||||||
&bufferCopySize) ||
|
&bufferCopySize));
|
||||||
!ValidateCopyLocationFitsInTexture(this, copy->destination) ||
|
|
||||||
!ValidateCopySizeFitsInBuffer(this, copy->source, bufferCopySize) ||
|
NXT_TRY(ValidateCopyLocationFitsInTexture(copy->destination));
|
||||||
!ValidateTexelBufferOffset(this, copy->destination.texture.Get(),
|
NXT_TRY(ValidateCopySizeFitsInBuffer(copy->source, bufferCopySize));
|
||||||
copy->source) ||
|
NXT_TRY(
|
||||||
!ValidateCanUseAs(this, copy->source.buffer.Get(),
|
ValidateTexelBufferOffset(copy->destination.texture.Get(), copy->source));
|
||||||
nxt::BufferUsageBit::TransferSrc) ||
|
|
||||||
!ValidateCanUseAs(this, copy->destination.texture.Get(),
|
NXT_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
|
||||||
nxt::TextureUsageBit::TransferDst)) {
|
nxt::BufferUsageBit::TransferSrc));
|
||||||
return false;
|
NXT_TRY(ValidateCanUseAs(copy->destination.texture.Get(),
|
||||||
}
|
nxt::TextureUsageBit::TransferDst));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::CopyTextureToBuffer: {
|
case Command::CopyTextureToBuffer: {
|
||||||
CopyTextureToBufferCmd* copy = mIterator.NextCommand<CopyTextureToBufferCmd>();
|
CopyTextureToBufferCmd* copy = mIterator.NextCommand<CopyTextureToBufferCmd>();
|
||||||
|
|
||||||
uint32_t bufferCopySize = 0;
|
uint32_t bufferCopySize = 0;
|
||||||
if (!ValidateRowPitch(this, copy->source, copy->rowPitch) ||
|
NXT_TRY(ValidateRowPitch(copy->source, copy->rowPitch));
|
||||||
!ComputeTextureCopyBufferSize(this, copy->source, copy->rowPitch,
|
NXT_TRY(ComputeTextureCopyBufferSize(copy->source, copy->rowPitch,
|
||||||
&bufferCopySize) ||
|
&bufferCopySize));
|
||||||
!ValidateCopyLocationFitsInTexture(this, copy->source) ||
|
|
||||||
!ValidateCopySizeFitsInBuffer(this, copy->destination, bufferCopySize) ||
|
NXT_TRY(ValidateCopyLocationFitsInTexture(copy->source));
|
||||||
!ValidateTexelBufferOffset(this, copy->source.texture.Get(),
|
NXT_TRY(ValidateCopySizeFitsInBuffer(copy->destination, bufferCopySize));
|
||||||
copy->destination) ||
|
NXT_TRY(
|
||||||
!ValidateCanUseAs(this, copy->source.texture.Get(),
|
ValidateTexelBufferOffset(copy->source.texture.Get(), copy->destination));
|
||||||
nxt::TextureUsageBit::TransferSrc) ||
|
|
||||||
!ValidateCanUseAs(this, copy->destination.buffer.Get(),
|
NXT_TRY(ValidateCanUseAs(copy->source.texture.Get(),
|
||||||
nxt::BufferUsageBit::TransferDst)) {
|
nxt::TextureUsageBit::TransferSrc));
|
||||||
return false;
|
NXT_TRY(ValidateCanUseAs(copy->destination.buffer.Get(),
|
||||||
}
|
nxt::BufferUsageBit::TransferDst));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
HandleError("Command disallowed outside of a pass");
|
NXT_RETURN_ERROR("Command disallowed outside of a pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferBuilder::ValidateComputePass() {
|
MaybeError CommandBufferBuilder::ValidateComputePass() {
|
||||||
PassResourceUsageTracker usageTracker;
|
PassResourceUsageTracker usageTracker;
|
||||||
|
|
||||||
Command type;
|
Command type;
|
||||||
@ -422,28 +399,22 @@ namespace backend {
|
|||||||
case Command::EndComputePass: {
|
case Command::EndComputePass: {
|
||||||
mIterator.NextCommand<EndComputePassCmd>();
|
mIterator.NextCommand<EndComputePassCmd>();
|
||||||
|
|
||||||
if (!usageTracker.AreUsagesValid(PassType::Compute)) {
|
NXT_TRY(usageTracker.ValidateUsages(PassType::Compute));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
|
mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
|
||||||
|
|
||||||
mState->EndPass();
|
mState->EndPass();
|
||||||
return true;
|
return {};
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::Dispatch: {
|
case Command::Dispatch: {
|
||||||
mIterator.NextCommand<DispatchCmd>();
|
mIterator.NextCommand<DispatchCmd>();
|
||||||
if (!mState->ValidateCanDispatch()) {
|
NXT_TRY(mState->ValidateCanDispatch());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::SetComputePipeline: {
|
case Command::SetComputePipeline: {
|
||||||
SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
|
SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
|
||||||
ComputePipelineBase* pipeline = cmd->pipeline.Get();
|
ComputePipelineBase* pipeline = cmd->pipeline.Get();
|
||||||
if (!mState->SetComputePipeline(pipeline)) {
|
mState->SetComputePipeline(pipeline);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::SetPushConstants: {
|
case Command::SetPushConstants: {
|
||||||
@ -453,9 +424,8 @@ namespace backend {
|
|||||||
// recorded because it impacts the size of an allocation in the
|
// recorded because it impacts the size of an allocation in the
|
||||||
// CommandAllocator.
|
// CommandAllocator.
|
||||||
if (cmd->stages & ~nxt::ShaderStageBit::Compute) {
|
if (cmd->stages & ~nxt::ShaderStageBit::Compute) {
|
||||||
HandleError(
|
NXT_RETURN_ERROR(
|
||||||
"SetPushConstants stage must be compute or 0 in compute passes");
|
"SetPushConstants stage must be compute or 0 in compute passes");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -467,16 +437,14 @@ namespace backend {
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
HandleError("Command disallowed inside a compute pass");
|
NXT_RETURN_ERROR("Command disallowed inside a compute pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleError("Unfinished compute pass");
|
NXT_RETURN_ERROR("Unfinished compute pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferBuilder::ValidateRenderPass(RenderPassDescriptorBase* renderPass) {
|
MaybeError CommandBufferBuilder::ValidateRenderPass(RenderPassDescriptorBase* renderPass) {
|
||||||
PassResourceUsageTracker usageTracker;
|
PassResourceUsageTracker usageTracker;
|
||||||
|
|
||||||
// Track usage of the render pass attachments
|
// Track usage of the render pass attachments
|
||||||
@ -496,27 +464,21 @@ namespace backend {
|
|||||||
case Command::EndRenderPass: {
|
case Command::EndRenderPass: {
|
||||||
mIterator.NextCommand<EndRenderPassCmd>();
|
mIterator.NextCommand<EndRenderPassCmd>();
|
||||||
|
|
||||||
if (!usageTracker.AreUsagesValid(PassType::Render)) {
|
NXT_TRY(usageTracker.ValidateUsages(PassType::Render));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
|
mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
|
||||||
|
|
||||||
mState->EndPass();
|
mState->EndPass();
|
||||||
return true;
|
return {};
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::DrawArrays: {
|
case Command::DrawArrays: {
|
||||||
mIterator.NextCommand<DrawArraysCmd>();
|
mIterator.NextCommand<DrawArraysCmd>();
|
||||||
if (!mState->ValidateCanDrawArrays()) {
|
NXT_TRY(mState->ValidateCanDrawArrays());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::DrawElements: {
|
case Command::DrawElements: {
|
||||||
mIterator.NextCommand<DrawElementsCmd>();
|
mIterator.NextCommand<DrawElementsCmd>();
|
||||||
if (!mState->ValidateCanDrawElements()) {
|
NXT_TRY(mState->ValidateCanDrawElements());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::SetRenderPipeline: {
|
case Command::SetRenderPipeline: {
|
||||||
@ -524,13 +486,10 @@ namespace backend {
|
|||||||
RenderPipelineBase* pipeline = cmd->pipeline.Get();
|
RenderPipelineBase* pipeline = cmd->pipeline.Get();
|
||||||
|
|
||||||
if (!pipeline->IsCompatibleWith(renderPass)) {
|
if (!pipeline->IsCompatibleWith(renderPass)) {
|
||||||
HandleError("Pipeline is incompatible with this render pass");
|
NXT_RETURN_ERROR("Pipeline is incompatible with this render pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mState->SetRenderPipeline(pipeline)) {
|
mState->SetRenderPipeline(pipeline);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::SetPushConstants: {
|
case Command::SetPushConstants: {
|
||||||
@ -541,10 +500,9 @@ namespace backend {
|
|||||||
// CommandAllocator.
|
// CommandAllocator.
|
||||||
if (cmd->stages &
|
if (cmd->stages &
|
||||||
~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) {
|
~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) {
|
||||||
HandleError(
|
NXT_RETURN_ERROR(
|
||||||
"SetPushConstants stage must be a subset of (vertex|fragment) in "
|
"SetPushConstants stage must be a subset of (vertex|fragment) in "
|
||||||
"render passes");
|
"render passes");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -571,9 +529,7 @@ namespace backend {
|
|||||||
SetIndexBufferCmd* cmd = mIterator.NextCommand<SetIndexBufferCmd>();
|
SetIndexBufferCmd* cmd = mIterator.NextCommand<SetIndexBufferCmd>();
|
||||||
|
|
||||||
usageTracker.BufferUsedAs(cmd->buffer.Get(), nxt::BufferUsageBit::Index);
|
usageTracker.BufferUsedAs(cmd->buffer.Get(), nxt::BufferUsageBit::Index);
|
||||||
if (!mState->SetIndexBuffer()) {
|
NXT_TRY(mState->SetIndexBuffer());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::SetVertexBuffers: {
|
case Command::SetVertexBuffers: {
|
||||||
@ -583,18 +539,16 @@ namespace backend {
|
|||||||
|
|
||||||
for (uint32_t i = 0; i < cmd->count; ++i) {
|
for (uint32_t i = 0; i < cmd->count; ++i) {
|
||||||
usageTracker.BufferUsedAs(buffers[i].Get(), nxt::BufferUsageBit::Vertex);
|
usageTracker.BufferUsedAs(buffers[i].Get(), nxt::BufferUsageBit::Vertex);
|
||||||
mState->SetVertexBuffer(cmd->startSlot + i);
|
NXT_TRY(mState->SetVertexBuffer(cmd->startSlot + i));
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
HandleError("Command disallowed inside a render pass");
|
NXT_RETURN_ERROR("Command disallowed inside a render pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleError("Unfinished render pass");
|
NXT_RETURN_ERROR("Unfinished render pass");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of the API's command recording methods
|
// Implementation of the API's command recording methods
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "backend/Builder.h"
|
#include "backend/Builder.h"
|
||||||
#include "backend/CommandAllocator.h"
|
#include "backend/CommandAllocator.h"
|
||||||
|
#include "backend/Error.h"
|
||||||
#include "backend/PassResourceUsage.h"
|
#include "backend/PassResourceUsage.h"
|
||||||
#include "backend/RefCounted.h"
|
#include "backend/RefCounted.h"
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ namespace backend {
|
|||||||
CommandBufferBuilder(DeviceBase* device);
|
CommandBufferBuilder(DeviceBase* device);
|
||||||
~CommandBufferBuilder();
|
~CommandBufferBuilder();
|
||||||
|
|
||||||
bool ValidateGetResult();
|
MaybeError ValidateGetResult();
|
||||||
|
|
||||||
CommandIterator AcquireCommands();
|
CommandIterator AcquireCommands();
|
||||||
std::vector<PassResourceUsage> AcquirePassResourceUsage();
|
std::vector<PassResourceUsage> AcquirePassResourceUsage();
|
||||||
@ -134,8 +135,8 @@ namespace backend {
|
|||||||
CommandBufferBase* GetResultImpl() override;
|
CommandBufferBase* GetResultImpl() override;
|
||||||
void MoveToIterator();
|
void MoveToIterator();
|
||||||
|
|
||||||
bool ValidateComputePass();
|
MaybeError ValidateComputePass();
|
||||||
bool ValidateRenderPass(RenderPassDescriptorBase* renderPass);
|
MaybeError ValidateRenderPass(RenderPassDescriptorBase* renderPass);
|
||||||
|
|
||||||
std::unique_ptr<CommandBufferStateTracker> mState;
|
std::unique_ptr<CommandBufferStateTracker> mState;
|
||||||
CommandAllocator mAllocator;
|
CommandAllocator mAllocator;
|
||||||
|
@ -29,54 +29,47 @@
|
|||||||
|
|
||||||
namespace backend {
|
namespace backend {
|
||||||
|
|
||||||
CommandBufferStateTracker::CommandBufferStateTracker(CommandBufferBuilder* mBuilder)
|
MaybeError CommandBufferStateTracker::ValidateCanDispatch() {
|
||||||
: mBuilder(mBuilder) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommandBufferStateTracker::ValidateCanDispatch() {
|
|
||||||
constexpr ValidationAspects requiredAspects =
|
constexpr ValidationAspects requiredAspects =
|
||||||
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS;
|
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS;
|
||||||
if ((requiredAspects & ~mAspects).none()) {
|
if ((requiredAspects & ~mAspects).none()) {
|
||||||
// Fast return-true path if everything is good
|
// Fast return-true path if everything is good
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
|
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
|
||||||
mBuilder->HandleError("No active compute pipeline");
|
NXT_RETURN_ERROR("No active compute pipeline");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// Compute the lazily computed mAspects
|
// Compute the lazily computed mAspects
|
||||||
if (!RecomputeHaveAspectBindGroups()) {
|
if (!RecomputeHaveAspectBindGroups()) {
|
||||||
mBuilder->HandleError("Bind group state not valid");
|
NXT_RETURN_ERROR("Bind group state not valid");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::ValidateCanDrawArrays() {
|
MaybeError CommandBufferStateTracker::ValidateCanDrawArrays() {
|
||||||
constexpr ValidationAspects requiredAspects = 1 << VALIDATION_ASPECT_PIPELINE |
|
constexpr ValidationAspects requiredAspects = 1 << VALIDATION_ASPECT_PIPELINE |
|
||||||
1 << VALIDATION_ASPECT_BIND_GROUPS |
|
1 << VALIDATION_ASPECT_BIND_GROUPS |
|
||||||
1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
|
1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
|
||||||
if ((requiredAspects & ~mAspects).none()) {
|
if ((requiredAspects & ~mAspects).none()) {
|
||||||
// Fast return-true path if everything is good
|
// Fast return-true path if everything is good
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return RevalidateCanDraw();
|
return RevalidateCanDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::ValidateCanDrawElements() {
|
MaybeError CommandBufferStateTracker::ValidateCanDrawElements() {
|
||||||
constexpr ValidationAspects requiredAspects =
|
constexpr ValidationAspects requiredAspects =
|
||||||
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS |
|
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS |
|
||||||
1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER;
|
1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER;
|
||||||
if ((requiredAspects & ~mAspects).none()) {
|
if ((requiredAspects & ~mAspects).none()) {
|
||||||
// Fast return-true path if everything is good
|
// Fast return-true path if everything is good
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mAspects[VALIDATION_ASPECT_INDEX_BUFFER]) {
|
if (!mAspects[VALIDATION_ASPECT_INDEX_BUFFER]) {
|
||||||
mBuilder->HandleError("Cannot DrawElements without index buffer set");
|
NXT_RETURN_ERROR("Cannot DrawElements without index buffer set");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return RevalidateCanDraw();
|
return RevalidateCanDraw();
|
||||||
}
|
}
|
||||||
@ -87,15 +80,13 @@ namespace backend {
|
|||||||
mBindgroups.fill(nullptr);
|
mBindgroups.fill(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) {
|
void CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) {
|
||||||
SetPipelineCommon(pipeline);
|
SetPipelineCommon(pipeline);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
|
void CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
|
||||||
mLastRenderPipeline = pipeline;
|
mLastRenderPipeline = pipeline;
|
||||||
SetPipelineCommon(pipeline);
|
SetPipelineCommon(pipeline);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) {
|
void CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) {
|
||||||
@ -103,24 +94,22 @@ namespace backend {
|
|||||||
mBindgroups[index] = bindgroup;
|
mBindgroups[index] = bindgroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::SetIndexBuffer() {
|
MaybeError CommandBufferStateTracker::SetIndexBuffer() {
|
||||||
if (!HavePipeline()) {
|
if (!HavePipeline()) {
|
||||||
mBuilder->HandleError("Can't set the index buffer without a pipeline");
|
NXT_RETURN_ERROR("Can't set the index buffer without a pipeline");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER);
|
mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER);
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::SetVertexBuffer(uint32_t index) {
|
MaybeError CommandBufferStateTracker::SetVertexBuffer(uint32_t index) {
|
||||||
if (!HavePipeline()) {
|
if (!HavePipeline()) {
|
||||||
mBuilder->HandleError("Can't set vertex buffers without a pipeline");
|
NXT_RETURN_ERROR("Can't set vertex buffers without a pipeline");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mInputsSet.set(index);
|
mInputsSet.set(index);
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() {
|
bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() {
|
||||||
@ -161,21 +150,18 @@ namespace backend {
|
|||||||
return mAspects[VALIDATION_ASPECT_PIPELINE];
|
return mAspects[VALIDATION_ASPECT_PIPELINE];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandBufferStateTracker::RevalidateCanDraw() {
|
MaybeError CommandBufferStateTracker::RevalidateCanDraw() {
|
||||||
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
|
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
|
||||||
mBuilder->HandleError("No active render pipeline");
|
NXT_RETURN_ERROR("No active render pipeline");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// Compute the lazily computed mAspects
|
// Compute the lazily computed mAspects
|
||||||
if (!RecomputeHaveAspectBindGroups()) {
|
if (!RecomputeHaveAspectBindGroups()) {
|
||||||
mBuilder->HandleError("Bind group state not valid");
|
NXT_RETURN_ERROR("Bind group state not valid");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!RecomputeHaveAspectVertexBuffers()) {
|
if (!RecomputeHaveAspectVertexBuffers()) {
|
||||||
mBuilder->HandleError("Some vertex buffers are not set");
|
NXT_RETURN_ERROR("Some vertex buffers are not set");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
|
void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
|
||||||
|
@ -27,21 +27,19 @@ namespace backend {
|
|||||||
|
|
||||||
class CommandBufferStateTracker {
|
class CommandBufferStateTracker {
|
||||||
public:
|
public:
|
||||||
explicit CommandBufferStateTracker(CommandBufferBuilder* builder);
|
|
||||||
|
|
||||||
// Non-state-modifying validation functions
|
// Non-state-modifying validation functions
|
||||||
bool ValidateCanCopy() const;
|
MaybeError ValidateCanCopy() const;
|
||||||
bool ValidateCanDispatch();
|
MaybeError ValidateCanDispatch();
|
||||||
bool ValidateCanDrawArrays();
|
MaybeError ValidateCanDrawArrays();
|
||||||
bool ValidateCanDrawElements();
|
MaybeError ValidateCanDrawElements();
|
||||||
|
|
||||||
// State-modifying methods
|
// State-modifying methods
|
||||||
void EndPass();
|
void EndPass();
|
||||||
bool SetComputePipeline(ComputePipelineBase* pipeline);
|
void SetComputePipeline(ComputePipelineBase* pipeline);
|
||||||
bool SetRenderPipeline(RenderPipelineBase* pipeline);
|
void SetRenderPipeline(RenderPipelineBase* pipeline);
|
||||||
void SetBindGroup(uint32_t index, BindGroupBase* bindgroup);
|
void SetBindGroup(uint32_t index, BindGroupBase* bindgroup);
|
||||||
bool SetIndexBuffer();
|
MaybeError SetIndexBuffer();
|
||||||
bool SetVertexBuffer(uint32_t index);
|
MaybeError SetVertexBuffer(uint32_t index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum ValidationAspect {
|
enum ValidationAspect {
|
||||||
@ -59,12 +57,10 @@ namespace backend {
|
|||||||
bool RecomputeHaveAspectVertexBuffers();
|
bool RecomputeHaveAspectVertexBuffers();
|
||||||
|
|
||||||
bool HavePipeline() const;
|
bool HavePipeline() const;
|
||||||
bool RevalidateCanDraw();
|
MaybeError RevalidateCanDraw();
|
||||||
|
|
||||||
void SetPipelineCommon(PipelineBase* pipeline);
|
void SetPipelineCommon(PipelineBase* pipeline);
|
||||||
|
|
||||||
CommandBufferBuilder* mBuilder;
|
|
||||||
|
|
||||||
ValidationAspects mAspects;
|
ValidationAspects mAspects;
|
||||||
|
|
||||||
std::bitset<kMaxBindGroups> mBindgroupsSet;
|
std::bitset<kMaxBindGroups> mBindgroupsSet;
|
||||||
|
@ -28,9 +28,9 @@ namespace backend {
|
|||||||
return mDevice;
|
return mDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueueBase::ValidateSubmitCommand(CommandBufferBase*) {
|
MaybeError QueueBase::ValidateSubmitCommand(CommandBufferBase*) {
|
||||||
// TODO(cwallez@chromium.org): Validate resources referenced by command buffers can be used
|
// TODO(cwallez@chromium.org): Validate resources referenced by command buffers can be used
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace backend
|
} // namespace backend
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#define BACKEND_QUEUE_H_
|
#define BACKEND_QUEUE_H_
|
||||||
|
|
||||||
#include "backend/Builder.h"
|
#include "backend/Builder.h"
|
||||||
|
#include "backend/Error.h"
|
||||||
#include "backend/Forward.h"
|
#include "backend/Forward.h"
|
||||||
#include "backend/RefCounted.h"
|
#include "backend/RefCounted.h"
|
||||||
|
|
||||||
@ -30,20 +31,18 @@ namespace backend {
|
|||||||
DeviceBase* GetDevice();
|
DeviceBase* GetDevice();
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool ValidateSubmit(uint32_t numCommands, T* const* commands) {
|
MaybeError ValidateSubmit(uint32_t numCommands, T* const* commands) {
|
||||||
static_assert(std::is_base_of<CommandBufferBase, T>::value,
|
static_assert(std::is_base_of<CommandBufferBase, T>::value,
|
||||||
"invalid command buffer type");
|
"invalid command buffer type");
|
||||||
|
|
||||||
for (uint32_t i = 0; i < numCommands; ++i) {
|
for (uint32_t i = 0; i < numCommands; ++i) {
|
||||||
if (!ValidateSubmitCommand(commands[i])) {
|
NXT_TRY(ValidateSubmitCommand(commands[i]));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ValidateSubmitCommand(CommandBufferBase* command);
|
MaybeError ValidateSubmitCommand(CommandBufferBase* command);
|
||||||
|
|
||||||
DeviceBase* mDevice;
|
DeviceBase* mDevice;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user