diff --git a/src/common/Constants.h b/src/common/Constants.h index 7aa9039415..0a41dfffd1 100644 --- a/src/common/Constants.h +++ b/src/common/Constants.h @@ -33,6 +33,8 @@ static constexpr uint32_t kMaxVertexInputStride = 2048u; static constexpr uint32_t kNumStages = 3; 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; // Non spec defined constants. static constexpr float kLodMin = 0.0; diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp index 8bc56c52b4..93af9bf80b 100644 --- a/src/dawn_native/BindGroup.cpp +++ b/src/dawn_native/BindGroup.cpp @@ -126,9 +126,11 @@ namespace dawn_native { // Perform binding-type specific validation. switch (layoutInfo.types[bindingIndex]) { case dawn::BindingType::UniformBuffer: + case dawn::BindingType::DynamicUniformBuffer: DAWN_TRY(ValidateBufferBinding(device, binding, dawn::BufferUsageBit::Uniform)); break; case dawn::BindingType::StorageBuffer: + case dawn::BindingType::DynamicStorageBuffer: DAWN_TRY(ValidateBufferBinding(device, binding, dawn::BufferUsageBit::Storage)); break; case dawn::BindingType::SampledTexture: @@ -138,10 +140,6 @@ namespace dawn_native { case dawn::BindingType::Sampler: DAWN_TRY(ValidateSamplerBinding(device, binding)); break; - // TODO(shaobo.yan@intel.com): Implement dynamic buffer offset. - case dawn::BindingType::DynamicUniformBuffer: - case dawn::BindingType::DynamicStorageBuffer: - return DAWN_VALIDATION_ERROR("Dawn doesn't support dynamic buffer yet"); } } @@ -209,7 +207,10 @@ namespace dawn_native { ASSERT(binding < kMaxBindingsPerGroup); ASSERT(mLayout->GetBindingInfo().mask[binding]); ASSERT(mLayout->GetBindingInfo().types[binding] == dawn::BindingType::UniformBuffer || - mLayout->GetBindingInfo().types[binding] == dawn::BindingType::StorageBuffer); + mLayout->GetBindingInfo().types[binding] == dawn::BindingType::StorageBuffer || + mLayout->GetBindingInfo().types[binding] == + dawn::BindingType::DynamicUniformBuffer || + mLayout->GetBindingInfo().types[binding] == dawn::BindingType::DynamicStorageBuffer); BufferBase* buffer = static_cast(mBindings[binding].Get()); return {buffer, mOffsets[binding], mSizes[binding]}; } diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp index e316917a52..4e94e9f7d3 100644 --- a/src/dawn_native/BindGroupLayout.cpp +++ b/src/dawn_native/BindGroupLayout.cpp @@ -87,6 +87,11 @@ namespace dawn_native { mBindingInfo.visibilities[index] = binding.visibility; mBindingInfo.types[index] = binding.type; + if (binding.type == dawn::BindingType::DynamicUniformBuffer || + binding.type == dawn::BindingType::DynamicStorageBuffer) { + mDynamicBufferCount++; + } + ASSERT(!mBindingInfo.mask[index]); mBindingInfo.mask.set(index); } @@ -122,4 +127,8 @@ namespace dawn_native { return a->mBindingInfo == b->mBindingInfo; } + uint32_t BindGroupLayoutBase::GetDynamicBufferCount() const { + return mDynamicBufferCount; + } + } // namespace dawn_native diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h index 047228f103..decc68f7da 100644 --- a/src/dawn_native/BindGroupLayout.h +++ b/src/dawn_native/BindGroupLayout.h @@ -54,11 +54,14 @@ namespace dawn_native { bool operator()(const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const; }; + uint32_t GetDynamicBufferCount() const; + private: BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag); LayoutBindingInfo mBindingInfo; bool mIsBlueprint = false; + uint32_t mDynamicBufferCount = 0; }; } // namespace dawn_native diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index cc13ec2b2a..58f22ee0d9 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -543,12 +543,14 @@ namespace dawn_native { dawn::BindingType type = layoutInfo.types[i]; switch (type) { - case dawn::BindingType::UniformBuffer: { + case dawn::BindingType::UniformBuffer: + case dawn::BindingType::DynamicUniformBuffer: { BufferBase* buffer = group->GetBindingAsBufferBinding(i).buffer; tracker->BufferUsedAs(buffer, dawn::BufferUsageBit::Uniform); } break; - case dawn::BindingType::StorageBuffer: { + case dawn::BindingType::StorageBuffer: + case dawn::BindingType::DynamicStorageBuffer: { BufferBase* buffer = group->GetBindingAsBufferBinding(i).buffer; tracker->BufferUsedAs(buffer, dawn::BufferUsageBit::Storage); } break; @@ -560,12 +562,6 @@ namespace dawn_native { case dawn::BindingType::Sampler: break; - - // TODO(shaobo.yan@intel.com): Implement dynamic buffer offset - case dawn::BindingType::DynamicUniformBuffer: - case dawn::BindingType::DynamicStorageBuffer: - UNREACHABLE(); - break; } } } @@ -1034,6 +1030,9 @@ namespace dawn_native { case Command::SetBindGroup: { SetBindGroupCmd* cmd = mIterator.NextCommand(); + if (cmd->dynamicOffsetCount > 0) { + mIterator.NextData(cmd->dynamicOffsetCount); + } TrackBindGroupResourceUsage(cmd->group.Get(), &usageTracker); persistentState.SetBindGroup(cmd->index, cmd->group.Get()); @@ -1149,6 +1148,9 @@ namespace dawn_native { case Command::SetBindGroup: { SetBindGroupCmd* cmd = mIterator.NextCommand(); + if (cmd->dynamicOffsetCount > 0) { + mIterator.NextData(cmd->dynamicOffsetCount); + } TrackBindGroupResourceUsage(cmd->group.Get(), &usageTracker); persistentState.SetBindGroup(cmd->index, cmd->group.Get()); diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index 1bae002649..98a978be1b 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -115,6 +115,9 @@ namespace dawn_native { } break; case Command::SetBindGroup: { SetBindGroupCmd* cmd = commands->NextCommand(); + if (cmd->dynamicOffsetCount > 0) { + commands->NextData(cmd->dynamicOffsetCount); + } cmd->~SetBindGroupCmd(); } break; case Command::SetIndexBuffer: { diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 26e0b682ab..6dc25e2a66 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -190,6 +190,7 @@ namespace dawn_native { struct SetBindGroupCmd { uint32_t index; Ref group; + uint32_t dynamicOffsetCount; }; struct SetIndexBufferCmd { diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp index bf9c451a8a..3ae7b113a0 100644 --- a/src/dawn_native/ProgrammablePassEncoder.cpp +++ b/src/dawn_native/ProgrammablePassEncoder.cpp @@ -83,6 +83,8 @@ namespace dawn_native { BindGroupBase* group, uint32_t dynamicOffsetCount, const uint64_t* dynamicOffsets) { + const BindGroupLayoutBase* layout = group->GetLayout(); + if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) || mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(group))) { return; @@ -93,15 +95,33 @@ namespace dawn_native { return; } - // TODO(shaobo.yan@intel.com): Implement dynamic buffer offset. - if (dynamicOffsetCount != 0) { - mTopLevelEncoder->HandleError("Dynamic Buffer Offset not supported yet"); - return; + // Dynamic offsets count must match the number required by the layout perfectly. + if (layout->GetDynamicBufferCount() != dynamicOffsetCount) { + mTopLevelEncoder->HandleError("dynamicOffset count mismatch"); + } + + for (uint32_t i = 0; i < dynamicOffsetCount; ++i) { + if (dynamicOffsets[i] % kMinDynamicBufferOffsetAlignment != 0) { + mTopLevelEncoder->HandleError("Dynamic Buffer Offset need to be aligned"); + return; + } + + BufferBinding bufferBinding = group->GetBindingAsBufferBinding(i); + + if (dynamicOffsets[i] >= bufferBinding.size - bufferBinding.offset) { + mTopLevelEncoder->HandleError("dynamic offset out of bounds"); + return; + } } SetBindGroupCmd* cmd = mAllocator->Allocate(Command::SetBindGroup); cmd->index = groupIndex; cmd->group = group; + cmd->dynamicOffsetCount = dynamicOffsetCount; + if (dynamicOffsetCount > 0) { + uint64_t* offsets = mAllocator->AllocateData(cmd->dynamicOffsetCount); + memcpy(offsets, dynamicOffsets, dynamicOffsetCount * sizeof(uint64_t)); + } } void ProgrammablePassEncoder::SetPushConstants(dawn::ShaderStageBit stages, diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp index 613eb35bb0..598d4f6177 100644 --- a/src/dawn_native/ShaderModule.cpp +++ b/src/dawn_native/ShaderModule.cpp @@ -66,6 +66,17 @@ namespace dawn_native { return {}; } + dawn::BindingType NonDynamicBindingType(dawn::BindingType type) { + switch (type) { + case dawn::BindingType::DynamicUniformBuffer: + return dawn::BindingType::UniformBuffer; + case dawn::BindingType::DynamicStorageBuffer: + return dawn::BindingType::StorageBuffer; + default: + return type; + } + } + // ShaderModuleBase ShaderModuleBase::ShaderModuleBase(DeviceBase* device, @@ -283,14 +294,18 @@ namespace dawn_native { const auto& layoutInfo = layout->GetBindingInfo(); for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) { const auto& moduleInfo = mBindingInfo[group][i]; + const auto& layoutBindingType = layoutInfo.types[i]; if (!moduleInfo.used) { continue; } - if (moduleInfo.type != layoutInfo.types[i]) { + // DynamicUniformBuffer and DynamicStorageBuffer are uniform buffer and + // storage buffer in shader. Need to translate them. + if (NonDynamicBindingType(layoutBindingType) != moduleInfo.type) { return false; } + if ((layoutInfo.visibilities[i] & StageBit(mExecutionModel)) == 0) { return false; } diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp index cd4f6c8656..843d176897 100644 --- a/src/tests/unittests/validation/BindGroupValidationTests.cpp +++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp @@ -15,6 +15,7 @@ #include "tests/unittests/validation/ValidationTest.h" #include "common/Constants.h" +#include "utils/ComboRenderPipelineDescriptor.h" #include "utils/DawnHelpers.h" class BindGroupValidationTest : public ValidationTest { @@ -446,3 +447,260 @@ TEST_F(BindGroupLayoutValidationTest, BindGroupLayoutCache) { // Caching should cause these to be the same. ASSERT_EQ(layout1.Get(), layout2.Get()); } + +constexpr uint32_t kBufferElementsCount = kMinDynamicBufferOffsetAlignment / sizeof(uint32_t) + 2; +constexpr uint32_t kBufferSize = kBufferElementsCount * sizeof(uint32_t); + +class SetBindGroupValidationTest : public ValidationTest { + public: + void SetUp() override { + std::array uniformData = {0}; + + uniformData[0] = 1.0; + uniformData[1] = 2.0; + uniformData[uniformData.size() - 2] = 5.0; + uniformData[uniformData.size() - 1] = 6.0; + + dawn::BufferDescriptor bufferDescriptor; + bufferDescriptor.size = kBufferSize; + bufferDescriptor.usage = dawn::BufferUsageBit::Storage; + + mUniformBuffer = utils::CreateBufferFromData(device, uniformData.data(), kBufferSize, + dawn::BufferUsageBit::Uniform); + mStorageBuffer = device.CreateBuffer(&bufferDescriptor); + + mBindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, dawn::ShaderStageBit::Compute | dawn::ShaderStageBit::Fragment, + dawn::BindingType::DynamicUniformBuffer}, + {1, dawn::ShaderStageBit::Compute | dawn::ShaderStageBit::Fragment, + dawn::BindingType::DynamicStorageBuffer}}); + + mBindGroup = utils::MakeBindGroup( + device, mBindGroupLayout, + {{0, mUniformBuffer, 0, kBufferSize}, {1, mStorageBuffer, 0, kBufferSize}}); + } + // Create objects to use as resources inside test bind groups. + + dawn::BindGroup mBindGroup; + dawn::BindGroupLayout mBindGroupLayout; + dawn::Buffer mUniformBuffer; + dawn::Buffer mStorageBuffer; + + dawn::RenderPipeline CreateRenderPipeline() { + dawn::ShaderModule vsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"( + #version 450 + void main() { + })"); + + dawn::ShaderModule fsModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, R"( + #version 450 + layout(std140, set = 0, binding = 0) uniform uBuffer { + vec2 value1; + }; + layout(std140, set = 0, binding = 1) buffer SBuffer { + vec2 value2; + } sBuffer; + layout(location = 0) out uvec4 fragColor; + void main() { + })"); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.cVertexStage.module = vsModule; + pipelineDescriptor.cFragmentStage.module = fsModule; + dawn::PipelineLayout pipelineLayout = + utils::MakeBasicPipelineLayout(device, &mBindGroupLayout); + pipelineDescriptor.layout = pipelineLayout; + return device.CreateRenderPipeline(&pipelineDescriptor); + } + + dawn::ComputePipeline CreateComputePipeline() { + dawn::ShaderModule csModule = + utils::CreateShaderModule(device, dawn::ShaderStage::Compute, R"( + #version 450 + const uint kTileSize = 4; + const uint kInstances = 11; + + layout(local_size_x = kTileSize, local_size_y = kTileSize, local_size_z = 1) in; + layout(std140, set = 0, binding = 0) uniform UniformBuffer { + float value1; + }; + layout(std140, set = 0, binding = 1) buffer SBuffer { + float value2; + } dst; + + void main() { + })"); + + dawn::ComputePipelineDescriptor csDesc; + dawn::PipelineLayout pipelineLayout = + utils::MakeBasicPipelineLayout(device, &mBindGroupLayout); + csDesc.layout = pipelineLayout; + + dawn::PipelineStageDescriptor computeStage; + computeStage.module = csModule; + computeStage.entryPoint = "main"; + csDesc.computeStage = &computeStage; + + return device.CreateComputePipeline(&csDesc); + } +}; + +// This is the test case that should work. +TEST_F(SetBindGroupValidationTest, Basic) { + std::array offsets = {256, 0}; + + // RenderPipeline SetBindGroup + { + dawn::RenderPipeline renderPipeline = CreateRenderPipeline(); + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); + renderPassEncoder.SetPipeline(renderPipeline); + renderPassEncoder.SetBindGroup(0, mBindGroup, 2, offsets.data()); + renderPassEncoder.Draw(3, 1, 0, 0); + renderPassEncoder.EndPass(); + commandEncoder.Finish(); + } + + { + dawn::ComputePipeline computePipeline = CreateComputePipeline(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); + computePassEncoder.SetPipeline(computePipeline); + computePassEncoder.SetBindGroup(0, mBindGroup, 2, offsets.data()); + computePassEncoder.Dispatch(1, 1, 1); + computePassEncoder.EndPass(); + commandEncoder.Finish(); + } +} + +// Test cases that test dynamic offsets count mismatch with bind group layout. +TEST_F(SetBindGroupValidationTest, DynamicOffsetsMismatch) { + std::array mismatchOffsets = {0}; + + // RenderPipeline SetBindGroup + { + dawn::RenderPipeline pipeline = CreateRenderPipeline(); + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); + renderPassEncoder.SetPipeline(pipeline); + renderPassEncoder.SetBindGroup(0, mBindGroup, 1, mismatchOffsets.data()); + renderPassEncoder.Draw(3, 1, 0, 0); + renderPassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + { + dawn::ComputePipeline computePipeline = CreateComputePipeline(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); + computePassEncoder.SetPipeline(computePipeline); + computePassEncoder.SetBindGroup(0, mBindGroup, 1, mismatchOffsets.data()); + computePassEncoder.Dispatch(1, 1, 1); + computePassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test cases that test dynamic offsets not aligned +TEST_F(SetBindGroupValidationTest, DynamicOffsetsNotAligned) { + std::array notAlignedOffsets = {1, 2}; + + // RenderPipeline SetBindGroup + { + dawn::RenderPipeline pipeline = CreateRenderPipeline(); + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); + renderPassEncoder.SetPipeline(pipeline); + renderPassEncoder.SetBindGroup(0, mBindGroup, 2, notAlignedOffsets.data()); + renderPassEncoder.Draw(3, 1, 0, 0); + renderPassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // ComputePipeline SetBindGroup + { + dawn::ComputePipeline pipeline = CreateComputePipeline(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); + computePassEncoder.SetPipeline(pipeline); + computePassEncoder.SetBindGroup(0, mBindGroup, 2, notAlignedOffsets.data()); + computePassEncoder.Dispatch(1, 1, 1); + computePassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test cases that test dynamic uniform buffer out of bound situation. +TEST_F(SetBindGroupValidationTest, OutOfBoundDynamicUniformBuffer) { + std::array overFlowOffsets = {512, 0}; + + // RenderPipeline SetBindGroup + { + dawn::RenderPipeline pipeline = CreateRenderPipeline(); + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); + renderPassEncoder.SetPipeline(pipeline); + renderPassEncoder.SetBindGroup(0, mBindGroup, 2, overFlowOffsets.data()); + renderPassEncoder.Draw(3, 1, 0, 0); + renderPassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // ComputePipeline SetBindGroup + { + dawn::ComputePipeline pipeline = CreateComputePipeline(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); + computePassEncoder.SetPipeline(pipeline); + computePassEncoder.SetBindGroup(0, mBindGroup, 2, overFlowOffsets.data()); + computePassEncoder.Dispatch(1, 1, 1); + computePassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +} + +// Test cases that test dynamic storage buffer out of bound situation. +TEST_F(SetBindGroupValidationTest, OutOfBoundDynamicStorageBuffer) { + std::array overFlowOffsets = {0, 512}; + + // RenderPipeline SetBindGroup + { + dawn::RenderPipeline pipeline = CreateRenderPipeline(); + DummyRenderPass renderPass(device); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); + renderPassEncoder.SetPipeline(pipeline); + renderPassEncoder.SetBindGroup(0, mBindGroup, 2, overFlowOffsets.data()); + renderPassEncoder.Draw(3, 1, 0, 0); + renderPassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } + + // ComputePipeline SetBindGroup + { + dawn::ComputePipeline pipeline = CreateComputePipeline(); + + dawn::CommandEncoder commandEncoder = device.CreateCommandEncoder(); + dawn::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); + computePassEncoder.SetPipeline(pipeline); + computePassEncoder.SetBindGroup(0, mBindGroup, 2, overFlowOffsets.data()); + computePassEncoder.Dispatch(1, 1, 1); + computePassEncoder.EndPass(); + ASSERT_DEVICE_ERROR(commandEncoder.Finish()); + } +}