mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(agc): read FMASK of up to 8 samples in every storage width
Since the synthetic FMASK mode was removed, a direct FMASK load reads the stored word and only Fmask32_S8_F8 is accepted. A load from any other FMASK format of up to 8 samples now reads the 8-, 16- or 32-bit surface and expands it in the shader to 4 bits per sample, an index past the fragment count reading 8 (unknown). One runtime image mode handles every such format, reading the format from the descriptor at run time or from a specialization constant. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
990f45e17d
commit
ad6a55121e
12 files changed
+198
-20
No files matched your search
@@ -179,12 +179,15 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
||||
Require(baseLevel <= lastLevel, "guest texture descriptor has a base mip level past its last mip level");
|
||||
|
||||
const bool fmask = ShaderRecompiler::IsFmaskTextureFormat(static_cast<ShaderRecompiler::IrBufferFormat>(format));
|
||||
const auto fmaskLayout = ShaderRecompiler::FmaskLayoutOf(static_cast<ShaderRecompiler::IrBufferFormat>(format));
|
||||
if (fmask) {
|
||||
Require(format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "guest FMASK format is not implemented");
|
||||
Require(fmaskLayout.has_value(), "guest FMASK format is not implemented");
|
||||
Require(!writeCompress && (!metaCompress || metaAddr != 0u), "FMASK CMASK compression is not implemented without readable metadata");
|
||||
Require(!metaCompress || metaPipeAligned, "FMASK CMASK metadata must be pipe aligned");
|
||||
Require(!metaCompress || format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK");
|
||||
Require((typeRaw == 9u || typeRaw == 13u) && tileModeRaw == 24u && baseLevel == 0u && lastLevel == 0u && maxMip == 0u, "FMASK reads require a single-mip 2D or 2D-array Z_64KB_X surface");
|
||||
for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented");
|
||||
const bool identity = format != static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8) && dstSelX == 4u && dstSelY == 5u && dstSelZ == 6u && dstSelW == 7u;
|
||||
for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(identity || selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented");
|
||||
}
|
||||
|
||||
const auto describe = [&] { return " (format " + std::to_string(format) + ", " + std::to_string(width) + "x" + std::to_string(height) + "x" + std::to_string(depth + 1) + ", type " + std::to_string(typeRaw) + ", tile " + std::to_string(tileModeRaw) + ", mips " + std::to_string(baseLevel) + ".." + std::to_string(lastLevel) + " of " + std::to_string(maxMip + 1) + ")"; };
|
||||
@@ -265,7 +268,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
||||
result.lastLevel = lastLevel;
|
||||
result.tileMode = tileMode;
|
||||
result.dimension = dimension;
|
||||
result.format = fmask ? static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt) : format;
|
||||
result.format = !fmask ? format : static_cast<std::uint32_t>(fmaskLayout->storageBits == 8u ? ShaderRecompiler::IrBufferFormat::Format8UInt : fmaskLayout->storageBits == 16u ? ShaderRecompiler::IrBufferFormat::Format16UInt : ShaderRecompiler::IrBufferFormat::Format32UInt);
|
||||
result.dstSelX = static_cast<std::uint8_t>(dstSelX);
|
||||
result.dstSelY = static_cast<std::uint8_t>(dstSelY);
|
||||
result.dstSelZ = static_cast<std::uint8_t>(dstSelZ);
|
||||
|
||||
@@ -184,8 +184,29 @@ void RunGuestTextureResourceTests() {
|
||||
fmaskArray.baseArray = 1u;
|
||||
const auto fmaskArrayResource = DecodeTextureResource(pack(fmaskArray));
|
||||
Require(fmaskArrayResource.format == 20u && fmaskArrayResource.baseArray == 1u && fmaskArrayResource.depthOrLastArray == 2u, "FMASK normalization changed the array view");
|
||||
for (std::uint32_t format = 156u; format <= 168u; ++format) {
|
||||
if (format == 166u) continue;
|
||||
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 8> narrowFormats{{{156u, 5u}, {157u, 5u}, {158u, 5u}, {159u, 5u}, {160u, 5u}, {161u, 5u}, {163u, 11u}, {165u, 20u}}};
|
||||
for (const auto& [format, surface] : narrowFormats) {
|
||||
auto narrow = fmask;
|
||||
narrow.format = format;
|
||||
const auto narrowResource = DecodeTextureResource(pack(narrow));
|
||||
Require(narrowResource.format == surface && narrowResource.tileMode == TextureTileMode::kZ64KBX, "FMASK " + std::to_string(format) + " did not use an integer Z surface of its storage width");
|
||||
narrow.dstSelX = 4u;
|
||||
narrow.dstSelY = 5u;
|
||||
narrow.dstSelZ = 6u;
|
||||
narrow.dstSelW = 7u;
|
||||
Require(DecodeTextureResource(pack(narrow)).format == surface, "an expanded FMASK " + std::to_string(format) + " view with identity selectors was refused");
|
||||
auto compressedNarrow = narrow;
|
||||
compressedNarrow.metaCompress = compressedNarrow.metaPipeAligned = true;
|
||||
compressedNarrow.metaAddr = 0x123456789aull;
|
||||
rejectFields(compressedNarrow, "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK");
|
||||
}
|
||||
auto identityWord = fmask;
|
||||
identityWord.dstSelX = 4u;
|
||||
identityWord.dstSelY = 5u;
|
||||
identityWord.dstSelZ = 6u;
|
||||
identityWord.dstSelW = 7u;
|
||||
rejectFields(identityWord, "FMASK destination channel selector is not implemented");
|
||||
for (const auto format : {162u, 164u, 167u, 168u}) {
|
||||
auto unsupported = fmask;
|
||||
unsupported.format = format;
|
||||
rejectFields(unsupported, "FMASK format is not implemented");
|
||||
|
||||
@@ -7577,8 +7577,19 @@ void verifyFmaskImageMode() {
|
||||
const auto modes = ResourceMaterializer::RuntimeImageModes(image);
|
||||
const auto selected = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes);
|
||||
require(modes[selected].numericClass == IrTextureNumericClass::Uint && modes[selected].packedFormat == IrBufferFormat::Invalid && !modes[selected].constantSwizzle, "FMASK did not select a bound integer image mode");
|
||||
for (std::uint32_t format = 156u; format <= 168u; ++format) {
|
||||
if (format == 166u) continue;
|
||||
for (const auto format : {156u, 157u, 158u, 159u, 160u, 161u, 163u, 165u}) {
|
||||
descriptor.dwords[1] = format << 20u;
|
||||
const auto expanded = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes);
|
||||
require(expanded != selected && modes[expanded].numericClass == IrTextureNumericClass::Uint && modes[expanded].conversionFormat == FmaskExpansion && modes[expanded].dimension == RdnaImageDimension::Dim2D, "an FMASK format of up to 8 samples did not select the FMASK expansion mode");
|
||||
}
|
||||
require(std::ranges::count_if(modes, [](const ImageResource& mode) { return mode.conversionFormat == FmaskExpansion; }) == 1, "FMASK expansion needs exactly one runtime image mode");
|
||||
for (const auto format : {161u, 166u}) {
|
||||
descriptor.dwords[1] = format << 20u;
|
||||
descriptor.dwords[3] = 0x91800005u;
|
||||
expectFailure([&] { static_cast<void>(ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)); }, "FMASK destination channel selector is not implemented", "an FMASK load selecting Y was accepted");
|
||||
}
|
||||
descriptor.dwords[3] = 0x91800004u;
|
||||
for (const auto format : {162u, 164u, 167u, 168u}) {
|
||||
descriptor.dwords[1] = format << 20u;
|
||||
expectFailure([&] { static_cast<void>(ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)); }, "FMASK format is not implemented", "an unsupported FMASK format was accepted");
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ std::array<std::uint32_t, 4> Buffer(const void* data, std::uint32_t bytes) {
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u) {
|
||||
std::array<std::uint32_t, 8> Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u, std::uint32_t format = 166u) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(Texels.data()) + pipeXor;
|
||||
return {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>(address >> 40u) | (166u << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u};
|
||||
return {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>(address >> 40u) | (format << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u};
|
||||
}
|
||||
|
||||
std::uint32_t Mapping(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) {
|
||||
@@ -56,15 +56,16 @@ std::uint64_t EquationOffset(const TextureSwizzleEquation& equation, std::uint32
|
||||
return offset;
|
||||
}
|
||||
|
||||
void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) {
|
||||
template<typename TValue>
|
||||
void FillWith(bool layered, std::uint32_t pipeXor, std::uint32_t surfaceFormat, std::uint32_t bytes, TValue valueAt) {
|
||||
auto words = Image(layered, pipeXor);
|
||||
words[1] = (words[1] & ~(0x1ffu << 20u)) | (20u << 20u);
|
||||
words[1] = (words[1] & ~(0x1ffu << 20u)) | (surfaceFormat << 20u);
|
||||
const auto resource = DecodeTextureResource(words);
|
||||
const auto geometry = DescribeSurface(resource);
|
||||
Require(geometry.guestBytes <= Texels.size(), "FMASK fixture exceeds its allocation");
|
||||
const auto& mip = geometry.mips.at(0);
|
||||
const auto block = ThinBlockLayout(resource.tileMode, 4u);
|
||||
const auto* equation = FindTextureSwizzleEquation(24u, 4u);
|
||||
const auto block = ThinBlockLayout(resource.tileMode, bytes);
|
||||
const auto* equation = FindTextureSwizzleEquation(24u, bytes);
|
||||
Require(equation != nullptr, "FMASK fixture has no Z swizzle equation");
|
||||
std::ranges::fill(Texels, std::byte{0});
|
||||
for (std::uint32_t layer = 0; layer < geometry.layers; ++layer) {
|
||||
@@ -73,14 +74,18 @@ void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) {
|
||||
auto offset = geometry.GuestLayerOffset(layer) + mip.tiledOffset;
|
||||
if (mip.tail) offset += EquationOffset(*equation, x + mip.tailX, y + mip.tailY, layer) ^ pipeXor;
|
||||
else offset += (static_cast<std::uint64_t>(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + (EquationOffset(*equation, x, y, layer) ^ pipeXor);
|
||||
const auto value = Mapping(x, y, layer, round);
|
||||
Require(offset + sizeof(value) <= Texels.size(), "FMASK fixture addresses outside its allocation");
|
||||
std::memcpy(Texels.data() + offset, &value, sizeof(value));
|
||||
const std::uint32_t value = valueAt(x, y, layer);
|
||||
Require(offset + bytes <= Texels.size(), "FMASK fixture addresses outside its allocation");
|
||||
std::memcpy(Texels.data() + offset, &value, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) {
|
||||
FillWith(layered, pipeXor, 20u, 4u, [round](std::uint32_t x, std::uint32_t y, std::uint32_t layer) { return Mapping(x, y, layer, round); });
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::span<const std::uint32_t> image, bool registration) {
|
||||
std::vector<std::uint32_t> userData(16u);
|
||||
const auto input = Buffer(Input.data(), sizeof(Input));
|
||||
@@ -140,6 +145,68 @@ void Run(AgcDriver::VulkanDevice& device, bool layered, bool registration, std::
|
||||
}
|
||||
}
|
||||
|
||||
struct NarrowFmask {
|
||||
std::uint32_t format;
|
||||
std::uint32_t surfaceFormat;
|
||||
std::uint32_t bytes;
|
||||
std::uint32_t samples;
|
||||
std::uint32_t fragments;
|
||||
std::uint32_t sampleBits;
|
||||
std::uint32_t identity;
|
||||
std::uint32_t expandedIdentity;
|
||||
};
|
||||
|
||||
constexpr std::array<NarrowFmask, 8> NarrowFormats{{
|
||||
{156u, 5u, 1u, 2u, 1u, 1u, 0x02u, 0x80u},
|
||||
{157u, 5u, 1u, 4u, 1u, 1u, 0x0eu, 0x8880u},
|
||||
{158u, 5u, 1u, 8u, 1u, 1u, 0xfeu, 0x88888880u},
|
||||
{159u, 5u, 1u, 2u, 2u, 1u, 0x02u, 0x10u},
|
||||
{160u, 5u, 1u, 4u, 2u, 2u, 0xa4u, 0x8810u},
|
||||
{161u, 5u, 1u, 4u, 4u, 2u, 0xe4u, 0x3210u},
|
||||
{163u, 11u, 2u, 8u, 2u, 2u, 0xaaa4u, 0x88888810u},
|
||||
{165u, 20u, 4u, 8u, 4u, 4u, 0x44443210u, 0x88883210u},
|
||||
}};
|
||||
|
||||
std::uint32_t Stored(const NarrowFmask& fmask, std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) {
|
||||
if (round == 0u) return fmask.identity;
|
||||
const auto value = (x * 0x9e3779b1u) ^ (y * 0x85ebca77u) ^ (layer * 0xc2b2ae3du) ^ (fmask.format * 0x27d4eb2fu);
|
||||
return fmask.bytes == 4u ? value : value & ((1u << (fmask.bytes * 8u)) - 1u);
|
||||
}
|
||||
|
||||
std::uint32_t Expanded(const NarrowFmask& fmask, std::uint32_t stored) {
|
||||
std::uint32_t expanded = 0;
|
||||
for (std::uint32_t sample = 0; sample < fmask.samples; ++sample) {
|
||||
const auto fragment = (stored >> (sample * fmask.sampleBits)) & ((1u << fmask.sampleBits) - 1u);
|
||||
expanded |= (fragment >= fmask.fragments ? 8u : fragment) << (sample * 4u);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
void RunNarrow(AgcDriver::VulkanDevice& device, const NarrowFmask& fmask, bool layered, bool registration) {
|
||||
auto code = Code;
|
||||
if (layered) code[4] = 0xf0001f28u;
|
||||
Require(Expanded(fmask, fmask.identity) == fmask.expandedIdentity, "FMASK " + std::to_string(fmask.format) + " reference does not expand PAL's identity value");
|
||||
for (std::uint32_t round = 0; round < 2u; ++round) {
|
||||
FillWith(layered, 0u, fmask.surfaceFormat, fmask.bytes, [&](std::uint32_t x, std::uint32_t y, std::uint32_t layer) { return Stored(fmask, x, y, layer, round); });
|
||||
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
|
||||
Input[lane * 4u] = (lane * 37u + 11u) % Width;
|
||||
Input[lane * 4u + 1u] = (lane * 23u + 5u) % Height;
|
||||
Input[lane * 4u + 2u] = layered ? lane & 1u : 0u;
|
||||
Input[lane * 4u + 3u] = 0u;
|
||||
}
|
||||
Output.fill(Sentinel);
|
||||
const auto shader = Compile(device, code, Image(layered, 0u, 4u, fmask.format), registration);
|
||||
device.Dispatch(shader, 1u, 1u, 1u);
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
|
||||
const auto stored = Stored(fmask, Input[lane * 4u], Input[lane * 4u + 1u], layered ? Input[lane * 4u + 2u] + 1u : 0u, round);
|
||||
const std::array expected{Expanded(fmask, stored), 0u, 0u, 0u};
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) Require(Output[lane * 4u + component] == expected[component], "FMASK " + std::to_string(fmask.format) + " load did not expand its mapping: round " + std::to_string(round) + ", lane " + std::to_string(lane) + ", component " + std::to_string(component) + ", read " + std::to_string(Output[lane * 4u + component]) + ", expected " + std::to_string(expected[component]));
|
||||
}
|
||||
Require(std::all_of(Output.begin() + Threads * 4u, Output.end(), [](auto value) { return value == Sentinel; }), "FMASK load wrote outside its output");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -149,9 +216,10 @@ int main() {
|
||||
for (const bool registration : {false, true}) {
|
||||
for (const bool layered : {false, true}) {
|
||||
for (const auto pipeXor : {0u, 0xf00u}) Run(*device, layered, registration, pipeXor);
|
||||
for (const auto& fmask : NarrowFormats) RunNarrow(*device, fmask, layered, registration);
|
||||
}
|
||||
}
|
||||
std::cout << "FMASK image reads passed: 40 dispatches, 5120 component checks, 8 compression refusals\n";
|
||||
std::cout << "FMASK image reads passed: 104 dispatches, 13312 component checks, 8 compression refusals\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
|
||||
@@ -249,6 +249,7 @@ function(add_shader_recompiler target runtimeTarget)
|
||||
ImageTableAbi::PoisonCode
|
||||
ImageTableAbi::PoisonReasonName
|
||||
ImageTableAbi::TableHeader
|
||||
FmaskLayoutOf
|
||||
IsFmaskTextureFormat
|
||||
PipelineSpecialization::IsWorkgroupOffset
|
||||
PipelineSpecialization::WorkgroupOffsetBase
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Optimization/DescriptorBindingBuilder.hpp"
|
||||
#include "Optimization/ResourceMaterializer.hpp"
|
||||
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
|
||||
#include "SpirvBackend/SpirvEmitterHelpers.hpp"
|
||||
#include "SpirvBackend/SpirvBufferFormat.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
@@ -355,6 +356,8 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
|
||||
}
|
||||
for (std::uint32_t index = 0; index < info.images.size() && !registrationModules; ++index) {
|
||||
if (info.images[index].table != NoTable) continue;
|
||||
const auto& modes = info.runtimeImageModes.at(index);
|
||||
if (std::ranges::any_of(modes, [](const ImageResource& mode) { return mode.conversionFormat == FmaskExpansion; })) plan.specialization.push_back({PipelineSpecialization::FmaskFormatBase + index, modes.at(imageModes[index]).conversionFormat == FmaskExpansion ? (snapshot.images.at(index).dwords[1] >> 20u) & 0x1ffu : 0u});
|
||||
const auto first = PipelineSpecialization::ImageBase + index * PipelineSpecialization::ImageWords;
|
||||
plan.specialization.push_back({first, imageModes[index]});
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({first + 1u + component, (snapshot.images.at(index).dwords[3] >> (component * 3u)) & 7u});
|
||||
|
||||
@@ -232,9 +232,14 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag
|
||||
if (decoded.fmask && (storage || base.depthCompare || table)) {
|
||||
return rejectImage(PoisonReason::Fmask, "FMASK requires a direct sampled image load");
|
||||
}
|
||||
if (decoded.fmask && format != IrBufferFormat::Fmask32_S8_F8) return rejectImage(PoisonReason::Fmask, "FMASK format is not implemented");
|
||||
if (decoded.fmask && !FmaskLayoutOf(format).has_value()) return rejectImage(PoisonReason::Fmask, "FMASK format is not implemented");
|
||||
if (decoded.fmask && ((descriptor.dwords[6] & (1u << 20u)) != 0u || ((descriptor.dwords[6] & (1u << 21u)) != 0u && (descriptor.dwords[6] >> 24u) == 0u && descriptor.dwords[7] == 0u))) return rejectImage(PoisonReason::Fmask, "FMASK CMASK compression is not implemented without readable metadata");
|
||||
if (decoded.fmask && (descriptor.dwords[6] & (1u << 21u)) != 0u && format != IrBufferFormat::Fmask32_S8_F8) return rejectImage(PoisonReason::Fmask, "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK");
|
||||
if (decoded.fmask && (base.packed || base.byElements != 0u)) return rejectImage(PoisonReason::Fmask, "packed FMASK image reads are not implemented");
|
||||
for (std::uint32_t component = 0; decoded.fmask && component < 4u; ++component) {
|
||||
const auto selector = (descriptorImageSwizzle(descriptor) >> (component * 3u)) & 7u;
|
||||
if (selector != 0u && selector != 1u && selector != 4u) return rejectImage(PoisonReason::Fmask, "FMASK destination channel selector is not implemented");
|
||||
}
|
||||
if (base.packed) {
|
||||
if (table || (!storage && descriptorImageSwizzle(descriptor) != ShaderImageIdentitySwizzle)) {
|
||||
return rejectImage(PoisonReason::InvalidFormat, "packed image access requires a direct image, with identity swizzle when sampled");
|
||||
@@ -271,6 +276,7 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag
|
||||
if (((descriptorImageSwizzle(descriptor) >> (component * 3u)) & 7u) == 7u) return rejectImage(PoisonReason::Conversion, "selects a channel the converted image format does not have");
|
||||
}
|
||||
}
|
||||
if (decoded.fmask && format != IrBufferFormat::Fmask32_S8_F8) decoded.conversionFormat = FmaskExpansion;
|
||||
decoded.srgbDecode = !storage && !base.depthCompare && (srgbDecodeFormats & SrgbDecodeBit(format)) != 0u;
|
||||
if (decoded.srgbDecode && !base.srgbDecodeCompatible) {
|
||||
return rejectImage(PoisonReason::SrgbUnsupported, "samples or gathers an sRGB image the device cannot sample, which is not implemented");
|
||||
@@ -1882,7 +1888,7 @@ std::vector<ImageResource> ResourceMaterializer::RuntimeImageModes(const ImageRe
|
||||
mode.shaderSwizzle = ShaderImageIdentitySwizzle;
|
||||
if (conversion == IrBufferFormat::Format11_11_10UNorm || conversion == IrBufferFormat::Format10_11_11Float) mode.shaderSwizzle = 0x2acu;
|
||||
modes.push_back(mode);
|
||||
const bool volumeLoad = (image.dimension == RdnaImageDimension::Dim2D || image.dimension == RdnaImageDimension::Dim2DArray) && image.fmaskCompatible;
|
||||
const bool volumeLoad = (image.dimension == RdnaImageDimension::Dim2D || image.dimension == RdnaImageDimension::Dim2DArray) && image.fmaskCompatible && conversion != FmaskExpansion;
|
||||
const bool volumeStore = image.dimension == RdnaImageDimension::Dim2DArray && storage && image.written && !image.read && !image.atomic && image.mipMode != ImageMipMode::DynamicStorage;
|
||||
const bool volumePlaneBias = image.dimension == RdnaImageDimension::Dim2D && image.singleMipPlaneBiasCompatible && !storage && image.table == NoTable && conversion == IrBufferFormat::Invalid;
|
||||
const bool volumeArraySample = image.dimension == RdnaImageDimension::Dim2DArray && image.volumeArraySampleCompatible && !storage && image.table == NoTable && conversion == IrBufferFormat::Invalid;
|
||||
@@ -1982,6 +1988,7 @@ std::vector<ImageResource> ResourceMaterializer::RuntimeImageModes(const ImageRe
|
||||
append(IrTextureNumericClass::Float, IrBufferFormat::Invalid, IrBufferFormat::Invalid, true, false);
|
||||
append(IrTextureNumericClass::Float, IrBufferFormat::Invalid, IrBufferFormat::Invalid, true, true);
|
||||
}
|
||||
if (image.fmaskCompatible && !table) append(IrTextureNumericClass::Uint, FmaskExpansion, IrBufferFormat::Invalid, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ inline constexpr std::uint32_t DescriptorIndexBase = 16384u;
|
||||
inline constexpr std::uint32_t DescriptorIndexStride = 128u;
|
||||
inline constexpr std::uint32_t FoldOffsetBase = 57344u;
|
||||
inline constexpr std::uint32_t UnnormalizedBaseLevelBase = 57408u;
|
||||
inline constexpr std::uint32_t FmaskFormatBase = 57472u;
|
||||
|
||||
inline constexpr std::uint32_t DescriptorIndex(std::uint32_t binding, std::uint32_t element) {
|
||||
return DescriptorIndexBase + binding * DescriptorIndexStride + element;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "IntermediateRepresentation/IrMetadata.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
@@ -21,6 +22,17 @@ enum class ImageType : std::uint32_t {
|
||||
|
||||
[[nodiscard]] IrTextureNumericClass VertexInputNumericClass(IrBufferFormat format);
|
||||
[[nodiscard]] bool IsFmaskTextureFormat(IrBufferFormat format);
|
||||
|
||||
struct FmaskLayout {
|
||||
std::uint32_t samples;
|
||||
std::uint32_t fragments;
|
||||
std::uint32_t sampleBits;
|
||||
std::uint32_t storageBits;
|
||||
};
|
||||
|
||||
inline constexpr IrBufferFormat FmaskExpansion = IrBufferFormat::Fmask8_S2_F1;
|
||||
|
||||
[[nodiscard]] std::optional<FmaskLayout> FmaskLayoutOf(IrBufferFormat format);
|
||||
[[nodiscard]] IrTextureNumericClass SampledTextureNumericClass(IrBufferFormat format);
|
||||
[[nodiscard]] IrBufferFormat RemapTextureFormat(IrBufferFormat format);
|
||||
[[nodiscard]] IrBufferFormat RemapStorageTextureFormat(IrBufferFormat format);
|
||||
|
||||
@@ -112,6 +112,21 @@ bool IsFmaskTextureFormat(IrBufferFormat format) {
|
||||
return format >= IrBufferFormat::Fmask8_S2_F1 && format <= IrBufferFormat::Fmask64_S16_F8;
|
||||
}
|
||||
|
||||
std::optional<FmaskLayout> FmaskLayoutOf(IrBufferFormat format) {
|
||||
switch (format) {
|
||||
case IrBufferFormat::Fmask8_S2_F1: return FmaskLayout{2u, 1u, 1u, 8u};
|
||||
case IrBufferFormat::Fmask8_S4_F1: return FmaskLayout{4u, 1u, 1u, 8u};
|
||||
case IrBufferFormat::Fmask8_S8_F1: return FmaskLayout{8u, 1u, 1u, 8u};
|
||||
case IrBufferFormat::Fmask8_S2_F2: return FmaskLayout{2u, 2u, 1u, 8u};
|
||||
case IrBufferFormat::Fmask8_S4_F2: return FmaskLayout{4u, 2u, 2u, 8u};
|
||||
case IrBufferFormat::Fmask8_S4_F4: return FmaskLayout{4u, 4u, 2u, 8u};
|
||||
case IrBufferFormat::Fmask16_S8_F2: return FmaskLayout{8u, 2u, 2u, 16u};
|
||||
case IrBufferFormat::Fmask32_S8_F4: return FmaskLayout{8u, 4u, 4u, 32u};
|
||||
case IrBufferFormat::Fmask32_S8_F8: return FmaskLayout{8u, 8u, 4u, 32u};
|
||||
default: return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
IrTextureNumericClass SampledTextureNumericClass(IrBufferFormat format) {
|
||||
const FormatInfo* info = FindFormatInfo(format);
|
||||
if (info == nullptr || !info->sampled) {
|
||||
|
||||
@@ -558,11 +558,47 @@ void RequireConvertedUnormAccess(SpirvValueEmitContext& ctx, const ImageEmitAcce
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t ExpandFmaskTexel(SpirvEmitterState& state, const ImageEmitAccess& access, std::uint32_t texel) {
|
||||
const auto resource = access.mem.resource;
|
||||
const auto u32 = TypeU32(state);
|
||||
const auto format = state.registrationModules
|
||||
? Binary(state, spv::OpBitwiseAnd, u32, Binary(state, spv::OpShiftRightLogical, u32, RuntimeImageDword(state, resource, offsetof(RuntimeAbi::ResourceMetadata, descriptor) / sizeof(std::uint32_t) + 1u), ConstantU32(state, 20u)), ConstantU32(state, 0x1ffu))
|
||||
: state.module.SpecializationConstant(u32, PipelineSpecialization::FmaskFormatBase + resource, 0u);
|
||||
auto layout = ConstantU32(state, 0u);
|
||||
for (auto value = static_cast<std::uint32_t>(IrBufferFormat::Fmask8_S2_F1); value <= static_cast<std::uint32_t>(IrBufferFormat::Fmask64_S16_F8); ++value) {
|
||||
const auto fmask = FmaskLayoutOf(static_cast<IrBufferFormat>(value));
|
||||
if (!fmask.has_value()) continue;
|
||||
layout = Select(state, u32, Binary(state, spv::OpIEqual, TypeBool(state), format, ConstantU32(state, value)), ConstantU32(state, fmask->sampleBits | (fmask->samples << 8u) | (fmask->fragments << 16u)), layout);
|
||||
}
|
||||
const auto sampleBits = Binary(state, spv::OpBitwiseAnd, u32, layout, ConstantU32(state, 0xffu));
|
||||
const auto samples = Binary(state, spv::OpBitwiseAnd, u32, Binary(state, spv::OpShiftRightLogical, u32, layout, ConstantU32(state, 8u)), ConstantU32(state, 0xffu));
|
||||
const auto fragments = Binary(state, spv::OpShiftRightLogical, u32, layout, ConstantU32(state, 16u));
|
||||
const auto packed = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeExtract, u32, packed, texel, 0u);
|
||||
auto expanded = ConstantU32(state, 0u);
|
||||
for (std::uint32_t sample = 0; sample < 8u; ++sample) {
|
||||
const auto field = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitFieldUExtract, u32, field, packed, Binary(state, spv::OpIMul, u32, sampleBits, ConstantU32(state, sample)), sampleBits);
|
||||
const auto fragment = Select(state, u32, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), field, fragments), ConstantU32(state, 8u), field);
|
||||
const auto nibble = Select(state, u32, Binary(state, spv::OpULessThan, TypeBool(state), ConstantU32(state, sample), samples), fragment, ConstantU32(state, 0u));
|
||||
expanded = Binary(state, spv::OpBitwiseOr, u32, expanded, Binary(state, spv::OpShiftLeftLogical, u32, nibble, ConstantU32(state, sample * 4u)));
|
||||
}
|
||||
const std::array<std::uint32_t, 4> components{expanded, ConstantU32(state, 0u), ConstantU32(state, 0u), ConstantU32(state, 0u)};
|
||||
std::uint32_t selected[4] = {};
|
||||
for (std::uint32_t component = 0; component < 4u; component++) selected[component] = RuntimeSwizzledComponent(state, RuntimeImageSwizzle(state, resource, component), components);
|
||||
const auto result = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), result, selected[0], selected[1], selected[2], selected[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint32_t UnpackImageTexel(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t texel) {
|
||||
auto& state = ctx.state;
|
||||
if (access.image.srgbDecode) {
|
||||
return DecodeSrgbTexel(state, texel);
|
||||
}
|
||||
if (access.image.conversionFormat == FmaskExpansion) {
|
||||
return ExpandFmaskTexel(state, access, texel);
|
||||
}
|
||||
const auto info = ImageConversionFormat(access.image);
|
||||
if (info.format == IrBufferFormat::Invalid) {
|
||||
return texel;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) whose key is neither a V#-relative scalar read nor a value the SRT walker evaluates (for example a shifted or masked runtime-offset V# read, a per-lane value made uniform by `v_readfirstlane`, or a loop-carried phi) binds every record of the snapshot domain instead of the selected ones. So does a table read at `(key << n) & mask` with such a key: a key domain would select records by the unmasked key, so that key is not narrowed. A key the walker evaluates selects its one record instead, unless the shader may write the memory the key is read from at run time (the same buffer, or any memory when the read or a write goes through a pointer or a GPU-only descriptor); then the column binds the whole domain too. A write to the key's memory through another V# over the same bytes is not detected. A shader whose selected records would pass 64 images or 64 sampled image/sampler pairs binds, for the heaps with the most such reads, the union of their keys in one table instead.
|
||||
- [Image table pointer indexing](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) propagates 32-bit value bounds through `ReadFirstLane` and `SelectU32` so waterfall key masks keep their bit domain. The SRT walker validates runtime select conditions whose active lane mask implies the condition through `LogicalAnd` conjunctions, assuming unselected inactive lanes need not be uniform across the full wave. The same assumption lets the two `s_load`s of one T# record match when one offset is added before `v_readfirstlane` and the other after (`ReadFirstLane(Select(mask, (key << 5) + 16, old), mask)` against `ReadFirstLane(key, mask) << 5`): with no active lane the hardware reads lane 0 of the inactive arm, and the table still binds the whole record.
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) T# of an MSAA or FMASK image, or of a color format under a depth compare, faults when selected: tables have no multisampled descriptors and no emulated color compare.
|
||||
- Direct [FMASK image loads](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read uncompressed `Fmask32_S8_F8` through a single-mip 2D or 2D-array `R32_UINT` snapshot with `Z_64KB_X` addressing. Destination selectors may name X, zero or one. Other FMASK formats, CMASK compression, packed accesses and other layouts throw. The layout follows PAL's [FMASK-to-surface conversion](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L877) and [32-bit view selectors](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9Device.cpp#L4973). Native RDNA/PS5 image-load results and title rendering are unmeasured; compressed color sampling remains unsupported.
|
||||
- Direct [FMASK image loads](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) of 16 samples, CMASK compression (bar the 8-fragment native owner path), packed accesses and other layouts throw. Uncompressed FMASK of up to 8 samples reads a single-mip 2D or 2D-array 8-, 16- or 32-bit `UINT` snapshot with `Z_64KB_X` addressing; selectors may name X, zero or one. `Fmask32_S8_F8` returns the stored word; others expand to 4 bits per sample, an index at or past the fragment count reading 8 (unknown), samples past the count 0. Layouts follow PAL's [expanded values](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9MaskRam.cpp#L5005) and [FMASK surfaces](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L877). Console results, unused nibbles included, are unmeasured; compressed color sampling is unsupported.
|
||||
- A [DIM 2D read of a 2D MSAA T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) returns sample 0, which is not measured on the console's gfx1013, at an implicit LOD above 0, at an explicit one for texels over 16 bits except formats 22 and 56 in SW_64KB_R_X, or for other formats and swizzle modes; 64- and 128-bit SW_64KB_Z_X texels are not covered: gfx1031 does not address their samples. Such a read is an `image_load` without a mip, `image_sample`, `image_sample_lz` or `image_sample_l` of a direct sampled image; other accesses (gathers, bias, gradients, offsets, LOD clamps, depth compares, `image_load_mip`, `image_get_resinfo`, `image_get_lod`, PCK, BY2/BY4) throw, as does a depth-bits T# (16_UINT, 32_UINT or 32_SINT in a Z swizzle mode). On gfx1031 (RX 6700 XT, HIP), such reads return sample 0 at 2x, 4x and 8x for 8- to 128-bit texels in SW_64KB_R_X and 8- to 32-bit ones in SW_64KB_Z_X (formats 1, 5, 7, 11, 13, 14, 18, 22, 29, 56, 60, 64, 71, 77, 133).
|
||||
- `image_store`, image atomics, PCK2/PCK4 and MIMG BY2/BY4 through an [image table](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) T# fault (`unsupported operation`) when an active lane executes them; the store is dropped and the load reads zeros. A T# whose words the SRT walker evaluates is bound directly instead, for every operation of the shader on it (`DirectImageHandles`), so only T#s keyed per lane or by a value the walker cannot evaluate reach a table with these operations; such a direct T# is read when the work is recorded, so a key its own dispatch writes is read before that write.
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) S# with a min or max reduction or FORCE_UNNORMALIZED faults when selected, as does an integer, converted or depth-bits table T# paired with a direct min or max S# that filters between texels.
|
||||
|
||||
Reference in new issue
Block a user