mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-07-06 13:15:59 +00:00
Implement drawIndexedIndirect validation
Every render pass which invokes DrawIndexedIndirect, either directly or through a RenderBundle execution, is now preceded immediately by at least one validation pass. All indirect buffer offests used with DII are validated, and their validated values are copied into a separate scratch buffer (or zeroed out there, in the case of validation failure). All encoded DII commands are rewritten to use the validated parameters instead of the original ones. Bug: dawn:809 Change-Id: I5eead937f19536f84f89e2c8e6fed7f18f0aee9f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/63461 Commit-Queue: Ken Rockot <rockot@google.com> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
95cfd263ab
commit
ebf183bde4
@ -21,6 +21,9 @@ class NonCopyable {
|
|||||||
constexpr NonCopyable() = default;
|
constexpr NonCopyable() = default;
|
||||||
~NonCopyable() = default;
|
~NonCopyable() = default;
|
||||||
|
|
||||||
|
NonCopyable(NonCopyable&&) = default;
|
||||||
|
NonCopyable& operator=(NonCopyable&&) = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonCopyable(const NonCopyable&) = delete;
|
NonCopyable(const NonCopyable&) = delete;
|
||||||
void operator=(const NonCopyable&) = delete;
|
void operator=(const NonCopyable&) = delete;
|
||||||
|
@ -237,9 +237,14 @@ source_set("dawn_native_sources") {
|
|||||||
"Format.cpp",
|
"Format.cpp",
|
||||||
"Format.h",
|
"Format.h",
|
||||||
"Forward.h",
|
"Forward.h",
|
||||||
|
"IndirectDrawMetadata.cpp",
|
||||||
|
"IndirectDrawMetadata.h",
|
||||||
|
"IndirectDrawValidationEncoder.cpp",
|
||||||
|
"IndirectDrawValidationEncoder.h",
|
||||||
"Instance.cpp",
|
"Instance.cpp",
|
||||||
"Instance.h",
|
"Instance.h",
|
||||||
"IntegerTypes.h",
|
"IntegerTypes.h",
|
||||||
|
"InternalPipelineStore.cpp",
|
||||||
"InternalPipelineStore.h",
|
"InternalPipelineStore.h",
|
||||||
"Limits.cpp",
|
"Limits.cpp",
|
||||||
"Limits.h",
|
"Limits.h",
|
||||||
@ -286,6 +291,8 @@ source_set("dawn_native_sources") {
|
|||||||
"RingBufferAllocator.h",
|
"RingBufferAllocator.h",
|
||||||
"Sampler.cpp",
|
"Sampler.cpp",
|
||||||
"Sampler.h",
|
"Sampler.h",
|
||||||
|
"ScratchBuffer.cpp",
|
||||||
|
"ScratchBuffer.h",
|
||||||
"ShaderModule.cpp",
|
"ShaderModule.cpp",
|
||||||
"ShaderModule.h",
|
"ShaderModule.h",
|
||||||
"StagingBuffer.cpp",
|
"StagingBuffer.cpp",
|
||||||
|
@ -147,6 +147,12 @@ namespace dawn_native {
|
|||||||
if (mUsage & wgpu::BufferUsage::QueryResolve) {
|
if (mUsage & wgpu::BufferUsage::QueryResolve) {
|
||||||
mUsage |= kInternalStorageBuffer;
|
mUsage |= kInternalStorageBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We also add internal storage usage for Indirect buffers if validation is enabled, since
|
||||||
|
// validation involves binding them as storage buffers for use in a compute pass.
|
||||||
|
if ((mUsage & wgpu::BufferUsage::Indirect) && device->IsValidationEnabled()) {
|
||||||
|
mUsage |= kInternalStorageBuffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferBase::BufferBase(DeviceBase* device,
|
BufferBase::BufferBase(DeviceBase* device,
|
||||||
|
@ -98,6 +98,10 @@ target_sources(dawn_native PRIVATE
|
|||||||
"Extensions.h"
|
"Extensions.h"
|
||||||
"ExternalTexture.cpp"
|
"ExternalTexture.cpp"
|
||||||
"ExternalTexture.h"
|
"ExternalTexture.h"
|
||||||
|
"IndirectDrawMetadata.cpp"
|
||||||
|
"IndirectDrawMetadata.h"
|
||||||
|
"IndirectDrawValidationEncoder.cpp"
|
||||||
|
"IndirectDrawValidationEncoder.h"
|
||||||
"ObjectContentHasher.cpp"
|
"ObjectContentHasher.cpp"
|
||||||
"ObjectContentHasher.h"
|
"ObjectContentHasher.h"
|
||||||
"Format.cpp"
|
"Format.cpp"
|
||||||
@ -105,6 +109,7 @@ target_sources(dawn_native PRIVATE
|
|||||||
"Forward.h"
|
"Forward.h"
|
||||||
"Instance.cpp"
|
"Instance.cpp"
|
||||||
"Instance.h"
|
"Instance.h"
|
||||||
|
"InternalPipelineStore.cpp"
|
||||||
"InternalPipelineStore.h"
|
"InternalPipelineStore.h"
|
||||||
"IntegerTypes.h"
|
"IntegerTypes.h"
|
||||||
"Limits.cpp"
|
"Limits.cpp"
|
||||||
@ -150,6 +155,8 @@ target_sources(dawn_native PRIVATE
|
|||||||
"RingBufferAllocator.h"
|
"RingBufferAllocator.h"
|
||||||
"Sampler.cpp"
|
"Sampler.cpp"
|
||||||
"Sampler.h"
|
"Sampler.h"
|
||||||
|
"ScratchBuffer.cpp"
|
||||||
|
"ScratchBuffer.h"
|
||||||
"ShaderModule.cpp"
|
"ShaderModule.cpp"
|
||||||
"ShaderModule.h"
|
"ShaderModule.h"
|
||||||
"StagingBuffer.cpp"
|
"StagingBuffer.cpp"
|
||||||
|
@ -38,6 +38,14 @@ namespace dawn_native {
|
|||||||
Destroy();
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandBufferBase::DoNextSetValidatedBufferLocationsInternal() {
|
||||||
|
SetValidatedBufferLocationsInternalCmd* cmd =
|
||||||
|
mCommands.NextCommand<SetValidatedBufferLocationsInternalCmd>();
|
||||||
|
for (const DeferredBufferLocationUpdate& update : cmd->updates) {
|
||||||
|
update.location->Set(update.buffer.Get(), update.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
CommandBufferBase* CommandBufferBase::MakeError(DeviceBase* device) {
|
CommandBufferBase* CommandBufferBase::MakeError(DeviceBase* device) {
|
||||||
return new CommandBufferBase(device, ObjectBase::kError);
|
return new CommandBufferBase(device, ObjectBase::kError);
|
||||||
|
@ -44,6 +44,8 @@ namespace dawn_native {
|
|||||||
protected:
|
protected:
|
||||||
~CommandBufferBase();
|
~CommandBufferBase();
|
||||||
|
|
||||||
|
void DoNextSetValidatedBufferLocationsInternal();
|
||||||
|
|
||||||
CommandIterator mCommands;
|
CommandIterator mCommands;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -308,4 +308,13 @@ namespace dawn_native {
|
|||||||
PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const {
|
PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const {
|
||||||
return mLastPipelineLayout;
|
return mLastPipelineLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wgpu::IndexFormat CommandBufferStateTracker::GetIndexFormat() const {
|
||||||
|
return mIndexFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t CommandBufferStateTracker::GetIndexBufferSize() const {
|
||||||
|
return mIndexBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dawn_native
|
} // namespace dawn_native
|
||||||
|
@ -47,6 +47,8 @@ namespace dawn_native {
|
|||||||
|
|
||||||
BindGroupBase* GetBindGroup(BindGroupIndex index) const;
|
BindGroupBase* GetBindGroup(BindGroupIndex index) const;
|
||||||
PipelineLayoutBase* GetPipelineLayout() const;
|
PipelineLayoutBase* GetPipelineLayout() const;
|
||||||
|
wgpu::IndexFormat GetIndexFormat() const;
|
||||||
|
uint64_t GetIndexBufferSize() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MaybeError ValidateOperation(ValidationAspects requiredAspects);
|
MaybeError ValidateOperation(ValidationAspects requiredAspects);
|
||||||
|
@ -508,6 +508,7 @@ namespace dawn_native {
|
|||||||
uint32_t width = 0;
|
uint32_t width = 0;
|
||||||
uint32_t height = 0;
|
uint32_t height = 0;
|
||||||
Ref<AttachmentState> attachmentState;
|
Ref<AttachmentState> attachmentState;
|
||||||
|
mEncodingContext.WillBeginRenderPass();
|
||||||
bool success =
|
bool success =
|
||||||
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
|
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
|
||||||
uint32_t sampleCount = 0;
|
uint32_t sampleCount = 0;
|
||||||
@ -922,6 +923,18 @@ namespace dawn_native {
|
|||||||
return commandBuffer.Detach();
|
return commandBuffer.Detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandEncoder::EncodeSetValidatedBufferLocationsInternal(
|
||||||
|
std::vector<DeferredBufferLocationUpdate> updates) {
|
||||||
|
ASSERT(GetDevice()->IsValidationEnabled());
|
||||||
|
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
|
||||||
|
SetValidatedBufferLocationsInternalCmd* cmd =
|
||||||
|
allocator->Allocate<SetValidatedBufferLocationsInternalCmd>(
|
||||||
|
Command::SetValidatedBufferLocationsInternal);
|
||||||
|
cmd->updates = std::move(updates);
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ResultOrError<Ref<CommandBufferBase>> CommandEncoder::FinishInternal(
|
ResultOrError<Ref<CommandBufferBase>> CommandEncoder::FinishInternal(
|
||||||
const CommandBufferDescriptor* descriptor) {
|
const CommandBufferDescriptor* descriptor) {
|
||||||
DeviceBase* device = GetDevice();
|
DeviceBase* device = GetDevice();
|
||||||
|
@ -76,6 +76,9 @@ namespace dawn_native {
|
|||||||
|
|
||||||
CommandBufferBase* APIFinish(const CommandBufferDescriptor* descriptor = nullptr);
|
CommandBufferBase* APIFinish(const CommandBufferDescriptor* descriptor = nullptr);
|
||||||
|
|
||||||
|
void EncodeSetValidatedBufferLocationsInternal(
|
||||||
|
std::vector<DeferredBufferLocationUpdate> updates);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ResultOrError<Ref<CommandBufferBase>> FinishInternal(
|
ResultOrError<Ref<CommandBufferBase>> FinishInternal(
|
||||||
const CommandBufferDescriptor* descriptor);
|
const CommandBufferDescriptor* descriptor);
|
||||||
|
@ -158,6 +158,12 @@ namespace dawn_native {
|
|||||||
cmd->~SetStencilReferenceCmd();
|
cmd->~SetStencilReferenceCmd();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Command::SetValidatedBufferLocationsInternal: {
|
||||||
|
SetValidatedBufferLocationsInternalCmd* cmd =
|
||||||
|
commands->NextCommand<SetValidatedBufferLocationsInternalCmd>();
|
||||||
|
cmd->~SetValidatedBufferLocationsInternalCmd();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Command::SetViewport: {
|
case Command::SetViewport: {
|
||||||
SetViewportCmd* cmd = commands->NextCommand<SetViewportCmd>();
|
SetViewportCmd* cmd = commands->NextCommand<SetViewportCmd>();
|
||||||
cmd->~SetViewportCmd();
|
cmd->~SetViewportCmd();
|
||||||
@ -313,6 +319,10 @@ namespace dawn_native {
|
|||||||
commands->NextCommand<SetStencilReferenceCmd>();
|
commands->NextCommand<SetStencilReferenceCmd>();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Command::SetValidatedBufferLocationsInternal:
|
||||||
|
commands->NextCommand<SetValidatedBufferLocationsInternalCmd>();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::SetViewport:
|
case Command::SetViewport:
|
||||||
commands->NextCommand<SetViewportCmd>();
|
commands->NextCommand<SetViewportCmd>();
|
||||||
break;
|
break;
|
||||||
|
@ -63,6 +63,7 @@ namespace dawn_native {
|
|||||||
SetBlendConstant,
|
SetBlendConstant,
|
||||||
SetBindGroup,
|
SetBindGroup,
|
||||||
SetIndexBuffer,
|
SetIndexBuffer,
|
||||||
|
SetValidatedBufferLocationsInternal,
|
||||||
SetVertexBuffer,
|
SetVertexBuffer,
|
||||||
WriteBuffer,
|
WriteBuffer,
|
||||||
WriteTimestamp,
|
WriteTimestamp,
|
||||||
@ -224,6 +225,16 @@ namespace dawn_native {
|
|||||||
uint32_t reference;
|
uint32_t reference;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DeferredBufferLocationUpdate {
|
||||||
|
Ref<BufferLocation> location;
|
||||||
|
Ref<BufferBase> buffer;
|
||||||
|
uint64_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetValidatedBufferLocationsInternalCmd {
|
||||||
|
std::vector<DeferredBufferLocationUpdate> updates;
|
||||||
|
};
|
||||||
|
|
||||||
struct SetViewportCmd {
|
struct SetViewportCmd {
|
||||||
float x, y, width, height, minDepth, maxDepth;
|
float x, y, width, height, minDepth, maxDepth;
|
||||||
};
|
};
|
||||||
|
@ -67,7 +67,7 @@ namespace dawn_native {
|
|||||||
|
|
||||||
return {};
|
return {};
|
||||||
})) {
|
})) {
|
||||||
mEncodingContext->ExitPass(this, mUsageTracker.AcquireResourceUsage());
|
mEncodingContext->ExitComputePass(this, mUsageTracker.AcquireResourceUsage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ namespace dawn_native {
|
|||||||
mDynamicUploader = std::make_unique<DynamicUploader>(this);
|
mDynamicUploader = std::make_unique<DynamicUploader>(this);
|
||||||
mCallbackTaskManager = std::make_unique<CallbackTaskManager>();
|
mCallbackTaskManager = std::make_unique<CallbackTaskManager>();
|
||||||
mDeprecationWarnings = std::make_unique<DeprecationWarnings>();
|
mDeprecationWarnings = std::make_unique<DeprecationWarnings>();
|
||||||
mInternalPipelineStore = std::make_unique<InternalPipelineStore>();
|
mInternalPipelineStore = std::make_unique<InternalPipelineStore>(this);
|
||||||
mPersistentCache = std::make_unique<PersistentCache>(this);
|
mPersistentCache = std::make_unique<PersistentCache>(this);
|
||||||
|
|
||||||
ASSERT(GetPlatform() != nullptr);
|
ASSERT(GetPlatform() != nullptr);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "dawn_native/Commands.h"
|
#include "dawn_native/Commands.h"
|
||||||
#include "dawn_native/Device.h"
|
#include "dawn_native/Device.h"
|
||||||
#include "dawn_native/ErrorData.h"
|
#include "dawn_native/ErrorData.h"
|
||||||
|
#include "dawn_native/IndirectDrawValidationEncoder.h"
|
||||||
#include "dawn_native/RenderBundleEncoder.h"
|
#include "dawn_native/RenderBundleEncoder.h"
|
||||||
|
|
||||||
namespace dawn_native {
|
namespace dawn_native {
|
||||||
@ -47,8 +48,9 @@ namespace dawn_native {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EncodingContext::MoveToIterator() {
|
void EncodingContext::MoveToIterator() {
|
||||||
|
CommitCommands(std::move(mPendingCommands));
|
||||||
if (!mWasMovedToIterator) {
|
if (!mWasMovedToIterator) {
|
||||||
mIterator = CommandIterator(std::move(mAllocator));
|
mIterator.AcquireCommandBlocks(std::move(mAllocators));
|
||||||
mWasMovedToIterator = true;
|
mWasMovedToIterator = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,6 +69,18 @@ namespace dawn_native {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EncodingContext::WillBeginRenderPass() {
|
||||||
|
ASSERT(mCurrentEncoder == mTopLevelEncoder);
|
||||||
|
if (mDevice->IsValidationEnabled()) {
|
||||||
|
// When validation is enabled, we are going to want to capture all commands encoded
|
||||||
|
// between and including BeginRenderPassCmd and EndRenderPassCmd, and defer their
|
||||||
|
// sequencing util after we have a chance to insert any necessary validation
|
||||||
|
// commands. To support this we commit any current commands now, so that the
|
||||||
|
// impending BeginRenderPassCmd starts in a fresh CommandAllocator.
|
||||||
|
CommitCommands(std::move(mPendingCommands));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EncodingContext::EnterPass(const ObjectBase* passEncoder) {
|
void EncodingContext::EnterPass(const ObjectBase* passEncoder) {
|
||||||
// Assert we're at the top level.
|
// Assert we're at the top level.
|
||||||
ASSERT(mCurrentEncoder == mTopLevelEncoder);
|
ASSERT(mCurrentEncoder == mTopLevelEncoder);
|
||||||
@ -75,15 +89,34 @@ namespace dawn_native {
|
|||||||
mCurrentEncoder = passEncoder;
|
mCurrentEncoder = passEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncodingContext::ExitPass(const ObjectBase* passEncoder, RenderPassResourceUsage usages) {
|
MaybeError EncodingContext::ExitRenderPass(const ObjectBase* passEncoder,
|
||||||
|
RenderPassResourceUsageTracker usageTracker,
|
||||||
|
CommandEncoder* commandEncoder,
|
||||||
|
IndirectDrawMetadata indirectDrawMetadata) {
|
||||||
ASSERT(mCurrentEncoder != mTopLevelEncoder);
|
ASSERT(mCurrentEncoder != mTopLevelEncoder);
|
||||||
ASSERT(mCurrentEncoder == passEncoder);
|
ASSERT(mCurrentEncoder == passEncoder);
|
||||||
|
|
||||||
mCurrentEncoder = mTopLevelEncoder;
|
mCurrentEncoder = mTopLevelEncoder;
|
||||||
mRenderPassUsages.push_back(std::move(usages));
|
|
||||||
|
if (mDevice->IsValidationEnabled()) {
|
||||||
|
// With validation enabled, commands were committed just before BeginRenderPassCmd was
|
||||||
|
// encoded by our RenderPassEncoder (see WillBeginRenderPass above). This means
|
||||||
|
// mPendingCommands contains only the commands from BeginRenderPassCmd to
|
||||||
|
// EndRenderPassCmd, inclusive. Now we swap out this allocator with a fresh one to give
|
||||||
|
// the validation encoder a chance to insert its commands first.
|
||||||
|
CommandAllocator renderCommands = std::move(mPendingCommands);
|
||||||
|
DAWN_TRY(EncodeIndirectDrawValidationCommands(mDevice, commandEncoder, &usageTracker,
|
||||||
|
&indirectDrawMetadata));
|
||||||
|
CommitCommands(std::move(mPendingCommands));
|
||||||
|
CommitCommands(std::move(renderCommands));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncodingContext::ExitPass(const ObjectBase* passEncoder, ComputePassResourceUsage usages) {
|
mRenderPassUsages.push_back(usageTracker.AcquireResourceUsage());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodingContext::ExitComputePass(const ObjectBase* passEncoder,
|
||||||
|
ComputePassResourceUsage usages) {
|
||||||
ASSERT(mCurrentEncoder != mTopLevelEncoder);
|
ASSERT(mCurrentEncoder != mTopLevelEncoder);
|
||||||
ASSERT(mCurrentEncoder == passEncoder);
|
ASSERT(mCurrentEncoder == passEncoder);
|
||||||
|
|
||||||
@ -126,6 +159,7 @@ namespace dawn_native {
|
|||||||
// if Finish() has been called.
|
// if Finish() has been called.
|
||||||
mCurrentEncoder = nullptr;
|
mCurrentEncoder = nullptr;
|
||||||
mTopLevelEncoder = nullptr;
|
mTopLevelEncoder = nullptr;
|
||||||
|
CommitCommands(std::move(mPendingCommands));
|
||||||
|
|
||||||
if (mError != nullptr) {
|
if (mError != nullptr) {
|
||||||
return std::move(mError);
|
return std::move(mError);
|
||||||
@ -136,6 +170,12 @@ namespace dawn_native {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EncodingContext::CommitCommands(CommandAllocator allocator) {
|
||||||
|
if (!allocator.IsEmpty()) {
|
||||||
|
mAllocators.push_back(std::move(allocator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool EncodingContext::IsFinished() const {
|
bool EncodingContext::IsFinished() const {
|
||||||
return mTopLevelEncoder == nullptr;
|
return mTopLevelEncoder == nullptr;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "dawn_native/CommandAllocator.h"
|
#include "dawn_native/CommandAllocator.h"
|
||||||
#include "dawn_native/Error.h"
|
#include "dawn_native/Error.h"
|
||||||
#include "dawn_native/ErrorData.h"
|
#include "dawn_native/ErrorData.h"
|
||||||
|
#include "dawn_native/IndirectDrawMetadata.h"
|
||||||
#include "dawn_native/PassResourceUsageTracker.h"
|
#include "dawn_native/PassResourceUsageTracker.h"
|
||||||
#include "dawn_native/dawn_platform.h"
|
#include "dawn_native/dawn_platform.h"
|
||||||
|
|
||||||
@ -25,6 +26,7 @@
|
|||||||
|
|
||||||
namespace dawn_native {
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class CommandEncoder;
|
||||||
class DeviceBase;
|
class DeviceBase;
|
||||||
class ObjectBase;
|
class ObjectBase;
|
||||||
|
|
||||||
@ -69,13 +71,21 @@ namespace dawn_native {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ASSERT(!mWasMovedToIterator);
|
ASSERT(!mWasMovedToIterator);
|
||||||
return !ConsumedError(encodeFunction(&mAllocator));
|
return !ConsumedError(encodeFunction(&mPendingCommands));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be called prior to encoding a BeginRenderPassCmd. Note that it's OK to call this
|
||||||
|
// and then not actually call EnterPass+ExitRenderPass, for example if some other pass setup
|
||||||
|
// failed validation before the BeginRenderPassCmd could be encoded.
|
||||||
|
void WillBeginRenderPass();
|
||||||
|
|
||||||
// Functions to set current encoder state
|
// Functions to set current encoder state
|
||||||
void EnterPass(const ObjectBase* passEncoder);
|
void EnterPass(const ObjectBase* passEncoder);
|
||||||
void ExitPass(const ObjectBase* passEncoder, RenderPassResourceUsage usages);
|
MaybeError ExitRenderPass(const ObjectBase* passEncoder,
|
||||||
void ExitPass(const ObjectBase* passEncoder, ComputePassResourceUsage usages);
|
RenderPassResourceUsageTracker usageTracker,
|
||||||
|
CommandEncoder* commandEncoder,
|
||||||
|
IndirectDrawMetadata indirectDrawMetadata);
|
||||||
|
void ExitComputePass(const ObjectBase* passEncoder, ComputePassResourceUsage usages);
|
||||||
MaybeError Finish();
|
MaybeError Finish();
|
||||||
|
|
||||||
const RenderPassUsages& GetRenderPassUsages() const;
|
const RenderPassUsages& GetRenderPassUsages() const;
|
||||||
@ -84,6 +94,8 @@ namespace dawn_native {
|
|||||||
ComputePassUsages AcquireComputePassUsages();
|
ComputePassUsages AcquireComputePassUsages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CommitCommands(CommandAllocator allocator);
|
||||||
|
|
||||||
bool IsFinished() const;
|
bool IsFinished() const;
|
||||||
void MoveToIterator();
|
void MoveToIterator();
|
||||||
|
|
||||||
@ -104,7 +116,9 @@ namespace dawn_native {
|
|||||||
ComputePassUsages mComputePassUsages;
|
ComputePassUsages mComputePassUsages;
|
||||||
bool mWereComputePassUsagesAcquired = false;
|
bool mWereComputePassUsagesAcquired = false;
|
||||||
|
|
||||||
CommandAllocator mAllocator;
|
CommandAllocator mPendingCommands;
|
||||||
|
|
||||||
|
std::vector<CommandAllocator> mAllocators;
|
||||||
CommandIterator mIterator;
|
CommandIterator mIterator;
|
||||||
bool mWasMovedToIterator = false;
|
bool mWasMovedToIterator = false;
|
||||||
bool mWereCommandsAcquired = false;
|
bool mWereCommandsAcquired = false;
|
||||||
|
193
src/dawn_native/IndirectDrawMetadata.cpp
Normal file
193
src/dawn_native/IndirectDrawMetadata.cpp
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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/IndirectDrawMetadata.h"
|
||||||
|
|
||||||
|
#include "common/Constants.h"
|
||||||
|
#include "common/RefCounted.h"
|
||||||
|
#include "dawn_native/IndirectDrawValidationEncoder.h"
|
||||||
|
#include "dawn_native/RenderBundle.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// In the unlikely scenario that indirect offsets used over a single buffer span more than
|
||||||
|
// this length of the buffer, we split the validation work into multiple batches.
|
||||||
|
constexpr uint64_t kMaxBatchOffsetRange = kMaxStorageBufferBindingSize -
|
||||||
|
kMinStorageBufferOffsetAlignment -
|
||||||
|
kDrawIndexedIndirectSize;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::IndexedIndirectBufferValidationInfo(
|
||||||
|
BufferBase* indirectBuffer)
|
||||||
|
: mIndirectBuffer(indirectBuffer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddIndexedIndirectDraw(
|
||||||
|
IndexedIndirectDraw draw) {
|
||||||
|
const uint64_t newOffset = draw.clientBufferOffset;
|
||||||
|
auto it = mBatches.begin();
|
||||||
|
while (it != mBatches.end()) {
|
||||||
|
IndexedIndirectValidationBatch& batch = *it;
|
||||||
|
if (batch.draws.size() >= kMaxDrawCallsPerIndirectValidationBatch) {
|
||||||
|
// This batch is full. If its minOffset is to the right of the new offset, we can
|
||||||
|
// just insert a new batch here.
|
||||||
|
if (newOffset < batch.minOffset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise keep looking.
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOffset >= batch.minOffset && newOffset <= batch.maxOffset) {
|
||||||
|
batch.draws.push_back(std::move(draw));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOffset < batch.minOffset &&
|
||||||
|
batch.maxOffset - newOffset <= kMaxBatchOffsetRange) {
|
||||||
|
// We can extend this batch to the left in order to fit the new offset.
|
||||||
|
batch.minOffset = newOffset;
|
||||||
|
batch.draws.push_back(std::move(draw));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOffset > batch.maxOffset &&
|
||||||
|
newOffset - batch.minOffset <= kMaxBatchOffsetRange) {
|
||||||
|
// We can extend this batch to the right in order to fit the new offset.
|
||||||
|
batch.maxOffset = newOffset;
|
||||||
|
batch.draws.push_back(std::move(draw));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOffset < batch.minOffset) {
|
||||||
|
// We want to insert a new batch just before this one.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedIndirectValidationBatch newBatch;
|
||||||
|
newBatch.minOffset = newOffset;
|
||||||
|
newBatch.maxOffset = newOffset;
|
||||||
|
newBatch.draws.push_back(std::move(draw));
|
||||||
|
|
||||||
|
mBatches.insert(it, std::move(newBatch));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddBatch(
|
||||||
|
const IndexedIndirectValidationBatch& newBatch) {
|
||||||
|
auto it = mBatches.begin();
|
||||||
|
while (it != mBatches.end()) {
|
||||||
|
IndexedIndirectValidationBatch& batch = *it;
|
||||||
|
uint64_t min = std::min(newBatch.minOffset, batch.minOffset);
|
||||||
|
uint64_t max = std::max(newBatch.maxOffset, batch.maxOffset);
|
||||||
|
if (max - min <= kMaxBatchOffsetRange && batch.draws.size() + newBatch.draws.size() <=
|
||||||
|
kMaxDrawCallsPerIndirectValidationBatch) {
|
||||||
|
// This batch fits within the limits of an existing batch. Merge it.
|
||||||
|
batch.minOffset = min;
|
||||||
|
batch.maxOffset = max;
|
||||||
|
batch.draws.insert(batch.draws.end(), newBatch.draws.begin(), newBatch.draws.end());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBatch.minOffset < batch.minOffset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
mBatches.push_back(newBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<IndirectDrawMetadata::IndexedIndirectValidationBatch>&
|
||||||
|
IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::GetBatches() const {
|
||||||
|
return mBatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndirectDrawMetadata::IndirectDrawMetadata() = default;
|
||||||
|
|
||||||
|
IndirectDrawMetadata::~IndirectDrawMetadata() = default;
|
||||||
|
|
||||||
|
IndirectDrawMetadata::IndirectDrawMetadata(IndirectDrawMetadata&&) = default;
|
||||||
|
|
||||||
|
IndirectDrawMetadata& IndirectDrawMetadata::operator=(IndirectDrawMetadata&&) = default;
|
||||||
|
|
||||||
|
IndirectDrawMetadata::IndexedIndirectBufferValidationInfoMap*
|
||||||
|
IndirectDrawMetadata::GetIndexedIndirectBufferValidationInfo() {
|
||||||
|
return &mIndexedIndirectBufferValidationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawMetadata::AddBundle(RenderBundleBase* bundle) {
|
||||||
|
auto result = mAddedBundles.insert(bundle);
|
||||||
|
if (!result.second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry :
|
||||||
|
bundle->GetIndirectDrawMetadata().mIndexedIndirectBufferValidationInfo) {
|
||||||
|
const IndexedIndirectConfig& config = entry.first;
|
||||||
|
auto it = mIndexedIndirectBufferValidationInfo.lower_bound(config);
|
||||||
|
if (it != mIndexedIndirectBufferValidationInfo.end() && it->first == config) {
|
||||||
|
// We already have batches for the same config. Merge the new ones in.
|
||||||
|
for (const IndexedIndirectValidationBatch& batch : entry.second.GetBatches()) {
|
||||||
|
it->second.AddBatch(batch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mIndexedIndirectBufferValidationInfo.emplace_hint(it, config, entry.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawMetadata::AddIndexedIndirectDraw(
|
||||||
|
wgpu::IndexFormat indexFormat,
|
||||||
|
uint64_t indexBufferSize,
|
||||||
|
BufferBase* indirectBuffer,
|
||||||
|
uint64_t indirectOffset,
|
||||||
|
BufferLocation* drawCmdIndirectBufferLocation) {
|
||||||
|
uint64_t numIndexBufferElements;
|
||||||
|
switch (indexFormat) {
|
||||||
|
case wgpu::IndexFormat::Uint16:
|
||||||
|
numIndexBufferElements = indexBufferSize / 2;
|
||||||
|
break;
|
||||||
|
case wgpu::IndexFormat::Uint32:
|
||||||
|
numIndexBufferElements = indexBufferSize / 4;
|
||||||
|
break;
|
||||||
|
case wgpu::IndexFormat::Undefined:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
const IndexedIndirectConfig config(indirectBuffer, numIndexBufferElements);
|
||||||
|
auto it = mIndexedIndirectBufferValidationInfo.find(config);
|
||||||
|
if (it == mIndexedIndirectBufferValidationInfo.end()) {
|
||||||
|
auto result = mIndexedIndirectBufferValidationInfo.emplace(
|
||||||
|
config, IndexedIndirectBufferValidationInfo(indirectBuffer));
|
||||||
|
it = result.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedIndirectDraw draw;
|
||||||
|
draw.clientBufferOffset = indirectOffset;
|
||||||
|
draw.bufferLocation = drawCmdIndirectBufferLocation;
|
||||||
|
it->second.AddIndexedIndirectDraw(std::move(draw));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
112
src/dawn_native/IndirectDrawMetadata.h
Normal file
112
src/dawn_native/IndirectDrawMetadata.h
Normal 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.
|
||||||
|
|
||||||
|
#ifndef DAWNNATIVE_INDIRECTDRAWMETADATA_H_
|
||||||
|
#define DAWNNATIVE_INDIRECTDRAWMETADATA_H_
|
||||||
|
|
||||||
|
#include "common/NonCopyable.h"
|
||||||
|
#include "common/RefCounted.h"
|
||||||
|
#include "dawn_native/Buffer.h"
|
||||||
|
#include "dawn_native/BufferLocation.h"
|
||||||
|
#include "dawn_native/CommandBufferStateTracker.h"
|
||||||
|
#include "dawn_native/Commands.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class RenderBundleBase;
|
||||||
|
|
||||||
|
// Metadata corresponding to the validation requirements of a single render pass. This metadata
|
||||||
|
// is accumulated while its corresponding render pass is encoded, and is later used to encode
|
||||||
|
// validation commands to be inserted into the command buffer just before the render pass's own
|
||||||
|
// commands.
|
||||||
|
class IndirectDrawMetadata : public NonCopyable {
|
||||||
|
public:
|
||||||
|
struct IndexedIndirectDraw {
|
||||||
|
uint64_t clientBufferOffset;
|
||||||
|
Ref<BufferLocation> bufferLocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndexedIndirectValidationBatch {
|
||||||
|
uint64_t minOffset;
|
||||||
|
uint64_t maxOffset;
|
||||||
|
std::vector<IndexedIndirectDraw> draws;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tracks information about every draw call in this render pass which uses the same indirect
|
||||||
|
// buffer and the same-sized index buffer. Calls are grouped by indirect offset ranges so
|
||||||
|
// that validation work can be chunked efficiently if necessary.
|
||||||
|
class IndexedIndirectBufferValidationInfo {
|
||||||
|
public:
|
||||||
|
explicit IndexedIndirectBufferValidationInfo(BufferBase* indirectBuffer);
|
||||||
|
|
||||||
|
// Logs a new drawIndexedIndirect call for the render pass. `cmd` is updated with an
|
||||||
|
// assigned (and deferred) buffer ref and relative offset before returning.
|
||||||
|
void AddIndexedIndirectDraw(IndexedIndirectDraw draw);
|
||||||
|
|
||||||
|
// Adds draw calls from an already-computed batch, e.g. from a previously encoded
|
||||||
|
// RenderBundle. The added batch is merged into an existing batch if possible, otherwise
|
||||||
|
// it's added to mBatch.
|
||||||
|
void AddBatch(const IndexedIndirectValidationBatch& batch);
|
||||||
|
|
||||||
|
const std::vector<IndexedIndirectValidationBatch>& GetBatches() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ref<BufferBase> mIndirectBuffer;
|
||||||
|
|
||||||
|
// A list of information about validation batches that will need to be executed for the
|
||||||
|
// corresponding indirect buffer prior to a single render pass. These are kept sorted by
|
||||||
|
// minOffset and may overlap iff the number of offsets in one batch would otherwise
|
||||||
|
// exceed some large upper bound (roughly ~33M draw calls).
|
||||||
|
//
|
||||||
|
// Since the most common expected cases will overwhelmingly require only a single
|
||||||
|
// validation pass per render pass, this is optimized for efficient updates to a single
|
||||||
|
// batch rather than for efficient manipulation of a large number of batches.
|
||||||
|
std::vector<IndexedIndirectValidationBatch> mBatches;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combination of an indirect buffer reference, and the number of addressable index buffer
|
||||||
|
// elements at the time of a draw call.
|
||||||
|
using IndexedIndirectConfig = std::pair<BufferBase*, uint64_t>;
|
||||||
|
using IndexedIndirectBufferValidationInfoMap =
|
||||||
|
std::map<IndexedIndirectConfig, IndexedIndirectBufferValidationInfo>;
|
||||||
|
|
||||||
|
IndirectDrawMetadata();
|
||||||
|
~IndirectDrawMetadata();
|
||||||
|
|
||||||
|
IndirectDrawMetadata(IndirectDrawMetadata&&);
|
||||||
|
IndirectDrawMetadata& operator=(IndirectDrawMetadata&&);
|
||||||
|
|
||||||
|
IndexedIndirectBufferValidationInfoMap* GetIndexedIndirectBufferValidationInfo();
|
||||||
|
|
||||||
|
void AddBundle(RenderBundleBase* bundle);
|
||||||
|
void AddIndexedIndirectDraw(wgpu::IndexFormat indexFormat,
|
||||||
|
uint64_t indexBufferSize,
|
||||||
|
BufferBase* indirectBuffer,
|
||||||
|
uint64_t indirectOffset,
|
||||||
|
BufferLocation* drawCmdIndirectBufferLocation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IndexedIndirectBufferValidationInfoMap mIndexedIndirectBufferValidationInfo;
|
||||||
|
std::set<RenderBundleBase*> mAddedBundles;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
||||||
|
|
||||||
|
#endif // DAWNNATIVE_INDIRECTDRAWMETADATA_H_
|
397
src/dawn_native/IndirectDrawValidationEncoder.cpp
Normal file
397
src/dawn_native/IndirectDrawValidationEncoder.cpp
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
// 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/IndirectDrawValidationEncoder.h"
|
||||||
|
|
||||||
|
#include "common/Constants.h"
|
||||||
|
#include "common/Math.h"
|
||||||
|
#include "dawn_native/BindGroup.h"
|
||||||
|
#include "dawn_native/BindGroupLayout.h"
|
||||||
|
#include "dawn_native/CommandEncoder.h"
|
||||||
|
#include "dawn_native/ComputePassEncoder.h"
|
||||||
|
#include "dawn_native/ComputePipeline.h"
|
||||||
|
#include "dawn_native/Device.h"
|
||||||
|
#include "dawn_native/InternalPipelineStore.h"
|
||||||
|
#include "dawn_native/Queue.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// NOTE: This must match the workgroup_size attribute on the compute entry point below.
|
||||||
|
constexpr uint64_t kWorkgroupSize = 64;
|
||||||
|
|
||||||
|
// Equivalent to the BatchInfo struct defined in the shader below.
|
||||||
|
struct BatchInfo {
|
||||||
|
uint64_t numIndexBufferElements;
|
||||||
|
uint32_t numDraws;
|
||||||
|
uint32_t padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(https://crbug.com/dawn/1108): Propagate validation feedback from this shader in
|
||||||
|
// various failure modes.
|
||||||
|
static const char sRenderValidationShaderSource[] = R"(
|
||||||
|
let kNumIndirectParamsPerDrawCall = 5u;
|
||||||
|
|
||||||
|
let kIndexCountEntry = 0u;
|
||||||
|
let kInstanceCountEntry = 1u;
|
||||||
|
let kFirstIndexEntry = 2u;
|
||||||
|
let kBaseVertexEntry = 3u;
|
||||||
|
let kFirstInstanceEntry = 4u;
|
||||||
|
|
||||||
|
[[block]] struct BatchInfo {
|
||||||
|
numIndexBufferElementsLow: u32;
|
||||||
|
numIndexBufferElementsHigh: u32;
|
||||||
|
numDraws: u32;
|
||||||
|
padding: u32;
|
||||||
|
indirectOffsets: array<u32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]] struct IndirectParams {
|
||||||
|
data: array<u32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> batch: BatchInfo;
|
||||||
|
[[group(0), binding(1)]] var<storage, read_write> clientParams: IndirectParams;
|
||||||
|
[[group(0), binding(2)]] var<storage, write> validatedParams: IndirectParams;
|
||||||
|
|
||||||
|
fn fail(drawIndex: u32) {
|
||||||
|
let index = drawIndex * kNumIndirectParamsPerDrawCall;
|
||||||
|
validatedParams.data[index + kIndexCountEntry] = 0u;
|
||||||
|
validatedParams.data[index + kInstanceCountEntry] = 0u;
|
||||||
|
validatedParams.data[index + kFirstIndexEntry] = 0u;
|
||||||
|
validatedParams.data[index + kBaseVertexEntry] = 0u;
|
||||||
|
validatedParams.data[index + kFirstInstanceEntry] = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pass(drawIndex: u32) {
|
||||||
|
let vIndex = drawIndex * kNumIndirectParamsPerDrawCall;
|
||||||
|
let cIndex = batch.indirectOffsets[drawIndex];
|
||||||
|
validatedParams.data[vIndex + kIndexCountEntry] =
|
||||||
|
clientParams.data[cIndex + kIndexCountEntry];
|
||||||
|
validatedParams.data[vIndex + kInstanceCountEntry] =
|
||||||
|
clientParams.data[cIndex + kInstanceCountEntry];
|
||||||
|
validatedParams.data[vIndex + kFirstIndexEntry] =
|
||||||
|
clientParams.data[cIndex + kFirstIndexEntry];
|
||||||
|
validatedParams.data[vIndex + kBaseVertexEntry] =
|
||||||
|
clientParams.data[cIndex + kBaseVertexEntry];
|
||||||
|
validatedParams.data[vIndex + kFirstInstanceEntry] =
|
||||||
|
clientParams.data[cIndex + kFirstInstanceEntry];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(compute), workgroup_size(64, 1, 1)]]
|
||||||
|
fn main([[builtin(global_invocation_id)]] id : vec3<u32>) {
|
||||||
|
if (id.x >= batch.numDraws) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clientIndex = batch.indirectOffsets[id.x];
|
||||||
|
let firstInstance = clientParams.data[clientIndex + kFirstInstanceEntry];
|
||||||
|
if (firstInstance != 0u) {
|
||||||
|
fail(id.x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.numIndexBufferElementsHigh >= 2u) {
|
||||||
|
// firstIndex and indexCount are both u32. The maximum possible sum of these
|
||||||
|
// values is 0x1fffffffe, which is less than 0x200000000. Nothing to validate.
|
||||||
|
pass(id.x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let firstIndex = clientParams.data[clientIndex + kFirstIndexEntry];
|
||||||
|
if (batch.numIndexBufferElementsHigh == 0u &&
|
||||||
|
batch.numIndexBufferElementsLow < firstIndex) {
|
||||||
|
fail(id.x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this subtraction may underflow, but only when
|
||||||
|
// numIndexBufferElementsHigh is 1u. The result is still correct in that case.
|
||||||
|
let maxIndexCount = batch.numIndexBufferElementsLow - firstIndex;
|
||||||
|
let indexCount = clientParams.data[clientIndex + kIndexCountEntry];
|
||||||
|
if (indexCount > maxIndexCount) {
|
||||||
|
fail(id.x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pass(id.x);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
ResultOrError<ComputePipelineBase*> GetOrCreateRenderValidationPipeline(
|
||||||
|
DeviceBase* device) {
|
||||||
|
InternalPipelineStore* store = device->GetInternalPipelineStore();
|
||||||
|
|
||||||
|
if (store->renderValidationPipeline == nullptr) {
|
||||||
|
// Create compute shader module if not cached before.
|
||||||
|
if (store->renderValidationShader == nullptr) {
|
||||||
|
ShaderModuleDescriptor descriptor;
|
||||||
|
ShaderModuleWGSLDescriptor wgslDesc;
|
||||||
|
wgslDesc.source = sRenderValidationShaderSource;
|
||||||
|
descriptor.nextInChain = reinterpret_cast<ChainedStruct*>(&wgslDesc);
|
||||||
|
DAWN_TRY_ASSIGN(store->renderValidationShader,
|
||||||
|
device->CreateShaderModule(&descriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
BindGroupLayoutEntry entries[3];
|
||||||
|
entries[0].binding = 0;
|
||||||
|
entries[0].visibility = wgpu::ShaderStage::Compute;
|
||||||
|
entries[0].buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
|
||||||
|
entries[1].binding = 1;
|
||||||
|
entries[1].visibility = wgpu::ShaderStage::Compute;
|
||||||
|
entries[1].buffer.type = kInternalStorageBufferBinding;
|
||||||
|
entries[2].binding = 2;
|
||||||
|
entries[2].visibility = wgpu::ShaderStage::Compute;
|
||||||
|
entries[2].buffer.type = wgpu::BufferBindingType::Storage;
|
||||||
|
|
||||||
|
BindGroupLayoutDescriptor bindGroupLayoutDescriptor;
|
||||||
|
bindGroupLayoutDescriptor.entryCount = 3;
|
||||||
|
bindGroupLayoutDescriptor.entries = entries;
|
||||||
|
Ref<BindGroupLayoutBase> bindGroupLayout;
|
||||||
|
DAWN_TRY_ASSIGN(bindGroupLayout,
|
||||||
|
device->CreateBindGroupLayout(&bindGroupLayoutDescriptor, true));
|
||||||
|
|
||||||
|
PipelineLayoutDescriptor pipelineDescriptor;
|
||||||
|
pipelineDescriptor.bindGroupLayoutCount = 1;
|
||||||
|
pipelineDescriptor.bindGroupLayouts = &bindGroupLayout.Get();
|
||||||
|
Ref<PipelineLayoutBase> pipelineLayout;
|
||||||
|
DAWN_TRY_ASSIGN(pipelineLayout, device->CreatePipelineLayout(&pipelineDescriptor));
|
||||||
|
|
||||||
|
ComputePipelineDescriptor computePipelineDescriptor = {};
|
||||||
|
computePipelineDescriptor.layout = pipelineLayout.Get();
|
||||||
|
computePipelineDescriptor.compute.module = store->renderValidationShader.Get();
|
||||||
|
computePipelineDescriptor.compute.entryPoint = "main";
|
||||||
|
|
||||||
|
DAWN_TRY_ASSIGN(store->renderValidationPipeline,
|
||||||
|
device->CreateComputePipeline(&computePipelineDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return store->renderValidationPipeline.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetBatchDataSize(uint32_t numDraws) {
|
||||||
|
return sizeof(BatchInfo) + numDraws * sizeof(uint32_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const uint32_t kBatchDrawCallLimitByDispatchSize =
|
||||||
|
kMaxComputePerDimensionDispatchSize * kWorkgroupSize;
|
||||||
|
const uint32_t kBatchDrawCallLimitByStorageBindingSize =
|
||||||
|
(kMaxStorageBufferBindingSize - sizeof(BatchInfo)) / sizeof(uint32_t);
|
||||||
|
const uint32_t kMaxDrawCallsPerIndirectValidationBatch =
|
||||||
|
std::min(kBatchDrawCallLimitByDispatchSize, kBatchDrawCallLimitByStorageBindingSize);
|
||||||
|
|
||||||
|
MaybeError EncodeIndirectDrawValidationCommands(DeviceBase* device,
|
||||||
|
CommandEncoder* commandEncoder,
|
||||||
|
RenderPassResourceUsageTracker* usageTracker,
|
||||||
|
IndirectDrawMetadata* indirectDrawMetadata) {
|
||||||
|
struct Batch {
|
||||||
|
const IndirectDrawMetadata::IndexedIndirectValidationBatch* metadata;
|
||||||
|
uint64_t numIndexBufferElements;
|
||||||
|
uint64_t dataBufferOffset;
|
||||||
|
uint64_t dataSize;
|
||||||
|
uint64_t clientIndirectOffset;
|
||||||
|
uint64_t clientIndirectSize;
|
||||||
|
uint64_t validatedParamsOffset;
|
||||||
|
uint64_t validatedParamsSize;
|
||||||
|
BatchInfo* batchInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pass {
|
||||||
|
BufferBase* clientIndirectBuffer;
|
||||||
|
uint64_t validatedParamsSize = 0;
|
||||||
|
uint64_t batchDataSize = 0;
|
||||||
|
std::unique_ptr<void, void (*)(void*)> batchData{nullptr, std::free};
|
||||||
|
std::vector<Batch> batches;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First stage is grouping all batches into passes. We try to pack as many batches into a
|
||||||
|
// single pass as possible. Batches can be grouped together as long as they're validating
|
||||||
|
// data from the same indirect buffer, but they may still be split into multiple passes if
|
||||||
|
// the number of draw calls in a pass would exceed some (very high) upper bound.
|
||||||
|
uint64_t numTotalDrawCalls = 0;
|
||||||
|
size_t validatedParamsSize = 0;
|
||||||
|
std::vector<Pass> passes;
|
||||||
|
IndirectDrawMetadata::IndexedIndirectBufferValidationInfoMap& bufferInfoMap =
|
||||||
|
*indirectDrawMetadata->GetIndexedIndirectBufferValidationInfo();
|
||||||
|
if (bufferInfoMap.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entry : bufferInfoMap) {
|
||||||
|
const IndirectDrawMetadata::IndexedIndirectConfig& config = entry.first;
|
||||||
|
BufferBase* clientIndirectBuffer = config.first;
|
||||||
|
for (const IndirectDrawMetadata::IndexedIndirectValidationBatch& batch :
|
||||||
|
entry.second.GetBatches()) {
|
||||||
|
const uint64_t minOffsetFromAlignedBoundary =
|
||||||
|
batch.minOffset % kMinStorageBufferOffsetAlignment;
|
||||||
|
const uint64_t minOffsetAlignedDown =
|
||||||
|
batch.minOffset - minOffsetFromAlignedBoundary;
|
||||||
|
|
||||||
|
Batch newBatch;
|
||||||
|
newBatch.metadata = &batch;
|
||||||
|
newBatch.numIndexBufferElements = config.second;
|
||||||
|
newBatch.dataSize = GetBatchDataSize(batch.draws.size());
|
||||||
|
newBatch.clientIndirectOffset = minOffsetAlignedDown;
|
||||||
|
newBatch.clientIndirectSize =
|
||||||
|
batch.maxOffset + kDrawIndexedIndirectSize - minOffsetAlignedDown;
|
||||||
|
numTotalDrawCalls += batch.draws.size();
|
||||||
|
|
||||||
|
newBatch.validatedParamsSize = batch.draws.size() * kDrawIndexedIndirectSize;
|
||||||
|
newBatch.validatedParamsOffset =
|
||||||
|
Align(validatedParamsSize, kMinStorageBufferOffsetAlignment);
|
||||||
|
validatedParamsSize = newBatch.validatedParamsOffset + newBatch.validatedParamsSize;
|
||||||
|
if (validatedParamsSize > kMaxStorageBufferBindingSize) {
|
||||||
|
return DAWN_INTERNAL_ERROR("Too many drawIndexedIndirect calls to validate");
|
||||||
|
}
|
||||||
|
|
||||||
|
Pass* currentPass = passes.empty() ? nullptr : &passes.back();
|
||||||
|
if (currentPass && currentPass->clientIndirectBuffer == clientIndirectBuffer) {
|
||||||
|
uint64_t nextBatchDataOffset =
|
||||||
|
Align(currentPass->batchDataSize, kMinStorageBufferOffsetAlignment);
|
||||||
|
uint64_t newPassBatchDataSize = nextBatchDataOffset + newBatch.dataSize;
|
||||||
|
if (newPassBatchDataSize <= kMaxStorageBufferBindingSize) {
|
||||||
|
// We can fit this batch in the current pass.
|
||||||
|
newBatch.dataBufferOffset = nextBatchDataOffset;
|
||||||
|
currentPass->batchDataSize = newPassBatchDataSize;
|
||||||
|
currentPass->batches.push_back(newBatch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to start a new pass for this batch.
|
||||||
|
newBatch.dataBufferOffset = 0;
|
||||||
|
|
||||||
|
Pass newPass;
|
||||||
|
newPass.clientIndirectBuffer = clientIndirectBuffer;
|
||||||
|
newPass.batchDataSize = newBatch.dataSize;
|
||||||
|
newPass.batches.push_back(newBatch);
|
||||||
|
passes.push_back(std::move(newPass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* const store = device->GetInternalPipelineStore();
|
||||||
|
ScratchBuffer& validatedParamsBuffer = store->scratchIndirectStorage;
|
||||||
|
ScratchBuffer& batchDataBuffer = store->scratchStorage;
|
||||||
|
|
||||||
|
uint64_t requiredBatchDataBufferSize = 0;
|
||||||
|
for (const Pass& pass : passes) {
|
||||||
|
requiredBatchDataBufferSize = std::max(requiredBatchDataBufferSize, pass.batchDataSize);
|
||||||
|
}
|
||||||
|
DAWN_TRY(batchDataBuffer.EnsureCapacity(requiredBatchDataBufferSize));
|
||||||
|
usageTracker->BufferUsedAs(batchDataBuffer.GetBuffer(), wgpu::BufferUsage::Storage);
|
||||||
|
|
||||||
|
DAWN_TRY(validatedParamsBuffer.EnsureCapacity(validatedParamsSize));
|
||||||
|
usageTracker->BufferUsedAs(validatedParamsBuffer.GetBuffer(), wgpu::BufferUsage::Indirect);
|
||||||
|
|
||||||
|
// Now we allocate and populate host-side batch data to be copied to the GPU, and prepare to
|
||||||
|
// update all DrawIndexedIndirectCmd buffer references.
|
||||||
|
std::vector<DeferredBufferLocationUpdate> deferredBufferLocationUpdates;
|
||||||
|
deferredBufferLocationUpdates.reserve(numTotalDrawCalls);
|
||||||
|
for (Pass& pass : passes) {
|
||||||
|
// We use std::malloc here because it guarantees maximal scalar alignment.
|
||||||
|
pass.batchData = {std::malloc(pass.batchDataSize), std::free};
|
||||||
|
memset(pass.batchData.get(), 0, pass.batchDataSize);
|
||||||
|
uint8_t* batchData = static_cast<uint8_t*>(pass.batchData.get());
|
||||||
|
for (Batch& batch : pass.batches) {
|
||||||
|
batch.batchInfo = new (&batchData[batch.dataBufferOffset]) BatchInfo();
|
||||||
|
batch.batchInfo->numIndexBufferElements = batch.numIndexBufferElements;
|
||||||
|
batch.batchInfo->numDraws = static_cast<uint32_t>(batch.metadata->draws.size());
|
||||||
|
|
||||||
|
uint32_t* indirectOffsets = reinterpret_cast<uint32_t*>(batch.batchInfo + 1);
|
||||||
|
uint64_t validatedParamsOffset = batch.validatedParamsOffset;
|
||||||
|
for (const auto& draw : batch.metadata->draws) {
|
||||||
|
// The shader uses this to index an array of u32, hence the division by 4 bytes.
|
||||||
|
*indirectOffsets++ = static_cast<uint32_t>(
|
||||||
|
(draw.clientBufferOffset - batch.clientIndirectOffset) / 4);
|
||||||
|
|
||||||
|
DeferredBufferLocationUpdate deferredUpdate;
|
||||||
|
deferredUpdate.location = draw.bufferLocation;
|
||||||
|
deferredUpdate.buffer = validatedParamsBuffer.GetBuffer();
|
||||||
|
deferredUpdate.offset = validatedParamsOffset;
|
||||||
|
deferredBufferLocationUpdates.push_back(std::move(deferredUpdate));
|
||||||
|
|
||||||
|
validatedParamsOffset += kDrawIndexedIndirectSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBase* pipeline;
|
||||||
|
DAWN_TRY_ASSIGN(pipeline, GetOrCreateRenderValidationPipeline(device));
|
||||||
|
|
||||||
|
Ref<BindGroupLayoutBase> layout;
|
||||||
|
DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0));
|
||||||
|
|
||||||
|
BindGroupEntry bindings[3];
|
||||||
|
BindGroupEntry& bufferDataBinding = bindings[0];
|
||||||
|
bufferDataBinding.binding = 0;
|
||||||
|
bufferDataBinding.buffer = batchDataBuffer.GetBuffer();
|
||||||
|
|
||||||
|
BindGroupEntry& clientIndirectBinding = bindings[1];
|
||||||
|
clientIndirectBinding.binding = 1;
|
||||||
|
|
||||||
|
BindGroupEntry& validatedParamsBinding = bindings[2];
|
||||||
|
validatedParamsBinding.binding = 2;
|
||||||
|
validatedParamsBinding.buffer = validatedParamsBuffer.GetBuffer();
|
||||||
|
|
||||||
|
BindGroupDescriptor bindGroupDescriptor = {};
|
||||||
|
bindGroupDescriptor.layout = layout.Get();
|
||||||
|
bindGroupDescriptor.entryCount = 3;
|
||||||
|
bindGroupDescriptor.entries = bindings;
|
||||||
|
|
||||||
|
// Finally, we can now encode our validation passes. Each pass first does a single
|
||||||
|
// WriteBuffer to get batch data over to the GPU, followed by a single compute pass. The
|
||||||
|
// compute pass encodes a separate SetBindGroup and Dispatch command for each batch.
|
||||||
|
commandEncoder->EncodeSetValidatedBufferLocationsInternal(
|
||||||
|
std::move(deferredBufferLocationUpdates));
|
||||||
|
for (const Pass& pass : passes) {
|
||||||
|
commandEncoder->APIWriteBuffer(batchDataBuffer.GetBuffer(), 0,
|
||||||
|
static_cast<const uint8_t*>(pass.batchData.get()),
|
||||||
|
pass.batchDataSize);
|
||||||
|
|
||||||
|
// TODO(dawn:723): change to not use AcquireRef for reentrant object creation.
|
||||||
|
ComputePassDescriptor descriptor = {};
|
||||||
|
Ref<ComputePassEncoder> passEncoder =
|
||||||
|
AcquireRef(commandEncoder->APIBeginComputePass(&descriptor));
|
||||||
|
passEncoder->APISetPipeline(pipeline);
|
||||||
|
|
||||||
|
clientIndirectBinding.buffer = pass.clientIndirectBuffer;
|
||||||
|
|
||||||
|
for (const Batch& batch : pass.batches) {
|
||||||
|
bufferDataBinding.offset = batch.dataBufferOffset;
|
||||||
|
bufferDataBinding.size = batch.dataSize;
|
||||||
|
clientIndirectBinding.offset = batch.clientIndirectOffset;
|
||||||
|
clientIndirectBinding.size = batch.clientIndirectSize;
|
||||||
|
validatedParamsBinding.offset = batch.validatedParamsOffset;
|
||||||
|
validatedParamsBinding.size = batch.validatedParamsSize;
|
||||||
|
|
||||||
|
Ref<BindGroupBase> bindGroup;
|
||||||
|
DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bindGroupDescriptor));
|
||||||
|
|
||||||
|
const uint32_t numDrawsRoundedUp =
|
||||||
|
(batch.batchInfo->numDraws + kWorkgroupSize - 1) / kWorkgroupSize;
|
||||||
|
passEncoder->APISetBindGroup(0, bindGroup.Get());
|
||||||
|
passEncoder->APIDispatch(numDrawsRoundedUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
passEncoder->APIEndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
39
src/dawn_native/IndirectDrawValidationEncoder.h
Normal file
39
src/dawn_native/IndirectDrawValidationEncoder.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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_INDIRECTDRAWVALIDATIONENCODER_H_
|
||||||
|
#define DAWNNATIVE_INDIRECTDRAWVALIDATIONENCODER_H_
|
||||||
|
|
||||||
|
#include "dawn_native/Error.h"
|
||||||
|
#include "dawn_native/IndirectDrawMetadata.h"
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class CommandEncoder;
|
||||||
|
class DeviceBase;
|
||||||
|
class RenderPassResourceUsageTracker;
|
||||||
|
|
||||||
|
// The maximum number of draws call we can fit into a single validation batch. This is
|
||||||
|
// essentially limited by the number of indirect parameter blocks that can fit into the maximum
|
||||||
|
// allowed storage binding size (about 6.7M).
|
||||||
|
extern const uint32_t kMaxDrawCallsPerIndirectValidationBatch;
|
||||||
|
|
||||||
|
MaybeError EncodeIndirectDrawValidationCommands(DeviceBase* device,
|
||||||
|
CommandEncoder* commandEncoder,
|
||||||
|
RenderPassResourceUsageTracker* usageTracker,
|
||||||
|
IndirectDrawMetadata* indirectDrawMetadata);
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
||||||
|
|
||||||
|
#endif // DAWNNATIVE_INDIRECTDRAWVALIDATIONENCODER_H_
|
38
src/dawn_native/InternalPipelineStore.cpp
Normal file
38
src/dawn_native/InternalPipelineStore.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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/InternalPipelineStore.h"
|
||||||
|
|
||||||
|
#include "dawn_native/ComputePipeline.h"
|
||||||
|
#include "dawn_native/Device.h"
|
||||||
|
#include "dawn_native/RenderPipeline.h"
|
||||||
|
#include "dawn_native/ShaderModule.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class RenderPipelineBase;
|
||||||
|
class ShaderModuleBase;
|
||||||
|
|
||||||
|
InternalPipelineStore::InternalPipelineStore(DeviceBase* device)
|
||||||
|
: scratchStorage(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Storage),
|
||||||
|
scratchIndirectStorage(device,
|
||||||
|
wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect |
|
||||||
|
wgpu::BufferUsage::Storage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
InternalPipelineStore::~InternalPipelineStore() = default;
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
@ -16,15 +16,23 @@
|
|||||||
#define DAWNNATIVE_INTERNALPIPELINESTORE_H_
|
#define DAWNNATIVE_INTERNALPIPELINESTORE_H_
|
||||||
|
|
||||||
#include "dawn_native/ObjectBase.h"
|
#include "dawn_native/ObjectBase.h"
|
||||||
|
#include "dawn_native/ScratchBuffer.h"
|
||||||
#include "dawn_native/dawn_platform.h"
|
#include "dawn_native/dawn_platform.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace dawn_native {
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class DeviceBase;
|
||||||
class RenderPipelineBase;
|
class RenderPipelineBase;
|
||||||
class ShaderModuleBase;
|
class ShaderModuleBase;
|
||||||
|
|
||||||
|
// Every DeviceBase owns an InternalPipelineStore. This is a general-purpose cache for
|
||||||
|
// long-lived objects scoped to a device and used to support arbitrary pipeline operations.
|
||||||
struct InternalPipelineStore {
|
struct InternalPipelineStore {
|
||||||
|
explicit InternalPipelineStore(DeviceBase* device);
|
||||||
|
~InternalPipelineStore();
|
||||||
|
|
||||||
std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>>
|
std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>>
|
||||||
copyTextureForBrowserPipelines;
|
copyTextureForBrowserPipelines;
|
||||||
|
|
||||||
@ -34,7 +42,18 @@ namespace dawn_native {
|
|||||||
Ref<ShaderModuleBase> timestampCS;
|
Ref<ShaderModuleBase> timestampCS;
|
||||||
|
|
||||||
Ref<ShaderModuleBase> dummyFragmentShader;
|
Ref<ShaderModuleBase> dummyFragmentShader;
|
||||||
|
|
||||||
|
// A scratch buffer suitable for use as a copy destination and storage binding.
|
||||||
|
ScratchBuffer scratchStorage;
|
||||||
|
|
||||||
|
// A scratch buffer suitable for use as a copy destination, storage binding, and indirect
|
||||||
|
// buffer for indirect dispatch or draw calls.
|
||||||
|
ScratchBuffer scratchIndirectStorage;
|
||||||
|
|
||||||
|
Ref<ComputePipelineBase> renderValidationPipeline;
|
||||||
|
Ref<ShaderModuleBase> renderValidationShader;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dawn_native
|
} // namespace dawn_native
|
||||||
|
|
||||||
#endif // DAWNNATIVE_INTERNALPIPELINESTORE_H_
|
#endif // DAWNNATIVE_INTERNALPIPELINESTORE_H_
|
||||||
|
@ -24,9 +24,11 @@ namespace dawn_native {
|
|||||||
RenderBundleBase::RenderBundleBase(RenderBundleEncoder* encoder,
|
RenderBundleBase::RenderBundleBase(RenderBundleEncoder* encoder,
|
||||||
const RenderBundleDescriptor* descriptor,
|
const RenderBundleDescriptor* descriptor,
|
||||||
Ref<AttachmentState> attachmentState,
|
Ref<AttachmentState> attachmentState,
|
||||||
RenderPassResourceUsage resourceUsage)
|
RenderPassResourceUsage resourceUsage,
|
||||||
|
IndirectDrawMetadata indirectDrawMetadata)
|
||||||
: ObjectBase(encoder->GetDevice(), kLabelNotImplemented),
|
: ObjectBase(encoder->GetDevice(), kLabelNotImplemented),
|
||||||
mCommands(encoder->AcquireCommands()),
|
mCommands(encoder->AcquireCommands()),
|
||||||
|
mIndirectDrawMetadata(std::move(indirectDrawMetadata)),
|
||||||
mAttachmentState(std::move(attachmentState)),
|
mAttachmentState(std::move(attachmentState)),
|
||||||
mResourceUsage(std::move(resourceUsage)) {
|
mResourceUsage(std::move(resourceUsage)) {
|
||||||
}
|
}
|
||||||
@ -58,4 +60,8 @@ namespace dawn_native {
|
|||||||
return mResourceUsage;
|
return mResourceUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IndirectDrawMetadata& RenderBundleBase::GetIndirectDrawMetadata() {
|
||||||
|
return mIndirectDrawMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dawn_native
|
} // namespace dawn_native
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "dawn_native/AttachmentState.h"
|
#include "dawn_native/AttachmentState.h"
|
||||||
#include "dawn_native/CommandAllocator.h"
|
#include "dawn_native/CommandAllocator.h"
|
||||||
#include "dawn_native/Error.h"
|
#include "dawn_native/Error.h"
|
||||||
|
#include "dawn_native/IndirectDrawMetadata.h"
|
||||||
#include "dawn_native/ObjectBase.h"
|
#include "dawn_native/ObjectBase.h"
|
||||||
#include "dawn_native/PassResourceUsage.h"
|
#include "dawn_native/PassResourceUsage.h"
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ namespace dawn_native {
|
|||||||
RenderBundleBase(RenderBundleEncoder* encoder,
|
RenderBundleBase(RenderBundleEncoder* encoder,
|
||||||
const RenderBundleDescriptor* descriptor,
|
const RenderBundleDescriptor* descriptor,
|
||||||
Ref<AttachmentState> attachmentState,
|
Ref<AttachmentState> attachmentState,
|
||||||
RenderPassResourceUsage resourceUsage);
|
RenderPassResourceUsage resourceUsage,
|
||||||
|
IndirectDrawMetadata indirectDrawMetadata);
|
||||||
|
|
||||||
static RenderBundleBase* MakeError(DeviceBase* device);
|
static RenderBundleBase* MakeError(DeviceBase* device);
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ namespace dawn_native {
|
|||||||
|
|
||||||
const AttachmentState* GetAttachmentState() const;
|
const AttachmentState* GetAttachmentState() const;
|
||||||
const RenderPassResourceUsage& GetResourceUsage() const;
|
const RenderPassResourceUsage& GetResourceUsage() const;
|
||||||
|
const IndirectDrawMetadata& GetIndirectDrawMetadata();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~RenderBundleBase() override;
|
~RenderBundleBase() override;
|
||||||
@ -52,6 +55,7 @@ namespace dawn_native {
|
|||||||
RenderBundleBase(DeviceBase* device, ErrorTag errorTag);
|
RenderBundleBase(DeviceBase* device, ErrorTag errorTag);
|
||||||
|
|
||||||
CommandIterator mCommands;
|
CommandIterator mCommands;
|
||||||
|
IndirectDrawMetadata mIndirectDrawMetadata;
|
||||||
Ref<AttachmentState> mAttachmentState;
|
Ref<AttachmentState> mAttachmentState;
|
||||||
RenderPassResourceUsage mResourceUsage;
|
RenderPassResourceUsage mResourceUsage;
|
||||||
};
|
};
|
||||||
|
@ -130,7 +130,8 @@ namespace dawn_native {
|
|||||||
DAWN_TRY(ValidateFinish(usages));
|
DAWN_TRY(ValidateFinish(usages));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RenderBundleBase(this, descriptor, AcquireAttachmentState(), std::move(usages));
|
return new RenderBundleBase(this, descriptor, AcquireAttachmentState(), std::move(usages),
|
||||||
|
std::move(mIndirectDrawMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeError RenderBundleEncoder::ValidateFinish(const RenderPassResourceUsage& usages) const {
|
MaybeError RenderBundleEncoder::ValidateFinish(const RenderPassResourceUsage& usages) const {
|
||||||
|
@ -157,16 +157,6 @@ namespace dawn_native {
|
|||||||
DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect));
|
DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect));
|
||||||
DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed());
|
DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed());
|
||||||
|
|
||||||
// Indexed indirect draws need a compute-shader based validation check that the
|
|
||||||
// range of indices is contained inside the index buffer on Metal. Disallow them as
|
|
||||||
// unsafe until the validation is implemented.
|
|
||||||
if (GetDevice()->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
|
|
||||||
return DAWN_VALIDATION_ERROR(
|
|
||||||
"DrawIndexedIndirect is disallowed because it doesn't validate that the "
|
|
||||||
"index "
|
|
||||||
"range is valid yet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indirectOffset % 4 != 0) {
|
if (indirectOffset % 4 != 0) {
|
||||||
return DAWN_VALIDATION_ERROR("Indirect offset must be a multiple of 4");
|
return DAWN_VALIDATION_ERROR("Indirect offset must be a multiple of 4");
|
||||||
}
|
}
|
||||||
@ -179,7 +169,14 @@ namespace dawn_native {
|
|||||||
|
|
||||||
DrawIndexedIndirectCmd* cmd =
|
DrawIndexedIndirectCmd* cmd =
|
||||||
allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect);
|
allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect);
|
||||||
|
if (IsValidationEnabled()) {
|
||||||
|
cmd->indirectBufferLocation = BufferLocation::New();
|
||||||
|
mIndirectDrawMetadata.AddIndexedIndirectDraw(
|
||||||
|
mCommandBufferState.GetIndexFormat(), mCommandBufferState.GetIndexBufferSize(),
|
||||||
|
indirectBuffer, indirectOffset, cmd->indirectBufferLocation.Get());
|
||||||
|
} else {
|
||||||
cmd->indirectBufferLocation = BufferLocation::New(indirectBuffer, indirectOffset);
|
cmd->indirectBufferLocation = BufferLocation::New(indirectBuffer, indirectOffset);
|
||||||
|
}
|
||||||
|
|
||||||
mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect);
|
mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect);
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "dawn_native/AttachmentState.h"
|
#include "dawn_native/AttachmentState.h"
|
||||||
#include "dawn_native/CommandBufferStateTracker.h"
|
#include "dawn_native/CommandBufferStateTracker.h"
|
||||||
#include "dawn_native/Error.h"
|
#include "dawn_native/Error.h"
|
||||||
|
#include "dawn_native/IndirectDrawMetadata.h"
|
||||||
#include "dawn_native/PassResourceUsageTracker.h"
|
#include "dawn_native/PassResourceUsageTracker.h"
|
||||||
#include "dawn_native/ProgrammablePassEncoder.h"
|
#include "dawn_native/ProgrammablePassEncoder.h"
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ namespace dawn_native {
|
|||||||
|
|
||||||
CommandBufferStateTracker mCommandBufferState;
|
CommandBufferStateTracker mCommandBufferState;
|
||||||
RenderPassResourceUsageTracker mUsageTracker;
|
RenderPassResourceUsageTracker mUsageTracker;
|
||||||
|
IndirectDrawMetadata mIndirectDrawMetadata;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ref<AttachmentState> mAttachmentState;
|
Ref<AttachmentState> mAttachmentState;
|
||||||
|
@ -99,9 +99,11 @@ namespace dawn_native {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass);
|
allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass);
|
||||||
|
DAWN_TRY(mEncodingContext->ExitRenderPass(this, std::move(mUsageTracker),
|
||||||
|
mCommandEncoder.Get(),
|
||||||
|
std::move(mIndirectDrawMetadata)));
|
||||||
return {};
|
return {};
|
||||||
})) {
|
})) {
|
||||||
mEncodingContext->ExitPass(this, mUsageTracker.AcquireResourceUsage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +226,10 @@ namespace dawn_native {
|
|||||||
mUsageTracker.AddRenderBundleTextureUsage(usages.textures[i],
|
mUsageTracker.AddRenderBundleTextureUsage(usages.textures[i],
|
||||||
usages.textureUsages[i]);
|
usages.textureUsages[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsValidationEnabled()) {
|
||||||
|
mIndirectDrawMetadata.AddBundle(renderBundles[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
47
src/dawn_native/ScratchBuffer.cpp
Normal file
47
src/dawn_native/ScratchBuffer.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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/ScratchBuffer.h"
|
||||||
|
|
||||||
|
#include "dawn_native/Device.h"
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
ScratchBuffer::ScratchBuffer(DeviceBase* device, wgpu::BufferUsage usage)
|
||||||
|
: mDevice(device), mUsage(usage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ScratchBuffer::~ScratchBuffer() = default;
|
||||||
|
|
||||||
|
void ScratchBuffer::Reset() {
|
||||||
|
mBuffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeError ScratchBuffer::EnsureCapacity(uint64_t capacity) {
|
||||||
|
if (!mBuffer.Get() || mBuffer->GetSize() < capacity) {
|
||||||
|
BufferDescriptor descriptor;
|
||||||
|
descriptor.size = capacity;
|
||||||
|
descriptor.usage = mUsage;
|
||||||
|
DAWN_TRY_ASSIGN(mBuffer, mDevice->CreateBuffer(&descriptor));
|
||||||
|
mBuffer->SetIsDataInitialized();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferBase* ScratchBuffer::GetBuffer() const {
|
||||||
|
ASSERT(mBuffer.Get() != nullptr);
|
||||||
|
return mBuffer.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
55
src/dawn_native/ScratchBuffer.h
Normal file
55
src/dawn_native/ScratchBuffer.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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_SCRATCHBUFFER_H_
|
||||||
|
#define DAWNNATIVE_SCRATCHBUFFER_H_
|
||||||
|
|
||||||
|
#include "common/RefCounted.h"
|
||||||
|
#include "dawn_native/Buffer.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace dawn_native {
|
||||||
|
|
||||||
|
class DeviceBase;
|
||||||
|
|
||||||
|
// A ScratchBuffer is a lazily allocated and lazily grown GPU buffer for intermittent use by
|
||||||
|
// commands in the GPU queue. Note that scratch buffers are not zero-initialized, so users must
|
||||||
|
// be careful not to exposed uninitialized bytes to client shaders.
|
||||||
|
class ScratchBuffer {
|
||||||
|
public:
|
||||||
|
// Note that this object does not retain a reference to `device`, so `device` MUST outlive
|
||||||
|
// this object.
|
||||||
|
ScratchBuffer(DeviceBase* device, wgpu::BufferUsage usage);
|
||||||
|
~ScratchBuffer();
|
||||||
|
|
||||||
|
// Resets this ScratchBuffer, guaranteeing that the next EnsureCapacity call allocates a
|
||||||
|
// fresh buffer.
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
// Ensures that this ScratchBuffer is backed by a buffer on `device` with at least
|
||||||
|
// `capacity` bytes of storage.
|
||||||
|
MaybeError EnsureCapacity(uint64_t capacity);
|
||||||
|
|
||||||
|
BufferBase* GetBuffer() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DeviceBase* const mDevice;
|
||||||
|
const wgpu::BufferUsage mUsage;
|
||||||
|
Ref<BufferBase> mBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dawn_native
|
||||||
|
|
||||||
|
#endif // DAWNNATIVE_SCRATCHBUFFER_H_
|
@ -981,6 +981,10 @@ namespace dawn_native { namespace d3d12 {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Command::SetValidatedBufferLocationsInternal:
|
||||||
|
DoNextSetValidatedBufferLocationsInternal();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::WriteBuffer: {
|
case Command::WriteBuffer: {
|
||||||
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
||||||
const uint64_t offset = write->offset;
|
const uint64_t offset = write->offset;
|
||||||
|
@ -987,6 +987,10 @@ namespace dawn_native { namespace metal {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Command::SetValidatedBufferLocationsInternal:
|
||||||
|
DoNextSetValidatedBufferLocationsInternal();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::WriteBuffer: {
|
case Command::WriteBuffer: {
|
||||||
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
||||||
const uint64_t offset = write->offset;
|
const uint64_t offset = write->offset;
|
||||||
|
@ -843,6 +843,10 @@ namespace dawn_native { namespace opengl {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Command::SetValidatedBufferLocationsInternal:
|
||||||
|
DoNextSetValidatedBufferLocationsInternal();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::WriteBuffer: {
|
case Command::WriteBuffer: {
|
||||||
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
||||||
uint64_t offset = write->offset;
|
uint64_t offset = write->offset;
|
||||||
|
@ -824,6 +824,10 @@ namespace dawn_native { namespace vulkan {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Command::SetValidatedBufferLocationsInternal:
|
||||||
|
DoNextSetValidatedBufferLocationsInternal();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::WriteBuffer: {
|
case Command::WriteBuffer: {
|
||||||
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
|
||||||
const uint64_t offset = write->offset;
|
const uint64_t offset = write->offset;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "tests/DawnTest.h"
|
#include "tests/DawnTest.h"
|
||||||
|
|
||||||
|
#include "utils/ComboRenderBundleEncoderDescriptor.h"
|
||||||
#include "utils/ComboRenderPipelineDescriptor.h"
|
#include "utils/ComboRenderPipelineDescriptor.h"
|
||||||
#include "utils/WGPUHelpers.h"
|
#include "utils/WGPUHelpers.h"
|
||||||
|
|
||||||
@ -59,25 +60,26 @@ class DrawIndexedIndirectTest : public DawnTest {
|
|||||||
// Second quad: the first 3 vertices represent the top right triangle
|
// Second quad: the first 3 vertices represent the top right triangle
|
||||||
-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f,
|
-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f,
|
||||||
0.0f, 1.0f});
|
0.0f, 1.0f});
|
||||||
indexBuffer = utils::CreateBufferFromData<uint32_t>(
|
|
||||||
device, wgpu::BufferUsage::Index,
|
|
||||||
{0, 1, 2, 0, 3, 1,
|
|
||||||
// The indices below are added to test negatve baseVertex
|
|
||||||
0 + 4, 1 + 4, 2 + 4, 0 + 4, 3 + 4, 1 + 4});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::BasicRenderPass renderPass;
|
utils::BasicRenderPass renderPass;
|
||||||
wgpu::RenderPipeline pipeline;
|
wgpu::RenderPipeline pipeline;
|
||||||
wgpu::Buffer vertexBuffer;
|
wgpu::Buffer vertexBuffer;
|
||||||
wgpu::Buffer indexBuffer;
|
|
||||||
|
|
||||||
void Test(std::initializer_list<uint32_t> bufferList,
|
wgpu::Buffer CreateIndirectBuffer(std::initializer_list<uint32_t> indirectParamList) {
|
||||||
|
return utils::CreateBufferFromData<uint32_t>(
|
||||||
|
device, wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Storage, indirectParamList);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::Buffer CreateIndexBuffer(std::initializer_list<uint32_t> indexList) {
|
||||||
|
return utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Index, indexList);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer EncodeDrawCommands(std::initializer_list<uint32_t> bufferList,
|
||||||
|
wgpu::Buffer indexBuffer,
|
||||||
uint64_t indexOffset,
|
uint64_t indexOffset,
|
||||||
uint64_t indirectOffset,
|
uint64_t indirectOffset) {
|
||||||
RGBA8 bottomLeftExpected,
|
wgpu::Buffer indirectBuffer = CreateIndirectBuffer(bufferList);
|
||||||
RGBA8 topRightExpected) {
|
|
||||||
wgpu::Buffer indirectBuffer =
|
|
||||||
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, bufferList);
|
|
||||||
|
|
||||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
{
|
{
|
||||||
@ -89,12 +91,28 @@ class DrawIndexedIndirectTest : public DawnTest {
|
|||||||
pass.EndPass();
|
pass.EndPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
wgpu::CommandBuffer commands = encoder.Finish();
|
return encoder.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDraw(wgpu::CommandBuffer commands, RGBA8 bottomLeftExpected, RGBA8 topRightExpected) {
|
||||||
queue.Submit(1, &commands);
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
EXPECT_PIXEL_RGBA8_EQ(bottomLeftExpected, renderPass.color, 1, 3);
|
EXPECT_PIXEL_RGBA8_EQ(bottomLeftExpected, renderPass.color, 1, 3);
|
||||||
EXPECT_PIXEL_RGBA8_EQ(topRightExpected, renderPass.color, 3, 1);
|
EXPECT_PIXEL_RGBA8_EQ(topRightExpected, renderPass.color, 3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Test(std::initializer_list<uint32_t> bufferList,
|
||||||
|
uint64_t indexOffset,
|
||||||
|
uint64_t indirectOffset,
|
||||||
|
RGBA8 bottomLeftExpected,
|
||||||
|
RGBA8 topRightExpected) {
|
||||||
|
wgpu::Buffer indexBuffer =
|
||||||
|
CreateIndexBuffer({0, 1, 2, 0, 3, 1,
|
||||||
|
// The indices below are added to test negatve baseVertex
|
||||||
|
0 + 4, 1 + 4, 2 + 4, 0 + 4, 3 + 4, 1 + 4});
|
||||||
|
TestDraw(EncodeDrawCommands(bufferList, indexBuffer, indexOffset, indirectOffset),
|
||||||
|
bottomLeftExpected, topRightExpected);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The most basic DrawIndexed triangle draw.
|
// The most basic DrawIndexed triangle draw.
|
||||||
@ -172,6 +190,467 @@ TEST_P(DrawIndexedIndirectTest, IndirectOffset) {
|
|||||||
Test({3, 1, 0, 4, 0, 3, 1, 3, 4, 0}, 0, 5 * sizeof(uint32_t), filled, notFilled);
|
Test({3, 1, 0, 4, 0, 3, 1, 3, 4, 0}, 0, 5 * sizeof(uint32_t), filled, notFilled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, BasicValidation) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1});
|
||||||
|
|
||||||
|
// Test a draw with an excessive indexCount. Should draw nothing.
|
||||||
|
TestDraw(EncodeDrawCommands({7, 1, 0, 0, 0}, indexBuffer, 0, 0), notFilled, notFilled);
|
||||||
|
|
||||||
|
// Test a draw with an excessive firstIndex. Should draw nothing.
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 7, 0, 0}, indexBuffer, 0, 0), notFilled, notFilled);
|
||||||
|
|
||||||
|
// Test a valid draw. Should draw only the second triangle.
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 3, 0, 0}, indexBuffer, 0, 0), notFilled, filled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateWithOffsets) {
|
||||||
|
// TODO(crbug.com/dawn/161): The GL/GLES backend doesn't support indirect index buffer offsets
|
||||||
|
// yet.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
// Test that validation properly accounts for index buffer offset.
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 0, 0, 0}, indexBuffer, 6 * sizeof(uint32_t), 0), filled,
|
||||||
|
notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({4, 1, 0, 0, 0}, indexBuffer, 6 * sizeof(uint32_t), 0), notFilled,
|
||||||
|
notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 4, 0, 0}, indexBuffer, 3 * sizeof(uint32_t), 0), notFilled,
|
||||||
|
notFilled);
|
||||||
|
|
||||||
|
// Test that validation properly accounts for indirect buffer offset.
|
||||||
|
TestDraw(
|
||||||
|
EncodeDrawCommands({3, 1, 0, 0, 0, 1000, 1, 0, 0, 0}, indexBuffer, 0, 4 * sizeof(uint32_t)),
|
||||||
|
notFilled, notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 0, 0, 0, 1000, 1, 0, 0, 0}, indexBuffer, 0, 0), filled,
|
||||||
|
notFilled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateMultiplePasses) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
// Test validation with multiple passes in a row. Namely this is exercising that scratch buffer
|
||||||
|
// data for use with a previous pass's validation commands is not overwritten before it can be
|
||||||
|
// used.
|
||||||
|
TestDraw(EncodeDrawCommands({10, 1, 0, 0, 0}, indexBuffer, 0, 0), notFilled, notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({6, 1, 0, 0, 0}, indexBuffer, 0, 0), filled, filled);
|
||||||
|
TestDraw(EncodeDrawCommands({4, 1, 6, 0, 0}, indexBuffer, 0, 0), notFilled, notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 6, 0, 0}, indexBuffer, 0, 0), filled, notFilled);
|
||||||
|
TestDraw(EncodeDrawCommands({3, 1, 3, 0, 0}, indexBuffer, 0, 0), notFilled, filled);
|
||||||
|
TestDraw(EncodeDrawCommands({6, 1, 3, 0, 0}, indexBuffer, 0, 0), filled, filled);
|
||||||
|
TestDraw(EncodeDrawCommands({6, 1, 6, 0, 0}, indexBuffer, 0, 0), notFilled, notFilled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateMultipleDraws) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Validate multiple draw calls using the same index and indirect buffers as input, but with
|
||||||
|
// different indirect offsets.
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::Buffer indirectBuffer =
|
||||||
|
CreateIndirectBuffer({3, 1, 3, 0, 0, 10, 1, 0, 0, 0, 3, 1, 6, 0, 0});
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.SetPipeline(pipeline);
|
||||||
|
pass.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 0);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 20);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 40);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
|
||||||
|
// Validate multiple draw calls using the same indirect buffer but different index buffers as
|
||||||
|
// input.
|
||||||
|
encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::Buffer indirectBuffer =
|
||||||
|
CreateIndirectBuffer({3, 1, 3, 0, 0, 10, 1, 0, 0, 0, 3, 1, 6, 0, 0});
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.SetPipeline(pipeline);
|
||||||
|
pass.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 0);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 3, 1, 0, 2, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 20);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 2, 1}),
|
||||||
|
wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(indirectBuffer, 40);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
commands = encoder.Finish();
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
|
||||||
|
// Validate multiple draw calls using the same index buffer but different indirect buffers as
|
||||||
|
// input.
|
||||||
|
encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.SetPipeline(pipeline);
|
||||||
|
pass.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({3, 1, 3, 0, 0}), 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({10, 1, 0, 0, 0}), 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({3, 1, 6, 0, 0}), 0);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
commands = encoder.Finish();
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
|
||||||
|
// Validate multiple draw calls across different index and indirect buffers.
|
||||||
|
encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.SetPipeline(pipeline);
|
||||||
|
pass.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({3, 1, 3, 0, 0}), 0);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 1, 2, 0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({10, 1, 0, 0, 0}), 0);
|
||||||
|
pass.SetIndexBuffer(CreateIndexBuffer({0, 3, 1}), wgpu::IndexFormat::Uint32, 0);
|
||||||
|
pass.DrawIndexedIndirect(CreateIndirectBuffer({3, 1, 3, 0, 0}), 0);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
commands = encoder.Finish();
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateEncodeMultipleThenSubmitInOrder) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands[7];
|
||||||
|
commands[0] = EncodeDrawCommands({10, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[1] = EncodeDrawCommands({6, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[2] = EncodeDrawCommands({4, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[3] = EncodeDrawCommands({3, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[4] = EncodeDrawCommands({3, 1, 3, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[5] = EncodeDrawCommands({6, 1, 3, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[6] = EncodeDrawCommands({6, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
|
||||||
|
TestDraw(commands[0], notFilled, notFilled);
|
||||||
|
TestDraw(commands[1], filled, filled);
|
||||||
|
TestDraw(commands[2], notFilled, notFilled);
|
||||||
|
TestDraw(commands[3], filled, notFilled);
|
||||||
|
TestDraw(commands[4], notFilled, filled);
|
||||||
|
TestDraw(commands[5], filled, filled);
|
||||||
|
TestDraw(commands[6], notFilled, notFilled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateEncodeMultipleThenSubmitAtOnce) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// TODO(crbug.com/dawn/1124): Fails on Intel+Vulkan+Windows for drivers
|
||||||
|
// older than 27.20.100.8587, which bots are actively using.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsVulkan() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands[5];
|
||||||
|
commands[0] = EncodeDrawCommands({10, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[1] = EncodeDrawCommands({6, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[2] = EncodeDrawCommands({4, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[3] = EncodeDrawCommands({3, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[4] = EncodeDrawCommands({3, 1, 3, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
|
||||||
|
queue.Submit(5, commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateEncodeMultipleThenSubmitOutOfOrder) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands[7];
|
||||||
|
commands[0] = EncodeDrawCommands({10, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[1] = EncodeDrawCommands({6, 1, 0, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[2] = EncodeDrawCommands({4, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[3] = EncodeDrawCommands({3, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[4] = EncodeDrawCommands({3, 1, 3, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[5] = EncodeDrawCommands({6, 1, 3, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
commands[6] = EncodeDrawCommands({6, 1, 6, 0, 0}, indexBuffer, 0, 0);
|
||||||
|
|
||||||
|
TestDraw(commands[6], notFilled, notFilled);
|
||||||
|
TestDraw(commands[5], filled, filled);
|
||||||
|
TestDraw(commands[4], notFilled, filled);
|
||||||
|
TestDraw(commands[3], filled, notFilled);
|
||||||
|
TestDraw(commands[2], notFilled, notFilled);
|
||||||
|
TestDraw(commands[1], filled, filled);
|
||||||
|
TestDraw(commands[0], notFilled, notFilled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateWithBundlesInSamePass) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indirectBuffer =
|
||||||
|
CreateIndirectBuffer({3, 1, 3, 0, 0, 10, 1, 0, 0, 0, 3, 1, 6, 0, 0});
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
std::vector<wgpu::RenderBundle> bundles;
|
||||||
|
{
|
||||||
|
utils::ComboRenderBundleEncoderDescriptor desc = {};
|
||||||
|
desc.colorFormatsCount = 1;
|
||||||
|
desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
wgpu::RenderBundleEncoder bundleEncoder = device.CreateRenderBundleEncoder(&desc);
|
||||||
|
bundleEncoder.SetPipeline(pipeline);
|
||||||
|
bundleEncoder.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
bundleEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32, 0);
|
||||||
|
bundleEncoder.DrawIndexedIndirect(indirectBuffer, 20);
|
||||||
|
bundles.push_back(bundleEncoder.Finish());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
utils::ComboRenderBundleEncoderDescriptor desc = {};
|
||||||
|
desc.colorFormatsCount = 1;
|
||||||
|
desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
wgpu::RenderBundleEncoder bundleEncoder = device.CreateRenderBundleEncoder(&desc);
|
||||||
|
bundleEncoder.SetPipeline(pipeline);
|
||||||
|
bundleEncoder.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
bundleEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32, 0);
|
||||||
|
bundleEncoder.DrawIndexedIndirect(indirectBuffer, 40);
|
||||||
|
bundles.push_back(bundleEncoder.Finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.ExecuteBundles(bundles.size(), bundles.data());
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateWithBundlesInDifferentPasses) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows only.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indirectBuffer =
|
||||||
|
CreateIndirectBuffer({3, 1, 3, 0, 0, 10, 1, 0, 0, 0, 3, 1, 6, 0, 0});
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1, 0, 1, 2});
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands[2];
|
||||||
|
{
|
||||||
|
wgpu::RenderBundle bundle;
|
||||||
|
utils::ComboRenderBundleEncoderDescriptor desc = {};
|
||||||
|
desc.colorFormatsCount = 1;
|
||||||
|
desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
wgpu::RenderBundleEncoder bundleEncoder = device.CreateRenderBundleEncoder(&desc);
|
||||||
|
bundleEncoder.SetPipeline(pipeline);
|
||||||
|
bundleEncoder.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
bundleEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32, 0);
|
||||||
|
bundleEncoder.DrawIndexedIndirect(indirectBuffer, 20);
|
||||||
|
bundle = bundleEncoder.Finish();
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
renderPass.renderPassInfo.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.ExecuteBundles(1, &bundle);
|
||||||
|
pass.EndPass();
|
||||||
|
|
||||||
|
commands[0] = encoder.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
wgpu::RenderBundle bundle;
|
||||||
|
utils::ComboRenderBundleEncoderDescriptor desc = {};
|
||||||
|
desc.colorFormatsCount = 1;
|
||||||
|
desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
wgpu::RenderBundleEncoder bundleEncoder = device.CreateRenderBundleEncoder(&desc);
|
||||||
|
bundleEncoder.SetPipeline(pipeline);
|
||||||
|
bundleEncoder.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
bundleEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32, 0);
|
||||||
|
bundleEncoder.DrawIndexedIndirect(indirectBuffer, 40);
|
||||||
|
bundle = bundleEncoder.Finish();
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
renderPass.renderPassInfo.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.ExecuteBundles(1, &bundle);
|
||||||
|
pass.EndPass();
|
||||||
|
|
||||||
|
commands[1] = encoder.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Submit(1, &commands[1]);
|
||||||
|
queue.Submit(1, &commands[0]);
|
||||||
|
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DrawIndexedIndirectTest, ValidateReusedBundleWithChangingParams) {
|
||||||
|
// TODO(crbug.com/dawn/789): Test is failing under SwANGLE on Windows.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows());
|
||||||
|
|
||||||
|
// TODO(crbug.com/dawn/1124): Fails on Intel+Vulkan+Windows for drivers
|
||||||
|
// older than 27.20.100.8587, which bots are actively using.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsIntel() && IsVulkan() && IsWindows());
|
||||||
|
|
||||||
|
// It doesn't make sense to test invalid inputs when validation is disabled.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("skip_validation"));
|
||||||
|
|
||||||
|
RGBA8 filled(0, 255, 0, 255);
|
||||||
|
// RGBA8 notFilled(0, 0, 0, 0);
|
||||||
|
|
||||||
|
wgpu::Buffer indirectBuffer = CreateIndirectBuffer({0, 0, 0, 0, 0});
|
||||||
|
wgpu::Buffer indexBuffer = CreateIndexBuffer({0, 1, 2, 0, 3, 1});
|
||||||
|
|
||||||
|
// Encode a single bundle that always uses indirectBuffer offset 0 for its params.
|
||||||
|
wgpu::RenderBundle bundle;
|
||||||
|
utils::ComboRenderBundleEncoderDescriptor desc = {};
|
||||||
|
desc.colorFormatsCount = 1;
|
||||||
|
desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
wgpu::RenderBundleEncoder bundleEncoder = device.CreateRenderBundleEncoder(&desc);
|
||||||
|
bundleEncoder.SetPipeline(pipeline);
|
||||||
|
bundleEncoder.SetVertexBuffer(0, vertexBuffer);
|
||||||
|
bundleEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32, 0);
|
||||||
|
bundleEncoder.DrawIndexedIndirect(indirectBuffer, 0);
|
||||||
|
bundle = bundleEncoder.Finish();
|
||||||
|
|
||||||
|
wgpu::ShaderModule paramWriterModule = utils::CreateShaderModule(device,
|
||||||
|
R"(
|
||||||
|
[[block]] struct Input { firstIndex: u32; };
|
||||||
|
[[block]] struct Params {
|
||||||
|
indexCount: u32;
|
||||||
|
instanceCount: u32;
|
||||||
|
firstIndex: u32;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]] var<uniform> input: Input;
|
||||||
|
[[group(0), binding(1)]] var<storage, write> params: Params;
|
||||||
|
[[stage(compute), workgroup_size(1)]] fn main() {
|
||||||
|
params.indexCount = 3u;
|
||||||
|
params.instanceCount = 1u;
|
||||||
|
params.firstIndex = input.firstIndex;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
wgpu::ComputePipelineDescriptor computeDesc;
|
||||||
|
computeDesc.compute.module = paramWriterModule;
|
||||||
|
computeDesc.compute.entryPoint = "main";
|
||||||
|
wgpu::ComputePipeline computePipeline = device.CreateComputePipeline(&computeDesc);
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
|
||||||
|
auto encodeComputePassToUpdateFirstIndex = [&](uint32_t newFirstIndex) {
|
||||||
|
wgpu::Buffer input = utils::CreateBufferFromData<uint32_t>(
|
||||||
|
device, wgpu::BufferUsage::Uniform, {newFirstIndex});
|
||||||
|
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
|
||||||
|
device, computePipeline.GetBindGroupLayout(0),
|
||||||
|
{{0, input, 0, sizeof(uint32_t)}, {1, indirectBuffer, 0, 5 * sizeof(uint32_t)}});
|
||||||
|
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||||
|
pass.SetPipeline(computePipeline);
|
||||||
|
pass.SetBindGroup(0, bindGroup);
|
||||||
|
pass.Dispatch(1);
|
||||||
|
pass.EndPass();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto encodeRenderPassToExecuteBundle = [&](wgpu::LoadOp colorLoadOp) {
|
||||||
|
renderPass.renderPassInfo.cColorAttachments[0].loadOp = colorLoadOp;
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||||
|
pass.ExecuteBundles(1, &bundle);
|
||||||
|
pass.EndPass();
|
||||||
|
};
|
||||||
|
|
||||||
|
encodeComputePassToUpdateFirstIndex(0);
|
||||||
|
encodeRenderPassToExecuteBundle(wgpu::LoadOp::Clear);
|
||||||
|
encodeComputePassToUpdateFirstIndex(3);
|
||||||
|
encodeRenderPassToExecuteBundle(wgpu::LoadOp::Load);
|
||||||
|
encodeComputePassToUpdateFirstIndex(6);
|
||||||
|
encodeRenderPassToExecuteBundle(wgpu::LoadOp::Load);
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(DrawIndexedIndirectTest,
|
DAWN_INSTANTIATE_TEST(DrawIndexedIndirectTest,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
|
@ -28,87 +28,6 @@ class UnsafeAPIValidationTest : public ValidationTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check that DrawIndexedIndirect is disallowed as part of unsafe APIs.
|
|
||||||
TEST_F(UnsafeAPIValidationTest, DrawIndexedIndirectDisallowed) {
|
|
||||||
// Create the index and indirect buffers.
|
|
||||||
wgpu::BufferDescriptor indexBufferDesc;
|
|
||||||
indexBufferDesc.size = 4;
|
|
||||||
indexBufferDesc.usage = wgpu::BufferUsage::Index;
|
|
||||||
wgpu::Buffer indexBuffer = device.CreateBuffer(&indexBufferDesc);
|
|
||||||
|
|
||||||
wgpu::BufferDescriptor indirectBufferDesc;
|
|
||||||
indirectBufferDesc.size = 64;
|
|
||||||
indirectBufferDesc.usage = wgpu::BufferUsage::Indirect;
|
|
||||||
wgpu::Buffer indirectBuffer = device.CreateBuffer(&indirectBufferDesc);
|
|
||||||
|
|
||||||
// The RenderPassDescriptor, RenderBundleDescriptor and pipeline for all sub-tests below.
|
|
||||||
DummyRenderPass renderPass(device);
|
|
||||||
|
|
||||||
utils::ComboRenderBundleEncoderDescriptor bundleDesc = {};
|
|
||||||
bundleDesc.colorFormatsCount = 1;
|
|
||||||
bundleDesc.cColorFormats[0] = renderPass.attachmentFormat;
|
|
||||||
|
|
||||||
utils::ComboRenderPipelineDescriptor desc;
|
|
||||||
desc.vertex.module = utils::CreateShaderModule(
|
|
||||||
device,
|
|
||||||
R"([[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
|
|
||||||
return vec4<f32>();
|
|
||||||
})");
|
|
||||||
desc.cFragment.module = utils::CreateShaderModule(device, "[[stage(fragment)]] fn main() {}");
|
|
||||||
desc.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
|
|
||||||
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
|
|
||||||
|
|
||||||
// Control cases: DrawIndirect and DrawIndexed are allowed inside a render pass.
|
|
||||||
{
|
|
||||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
|
||||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
|
||||||
pass.SetPipeline(pipeline);
|
|
||||||
|
|
||||||
pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
|
|
||||||
pass.DrawIndexed(1);
|
|
||||||
|
|
||||||
pass.DrawIndirect(indirectBuffer, 0);
|
|
||||||
pass.EndPass();
|
|
||||||
encoder.Finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Control case: DrawIndirect and DrawIndexed are allowed inside a render bundle.
|
|
||||||
{
|
|
||||||
wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&bundleDesc);
|
|
||||||
encoder.SetPipeline(pipeline);
|
|
||||||
|
|
||||||
encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
|
|
||||||
encoder.DrawIndexed(1);
|
|
||||||
|
|
||||||
encoder.DrawIndirect(indirectBuffer, 0);
|
|
||||||
encoder.Finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case, DrawIndexedIndirect is disallowed inside a render pass.
|
|
||||||
{
|
|
||||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
|
||||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
|
||||||
|
|
||||||
pass.SetPipeline(pipeline);
|
|
||||||
pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
|
|
||||||
pass.DrawIndexedIndirect(indirectBuffer, 0);
|
|
||||||
|
|
||||||
pass.EndPass();
|
|
||||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case, DrawIndexedIndirect is disallowed inside a render bundle.
|
|
||||||
{
|
|
||||||
wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&bundleDesc);
|
|
||||||
|
|
||||||
encoder.SetPipeline(pipeline);
|
|
||||||
encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
|
|
||||||
encoder.DrawIndexedIndirect(indirectBuffer, 0);
|
|
||||||
|
|
||||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that DispatchIndirect is disallowed as part of unsafe APIs.
|
// Check that DispatchIndirect is disallowed as part of unsafe APIs.
|
||||||
TEST_F(UnsafeAPIValidationTest, DispatchIndirectDisallowed) {
|
TEST_F(UnsafeAPIValidationTest, DispatchIndirectDisallowed) {
|
||||||
// Create the index and indirect buffers.
|
// Create the index and indirect buffers.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user