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>
This commit is contained in:
shrekshao 2020-12-24 03:11:17 +00:00 committed by Commit Bot service account
parent c08276644f
commit f8c5e4ab74
13 changed files with 553 additions and 29 deletions

View File

@ -1530,7 +1530,8 @@
{"name": "mipmap filter", "type": "filter mode", "default": "nearest"}, {"name": "mipmap filter", "type": "filter mode", "default": "nearest"},
{"name": "lod min clamp", "type": "float", "default": "0.0f"}, {"name": "lod min clamp", "type": "float", "default": "0.0f"},
{"name": "lod max clamp", "type": "float", "default": "1000.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": { "sampler descriptor dummy anisotropic filtering": {
@ -1925,15 +1926,18 @@
"void const *": { "void const *": {
"category": "native" "category": "native"
}, },
"uint32_t": {
"category": "native"
},
"int32_t": { "int32_t": {
"category": "native" "category": "native"
}, },
"size_t": { "size_t": {
"category": "native" "category": "native"
}, },
"uint16_t": {
"category": "native"
},
"uint32_t": {
"category": "native"
},
"uint64_t": { "uint64_t": {
"category": "native" "category": "native"
}, },

View File

@ -20,6 +20,12 @@
#include <cmath> #include <cmath>
namespace {
uint16_t GetClampedMaxAnisotropy(uint16_t value) {
return value >= 1u ? value : 1u;
}
} // anonymous namespace
namespace dawn_native { namespace dawn_native {
MaybeError ValidateSamplerDescriptor(DeviceBase*, const SamplerDescriptor* descriptor) { MaybeError ValidateSamplerDescriptor(DeviceBase*, const SamplerDescriptor* descriptor) {
@ -40,6 +46,16 @@ namespace dawn_native {
"Min lod clamp value cannot greater than max lod clamp value"); "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->minFilter));
DAWN_TRY(ValidateFilterMode(descriptor->magFilter)); DAWN_TRY(ValidateFilterMode(descriptor->magFilter));
DAWN_TRY(ValidateFilterMode(descriptor->mipmapFilter)); DAWN_TRY(ValidateFilterMode(descriptor->mipmapFilter));
@ -62,7 +78,8 @@ namespace dawn_native {
mMipmapFilter(descriptor->mipmapFilter), mMipmapFilter(descriptor->mipmapFilter),
mLodMinClamp(descriptor->lodMinClamp), mLodMinClamp(descriptor->lodMinClamp),
mLodMaxClamp(descriptor->lodMaxClamp), mLodMaxClamp(descriptor->lodMaxClamp),
mCompareFunction(descriptor->compare) { mCompareFunction(descriptor->compare),
mMaxAnisotropy(GetClampedMaxAnisotropy(descriptor->maxAnisotropy)) {
} }
SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag) SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@ -87,7 +104,8 @@ namespace dawn_native {
size_t SamplerBase::ComputeContentHash() { size_t SamplerBase::ComputeContentHash() {
ObjectContentHasher recorder; ObjectContentHasher recorder;
recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter, recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter,
mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction); mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction,
mMaxAnisotropy);
return recorder.GetContentHash(); return recorder.GetContentHash();
} }
@ -105,7 +123,7 @@ namespace dawn_native {
a->mAddressModeW == b->mAddressModeW && a->mMagFilter == b->mMagFilter && a->mAddressModeW == b->mAddressModeW && a->mMagFilter == b->mMagFilter &&
a->mMinFilter == b->mMinFilter && a->mMipmapFilter == b->mMipmapFilter && a->mMinFilter == b->mMinFilter && a->mMipmapFilter == b->mMipmapFilter &&
a->mLodMinClamp == b->mLodMinClamp && a->mLodMaxClamp == b->mLodMaxClamp && a->mLodMinClamp == b->mLodMinClamp && a->mLodMaxClamp == b->mLodMaxClamp &&
a->mCompareFunction == b->mCompareFunction; a->mCompareFunction == b->mCompareFunction && a->mMaxAnisotropy == b->mMaxAnisotropy;
} }
} // namespace dawn_native } // namespace dawn_native

View File

@ -42,6 +42,10 @@ namespace dawn_native {
bool operator()(const SamplerBase* a, const SamplerBase* b) const; bool operator()(const SamplerBase* a, const SamplerBase* b) const;
}; };
uint16_t GetMaxAnisotropy() const {
return mMaxAnisotropy;
}
private: private:
SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag); SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag);
@ -55,6 +59,7 @@ namespace dawn_native {
float mLodMinClamp; float mLodMinClamp;
float mLodMaxClamp; float mLodMaxClamp;
wgpu::CompareFunction mCompareFunction; wgpu::CompareFunction mCompareFunction;
uint16_t mMaxAnisotropy;
}; };
} // namespace dawn_native } // namespace dawn_native

