Merge pull request #1377 from gjonn/fix/agc-unnormalized-base-level-view
fix(agc): sample the base level of mipmapped views with unnormalized coordinates
This commit is contained in:
commit
5ad2cb8cd0
|
|
@ -434,6 +434,7 @@ private:
|
|||
std::uint64_t dispatchThreads = 0;
|
||||
std::vector<std::shared_ptr<Texture>> textures;
|
||||
std::vector<bool> textureFirstLayer;
|
||||
std::vector<bool> textureBaseLevel;
|
||||
std::vector<std::shared_ptr<StorageTexture>> storageTextures;
|
||||
std::vector<std::uint32_t> storageMips;
|
||||
std::vector<std::uint64_t> storageKeys;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
|
@ -79,6 +81,7 @@ public:
|
|||
|
||||
VkImageView View() const;
|
||||
VkImageView FirstLayerView() const { return firstLayerView; }
|
||||
VkImageView BaseLevelView() const;
|
||||
struct ViewRange {
|
||||
VkImageViewType type;
|
||||
std::uint32_t levels;
|
||||
|
|
@ -101,6 +104,7 @@ public:
|
|||
private:
|
||||
void release() noexcept;
|
||||
void createFirstLayerView(const GuestTextureResource& descriptor, VkImageViewCreateInfo viewInfo);
|
||||
void prepareBaseLevelView(const GuestTextureResource& descriptor, VkImageViewCreateInfo viewInfo);
|
||||
|
||||
// Held by value: cached textures outlive the Context of the draw that created them.
|
||||
Context context;
|
||||
|
|
@ -112,6 +116,9 @@ private:
|
|||
VkImageView firstLayerView = VK_NULL_HANDLE;
|
||||
ViewRange viewRange{};
|
||||
ViewRange firstLayerRange{};
|
||||
std::optional<VkImageViewCreateInfo> baseLevelInfo;
|
||||
mutable std::mutex baseLevelMutex;
|
||||
mutable VkImageView baseLevelView = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
VkDeviceSize allocationBytes = 0;
|
||||
VkDeviceSize countedBytes = 0;
|
||||
|
|
|
|||
|
|
@ -1506,7 +1506,7 @@ void ShaderResources::buildComplete() {
|
|||
write.pImageInfo = images.data() + images.size();
|
||||
for (const auto index : binding.imageAllocations) {
|
||||
if (index == std::numeric_limits<std::size_t>::max()) images.push_back(absentImage(binding.layout.binding));
|
||||
else images.push_back({VK_NULL_HANDLE, textureFirstLayer[index] ? textures[index]->FirstLayerView() : textures[index]->View(), textures[index]->Layout()});
|
||||
else images.push_back({VK_NULL_HANDLE, textureBaseLevel[index] ? textures[index]->BaseLevelView() : textureFirstLayer[index] ? textures[index]->FirstLayerView() : textures[index]->View(), textures[index]->Layout()});
|
||||
}
|
||||
Require(binding.imageAllocations.size() == binding.layout.descriptorCount, "descriptor allocations disagree with compact binding");
|
||||
break;
|
||||
|
|
@ -1681,6 +1681,7 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB
|
|||
packBits(binding.samplerUnnormalized);
|
||||
packBits(binding.samplerIntegerBorder);
|
||||
packBits(binding.imageUnnormalized);
|
||||
packBits(binding.imageUnnormalizedBaseLevel);
|
||||
// Read-only elements are bound without a write set: an object built for one written set
|
||||
// must not serve a build with another (the variant implies it, this makes it explicit).
|
||||
packBits(binding.bufferWritten);
|
||||
|
|
@ -3162,10 +3163,14 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
(texture != nullptr ? counters.fastHits : counters.fastMisses).fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare);
|
||||
bool baseLevel = false;
|
||||
if (element < binding.imageUnnormalized.size() && binding.imageUnnormalized[element]) {
|
||||
const auto range = texture->SampledViewRange(firstLayer);
|
||||
const bool singleLevel = range.levels == 1u && range.layers == 1u && resource.baseLevel == 0u && EffectiveMinLod(resource) == 0.0f;
|
||||
if (!singleLevel || (range.type != VK_IMAGE_VIEW_TYPE_1D && range.type != VK_IMAGE_VIEW_TYPE_2D)) throw std::runtime_error("AGC graphics: guest texture sampled with unnormalized coordinates is not a single-level, single-layer 1D or 2D view starting at mip 0, which is not implemented (base level " + std::to_string(resource.baseLevel) + ", levels " + std::to_string(range.levels) + ", layers " + std::to_string(range.layers) + ", view type " + std::to_string(static_cast<int>(range.type)) + ")");
|
||||
const bool exactAtBase = element < binding.imageUnnormalizedBaseLevel.size() && binding.imageUnnormalizedBaseLevel[element];
|
||||
const bool singleLevel = range.levels == 1u && resource.baseLevel == 0u;
|
||||
const bool supported = range.layers == 1u && EffectiveMinLod(resource) == 0.0f && (singleLevel || exactAtBase);
|
||||
if (!supported || (range.type != VK_IMAGE_VIEW_TYPE_1D && range.type != VK_IMAGE_VIEW_TYPE_2D)) throw std::runtime_error("AGC graphics: guest texture sampled with unnormalized coordinates is not a single-layer 1D or 2D view without a minimum LOD clamp, and is neither a single-level view at mip 0 nor read only by image_sample_lz through an S# without minimum LOD or bias, which is not implemented (base level " + std::to_string(resource.baseLevel) + ", levels " + std::to_string(range.levels) + ", layers " + std::to_string(range.layers) + ", view type " + std::to_string(static_cast<int>(range.type)) + ")");
|
||||
baseLevel = range.levels > 1u;
|
||||
}
|
||||
RequireFilterMinmax(context, texture->ViewFormat(), binding.imageSamplers[element], shaderSamplers);
|
||||
RequireBorderSwizzle(resource.bcSwizzle, binding.imageSamplers[element], shaderSamplers);
|
||||
|
|
@ -3185,6 +3190,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
}
|
||||
textures.push_back(std::move(texture));
|
||||
textureFirstLayer.push_back(firstLayer);
|
||||
textureBaseLevel.push_back(baseLevel);
|
||||
describedRanges.push_back({"texture", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
|
||||
item.imageAllocations.push_back(textures.size() - 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,6 +445,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
|||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView");
|
||||
viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount};
|
||||
createFirstLayerView(descriptor, viewInfo);
|
||||
prepareBaseLevelView(descriptor, viewInfo);
|
||||
if (profile) {
|
||||
auto& totals = Profile();
|
||||
totals.view += timer.lap();
|
||||
|
|
@ -646,6 +647,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
|||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView storage view");
|
||||
viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount};
|
||||
createFirstLayerView(descriptor, viewInfo);
|
||||
prepareBaseLevelView(descriptor, viewInfo);
|
||||
if (profile) {
|
||||
auto& totals = Profile();
|
||||
totals.view += timer.lap();
|
||||
|
|
@ -683,12 +685,31 @@ void Texture::createFirstLayerView(const GuestTextureResource& descriptor, VkIma
|
|||
firstLayerRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount};
|
||||
}
|
||||
|
||||
void Texture::prepareBaseLevelView(const GuestTextureResource& descriptor, VkImageViewCreateInfo viewInfo) {
|
||||
if (viewInfo.subresourceRange.levelCount == 1u) return;
|
||||
if (descriptor.dimension != TextureDimension::k1D && descriptor.dimension != TextureDimension::k2D && descriptor.dimension != TextureDimension::k2DArray) return;
|
||||
viewInfo.pNext = nullptr;
|
||||
viewInfo.viewType = descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
baseLevelInfo = viewInfo;
|
||||
}
|
||||
|
||||
VkImageView Texture::BaseLevelView() const {
|
||||
Require(baseLevelInfo.has_value(), "a base-level view needs a mipmapped 1D, 2D or 2D array texture");
|
||||
std::lock_guard lock(baseLevelMutex);
|
||||
if (!baseLevelView) Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &*baseLevelInfo, nullptr, &baseLevelView), "vkCreateImageView base level");
|
||||
return baseLevelView;
|
||||
}
|
||||
|
||||
void Texture::release() noexcept {
|
||||
upload.reset();
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
view = VK_NULL_HANDLE;
|
||||
if (firstLayerView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, firstLayerView, nullptr);
|
||||
firstLayerView = VK_NULL_HANDLE;
|
||||
if (baseLevelView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, baseLevelView, nullptr);
|
||||
baseLevelView = VK_NULL_HANDLE;
|
||||
// The image and its memory go with the last holder: this texture, or the batch still uploading it.
|
||||
owned.reset();
|
||||
image = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ void setEnvironment(const char* name, const std::string& value) {
|
|||
}
|
||||
|
||||
bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) {
|
||||
return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten && left.bufferRead == right.bufferRead && left.samplerUnnormalized == right.samplerUnnormalized && left.samplerIntegerBorder == right.samplerIntegerBorder && left.imageUnnormalized == right.imageUnnormalized && left.imageSamplers == right.imageSamplers;
|
||||
return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten && left.bufferRead == right.bufferRead && left.samplerUnnormalized == right.samplerUnnormalized && left.samplerIntegerBorder == right.samplerIntegerBorder && left.imageUnnormalized == right.imageUnnormalized && left.imageUnnormalizedBaseLevel == right.imageUnnormalizedBaseLevel && left.imageSamplers == right.imageSamplers;
|
||||
}
|
||||
|
||||
bool sameBindings(const std::vector<DescriptorBinding>& left, const std::vector<DescriptorBinding>& right) {
|
||||
|
|
@ -121,6 +121,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) {
|
|||
binding.samplerUnnormalized = {false, true, true};
|
||||
binding.samplerIntegerBorder = {true, false, seed % 2 == 1};
|
||||
binding.imageUnnormalized = {true, false, seed % 2 == 0};
|
||||
binding.imageUnnormalizedBaseLevel = {false, true, seed % 3 == 0};
|
||||
binding.imageSamplers = {0x5u, 0u, 0x80000000u};
|
||||
binding.bufferRead = {true, false, false, true, true, false};
|
||||
return binding;
|
||||
|
|
|
|||
|
|
@ -2096,6 +2096,21 @@ void verifyUnusedUnnormalizedSampler() {
|
|||
snapshot.images[0].dwords[3] = 0xa0000facu;
|
||||
expectFailure([&] { static_cast<void>(populate(foldVolume)); }, "takes only images an unnormalized S# can sample", "unnormalized texel offsets: an ordinary 3D view matched a constant-only mode");
|
||||
snapshot.images[0].dwords[3] = 0x90000facu;
|
||||
const auto atBaseLevel = [&](const ShaderInfo& shader) { return populate(shader)[0].imageUnnormalizedBaseLevel; };
|
||||
auto levelZero = info;
|
||||
levelZero.samplers[0].uses = SamplerUseExplicitLod;
|
||||
require(atBaseLevel(levelZero) == std::vector<bool>{true}, "unnormalized samplers: an image read only by image_sample_lz was not flagged as exact at its base level");
|
||||
require(atBaseLevel(info) == std::vector<bool>{false}, "unnormalized samplers: an image without live uses was flagged as exact at its base level");
|
||||
require(atBaseLevel(offset) == std::vector<bool>{false}, "unnormalized samplers: an image read with a texel offset was flagged as exact at its base level");
|
||||
auto selectedLod = levelZero;
|
||||
selectedLod.samplers[0].uses |= SamplerUseNonZeroLod;
|
||||
require(atBaseLevel(selectedLod) == std::vector<bool>{false}, "unnormalized samplers: an image also read at a selected LOD was flagged as exact at its base level");
|
||||
snapshot.samplers[0].dwords[1] = 0x00fff100u;
|
||||
require(atBaseLevel(levelZero) == std::vector<bool>{false}, "unnormalized samplers: an S# with a minimum LOD was flagged as exact at the base level");
|
||||
snapshot.samplers[0].dwords[1] = 0x00fff000u;
|
||||
snapshot.samplers[0].dwords[2] = 0x05500180u;
|
||||
require(atBaseLevel(levelZero) == std::vector<bool>{false}, "unnormalized samplers: an S# with a LOD bias was flagged as exact at the base level");
|
||||
snapshot.samplers[0].dwords[2] = 0x05500000u;
|
||||
snapshot.samplers[0].dwords[0] = 0x00000092u;
|
||||
const auto normalized = populate(info);
|
||||
require(normalized[0].imageUnnormalized == std::vector<bool>{false} && normalized[1].samplerUnnormalized == std::vector<bool>{false}, "unnormalized samplers: a normalized S# was flagged");
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ alignas(256) constexpr std::array<std::uint32_t, 40> Code{
|
|||
0x80001001, 0xe0701034, 0x80001101, 0xe0701038, 0x80001201, 0xe070103c, 0x80001301, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 18> LevelZeroCode{
|
||||
0x34020086, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xbf8c3f70, 0xf09c0f08, 0x00610802,
|
||||
0xbf8c3f70, 0xe0701010, 0x80000801, 0xe0701014, 0x80000901, 0xe0701018, 0x80000a01, 0xe070101c,
|
||||
0x80000b01, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 45> OffsetCode{
|
||||
0x34020086, 0xe0301008, 0x80000201, 0xe0301000, 0x80000301, 0xe0301004, 0x80000401, 0xbf8c3f70,
|
||||
0x7e0a02ff, 0x402ccccd, 0x7e2802ff, 0x0000023d, 0xf0dc0f08, 0x00610802, 0xf0d00f08, 0x00610c02,
|
||||
|
|
@ -64,9 +70,11 @@ struct Program {
|
|||
std::span<const std::uint32_t> code;
|
||||
std::array<const char*, 3> instructions;
|
||||
std::array<Offset, 3> offsets;
|
||||
std::size_t count = 3;
|
||||
};
|
||||
|
||||
const Program Plain{Code, {"image_sample_lz", "image_sample_l 2.7", "image_sample"}, {Offset::None, Offset::None, Offset::None}};
|
||||
const Program LevelZero{LevelZeroCode, {"image_sample_lz", "", ""}, {Offset::None, Offset::None, Offset::None}, 1};
|
||||
const Program Offsets{OffsetCode, {"image_sample_lz_o", "image_sample_l_o 2.7", "image_sample_lz_o (-3, 2) NSA"}, {Offset::Lane, Offset::Lane, Offset::Constant}};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 34> ConstantCode{
|
||||
|
|
@ -82,14 +90,15 @@ struct SamplerCase {
|
|||
std::array<std::uint32_t, 4> words;
|
||||
bool linear;
|
||||
bool border;
|
||||
bool atBaseLevel;
|
||||
};
|
||||
|
||||
constexpr std::array<SamplerCase, 5> Samplers{{
|
||||
{"bilinear clamp-to-last-texel, point mips, MAX_LOD 0xfff", {0x00008092u, 0x00fff000u, 0x05500000u, 0u}, true, false},
|
||||
{"point clamp-to-last-texel, point mips", {0x00008092u, 0x00fff000u, 0x04000000u, 0u}, false, false},
|
||||
{"bilinear clamp-to-last-texel, linear mips, MIN_LOD 1, LOD bias 1.5", {0x00008092u, 0x00fff100u, 0x09500180u, 0u}, true, false},
|
||||
{"bilinear clamp-to-border, transparent black", {0x000080b6u, 0x00fff000u, 0x05500000u, 0u}, true, true},
|
||||
{"point clamp-to-border, no mips, transparent black", {0x000080b6u, 0u, 0u, 0u}, false, true},
|
||||
{"bilinear clamp-to-last-texel, point mips, MAX_LOD 0xfff", {0x00008092u, 0x00fff000u, 0x05500000u, 0u}, true, false, true},
|
||||
{"point clamp-to-last-texel, point mips", {0x00008092u, 0x00fff000u, 0x04000000u, 0u}, false, false, true},
|
||||
{"bilinear clamp-to-last-texel, linear mips, MIN_LOD 1, LOD bias 1.5", {0x00008092u, 0x00fff100u, 0x09500180u, 0u}, true, false, false},
|
||||
{"bilinear clamp-to-border, transparent black", {0x000080b6u, 0x00fff000u, 0x05500000u, 0u}, true, true, true},
|
||||
{"point clamp-to-border, no mips, transparent black", {0x000080b6u, 0u, 0u, 0u}, false, true, true},
|
||||
}};
|
||||
|
||||
struct Coordinate {
|
||||
|
|
@ -99,21 +108,31 @@ struct Coordinate {
|
|||
std::int32_t dv = 0;
|
||||
};
|
||||
|
||||
std::vector<Coordinate> Coordinates() {
|
||||
std::int32_t LevelWidth(std::uint32_t level) {
|
||||
return std::max(Width >> level, 1);
|
||||
}
|
||||
|
||||
std::int32_t LevelHeight(std::uint32_t level) {
|
||||
return std::max(Height >> level, 1);
|
||||
}
|
||||
|
||||
std::vector<Coordinate> Coordinates(std::uint32_t level) {
|
||||
const auto width = LevelWidth(level);
|
||||
const auto height = LevelHeight(level);
|
||||
std::vector<float> us;
|
||||
for (std::int32_t k = 0; k < Width; ++k) {
|
||||
for (std::int32_t k = 0; k < width; ++k) {
|
||||
for (const float fraction : {0.5f, 0.0f, 0.25f, 0.75f}) us.push_back(static_cast<float>(k) + fraction);
|
||||
}
|
||||
for (const float edge : {-1.0f, -0.5f, 13.0f, 14.0f, 12.75f}) us.push_back(edge);
|
||||
for (const float edge : {-1.0f, -0.5f, static_cast<float>(width), static_cast<float>(width + 1), static_cast<float>(width) - 0.25f}) us.push_back(edge);
|
||||
constexpr std::array<float, 3> fractions{0.5f, 0.0f, 0.75f};
|
||||
std::vector<Coordinate> result;
|
||||
for (std::size_t index = 0; index < us.size(); ++index) {
|
||||
for (std::size_t fraction = 0; fraction < fractions.size(); ++fraction) {
|
||||
result.push_back({us[index], static_cast<float>((index * fractions.size() + fraction) % Height) + fractions[fraction]});
|
||||
result.push_back({us[index], static_cast<float>((index * fractions.size() + fraction) % height) + fractions[fraction]});
|
||||
}
|
||||
}
|
||||
for (std::int32_t x = 0; x < Width; ++x) {
|
||||
const float v = static_cast<float>(x % Height) + 0.5f;
|
||||
for (std::int32_t x = 0; x < width; ++x) {
|
||||
const float v = static_cast<float>(x % height) + 0.5f;
|
||||
result.push_back({static_cast<float>(x) - 1.0f, v});
|
||||
result.push_back({static_cast<float>(x) + 2.0f, v});
|
||||
}
|
||||
|
|
@ -123,7 +142,7 @@ std::vector<Coordinate> Coordinates() {
|
|||
std::vector<Coordinate> OffsetCoordinates() {
|
||||
constexpr std::array<std::int32_t, 7> us{-32, -5, -1, 0, 1, 3, 31};
|
||||
constexpr std::array<std::int32_t, 6> vs{-2, -1, 0, 2, -32, 31};
|
||||
auto result = Coordinates();
|
||||
auto result = Coordinates(0u);
|
||||
for (std::size_t index = 0; index < result.size(); ++index) {
|
||||
result[index].du = us[index % us.size()];
|
||||
result[index].dv = vs[index % vs.size()];
|
||||
|
|
@ -131,7 +150,8 @@ std::vector<Coordinate> OffsetCoordinates() {
|
|||
return result;
|
||||
}
|
||||
|
||||
std::array<double, 4> TexelOf(std::int32_t x, std::int32_t y) {
|
||||
std::array<double, 4> TexelOf(std::uint32_t level, std::int32_t x, std::int32_t y) {
|
||||
if (level == 1u) return {240.0 * ((x + y + 1) & 1), 32.0 * y + 48.0, 16.0 * x + 40.0, 200.0};
|
||||
return {240.0 * ((x + y) & 1), 16.0 * x + 16.0, 32.0 * y + 32.0, 240.0};
|
||||
}
|
||||
|
||||
|
|
@ -144,9 +164,9 @@ void FillTexture(std::span<std::uint8_t> texels, std::uint32_t levels) {
|
|||
for (std::uint32_t y = 0; y < mip.height; ++y) {
|
||||
for (std::uint32_t x = 0; x < mip.width; ++x) {
|
||||
auto* texel = &texels[mip.tiledOffset + static_cast<std::uint64_t>(y) * mip.pitchBytes + x * 4u];
|
||||
const auto value = TexelOf(static_cast<std::int32_t>(x), static_cast<std::int32_t>(y));
|
||||
const auto value = TexelOf(level, static_cast<std::int32_t>(x), static_cast<std::int32_t>(y));
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) {
|
||||
texel[component] = level == 0u ? static_cast<std::uint8_t>(value[component]) : OtherLevelTexel;
|
||||
texel[component] = level <= 1u ? static_cast<std::uint8_t>(value[component]) : OtherLevelTexel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,13 +237,15 @@ Samples Run(AgcDriver::VulkanDevice& device, const Program& program, const std::
|
|||
return samples;
|
||||
}
|
||||
|
||||
std::array<double, 4> Fetch(const SamplerCase& sampler, std::int32_t x, std::int32_t y) {
|
||||
if (sampler.border && (x < 0 || x >= Width || y < 0 || y >= Height)) return {0.0, 0.0, 0.0, 0.0};
|
||||
return TexelOf(std::clamp(x, 0, Width - 1), std::clamp(y, 0, Height - 1));
|
||||
std::array<double, 4> Fetch(const SamplerCase& sampler, std::uint32_t level, std::int32_t x, std::int32_t y) {
|
||||
const auto width = LevelWidth(level);
|
||||
const auto height = LevelHeight(level);
|
||||
if (sampler.border && (x < 0 || x >= width || y < 0 || y >= height)) return {0.0, 0.0, 0.0, 0.0};
|
||||
return TexelOf(level, std::clamp(x, 0, width - 1), std::clamp(y, 0, height - 1));
|
||||
}
|
||||
|
||||
std::array<double, 4> Reference(const SamplerCase& sampler, const Coordinate& coordinate) {
|
||||
if (!sampler.linear) return Fetch(sampler, static_cast<std::int32_t>(std::floor(coordinate.u)), static_cast<std::int32_t>(std::floor(coordinate.v)));
|
||||
std::array<double, 4> Reference(const SamplerCase& sampler, std::uint32_t level, const Coordinate& coordinate) {
|
||||
if (!sampler.linear) return Fetch(sampler, level, static_cast<std::int32_t>(std::floor(coordinate.u)), static_cast<std::int32_t>(std::floor(coordinate.v)));
|
||||
const double u = static_cast<double>(coordinate.u) - 0.5;
|
||||
const double v = static_cast<double>(coordinate.v) - 0.5;
|
||||
const auto i = static_cast<std::int32_t>(std::floor(u));
|
||||
|
|
@ -232,22 +254,22 @@ std::array<double, 4> Reference(const SamplerCase& sampler, const Coordinate& co
|
|||
const double beta = v - j;
|
||||
std::array<double, 4> result{};
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) {
|
||||
result[component] = (1.0 - alpha) * (1.0 - beta) * Fetch(sampler, i, j)[component] + alpha * (1.0 - beta) * Fetch(sampler, i + 1, j)[component]
|
||||
+ (1.0 - alpha) * beta * Fetch(sampler, i, j + 1)[component] + alpha * beta * Fetch(sampler, i + 1, j + 1)[component];
|
||||
result[component] = (1.0 - alpha) * (1.0 - beta) * Fetch(sampler, level, i, j)[component] + alpha * (1.0 - beta) * Fetch(sampler, level, i + 1, j)[component]
|
||||
+ (1.0 - alpha) * beta * Fetch(sampler, level, i, j + 1)[component] + alpha * beta * Fetch(sampler, level, i + 1, j + 1)[component];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Check(const Program& program, const SamplerCase& sampler, const char* image, const std::vector<Coordinate>& coordinates, const Samples& samples) {
|
||||
void Check(const Program& program, const SamplerCase& sampler, std::uint32_t level, const char* image, const std::vector<Coordinate>& coordinates, const Samples& samples) {
|
||||
Require(samples.size() == coordinates.size(), "image sample unnormalized: a dispatch lost samples");
|
||||
const double tolerance = sampler.linear ? 0.5 : 0.25;
|
||||
for (std::size_t index = 0; index < coordinates.size(); ++index) {
|
||||
const auto& coordinate = coordinates[index];
|
||||
for (std::uint32_t instruction = 0; instruction < program.instructions.size(); ++instruction) {
|
||||
for (std::uint32_t instruction = 0; instruction < program.count; ++instruction) {
|
||||
const auto offset = program.offsets[instruction];
|
||||
const std::int32_t du = offset == Offset::Lane ? coordinate.du : offset == Offset::Constant ? ConstantOffsetU : 0;
|
||||
const std::int32_t dv = offset == Offset::Lane ? coordinate.dv : offset == Offset::Constant ? ConstantOffsetV : 0;
|
||||
const auto expected = Reference(sampler, {coordinate.u + static_cast<float>(du), coordinate.v + static_cast<float>(dv)});
|
||||
const auto expected = Reference(sampler, level, {coordinate.u + static_cast<float>(du), coordinate.v + static_cast<float>(dv)});
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) {
|
||||
const double value = static_cast<double>(std::bit_cast<float>(samples[index][instruction * 4u + component])) * 255.0;
|
||||
Require(std::fabs(value - expected[component]) <= tolerance, std::string(program.instructions[instruction]) + ", " + sampler.name + ", " + image + ": (" + std::to_string(coordinate.u) + ", " + std::to_string(coordinate.v) + ") offset (" + std::to_string(du) + ", " + std::to_string(dv) + ") component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component]));
|
||||
|
|
@ -256,10 +278,10 @@ void Check(const Program& program, const SamplerCase& sampler, const char* image
|
|||
}
|
||||
}
|
||||
|
||||
void ExpectFailure(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, const std::array<std::uint32_t, 4>& sampler, std::string_view reason, const char* what) {
|
||||
void ExpectFailure(AgcDriver::VulkanDevice& device, const Program& program, const std::array<std::uint32_t, 8>& texture, const std::array<std::uint32_t, 4>& sampler, std::string_view reason, const char* what) {
|
||||
const std::vector<Coordinate> one{{0.5f, 0.5f}};
|
||||
try {
|
||||
static_cast<void>(Run(device, Plain, texture, sampler, one));
|
||||
static_cast<void>(Run(device, program, texture, sampler, one));
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string(what) + ": unexpected error: " + error.what());
|
||||
return;
|
||||
|
|
@ -275,21 +297,36 @@ int main() {
|
|||
if (!device) return VulkanTestSkipped;
|
||||
FillTexture(SingleLevel, 1);
|
||||
FillTexture(MultiLevel, StorageLevels);
|
||||
const auto coordinates = Coordinates();
|
||||
const auto coordinates = Coordinates(0u);
|
||||
const auto levelOneCoordinates = Coordinates(1u);
|
||||
const auto single = TextureDescriptor(SingleLevel.data(), 0u, 0u);
|
||||
const auto multi = TextureDescriptor(MultiLevel.data(), 0u, StorageLevels - 1u);
|
||||
const auto chain = TextureDescriptor(MultiLevel.data(), StorageLevels - 1u, StorageLevels - 1u);
|
||||
auto fromLevelOne = chain;
|
||||
fromLevelOne[3] |= 1u << 12u;
|
||||
const bool driverSamplesUnnormalizedAboveMipZero = device->DeviceName().find("llvmpipe") == std::string::npos;
|
||||
const auto offsetCoordinates = OffsetCoordinates();
|
||||
for (const auto& sampler : Samplers) {
|
||||
const auto singleSamples = Run(*device, Plain, single, sampler.words, coordinates);
|
||||
Check(Plain, sampler, "1-level image", coordinates, singleSamples);
|
||||
Check(Plain, sampler, 0u, "1-level image", coordinates, singleSamples);
|
||||
const auto multiSamples = Run(*device, Plain, multi, sampler.words, coordinates);
|
||||
Check(Plain, sampler, "first level of a 4-level image", coordinates, multiSamples);
|
||||
Check(Plain, sampler, 0u, "first level of a 4-level image", coordinates, multiSamples);
|
||||
Require(multiSamples == singleSamples, std::string(sampler.name) + ": the first level of a 4-level image does not sample like a 1-level image");
|
||||
Check(Offsets, sampler, "1-level image", offsetCoordinates, Run(*device, Offsets, single, sampler.words, offsetCoordinates));
|
||||
Check(Offsets, sampler, 0u, "1-level image", offsetCoordinates, Run(*device, Offsets, single, sampler.words, offsetCoordinates));
|
||||
if (!sampler.atBaseLevel) continue;
|
||||
const auto levelZeroSingle = Run(*device, LevelZero, single, sampler.words, coordinates);
|
||||
const auto levelZeroChain = Run(*device, LevelZero, chain, sampler.words, coordinates);
|
||||
Check(LevelZero, sampler, 0u, "4-level view", coordinates, levelZeroChain);
|
||||
Require(levelZeroChain == levelZeroSingle, std::string(sampler.name) + ": a 4-level view read by image_sample_lz does not sample its base level like a 1-level image");
|
||||
if (driverSamplesUnnormalizedAboveMipZero) Check(LevelZero, sampler, 1u, "3-level view starting at mip 1", levelOneCoordinates, Run(*device, LevelZero, fromLevelOne, sampler.words, levelOneCoordinates));
|
||||
}
|
||||
ExpectFailure(*device, TextureDescriptor(MultiLevel.data(), 2u, 2u), Samplers[0].words, "single-level", "a 3-level view");
|
||||
ExpectFailure(*device, single, {0x00008092u, 0x00fff000u, 0x05100000u, 0u}, "different minification", "unequal minification and magnification filters");
|
||||
ExpectFailure(*device, single, {0x00008090u, 0x00fff000u, 0x05500000u, 0u}, "clamp mode 0", "wrap on X");
|
||||
ExpectFailure(*device, Plain, TextureDescriptor(MultiLevel.data(), 2u, 2u), Samplers[0].words, "single-level", "a 3-level view read by a selected LOD");
|
||||
ExpectFailure(*device, Plain, chain, Samplers[0].words, "single-level", "a 4-level view read by image_sample_l and image_sample");
|
||||
ExpectFailure(*device, Plain, fromLevelOne, Samplers[0].words, "single-level", "a view starting at mip 1 read by image_sample_l and image_sample");
|
||||
ExpectFailure(*device, Offsets, chain, Samplers[0].words, "single-level", "a 4-level view read with texel offsets");
|
||||
ExpectFailure(*device, LevelZero, chain, Samplers[2].words, "single-level", "a 4-level view through an S# with a minimum LOD and a LOD bias");
|
||||
ExpectFailure(*device, Plain, single, {0x00008092u, 0x00fff000u, 0x05100000u, 0u}, "different minification", "unequal minification and magnification filters");
|
||||
ExpectFailure(*device, Plain, single, {0x00008090u, 0x00fff000u, 0x05500000u, 0u}, "clamp mode 0", "wrap on X");
|
||||
const auto constantView = [&](std::uint32_t type, std::uint32_t format, std::uint32_t swizzle) {
|
||||
auto descriptor = TextureDescriptor(SingleLevel.data(), 0u, 0u);
|
||||
descriptor[1] = (descriptor[1] & ~(0x1ffu << 20u)) | (format << 20u);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ struct ImageResource {
|
|||
bool operator==(const ImageResource& other) const = default;
|
||||
};
|
||||
|
||||
enum SamplerUse : std::uint8_t {
|
||||
enum SamplerUse : std::uint16_t {
|
||||
SamplerUseExplicitLod = 1u << 0u,
|
||||
SamplerUseImplicitLod = 1u << 1u,
|
||||
SamplerUseGradient = 1u << 2u,
|
||||
|
|
@ -109,6 +109,7 @@ enum SamplerUse : std::uint8_t {
|
|||
SamplerUseGather = 1u << 5u,
|
||||
SamplerUseQueryLod = 1u << 6u,
|
||||
SamplerUseAdjust = 1u << 7u,
|
||||
SamplerUseNonZeroLod = 1u << 8u,
|
||||
};
|
||||
|
||||
inline bool ImageSampleExplicitLod(std::uint32_t flags, IrShaderStage stage) {
|
||||
|
|
@ -123,14 +124,14 @@ struct SamplerResource {
|
|||
std::uint32_t copyOf = NoCopy;
|
||||
bool forcePointFiltering = false;
|
||||
bool depthCompare = false;
|
||||
std::uint8_t uses = 0;
|
||||
std::uint16_t uses = 0;
|
||||
bool foldTexelOffsetsOnly = false;
|
||||
|
||||
bool operator==(const SamplerResource& other) const = default;
|
||||
};
|
||||
|
||||
inline bool FoldsTexelOffsets(const SamplerResource& sampler) {
|
||||
return (sampler.uses & SamplerUseOffset) != 0u && (sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset)) == 0u && !sampler.depthCompare;
|
||||
return (sampler.uses & SamplerUseOffset) != 0u && (sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod)) == 0u && !sampler.depthCompare;
|
||||
}
|
||||
|
||||
struct SampledResourcePair {
|
||||
|
|
|
|||
|
|
@ -160,10 +160,11 @@ const char* UnnormalizedUseReason(std::uint32_t uses) {
|
|||
struct UnnormalizedProof {
|
||||
std::vector<bool> samplers;
|
||||
std::vector<bool> images;
|
||||
std::vector<bool> imagesAtBaseLevel;
|
||||
};
|
||||
|
||||
UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapshot& snapshot) {
|
||||
UnnormalizedProof proof{std::vector<bool>(info.samplers.size()), std::vector<bool>(info.images.size())};
|
||||
UnnormalizedProof proof{std::vector<bool>(info.samplers.size()), std::vector<bool>(info.images.size()), std::vector<bool>(info.images.size())};
|
||||
for (std::uint32_t r = 0; r < info.samplers.size(); r++) {
|
||||
if (snapshot.samplers.at(r).dwordCount != 4u) fail("sampler descriptor must contain four dwords");
|
||||
const auto& sampler = info.samplers[r];
|
||||
|
|
@ -173,7 +174,7 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh
|
|||
}
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t unsupported = sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset);
|
||||
const std::uint32_t unsupported = sampler.uses & ~static_cast<std::uint32_t>(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod);
|
||||
if (unsupported != 0u) {
|
||||
failUnnormalized(UnnormalizedUseReason(unsupported));
|
||||
}
|
||||
|
|
@ -194,6 +195,16 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh
|
|||
}
|
||||
proof.samplers[r] = true;
|
||||
}
|
||||
for (std::uint32_t resource = 0; resource < info.images.size(); resource++) {
|
||||
bool exact = proof.images[resource];
|
||||
for (const auto& pair : info.sampledPairs) {
|
||||
if (!exact) break;
|
||||
if (pair.image != resource) continue;
|
||||
const auto& words = snapshot.samplers.at(pair.sampler).dwords;
|
||||
exact = proof.samplers[pair.sampler] && info.samplers.at(pair.sampler).uses == SamplerUseExplicitLod && (words[1] & 0xfffu) == 0u && (words[2] & 0x3fffu) == 0u;
|
||||
}
|
||||
proof.imagesAtBaseLevel[resource] = exact;
|
||||
}
|
||||
return proof;
|
||||
}
|
||||
|
||||
|
|
@ -390,6 +401,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
|
|||
physical.imageDepthCompare.push_back(mode.depthCompare);
|
||||
physical.imageAtomic.push_back(image.atomic);
|
||||
physical.imageUnnormalized.push_back(unnormalized.images.at(resource));
|
||||
physical.imageUnnormalizedBaseLevel.push_back(unnormalized.imagesAtBaseLevel.at(resource));
|
||||
physical.imageSamplers.push_back(0u);
|
||||
physical.imageAtomic64.push_back(image.atomic64);
|
||||
if (layout.runtimeImageCount == 0u || image.indirectRoot == ImageResource::NoIndirectImage) continue;
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ void CollectOutputs(const IrProgram& program, ShaderStageInputInfo inputInfo, Sh
|
|||
}
|
||||
}
|
||||
|
||||
std::uint8_t SamplerUseOf(IrOpcode opcode, std::uint32_t flags, IrShaderStage stage) {
|
||||
std::uint16_t SamplerUseOf(IrOpcode opcode, std::uint32_t flags, IrShaderStage stage) {
|
||||
if (opcode == IrOpcode::ImageGatherRaw) {
|
||||
return SamplerUseGather;
|
||||
}
|
||||
|
|
@ -465,6 +465,9 @@ std::uint8_t SamplerUseOf(IrOpcode opcode, std::uint32_t flags, IrShaderStage st
|
|||
return SamplerUseQueryLod;
|
||||
}
|
||||
std::uint32_t use = (flags & RdnaImageSampleFlagDerivative) != 0u ? SamplerUseGradient : ImageSampleExplicitLod(flags, stage) ? SamplerUseExplicitLod : SamplerUseImplicitLod;
|
||||
if (use == SamplerUseExplicitLod && (flags & RdnaImageSampleFlagLevelZero) == 0u) {
|
||||
use |= SamplerUseNonZeroLod;
|
||||
}
|
||||
if ((flags & RdnaImageSampleFlagOffset) != 0u) {
|
||||
use |= SamplerUseOffset;
|
||||
}
|
||||
|
|
@ -474,7 +477,7 @@ std::uint8_t SamplerUseOf(IrOpcode opcode, std::uint32_t flags, IrShaderStage st
|
|||
if ((flags & RdnaImageSampleFlagAdjust) != 0u) {
|
||||
use |= SamplerUseAdjust;
|
||||
}
|
||||
return static_cast<std::uint8_t>(use);
|
||||
return static_cast<std::uint16_t>(use);
|
||||
}
|
||||
|
||||
void CollectSamplerUses(const IrProgram& program, ShaderInfo& info) {
|
||||
|
|
@ -496,7 +499,7 @@ void CollectSamplerUses(const IrProgram& program, ShaderInfo& info) {
|
|||
return Fail("image instruction references an unknown sampler");
|
||||
}
|
||||
auto& sampler = info.samplers[memory.sampler];
|
||||
sampler.uses = static_cast<std::uint8_t>(sampler.uses | SamplerUseOf(inst->Opcode(), memory.imageSampleFlags, program.Resources().stage));
|
||||
sampler.uses = static_cast<std::uint16_t>(sampler.uses | SamplerUseOf(inst->Opcode(), memory.imageSampleFlags, program.Resources().stage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,6 +368,7 @@ struct DescriptorBinding {
|
|||
std::vector<bool> samplerUnnormalized;
|
||||
std::vector<bool> samplerIntegerBorder;
|
||||
std::vector<bool> imageUnnormalized;
|
||||
std::vector<bool> imageUnnormalizedBaseLevel;
|
||||
std::vector<std::uint32_t> imageSamplers;
|
||||
std::vector<bool> bufferRead;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace ShaderRecompiler::ShaderDiskCache {
|
|||
static_assert(sizeof(CompiledShaderArtifact) == 192, "CompiledShaderArtifact changed: update the artifact encoder");
|
||||
static_assert(sizeof(ShaderInvocation) == 112, "ShaderInvocation changed: update the invocation encoder");
|
||||
static_assert(sizeof(RecompileResult) == 312, "RecompileResult changed: update EncodeResult and DecodeResult");
|
||||
static_assert(sizeof(DescriptorBinding) == 528, "DescriptorBinding changed: update the binding encoder");
|
||||
static_assert(sizeof(DescriptorBinding) == 568, "DescriptorBinding changed: update the binding encoder");
|
||||
static_assert(sizeof(VertexAttribute) == 32, "VertexAttribute changed: update the attribute encoder");
|
||||
static_assert(sizeof(VertexInput) == 16, "VertexInput changed: update the vertex input encoder");
|
||||
static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: update the parameter encoder");
|
||||
|
|
@ -75,7 +75,7 @@ static_assert(sizeof(CompiledShaderInfo) == 344, "CompiledShaderInfo changed: up
|
|||
static_assert(sizeof(ShaderInfo) == 232, "ShaderInfo changed: update the info encoder");
|
||||
static_assert(sizeof(BufferResource) == 32, "BufferResource changed: update the info encoder");
|
||||
static_assert(sizeof(ImageResource) == 120, "ImageResource changed: update the info encoder");
|
||||
static_assert(sizeof(SamplerResource) == 16, "SamplerResource changed: update the info encoder");
|
||||
static_assert(sizeof(SamplerResource) == 20, "SamplerResource changed: update the info encoder");
|
||||
static_assert(sizeof(SampledResourcePair) == 12, "SampledResourcePair changed: update the info encoder");
|
||||
static_assert(sizeof(StageInput) == 56, "StageInput changed: update the info encoder");
|
||||
static_assert(sizeof(StageOutput) == 48, "StageOutput changed: update the info encoder");
|
||||
|
|
@ -256,6 +256,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) {
|
|||
writer.Flags(binding.bufferWritten);
|
||||
writer.Flags(binding.samplerUnnormalized);
|
||||
writer.Flags(binding.imageUnnormalized);
|
||||
writer.Flags(binding.imageUnnormalizedBaseLevel);
|
||||
writer.Flags(binding.samplerIntegerBorder);
|
||||
writer.Values(std::span<const std::uint32_t>(binding.imageSamplers));
|
||||
writer.Flags(binding.bufferRead);
|
||||
|
|
@ -281,6 +282,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) {
|
|||
reader.Flags(binding.bufferWritten);
|
||||
reader.Flags(binding.samplerUnnormalized);
|
||||
reader.Flags(binding.imageUnnormalized);
|
||||
reader.Flags(binding.imageUnnormalizedBaseLevel);
|
||||
reader.Flags(binding.samplerIntegerBorder);
|
||||
reader.Values(binding.imageSamplers);
|
||||
reader.Flags(binding.bufferRead);
|
||||
|
|
@ -608,7 +610,7 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) {
|
|||
bool preparedImageModes = false;
|
||||
reader.Value(preparedImageModes);
|
||||
if (preparedImageModes) ResourceMaterializer::PrepareImageModes(info);
|
||||
reader.List(info.samplers, 16, [](Reader& in, SamplerResource& sampler) {
|
||||
reader.List(info.samplers, 17, [](Reader& in, SamplerResource& sampler) {
|
||||
in.Value(sampler.source);
|
||||
in.Value(sampler.firstUsePc);
|
||||
in.Value(sampler.copyOf);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace ShaderRecompiler::ShaderDiskCache {
|
||||
|
||||
inline constexpr std::uint32_t FormatVersion = 26;
|
||||
inline constexpr std::uint32_t FormatVersion = 27;
|
||||
|
||||
enum class LoadStatus {
|
||||
Loaded,
|
||||
|
|
|
|||
Loading…
Reference in New Issue