| // Copyright 2023 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <vector> |
| |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class PixelLocalStorageTests : public DawnTest { |
| protected: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| DAWN_TEST_UNSUPPORTED_IF( |
| !device.HasFeature(wgpu::FeatureName::PixelLocalStorageCoherent) && |
| !device.HasFeature(wgpu::FeatureName::PixelLocalStorageNonCoherent)); |
| |
| supportsCoherent = device.HasFeature(wgpu::FeatureName::PixelLocalStorageCoherent); |
| } |
| |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| std::vector<wgpu::FeatureName> requiredFeatures = {}; |
| if (SupportsFeatures({wgpu::FeatureName::PixelLocalStorageCoherent})) { |
| requiredFeatures.push_back(wgpu::FeatureName::PixelLocalStorageCoherent); |
| supportsCoherent = true; |
| } |
| if (SupportsFeatures({wgpu::FeatureName::PixelLocalStorageNonCoherent})) { |
| requiredFeatures.push_back(wgpu::FeatureName::PixelLocalStorageNonCoherent); |
| } |
| return requiredFeatures; |
| } |
| |
| struct StorageSpec { |
| uint64_t offset; |
| wgpu::TextureFormat format; |
| wgpu::LoadOp loadOp = wgpu::LoadOp::Clear; |
| wgpu::StoreOp storeOp = wgpu::StoreOp::Store; |
| wgpu::Color clearValue = {0, 0, 0, 0}; |
| bool discardAfterInit = false; |
| }; |
| |
| enum class CheckMethod { |
| StorageBuffer, |
| ReadStorageAttachments, |
| RenderAttachment, |
| }; |
| |
| struct PLSSpec { |
| uint64_t totalSize; |
| std::vector<StorageSpec> attachments; |
| CheckMethod checkMethod = CheckMethod::ReadStorageAttachments; |
| }; |
| |
| // Builds a shader module with multiple entry points used for testing PLS. |
| // |
| // - A trivial vertex entrypoint to render a point. |
| // - Various fragment entrypoints using a pixel_local block matching the `spec`. |
| // - An accumulator entrypoint adding (slot + 1) to each pls slot so we can check that |
| // access to the PLS is correctly synchronized. |
| // - An entrypoint copying the PLS data to a storage buffer for readback. |
| // - An entrypoint copying the PLS data to a render attachment for readback. |
| wgpu::ShaderModule MakeTestModule(const PLSSpec& spec) const { |
| std::vector<const char*> plsTypes; |
| plsTypes.resize(spec.totalSize / kPLSSlotByteSize, "u32"); |
| for (const auto& attachment : spec.attachments) { |
| switch (attachment.format) { |
| case wgpu::TextureFormat::R32Uint: |
| plsTypes[attachment.offset / kPLSSlotByteSize] = "u32"; |
| break; |
| case wgpu::TextureFormat::R32Sint: |
| plsTypes[attachment.offset / kPLSSlotByteSize] = "i32"; |
| break; |
| case wgpu::TextureFormat::R32Float: |
| plsTypes[attachment.offset / kPLSSlotByteSize] = "f32"; |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| std::ostringstream o; |
| o << R"( |
| enable chromium_experimental_pixel_local; |
| |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0, 0.5); |
| } |
| |
| )"; |
| o << "struct PLS {\n"; |
| for (size_t i = 0; i < plsTypes.size(); i++) { |
| // e.g.: a0 : u32, |
| o << " a" << i << " : " << plsTypes[i] << ",\n"; |
| } |
| o << "}\n"; |
| o << "var<pixel_local> pls : PLS;\n"; |
| o << "@fragment fn accumulator() {\n"; |
| for (size_t i = 0; i < plsTypes.size(); i++) { |
| // e.g.: pls.a0 = pls.a0 + 1; |
| o << " pls.a" << i << " = pls.a" << i << " + " << (i + 1) << ";\n"; |
| } |
| o << "}\n"; |
| o << "\n"; |
| o << "@group(0) @binding(0) var<storage, read_write> readbackStorageBuffer : array<u32>;\n"; |
| o << "@fragment fn readbackToStorageBuffer() {\n"; |
| for (size_t i = 0; i < plsTypes.size(); i++) { |
| // e.g.: readbackStorageBuffer[0] = u32(pls.a0); |
| o << " readbackStorageBuffer[" << i << "] = u32(pls.a" << i << ");\n"; |
| } |
| o << "}\n"; |
| o << "\n"; |
| o << "@fragment fn copyToColorAttachment() -> @location(0) vec4f {\n"; |
| o << " var result : vec4f;\n"; |
| for (size_t i = 0; i < plsTypes.size(); i++) { |
| // e.g.: result[0] = f32(pls.a0) / 255.0; |
| o << " result[" << i << "] = f32(pls.a" << i << ") / 255.0;\n"; |
| } |
| o << " return result;"; |
| o << "}\n"; |
| |
| return utils::CreateShaderModule(device, o.str().c_str()); |
| } |
| |
| wgpu::PipelineLayout MakeTestLayout(const PLSSpec& spec, wgpu::BindGroupLayout bgl = {}) const { |
| std::vector<wgpu::PipelineLayoutStorageAttachment> storageAttachments; |
| for (const auto& attachmentSpec : spec.attachments) { |
| wgpu::PipelineLayoutStorageAttachment attachment; |
| attachment.format = attachmentSpec.format; |
| attachment.offset = attachmentSpec.offset; |
| storageAttachments.push_back(attachment); |
| } |
| |
| wgpu::PipelineLayoutPixelLocalStorage pls; |
| pls.totalPixelLocalStorageSize = spec.totalSize; |
| pls.storageAttachmentCount = storageAttachments.size(); |
| pls.storageAttachments = storageAttachments.data(); |
| |
| wgpu::PipelineLayoutDescriptor plDesc; |
| plDesc.nextInChain = &pls; |
| plDesc.bindGroupLayoutCount = 0; |
| if (bgl != nullptr) { |
| plDesc.bindGroupLayoutCount = 1; |
| plDesc.bindGroupLayouts = &bgl; |
| } |
| |
| return device.CreatePipelineLayout(&plDesc); |
| } |
| |
| std::vector<wgpu::Texture> MakeTestStorageAttachments(const PLSSpec& spec) const { |
| std::vector<wgpu::Texture> attachments; |
| for (size_t i = 0; i < spec.attachments.size(); i++) { |
| const StorageSpec& attachmentSpec = spec.attachments[i]; |
| |
| wgpu::TextureDescriptor desc; |
| desc.format = attachmentSpec.format; |
| desc.size = {1, 1}; |
| desc.usage = wgpu::TextureUsage::StorageAttachment | wgpu::TextureUsage::CopySrc | |
| wgpu::TextureUsage::CopyDst; |
| if (attachmentSpec.discardAfterInit) { |
| desc.usage |= wgpu::TextureUsage::RenderAttachment; |
| } |
| |
| wgpu::Texture attachment = device.CreateTexture(&desc); |
| |
| // Initialize the attachment with 1s if LoadOp is Load, copying from another texture |
| // so that we avoid adding the extra RenderAttachment usage to the storage attachment. |
| if (attachmentSpec.loadOp == wgpu::LoadOp::Load) { |
| desc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| wgpu::Texture clearedTexture = device.CreateTexture(&desc); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| |
| // The pass that clears clearedTexture. |
| utils::ComboRenderPassDescriptor rpDesc({clearedTexture.CreateView()}); |
| rpDesc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| rpDesc.cColorAttachments[0].clearValue = attachmentSpec.clearValue; |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rpDesc); |
| pass.End(); |
| |
| // Copy clearedTexture -> attachment. |
| wgpu::ImageCopyTexture src = utils::CreateImageCopyTexture(clearedTexture); |
| wgpu::ImageCopyTexture dst = utils::CreateImageCopyTexture(attachment); |
| wgpu::Extent3D copySize = {1, 1, 1}; |
| encoder.CopyTextureToTexture(&src, &dst, ©Size); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| } |
| |
| // Discard after initialization to check that the lazy zero init is actually triggered |
| // (and it's not just that the resource happened to be zeroes already). |
| if (attachmentSpec.discardAfterInit) { |
| utils::ComboRenderPassDescriptor rpDesc({attachment.CreateView()}); |
| rpDesc.cColorAttachments[0].loadOp = wgpu::LoadOp::Load; |
| rpDesc.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rpDesc); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| } |
| |
| attachments.push_back(attachment); |
| } |
| |
| return attachments; |
| } |
| |
| wgpu::RenderPassEncoder BeginTestRenderPass( |
| const PLSSpec& spec, |
| const wgpu::CommandEncoder& encoder, |
| const std::vector<wgpu::Texture>& storageAttachments, |
| wgpu::Texture colorAttachment) const { |
| std::vector<wgpu::RenderPassStorageAttachment> attachmentDescs; |
| for (size_t i = 0; i < spec.attachments.size(); i++) { |
| const StorageSpec& attachmentSpec = spec.attachments[i]; |
| |
| wgpu::RenderPassStorageAttachment attachment; |
| attachment.storage = storageAttachments[i].CreateView(); |
| attachment.offset = attachmentSpec.offset; |
| attachment.loadOp = attachmentSpec.loadOp; |
| attachment.storeOp = attachmentSpec.storeOp; |
| attachment.clearValue = attachmentSpec.clearValue; |
| attachmentDescs.push_back(attachment); |
| } |
| |
| wgpu::RenderPassPixelLocalStorage rpPlsDesc; |
| rpPlsDesc.totalPixelLocalStorageSize = spec.totalSize; |
| rpPlsDesc.storageAttachmentCount = attachmentDescs.size(); |
| rpPlsDesc.storageAttachments = attachmentDescs.data(); |
| |
| wgpu::RenderPassDescriptor rpDesc; |
| rpDesc.nextInChain = &rpPlsDesc; |
| rpDesc.colorAttachmentCount = 0; |
| rpDesc.depthStencilAttachment = nullptr; |
| |
| wgpu::RenderPassColorAttachment rpColor; |
| if (colorAttachment != nullptr) { |
| rpColor.view = colorAttachment.CreateView(); |
| rpColor.loadOp = wgpu::LoadOp::Clear; |
| rpColor.clearValue = {0, 0, 0, 0}; |
| rpColor.storeOp = wgpu::StoreOp::Store; |
| |
| rpDesc.colorAttachments = &rpColor; |
| rpDesc.colorAttachmentCount = 1; |
| } |
| |
| return encoder.BeginRenderPass(&rpDesc); |
| } |
| |
| uint32_t ComputeExpectedValue(const PLSSpec& spec, size_t slot) { |
| for (const StorageSpec& attachment : spec.attachments) { |
| if (attachment.offset / kPLSSlotByteSize != slot) { |
| continue; |
| } |
| |
| // Compute the expected value depending on load/store ops by "replaying" the operations |
| // that would be done. |
| int32_t expectedValue = 0; |
| if (!attachment.discardAfterInit) { |
| expectedValue = attachment.clearValue.r; |
| } |
| expectedValue += (slot + 1) * kIterations; |
| |
| if (attachment.storeOp == wgpu::StoreOp::Discard) { |
| expectedValue = 0; |
| } |
| |
| return expectedValue; |
| } |
| |
| // This is not an explicit storage attachment. |
| return (slot + 1) * kIterations; |
| } |
| |
| void CheckByReadingStorageAttachments(const PLSSpec& spec, |
| const std::vector<wgpu::Texture>& storageAttachments) { |
| for (size_t i = 0; i < spec.attachments.size(); i++) { |
| const StorageSpec& attachmentSpec = spec.attachments[i]; |
| uint32_t slot = attachmentSpec.offset / kPLSSlotByteSize; |
| |
| uint32_t expectedValue = ComputeExpectedValue(spec, slot); |
| |
| switch (spec.attachments[i].format) { |
| case wgpu::TextureFormat::R32Float: |
| EXPECT_TEXTURE_EQ(static_cast<float>(expectedValue), storageAttachments[i], |
| {0, 0}); |
| break; |
| case wgpu::TextureFormat::R32Uint: |
| case wgpu::TextureFormat::R32Sint: |
| EXPECT_TEXTURE_EQ(expectedValue, storageAttachments[i], {0, 0}); |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| } |
| |
| void CheckByReadingColorAttachment(const PLSSpec& spec, wgpu::Texture color) { |
| std::array<uint32_t, 4> expected = {0, 0, 0, 0}; |
| for (size_t slot = 0; slot < spec.totalSize / kPLSSlotByteSize; slot++) { |
| expected[slot] = ComputeExpectedValue(spec, slot); |
| } |
| |
| utils::RGBA8 expectedColor(expected[0], expected[1], expected[2], expected[3]); |
| EXPECT_TEXTURE_EQ(expectedColor, color, {0, 0}); |
| } |
| |
| void CheckByReadingStorageBuffer(const PLSSpec& spec, wgpu::Buffer buffer) { |
| for (size_t slot = 0; slot < spec.totalSize / kPLSSlotByteSize; slot++) { |
| uint32_t expectedValue = ComputeExpectedValue(spec, slot); |
| EXPECT_BUFFER_U32_EQ(expectedValue, buffer, slot * kPLSSlotByteSize); |
| } |
| } |
| |
| bool RequiresColorAttachment(const PLSSpec& spec) { |
| return spec.attachments.empty() || spec.checkMethod == CheckMethod::RenderAttachment; |
| } |
| |
| void SetColorTargets(const PLSSpec& spec, |
| utils::ComboRenderPipelineDescriptor* desc, |
| bool writesColor) { |
| if (RequiresColorAttachment(spec)) { |
| desc->cFragment.targetCount = 1; |
| desc->cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| desc->cTargets[0].writeMask = |
| writesColor ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None; |
| } else { |
| desc->cFragment.targetCount = 0; |
| } |
| } |
| |
| void DoTest(const PLSSpec& spec) { |
| wgpu::ShaderModule module = MakeTestModule(spec); |
| |
| // Make the pipeline that will draw a point that adds i to the i-th slot of the PLS. |
| wgpu::RenderPipeline accumulatorPipeline; |
| { |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.layout = MakeTestLayout(spec); |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| desc.cFragment.entryPoint = "accumulator"; |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| SetColorTargets(spec, &desc, false); |
| accumulatorPipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| wgpu::RenderPipeline checkPipeline; |
| wgpu::BindGroup checkBindGroup; |
| wgpu::Buffer readbackStorageBuffer; |
| |
| if (spec.checkMethod == CheckMethod::StorageBuffer) { |
| // Make the pipeline copying the PLS to the storage buffer. |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.layout = MakeTestLayout(spec, bgl); |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| desc.cFragment.entryPoint = "readbackToStorageBuffer"; |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| SetColorTargets(spec, &desc, false); |
| checkPipeline = device.CreateRenderPipeline(&desc); |
| |
| wgpu::BufferDescriptor bufDesc; |
| bufDesc.size = spec.totalSize; |
| bufDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; |
| readbackStorageBuffer = device.CreateBuffer(&bufDesc); |
| |
| checkBindGroup = utils::MakeBindGroup(device, bgl, {{0, readbackStorageBuffer}}); |
| } |
| if (spec.checkMethod == CheckMethod::RenderAttachment) { |
| // Make the pipeline copying the PLS to the render attachment. |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.layout = MakeTestLayout(spec); |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| desc.cFragment.entryPoint = "copyToColorAttachment"; |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| SetColorTargets(spec, &desc, true); |
| checkPipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| // Make all the attachments. |
| std::vector<wgpu::Texture> storageAttachments = MakeTestStorageAttachments(spec); |
| |
| wgpu::Texture colorAttachment; |
| if (RequiresColorAttachment(spec)) { |
| wgpu::TextureDescriptor desc; |
| desc.size = {1, 1}; |
| desc.format = wgpu::TextureFormat::RGBA8Unorm; |
| desc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| colorAttachment = device.CreateTexture(&desc); |
| } |
| |
| { |
| // Build the render pass with the specified storage attachments |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = |
| BeginTestRenderPass(spec, encoder, storageAttachments, colorAttachment); |
| |
| // Draw the points accumulating to PLS, with a PLS barrier if needed. |
| pass.SetPipeline(accumulatorPipeline); |
| if (supportsCoherent) { |
| pass.Draw(kIterations); |
| } else { |
| for (uint32_t i = 0; i < kIterations; i++) { |
| pass.Draw(1); |
| pass.PixelLocalStorageBarrier(); |
| } |
| } |
| |
| // Run the checkPipeline, if any. |
| if (checkPipeline != nullptr) { |
| pass.SetPipeline(checkPipeline); |
| if (checkBindGroup != nullptr) { |
| pass.SetBindGroup(0, checkBindGroup); |
| } |
| pass.Draw(1); |
| } |
| |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| } |
| |
| switch (spec.checkMethod) { |
| case CheckMethod::StorageBuffer: |
| CheckByReadingStorageBuffer(spec, readbackStorageBuffer); |
| break; |
| case CheckMethod::ReadStorageAttachments: |
| CheckByReadingStorageAttachments(spec, storageAttachments); |
| break; |
| case CheckMethod::RenderAttachment: |
| CheckByReadingColorAttachment(spec, colorAttachment); |
| break; |
| } |
| |
| // Youpi! |
| } |
| |
| static constexpr uint32_t kIterations = 10; |
| bool supportsCoherent; |
| }; |
| |
| // Test that the various supported PLS format work for accumulation. |
| TEST_P(PixelLocalStorageTests, Formats) { |
| for (const auto format : {wgpu::TextureFormat::R32Uint, wgpu::TextureFormat::R32Sint, |
| wgpu::TextureFormat::R32Float}) { |
| PLSSpec spec = {4, {{0, format}}}; |
| DoTest(spec); |
| } |
| } |
| |
| // Tests the storage attachment load ops |
| TEST_P(PixelLocalStorageTests, LoadOp) { |
| // Test LoadOp::Clear with a couple values. |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].loadOp = wgpu::LoadOp::Clear; |
| |
| spec.attachments[0].clearValue.r = 42; |
| DoTest(spec); |
| |
| spec.attachments[0].clearValue.r = 38; |
| DoTest(spec); |
| } |
| |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Sint}}}; |
| spec.attachments[0].loadOp = wgpu::LoadOp::Clear; |
| |
| spec.attachments[0].clearValue.r = 42; |
| DoTest(spec); |
| |
| spec.attachments[0].clearValue.r = -38; |
| DoTest(spec); |
| } |
| |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Float}}}; |
| spec.attachments[0].loadOp = wgpu::LoadOp::Clear; |
| |
| spec.attachments[0].clearValue.r = 4.0; |
| DoTest(spec); |
| |
| spec.attachments[0].clearValue.r = -3.0; |
| DoTest(spec); |
| } |
| |
| // Test LoadOp::Load (the test helper clears the texture to clearValue). |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].clearValue.r = 18; |
| spec.attachments[0].loadOp = wgpu::LoadOp::Load; |
| DoTest(spec); |
| } |
| } |
| |
| // Tests the storage attachment store ops |
| TEST_P(PixelLocalStorageTests, StoreOp) { |
| // Test StoreOp::Store. |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].storeOp = wgpu::StoreOp::Store; |
| DoTest(spec); |
| } |
| |
| // Test StoreOp::Discard. |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].storeOp = wgpu::StoreOp::Discard; |
| DoTest(spec); |
| } |
| } |
| |
| // Test lazy zero initialization of the storage attachments. |
| TEST_P(PixelLocalStorageTests, ZeroInit) { |
| // Discard causes the storage attachment to be lazy zeroed. |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].storeOp = wgpu::StoreOp::Discard; |
| DoTest(spec); |
| } |
| |
| // Discard before using as a storage attachment, it should be lazy-cleared. |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Uint}}}; |
| spec.attachments[0].loadOp = wgpu::LoadOp::Load; |
| spec.attachments[0].clearValue.r = 18; |
| spec.attachments[0].discardAfterInit = true; |
| DoTest(spec); |
| } |
| } |
| |
| // Test many explicit storage attachments. |
| TEST_P(PixelLocalStorageTests, MultipleStorageAttachments) { |
| PLSSpec spec = {16, |
| { |
| {0, wgpu::TextureFormat::R32Sint}, |
| {4, wgpu::TextureFormat::R32Uint}, |
| {8, wgpu::TextureFormat::R32Float}, |
| {12, wgpu::TextureFormat::R32Sint}, |
| }}; |
| DoTest(spec); |
| } |
| |
| // Test explicit storage attachments in inverse offset order |
| TEST_P(PixelLocalStorageTests, InvertedOffsetOrder) { |
| PLSSpec spec = {8, |
| { |
| {4, wgpu::TextureFormat::R32Uint}, |
| {0, wgpu::TextureFormat::R32Sint}, |
| }}; |
| DoTest(spec); |
| } |
| |
| // Test implicit pixel local slot. |
| TEST_P(PixelLocalStorageTests, ImplicitSlot) { |
| PLSSpec spec = {4, {}, CheckMethod::StorageBuffer}; |
| DoTest(spec); |
| } |
| |
| // Test multiple implicit pixel local slot. |
| TEST_P(PixelLocalStorageTests, MultipleImplicitSlot) { |
| PLSSpec spec = {16, {}, CheckMethod::StorageBuffer}; |
| DoTest(spec); |
| } |
| |
| // Test mixed implicit / explicit pixel local slot. |
| TEST_P(PixelLocalStorageTests, MixedImplicitExplicit) { |
| { |
| PLSSpec spec = {16, |
| {{4, wgpu::TextureFormat::R32Uint}, {8, wgpu::TextureFormat::R32Float}}, |
| CheckMethod::StorageBuffer}; |
| DoTest(spec); |
| } |
| { |
| PLSSpec spec = {16, |
| {{4, wgpu::TextureFormat::R32Uint}, {12, wgpu::TextureFormat::R32Float}}, |
| CheckMethod::StorageBuffer}; |
| DoTest(spec); |
| } |
| { |
| PLSSpec spec = {16, |
| {{0, wgpu::TextureFormat::R32Uint}, {12, wgpu::TextureFormat::R32Float}}, |
| CheckMethod::StorageBuffer}; |
| DoTest(spec); |
| } |
| } |
| |
| // Test using PLS and then copying it to a render attachment, fully implicit version. |
| TEST_P(PixelLocalStorageTests, CopyToRenderAttachment) { |
| { |
| PLSSpec spec = {4, {}, CheckMethod::RenderAttachment}; |
| DoTest(spec); |
| } |
| { |
| PLSSpec spec = {16, {}, CheckMethod::RenderAttachment}; |
| DoTest(spec); |
| } |
| } |
| |
| // Test using PLS and then copying it to a render attachment, fully implicit version. |
| TEST_P(PixelLocalStorageTests, CopyToRenderAttachmentWithStorageAttachments) { |
| { |
| PLSSpec spec = {4, {{0, wgpu::TextureFormat::R32Float}}, CheckMethod::RenderAttachment}; |
| DoTest(spec); |
| } |
| { |
| PLSSpec spec = {16, {{8, wgpu::TextureFormat::R32Uint}}, CheckMethod::RenderAttachment}; |
| DoTest(spec); |
| } |
| } |
| |
| // Test using PLS in multiple render passes with different sizes in one command buffer, fully |
| // implicit version. |
| TEST_P(PixelLocalStorageTests, ImplicitPLSAndLargerRenderAttachmentsInOneCommandBuffer) { |
| // Prepare the shader modules with one fragment shader entry point that increases the pixel |
| // local storage variables and another fragment shader entry point that computes the output to |
| // the color attachment. |
| wgpu::ShaderModule wgslModule = utils::CreateShaderModule(device, R"( |
| enable chromium_experimental_pixel_local; |
| @vertex |
| fn vsMain(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { |
| var pos = array( |
| vec2f(-1.0, -1.0), |
| vec2f(-1.0, 1.0), |
| vec2f( 1.0, -1.0), |
| vec2f( 1.0, 1.0), |
| vec2f(-1.0, 1.0), |
| vec2f( 1.0, -1.0)); |
| return vec4f(pos[VertexIndex % 6], 0.5, 1.0); |
| } |
| |
| struct PLS { |
| a : u32, |
| b : u32, |
| }; |
| var<pixel_local> pls : PLS; |
| @fragment fn accumulate() -> @location(0) vec4u { |
| pls.a = pls.a + 1; |
| pls.b = pls.b + 2; |
| return vec4u(0, 0, 0, 1u); |
| } |
| |
| @fragment fn copyToColorAttachment() -> @location(0) vec4u { |
| return vec4u(pls.a + pls.b, 0, 0, 1u); |
| })"); |
| constexpr uint32_t kPixelLocalStorageSize = kPLSSlotByteSize * 2; |
| |
| // Create render pipelines in the test |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor; |
| { |
| pipelineDescriptor.vertex.module = wgslModule; |
| pipelineDescriptor.cFragment.module = wgslModule; |
| pipelineDescriptor.cFragment.entryPoint = "accumulate"; |
| pipelineDescriptor.cFragment.targetCount = 1; |
| pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::R32Uint; |
| pipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| |
| // Create pipeline layout with pixel local storage |
| wgpu::PipelineLayoutPixelLocalStorage pixelLocalStoragePipelineLayout; |
| pixelLocalStoragePipelineLayout.totalPixelLocalStorageSize = kPixelLocalStorageSize; |
| pixelLocalStoragePipelineLayout.storageAttachmentCount = 0; |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesciptor; |
| pipelineLayoutDesciptor.nextInChain = &pixelLocalStoragePipelineLayout; |
| pipelineLayoutDesciptor.bindGroupLayoutCount = 0; |
| pipelineDescriptor.layout = device.CreatePipelineLayout(&pipelineLayoutDesciptor); |
| } |
| // Create the render pipeline to update pixel local storage values |
| wgpu::RenderPipeline accumulatePipeline = device.CreateRenderPipeline(&pipelineDescriptor); |
| |
| // Create the render pipeline to compute the final output with the current pixel local storage |
| // values |
| pipelineDescriptor.cFragment.entryPoint = "copyToColorAttachment"; |
| wgpu::RenderPipeline copyToColorAttachmentPipeline = |
| device.CreateRenderPipeline(&pipelineDescriptor); |
| |
| // Create two color attachments with different sizes |
| wgpu::Texture color1; |
| wgpu::Texture color2; |
| constexpr wgpu::Extent3D kColorSize1 = {1, 1, 1}; |
| constexpr wgpu::Extent3D kColorSize2 = {2, 2, 1}; |
| { |
| wgpu::TextureDescriptor colorDescriptor; |
| colorDescriptor.size = kColorSize1; |
| colorDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; |
| colorDescriptor.format = wgpu::TextureFormat::R32Uint; |
| color1 = device.CreateTexture(&colorDescriptor); |
| colorDescriptor.size = kColorSize2; |
| color2 = device.CreateTexture(&colorDescriptor); |
| } |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| |
| // Use first color attachment with smaller size |
| wgpu::RenderPassDescriptor renderPassDescriptor; |
| wgpu::RenderPassPixelLocalStorage renderPassPixelLocalStorageDescriptor; |
| wgpu::RenderPassColorAttachment renderPassColor; |
| { |
| renderPassPixelLocalStorageDescriptor.totalPixelLocalStorageSize = kPixelLocalStorageSize; |
| renderPassPixelLocalStorageDescriptor.storageAttachmentCount = 0; |
| renderPassDescriptor.nextInChain = &renderPassPixelLocalStorageDescriptor; |
| renderPassColor.view = color1.CreateView(); |
| renderPassColor.loadOp = wgpu::LoadOp::Clear; |
| renderPassColor.clearValue = {0, 0, 0, 0}; |
| renderPassColor.storeOp = wgpu::StoreOp::Store; |
| renderPassDescriptor.colorAttachmentCount = 1; |
| renderPassDescriptor.colorAttachments = &renderPassColor; |
| } |
| wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPassDescriptor); |
| pass1.SetPipeline(accumulatePipeline); |
| if (supportsCoherent) { |
| pass1.Draw(kIterations * 6); |
| } else { |
| for (uint32_t i = 0; i < kIterations; i++) { |
| pass1.Draw(6); |
| pass1.PixelLocalStorageBarrier(); |
| } |
| } |
| pass1.SetPipeline(copyToColorAttachmentPipeline); |
| pass1.Draw(6); |
| pass1.End(); |
| |
| // Use second color attachment with larger size |
| renderPassColor.view = color2.CreateView(); |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&renderPassDescriptor); |
| pass2.SetPipeline(accumulatePipeline); |
| if (supportsCoherent) { |
| pass2.Draw(kIterations * 6 * 2); |
| } else { |
| for (uint32_t i = 0; i < kIterations * 2; i++) { |
| pass2.Draw(6); |
| pass2.PixelLocalStorageBarrier(); |
| } |
| } |
| |
| pass2.SetPipeline(copyToColorAttachmentPipeline); |
| pass2.Draw(6); |
| pass2.End(); |
| |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| // Check the data in color1 and color2 |
| constexpr uint32_t kExpectedResult1 = kIterations * 3; |
| EXPECT_TEXTURE_EQ(&kExpectedResult1, color1, {0, 0, 0}, {1, 1, 1}); |
| |
| constexpr std::array<uint32_t, 2> kExpectedResult2 = {{kIterations * 6, kIterations * 6}}; |
| EXPECT_TEXTURE_EQ(kExpectedResult2.data(), color2, {0, 0, 0}, {2, 1, 1}); |
| EXPECT_TEXTURE_EQ(kExpectedResult2.data(), color2, {0, 1, 0}, {2, 1, 1}); |
| } |
| |
| DAWN_INSTANTIATE_TEST(PixelLocalStorageTests, D3D11Backend(), MetalBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |