| // 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 <cmath> | |
| #include "tests/DawnTest.h" | |
| #include "common/Assert.h" | |
| #include "common/Constants.h" | |
| #include "utils/ComboRenderPipelineDescriptor.h" | |
| #include "utils/WGPUHelpers.h" | |
| constexpr static unsigned int kRTSize = 16; | |
| namespace { | |
| // MipLevel colors, ordering from base level to high level | |
| // each mipmap of the texture is having a different color | |
| // so we can check if the sampler anisotropic filtering is fetching | |
| // from the correct miplevel | |
| const std::array<RGBA8, 3> colors = {RGBA8::kRed, RGBA8::kGreen, RGBA8::kBlue}; | |
| } // namespace | |
| class SamplerFilterAnisotropicTest : public DawnTest { | |
| protected: | |
| void SetUp() override { | |
| DawnTest::SetUp(); | |
| mRenderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); | |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( | |
| struct Uniforms { | |
| matrix : mat4x4<f32>; | |
| }; | |
| struct VertexIn { | |
| [[location(0)]] position : vec4<f32>; | |
| [[location(1)]] uv : vec2<f32>; | |
| }; | |
| [[group(0), binding(2)]] var<uniform> uniforms : Uniforms; | |
| struct VertexOut { | |
| [[location(0)]] uv : vec2<f32>; | |
| [[builtin(position)]] position : vec4<f32>; | |
| }; | |
| [[stage(vertex)]] | |
| fn main(input : VertexIn) -> VertexOut { | |
| var output : VertexOut; | |
| output.uv = input.uv; | |
| output.position = uniforms.matrix * input.position; | |
| return output; | |
| } | |
| )"); | |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var sampler0 : sampler; | |
| [[group(0), binding(1)]] var texture0 : texture_2d<f32>; | |
| struct FragmentIn { | |
| [[location(0)]] uv: vec2<f32>; | |
| [[builtin(position)]] fragCoord : vec4<f32>; | |
| }; | |
| [[stage(fragment)]] | |
| fn main(input : FragmentIn) -> [[location(0)]] vec4<f32> { | |
| return textureSample(texture0, sampler0, input.uv); | |
| })"); | |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor; | |
| pipelineDescriptor.vertex.module = vsModule; | |
| pipelineDescriptor.cFragment.module = fsModule; | |
| pipelineDescriptor.cBuffers[0].attributeCount = 2; | |
| pipelineDescriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4; | |
| pipelineDescriptor.cAttributes[1].shaderLocation = 1; | |
| pipelineDescriptor.cAttributes[1].offset = 4 * sizeof(float); | |
| pipelineDescriptor.cAttributes[1].format = wgpu::VertexFormat::Float32x2; | |
| pipelineDescriptor.vertex.bufferCount = 1; | |
| pipelineDescriptor.cBuffers[0].arrayStride = 6 * sizeof(float); | |
| pipelineDescriptor.cTargets[0].format = mRenderPass.colorFormat; | |
| mPipeline = device.CreateRenderPipeline(&pipelineDescriptor); | |
| mBindGroupLayout = mPipeline.GetBindGroupLayout(0); | |
| InitTexture(); | |
| } | |
| void InitTexture() { | |
| const uint32_t mipLevelCount = colors.size(); | |
| const uint32_t textureWidthLevel0 = 1 << (mipLevelCount - 1); | |
| const uint32_t textureHeightLevel0 = 1 << (mipLevelCount - 1); | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.size.width = textureWidthLevel0; | |
| descriptor.size.height = textureHeightLevel0; | |
| descriptor.size.depthOrArrayLayers = 1; | |
| descriptor.sampleCount = 1; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.mipLevelCount = mipLevelCount; | |
| descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; | |
| wgpu::Texture texture = device.CreateTexture(&descriptor); | |
| const uint32_t rowPixels = kTextureBytesPerRowAlignment / sizeof(RGBA8); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| // Populate each mip level with a different color | |
| for (uint32_t level = 0; level < mipLevelCount; ++level) { | |
| const uint32_t texWidth = textureWidthLevel0 >> level; | |
| const uint32_t texHeight = textureHeightLevel0 >> level; | |
| const RGBA8 color = colors[level]; | |
| std::vector<RGBA8> data(rowPixels * texHeight, color); | |
| wgpu::Buffer stagingBuffer = utils::CreateBufferFromData( | |
| device, data.data(), data.size() * sizeof(RGBA8), wgpu::BufferUsage::CopySrc); | |
| wgpu::ImageCopyBuffer imageCopyBuffer = | |
| utils::CreateImageCopyBuffer(stagingBuffer, 0, kTextureBytesPerRowAlignment); | |
| wgpu::ImageCopyTexture imageCopyTexture = | |
| utils::CreateImageCopyTexture(texture, level, {0, 0, 0}); | |
| wgpu::Extent3D copySize = {texWidth, texHeight, 1}; | |
| encoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, ©Size); | |
| } | |
| wgpu::CommandBuffer copy = encoder.Finish(); | |
| queue.Submit(1, ©); | |
| mTextureView = texture.CreateView(); | |
| } | |
| // void TestFilterAnisotropic(const FilterAnisotropicTestCase& testCase) { | |
| void TestFilterAnisotropic(const uint16_t maxAnisotropy) { | |
| wgpu::Sampler sampler; | |
| { | |
| wgpu::SamplerDescriptor descriptor = {}; | |
| descriptor.minFilter = wgpu::FilterMode::Linear; | |
| descriptor.magFilter = wgpu::FilterMode::Linear; | |
| descriptor.mipmapFilter = wgpu::FilterMode::Linear; | |
| descriptor.maxAnisotropy = maxAnisotropy; | |
| sampler = device.CreateSampler(&descriptor); | |
| } | |
| // The transform matrix gives us a slanted plane | |
| // Tweaking happens at: https://jsfiddle.net/t8k7c95o/5/ | |
| // You can get an idea of what the test looks like at the url rendered by webgl | |
| std::array<float, 16> transform = {-1.7320507764816284, | |
| 1.8322050568049563e-16, | |
| -6.176817699518044e-17, | |
| -6.170640314703498e-17, | |
| -2.1211504944260596e-16, | |
| -1.496108889579773, | |
| 0.5043753981590271, | |
| 0.5038710236549377, | |
| 0, | |
| -43.63650894165039, | |
| -43.232173919677734, | |
| -43.18894577026367, | |
| 0, | |
| 21.693578720092773, | |
| 21.789791107177734, | |
| 21.86800193786621}; | |
| wgpu::Buffer transformBuffer = utils::CreateBufferFromData( | |
| device, transform.data(), sizeof(transform), wgpu::BufferUsage::Uniform); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, mBindGroupLayout, | |
| {{0, sampler}, {1, mTextureView}, {2, transformBuffer, 0, sizeof(transform)}}); | |
| // The plane is scaled on z axis in the transform matrix | |
| // so uv here is also scaled | |
| // vertex attribute layout: | |
| // position : vec4, uv : vec2 | |
| const float vertexData[] = { | |
| -0.5, 0.5, -0.5, 1, 0, 0, 0.5, 0.5, -0.5, 1, 1, 0, -0.5, 0.5, 0.5, 1, 0, 50, | |
| -0.5, 0.5, 0.5, 1, 0, 50, 0.5, 0.5, -0.5, 1, 1, 0, 0.5, 0.5, 0.5, 1, 1, 50, | |
| }; | |
| wgpu::Buffer vertexBuffer = utils::CreateBufferFromData( | |
| device, vertexData, sizeof(vertexData), wgpu::BufferUsage::Vertex); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| { | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&mRenderPass.renderPassInfo); | |
| pass.SetPipeline(mPipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetVertexBuffer(0, vertexBuffer); | |
| pass.Draw(6); | |
| pass.EndPass(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| // https://jsfiddle.net/t8k7c95o/5/ | |
| // (x, y) -> (8, [0,15)) full readpixels result on Win10 Nvidia D3D12 GPU | |
| // maxAnisotropy: 1 | |
| // 0 - 00 00 00 | |
| // 1 - 00 00 ff | |
| // 2 - 00 00 ff | |
| // 3 - 00 00 ff | |
| // 4 - 00 f9 06 | |
| // 5 - 00 f9 06 | |
| // 6 - f2 0d 00 | |
| // 7 - f2 0d 00 | |
| // 8 - ff 00 00 | |
| // 9 - ff 00 00 | |
| // 10 - ff 00 00 | |
| // 11 - ff 00 00 | |
| // 12 - ff 00 00 | |
| // 13 - ff 00 00 | |
| // 14 - ff 00 00 | |
| // 15 - ff 00 00 | |
| // maxAnisotropy: 2 | |
| // 0 - 00 00 00 | |
| // 1 - 00 00 ff | |
| // 2 - 00 7e 81 | |
| // 3 - 00 7e 81 | |
| // 4 - ff 00 00 | |
| // 5 - ff 00 00 | |
| // 6 - ff 00 00 | |
| // 7 - ff 00 00 | |
| // 8 - ff 00 00 | |
| // 9 - ff 00 00 | |
| // 10 - ff 00 00 | |
| // 11 - ff 00 00 | |
| // 12 - ff 00 00 | |
| // 13 - ff 00 00 | |
| // 14 - ff 00 00 | |
| // 15 - ff 00 00 | |
| // maxAnisotropy: 16 | |
| // 0 - 00 00 00 | |
| // 1 - 00 00 ff | |
| // 2 - dd 22 00 | |
| // 3 - dd 22 00 | |
| // 4 - ff 00 00 | |
| // 5 - ff 00 00 | |
| // 6 - ff 00 00 | |
| // 7 - ff 00 00 | |
| // 8 - ff 00 00 | |
| // 9 - ff 00 00 | |
| // 10 - ff 00 00 | |
| // 11 - ff 00 00 | |
| // 12 - ff 00 00 | |
| // 13 - ff 00 00 | |
| // 14 - ff 00 00 | |
| // 15 - ff 00 00 | |
| if (maxAnisotropy >= 16) { | |
| EXPECT_PIXEL_RGBA8_BETWEEN(colors[0], colors[1], mRenderPass.color, 8, 2); | |
| EXPECT_PIXEL_RGBA8_EQ(colors[0], mRenderPass.color, 8, 6); | |
| } else if (maxAnisotropy == 2) { | |
| EXPECT_PIXEL_RGBA8_BETWEEN(colors[1], colors[2], mRenderPass.color, 8, 2); | |
| EXPECT_PIXEL_RGBA8_EQ(colors[0], mRenderPass.color, 8, 6); | |
| } else if (maxAnisotropy <= 1) { | |
| EXPECT_PIXEL_RGBA8_EQ(colors[2], mRenderPass.color, 8, 2); | |
| EXPECT_PIXEL_RGBA8_BETWEEN(colors[0], colors[1], mRenderPass.color, 8, 6); | |
| } | |
| } | |
| utils::BasicRenderPass mRenderPass; | |
| wgpu::BindGroupLayout mBindGroupLayout; | |
| wgpu::RenderPipeline mPipeline; | |
| wgpu::TextureView mTextureView; | |
| }; | |
| TEST_P(SamplerFilterAnisotropicTest, SlantedPlaneMipmap) { | |
| // TODO(crbug.com/dawn/740): Test output is wrong with D3D12 + WARP. | |
| DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP()); | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); | |
| const uint16_t maxAnisotropyLists[] = {1, 2, 16, 128}; | |
| for (uint16_t t : maxAnisotropyLists) { | |
| TestFilterAnisotropic(t); | |
| } | |
| } | |
| DAWN_INSTANTIATE_TEST(SamplerFilterAnisotropicTest, | |
| D3D12Backend(), | |
| MetalBackend(), | |
| OpenGLBackend(), | |
| OpenGLESBackend(), | |
| VulkanBackend()); |