// 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 "common/Assert.h" #include "common/Constants.h" #include "tests/DawnTest.h" #include "utils/ComboRenderPipelineDescriptor.h" #include "utils/DawnHelpers.h" constexpr static unsigned int kRTSize = 8; class BindGroupTests : public DawnTest { protected: dawn::CommandBuffer CreateSimpleComputeCommandBuffer( const dawn::ComputePipeline& pipeline, const dawn::BindGroup& bindGroup) { dawn::CommandEncoder encoder = device.CreateCommandEncoder(); dawn::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(pipeline); pass.SetBindGroup(0, bindGroup, 0, nullptr); pass.Dispatch(1, 1, 1); pass.EndPass(); return encoder.Finish(); } dawn::PipelineLayout MakeBasicPipelineLayout( dawn::Device device, std::vector bindingInitializer) const { dawn::PipelineLayoutDescriptor descriptor; descriptor.bindGroupLayoutCount = bindingInitializer.size(); descriptor.bindGroupLayouts = bindingInitializer.data(); return device.CreatePipelineLayout(&descriptor); } }; // Test a bindgroup reused in two command buffers in the same call to queue.Submit(). // This test passes by not asserting or crashing. TEST_P(BindGroupTests, ReusedBindGroupSingleSubmit) { dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, { {0, dawn::ShaderStageBit::Compute, dawn::BindingType::UniformBuffer}, } ); dawn::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl); const char* shader = R"( #version 450 layout(std140, set = 0, binding = 0) uniform Contents { float f; } contents; void main() { } )"; dawn::ShaderModule module = utils::CreateShaderModule(device, utils::ShaderStage::Compute, shader); dawn::ComputePipelineDescriptor cpDesc; cpDesc.layout = pl; dawn::PipelineStageDescriptor computeStage; computeStage.module = module; computeStage.entryPoint = "main"; cpDesc.computeStage = &computeStage; dawn::ComputePipeline cp = device.CreateComputePipeline(&cpDesc); dawn::BufferDescriptor bufferDesc; bufferDesc.size = sizeof(float); bufferDesc.usage = dawn::BufferUsageBit::CopyDst | dawn::BufferUsageBit::Uniform; dawn::Buffer buffer = device.CreateBuffer(&bufferDesc); dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, sizeof(float)}}); dawn::CommandBuffer cb[2]; cb[0] = CreateSimpleComputeCommandBuffer(cp, bindGroup); cb[1] = CreateSimpleComputeCommandBuffer(cp, bindGroup); queue.Submit(2, cb); } // Test a bindgroup containing a UBO which is used in both the vertex and fragment shader. // It contains a transformation matrix for the VS and the fragment color for the FS. // These must result in different register offsets in the native APIs. TEST_P(BindGroupTests, ReusedUBO) { // TODO(jiawei.shao@intel.com): find out why this test fails on Metal DAWN_SKIP_TEST_IF(IsMetal()); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); dawn::ShaderModule vsModule = utils::CreateShaderModule(device, utils::ShaderStage::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); })"); dawn::ShaderModule fsModule = utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"( #version 450 layout (set = 0, binding = 1) uniform fragmentUniformBuffer { vec4 color; }; layout(location = 0) out vec4 fragColor; void main() { fragColor = color; })"); dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, { {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}, {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer}, } ); dawn::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, &bgl); utils::ComboRenderPipelineDescriptor textureDescriptor(device); textureDescriptor.layout = pipelineLayout; textureDescriptor.cVertexStage.module = vsModule; textureDescriptor.cFragmentStage.module = fsModule; textureDescriptor.cColorStates[0]->format = renderPass.colorFormat; dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&textureDescriptor); struct Data { float transform[8]; char padding[256 - 8 * sizeof(float)]; float color[4]; }; ASSERT(offsetof(Data, color) == 256); constexpr float dummy = 0.0f; Data data { { 1.f, 0.f, dummy, dummy, 0.f, 1.0f, dummy, dummy }, { 0 }, { 0.f, 1.f, 0.f, 1.f }, }; dawn::Buffer buffer = utils::CreateBufferFromData(device, &data, sizeof(data), dawn::BufferUsageBit::Uniform); dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, { {0, buffer, 0, sizeof(Data::transform)}, {1, buffer, 256, sizeof(Data::color)} }); dawn::CommandEncoder encoder = device.CreateCommandEncoder(); dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); pass.SetPipeline(pipeline); pass.SetBindGroup(0, bindGroup, 0, nullptr); pass.Draw(3, 1, 0, 0); pass.EndPass(); dawn::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); RGBA8 filled(0, 255, 0, 255); RGBA8 notFilled(0, 0, 0, 0); int min = 1, max = kRTSize - 3; EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max); EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max); } // Test a bindgroup containing a UBO in the vertex shader and a sampler and texture in the fragment shader. // In D3D12 for example, these different types of bindings end up in different namespaces, but the register // offsets used must match between the shader module and descriptor range. TEST_P(BindGroupTests, UBOSamplerAndTexture) { // TODO(jiawei.shao@intel.com): find out why this test fails on Metal DAWN_SKIP_TEST_IF(IsMetal()); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); dawn::ShaderModule vsModule = utils::CreateShaderModule(device, utils::ShaderStage::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); })"); dawn::ShaderModule fsModule = utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"( #version 450 layout (set = 0, binding = 1) uniform sampler samp; layout (set = 0, binding = 2) uniform texture2D tex; layout (location = 0) out vec4 fragColor; void main() { fragColor = texture(sampler2D(tex, samp), gl_FragCoord.xy); })"); dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, { {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}, {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler}, {2, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture}, } ); dawn::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, &bgl); utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); pipelineDescriptor.layout = pipelineLayout; pipelineDescriptor.cVertexStage.module = vsModule; pipelineDescriptor.cFragmentStage.module = fsModule; pipelineDescriptor.cColorStates[0]->format = renderPass.colorFormat; dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); constexpr float dummy = 0.0f; constexpr float transform[] = { 1.f, 0.f, dummy, dummy, 0.f, 1.f, dummy, dummy }; dawn::Buffer buffer = utils::CreateBufferFromData(device, &transform, sizeof(transform), dawn::BufferUsageBit::Uniform); dawn::SamplerDescriptor samplerDescriptor; samplerDescriptor.minFilter = dawn::FilterMode::Nearest; samplerDescriptor.magFilter = dawn::FilterMode::Nearest; samplerDescriptor.mipmapFilter = dawn::FilterMode::Nearest; samplerDescriptor.addressModeU = dawn::AddressMode::ClampToEdge; samplerDescriptor.addressModeV = dawn::AddressMode::ClampToEdge; samplerDescriptor.addressModeW = dawn::AddressMode::ClampToEdge; samplerDescriptor.lodMinClamp = kLodMin; samplerDescriptor.lodMaxClamp = kLodMax; samplerDescriptor.compare = dawn::CompareFunction::Never; dawn::Sampler sampler = device.CreateSampler(&samplerDescriptor); dawn::TextureDescriptor descriptor; descriptor.dimension = dawn::TextureDimension::e2D; descriptor.size.width = kRTSize; descriptor.size.height = kRTSize; descriptor.size.depth = 1; descriptor.arrayLayerCount = 1; descriptor.sampleCount = 1; descriptor.format = dawn::TextureFormat::RGBA8Unorm; descriptor.mipLevelCount = 1; descriptor.usage = dawn::TextureUsageBit::CopyDst | dawn::TextureUsageBit::Sampled; dawn::Texture texture = device.CreateTexture(&descriptor); dawn::TextureView textureView = texture.CreateDefaultView(); int width = kRTSize, height = kRTSize; int widthInBytes = width * sizeof(RGBA8); widthInBytes = (widthInBytes + 255) & ~255; int sizeInBytes = widthInBytes * height; int size = sizeInBytes / sizeof(RGBA8); std::vector data = std::vector(size); for (int i = 0; i < size; i++) { data[i] = RGBA8(0, 255, 0, 255); } dawn::Buffer stagingBuffer = utils::CreateBufferFromData(device, data.data(), sizeInBytes, dawn::BufferUsageBit::CopySrc); dawn::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, { {0, buffer, 0, sizeof(transform)}, {1, sampler}, {2, textureView} }); dawn::CommandEncoder encoder = device.CreateCommandEncoder(); dawn::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(stagingBuffer, 0, widthInBytes, 0); dawn::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, 0, {0, 0, 0}); dawn::Extent3D copySize = {width, height, 1}; encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©Size); dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); pass.SetPipeline(pipeline); pass.SetBindGroup(0, bindGroup, 0, nullptr); pass.Draw(3, 1, 0, 0); pass.EndPass(); dawn::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); RGBA8 filled(0, 255, 0, 255); RGBA8 notFilled(0, 0, 0, 0); int min = 1, max = kRTSize - 3; EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max); EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max); } TEST_P(BindGroupTests, MultipleBindLayouts) { // Test fails on Metal. // https://bugs.chromium.org/p/dawn/issues/detail?id=33 DAWN_SKIP_TEST_IF(IsMetal()); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); dawn::ShaderModule vsModule = utils::CreateShaderModule(device, utils::ShaderStage::Vertex, R"( #version 450 layout (set = 0, binding = 0) uniform vertexUniformBuffer1 { mat2 transform1; }; layout (set = 1, binding = 0) uniform vertexUniformBuffer2 { mat2 transform2; }; 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((transform1 + transform2) * pos[gl_VertexIndex], 0.f, 1.f); })"); dawn::ShaderModule fsModule = utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"( #version 450 layout (set = 0, binding = 1) uniform fragmentUniformBuffer1 { vec4 color1; }; layout (set = 1, binding = 1) uniform fragmentUniformBuffer2 { vec4 color2; }; layout(location = 0) out vec4 fragColor; void main() { fragColor = color1 + color2; })"); dawn::BindGroupLayout layout = utils::MakeBindGroupLayout( device, { {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer}, {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer}, }); dawn::PipelineLayout pipelineLayout = MakeBasicPipelineLayout(device, {layout, layout}); utils::ComboRenderPipelineDescriptor textureDescriptor(device); textureDescriptor.layout = pipelineLayout; textureDescriptor.cVertexStage.module = vsModule; textureDescriptor.cFragmentStage.module = fsModule; textureDescriptor.cColorStates[0]->format = renderPass.colorFormat; dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&textureDescriptor); struct Data { float transform[8]; char padding[256 - 8 * sizeof(float)]; float color[4]; }; ASSERT(offsetof(Data, color) == 256); std::vector data; std::vector buffers; std::vector bindGroups; data.push_back( {{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, {0}, {0.0f, 1.0f, 0.0f, 1.0f}}); data.push_back( {{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, {0}, {1.0f, 0.0f, 0.0f, 1.0f}}); for (int i = 0; i < 2; i++) { dawn::Buffer buffer = utils::CreateBufferFromData(device, &data[i], sizeof(Data), dawn::BufferUsageBit::Uniform); buffers.push_back(buffer); bindGroups.push_back(utils::MakeBindGroup(device, layout, {{0, buffers[i], 0, sizeof(Data::transform)}, {1, buffers[i], 256, sizeof(Data::color)}})); } dawn::CommandEncoder encoder = device.CreateCommandEncoder(); dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); pass.SetPipeline(pipeline); pass.SetBindGroup(0, bindGroups[0], 0, nullptr); pass.SetBindGroup(1, bindGroups[1], 0, nullptr); pass.Draw(3, 1, 0, 0); pass.EndPass(); dawn::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); RGBA8 filled(255, 255, 0, 255); RGBA8 notFilled(0, 0, 0, 0); int min = 1, max = kRTSize - 3; EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max); EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max); } // This test reproduces an out-of-bound bug on D3D12 backends when calling draw command twice with // one pipeline that has 4 bind group sets in one render pass. TEST_P(BindGroupTests, DrawTwiceInSamePipelineWithFourBindGroupSets) { utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); dawn::ShaderModule vsModule = utils::CreateShaderModule(device, utils::ShaderStage::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); })"); dawn::ShaderModule fsModule = utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"( #version 450 layout (std140, set = 0, binding = 0) uniform fragmentUniformBuffer1 { vec4 color1; }; layout (std140, set = 1, binding = 0) uniform fragmentUniformBuffer2 { vec4 color2; }; layout (std140, set = 2, binding = 0) uniform fragmentUniformBuffer3 { vec4 color3; }; layout (std140, set = 3, binding = 0) uniform fragmentUniformBuffer4 { vec4 color4; }; layout(location = 0) out vec4 fragColor; void main() { fragColor = color1 + color2 + color3 + color4; })"); dawn::BindGroupLayout layout = utils::MakeBindGroupLayout( device, { { 0, dawn::ShaderStageBit::Fragment, dawn::BindingType::UniformBuffer } }); dawn::PipelineLayout pipelineLayout = MakeBasicPipelineLayout( device, { layout, layout, layout, layout }); utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); pipelineDescriptor.layout = pipelineLayout; pipelineDescriptor.cVertexStage.module = vsModule; pipelineDescriptor.cFragmentStage.module = fsModule; pipelineDescriptor.cColorStates[0]->format = renderPass.colorFormat; dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); dawn::CommandEncoder encoder = device.CreateCommandEncoder(); dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); pass.SetPipeline(pipeline); std::array color = { 0.25, 0, 0, 0.25 }; dawn::Buffer uniformBuffer = utils::CreateBufferFromData( device, &color, sizeof(color), dawn::BufferUsageBit::Uniform); dawn::BindGroup bindGroup = utils::MakeBindGroup( device, layout, { { 0, uniformBuffer, 0, sizeof(color) } }); pass.SetBindGroup(0, bindGroup, 0, nullptr); pass.SetBindGroup(1, bindGroup, 0, nullptr); pass.SetBindGroup(2, bindGroup, 0, nullptr); pass.SetBindGroup(3, bindGroup, 0, nullptr); pass.Draw(3, 1, 0, 0); pass.SetPipeline(pipeline); pass.Draw(3, 1, 0, 0); pass.EndPass(); dawn::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); RGBA8 filled(255, 0, 0, 255); RGBA8 notFilled(0, 0, 0, 0); int min = 1, max = kRTSize - 3; EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min); EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max); EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max); } DAWN_INSTANTIATE_TEST(BindGroupTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);