Add maxAnisotropy to GPUSamplerDescriptor
Adds some maxAnisotropy implementation.
Adds an end2end test, drawing a slanted plane with a texture of which each mipmap has a different color, with different maxAnisotropy values.
You can get an idea of what it does at https://jsfiddle.net/t64kpu81/85/
Needs further CTS.
Bug: dawn:568
Change-Id: I89ac56d8cf0fbb655358bf6effa016ddc1f8426f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/35143
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/dawn.json b/dawn.json
index a485484..f11d123 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1530,7 +1530,8 @@
{"name": "mipmap filter", "type": "filter mode", "default": "nearest"},
{"name": "lod min clamp", "type": "float", "default": "0.0f"},
{"name": "lod max clamp", "type": "float", "default": "1000.0f"},
- {"name": "compare", "type": "compare function", "default": "undefined"}
+ {"name": "compare", "type": "compare function", "default": "undefined"},
+ {"name": "max anisotropy", "type": "uint16_t", "default": "1"}
]
},
"sampler descriptor dummy anisotropic filtering": {
@@ -1925,15 +1926,18 @@
"void const *": {
"category": "native"
},
- "uint32_t": {
- "category": "native"
- },
"int32_t": {
"category": "native"
},
"size_t": {
"category": "native"
},
+ "uint16_t": {
+ "category": "native"
+ },
+ "uint32_t": {
+ "category": "native"
+ },
"uint64_t": {
"category": "native"
},
diff --git a/src/dawn_native/Sampler.cpp b/src/dawn_native/Sampler.cpp
index 11b398a..3a75cb1 100644
--- a/src/dawn_native/Sampler.cpp
+++ b/src/dawn_native/Sampler.cpp
@@ -20,6 +20,12 @@
#include <cmath>
+namespace {
+ uint16_t GetClampedMaxAnisotropy(uint16_t value) {
+ return value >= 1u ? value : 1u;
+ }
+} // anonymous namespace
+
namespace dawn_native {
MaybeError ValidateSamplerDescriptor(DeviceBase*, const SamplerDescriptor* descriptor) {
@@ -40,6 +46,16 @@
"Min lod clamp value cannot greater than max lod clamp value");
}
+ if (descriptor->maxAnisotropy > 1) {
+ if (descriptor->minFilter != wgpu::FilterMode::Linear ||
+ descriptor->magFilter != wgpu::FilterMode::Linear ||
+ descriptor->mipmapFilter != wgpu::FilterMode::Linear) {
+ return DAWN_VALIDATION_ERROR(
+ "min, mag, and mipmap filter should be linear when using anisotropic "
+ "filtering");
+ }
+ }
+
DAWN_TRY(ValidateFilterMode(descriptor->minFilter));
DAWN_TRY(ValidateFilterMode(descriptor->magFilter));
DAWN_TRY(ValidateFilterMode(descriptor->mipmapFilter));
@@ -62,7 +78,8 @@
mMipmapFilter(descriptor->mipmapFilter),
mLodMinClamp(descriptor->lodMinClamp),
mLodMaxClamp(descriptor->lodMaxClamp),
- mCompareFunction(descriptor->compare) {
+ mCompareFunction(descriptor->compare),
+ mMaxAnisotropy(GetClampedMaxAnisotropy(descriptor->maxAnisotropy)) {
}
SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@@ -87,7 +104,8 @@
size_t SamplerBase::ComputeContentHash() {
ObjectContentHasher recorder;
recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter,
- mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction);
+ mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction,
+ mMaxAnisotropy);
return recorder.GetContentHash();
}
@@ -105,7 +123,7 @@
a->mAddressModeW == b->mAddressModeW && a->mMagFilter == b->mMagFilter &&
a->mMinFilter == b->mMinFilter && a->mMipmapFilter == b->mMipmapFilter &&
a->mLodMinClamp == b->mLodMinClamp && a->mLodMaxClamp == b->mLodMaxClamp &&
- a->mCompareFunction == b->mCompareFunction;
+ a->mCompareFunction == b->mCompareFunction && a->mMaxAnisotropy == b->mMaxAnisotropy;
}
} // namespace dawn_native
diff --git a/src/dawn_native/Sampler.h b/src/dawn_native/Sampler.h
index 62ef4d5..2fd938c 100644
--- a/src/dawn_native/Sampler.h
+++ b/src/dawn_native/Sampler.h
@@ -42,6 +42,10 @@
bool operator()(const SamplerBase* a, const SamplerBase* b) const;
};
+ uint16_t GetMaxAnisotropy() const {
+ return mMaxAnisotropy;
+ }
+
private:
SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag);
@@ -55,6 +59,7 @@
float mLodMinClamp;
float mLodMaxClamp;
wgpu::CompareFunction mCompareFunction;
+ uint16_t mMaxAnisotropy;
};
} // namespace dawn_native
diff --git a/src/dawn_native/d3d12/SamplerD3D12.cpp b/src/dawn_native/d3d12/SamplerD3D12.cpp
index e453fc4..7d63e0d 100644
--- a/src/dawn_native/d3d12/SamplerD3D12.cpp
+++ b/src/dawn_native/d3d12/SamplerD3D12.cpp
@@ -69,13 +69,21 @@
? D3D12_FILTER_REDUCTION_TYPE_STANDARD
: D3D12_FILTER_REDUCTION_TYPE_COMPARISON;
- mSamplerDesc.Filter =
- D3D12_ENCODE_BASIC_FILTER(minFilter, magFilter, mipmapFilter, reduction);
+ // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_sampler_desc
+ mSamplerDesc.MaxAnisotropy = std::min<uint16_t>(GetMaxAnisotropy(), 16u);
+
+ if (mSamplerDesc.MaxAnisotropy > 1) {
+ mSamplerDesc.Filter = D3D12_ENCODE_ANISOTROPIC_FILTER(reduction);
+ } else {
+ mSamplerDesc.Filter =
+ D3D12_ENCODE_BASIC_FILTER(minFilter, magFilter, mipmapFilter, reduction);
+ }
+
mSamplerDesc.AddressU = AddressMode(descriptor->addressModeU);
mSamplerDesc.AddressV = AddressMode(descriptor->addressModeV);
mSamplerDesc.AddressW = AddressMode(descriptor->addressModeW);
mSamplerDesc.MipLODBias = 0.f;
- mSamplerDesc.MaxAnisotropy = 1;
+
if (descriptor->compare != wgpu::CompareFunction::Undefined) {
mSamplerDesc.ComparisonFunc = ToD3D12ComparisonFunc(descriptor->compare);
} else {
diff --git a/src/dawn_native/metal/SamplerMTL.mm b/src/dawn_native/metal/SamplerMTL.mm
index 0559605..34a5b1f 100644
--- a/src/dawn_native/metal/SamplerMTL.mm
+++ b/src/dawn_native/metal/SamplerMTL.mm
@@ -75,6 +75,8 @@
mtlDesc.lodMinClamp = descriptor->lodMinClamp;
mtlDesc.lodMaxClamp = descriptor->lodMaxClamp;
+ // https://developer.apple.com/documentation/metal/mtlsamplerdescriptor/1516164-maxanisotropy
+ mtlDesc.maxAnisotropy = std::min<uint16_t>(GetMaxAnisotropy(), 16u);
if (descriptor->compare != wgpu::CompareFunction::Undefined) {
// Sampler compare is unsupported before A9, which we validate in
diff --git a/src/dawn_native/opengl/SamplerGL.cpp b/src/dawn_native/opengl/SamplerGL.cpp
index 6f9235d..32aa56f 100644
--- a/src/dawn_native/opengl/SamplerGL.cpp
+++ b/src/dawn_native/opengl/SamplerGL.cpp
@@ -82,7 +82,8 @@
void Sampler::SetupGLSampler(GLuint sampler,
const SamplerDescriptor* descriptor,
bool forceNearest) {
- const OpenGLFunctions& gl = ToBackend(GetDevice())->gl;
+ Device* device = ToBackend(GetDevice());
+ const OpenGLFunctions& gl = device->gl;
if (forceNearest) {
gl.SamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -105,6 +106,11 @@
gl.SamplerParameteri(sampler, GL_TEXTURE_COMPARE_FUNC,
ToOpenGLCompareFunction(descriptor->compare));
}
+
+ if (gl.IsAtLeastGL(4, 6) ||
+ gl.IsGLExtensionSupported("GL_EXT_texture_filter_anisotropic")) {
+ gl.SamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY, GetMaxAnisotropy());
+ }
}
GLuint Sampler::GetFilteringHandle() const {
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 2fda4ed..398cc57 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -316,6 +316,10 @@
mComputeSubgroupSize = FindComputeSubgroupSize();
}
+ if (mDeviceInfo.features.samplerAnisotropy == VK_TRUE) {
+ usedKnobs.features.samplerAnisotropy = VK_TRUE;
+ }
+
if (IsExtensionEnabled(Extension::TextureCompressionBC)) {
ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionBC ==
VK_TRUE);
diff --git a/src/dawn_native/vulkan/SamplerVk.cpp b/src/dawn_native/vulkan/SamplerVk.cpp
index 9429830..033f7b7 100644
--- a/src/dawn_native/vulkan/SamplerVk.cpp
+++ b/src/dawn_native/vulkan/SamplerVk.cpp
@@ -71,8 +71,6 @@
createInfo.addressModeV = VulkanSamplerAddressMode(descriptor->addressModeV);
createInfo.addressModeW = VulkanSamplerAddressMode(descriptor->addressModeW);
createInfo.mipLodBias = 0.0f;
- createInfo.anisotropyEnable = VK_FALSE;
- createInfo.maxAnisotropy = 1.0f;
if (descriptor->compare != wgpu::CompareFunction::Undefined) {
createInfo.compareOp = ToVulkanCompareOp(descriptor->compare);
createInfo.compareEnable = VK_TRUE;
@@ -86,6 +84,18 @@
createInfo.unnormalizedCoordinates = VK_FALSE;
Device* device = ToBackend(GetDevice());
+ uint16_t maxAnisotropy = GetMaxAnisotropy();
+ if (device->GetDeviceInfo().features.samplerAnisotropy == VK_TRUE && maxAnisotropy > 1) {
+ createInfo.anisotropyEnable = VK_TRUE;
+ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSamplerCreateInfo.html
+ createInfo.maxAnisotropy =
+ std::min(static_cast<float>(maxAnisotropy),
+ device->GetDeviceInfo().properties.limits.maxSamplerAnisotropy);
+ } else {
+ createInfo.anisotropyEnable = VK_FALSE;
+ createInfo.maxAnisotropy = 1;
+ }
+
return CheckVkSuccess(
device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
"CreateSampler");
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 342aa80..258d26e 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -310,6 +310,7 @@
"end2end/RenderBundleTests.cpp",
"end2end/RenderPassLoadOpTests.cpp",
"end2end/RenderPassTests.cpp",
+ "end2end/SamplerFilterAnisotropicTests.cpp",
"end2end/SamplerTests.cpp",
"end2end/ScissorTests.cpp",
"end2end/ShaderFloat16Tests.cpp",
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index e47b054..446ebd0 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -85,6 +85,21 @@
DawnTestEnvironment* gTestEnv = nullptr;
+ template <typename T>
+ void printBuffer(testing::AssertionResult& result, const T* buffer, const size_t count) {
+ static constexpr unsigned int kBytes = sizeof(T);
+
+ for (size_t index = 0; index < count; ++index) {
+ auto byteView = reinterpret_cast<const uint8_t*>(buffer + index);
+ for (unsigned int b = 0; b < kBytes; ++b) {
+ char buf[4];
+ sprintf(buf, "%02X ", byteView[b]);
+ result << buf;
+ }
+ }
+ result << std::endl;
+ }
+
} // anonymous namespace
const RGBA8 RGBA8::kZero = RGBA8(0, 0, 0, 0);
@@ -1153,6 +1168,14 @@
return !(*this == other);
}
+bool RGBA8::operator<=(const RGBA8& other) const {
+ return (r <= other.r && g <= other.g && b <= other.b && a <= other.a);
+}
+
+bool RGBA8::operator>=(const RGBA8& other) const {
+ return (r >= other.r && g >= other.g && b >= other.b && a >= other.a);
+}
+
std::ostream& operator<<(std::ostream& stream, const RGBA8& color) {
return stream << "RGBA8(" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g)
<< ", " << static_cast<int>(color.b) << ", " << static_cast<int>(color.a) << ")";
@@ -1191,26 +1214,12 @@
<< mExpected[i] << ", actual " << actual[i]
<< std::endl;
- auto printBuffer = [&](const T* buffer) {
- static constexpr unsigned int kBytes = sizeof(T);
-
- for (size_t index = 0; index < mExpected.size(); ++index) {
- auto byteView = reinterpret_cast<const uint8_t*>(buffer + index);
- for (unsigned int b = 0; b < kBytes; ++b) {
- char buf[4];
- sprintf(buf, "%02X ", byteView[b]);
- result << buf;
- }
- }
- result << std::endl;
- };
-
if (mExpected.size() <= 1024) {
result << "Expected:" << std::endl;
- printBuffer(mExpected.data());
+ printBuffer(result, mExpected.data(), mExpected.size());
result << "Actual:" << std::endl;
- printBuffer(actual);
+ printBuffer(result, actual, mExpected.size());
}
return result;
@@ -1226,4 +1235,59 @@
template class ExpectEq<uint64_t>;
template class ExpectEq<RGBA8>;
template class ExpectEq<float>;
+
+ template <typename T>
+ ExpectBetweenColors<T>::ExpectBetweenColors(T value0, T value1) {
+ T l, h;
+ l.r = std::min(value0.r, value1.r);
+ l.g = std::min(value0.g, value1.g);
+ l.b = std::min(value0.b, value1.b);
+ l.a = std::min(value0.a, value1.a);
+
+ h.r = std::max(value0.r, value1.r);
+ h.g = std::max(value0.g, value1.g);
+ h.b = std::max(value0.b, value1.b);
+ h.a = std::max(value0.a, value1.a);
+
+ mLowerColorChannels.push_back(l);
+ mHigherColorChannels.push_back(h);
+
+ mValues0.push_back(value0);
+ mValues1.push_back(value1);
+ }
+
+ template <typename T>
+ testing::AssertionResult ExpectBetweenColors<T>::Check(const void* data, size_t size) {
+ DAWN_ASSERT(size == sizeof(T) * mLowerColorChannels.size());
+ DAWN_ASSERT(mHigherColorChannels.size() == mLowerColorChannels.size());
+ DAWN_ASSERT(mValues0.size() == mValues1.size());
+ DAWN_ASSERT(mValues0.size() == mLowerColorChannels.size());
+
+ const T* actual = static_cast<const T*>(data);
+
+ for (size_t i = 0; i < mLowerColorChannels.size(); ++i) {
+ if (!(actual[i] >= mLowerColorChannels[i] && actual[i] <= mHigherColorChannels[i])) {
+ testing::AssertionResult result = testing::AssertionFailure()
+ << "Expected data[" << i << "] to be between "
+ << mValues0[i] << " and " << mValues1[i]
+ << ", actual " << actual[i] << std::endl;
+
+ if (mLowerColorChannels.size() <= 1024) {
+ result << "Expected between:" << std::endl;
+ printBuffer(result, mValues0.data(), mLowerColorChannels.size());
+ result << "and" << std::endl;
+ printBuffer(result, mValues1.data(), mLowerColorChannels.size());
+
+ result << "Actual:" << std::endl;
+ printBuffer(result, actual, mLowerColorChannels.size());
+ }
+
+ return result;
+ }
+ }
+
+ return testing::AssertionSuccess();
+ }
+
+ template class ExpectBetweenColors<RGBA8>;
} // namespace detail
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index e175132..aefafeb 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -73,6 +73,9 @@
#define EXPECT_TEXTURE_FLOAT_EQ(expected, texture, x, y, width, height, level, slice) \
AddTextureExpectation(__FILE__, __LINE__, expected, texture, x, y, width, height, level, slice)
+#define EXPECT_PIXEL_RGBA8_BETWEEN(color0, color1, texture, x, y) \
+ AddTextureBetweenColorsExpectation(__FILE__, __LINE__, color0, color1, texture, x, y)
+
// TODO(enga): Migrate other texure expectation helpers to this common one.
#define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__)
@@ -95,6 +98,8 @@
}
bool operator==(const RGBA8& other) const;
bool operator!=(const RGBA8& other) const;
+ bool operator<=(const RGBA8& other) const;
+ bool operator>=(const RGBA8& other) const;
uint8_t r, g, b, a;
@@ -170,6 +175,8 @@
template <typename T>
class ExpectEq;
+ template <typename T>
+ class ExpectBetweenColors;
} // namespace detail
namespace dawn_wire {
@@ -331,6 +338,24 @@
x, y, 1, 1, level, slice, aspect, sizeof(T), bytesPerRow);
}
+ template <typename T>
+ std::ostringstream& AddTextureBetweenColorsExpectation(
+ const char* file,
+ int line,
+ const T& color0,
+ const T& color1,
+ const wgpu::Texture& texture,
+ uint32_t x,
+ uint32_t y,
+ uint32_t level = 0,
+ uint32_t slice = 0,
+ wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
+ uint32_t bytesPerRow = 0) {
+ return AddTextureExpectationImpl(
+ file, line, new detail::ExpectBetweenColors<T>(color0, color1), texture, x, y, 1, 1,
+ level, slice, aspect, sizeof(T), bytesPerRow);
+ }
+
void WaitABit();
void FlushWire();
void WaitForAllOperations();
@@ -518,6 +543,26 @@
extern template class ExpectEq<uint64_t>;
extern template class ExpectEq<RGBA8>;
extern template class ExpectEq<float>;
+
+ template <typename T>
+ class ExpectBetweenColors : public Expectation {
+ public:
+ // Inclusive for now
+ ExpectBetweenColors(T value0, T value1);
+ testing::AssertionResult Check(const void* data, size_t size) override;
+
+ private:
+ std::vector<T> mLowerColorChannels;
+ std::vector<T> mHigherColorChannels;
+
+ // used for printing error
+ std::vector<T> mValues0;
+ std::vector<T> mValues1;
+ };
+ // A color is considered between color0 and color1 when all channel values are within range of
+ // each counterparts. It doesn't matter which value is higher or lower. Essentially color =
+ // lerp(color0, color1, t) where t is [0,1]. But I don't want to be too strict here.
+ extern template class ExpectBetweenColors<RGBA8>;
} // namespace detail
#endif // TESTS_DAWNTEST_H_
diff --git a/src/tests/end2end/SamplerFilterAnisotropicTests.cpp b/src/tests/end2end/SamplerFilterAnisotropicTests.cpp
new file mode 100644
index 0000000..cd96b97
--- /dev/null
+++ b/src/tests/end2end/SamplerFilterAnisotropicTests.cpp
@@ -0,0 +1,286 @@
+// 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::CreateShaderModuleFromWGSL(device, R"(
+ [[block]] struct Uniforms {
+ [[offset(0)]] matrix : mat4x4<f32>;
+ };
+
+ [[location(0)]] var<in> position : vec4<f32>;
+ [[location(1)]] var<in> uv : vec2<f32>;
+
+ [[set(0), binding(2)]] var<uniform> uniforms : Uniforms;
+
+ [[builtin(position)]] var<out> Position : vec4<f32>;
+ [[location(0)]] var<out> fragUV : vec2<f32>;
+
+ [[stage(vertex)]] fn main() -> void {
+ fragUV = uv;
+ Position = uniforms.matrix * position;
+ }
+ )");
+ wgpu::ShaderModule fsModule = utils::CreateShaderModuleFromWGSL(device, R"(
+ [[set(0), binding(0)]] var<uniform_constant> sampler0 : sampler;
+ [[set(0), binding(1)]] var<uniform_constant> texture0 : texture_2d<f32>;
+
+ [[builtin(frag_coord)]] var<in> FragCoord : vec4<f32>;
+
+ [[location(0)]] var<in> fragUV: vec2<f32>;
+
+ [[location(0)]] var<out> fragColor : vec4<f32>;
+
+ [[stage(fragment)]] fn main() -> void {
+ fragColor = textureSample(texture0, sampler0, fragUV);
+ })");
+
+ utils::ComboVertexStateDescriptor vertexState;
+ vertexState.cVertexBuffers[0].attributeCount = 2;
+ vertexState.cAttributes[0].format = wgpu::VertexFormat::Float4;
+ vertexState.cAttributes[1].shaderLocation = 1;
+ vertexState.cAttributes[1].offset = 4 * sizeof(float);
+ vertexState.cAttributes[1].format = wgpu::VertexFormat::Float2;
+ vertexState.vertexBufferCount = 1;
+ vertexState.cVertexBuffers[0].arrayStride = 6 * sizeof(float);
+
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
+ pipelineDescriptor.vertexStage.module = vsModule;
+ pipelineDescriptor.cFragmentStage.module = fsModule;
+ pipelineDescriptor.vertexState = &vertexState;
+ pipelineDescriptor.cColorStates[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;
+ const uint32_t textureHeightLevel0 = 1 << mipLevelCount;
+
+ wgpu::TextureDescriptor descriptor;
+ descriptor.dimension = wgpu::TextureDimension::e2D;
+ descriptor.size.width = textureWidthLevel0;
+ descriptor.size.height = textureHeightLevel0;
+ descriptor.size.depth = 1;
+ descriptor.sampleCount = 1;
+ descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+ descriptor.mipLevelCount = mipLevelCount;
+ descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+ 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::BufferCopyView bufferCopyView =
+ utils::CreateBufferCopyView(stagingBuffer, 0, kTextureBytesPerRowAlignment);
+ wgpu::TextureCopyView textureCopyView =
+ utils::CreateTextureCopyView(texture, level, {0, 0, 0});
+ wgpu::Extent3D copySize = {texWidth, texHeight, 1};
+ encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©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 Mac metal backend Intel GPU
+ // maxAnisotropy: 1
+ // 0 - 00 00 00
+ // 1 - 00 00 ff
+ // 2 - 00 00 ff
+ // 3 - 00 00 ff
+ // 4 - 00 00 ff
+ // 5 - 00 00 ff
+ // 6 - 00 ef 10
+ // 7 - 00 ef 10
+ // 8 - a7 58 00
+ // 9 - a7 58 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 00 ff
+ // 3 - 00 00 ff
+ // 4 - 00 f7 08
+ // 5 - 00 f7 08
+ // 6 - ed 12 00
+ // 7 - ed 12 10
+ // 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 - 00 ad 52
+ // 3 - 00 ad 52
+ // 4 - 81 7e 00
+ // 5 - 81 7e 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, 4);
+ EXPECT_PIXEL_RGBA8_EQ(colors[0], mRenderPass.color, 8, 7);
+ } else if (maxAnisotropy == 2) {
+ EXPECT_PIXEL_RGBA8_BETWEEN(colors[1], colors[2], mRenderPass.color, 8, 4);
+ EXPECT_PIXEL_RGBA8_BETWEEN(colors[0], colors[1], mRenderPass.color, 8, 7);
+ } else if (maxAnisotropy <= 1) {
+ EXPECT_PIXEL_RGBA8_EQ(colors[2], mRenderPass.color, 8, 4);
+ EXPECT_PIXEL_RGBA8_BETWEEN(colors[1], colors[2], mRenderPass.color, 8, 7);
+ }
+ }
+
+ utils::BasicRenderPass mRenderPass;
+ wgpu::BindGroupLayout mBindGroupLayout;
+ wgpu::RenderPipeline mPipeline;
+ wgpu::TextureView mTextureView;
+};
+
+TEST_P(SamplerFilterAnisotropicTest, SlantedPlaneMipmap) {
+ DAWN_SKIP_TEST_IF(IsOpenGL());
+ const uint16_t maxAnisotropyLists[] = {1, 2, 16, 128};
+ for (uint16_t t : maxAnisotropyLists) {
+ TestFilterAnisotropic(t);
+ }
+}
+
+DAWN_INSTANTIATE_TEST(SamplerFilterAnisotropicTest,
+ D3D12Backend(),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend());
diff --git a/src/tests/unittests/validation/SamplerValidationTests.cpp b/src/tests/unittests/validation/SamplerValidationTests.cpp
index 96f9187..60e2564 100644
--- a/src/tests/unittests/validation/SamplerValidationTests.cpp
+++ b/src/tests/unittests/validation/SamplerValidationTests.cpp
@@ -51,4 +51,75 @@
}
}
+ TEST_F(SamplerValidationTest, InvalidFilterAnisotropic) {
+ wgpu::SamplerDescriptor kValidAnisoSamplerDesc = {};
+ kValidAnisoSamplerDesc.maxAnisotropy = 2;
+ kValidAnisoSamplerDesc.minFilter = wgpu::FilterMode::Linear;
+ kValidAnisoSamplerDesc.magFilter = wgpu::FilterMode::Linear;
+ kValidAnisoSamplerDesc.mipmapFilter = wgpu::FilterMode::Linear;
+ {
+ // when maxAnisotropy > 1, min, mag, mipmap filter should be linear
+ device.CreateSampler(&kValidAnisoSamplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.minFilter = wgpu::FilterMode::Nearest;
+ samplerDesc.magFilter = wgpu::FilterMode::Nearest;
+ samplerDesc.mipmapFilter = wgpu::FilterMode::Nearest;
+ ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.minFilter = wgpu::FilterMode::Nearest;
+ ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.magFilter = wgpu::FilterMode::Nearest;
+ ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.mipmapFilter = wgpu::FilterMode::Nearest;
+ ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
+ }
+ }
+
+ TEST_F(SamplerValidationTest, ValidFilterAnisotropic) {
+ wgpu::SamplerDescriptor kValidAnisoSamplerDesc = {};
+ kValidAnisoSamplerDesc.maxAnisotropy = 2;
+ kValidAnisoSamplerDesc.minFilter = wgpu::FilterMode::Linear;
+ kValidAnisoSamplerDesc.magFilter = wgpu::FilterMode::Linear;
+ kValidAnisoSamplerDesc.mipmapFilter = wgpu::FilterMode::Linear;
+ {
+ wgpu::SamplerDescriptor samplerDesc = {};
+ device.CreateSampler(&samplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.maxAnisotropy = 16;
+ device.CreateSampler(&samplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.maxAnisotropy = 32;
+ device.CreateSampler(&samplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.maxAnisotropy = 0x7FFF;
+ device.CreateSampler(&samplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.maxAnisotropy = 0x8000;
+ device.CreateSampler(&samplerDesc);
+ }
+ {
+ wgpu::SamplerDescriptor samplerDesc = kValidAnisoSamplerDesc;
+ samplerDesc.maxAnisotropy = 0xFFFF;
+ device.CreateSampler(&samplerDesc);
+ }
+ }
+
} // anonymous namespace