diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp index 59840951cf..225918c26f 100644 --- a/src/dawn_native/Toggles.cpp +++ b/src/dawn_native/Toggles.cpp @@ -119,6 +119,10 @@ namespace dawn_native { {"disable_base_instance", "Disables the use of non-zero base instance which is unsupported on some " "platforms."}}, + {Toggle::UseD3D12SmallShaderVisibleHeapForTesting, + {"use_d3d12_small_shader_visible_heap", + "Enable use of a small D3D12 shader visible heap, instead of using a large one by " + "default. This setting is used to test bindgroup encoding."}}, }}; } // anonymous namespace diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h index c05363b7c8..3cc40ecb28 100644 --- a/src/dawn_native/Toggles.h +++ b/src/dawn_native/Toggles.h @@ -40,6 +40,7 @@ namespace dawn_native { MetalDisableSamplerCompare, DisableBaseVertex, DisableBaseInstance, + UseD3D12SmallShaderVisibleHeapForTesting, EnumCount, InvalidEnum = EnumCount, diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index 2558a60b9b..e9fdf874c0 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -410,6 +410,9 @@ namespace dawn_native { namespace d3d12 { SetToggle(Toggle::UseD3D12ResourceHeapTier2, useResourceHeapTier2); SetToggle(Toggle::UseD3D12RenderPass, GetDeviceInfo().supportsRenderPass); SetToggle(Toggle::UseD3D12ResidencyManagement, false); + + // By default use the maximum shader-visible heap size allowed. + SetToggle(Toggle::UseD3D12SmallShaderVisibleHeapForTesting, false); } MaybeError Device::WaitForIdleForDestruction() { diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp index 76e2db2109..101ca4b190 100644 --- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp +++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp @@ -22,7 +22,14 @@ namespace dawn_native { namespace d3d12 { static_assert(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV == 0, ""); static_assert(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER == 1, ""); - uint32_t GetD3D12ShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType) { + // Thresholds should be adjusted (lower == faster) to avoid tests taking too long to complete. + static constexpr const uint32_t kShaderVisibleSmallHeapSizes[] = {1024, 512}; + + uint32_t GetD3D12ShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType, bool useSmallSize) { + if (useSmallSize) { + return kShaderVisibleSmallHeapSizes[heapType]; + } + switch (heapType) { case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: return D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1; @@ -144,7 +151,8 @@ namespace dawn_native { namespace d3d12 { // TODO(bryan.bernhart@intel.com): Allocating to max heap size wastes memory // should the developer not allocate any bindings for the heap type. // Consider dynamically re-sizing GPU heaps. - const uint32_t descriptorCount = GetD3D12ShaderVisibleHeapSize(heapType); + const uint32_t descriptorCount = GetD3D12ShaderVisibleHeapSize( + heapType, mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); if (heap == nullptr) { D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor; diff --git a/src/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/tests/white_box/D3D12DescriptorHeapTests.cpp index 7dc0a918bd..decec9cdc8 100644 --- a/src/tests/white_box/D3D12DescriptorHeapTests.cpp +++ b/src/tests/white_box/D3D12DescriptorHeapTests.cpp @@ -14,6 +14,7 @@ #include "tests/DawnTest.h" +#include "dawn_native/Toggles.h" #include "dawn_native/d3d12/DeviceD3D12.h" #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" #include "utils/ComboRenderPipelineDescriptor.h" @@ -33,9 +34,63 @@ class D3D12DescriptorHeapTests : public DawnTest { void TestSetUp() override { DAWN_SKIP_TEST_IF(UsesWire()); mD3DDevice = reinterpret_cast(device.Get()); + + mSimpleVSModule = utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( + #version 450 + void main() { + const vec2 pos[3] = vec2[3](vec2(-1.f, 1.f), vec2(1.f, 1.f), vec2(-1.f, -1.f)); + gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f); + })"); + + mSimpleFSModule = utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( + #version 450 + layout (location = 0) out vec4 fragColor; + layout (set = 0, binding = 0) uniform colorBuffer { + vec4 color; + }; + void main() { + fragColor = color; + })"); + } + + utils::BasicRenderPass MakeRenderPass(const wgpu::Device& device, + uint32_t width, + uint32_t height, + wgpu::TextureFormat format) { + DAWN_ASSERT(width > 0 && height > 0); + + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size.width = width; + descriptor.size.height = height; + descriptor.size.depth = 1; + descriptor.arrayLayerCount = 1; + descriptor.sampleCount = 1; + descriptor.format = format; + descriptor.mipLevelCount = 1; + descriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc; + wgpu::Texture color = device.CreateTexture(&descriptor); + + return utils::BasicRenderPass(width, height, color); + } + + uint32_t GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType) const { + return mD3DDevice->GetShaderVisibleDescriptorAllocator() + ->GetShaderVisibleHeapSizeForTesting(heapType); + } + + std::array GetSolidColor(uint32_t n) const { + ASSERT(n >> 24 == 0); + float b = (n & 0xFF) / 255.0f; + float g = ((n >> 8) & 0xFF) / 255.0f; + float r = ((n >> 16) & 0xFF) / 255.0f; + return {r, g, b, 1}; } Device* mD3DDevice = nullptr; + + wgpu::ShaderModule mSimpleVSModule; + wgpu::ShaderModule mSimpleFSModule; }; // Verify the shader visible heaps switch over within a single submit. @@ -198,4 +253,441 @@ TEST_P(D3D12DescriptorHeapTests, PoolHeapsInPendingAndMultipleSubmits) { EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), kNumOfSwitches); } -DAWN_INSTANTIATE_TEST(D3D12DescriptorHeapTests, D3D12Backend()); +// Verify encoding multiple heaps worth of bindgroups. +// Shader-visible heaps will switch out |kNumOfHeaps| times. +TEST_P(D3D12DescriptorHeapTests, EncodeManyUBO) { + // This test draws a solid color triangle |heapSize| times. Each draw uses a new bindgroup that + // has its own UBO with a "color value" in the range [1... heapSize]. After |heapSize| draws, + // the result is the arithmetic sum of the sequence after the framebuffer is blended by + // accumulation. By checking for this sum, we ensure each bindgroup was encoded correctly. + DAWN_SKIP_TEST_IF(!mD3DDevice->IsToggleEnabled( + dawn_native::Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); + + utils::BasicRenderPass renderPass = + MakeRenderPass(device, kRTSize, kRTSize, wgpu::TextureFormat::R32Float); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.vertexStage.module = mSimpleVSModule; + + pipelineDescriptor.cFragmentStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( + #version 450 + layout (location = 0) out float fragColor; + layout (set = 0, binding = 0) uniform buffer0 { + float heapSize; + }; + void main() { + fragColor = heapSize; + })"); + + pipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::R32Float; + pipelineDescriptor.cColorStates[0].colorBlend.operation = wgpu::BlendOperation::Add; + pipelineDescriptor.cColorStates[0].colorBlend.srcFactor = wgpu::BlendFactor::One; + pipelineDescriptor.cColorStates[0].colorBlend.dstFactor = wgpu::BlendFactor::One; + pipelineDescriptor.cColorStates[0].alphaBlend.operation = wgpu::BlendOperation::Add; + pipelineDescriptor.cColorStates[0].alphaBlend.srcFactor = wgpu::BlendFactor::One; + pipelineDescriptor.cColorStates[0].alphaBlend.dstFactor = wgpu::BlendFactor::One; + + wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&pipelineDescriptor); + + const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + constexpr uint32_t kNumOfHeaps = 2; + + const uint32_t numOfEncodedBindGroups = kNumOfHeaps * heapSize; + + std::vector bindGroups; + for (uint32_t i = 0; i < numOfEncodedBindGroups; i++) { + const float color = i + 1; + wgpu::Buffer uniformBuffer = + utils::CreateBufferFromData(device, &color, sizeof(color), wgpu::BufferUsage::Uniform); + bindGroups.push_back(utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0), + {{0, uniformBuffer}})); + } + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(renderPipeline); + + for (uint32_t i = 0; i < numOfEncodedBindGroups; ++i) { + pass.SetBindGroup(0, bindGroups[i]); + pass.Draw(3, 1, 0, 0); + } + + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + float colorSum = numOfEncodedBindGroups * (numOfEncodedBindGroups + 1) / 2; + EXPECT_PIXEL_FLOAT_EQ(colorSum, renderPass.color, 0, 0); +} + +// Verify encoding one bindgroup then a heaps worth in different submits. +// Shader-visible heaps should switch out once upon encoding 1 + |heapSize| descriptors. +// The first descriptor's memory will be reused when the second submit encodes |heapSize| +// descriptors. +TEST_P(D3D12DescriptorHeapTests, EncodeUBOOverflowMultipleSubmit) { + DAWN_SKIP_TEST_IF(!mD3DDevice->IsToggleEnabled( + dawn_native::Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); + + utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device); + + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.vertexStage.module = mSimpleVSModule; + pipelineDescriptor.cFragmentStage.module = mSimpleFSModule; + pipelineDescriptor.cColorStates[0].format = renderPass.colorFormat; + + wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&pipelineDescriptor); + + // Encode the first descriptor and submit. + { + std::array greenColor = {0, 1, 0, 1}; + wgpu::Buffer uniformBuffer = utils::CreateBufferFromData( + device, &greenColor, sizeof(greenColor), wgpu::BufferUsage::Uniform); + + wgpu::BindGroup bindGroup = utils::MakeBindGroup( + device, renderPipeline.GetBindGroupLayout(0), {{0, uniformBuffer}}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(renderPipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(3, 1, 0, 0); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + } + + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kGreen, renderPass.color, 0, 0); + + // Encode a heap worth of descriptors. + { + const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + std::vector bindGroups; + for (uint32_t i = 0; i < heapSize - 1; i++) { + std::array fillColor = GetSolidColor(i + 1); // Avoid black + wgpu::Buffer uniformBuffer = utils::CreateBufferFromData( + device, &fillColor, sizeof(fillColor), wgpu::BufferUsage::Uniform); + + bindGroups.push_back(utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0), + {{0, uniformBuffer}})); + } + + std::array redColor = {1, 0, 0, 1}; + wgpu::Buffer lastUniformBuffer = utils::CreateBufferFromData( + device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform); + + bindGroups.push_back(utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0), + {{0, lastUniformBuffer, 0, sizeof(redColor)}})); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(renderPipeline); + + for (uint32_t i = 0; i < heapSize; ++i) { + pass.SetBindGroup(0, bindGroups[i]); + pass.Draw(3, 1, 0, 0); + } + + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + } + + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0); +} + +// Verify encoding a heaps worth of bindgroups plus one more then reuse the first +// bindgroup in the same submit. +// Shader-visible heaps should switch out once then re-encode the first descriptor at a new offset +// in the heap. +TEST_P(D3D12DescriptorHeapTests, EncodeReuseUBOOverflow) { + DAWN_SKIP_TEST_IF(!mD3DDevice->IsToggleEnabled( + dawn_native::Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); + + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.vertexStage.module = mSimpleVSModule; + pipelineDescriptor.cFragmentStage.module = mSimpleFSModule; + pipelineDescriptor.cColorStates[0].format = renderPass.colorFormat; + + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); + + std::array redColor = {1, 0, 0, 1}; + wgpu::Buffer firstUniformBuffer = utils::CreateBufferFromData( + device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform); + + std::vector bindGroups = {utils::MakeBindGroup( + device, pipeline.GetBindGroupLayout(0), {{0, firstUniformBuffer, 0, sizeof(redColor)}})}; + + const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + for (uint32_t i = 0; i < heapSize; i++) { + const std::array& fillColor = GetSolidColor(i + 1); // Avoid black + wgpu::Buffer uniformBuffer = utils::CreateBufferFromData( + device, &fillColor, sizeof(fillColor), wgpu::BufferUsage::Uniform); + bindGroups.push_back(utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), + {{0, uniformBuffer, 0, sizeof(fillColor)}})); + } + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(pipeline); + + // Encode a heap worth of descriptors plus one more. + for (uint32_t i = 0; i < heapSize + 1; ++i) { + pass.SetBindGroup(0, bindGroups[i]); + pass.Draw(3, 1, 0, 0); + } + + // Re-encode the first bindgroup again. + pass.SetBindGroup(0, bindGroups[0]); + pass.Draw(3, 1, 0, 0); + + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + // Make sure the first bindgroup was encoded correctly. + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0); +} + +// Verify encoding a heaps worth of bindgroups plus one more in the first submit then reuse the +// first bindgroup again in the second submit. +// Shader-visible heaps should switch out once then re-encode the +// first descriptor at the same offset in the heap. +TEST_P(D3D12DescriptorHeapTests, EncodeReuseUBOMultipleSubmits) { + DAWN_SKIP_TEST_IF(!mD3DDevice->IsToggleEnabled( + dawn_native::Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); + + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.vertexStage.module = mSimpleVSModule; + pipelineDescriptor.cFragmentStage.module = mSimpleFSModule; + pipelineDescriptor.cColorStates[0].format = renderPass.colorFormat; + + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); + + // Encode heap worth of descriptors plus one more. + std::array redColor = {1, 0, 0, 1}; + + wgpu::Buffer firstUniformBuffer = utils::CreateBufferFromData( + device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform); + + std::vector bindGroups = {utils::MakeBindGroup( + device, pipeline.GetBindGroupLayout(0), {{0, firstUniformBuffer, 0, sizeof(redColor)}})}; + + const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + for (uint32_t i = 0; i < heapSize; i++) { + std::array fillColor = GetSolidColor(i + 1); // Avoid black + wgpu::Buffer uniformBuffer = utils::CreateBufferFromData( + device, &fillColor, sizeof(fillColor), wgpu::BufferUsage::Uniform); + + bindGroups.push_back(utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), + {{0, uniformBuffer, 0, sizeof(fillColor)}})); + } + + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(pipeline); + + for (uint32_t i = 0; i < heapSize + 1; ++i) { + pass.SetBindGroup(0, bindGroups[i]); + pass.Draw(3, 1, 0, 0); + } + + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + } + + // Re-encode the first bindgroup again. + { + std::array greenColor = {0, 1, 0, 1}; + firstUniformBuffer.SetSubData(0, sizeof(greenColor), &greenColor); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(pipeline); + + pass.SetBindGroup(0, bindGroups[0]); + pass.Draw(3, 1, 0, 0); + + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + } + + // Make sure the first bindgroup was re-encoded correctly. + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kGreen, renderPass.color, 0, 0); +} + +// Verify encoding many sampler and ubo worth of bindgroups. +// Shader-visible heaps should switch out |kNumOfHeaps| times. +TEST_P(D3D12DescriptorHeapTests, EncodeManyUBOAndSamplers) { + // Create a solid filled texture. + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size.width = kRTSize; + descriptor.size.height = kRTSize; + descriptor.size.depth = 1; + descriptor.arrayLayerCount = 1; + descriptor.sampleCount = 1; + descriptor.format = wgpu::TextureFormat::RGBA8Unorm; + descriptor.mipLevelCount = 1; + descriptor.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment | + wgpu::TextureUsage::CopySrc; + wgpu::Texture texture = device.CreateTexture(&descriptor); + wgpu::TextureView textureView = texture.CreateView(); + + { + utils::BasicRenderPass renderPass = utils::BasicRenderPass(kRTSize, kRTSize, texture); + + utils::ComboRenderPassDescriptor renderPassDesc({textureView}); + renderPassDesc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; + renderPassDesc.cColorAttachments[0].clearColor = {0.0f, 1.0f, 0.0f, 1.0f}; + renderPass.renderPassInfo.cColorAttachments[0].attachment = textureView; + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + auto pass = encoder.BeginRenderPass(&renderPassDesc); + pass.EndPass(); + + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + queue.Submit(1, &commandBuffer); + + RGBA8 filled(0, 255, 0, 255); + EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 0, 0); + } + + { + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + + pipelineDescriptor.vertexStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( + #version 450 + layout (set = 0, binding = 0) uniform vertexUniformBuffer { + mat2 transform; + }; + void main() { + const vec2 pos[3] = vec2[3](vec2(-1.f, 1.f), vec2(1.f, 1.f), vec2(-1.f, -1.f)); + gl_Position = vec4(transform * pos[gl_VertexIndex], 0.f, 1.f); + })"); + + pipelineDescriptor.cFragmentStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( + #version 450 + layout (set = 0, binding = 1) uniform sampler sampler0; + layout (set = 0, binding = 2) uniform texture2D texture0; + layout (set = 0, binding = 3) uniform buffer0 { + vec4 color; + }; + layout (location = 0) out vec4 fragColor; + void main() { + fragColor = texture(sampler2D(texture0, sampler0), gl_FragCoord.xy); + fragColor += color; + })"); + + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + pipelineDescriptor.cColorStates[0].format = renderPass.colorFormat; + + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); + + // Encode a heap worth of descriptors |kNumOfHeaps| times. + constexpr float dummy = 0.0f; + constexpr float transform[] = {1.f, 0.f, dummy, dummy, 0.f, 1.f, dummy, dummy}; + wgpu::Buffer transformBuffer = utils::CreateBufferFromData( + device, &transform, sizeof(transform), wgpu::BufferUsage::Uniform); + + wgpu::SamplerDescriptor samplerDescriptor; + wgpu::Sampler sampler = device.CreateSampler(&samplerDescriptor); + + constexpr uint32_t kNumOfBindGroups = 4; + std::vector bindGroups; + for (uint32_t i = 0; i < kNumOfBindGroups - 1; i++) { + std::array fillColor = GetSolidColor(i + 1); // Avoid black + wgpu::Buffer uniformBuffer = utils::CreateBufferFromData( + device, &fillColor, sizeof(fillColor), wgpu::BufferUsage::Uniform); + + bindGroups.push_back( + utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), + {{0, transformBuffer, 0, sizeof(transformBuffer)}, + {1, sampler}, + {2, textureView}, + {3, uniformBuffer, 0, sizeof(fillColor)}})); + } + + std::array redColor = {1, 0, 0, 1}; + wgpu::Buffer lastUniformBuffer = utils::CreateBufferFromData( + device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform); + + bindGroups.push_back(utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), + {{0, transformBuffer, 0, sizeof(transform)}, + {1, sampler}, + {2, textureView}, + {3, lastUniformBuffer, 0, sizeof(redColor)}})); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + + pass.SetPipeline(pipeline); + + constexpr uint32_t kBindingsPerGroup = 4; + constexpr uint32_t kNumOfHeaps = 5; + + const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + const uint32_t bindGroupsPerHeap = heapSize / kBindingsPerGroup; + + ASSERT_TRUE(heapSize % kBindingsPerGroup == 0); + + for (uint32_t i = 0; i < kNumOfHeaps * bindGroupsPerHeap; ++i) { + pass.SetBindGroup(0, bindGroups[i % kNumOfBindGroups]); + pass.Draw(3, 1, 0, 0); + } + + pass.EndPass(); + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + // Final accumulated color is result of sampled + UBO color. + RGBA8 filled(255, 255, 0, 255); + RGBA8 notFilled(0, 0, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, kRTSize - 1, 0); + } +} + +DAWN_INSTANTIATE_TEST(D3D12DescriptorHeapTests, + D3D12Backend(), + D3D12Backend({"use_d3d12_small_shader_visible_heap"}));