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:
parent
c08276644f
commit
f8c5e4ab74
12
dawn.json
12
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"
|
||||
},
|
||||
|
|
|
@ -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 @@ namespace dawn_native {
|
|||
"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 @@ namespace dawn_native {
|
|||
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 @@ namespace dawn_native {
|
|||
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 @@ namespace dawn_native {
|
|||
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
|
||||
|
|
|
@ -42,6 +42,10 @@ namespace dawn_native {
|
|||
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 @@ namespace dawn_native {
|
|||
float mLodMinClamp;
|
||||
float mLodMaxClamp;
|
||||
wgpu::CompareFunction mCompareFunction;
|
||||
uint16_t mMaxAnisotropy;
|
||||
};
|
||||
|
||||
} // namespace dawn_native
|
||||
|
|
|
@ -69,13 +69,21 @@ namespace dawn_native { namespace d3d12 {
|
|||
? D3D12_FILTER_REDUCTION_TYPE_STANDARD
|
||||
: D3D12_FILTER_REDUCTION_TYPE_COMPARISON;
|
||||
|
||||
// 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 {
|
||||
|
|
|
@ -75,6 +75,8 @@ namespace dawn_native { namespace metal {
|
|||
|
||||
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
|
||||
|
|
|
@ -82,7 +82,8 @@ namespace dawn_native { namespace opengl {
|
|||
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 @@ namespace dawn_native { namespace opengl {
|
|||
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 {
|
||||
|
|
|
@ -316,6 +316,10 @@ namespace dawn_native { namespace vulkan {
|
|||
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);
|
||||
|
|
|
@ -71,8 +71,6 @@ namespace dawn_native { namespace vulkan {
|
|||
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 @@ namespace dawn_native { namespace vulkan {
|
|||
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");
|
||||
|
|
|
@ -310,6 +310,7 @@ source_set("dawn_end2end_tests_sources") {
|
|||
"end2end/RenderBundleTests.cpp",
|
||||
"end2end/RenderPassLoadOpTests.cpp",
|
||||
"end2end/RenderPassTests.cpp",
|
||||
"end2end/SamplerFilterAnisotropicTests.cpp",
|
||||
"end2end/SamplerTests.cpp",
|
||||
"end2end/ScissorTests.cpp",
|
||||
"end2end/ShaderFloat16Tests.cpp",
|
||||
|
|
|
@ -85,6 +85,21 @@ namespace {
|
|||
|
||||
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 @@ bool RGBA8::operator!=(const RGBA8& other) const {
|
|||
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 @@ namespace detail {
|
|||
<< 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 @@ namespace detail {
|
|||
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
|
||||
|
|
|
@ -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 @@ 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;
|
||||
|
||||
uint8_t r, g, b, a;
|
||||
|
||||
|
@ -170,6 +175,8 @@ namespace detail {
|
|||
|
||||
template <typename T>
|
||||
class ExpectEq;
|
||||
template <typename T>
|
||||
class ExpectBetweenColors;
|
||||
} // namespace detail
|
||||
|
||||
namespace dawn_wire {
|
||||
|
@ -331,6 +338,24 @@ class DawnTestBase {
|
|||
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 @@ namespace detail {
|
|||
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_
|
||||
|
|
|
@ -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());
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue