From 7eb6be186be60a536a9f529b514bb5c5fd79293d Mon Sep 17 00:00:00 2001 From: Idan Raiter Date: Fri, 7 Jun 2019 17:32:37 +0000 Subject: [PATCH] dawn_native: Indirect draw/dispatch Adds indirect draw and dispatch for all backends (without validation). Tests for opengl negative offset are skipped since there is no easy way to add the index buffer offset. Current idea is to use a compute shader to modify the indirect draw buffer. Change-Id: I1d3eec7c699b211423f4b911769cca17bfbcd045 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7481 Commit-Queue: Idan Raiter Reviewed-by: Kai Ninomiya --- BUILD.gn | 5 + dawn.json | 24 ++- src/common/Constants.h | 4 + src/dawn_native/CommandEncoder.cpp | 21 +++ src/dawn_native/Commands.cpp | 24 +++ src/dawn_native/Commands.h | 18 ++ src/dawn_native/ComputePassEncoder.cpp | 20 +++ src/dawn_native/ComputePassEncoder.h | 1 + src/dawn_native/RenderPassEncoder.cpp | 37 ++++ src/dawn_native/RenderPassEncoder.h | 3 + src/dawn_native/d3d12/CommandBufferD3D12.cpp | 35 ++++ src/dawn_native/d3d12/DeviceD3D12.cpp | 40 +++++ src/dawn_native/d3d12/DeviceD3D12.h | 8 + src/dawn_native/metal/CommandBufferMTL.mm | 34 ++++ src/dawn_native/opengl/CommandBufferGL.cpp | 43 +++++ src/dawn_native/vulkan/CommandBufferVk.cpp | 30 ++++ src/tests/end2end/ComputeIndirectTests.cpp | 131 ++++++++++++++ .../end2end/DrawIndexedIndirectTests.cpp | 165 ++++++++++++++++++ src/tests/end2end/DrawIndirectTests.cpp | 128 ++++++++++++++ .../ComputeIndirectValidationTests.cpp | 90 ++++++++++ .../DrawIndirectValidationTests.cpp | 143 +++++++++++++++ 21 files changed, 1003 insertions(+), 1 deletion(-) create mode 100644 src/tests/end2end/ComputeIndirectTests.cpp create mode 100644 src/tests/end2end/DrawIndexedIndirectTests.cpp create mode 100644 src/tests/end2end/DrawIndirectTests.cpp create mode 100644 src/tests/unittests/validation/ComputeIndirectValidationTests.cpp create mode 100644 src/tests/unittests/validation/DrawIndirectValidationTests.cpp diff --git a/BUILD.gn b/BUILD.gn index b66d8079e3..7b3e78a93e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -568,9 +568,11 @@ test("dawn_unittests") { "src/tests/unittests/validation/BindGroupValidationTests.cpp", "src/tests/unittests/validation/BufferValidationTests.cpp", "src/tests/unittests/validation/CommandBufferValidationTests.cpp", + "src/tests/unittests/validation/ComputeIndirectValidationTests.cpp", "src/tests/unittests/validation/ComputeValidationTests.cpp", "src/tests/unittests/validation/CopyCommandsValidationTests.cpp", "src/tests/unittests/validation/DebugMarkerValidationTests.cpp", + "src/tests/unittests/validation/DrawIndirectValidationTests.cpp", "src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp", "src/tests/unittests/validation/FenceValidationTests.cpp", "src/tests/unittests/validation/QueueSubmitValidationTests.cpp", @@ -632,12 +634,15 @@ test("dawn_end2end_tests") { "src/tests/end2end/ClipSpaceTests.cpp", "src/tests/end2end/ColorStateTests.cpp", "src/tests/end2end/ComputeCopyStorageBufferTests.cpp", + "src/tests/end2end/ComputeIndirectTests.cpp", "src/tests/end2end/ComputeSharedMemoryTests.cpp", "src/tests/end2end/CopyTests.cpp", "src/tests/end2end/DebugMarkerTests.cpp", "src/tests/end2end/DepthStencilStateTests.cpp", "src/tests/end2end/DestroyTests.cpp", + "src/tests/end2end/DrawIndexedIndirectTests.cpp", "src/tests/end2end/DrawIndexedTests.cpp", + "src/tests/end2end/DrawIndirectTests.cpp", "src/tests/end2end/DrawTests.cpp", "src/tests/end2end/DynamicBufferOffsetTests.cpp", "src/tests/end2end/FenceTests.cpp", diff --git a/dawn.json b/dawn.json index 8ed7ed56c8..a0fa36bd01 100644 --- a/dawn.json +++ b/dawn.json @@ -204,7 +204,8 @@ {"value": 16, "name": "index"}, {"value": 32, "name": "vertex"}, {"value": 64, "name": "uniform"}, - {"value": 128, "name": "storage"} + {"value": 128, "name": "storage"}, + {"value": 256, "name": "indirect"} ] }, "char": { @@ -353,6 +354,13 @@ {"name": "z", "type": "uint32_t"} ] }, + { + "name": "dispatch indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, { "name": "end pass", "TODO": "This returns the top-level encoder in the WebGPU IDL" @@ -764,6 +772,20 @@ {"name": "first instance", "type": "uint32_t"} ] }, + { + "name": "draw indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "draw indexed indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, { "name": "insert debug marker", "args": [ diff --git a/src/common/Constants.h b/src/common/Constants.h index 7924a70e2e..c2fa5c2d6a 100644 --- a/src/common/Constants.h +++ b/src/common/Constants.h @@ -35,6 +35,10 @@ static constexpr uint32_t kMaxColorAttachments = 4u; static constexpr uint32_t kTextureRowPitchAlignment = 256u; // Dynamic buffer offsets require offset to be divisible by 256 static constexpr uint64_t kMinDynamicBufferOffsetAlignment = 256u; +// Indirect command sizes +static constexpr uint64_t kDispatchIndirectSize = 3 * sizeof(uint32_t); +static constexpr uint64_t kDrawIndirectSize = 4 * sizeof(uint32_t); +static constexpr uint64_t kDrawIndexedIndirectSize = 5 * sizeof(uint32_t); // Non spec defined constants. static constexpr float kLodMin = 0.0; diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index f2d42691ba..acb17d34d1 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -999,6 +999,13 @@ namespace dawn_native { DAWN_TRY(persistentState.ValidateCanDispatch()); } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* cmd = mIterator.NextCommand(); + DAWN_TRY(persistentState.ValidateCanDispatch()); + usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(), + dawn::BufferUsageBit::Indirect); + } break; + case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = mIterator.NextCommand(); mIterator.NextData(cmd->length + 1); @@ -1085,6 +1092,20 @@ namespace dawn_native { DAWN_TRY(persistentState.ValidateCanDrawIndexed()); } break; + case Command::DrawIndirect: { + DrawIndirectCmd* cmd = mIterator.NextCommand(); + DAWN_TRY(persistentState.ValidateCanDraw()); + usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(), + dawn::BufferUsageBit::Indirect); + } break; + + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* cmd = mIterator.NextCommand(); + DAWN_TRY(persistentState.ValidateCanDrawIndexed()); + usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(), + dawn::BufferUsageBit::Indirect); + } break; + case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = mIterator.NextCommand(); mIterator.NextData(cmd->length + 1); diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index 512e7e4191..93f7ddfc97 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -58,6 +58,10 @@ namespace dawn_native { DispatchCmd* dispatch = commands->NextCommand(); dispatch->~DispatchCmd(); } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = commands->NextCommand(); + dispatch->~DispatchIndirectCmd(); + } break; case Command::Draw: { DrawCmd* draw = commands->NextCommand(); draw->~DrawCmd(); @@ -66,6 +70,14 @@ namespace dawn_native { DrawIndexedCmd* draw = commands->NextCommand(); draw->~DrawIndexedCmd(); } break; + case Command::DrawIndirect: { + DrawIndirectCmd* draw = commands->NextCommand(); + draw->~DrawIndirectCmd(); + } break; + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* draw = commands->NextCommand(); + draw->~DrawIndexedIndirectCmd(); + } break; case Command::EndComputePass: { EndComputePassCmd* cmd = commands->NextCommand(); cmd->~EndComputePassCmd(); @@ -163,6 +175,10 @@ namespace dawn_native { commands->NextCommand(); break; + case Command::DispatchIndirect: + commands->NextCommand(); + break; + case Command::Draw: commands->NextCommand(); break; @@ -171,6 +187,14 @@ namespace dawn_native { commands->NextCommand(); break; + case Command::DrawIndirect: + commands->NextCommand(); + break; + + case Command::DrawIndexedIndirect: + commands->NextCommand(); + break; + case Command::EndComputePass: commands->NextCommand(); break; diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 53d6047c47..04ae7f58a6 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -38,8 +38,11 @@ namespace dawn_native { CopyTextureToBuffer, CopyTextureToTexture, Dispatch, + DispatchIndirect, Draw, DrawIndexed, + DrawIndirect, + DrawIndexedIndirect, EndComputePass, EndRenderPass, InsertDebugMarker, @@ -133,6 +136,11 @@ namespace dawn_native { uint32_t z; }; + struct DispatchIndirectCmd { + Ref indirectBuffer; + uint64_t indirectOffset; + }; + struct DrawCmd { uint32_t vertexCount; uint32_t instanceCount; @@ -148,6 +156,16 @@ namespace dawn_native { uint32_t firstInstance; }; + struct DrawIndirectCmd { + Ref indirectBuffer; + uint64_t indirectOffset; + }; + + struct DrawIndexedIndirectCmd { + Ref indirectBuffer; + uint64_t indirectOffset; + }; + struct EndComputePassCmd {}; struct EndRenderPassCmd {}; diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp index 1217b92897..708b2cb01b 100644 --- a/src/dawn_native/ComputePassEncoder.cpp +++ b/src/dawn_native/ComputePassEncoder.cpp @@ -14,6 +14,7 @@ #include "dawn_native/ComputePassEncoder.h" +#include "dawn_native/Buffer.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" #include "dawn_native/ComputePipeline.h" @@ -49,6 +50,25 @@ namespace dawn_native { dispatch->z = z; } + void ComputePassEncoderBase::DispatchIndirect(BufferBase* indirectBuffer, + uint64_t indirectOffset) { + if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || + mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) { + return; + } + + if (indirectOffset >= indirectBuffer->GetSize() || + indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize()) { + mTopLevelEncoder->HandleError("Indirect offset out of bounds"); + return; + } + + DispatchIndirectCmd* dispatch = + mAllocator->Allocate(Command::DispatchIndirect); + dispatch->indirectBuffer = indirectBuffer; + dispatch->indirectOffset = indirectOffset; + } + void ComputePassEncoderBase::SetPipeline(ComputePipelineBase* pipeline) { if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(pipeline))) { diff --git a/src/dawn_native/ComputePassEncoder.h b/src/dawn_native/ComputePassEncoder.h index a45dad5e87..36a7e31e64 100644 --- a/src/dawn_native/ComputePassEncoder.h +++ b/src/dawn_native/ComputePassEncoder.h @@ -34,6 +34,7 @@ namespace dawn_native { CommandEncoderBase* topLevelEncoder); void Dispatch(uint32_t x, uint32_t y, uint32_t z); + void DispatchIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); void SetPipeline(ComputePipelineBase* pipeline); protected: diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp index c83666b28d..c3820d0e01 100644 --- a/src/dawn_native/RenderPassEncoder.cpp +++ b/src/dawn_native/RenderPassEncoder.cpp @@ -14,6 +14,7 @@ #include "dawn_native/RenderPassEncoder.h" +#include "common/Constants.h" #include "dawn_native/Buffer.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" @@ -73,6 +74,42 @@ namespace dawn_native { draw->firstInstance = firstInstance; } + void RenderPassEncoderBase::DrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset) { + if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || + mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) { + return; + } + + if (indirectOffset >= indirectBuffer->GetSize() || + indirectOffset + kDrawIndirectSize > indirectBuffer->GetSize()) { + mTopLevelEncoder->HandleError("Indirect offset out of bounds"); + return; + } + + DrawIndirectCmd* cmd = mAllocator->Allocate(Command::DrawIndirect); + cmd->indirectBuffer = indirectBuffer; + cmd->indirectOffset = indirectOffset; + } + + void RenderPassEncoderBase::DrawIndexedIndirect(BufferBase* indirectBuffer, + uint64_t indirectOffset) { + if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || + mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) { + return; + } + + if (indirectOffset >= indirectBuffer->GetSize() || + indirectOffset + kDrawIndexedIndirectSize > indirectBuffer->GetSize()) { + mTopLevelEncoder->HandleError("Indirect offset out of bounds"); + return; + } + + DrawIndexedIndirectCmd* cmd = + mAllocator->Allocate(Command::DrawIndexedIndirect); + cmd->indirectBuffer = indirectBuffer; + cmd->indirectOffset = indirectOffset; + } + void RenderPassEncoderBase::SetPipeline(RenderPipelineBase* pipeline) { if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(pipeline))) { diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h index dc7b54fedc..c6029c5d7d 100644 --- a/src/dawn_native/RenderPassEncoder.h +++ b/src/dawn_native/RenderPassEncoder.h @@ -43,6 +43,9 @@ namespace dawn_native { int32_t baseVertex, uint32_t firstInstance); + void DrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); + void DrawIndexedIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); + void SetPipeline(RenderPipelineBase* pipeline); void SetStencilReference(uint32_t reference); diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index eed39632a0..98afe265b1 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -684,6 +684,17 @@ namespace dawn_native { namespace d3d12 { commandList->Dispatch(dispatch->x, dispatch->y, dispatch->z); } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = mCommands.NextCommand(); + + Buffer* buffer = ToBackend(dispatch->indirectBuffer.Get()); + ComPtr signature = + ToBackend(GetDevice())->GetDispatchIndirectSignature(); + commandList->ExecuteIndirect(signature.Get(), 1, + buffer->GetD3D12Resource().Get(), + dispatch->indirectOffset, nullptr, 0); + } break; + case Command::EndComputePass: { mCommands.NextCommand(); return; @@ -818,6 +829,30 @@ namespace dawn_native { namespace d3d12 { draw->firstInstance); } break; + case Command::DrawIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + + FlushSetVertexBuffers(commandList, &vertexBuffersInfo, lastPipeline); + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + ComPtr signature = + ToBackend(GetDevice())->GetDrawIndirectSignature(); + commandList->ExecuteIndirect(signature.Get(), 1, + buffer->GetD3D12Resource().Get(), + draw->indirectOffset, nullptr, 0); + } break; + + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* draw = mCommands.NextCommand(); + + FlushSetVertexBuffers(commandList, &vertexBuffersInfo, lastPipeline); + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + ComPtr signature = + ToBackend(GetDevice())->GetDrawIndexedIndirectSignature(); + commandList->ExecuteIndirect(signature.Get(), 1, + buffer->GetD3D12Resource().Get(), + draw->indirectOffset, nullptr, 0); + } break; + case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = mCommands.NextCommand(); const char* label = mCommands.NextData(cmd->length + 1); diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index a13f5b5008..651323e2b7 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -68,6 +68,34 @@ namespace dawn_native { namespace d3d12 { mResourceAllocator = std::make_unique(this); NextSerial(); + + // Initialize indirect commands + D3D12_INDIRECT_ARGUMENT_DESC argumentDesc = {}; + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + + D3D12_COMMAND_SIGNATURE_DESC programDesc = {}; + programDesc.ByteStride = 3 * sizeof(uint32_t); + programDesc.NumArgumentDescs = 1; + programDesc.pArgumentDescs = &argumentDesc; + + ToBackend(GetDevice()) + ->GetD3D12Device() + ->CreateCommandSignature(&programDesc, NULL, IID_PPV_ARGS(&mDispatchIndirectSignature)); + + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; + programDesc.ByteStride = 4 * sizeof(uint32_t); + + ToBackend(GetDevice()) + ->GetD3D12Device() + ->CreateCommandSignature(&programDesc, NULL, IID_PPV_ARGS(&mDrawIndirectSignature)); + + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + programDesc.ByteStride = 5 * sizeof(uint32_t); + + ToBackend(GetDevice()) + ->GetD3D12Device() + ->CreateCommandSignature(&programDesc, NULL, + IID_PPV_ARGS(&mDrawIndexedIndirectSignature)); } Device::~Device() { @@ -101,6 +129,18 @@ namespace dawn_native { namespace d3d12 { return mCommandQueue; } + ComPtr Device::GetDispatchIndirectSignature() const { + return mDispatchIndirectSignature; + } + + ComPtr Device::GetDrawIndirectSignature() const { + return mDrawIndirectSignature; + } + + ComPtr Device::GetDrawIndexedIndirectSignature() const { + return mDrawIndexedIndirectSignature; + } + DescriptorHeapAllocator* Device::GetDescriptorHeapAllocator() const { return mDescriptorHeapAllocator.get(); } diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h index 528758857d..18efaabc69 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.h +++ b/src/dawn_native/d3d12/DeviceD3D12.h @@ -51,6 +51,10 @@ namespace dawn_native { namespace d3d12 { ComPtr GetD3D12Device() const; ComPtr GetCommandQueue() const; + ComPtr GetDispatchIndirectSignature() const; + ComPtr GetDrawIndirectSignature() const; + ComPtr GetDrawIndexedIndirectSignature() const; + DescriptorHeapAllocator* GetDescriptorHeapAllocator() const; MapRequestTracker* GetMapRequestTracker() const; ResourceAllocator* GetResourceAllocator() const; @@ -107,6 +111,10 @@ namespace dawn_native { namespace d3d12 { ComPtr mD3d12Device; ComPtr mCommandQueue; + ComPtr mDispatchIndirectSignature; + ComPtr mDrawIndirectSignature; + ComPtr mDrawIndexedIndirectSignature; + struct PendingCommandList { ComPtr commandList; bool open = false; diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 7cc8897d58..65cf7264fc 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -645,6 +645,17 @@ namespace dawn_native { namespace metal { threadsPerThreadgroup:lastPipeline->GetLocalWorkGroupSize()]; } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = mCommands.NextCommand(); + + Buffer* buffer = ToBackend(dispatch->indirectBuffer.Get()); + id indirectBuffer = buffer->GetMTLBuffer(); + [encoder dispatchThreadgroupsWithIndirectBuffer:indirectBuffer + indirectBufferOffset:dispatch->indirectOffset + threadsPerThreadgroup:lastPipeline + ->GetLocalWorkGroupSize()]; + } break; + case Command::SetComputePipeline: { SetComputePipelineCmd* cmd = mCommands.NextCommand(); lastPipeline = ToBackend(cmd->pipeline).Get(); @@ -817,6 +828,29 @@ namespace dawn_native { namespace metal { } } break; + case Command::DrawIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + id indirectBuffer = buffer->GetMTLBuffer(); + [encoder drawPrimitives:lastPipeline->GetMTLPrimitiveTopology() + indirectBuffer:indirectBuffer + indirectBufferOffset:draw->indirectOffset]; + } break; + + case Command::DrawIndexedIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + id indirectBuffer = buffer->GetMTLBuffer(); + [encoder drawIndexedPrimitives:lastPipeline->GetMTLPrimitiveTopology() + indexType:lastPipeline->GetMTLIndexType() + indexBuffer:indexBuffer + indexBufferOffset:indexBufferBaseOffset + indirectBuffer:indirectBuffer + indirectBufferOffset:draw->indirectOffset]; + } break; + case Command::InsertDebugMarker: { InsertDebugMarkerCmd* cmd = mCommands.NextCommand(); auto label = mCommands.NextData(cmd->length + 1); diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 7c5a8dc9de..8c93a82904 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -499,6 +499,19 @@ namespace dawn_native { namespace opengl { glMemoryBarrier(GL_ALL_BARRIER_BITS); } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = mCommands.NextCommand(); + + uint64_t indirectBufferOffset = dispatch->indirectOffset; + Buffer* indirectBuffer = ToBackend(dispatch->indirectBuffer.Get()); + + glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, indirectBuffer->GetHandle()); + glDispatchComputeIndirect( + reinterpret_cast(static_cast(indirectBufferOffset))); + // TODO(cwallez@chromium.org): add barriers to the API + glMemoryBarrier(GL_ALL_BARRIER_BITS); + } break; + case Command::SetComputePipeline: { SetComputePipelineCmd* cmd = mCommands.NextCommand(); lastPipeline = ToBackend(cmd->pipeline).Get(); @@ -706,6 +719,36 @@ namespace dawn_native { namespace opengl { } } break; + case Command::DrawIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + inputBuffers.Apply(); + + uint64_t indirectBufferOffset = draw->indirectOffset; + Buffer* indirectBuffer = ToBackend(draw->indirectBuffer.Get()); + + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer->GetHandle()); + glDrawArraysIndirect( + lastPipeline->GetGLPrimitiveTopology(), + reinterpret_cast(static_cast(indirectBufferOffset))); + } break; + + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* draw = mCommands.NextCommand(); + inputBuffers.Apply(); + + dawn::IndexFormat indexFormat = + lastPipeline->GetVertexInputDescriptor()->indexFormat; + GLenum formatType = IndexFormatType(indexFormat); + + uint64_t indirectBufferOffset = draw->indirectOffset; + Buffer* indirectBuffer = ToBackend(draw->indirectBuffer.Get()); + + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer->GetHandle()); + glDrawElementsIndirect( + lastPipeline->GetGLPrimitiveTopology(), formatType, + reinterpret_cast(static_cast(indirectBufferOffset))); + } break; + case Command::InsertDebugMarker: case Command::PopDebugGroup: case Command::PushDebugGroup: { diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index a04bec72e8..73c7a4f6b9 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -425,6 +425,16 @@ namespace dawn_native { namespace vulkan { device->fn.CmdDispatch(commands, dispatch->x, dispatch->y, dispatch->z); } break; + case Command::DispatchIndirect: { + DispatchIndirectCmd* Dispatch = mCommands.NextCommand(); + VkBuffer indirectBuffer = ToBackend(Dispatch->indirectBuffer)->GetHandle(); + + descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_COMPUTE); + device->fn.CmdDispatchIndirect( + commands, indirectBuffer, + static_cast(Dispatch->indirectOffset)); + } break; + case Command::SetBindGroup: { SetBindGroupCmd* cmd = mCommands.NextCommand(); VkDescriptorSet set = ToBackend(cmd->group.Get())->GetHandle(); @@ -521,6 +531,26 @@ namespace dawn_native { namespace vulkan { draw->firstInstance); } break; + case Command::DrawIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + VkBuffer indirectBuffer = ToBackend(draw->indirectBuffer)->GetHandle(); + + descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_GRAPHICS); + device->fn.CmdDrawIndirect(commands, indirectBuffer, + static_cast(draw->indirectOffset), 1, + 0); + } break; + + case Command::DrawIndexedIndirect: { + DrawIndirectCmd* draw = mCommands.NextCommand(); + VkBuffer indirectBuffer = ToBackend(draw->indirectBuffer)->GetHandle(); + + descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_GRAPHICS); + device->fn.CmdDrawIndexedIndirect( + commands, indirectBuffer, static_cast(draw->indirectOffset), + 1, 0); + } break; + case Command::InsertDebugMarker: { if (device->GetDeviceInfo().debugMarker) { InsertDebugMarkerCmd* cmd = mCommands.NextCommand(); diff --git a/src/tests/end2end/ComputeIndirectTests.cpp b/src/tests/end2end/ComputeIndirectTests.cpp new file mode 100644 index 0000000000..5dea851a7c --- /dev/null +++ b/src/tests/end2end/ComputeIndirectTests.cpp @@ -0,0 +1,131 @@ +// Copyright 2019 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/dawncpp.h" +#include "tests/DawnTest.h" + +#include "utils/DawnHelpers.h" + +#include +#include + +class ComputeIndirectTests : public DawnTest { + public: + // Write into the output buffer if we saw the biggest dispatch + // This is a workaround since D3D12 doesn't have gl_NumWorkGroups + const char* shaderSource = R"( + #version 450 + layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + layout(std140, set = 0, binding = 0) uniform inputBuf { + uvec3 expectedDispatch; + }; + layout(std140, set = 0, binding = 1) buffer outputBuf { + uvec3 workGroups; + }; + + void main() { + if (gl_GlobalInvocationID == expectedDispatch - uvec3(1, 1, 1)) { + workGroups = expectedDispatch; + } + })"; + + void BasicTest(std::initializer_list buffer, uint64_t indirectOffset); +}; + +void ComputeIndirectTests::BasicTest(std::initializer_list bufferList, + uint64_t indirectOffset) { + dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, { + {0, dawn::ShaderStageBit::Compute, dawn::BindingType::UniformBuffer}, + {1, dawn::ShaderStageBit::Compute, dawn::BindingType::StorageBuffer}, + }); + + // Set up shader and pipeline + dawn::ShaderModule module = + utils::CreateShaderModule(device, dawn::ShaderStage::Compute, shaderSource); + dawn::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl); + + dawn::ComputePipelineDescriptor csDesc; + csDesc.layout = pl; + + dawn::PipelineStageDescriptor computeStage; + computeStage.module = module; + computeStage.entryPoint = "main"; + csDesc.computeStage = &computeStage; + + dawn::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); + + // Set up dst storage buffer to contain dispatch x, y, z + dawn::Buffer dst = utils::CreateBufferFromData(device, + dawn::BufferUsageBit::Storage | + dawn::BufferUsageBit::TransferSrc | + dawn::BufferUsageBit::TransferDst, + {0, 0, 0}); + + std::vector indirectBufferData = bufferList; + + dawn::Buffer indirectBuffer = + utils::CreateBufferFromData(device, dawn::BufferUsageBit::Indirect, bufferList); + + dawn::Buffer expectedBuffer = + utils::CreateBufferFromData(device, &indirectBufferData[indirectOffset / sizeof(uint32_t)], + 3 * sizeof(uint32_t), dawn::BufferUsageBit::Uniform); + + // Set up bind group and issue dispatch + dawn::BindGroup bindGroup = + utils::MakeBindGroup(device, bgl, + { + {0, expectedBuffer, 0, 3 * sizeof(uint32_t)}, + {1, dst, 0, 3 * sizeof(uint32_t)}, + }); + + dawn::CommandBuffer commands; + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup, 0, nullptr); + pass.DispatchIndirect(indirectBuffer, indirectOffset); + pass.EndPass(); + + commands = encoder.Finish(); + } + + queue.Submit(1, &commands); + + // Verify the dispatch got called with group counts in indirect buffer + EXPECT_BUFFER_U32_RANGE_EQ(&indirectBufferData[indirectOffset / sizeof(uint32_t)], dst, 0, 3); +} + +// Test basic indirect +TEST_P(ComputeIndirectTests, Basic) { + // See https://bugs.chromium.org/p/dawn/issues/detail?id=159 + DAWN_SKIP_TEST_IF(IsD3D12() && IsNvidia()); + + BasicTest({2, 3, 4}, 0); +} + +// Test indirect with buffer offset +TEST_P(ComputeIndirectTests, IndirectOffset) { + // See https://bugs.chromium.org/p/dawn/issues/detail?id=159 + DAWN_SKIP_TEST_IF(IsD3D12() && IsNvidia()); + + BasicTest({0, 0, 0, 2, 3, 4}, 3 * sizeof(uint32_t)); +} + +DAWN_INSTANTIATE_TEST(ComputeIndirectTests, + D3D12Backend, + MetalBackend, + OpenGLBackend, + VulkanBackend); diff --git a/src/tests/end2end/DrawIndexedIndirectTests.cpp b/src/tests/end2end/DrawIndexedIndirectTests.cpp new file mode 100644 index 0000000000..c525735949 --- /dev/null +++ b/src/tests/end2end/DrawIndexedIndirectTests.cpp @@ -0,0 +1,165 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/DawnTest.h" + +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/DawnHelpers.h" + +constexpr uint32_t kRTSize = 4; + +class DrawIndexedIndirectTest : public DawnTest { + protected: + void SetUp() override { + DawnTest::SetUp(); + + renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + dawn::ShaderModule vsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"( + #version 450 + layout(location = 0) in vec4 pos; + void main() { + gl_Position = pos; + })"); + + dawn::ShaderModule fsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, R"( + #version 450 + layout(location = 0) out vec4 fragColor; + void main() { + fragColor = vec4(0.0, 1.0, 0.0, 1.0); + })"); + + utils::ComboRenderPipelineDescriptor descriptor(device); + descriptor.cVertexStage.module = vsModule; + descriptor.cFragmentStage.module = fsModule; + descriptor.primitiveTopology = dawn::PrimitiveTopology::TriangleStrip; + descriptor.cVertexInput.bufferCount = 1; + descriptor.cVertexInput.cBuffers[0].stride = 4 * sizeof(float); + descriptor.cVertexInput.cBuffers[0].attributeCount = 1; + descriptor.cVertexInput.cAttributes[0].format = dawn::VertexFormat::Float4; + descriptor.cColorStates[0]->format = renderPass.colorFormat; + + pipeline = device.CreateRenderPipeline(&descriptor); + + vertexBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Vertex, + {// First quad: the first 3 vertices represent the bottom left 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, + 0.0f, 1.0f, + + // 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, + 0.0f, 1.0f}); + indexBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::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; + dawn::RenderPipeline pipeline; + dawn::Buffer vertexBuffer; + dawn::Buffer indexBuffer; + + void Test(std::initializer_list bufferList, + uint64_t indexOffset, + uint64_t indirectOffset, + RGBA8 bottomLeftExpected, + RGBA8 topRightExpected) { + dawn::Buffer indirectBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Indirect, bufferList); + + uint64_t zeroOffset = 0; + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.SetPipeline(pipeline); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.SetIndexBuffer(indexBuffer, indexOffset); + pass.DrawIndexedIndirect(indirectBuffer, indirectOffset); + pass.EndPass(); + } + + dawn::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(bottomLeftExpected, renderPass.color, 1, 3); + EXPECT_PIXEL_RGBA8_EQ(topRightExpected, renderPass.color, 3, 1); + } +}; + +// The most basic DrawIndexed triangle draw. +TEST_P(DrawIndexedIndirectTest, Uint32) { + RGBA8 filled(0, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + + // Test a draw with no indices. + Test({0, 0, 0, 0, 0}, 0, 0, notFilled, notFilled); + + // Test a draw with only the first 3 indices of the first quad (bottom left triangle) + Test({3, 1, 0, 0, 0}, 0, 0, filled, notFilled); + + // Test a draw with only the last 3 indices of the first quad (top right triangle) + Test({3, 1, 3, 0, 0}, 0, 0, notFilled, filled); + + // Test a draw with all 6 indices (both triangles). + Test({6, 1, 0, 0, 0}, 0, 0, filled, filled); +} + +// Test the parameter 'baseVertex' of DrawIndexed() works. +TEST_P(DrawIndexedIndirectTest, BaseVertex) { + // TODO(crbug.com/dawn/161): add workaround for OpenGL index buffer offset (could be compute + // shader that adds it to the draw calls) + DAWN_SKIP_TEST_IF(IsOpenGL()); + + RGBA8 filled(0, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + + // Test a draw with only the first 3 indices of the second quad (top right triangle) + Test({3, 1, 0, 4, 0}, 0, 0, notFilled, filled); + + // Test a draw with only the last 3 indices of the second quad (bottom left triangle) + Test({3, 1, 3, 4, 0}, 0, 0, filled, notFilled); + + // Test negative baseVertex + // Test a draw with only the first 3 indices of the first quad (bottom left triangle) + Test({3, 1, 0, -4, 0}, 6 * sizeof(uint32_t), 0, filled, notFilled); + + // Test a draw with only the last 3 indices of the first quad (top right triangle) + Test({3, 1, 3, -4, 0}, 6 * sizeof(uint32_t), 0, notFilled, filled); +} + +TEST_P(DrawIndexedIndirectTest, IndirectOffset) { + RGBA8 filled(0, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + + // Test an offset draw call, with indirect buffer containing 2 calls: + // 1) first 3 indices of the second quad (top right triangle) + // 2) last 3 indices of the second quad + + // Test #1 (no offset) + Test({3, 1, 0, 4, 0, 3, 1, 3, 4, 0}, 0, 0, notFilled, filled); + + // Offset to draw #2 + Test({3, 1, 0, 4, 0, 3, 1, 3, 4, 0}, 0, 5 * sizeof(uint32_t), filled, notFilled); +} + +DAWN_INSTANTIATE_TEST(DrawIndexedIndirectTest, + D3D12Backend, + MetalBackend, + OpenGLBackend, + VulkanBackend); diff --git a/src/tests/end2end/DrawIndirectTests.cpp b/src/tests/end2end/DrawIndirectTests.cpp new file mode 100644 index 0000000000..a27aac9095 --- /dev/null +++ b/src/tests/end2end/DrawIndirectTests.cpp @@ -0,0 +1,128 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/DawnTest.h" + +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/DawnHelpers.h" + +constexpr uint32_t kRTSize = 4; + +class DrawIndirectTest : public DawnTest { + protected: + void SetUp() override { + DawnTest::SetUp(); + + renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + dawn::ShaderModule vsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"( + #version 450 + layout(location = 0) in vec4 pos; + void main() { + gl_Position = pos; + })"); + + dawn::ShaderModule fsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, R"( + #version 450 + layout(location = 0) out vec4 fragColor; + void main() { + fragColor = vec4(0.0, 1.0, 0.0, 1.0); + })"); + + utils::ComboRenderPipelineDescriptor descriptor(device); + descriptor.cVertexStage.module = vsModule; + descriptor.cFragmentStage.module = fsModule; + descriptor.primitiveTopology = dawn::PrimitiveTopology::TriangleStrip; + descriptor.cVertexInput.bufferCount = 1; + descriptor.cVertexInput.cBuffers[0].stride = 4 * sizeof(float); + descriptor.cVertexInput.cBuffers[0].attributeCount = 1; + descriptor.cVertexInput.cAttributes[0].format = dawn::VertexFormat::Float4; + descriptor.cColorStates[0]->format = renderPass.colorFormat; + + pipeline = device.CreateRenderPipeline(&descriptor); + + vertexBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Vertex, + {// The bottom left 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, + + // 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}); + } + + utils::BasicRenderPass renderPass; + dawn::RenderPipeline pipeline; + dawn::Buffer vertexBuffer; + + void Test(std::initializer_list bufferList, + uint64_t indirectOffset, + RGBA8 bottomLeftExpected, + RGBA8 topRightExpected) { + dawn::Buffer indirectBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Indirect, bufferList); + + uint64_t zeroOffset = 0; + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.SetPipeline(pipeline); + pass.SetVertexBuffers(0, 1, &vertexBuffer, &zeroOffset); + pass.DrawIndirect(indirectBuffer, indirectOffset); + pass.EndPass(); + } + + dawn::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(bottomLeftExpected, renderPass.color, 1, 3); + EXPECT_PIXEL_RGBA8_EQ(topRightExpected, renderPass.color, 3, 1); + } +}; + +// The basic triangle draw. +TEST_P(DrawIndirectTest, Uint32) { + RGBA8 filled(0, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + + // Test a draw with no indices. + Test({0, 0, 0, 0}, 0, notFilled, notFilled); + + // Test a draw with only the first 3 indices (bottom left triangle) + Test({3, 1, 0, 0}, 0, filled, notFilled); + + // Test a draw with only the last 3 indices (top right triangle) + Test({3, 1, 3, 0}, 0, notFilled, filled); + + // Test a draw with all 6 indices (both triangles). + Test({6, 1, 0, 0}, 0, filled, filled); +} + +TEST_P(DrawIndirectTest, IndirectOffset) { + RGBA8 filled(0, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + + // Test an offset draw call, with indirect buffer containing 2 calls: + // 1) only the first 3 indices (bottom left triangle) + // 2) only the last 3 indices (top right triangle) + + // Test #1 (no offset) + Test({3, 1, 0, 0, 3, 1, 3, 0}, 0, filled, notFilled); + + // Offset to draw #2 + Test({3, 1, 0, 0, 3, 1, 3, 0}, 4 * sizeof(uint32_t), notFilled, filled); +} + +DAWN_INSTANTIATE_TEST(DrawIndirectTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend); diff --git a/src/tests/unittests/validation/ComputeIndirectValidationTests.cpp b/src/tests/unittests/validation/ComputeIndirectValidationTests.cpp new file mode 100644 index 0000000000..294b5b1f4a --- /dev/null +++ b/src/tests/unittests/validation/ComputeIndirectValidationTests.cpp @@ -0,0 +1,90 @@ +// Copyright 2019 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 +#include +#include "tests/unittests/validation/ValidationTest.h" +#include "utils/DawnHelpers.h" + +class ComputeIndirectValidationTest : public ValidationTest { + protected: + void SetUp() override { + ValidationTest::SetUp(); + + dawn::ShaderModule computeModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Compute, R"( + #version 450 + layout(local_size_x = 1) in; + void main() { + })"); + + // Set up compute pipeline + dawn::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, nullptr); + + dawn::ComputePipelineDescriptor csDesc; + csDesc.layout = pl; + + dawn::PipelineStageDescriptor computeStage; + computeStage.module = computeModule; + computeStage.entryPoint = "main"; + csDesc.computeStage = &computeStage; + + pipeline = device.CreateComputePipeline(&csDesc); + } + + void ValidateExpectation(dawn::CommandEncoder encoder, utils::Expectation expectation) { + if (expectation == utils::Expectation::Success) { + encoder.Finish(); + } else { + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + } + + void TestIndirectOffset(utils::Expectation expectation, + std::initializer_list bufferList, + uint64_t indirectOffset) { + dawn::Buffer indirectBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Indirect, bufferList); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(pipeline); + pass.DispatchIndirect(indirectBuffer, indirectOffset); + pass.EndPass(); + + ValidateExpectation(encoder, expectation); + } + + dawn::ComputePipeline pipeline; +}; + +// Verify out of bounds indirect dispatch calls are caught early +TEST_F(ComputeIndirectValidationTest, IndirectOffsetBounds) { + // In bounds + TestIndirectOffset(utils::Expectation::Success, {1, 2, 3}, 0); + // In bounds, bigger buffer + TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4, 5, 6}, 0); + // In bounds, bigger buffer, positive offset + TestIndirectOffset(utils::Expectation::Success, {1, 2, 3, 4, 5, 6}, 3 * sizeof(uint32_t)); + + // Out of bounds, buffer too small + TestIndirectOffset(utils::Expectation::Failure, {1, 2}, 0); + // Out of bounds, index too big + TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3}, 1 * sizeof(uint32_t)); + // Out of bounds, index past buffer + TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3}, 4 * sizeof(uint32_t)); + // Out of bounds, index + size of command overflows + uint64_t offset = std::numeric_limits::max(); + TestIndirectOffset(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6}, offset); +} diff --git a/src/tests/unittests/validation/DrawIndirectValidationTests.cpp b/src/tests/unittests/validation/DrawIndirectValidationTests.cpp new file mode 100644 index 0000000000..de3a3bbe59 --- /dev/null +++ b/src/tests/unittests/validation/DrawIndirectValidationTests.cpp @@ -0,0 +1,143 @@ +// Copyright 2019 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 +#include +#include "tests/unittests/validation/ValidationTest.h" +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/DawnHelpers.h" + +class DrawIndirectValidationTest : public ValidationTest { + protected: + void SetUp() override { + ValidationTest::SetUp(); + + dawn::ShaderModule vsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"( + #version 450 + void main() { + gl_Position = vec4(0.0); + })"); + + dawn::ShaderModule fsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, R"( + #version 450 + layout(location = 0) out vec4 fragColor; + void main() { + fragColor = vec4(0.0); + })"); + + // Set up render pipeline + dawn::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, nullptr); + + utils::ComboRenderPipelineDescriptor descriptor(device); + descriptor.layout = pipelineLayout; + descriptor.cVertexStage.module = vsModule; + descriptor.cFragmentStage.module = fsModule; + + pipeline = device.CreateRenderPipeline(&descriptor); + } + + void ValidateExpectation(dawn::CommandEncoder encoder, utils::Expectation expectation) { + if (expectation == utils::Expectation::Success) { + encoder.Finish(); + } else { + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + } + + void TestIndirectOffsetDrawIndexed(utils::Expectation expectation, + std::initializer_list bufferList, + uint64_t indirectOffset) { + TestIndirectOffset(expectation, bufferList, indirectOffset, true); + } + + void TestIndirectOffsetDraw(utils::Expectation expectation, + std::initializer_list bufferList, + uint64_t indirectOffset) { + TestIndirectOffset(expectation, bufferList, indirectOffset, false); + } + + void TestIndirectOffset(utils::Expectation expectation, + std::initializer_list bufferList, + uint64_t indirectOffset, + bool indexed) { + dawn::Buffer indirectBuffer = utils::CreateBufferFromData( + device, dawn::BufferUsageBit::Indirect, bufferList); + + DummyRenderPass renderPass(device); + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline); + if (indexed) { + uint32_t zeros[100] = {}; + dawn::Buffer indexBuffer = utils::CreateBufferFromData(device, zeros, sizeof(zeros), + dawn::BufferUsageBit::Index); + pass.SetIndexBuffer(indexBuffer, 0); + pass.DrawIndexedIndirect(indirectBuffer, indirectOffset); + } else { + pass.DrawIndirect(indirectBuffer, indirectOffset); + } + pass.EndPass(); + + ValidateExpectation(encoder, expectation); + } + + dawn::RenderPipeline pipeline; +}; + +// Verify out of bounds indirect draw calls are caught early +TEST_F(DrawIndirectValidationTest, DrawIndirectOffsetBounds) { + // In bounds + TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4}, 0); + // In bounds, bigger buffer + TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7}, 0); + // In bounds, bigger buffer, positive offset + TestIndirectOffsetDraw(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8}, + 4 * sizeof(uint32_t)); + + // Out of bounds, buffer too small + TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3}, 0); + // Out of bounds, index too big + TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 1 * sizeof(uint32_t)); + // Out of bounds, index past buffer + TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4}, 5 * sizeof(uint32_t)); + // Out of bounds, index + size of command overflows + uint64_t offset = std::numeric_limits::max(); + TestIndirectOffsetDraw(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7}, offset); +} + +// Verify out of bounds indirect draw indexed calls are caught early +TEST_F(DrawIndirectValidationTest, DrawIndexedIndirectOffsetBounds) { + // In bounds + TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5}, 0); + // In bounds, bigger buffer + TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9}, 0); + // In bounds, bigger buffer, positive offset + TestIndirectOffsetDrawIndexed(utils::Expectation::Success, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + 5 * sizeof(uint32_t)); + + // Out of bounds, buffer too small + TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4}, 0); + // Out of bounds, index too big + TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5}, + 1 * sizeof(uint32_t)); + // Out of bounds, index past buffer + TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5}, + 5 * sizeof(uint32_t)); + // Out of bounds, index + size of command overflows + uint64_t offset = std::numeric_limits::max(); + TestIndirectOffsetDrawIndexed(utils::Expectation::Failure, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + offset); +}