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); +}