D3D12: Add end2end tests for small shader-visible heaps.
Adds a toggle to force the use of small shader-visible heaps and
whitebox tests to verify bindgroup encoding correctness.
BUG=dawn:155
Change-Id: I4118b850d9f2cb445ae805aa68ebf4fab671261b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16960
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp
index 5984095..225918c 100644
--- a/src/dawn_native/Toggles.cpp
+++ b/src/dawn_native/Toggles.cpp
@@ -119,6 +119,10 @@
{"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 c05363b..3cc40ec 100644
--- a/src/dawn_native/Toggles.h
+++ b/src/dawn_native/Toggles.h
@@ -40,6 +40,7 @@
MetalDisableSamplerCompare,
DisableBaseVertex,
DisableBaseInstance,
+ UseD3D12SmallShaderVisibleHeapForTesting,
EnumCount,
InvalidEnum = EnumCount,
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 2558a60..e9fdf87 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -410,6 +410,9 @@
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 76e2db2..101ca4b 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
@@ -22,7 +22,14 @@
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 @@
// 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 7dc0a91..decec9c 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 @@
void TestSetUp() override {
DAWN_SKIP_TEST_IF(UsesWire());
mD3DDevice = reinterpret_cast<Device*>(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<float, 4> 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 @@
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<wgpu::BindGroup> 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<float, 4> 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<wgpu::BindGroup> bindGroups;
+ for (uint32_t i = 0; i < heapSize - 1; i++) {
+ std::array<float, 4> 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<float, 4> 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<float, 4> redColor = {1, 0, 0, 1};
+ wgpu::Buffer firstUniformBuffer = utils::CreateBufferFromData(
+ device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform);
+
+ std::vector<wgpu::BindGroup> 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<float, 4>& 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<float, 4> redColor = {1, 0, 0, 1};
+
+ wgpu::Buffer firstUniformBuffer = utils::CreateBufferFromData(
+ device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform);
+
+ std::vector<wgpu::BindGroup> 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<float, 4> 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<float, 4> 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<wgpu::BindGroup> bindGroups;
+ for (uint32_t i = 0; i < kNumOfBindGroups - 1; i++) {
+ std::array<float, 4> 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<float, 4> 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"}));