// Copyright 2020 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 "common/Math.h" #include "utils/ComboRenderPipelineDescriptor.h" #include "utils/WGPUHelpers.h" namespace { // https://github.com/gpuweb/gpuweb/issues/108 // Vulkan, Metal, and D3D11 have the same standard multisample pattern. D3D12 is the same as // D3D11 but it was left out of the documentation. // {0.375, 0.125}, {0.875, 0.375}, {0.125 0.625}, {0.625, 0.875} // In this test, we store them in -1 to 1 space because it makes it // simpler to upload vertex data. Y is flipped because there is a flip between clip space and // rasterization space. static constexpr std::array, 4> kSamplePositions = { {{0.375 * 2 - 1, 1 - 0.125 * 2}, {0.875 * 2 - 1, 1 - 0.375 * 2}, {0.125 * 2 - 1, 1 - 0.625 * 2}, {0.625 * 2 - 1, 1 - 0.875 * 2}}}; } // anonymous namespace class MultisampledSamplingTest : public DawnTest { protected: static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::R8Unorm; static constexpr wgpu::TextureFormat kDepthFormat = wgpu::TextureFormat::Depth32Float; static constexpr wgpu::TextureFormat kDepthOutFormat = wgpu::TextureFormat::R32Float; static constexpr uint32_t kSampleCount = 4; // Render pipeline for drawing to a multisampled color and depth attachment. wgpu::RenderPipeline drawPipeline; // A compute pipeline to texelFetch the sample locations and output the results to a buffer. wgpu::ComputePipeline checkSamplePipeline; void SetUp() override { DawnTest::SetUp(); { utils::ComboRenderPipelineDescriptor2 desc; desc.vertex.module = utils::CreateShaderModuleFromWGSL(device, R"( [[location(0)]] var pos : vec2; [[builtin(position)]] var Position : vec4; [[stage(vertex)]] fn main() -> void { Position = vec4(pos, 0.0, 1.0); })"); desc.cFragment.module = utils::CreateShaderModuleFromWGSL(device, R"( [[location(0)]] var fragColor : f32; [[builtin(frag_depth)]] var FragDepth : f32; [[stage(fragment)]] fn main() -> void { fragColor = 1.0; FragDepth = 0.7; })"); desc.primitive.stripIndexFormat = wgpu::IndexFormat::Uint32; desc.vertex.bufferCount = 1; desc.cBuffers[0].attributeCount = 1; desc.cBuffers[0].arrayStride = 2 * sizeof(float); desc.cAttributes[0].format = wgpu::VertexFormat::Float32x2; wgpu::DepthStencilState* depthStencil = desc.EnableDepthStencil(kDepthFormat); depthStencil->depthWriteEnabled = true; desc.multisample.count = kSampleCount; desc.cFragment.targetCount = 1; desc.cTargets[0].format = kColorFormat; desc.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip; drawPipeline = device.CreateRenderPipeline2(&desc); } { wgpu::ComputePipelineDescriptor desc = {}; desc.computeStage.entryPoint = "main"; desc.computeStage.module = utils::CreateShaderModuleFromWGSL(device, R"( [[group(0), binding(0)]] var texture0 : texture_multisampled_2d; [[group(0), binding(1)]] var texture1 : texture_multisampled_2d; [[block]] struct Results { colorSamples : array; depthSamples : array; }; [[group(0), binding(2)]] var results : [[access(read_write)]] Results; [[stage(compute)]] fn main() -> void { for (var i : i32 = 0; i < 4; i = i + 1) { results.colorSamples[i] = textureLoad(texture0, vec2(0, 0), i).x; results.depthSamples[i] = textureLoad(texture1, vec2(0, 0), i).x; } })"); checkSamplePipeline = device.CreateComputePipeline(&desc); } } }; // Test that the multisampling sample positions are correct. This test works by drawing a // thin quad multiple times from left to right and from top to bottom on a 1x1 canvas. // Each time, the quad should cover a single sample position. // After drawing, a compute shader fetches all of the samples (both color and depth), // and we check that only the one covered has data. // We "scan" the vertical and horizontal dimensions separately to check that the triangle // must cover both the X and Y coordinates of the sample position (no false positives if // it covers the X position but not the Y, or vice versa). TEST_P(MultisampledSamplingTest, SamplePositions) { static constexpr wgpu::Extent3D kTextureSize = {1, 1, 1}; wgpu::Texture colorTexture; { wgpu::TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::RenderAttachment; desc.size = kTextureSize; desc.format = kColorFormat; desc.sampleCount = kSampleCount; colorTexture = device.CreateTexture(&desc); } wgpu::Texture depthTexture; { wgpu::TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::RenderAttachment; desc.size = kTextureSize; desc.format = kDepthFormat; desc.sampleCount = kSampleCount; depthTexture = device.CreateTexture(&desc); } static constexpr float kQuadWidth = 0.075; std::vector vBufferData; // Add vertices for vertical quads for (uint32_t s = 0; s < kSampleCount; ++s) { // clang-format off vBufferData.insert(vBufferData.end(), { kSamplePositions[s][0] - kQuadWidth, -1.0, kSamplePositions[s][0] - kQuadWidth, 1.0, kSamplePositions[s][0] + kQuadWidth, -1.0, kSamplePositions[s][0] + kQuadWidth, 1.0, }); // clang-format on } // Add vertices for horizontal quads for (uint32_t s = 0; s < kSampleCount; ++s) { // clang-format off vBufferData.insert(vBufferData.end(), { -1.0, kSamplePositions[s][1] - kQuadWidth, -1.0, kSamplePositions[s][1] + kQuadWidth, 1.0, kSamplePositions[s][1] - kQuadWidth, 1.0, kSamplePositions[s][1] + kQuadWidth, }); // clang-format on } wgpu::Buffer vBuffer = utils::CreateBufferFromData( device, vBufferData.data(), static_cast(vBufferData.size() * sizeof(float)), wgpu::BufferUsage::Vertex); static constexpr uint32_t kQuadNumBytes = 8 * sizeof(float); wgpu::TextureView colorView = colorTexture.CreateView(); wgpu::TextureView depthView = depthTexture.CreateView(); static constexpr uint64_t kResultSize = 4 * sizeof(float) + 4 * sizeof(float); uint64_t alignedResultSize = Align(kResultSize, 256); wgpu::BufferDescriptor outputBufferDesc = {}; outputBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; outputBufferDesc.size = alignedResultSize * 8; wgpu::Buffer outputBuffer = device.CreateBuffer(&outputBufferDesc); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); for (uint32_t iter = 0; iter < 2; ++iter) { for (uint32_t sample = 0; sample < kSampleCount; ++sample) { uint32_t sampleOffset = (iter * kSampleCount + sample); utils::ComboRenderPassDescriptor renderPass({colorView}, depthView); renderPass.cDepthStencilAttachmentInfo.clearDepth = 0.f; wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); renderPassEncoder.SetPipeline(drawPipeline); renderPassEncoder.SetVertexBuffer(0, vBuffer, kQuadNumBytes * sampleOffset, kQuadNumBytes); renderPassEncoder.Draw(4); renderPassEncoder.EndPass(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(checkSamplePipeline); computePassEncoder.SetBindGroup( 0, utils::MakeBindGroup( device, checkSamplePipeline.GetBindGroupLayout(0), {{0, colorView}, {1, depthView}, {2, outputBuffer, alignedResultSize * sampleOffset, kResultSize}})); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); } } wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(); queue.Submit(1, &commandBuffer); std::array expectedData; expectedData = {1, 0, 0, 0, 0.7, 0, 0, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 0 * alignedResultSize, 8) << "vertical sample 0"; expectedData = {0, 1, 0, 0, 0, 0.7, 0, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 1 * alignedResultSize, 8) << "vertical sample 1"; expectedData = {0, 0, 1, 0, 0, 0, 0.7, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 2 * alignedResultSize, 8) << "vertical sample 2"; expectedData = {0, 0, 0, 1, 0, 0, 0, 0.7}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 3 * alignedResultSize, 8) << "vertical sample 3"; expectedData = {1, 0, 0, 0, 0.7, 0, 0, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 4 * alignedResultSize, 8) << "horizontal sample 0"; expectedData = {0, 1, 0, 0, 0, 0.7, 0, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 5 * alignedResultSize, 8) << "horizontal sample 1"; expectedData = {0, 0, 1, 0, 0, 0, 0.7, 0}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 6 * alignedResultSize, 8) << "horizontal sample 2"; expectedData = {0, 0, 0, 1, 0, 0, 0, 0.7}; EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 7 * alignedResultSize, 8) << "horizontal sample 3"; } DAWN_INSTANTIATE_TEST(MultisampledSamplingTest, D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend());