| // 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 "common/Assert.h" | |
| #include "tests/DawnTest.h" | |
| #include "utils/ComboRenderPipelineDescriptor.h" | |
| #include "utils/WGPUHelpers.h" | |
| namespace { | |
| constexpr wgpu::CompareFunction kCompareFunctions[] = { | |
| wgpu::CompareFunction::Never, | |
| wgpu::CompareFunction::Less, | |
| wgpu::CompareFunction::LessEqual, | |
| wgpu::CompareFunction::Greater, | |
| wgpu::CompareFunction::GreaterEqual, | |
| wgpu::CompareFunction::Equal, | |
| wgpu::CompareFunction::NotEqual, | |
| wgpu::CompareFunction::Always, | |
| }; | |
| // Test a "normal" ref value between 0 and 1; as well as negative and > 1 refs. | |
| constexpr float kCompareRefs[] = {-0.1, 0.4, 1.2}; | |
| // Test 0, below the ref, equal to, above the ref, and 1. | |
| const std::vector<float> kNormalizedTextureValues = {0.0, 0.3, 0.4, 0.5, 1.0}; | |
| const std::vector<float> kNonNormalizedTextureValues = {-0.2, -0.1, 1.2, 1.3}; | |
| } // anonymous namespace | |
| class DepthSamplingTest : public DawnTest { | |
| protected: | |
| void SetUp() override { | |
| DawnTest::SetUp(); | |
| wgpu::BufferDescriptor uniformBufferDesc; | |
| uniformBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; | |
| uniformBufferDesc.size = sizeof(float); | |
| mUniformBuffer = device.CreateBuffer(&uniformBufferDesc); | |
| wgpu::BufferDescriptor textureUploadDesc; | |
| textureUploadDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; | |
| textureUploadDesc.size = sizeof(float); | |
| mTextureUploadBuffer = device.CreateBuffer(&textureUploadDesc); | |
| wgpu::TextureDescriptor inputTextureDesc; | |
| inputTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled | | |
| wgpu::TextureUsage::OutputAttachment; | |
| inputTextureDesc.size = {1, 1, 1}; | |
| inputTextureDesc.format = wgpu::TextureFormat::Depth32Float; | |
| mInputTexture = device.CreateTexture(&inputTextureDesc); | |
| wgpu::TextureDescriptor outputTextureDesc; | |
| outputTextureDesc.usage = | |
| wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc; | |
| outputTextureDesc.size = {1, 1, 1}; | |
| outputTextureDesc.format = wgpu::TextureFormat::R32Float; | |
| mOutputTexture = device.CreateTexture(&outputTextureDesc); | |
| wgpu::BufferDescriptor outputBufferDesc; | |
| outputBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; | |
| outputBufferDesc.size = sizeof(float); | |
| mOutputBuffer = device.CreateBuffer(&outputBufferDesc); | |
| } | |
| wgpu::RenderPipeline CreateSamplingRenderPipeline() { | |
| wgpu::ShaderModule vsModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( | |
| #version 450 | |
| void main() { | |
| gl_Position = vec4(0.f, 0.f, 0.f, 1.f); | |
| gl_PointSize = 1.0; | |
| } | |
| )"); | |
| wgpu::ShaderModule fsModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( | |
| #version 450 | |
| layout(set = 0, binding = 0) uniform sampler samp; | |
| layout(set = 0, binding = 1) uniform texture2D tex; | |
| layout(location = 0) out float samplerResult; | |
| void main() { | |
| samplerResult = texture(sampler2D(tex, samp), vec2(0.5, 0.5)).r; | |
| } | |
| )"); | |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); | |
| pipelineDescriptor.vertexStage.module = vsModule; | |
| pipelineDescriptor.cFragmentStage.module = fsModule; | |
| pipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList; | |
| pipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::R32Float; | |
| return device.CreateRenderPipeline(&pipelineDescriptor); | |
| } | |
| wgpu::ComputePipeline CreateSamplingComputePipeline() { | |
| wgpu::ShaderModule csModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"( | |
| #version 450 | |
| layout(set = 0, binding = 0) uniform sampler samp; | |
| layout(set = 0, binding = 1) uniform texture2D tex; | |
| layout(set = 0, binding = 2) writeonly buffer SamplerResult { | |
| float samplerResult; | |
| }; | |
| void main() { | |
| samplerResult = texture(sampler2D(tex, samp), vec2(0.5, 0.5)).r; | |
| } | |
| )"); | |
| wgpu::ComputePipelineDescriptor pipelineDescriptor; | |
| pipelineDescriptor.computeStage.module = csModule; | |
| pipelineDescriptor.computeStage.entryPoint = "main"; | |
| return device.CreateComputePipeline(&pipelineDescriptor); | |
| } | |
| wgpu::RenderPipeline CreateComparisonRenderPipeline() { | |
| wgpu::ShaderModule vsModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( | |
| #version 450 | |
| void main() { | |
| gl_Position = vec4(0.f, 0.f, 0.f, 1.f); | |
| gl_PointSize = 1.0; | |
| } | |
| )"); | |
| wgpu::ShaderModule fsModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( | |
| #version 450 | |
| layout(set = 0, binding = 0) uniform samplerShadow samp; | |
| layout(set = 0, binding = 1) uniform texture2D tex; | |
| layout(set = 0, binding = 2) uniform Uniforms { | |
| float compareRef; | |
| }; | |
| layout(location = 0) out float samplerResult; | |
| void main() { | |
| samplerResult = texture(sampler2DShadow(tex, samp), vec3(0.5, 0.5, compareRef)); | |
| } | |
| )"); | |
| // TODO(dawn:367): Cannot use GetBindGroupLayout for comparison samplers without shader | |
| // reflection data. | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ComparisonSampler}, | |
| {1, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture}, | |
| {2, wgpu::ShaderStage::Fragment, wgpu::BindingType::UniformBuffer}}); | |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); | |
| pipelineDescriptor.vertexStage.module = vsModule; | |
| pipelineDescriptor.cFragmentStage.module = fsModule; | |
| pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); | |
| pipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList; | |
| pipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::R32Float; | |
| return device.CreateRenderPipeline(&pipelineDescriptor); | |
| } | |
| wgpu::ComputePipeline CreateComparisonComputePipeline() { | |
| wgpu::ShaderModule csModule = | |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"( | |
| #version 450 | |
| layout(set = 0, binding = 0) uniform samplerShadow samp; | |
| layout(set = 0, binding = 1) uniform texture2D tex; | |
| layout(set = 0, binding = 2) uniform Uniforms { | |
| float compareRef; | |
| }; | |
| layout(set = 0, binding = 3) writeonly buffer SamplerResult { | |
| float samplerResult; | |
| }; | |
| void main() { | |
| samplerResult = texture(sampler2DShadow(tex, samp), vec3(0.5, 0.5, compareRef)); | |
| } | |
| )"); | |
| // TODO(dawn:367): Cannot use GetBindGroupLayout without shader reflection data. | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ComparisonSampler}, | |
| {1, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture}, | |
| {2, wgpu::ShaderStage::Compute, wgpu::BindingType::UniformBuffer}, | |
| {3, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}}); | |
| wgpu::ComputePipelineDescriptor pipelineDescriptor; | |
| pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); | |
| pipelineDescriptor.computeStage.module = csModule; | |
| pipelineDescriptor.computeStage.entryPoint = "main"; | |
| return device.CreateComputePipeline(&pipelineDescriptor); | |
| } | |
| void UpdateInputTexture(wgpu::CommandEncoder commandEncoder, float textureValue) { | |
| queue.WriteBuffer(mTextureUploadBuffer, 0, &textureValue, sizeof(float)); | |
| wgpu::BufferCopyView bufferCopyView = {}; | |
| bufferCopyView.buffer = mTextureUploadBuffer; | |
| bufferCopyView.offset = 0; | |
| bufferCopyView.bytesPerRow = kTextureBytesPerRowAlignment; | |
| wgpu::TextureCopyView textureCopyView; | |
| textureCopyView.texture = mInputTexture; | |
| textureCopyView.origin = {0, 0, 0}; | |
| wgpu::Extent3D copySize = {1, 1, 1}; | |
| commandEncoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©Size); | |
| } | |
| void DoSamplingTest(wgpu::RenderPipeline pipeline, std::vector<float> textureValues) { | |
| wgpu::SamplerDescriptor samplerDesc; | |
| wgpu::Sampler sampler = device.CreateSampler(&samplerDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, sampler}, | |
| {1, mInputTexture.CreateView()}, | |
| }); | |
| for (float textureValue : textureValues) { | |
| // Set the input depth texture to the provided texture value | |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); | |
| UpdateInputTexture(commandEncoder, textureValue); | |
| // Render into the output texture | |
| { | |
| utils::ComboRenderPassDescriptor passDescriptor({mOutputTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(1); | |
| pass.EndPass(); | |
| } | |
| wgpu::CommandBuffer commands = commandEncoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_FLOAT_EQ(textureValue, mOutputTexture, 0, 0); | |
| } | |
| } | |
| void DoSamplingTest(wgpu::ComputePipeline pipeline, std::vector<float> textureValues) { | |
| wgpu::SamplerDescriptor samplerDesc; | |
| wgpu::Sampler sampler = device.CreateSampler(&samplerDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, sampler}, | |
| {1, mInputTexture.CreateView()}, | |
| {2, mOutputBuffer} | |
| }); | |
| for (float textureValue : textureValues) { | |
| // Set the input depth texture to the provided texture value | |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); | |
| UpdateInputTexture(commandEncoder, textureValue); | |
| // Sample into the output buffer | |
| { | |
| wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Dispatch(1); | |
| pass.EndPass(); | |
| } | |
| wgpu::CommandBuffer commands = commandEncoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_U32_EQ(*reinterpret_cast<uint32_t*>(&textureValue), mOutputBuffer, 0); | |
| } | |
| } | |
| static bool CompareFunctionPasses(float compareRef, | |
| wgpu::CompareFunction compare, | |
| float textureValue) { | |
| switch (compare) { | |
| case wgpu::CompareFunction::Never: | |
| return false; | |
| case wgpu::CompareFunction::Less: | |
| return compareRef < textureValue; | |
| case wgpu::CompareFunction::LessEqual: | |
| return compareRef <= textureValue; | |
| case wgpu::CompareFunction::Greater: | |
| return compareRef > textureValue; | |
| case wgpu::CompareFunction::GreaterEqual: | |
| return compareRef >= textureValue; | |
| case wgpu::CompareFunction::Equal: | |
| return compareRef == textureValue; | |
| case wgpu::CompareFunction::NotEqual: | |
| return compareRef != textureValue; | |
| case wgpu::CompareFunction::Always: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void DoCompareRefTest(wgpu::RenderPipeline pipeline, | |
| float compareRef, | |
| wgpu::CompareFunction compare, | |
| std::vector<float> textureValues) { | |
| queue.WriteBuffer(mUniformBuffer, 0, &compareRef, sizeof(float)); | |
| wgpu::SamplerDescriptor samplerDesc; | |
| samplerDesc.compare = compare; | |
| wgpu::Sampler sampler = device.CreateSampler(&samplerDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, sampler}, | |
| {1, mInputTexture.CreateView()}, | |
| {2, mUniformBuffer}, | |
| }); | |
| for (float textureValue : textureValues) { | |
| // Set the input depth texture to the provided texture value | |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); | |
| UpdateInputTexture(commandEncoder, textureValue); | |
| // Render into the output texture | |
| { | |
| utils::ComboRenderPassDescriptor passDescriptor({mOutputTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(1); | |
| pass.EndPass(); | |
| } | |
| wgpu::CommandBuffer commands = commandEncoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_FLOAT_EQ( | |
| CompareFunctionPasses(compareRef, compare, textureValue) ? 1.0 : 0.0, | |
| mOutputTexture, 0, 0); | |
| } | |
| } | |
| void DoCompareRefTest(wgpu::ComputePipeline pipeline, | |
| float compareRef, | |
| wgpu::CompareFunction compare, | |
| std::vector<float> textureValues) { | |
| queue.WriteBuffer(mUniformBuffer, 0, &compareRef, sizeof(float)); | |
| wgpu::SamplerDescriptor samplerDesc; | |
| samplerDesc.compare = compare; | |
| wgpu::Sampler sampler = device.CreateSampler(&samplerDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, sampler}, | |
| {1, mInputTexture.CreateView()}, | |
| {2, mUniformBuffer}, | |
| {3, mOutputBuffer} | |
| }); | |
| for (float textureValue : textureValues) { | |
| // Set the input depth texture to the provided texture value | |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); | |
| UpdateInputTexture(commandEncoder, textureValue); | |
| // Sample into the output buffer | |
| { | |
| wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Dispatch(1); | |
| pass.EndPass(); | |
| } | |
| wgpu::CommandBuffer commands = commandEncoder.Finish(); | |
| queue.Submit(1, &commands); | |
| float float0 = 0.f; | |
| float float1 = 1.f; | |
| float* expected = | |
| CompareFunctionPasses(compareRef, compare, textureValue) ? &float1 : &float0; | |
| EXPECT_BUFFER_U32_EQ(*reinterpret_cast<uint32_t*>(expected), mOutputBuffer, 0); | |
| } | |
| } | |
| private: | |
| wgpu::Buffer mUniformBuffer; | |
| wgpu::Buffer mTextureUploadBuffer; | |
| wgpu::Texture mInputTexture; | |
| wgpu::Texture mOutputTexture; | |
| wgpu::Buffer mOutputBuffer; | |
| }; | |
| // Test that sampling a depth texture with a render pipeline works | |
| TEST_P(DepthSamplingTest, SampleRender) { | |
| // Test 0, between [0, 1], and 1. | |
| DoSamplingTest(CreateSamplingRenderPipeline(), kNormalizedTextureValues); | |
| } | |
| // Test that sampling a depth texture with a compute pipeline works | |
| TEST_P(DepthSamplingTest, SampleCompute) { | |
| // Test 0, between [0, 1], and 1. | |
| DoSamplingTest(CreateSamplingComputePipeline(), kNormalizedTextureValues); | |
| } | |
| // Test that sampling a depth texture with a render pipeline works, | |
| // when the texture contents are outside the 0-1 range. | |
| TEST_P(DepthSamplingTest, SampleNonNormalizedContentsRender) { | |
| // TODO(enga): Sampling depth textures is clamped. Unless we reinterpret | |
| // contents as R32F. | |
| DAWN_SKIP_TEST_IF(IsOpenGL()); | |
| // Test values not between [0, 1] | |
| DoSamplingTest(CreateSamplingRenderPipeline(), kNonNormalizedTextureValues); | |
| } | |
| // Test that sampling a depth texture with a render pipeline works, | |
| // when the texture contents are outside the 0-1 range. | |
| TEST_P(DepthSamplingTest, SampleNonNormalizedContentsCompute) { | |
| // TODO(enga): Sampling depth textures is clamped. Unless we reinterpret | |
| // contents as R32F. | |
| DAWN_SKIP_TEST_IF(IsOpenGL()); | |
| // Test values not between [0, 1] | |
| DoSamplingTest(CreateSamplingComputePipeline(), kNonNormalizedTextureValues); | |
| } | |
| // Test that sampling in a render pipeline with all of the compare functions works. | |
| TEST_P(DepthSamplingTest, CompareFunctionsRender) { | |
| wgpu::RenderPipeline pipeline = CreateComparisonRenderPipeline(); | |
| // Test a "normal" ref value between 0 and 1; as well as negative and > 1 refs. | |
| for (float compareRef : kCompareRefs) { | |
| // Test 0, below the ref, equal to, above the ref, and 1. | |
| for (wgpu::CompareFunction f : kCompareFunctions) { | |
| DoCompareRefTest(pipeline, compareRef, f, kNormalizedTextureValues); | |
| } | |
| } | |
| } | |
| // Test that sampling in a render pipeline with all of the compare functions works. | |
| TEST_P(DepthSamplingTest, CompareFunctionsCompute) { | |
| // Comparison is always 0 on Mac Intel when using compute. | |
| DAWN_SKIP_TEST_IF(IsMetal() && IsIntel()); | |
| wgpu::ComputePipeline pipeline = CreateComparisonComputePipeline(); | |
| // Test a "normal" ref value between 0 and 1; as well as negative and > 1 refs. | |
| for (float compareRef : kCompareRefs) { | |
| // Test 0, below the ref, equal to, above the ref, and 1. | |
| for (wgpu::CompareFunction f : kCompareFunctions) { | |
| DoCompareRefTest(pipeline, compareRef, f, kNormalizedTextureValues); | |
| } | |
| } | |
| } | |
| // Test that sampling in a render pipeline with all of the compare functions works, | |
| // when the texture contents are outside the 0-1 range. | |
| TEST_P(DepthSamplingTest, CompareFunctionsNonNormalizedContentsRender) { | |
| // TODO(enga): Sampling depth textures is clamped. Unless we reinterpret | |
| // contents as R32F. | |
| DAWN_SKIP_TEST_IF(IsOpenGL()); | |
| wgpu::RenderPipeline pipeline = CreateComparisonRenderPipeline(); | |
| // Test a "normal" ref value between 0 and 1; as well as negative and > 1 refs. | |
| for (float compareRef : kCompareRefs) { | |
| // Test negative, and above 1. | |
| for (wgpu::CompareFunction f : kCompareFunctions) { | |
| DoCompareRefTest(pipeline, compareRef, f, kNonNormalizedTextureValues); | |
| } | |
| } | |
| } | |
| // Test that sampling in a compute pipeline with all of the compare functions works, | |
| // when the texture contents are outside the 0-1 range. | |
| TEST_P(DepthSamplingTest, CompareFunctionsNonNormalizedContentsCompute) { | |
| // Comparison is always 0 on Mac Intel when using compute. | |
| DAWN_SKIP_TEST_IF(IsMetal() && IsIntel()); | |
| // TODO(enga): Sampling depth textures is clamped. Unless we reinterpret | |
| // contents as R32F. | |
| DAWN_SKIP_TEST_IF(IsOpenGL()); | |
| wgpu::ComputePipeline pipeline = CreateComparisonComputePipeline(); | |
| // Test a "normal" ref value between 0 and 1; as well as negative and > 1 refs. | |
| for (float compareRef : kCompareRefs) { | |
| // Test negative, and above 1. | |
| for (wgpu::CompareFunction f : kCompareFunctions) { | |
| DoCompareRefTest(pipeline, compareRef, f, kNormalizedTextureValues); | |
| } | |
| } | |
| } | |
| DAWN_INSTANTIATE_TEST(DepthSamplingTest, | |
| D3D12Backend(), | |
| MetalBackend(), | |
| OpenGLBackend(), | |
| VulkanBackend()); |