View File

@ -69,13 +69,21 @@ namespace dawn_native { namespace d3d12 {
? D3D12_FILTER_REDUCTION_TYPE_STANDARD ? D3D12_FILTER_REDUCTION_TYPE_STANDARD
: D3D12_FILTER_REDUCTION_TYPE_COMPARISON; : D3D12_FILTER_REDUCTION_TYPE_COMPARISON;
mSamplerDesc.Filter = // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_sampler_desc
D3D12_ENCODE_BASIC_FILTER(minFilter, magFilter, mipmapFilter, reduction); 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.AddressU = AddressMode(descriptor->addressModeU);
mSamplerDesc.AddressV = AddressMode(descriptor->addressModeV); mSamplerDesc.AddressV = AddressMode(descriptor->addressModeV);
mSamplerDesc.AddressW = AddressMode(descriptor->addressModeW); mSamplerDesc.AddressW = AddressMode(descriptor->addressModeW);
mSamplerDesc.MipLODBias = 0.f; mSamplerDesc.MipLODBias = 0.f;
mSamplerDesc.MaxAnisotropy = 1;
if (descriptor->compare != wgpu::CompareFunction::Undefined) { if (descriptor->compare != wgpu::CompareFunction::Undefined) {
mSamplerDesc.ComparisonFunc = ToD3D12ComparisonFunc(descriptor->compare); mSamplerDesc.ComparisonFunc = ToD3D12ComparisonFunc(descriptor->compare);
} else { } else {

View File

@ -75,6 +75,8 @@ namespace dawn_native { namespace metal {
mtlDesc.lodMinClamp = descriptor->lodMinClamp; mtlDesc.lodMinClamp = descriptor->lodMinClamp;
mtlDesc.lodMaxClamp = descriptor->lodMaxClamp; 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) { if (descriptor->compare != wgpu::CompareFunction::Undefined) {
// Sampler compare is unsupported before A9, which we validate in // Sampler compare is unsupported before A9, which we validate in

View File

@ -82,7 +82,8 @@ namespace dawn_native { namespace opengl {
void Sampler::SetupGLSampler(GLuint sampler, void Sampler::SetupGLSampler(GLuint sampler,
const SamplerDescriptor* descriptor, const SamplerDescriptor* descriptor,
bool forceNearest) { bool forceNearest) {
const OpenGLFunctions& gl = ToBackend(GetDevice())->gl; Device* device = ToBackend(GetDevice());
const OpenGLFunctions& gl = device->gl;
if (forceNearest) { if (forceNearest) {
gl.SamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl.SamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@ -105,6 +106,11 @@ namespace dawn_native { namespace opengl {
gl.SamplerParameteri(sampler, GL_TEXTURE_COMPARE_FUNC, gl.SamplerParameteri(sampler, GL_TEXTURE_COMPARE_FUNC,
ToOpenGLCompareFunction(descriptor->compare)); 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 { GLuint Sampler::GetFilteringHandle() const {

View File

@ -316,6 +316,10 @@ namespace dawn_native { namespace vulkan {
mComputeSubgroupSize = FindComputeSubgroupSize(); mComputeSubgroupSize = FindComputeSubgroupSize();
} }
if (mDeviceInfo.features.samplerAnisotropy == VK_TRUE) {
usedKnobs.features.samplerAnisotropy = VK_TRUE;
}
if (IsExtensionEnabled(Extension::TextureCompressionBC)) { if (IsExtensionEnabled(Extension::TextureCompressionBC)) {
ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionBC == ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionBC ==
VK_TRUE); VK_TRUE);

View File

@ -71,8 +71,6 @@ namespace dawn_native { namespace vulkan {
createInfo.addressModeV = VulkanSamplerAddressMode(descriptor->addressModeV); createInfo.addressModeV = VulkanSamplerAddressMode(descriptor->addressModeV);
createInfo.addressModeW = VulkanSamplerAddressMode(descriptor->addressModeW); createInfo.addressModeW = VulkanSamplerAddressMode(descriptor->addressModeW);
createInfo.mipLodBias = 0.0f; createInfo.mipLodBias = 0.0f;
createInfo.anisotropyEnable = VK_FALSE;
createInfo.maxAnisotropy = 1.0f;
if (descriptor->compare != wgpu::CompareFunction::Undefined) { if (descriptor->compare != wgpu::CompareFunction::Undefined) {
createInfo.compareOp = ToVulkanCompareOp(descriptor->compare); createInfo.compareOp = ToVulkanCompareOp(descriptor->compare);
createInfo.compareEnable = VK_TRUE; createInfo.compareEnable = VK_TRUE;
@ -86,6 +84,18 @@ namespace dawn_native { namespace vulkan {
createInfo.unnormalizedCoordinates = VK_FALSE; createInfo.unnormalizedCoordinates = VK_FALSE;
Device* device = ToBackend(GetDevice()); 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( return CheckVkSuccess(
device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &*mHandle), device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
"CreateSampler"); "CreateSampler");

View File

@ -310,6 +310,7 @@ source_set("dawn_end2end_tests_sources") {
"end2end/RenderBundleTests.cpp", "end2end/RenderBundleTests.cpp",
"end2end/RenderPassLoadOpTests.cpp", "end2end/RenderPassLoadOpTests.cpp",
"end2end/RenderPassTests.cpp", "end2end/RenderPassTests.cpp",
"end2end/SamplerFilterAnisotropicTests.cpp",
"end2end/SamplerTests.cpp", "end2end/SamplerTests.cpp",
"end2end/ScissorTests.cpp", "end2end/ScissorTests.cpp",
"end2end/ShaderFloat16Tests.cpp", "end2end/ShaderFloat16Tests.cpp",

View File

@ -85,6 +85,21 @@ namespace {
DawnTestEnvironment* gTestEnv = nullptr; 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 } // anonymous namespace
const RGBA8 RGBA8::kZero = RGBA8(0, 0, 0, 0); const RGBA8 RGBA8::kZero = RGBA8(0, 0, 0, 0);
@ -1153,6 +1168,14 @@ bool RGBA8::operator!=(const RGBA8& other) const {
return !(*this == other); 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) { std::ostream& operator<<(std::ostream& stream, const RGBA8& color) {
return stream << "RGBA8(" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g) return stream << "RGBA8(" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g)
<< ", " << static_cast<int>(color.b) << ", " << static_cast<int>(color.a) << ")"; << ", " << static_cast<int>(color.b) << ", " << static_cast<int>(color.a) << ")";
@ -1191,26 +1214,12 @@ namespace detail {
<< mExpected[i] << ", actual " << actual[i] << mExpected[i] << ", actual " << actual[i]
<< std::endl; << 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) { if (mExpected.size() <= 1024) {
result << "Expected:" << std::endl; result << "Expected:" << std::endl;
printBuffer(mExpected.data()); printBuffer(result, mExpected.data(), mExpected.size());
result << "Actual:" << std::endl; result << "Actual:" << std::endl;
printBuffer(actual); printBuffer(result, actual, mExpected.size());
} }
return result; return result;
@ -1226,4 +1235,59 @@ namespace detail {
template class ExpectEq<uint64_t>; template class ExpectEq<uint64_t>;
template class ExpectEq<RGBA8>; template class ExpectEq<RGBA8>;
template class ExpectEq<float>; 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 } // namespace detail

View File

@ -73,6 +73,9 @@
#define EXPECT_TEXTURE_FLOAT_EQ(expected, texture, x, y, width, height, level, slice) \ #define EXPECT_TEXTURE_FLOAT_EQ(expected, texture, x, y, width, height, level, slice) \
AddTextureExpectation(__FILE__, __LINE__, 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. // TODO(enga): Migrate other texure expectation helpers to this common one.
#define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__) #define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__)
@ -95,6 +98,8 @@ struct RGBA8 {
} }
bool operator==(const RGBA8& other) const; bool operator==(const RGBA8& other) const;
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; uint8_t r, g, b, a;
@ -170,6 +175,8 @@ namespace detail {
template <typename T> template <typename T>
class ExpectEq; class ExpectEq;
template <typename T>
class ExpectBetweenColors;
} // namespace detail } // namespace detail
namespace dawn_wire { namespace dawn_wire {
@ -331,6 +338,24 @@ class DawnTestBase {
x, y, 1, 1, level, slice, aspect, sizeof(T), bytesPerRow); 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 WaitABit();
void FlushWire(); void FlushWire();
void WaitForAllOperations(); void WaitForAllOperations();
@ -518,6 +543,26 @@ namespace detail {
extern template class ExpectEq<uint64_t>; extern template class ExpectEq<uint64_t>;
extern template class ExpectEq<RGBA8>; extern template class ExpectEq<RGBA8>;
extern template class ExpectEq<float>; 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 } // namespace detail
#endif // TESTS_DAWNTEST_H_ #endif // TESTS_DAWNTEST_H_

View File

@ -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, &copySize);
}
wgpu::CommandBuffer copy = encoder.Finish();
queue.Submit(1, &copy);
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());

View File

@ -51,4 +51,75 @@ namespace {
} }
} }
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 } // anonymous namespace