diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 88cb57f915..623d9fcbcf 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -137,6 +137,29 @@ namespace dawn_native { namespace d3d12 { commandList->EndQuery(querySet->GetQueryHeap(), D3D12_QUERY_TYPE_TIMESTAMP, cmd->queryIndex); } + + void RecordFirstIndexOffset(ID3D12GraphicsCommandList* commandList, + RenderPipeline* pipeline, + uint32_t firstVertex, + uint32_t firstInstance) { + const FirstOffsetInfo& firstOffsetInfo = pipeline->GetFirstOffsetInfo(); + if (!firstOffsetInfo.usesVertexIndex && !firstOffsetInfo.usesInstanceIndex) { + return; + } + std::array offsets{}; + uint32_t count = 0; + if (firstOffsetInfo.usesVertexIndex) { + offsets[firstOffsetInfo.vertexIndexOffset / sizeof(uint32_t)] = firstVertex; + ++count; + } + if (firstOffsetInfo.usesInstanceIndex) { + offsets[firstOffsetInfo.instanceIndexOffset / sizeof(uint32_t)] = firstInstance; + ++count; + } + PipelineLayout* layout = ToBackend(pipeline->GetLayout()); + commandList->SetGraphicsRoot32BitConstants(layout->GetFirstIndexOffsetParameterIndex(), + count, offsets.data(), 0); + } } // anonymous namespace class BindGroupStateTracker : public BindGroupTrackerBase { @@ -1227,6 +1250,8 @@ namespace dawn_native { namespace d3d12 { DAWN_TRY(bindingTracker->Apply(commandContext)); vertexBufferTracker.Apply(commandList, lastPipeline); + RecordFirstIndexOffset(commandList, lastPipeline, draw->firstVertex, + draw->firstInstance); commandList->DrawInstanced(draw->vertexCount, draw->instanceCount, draw->firstVertex, draw->firstInstance); break; @@ -1237,6 +1262,8 @@ namespace dawn_native { namespace d3d12 { DAWN_TRY(bindingTracker->Apply(commandContext)); vertexBufferTracker.Apply(commandList, lastPipeline); + RecordFirstIndexOffset(commandList, lastPipeline, draw->baseVertex, + draw->firstInstance); commandList->DrawIndexedInstanced(draw->indexCount, draw->instanceCount, draw->firstIndex, draw->baseVertex, draw->firstInstance); diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp index f5e21dba74..93e8bc0d64 100644 --- a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp +++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp @@ -151,6 +151,39 @@ namespace dawn_native { namespace d3d12 { } } + // Since Tint's HLSL writer doesn't currently map sets to spaces, we use the default space + // (0). + mFirstIndexOffsetRegisterSpace = 0; + BindGroupIndex firstOffsetGroup{mFirstIndexOffsetRegisterSpace}; + if (GetBindGroupLayoutsMask()[firstOffsetGroup]) { + // Find the last register used on firstOffsetGroup. + uint32_t maxRegister = 0; + for (uint32_t shaderRegister : + ToBackend(GetBindGroupLayout(firstOffsetGroup))->GetBindingOffsets()) { + if (shaderRegister > maxRegister) { + maxRegister = shaderRegister; + } + } + mFirstIndexOffsetShaderRegister = maxRegister + 1; + } else { + // firstOffsetGroup is not in use, we can use the first register. + mFirstIndexOffsetShaderRegister = 0; + } + + D3D12_ROOT_PARAMETER indexOffsetConstants{}; + indexOffsetConstants.ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + indexOffsetConstants.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + // Always allocate 2 constants for vertex_index and instance_index + // NOTE: We should consider delaying root signature creation until we know how many values + // we need + indexOffsetConstants.Constants.Num32BitValues = 2; + indexOffsetConstants.Constants.RegisterSpace = mFirstIndexOffsetRegisterSpace; + indexOffsetConstants.Constants.ShaderRegister = mFirstIndexOffsetShaderRegister; + mFirstIndexOffsetParameterIndex = rootParameters.size(); + // NOTE: We should consider moving this entry to earlier in the root signature since offsets + // would need to be updated often + rootParameters.emplace_back(indexOffsetConstants); + D3D12_ROOT_SIGNATURE_DESC rootSignatureDescriptor; rootSignatureDescriptor.NumParameters = rootParameters.size(); rootSignatureDescriptor.pParameters = rootParameters.data(); @@ -195,4 +228,16 @@ namespace dawn_native { namespace d3d12 { wgpu::ShaderStage::None); return mDynamicRootParameterIndices[group][bindingIndex]; } + + uint32_t PipelineLayout::GetFirstIndexOffsetRegisterSpace() const { + return mFirstIndexOffsetRegisterSpace; + } + + uint32_t PipelineLayout::GetFirstIndexOffsetShaderRegister() const { + return mFirstIndexOffsetShaderRegister; + } + + uint32_t PipelineLayout::GetFirstIndexOffsetParameterIndex() const { + return mFirstIndexOffsetParameterIndex; + } }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.h b/src/dawn_native/d3d12/PipelineLayoutD3D12.h index 6116cd8198..45c4779707 100644 --- a/src/dawn_native/d3d12/PipelineLayoutD3D12.h +++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.h @@ -36,6 +36,10 @@ namespace dawn_native { namespace d3d12 { uint32_t GetDynamicRootParameterIndex(BindGroupIndex group, BindingIndex bindingIndex) const; + uint32_t GetFirstIndexOffsetRegisterSpace() const; + uint32_t GetFirstIndexOffsetShaderRegister() const; + uint32_t GetFirstIndexOffsetParameterIndex() const; + ID3D12RootSignature* GetRootSignature() const; private: @@ -48,6 +52,9 @@ namespace dawn_native { namespace d3d12 { ityp::array, kMaxBindGroups> mDynamicRootParameterIndices; + uint32_t mFirstIndexOffsetRegisterSpace; + uint32_t mFirstIndexOffsetShaderRegister; + uint32_t mFirstIndexOffsetParameterIndex; ComPtr mRootSignature; }; diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp index 6aec33219c..eb3f2d1d43 100644 --- a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp +++ b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp @@ -333,6 +333,8 @@ namespace dawn_native { namespace d3d12 { *shaders[stage] = compiledShader[stage].GetD3D12ShaderBytecode(); } + mFirstOffsetInfo = compiledShader[SingleShaderStage::Vertex].firstOffsetInfo; + PipelineLayout* layout = ToBackend(GetLayout()); descriptorD3D12.pRootSignature = layout->GetRootSignature(); @@ -403,6 +405,10 @@ namespace dawn_native { namespace d3d12 { return mPipelineState.Get(); } + const FirstOffsetInfo& RenderPipeline::GetFirstOffsetInfo() const { + return mFirstOffsetInfo; + } + D3D12_INPUT_LAYOUT_DESC RenderPipeline::ComputeInputLayout( std::array* inputElementDescriptors) { unsigned int count = 0; diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.h b/src/dawn_native/d3d12/RenderPipelineD3D12.h index 50f0decac7..7e6e70340a 100644 --- a/src/dawn_native/d3d12/RenderPipelineD3D12.h +++ b/src/dawn_native/d3d12/RenderPipelineD3D12.h @@ -17,6 +17,7 @@ #include "dawn_native/RenderPipeline.h" +#include "dawn_native/d3d12/ShaderModuleD3D12.h" #include "dawn_native/d3d12/d3d12_platform.h" namespace dawn_native { namespace d3d12 { @@ -32,6 +33,8 @@ namespace dawn_native { namespace d3d12 { D3D12_PRIMITIVE_TOPOLOGY GetD3D12PrimitiveTopology() const; ID3D12PipelineState* GetPipelineState() const; + const FirstOffsetInfo& GetFirstOffsetInfo() const; + private: ~RenderPipeline() override; using RenderPipelineBase::RenderPipelineBase; @@ -41,6 +44,7 @@ namespace dawn_native { namespace d3d12 { D3D12_PRIMITIVE_TOPOLOGY mD3d12PrimitiveTopology; ComPtr mPipelineState; + FirstOffsetInfo mFirstOffsetInfo; }; }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp index b117e3f05d..b91450f823 100644 --- a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp +++ b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp @@ -197,7 +197,8 @@ namespace dawn_native { namespace d3d12 { const char* entryPointName, SingleShaderStage stage, PipelineLayout* layout, - std::string* remappedEntryPointName) const { + std::string* remappedEntryPointName, + FirstOffsetInfo* firstOffsetInfo) const { ASSERT(!IsError()); #ifdef DAWN_ENABLE_WGSL @@ -207,9 +208,32 @@ namespace dawn_native { namespace d3d12 { tint::transform::Manager transformManager; transformManager.append(std::make_unique()); + tint::transform::FirstIndexOffset* firstOffsetTransform = nullptr; + if (stage == SingleShaderStage::Vertex) { + auto transformer = std::make_unique( + layout->GetFirstIndexOffsetShaderRegister(), + layout->GetFirstIndexOffsetRegisterSpace()); + firstOffsetTransform = transformer.get(); + transformManager.append(std::move(transformer)); + } + tint::ast::Module module; DAWN_TRY_ASSIGN(module, RunTransforms(&transformManager, mTintModule.get())); + if (firstOffsetTransform != nullptr) { + // Functions are only available after transform has been performed + firstOffsetInfo->usesVertexIndex = firstOffsetTransform->HasVertexIndex(); + if (firstOffsetInfo->usesVertexIndex) { + firstOffsetInfo->vertexIndexOffset = firstOffsetTransform->GetFirstVertexOffset(); + } + + firstOffsetInfo->usesInstanceIndex = firstOffsetTransform->HasInstanceIndex(); + if (firstOffsetInfo->usesInstanceIndex) { + firstOffsetInfo->instanceIndexOffset = + firstOffsetTransform->GetFirstInstanceOffset(); + } + } + ASSERT(remappedEntryPointName != nullptr); tint::inspector::Inspector inspector(module); *remappedEntryPointName = inspector.GetRemappedNameForEntryPoint(entryPointName); @@ -302,9 +326,11 @@ namespace dawn_native { namespace d3d12 { // Compile the source shader to HLSL. std::string hlslSource; std::string remappedEntryPoint; + CompiledShader compiledShader = {}; if (device->IsToggleEnabled(Toggle::UseTintGenerator)) { DAWN_TRY_ASSIGN(hlslSource, TranslateToHLSLWithTint(entryPointName, stage, layout, - &remappedEntryPoint)); + &remappedEntryPoint, + &compiledShader.firstOffsetInfo)); entryPointName = remappedEntryPoint.c_str(); } else { DAWN_TRY_ASSIGN(hlslSource, @@ -326,7 +352,6 @@ namespace dawn_native { namespace d3d12 { DAWN_TRY_ASSIGN(shaderCacheKey, CreateHLSLKey(entryPointName, stage, hlslSource, compileFlags)); - CompiledShader compiledShader = {}; DAWN_TRY_ASSIGN(compiledShader.cachedShader, device->GetPersistentCache()->GetOrCreate( shaderCacheKey, [&](auto doCache) -> MaybeError { diff --git a/src/dawn_native/d3d12/ShaderModuleD3D12.h b/src/dawn_native/d3d12/ShaderModuleD3D12.h index 1f04759890..ed8e1e8636 100644 --- a/src/dawn_native/d3d12/ShaderModuleD3D12.h +++ b/src/dawn_native/d3d12/ShaderModuleD3D12.h @@ -25,12 +25,22 @@ namespace dawn_native { namespace d3d12 { class Device; class PipelineLayout; - // Manages a ref to one of the various representations of shader blobs. + struct FirstOffsetInfo { + bool usesVertexIndex; + uint32_t vertexIndexOffset; + bool usesInstanceIndex; + uint32_t instanceIndexOffset; + }; + + // Manages a ref to one of the various representations of shader blobs and information used to + // emulate vertex/instance index starts struct CompiledShader { ScopedCachedBlob cachedShader; ComPtr compiledFXCShader; ComPtr compiledDXCShader; D3D12_SHADER_BYTECODE GetD3D12ShaderBytecode() const; + + FirstOffsetInfo firstOffsetInfo; }; class ShaderModule final : public ShaderModuleBase { @@ -49,11 +59,11 @@ namespace dawn_native { namespace d3d12 { ~ShaderModule() override = default; MaybeError Initialize(ShaderModuleParseResult* parseResult); - ResultOrError TranslateToHLSLWithTint( - const char* entryPointName, - SingleShaderStage stage, - PipelineLayout* layout, - std::string* remappedEntryPointName) const; + ResultOrError TranslateToHLSLWithTint(const char* entryPointName, + SingleShaderStage stage, + PipelineLayout* layout, + std::string* remappedEntryPointName, + FirstOffsetInfo* firstOffsetInfo) const; ResultOrError TranslateToHLSLWithSPIRVCross(const char* entryPointName, SingleShaderStage stage, diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index e614db2b3c..3971b09282 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -298,6 +298,7 @@ source_set("dawn_end2end_tests_sources") { "end2end/DynamicBufferOffsetTests.cpp", "end2end/EntryPointTests.cpp", "end2end/FenceTests.cpp", + "end2end/FirstIndexOffsetTests.cpp", "end2end/GpuMemorySynchronizationTests.cpp", "end2end/IndexFormatTests.cpp", "end2end/MultisampledRenderingTests.cpp", diff --git a/src/tests/end2end/FirstIndexOffsetTests.cpp b/src/tests/end2end/FirstIndexOffsetTests.cpp new file mode 100644 index 0000000000..241165565a --- /dev/null +++ b/src/tests/end2end/FirstIndexOffsetTests.cpp @@ -0,0 +1,237 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/DawnTest.h" + +#include +#include + +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/WGPUHelpers.h" + +constexpr uint32_t kRTSize = 1; + +enum class DrawMode { + NonIndexed, + Indexed, +}; + +enum class CheckIndex : uint32_t { + Vertex = 0x0000001, + Instance = 0x0000002, +}; + +namespace wgpu { + template <> + struct IsDawnBitmask { + static constexpr bool enable = true; + }; +} // namespace wgpu + +class FirstIndexOffsetTests : public DawnTest { + public: + void TestVertexIndex(DrawMode mode, uint32_t firstVertex); + void TestInstanceIndex(DrawMode mode, uint32_t firstInstance); + void TestBothIndices(DrawMode mode, uint32_t firstVertex, uint32_t firstInstance); + + protected: + void SetUp() override { + DawnTest::SetUp(); + + // WGSL doesn't have the ability to tag attributes as "flat". "flat" is required on u32 + // attributes for correct runtime behavior under Vulkan and codegen under OpenGL(ES). + // TODO(tint:451): Remove once resolved by spec/tint + DAWN_SKIP_TEST_IF(IsVulkan() || IsOpenGL() || IsOpenGLES()); + } + + private: + void TestImpl(DrawMode mode, + CheckIndex checkIndex, + uint32_t vertexIndex, + uint32_t instanceIndex); +}; + +void FirstIndexOffsetTests::TestVertexIndex(DrawMode mode, uint32_t firstVertex) { + TestImpl(mode, CheckIndex::Vertex, firstVertex, 0); +} + +void FirstIndexOffsetTests::TestInstanceIndex(DrawMode mode, uint32_t firstInstance) { + TestImpl(mode, CheckIndex::Instance, 0, firstInstance); +} + +void FirstIndexOffsetTests::TestBothIndices(DrawMode mode, + uint32_t firstVertex, + uint32_t firstInstance) { + using wgpu::operator|; + TestImpl(mode, CheckIndex::Vertex | CheckIndex::Instance, firstVertex, firstInstance); +} + +// Conditionally tests if first/baseVertex and/or firstInstance have been correctly passed to the +// vertex shader. Since vertex shaders can't write to storage buffers, we pass vertex/instance +// indices to a fragment shader via u32 attributes. The fragment shader runs once and writes the +// values to a storage buffer. If vertex index is used, the vertex buffer is padded with 0s. +void FirstIndexOffsetTests::TestImpl(DrawMode mode, + CheckIndex checkIndex, + uint32_t firstVertex, + uint32_t firstInstance) { + using wgpu::operator&; + std::stringstream vertexShader; + std::stringstream fragmentShader; + + if ((checkIndex & CheckIndex::Vertex) != 0) { + vertexShader << R"( + [[builtin(vertex_idx)]] var vertex_index : u32; + [[location(1)]] var out_vertex_index : u32; + )"; + fragmentShader << R"( + [[location(1)]] var in_vertex_index : u32; + )"; + } + if ((checkIndex & CheckIndex::Instance) != 0) { + vertexShader << R"( + [[builtin(instance_idx)]] var instance_index : u32; + [[location(2)]] var out_instance_index : u32; + )"; + fragmentShader << R"( + [[location(2)]] var in_instance_index : u32; + )"; + } + + vertexShader << R"( + [[builtin(position)]] var position : vec4; + [[location(0)]] var pos : vec4; + + [[stage(vertex)]] fn main() -> void {)"; + fragmentShader << R"( + [[block]] struct IndexVals { + [[offset(0)]] vertex_index : u32; + [[offset(4)]] instance_index : u32; + }; + + [[set(0), binding(0)]] var idx_vals : [[access(read_write)]] IndexVals; + + [[stage(fragment)]] fn main() -> void { + )"; + + if ((checkIndex & CheckIndex::Vertex) != 0) { + vertexShader << R"( + out_vertex_index = vertex_index; + )"; + fragmentShader << R"( + idx_vals.vertex_index = in_vertex_index; + )"; + } + if ((checkIndex & CheckIndex::Instance) != 0) { + vertexShader << R"( + out_instance_index = instance_index; + )"; + fragmentShader << R"( + idx_vals.instance_index = in_instance_index; + )"; + } + + vertexShader << R"( + position = pos; + return; + })"; + + fragmentShader << R"( + return; + })"; + + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + constexpr uint32_t kComponentsPerVertex = 4; + + utils::ComboRenderPipelineDescriptor pipelineDesc(device); + pipelineDesc.vertexStage.module = + utils::CreateShaderModuleFromWGSL(device, vertexShader.str().c_str()); + pipelineDesc.cFragmentStage.module = + utils::CreateShaderModuleFromWGSL(device, fragmentShader.str().c_str()); + pipelineDesc.primitiveTopology = wgpu::PrimitiveTopology::PointList; + pipelineDesc.cVertexState.vertexBufferCount = 1; + pipelineDesc.cVertexState.cVertexBuffers[0].arrayStride = kComponentsPerVertex * sizeof(float); + pipelineDesc.cVertexState.cVertexBuffers[0].attributeCount = 1; + pipelineDesc.cVertexState.cAttributes[0].format = wgpu::VertexFormat::Float4; + pipelineDesc.cColorStates[0].format = renderPass.colorFormat; + + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); + + std::vector vertexData(firstVertex * kComponentsPerVertex); + vertexData.insert(vertexData.end(), {0, 0, 0, 1}); + wgpu::Buffer vertices = utils::CreateBufferFromData( + device, vertexData.data(), vertexData.size() * sizeof(float), wgpu::BufferUsage::Vertex); + wgpu::Buffer indices = + utils::CreateBufferFromData(device, wgpu::BufferUsage::Index, {0}); + wgpu::Buffer buffer = utils::CreateBufferFromData( + device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Storage, {0u, 0u}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.SetPipeline(pipeline); + pass.SetVertexBuffer(0, vertices); + pass.SetBindGroup(0, + utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}})); + if (mode == DrawMode::Indexed) { + pass.SetIndexBuffer(indices, wgpu::IndexFormat::Uint32); + pass.DrawIndexed(1, 1, 0, firstVertex, firstInstance); + + } else { + pass.Draw(1, 1, firstVertex, firstInstance); + } + pass.EndPass(); + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + std::array expected = {firstVertex, firstInstance}; + EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, expected.size()); +} + +// Test that vertex_index starts at 7 when drawn using Draw() +TEST_P(FirstIndexOffsetTests, NonIndexedVertexOffset) { + TestVertexIndex(DrawMode::NonIndexed, 7); +} + +// Test that instance_index starts at 11 when drawn using Draw() +TEST_P(FirstIndexOffsetTests, NonIndexedInstanceOffset) { + TestInstanceIndex(DrawMode::NonIndexed, 11); +} + +// Test that vertex_index and instance_index start at 7 and 11 respectively when drawn using Draw() +TEST_P(FirstIndexOffsetTests, NonIndexedBothOffset) { + TestBothIndices(DrawMode::NonIndexed, 7, 11); +} + +// Test that vertex_index starts at 7 when drawn using DrawIndexed() +TEST_P(FirstIndexOffsetTests, IndexedVertex) { + TestVertexIndex(DrawMode::Indexed, 7); +} + +// Test that instance_index starts at 11 when drawn using DrawIndexed() +TEST_P(FirstIndexOffsetTests, IndexedInstance) { + TestInstanceIndex(DrawMode::Indexed, 11); +} + +// Test that vertex_index and instance_index start at 7 and 11 respectively when drawn using +// DrawIndexed() +TEST_P(FirstIndexOffsetTests, IndexedBothOffset) { + TestBothIndices(DrawMode::Indexed, 7, 11); +} + +DAWN_INSTANTIATE_TEST(FirstIndexOffsetTests, + D3D12Backend({"use_tint_generator"}), + MetalBackend(), + OpenGLBackend(), + OpenGLESBackend(), + VulkanBackend());