Merge pull request #2132 from Raghav314/fix/agc-msaa-fill-clear
fix(agc): clear a multisampled target on a uniform fill of its color memory
This commit is contained in:
commit
226b40db0f
|
|
@ -6,6 +6,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
|
@ -30,6 +31,23 @@ private:
|
|||
std::string refusal;
|
||||
};
|
||||
|
||||
class MultisampledColorFill {
|
||||
public:
|
||||
explicit MultisampledColorFill(const ColorTarget& target);
|
||||
void NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern);
|
||||
std::optional<std::uint32_t> TakeFill();
|
||||
void Discard();
|
||||
|
||||
private:
|
||||
std::uint64_t address = 0;
|
||||
std::uint64_t bytes = 0;
|
||||
std::optional<std::uint32_t> filled;
|
||||
std::string refusal;
|
||||
};
|
||||
|
||||
std::uint64_t MultisampledColorBytes(const ColorTarget& target);
|
||||
bool MultisampledFillClearColor(const ColorTarget& target, std::uint32_t fill, VkClearColorValue& clear);
|
||||
bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colorFill, const ColorTarget& use, VkClearColorValue& clear);
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target);
|
||||
void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, StorageTexture& destination, VkImageView destinationView);
|
||||
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
|
@ -44,7 +46,7 @@ void recordCommands(const Context& context, Record&& record) {
|
|||
|
||||
class MultisampleTarget {
|
||||
public:
|
||||
MultisampleTarget(const Context& context, const ColorTarget& target) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes) {
|
||||
MultisampleTarget(const Context& context, const ColorTarget& target) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) {
|
||||
this->context.bufferPool.reset();
|
||||
const auto samples = static_cast<VkSampleCountFlagBits>(target.samples);
|
||||
Require((context.limits.framebufferColorSampleCounts & samples) != 0, "the device cannot render color with the target's sample count");
|
||||
|
|
@ -106,8 +108,8 @@ public:
|
|||
MultisampleTarget& operator=(const MultisampleTarget&) = delete;
|
||||
|
||||
void ApplyFastClear(const ColorTarget& use) {
|
||||
if (!cmask.TakeClear()) return;
|
||||
const auto clear = fastClearColor(use);
|
||||
VkClearColorValue clear{};
|
||||
if (!TakeMultisampledClear(cmask, colorFill, use, clear)) return;
|
||||
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
|
@ -157,6 +159,7 @@ public:
|
|||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkRenderPass resolvePass = VK_NULL_HANDLE;
|
||||
MultisampledCmask cmask;
|
||||
MultisampledColorFill colorFill;
|
||||
|
||||
private:
|
||||
void release() noexcept {
|
||||
|
|
@ -282,6 +285,71 @@ bool MultisampledCmask::TakeClear() {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t MultisampledColorBytes(const ColorTarget& target) {
|
||||
const auto blockLog2 = target.tileMode == ColorTileMode::Standard4KB ? 12u : target.tileMode == ColorTileMode::Standard64KB || target.tileMode == ColorTileMode::RenderTarget ? 16u : 0u;
|
||||
if (blockLog2 == 0 || !std::has_single_bit(target.elementBytes) || !std::has_single_bit(target.samples)) return 0;
|
||||
const auto samplesLog2 = static_cast<std::uint32_t>(std::countr_zero(target.samples));
|
||||
const auto elementsLog2 = blockLog2 - static_cast<std::uint32_t>(std::countr_zero(target.elementBytes)) - samplesLog2;
|
||||
const auto widthLog2 = (elementsLog2 + ((samplesLog2 & 1u) == 0 ? 1u : 0u)) / 2u;
|
||||
const std::uint64_t blockWidth = std::uint64_t{1} << widthLog2;
|
||||
const std::uint64_t blockHeight = std::uint64_t{1} << (elementsLog2 - widthLog2);
|
||||
return ((target.extent.width + blockWidth - 1) / blockWidth) * ((target.extent.height + blockHeight - 1) / blockHeight) << blockLog2;
|
||||
}
|
||||
|
||||
bool MultisampledFillClearColor(const ColorTarget& target, std::uint32_t fill, VkClearColorValue& clear) {
|
||||
const std::array<std::uint32_t, 4> pattern{fill, fill, fill, fill};
|
||||
VkFormat unorm = VK_FORMAT_UNDEFINED;
|
||||
switch (target.format) {
|
||||
case VK_FORMAT_R8G8B8A8_SRGB: unorm = VK_FORMAT_R8G8B8A8_UNORM; break;
|
||||
case VK_FORMAT_B8G8R8A8_SRGB: unorm = VK_FORMAT_B8G8R8A8_UNORM; break;
|
||||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: unorm = VK_FORMAT_A8B8G8R8_UNORM_PACK32; break;
|
||||
default: return ClearColorForTexel(target.format, target.elementBytes, pattern, clear);
|
||||
}
|
||||
if (!ClearColorForTexel(unorm, target.elementBytes, pattern, clear)) return false;
|
||||
return std::all_of(clear.float32, clear.float32 + 3, [](float value) { return value == 0.0f || value == 1.0f; });
|
||||
}
|
||||
|
||||
MultisampledColorFill::MultisampledColorFill(const ColorTarget& target) : address(target.address), bytes(MultisampledColorBytes(target)) {}
|
||||
|
||||
void MultisampledColorFill::NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern) {
|
||||
if (bytes == 0 ? fillAddress > address || address >= fillAddress + fillBytes : fillAddress >= address + bytes || address >= fillAddress + fillBytes) return;
|
||||
char text[192];
|
||||
if (bytes == 0) {
|
||||
std::snprintf(text, sizeof(text), "a fill of 0x%llx+0x%zx covers the color memory at 0x%llx of a multisampled target whose footprint is not modeled", static_cast<unsigned long long>(fillAddress), fillBytes, static_cast<unsigned long long>(address));
|
||||
refusal = text;
|
||||
return;
|
||||
}
|
||||
if (fillAddress > address || fillAddress + fillBytes < address + bytes) {
|
||||
std::snprintf(text, sizeof(text), "a fill of 0x%llx+0x%zx partly covers the color memory 0x%llx+0x%llx of a multisampled target, leaving its samples mixed", static_cast<unsigned long long>(fillAddress), fillBytes, static_cast<unsigned long long>(address), static_cast<unsigned long long>(bytes));
|
||||
refusal = text;
|
||||
return;
|
||||
}
|
||||
refusal.clear();
|
||||
filled = pattern;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> MultisampledColorFill::TakeFill() {
|
||||
Require(refusal.empty(), refusal);
|
||||
return std::exchange(filled, std::nullopt);
|
||||
}
|
||||
|
||||
void MultisampledColorFill::Discard() {
|
||||
refusal.clear();
|
||||
filled.reset();
|
||||
}
|
||||
|
||||
bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colorFill, const ColorTarget& use, VkClearColorValue& clear) {
|
||||
if (cmask.TakeClear()) {
|
||||
colorFill.Discard();
|
||||
clear = fastClearColor(use);
|
||||
return true;
|
||||
}
|
||||
const auto fill = colorFill.TakeFill();
|
||||
if (!fill) return false;
|
||||
Require(MultisampledFillClearColor(use, *fill, clear), "a fill over the color memory of a multisampled target has no exact clear value in its format");
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target) {
|
||||
std::lock_guard lock(targetsMutex());
|
||||
return acquire(context, target).view;
|
||||
|
|
@ -312,6 +380,7 @@ void ResolveMultisampleTarget(const Context& context, const ColorTarget& source,
|
|||
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern) {
|
||||
std::lock_guard lock(targetsMutex());
|
||||
for (const auto& target : targets()) {
|
||||
target->colorFill.NoteFill(address, bytes, pattern);
|
||||
const auto before = target->cmask.Keys();
|
||||
target->cmask.NoteFill(address, bytes, pattern);
|
||||
if (before == DccKeys::Clear0000 || target->cmask.Keys() != DccKeys::Clear0000) continue;
|
||||
|
|
|
|||
|
|
@ -2158,6 +2158,78 @@ void multisampleTests() {
|
|||
MultisampledCmask unaddressed;
|
||||
unaddressed.NoteFill(0, 0x1000, 0);
|
||||
Require(!unaddressed.TakeClear(), "a target without a CMASK cleared");
|
||||
|
||||
using AgcDriver::Graphics::ColorTarget;
|
||||
using AgcDriver::Graphics::ColorTileMode;
|
||||
using AgcDriver::Graphics::MultisampledColorBytes;
|
||||
using AgcDriver::Graphics::MultisampledColorFill;
|
||||
using AgcDriver::Graphics::MultisampledFillClearColor;
|
||||
ColorTarget halfFloat{};
|
||||
halfFloat.address = 0x40000000;
|
||||
halfFloat.extent = {960, 540};
|
||||
halfFloat.format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
halfFloat.tileMode = ColorTileMode::RenderTarget;
|
||||
halfFloat.elementBytes = 8;
|
||||
halfFloat.samples = 4;
|
||||
Require(MultisampledColorBytes(halfFloat) == 0xff0000, "a 4x 960x540 8-byte target is not 64x32 pixels per 64 KiB block");
|
||||
auto srgb = halfFloat;
|
||||
srgb.extent = {1536, 1536};
|
||||
srgb.format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
srgb.elementBytes = 4;
|
||||
Require(MultisampledColorBytes(srgb) == 0x2400000, "a 4x 1536x1536 4-byte target is not 64x64 pixels per 64 KiB block");
|
||||
auto narrow = srgb;
|
||||
narrow.extent = {130, 60};
|
||||
narrow.samples = 2;
|
||||
Require(MultisampledColorBytes(narrow) == 0x30000, "a 2x 4-byte target is not 64x128 pixels per 64 KiB block");
|
||||
narrow.samples = 8;
|
||||
Require(MultisampledColorBytes(narrow) == 0x50000, "an 8x 4-byte target is not 32x64 pixels per 64 KiB block");
|
||||
srgb.extent = {100, 100};
|
||||
Require(MultisampledColorBytes(srgb) == 0x40000, "a 4x 100x100 4-byte target does not span 2x2 64 KiB blocks");
|
||||
srgb.tileMode = ColorTileMode::Standard4KB;
|
||||
Require(MultisampledColorBytes(srgb) == 0x31000, "a 4x 4-byte target is not 16x16 pixels per 4 KiB block");
|
||||
srgb.tileMode = ColorTileMode::Linear;
|
||||
Require(MultisampledColorBytes(srgb) == 0, "a linear multisampled target has a footprint");
|
||||
|
||||
MultisampledColorFill fill(halfFloat);
|
||||
Require(!fill.TakeFill(), "an unfilled target cleared");
|
||||
fill.NoteFill(0x40000000, 0xff0000, 0);
|
||||
const auto zero = fill.TakeFill();
|
||||
Require(zero && *zero == 0 && !fill.TakeFill(), "a fill of the whole color memory did not clear once");
|
||||
fill.NoteFill(0x40ff0000, 0x10000, 0);
|
||||
fill.NoteFill(0x3fff0000, 0x10000, 0);
|
||||
Require(!fill.TakeFill(), "a fill next to the color memory cleared it");
|
||||
fill.NoteFill(0x40000000, 0xfe0000, 0);
|
||||
expectFailure([&] { fill.TakeFill(); }, "partly covers");
|
||||
fill.NoteFill(0x3fff0000, 0x1010000, 0x3c003c00u);
|
||||
const auto one = fill.TakeFill();
|
||||
Require(one && *one == 0x3c003c00u, "a fill over and around the color memory did not clear it");
|
||||
MultisampledColorFill linear(srgb);
|
||||
linear.NoteFill(0x40001000, 0x1000, 0);
|
||||
Require(!linear.TakeFill(), "a fill past the start of a target with no modeled footprint was taken");
|
||||
linear.NoteFill(0x40000000, 0x1000, 0);
|
||||
expectFailure([&] { linear.TakeFill(); }, "footprint is not modeled");
|
||||
|
||||
VkClearColorValue clear{};
|
||||
Require(MultisampledFillClearColor(halfFloat, 0x3c003c00u, clear) && clear.float32[0] == 1.0f && clear.float32[3] == 1.0f, "a half-float fill of 1.0 did not clear to 1.0");
|
||||
Require(MultisampledFillClearColor(srgb, 0, clear) && clear.float32[0] == 0.0f && clear.float32[3] == 0.0f, "an sRGB fill of 0 did not clear to 0");
|
||||
Require(MultisampledFillClearColor(srgb, 0x80ffffffu, clear) && clear.float32[2] == 1.0f && clear.float32[3] == 128.0f / 255.0f, "an sRGB fill of white with linear alpha 0x80 did not clear to it");
|
||||
Require(!MultisampledFillClearColor(srgb, 0x00000080u, clear), "an sRGB fill whose color decodes inexactly cleared");
|
||||
|
||||
using AgcDriver::Graphics::TakeMultisampledClear;
|
||||
halfFloat.clearWords = {0x3c003c00u, 0x3c003c00u};
|
||||
MultisampledCmask clearedCmask(0x50000000, 0x1000);
|
||||
MultisampledColorFill pending(halfFloat);
|
||||
Require(!TakeMultisampledClear(clearedCmask, pending, halfFloat, clear), "a target with no CMASK clear and no fill cleared");
|
||||
pending.NoteFill(0x40000000, 0xff0000, 0);
|
||||
Require(TakeMultisampledClear(clearedCmask, pending, halfFloat, clear) && clear.float32[0] == 0.0f, "a fill of the whole color memory did not clear to its value");
|
||||
pending.NoteFill(0x40000000, 0xfe0000, 0);
|
||||
expectFailure([&] { TakeMultisampledClear(clearedCmask, pending, halfFloat, clear); }, "partly covers");
|
||||
clearedCmask.NoteFill(0x50000000, 0x1000, 0);
|
||||
Require(TakeMultisampledClear(clearedCmask, pending, halfFloat, clear) && clear.float32[0] == 1.0f, "a pending CMASK clear did not take precedence over a partial fill of the color memory");
|
||||
Require(!TakeMultisampledClear(clearedCmask, pending, halfFloat, clear), "a partial fill of the color memory outlived the CMASK clear over it");
|
||||
pending.NoteFill(0x40000000, 0xff0000, 0);
|
||||
clearedCmask.NoteFill(0x50000000, 0x1000, 0);
|
||||
Require(TakeMultisampledClear(clearedCmask, pending, halfFloat, clear) && clear.float32[0] == 1.0f && !TakeMultisampledClear(clearedCmask, pending, halfFloat, clear), "a pending CMASK clear and a whole fill did not clear once to the CMASK clear color");
|
||||
}
|
||||
|
||||
void DepthClipTests() {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
- [Cube texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) with equal `BASE_ARRAY` and `DEPTH` expose six faces starting at `BASE_ARRAY`. Measured with `image_load`, `image_sample` and `image_get_resinfo` on RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces or incomplete cube arrays still throw: hardware load bounds and filtered-sample addressing differ. Out-of-range cube-array sampling remains unimplemented. Samplers with `DISABLE_CUBE_WRAP` are created with `VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT`, the flag RADV maps to that bit (Mesa `src/amd/vulkan/radv_sampler.c`, `src/amd/common/ac_descriptors.c`); without `VK_EXT_non_seamless_cube_map` a cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware.
|
||||
- [Multisampled CMASK](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a multisampled color target's CMASK is decoded under CB_COLOR_INFO FAST_CLEAR (bit 13, as for a single-sample target) or COMPRESSION (bit 14). Which bit a multisampled target sets is inferred: its fast clears were seen on GTA V's multisampled targets through their CMASK fills, not in a register dump. A COMPRESSION-only target's zero CMASK is not read as cleared (inferred: zero means cleared only under FAST_CLEAR), so only a fill after its first use clears it. A slice of a multisampled array is its own image at its own address with its own CB_COLOR0_CMASK (inferred from the register layout). Its CMASK is main's whole-metablock size.
|
||||
- [Multisampled CMASK state](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target has no guest texels, so its CMASK state comes from fills rather than from a read at each draw as a single-sample target's does. A fill of the whole CMASK with 0 clears the host image at the target's next use, one with 0xffffffff marks it expanded, and any other pattern, or a fill that covers only part of the CMASK, throws at the next use (inferred: main's rule that a mixed CMASK is unmodeled, applied to the one host image). A FAST_CLEAR target starts from its CMASK keys at its first use, and a COMPRESSION-only target starts expanded. Copies by DMA into the CMASK are not seen, and a later use with another CMASK address throws. The clear does not rewrite the CMASK bytes as expanded, so a new target over the same CMASK (a different extent, so a different image) seeds from them again and clears once more.
|
||||
- [Multisampled color fills](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target's samples are on the host, so a fill of its color memory is applied at its next use: a uniform fill over the whole of it clears the host image to that value, and a pending CMASK fast clear takes precedence over the fill and over a fill that would throw. Its color memory is a single mip and slice of addrlib's block dimensions for its sample count (ComputeThinBlockDimension). A fill over part of it, a fill reaching the start of a target whose footprint is not computed (linear), and a fill value with no exact clear value in the format (sRGB channels other than 0 and 1) throw at the next use. A fill before the target's first use is not seen (the host image starts at 0), nor are copies or shader writes into its color memory.
|
||||
- [Multisampled targets in CB passes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB eliminate or DCC pass over a multisampled target throws, since its texels are on the host and only a CB resolve is modeled. A CB resolve into a fast-clear destination throws: the resolve writes the destination's texels but not its CMASK, which would still read as cleared.
|
||||
- [Multisampled target guest access](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - the memory a multisampled color target throws on (CPU read, DMA copy, texture or storage image read) uses fragment-aware 64 KiB render-target blocks. [AMD Addrlib's GFX10 thin-block rule](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L1680-L1712) gives the block width and height from element bytes and fragment count; its [surface-size calculation](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L4000-L4005) rounds both extents to those blocks and multiplies by element bytes and fragments. Thus a 1920x1080 4x RGBA8 surface occupies 0x1fe0000 bytes, leaving the next allocation at +0x2000000 outside the guard. The 2x and 8x block orientations differ from treating the fragments as a larger element. Console address-layout equivalence has not been measured. Other tile modes retain the conservative `max(CB bytes * samples, width * height * element bytes * samples)` bound, which may still reject a distinct allocation. Targets are kept per device for the device's lifetime, one per surface address, format, extent and sample count, and are not evicted.
|
||||
- [CB resolve sample count](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a draw with CB_COLOR_CONTROL in resolve mode is a CB resolve when either PA_SC_AA_CONFIG or CB_COLOR0_ATTRIB.NUM_SAMPLES is multisampled, and takes its sample count from CB0. Inferred, not from documentation: PPSA36082 resolves its 4x targets with no sample count in PA_SC_AA_CONFIG, and radeonsi sets PA_SC_AA_CONFIG to 0 for a multisampled framebuffer when rasterizer multisampling and line smoothing are off.
|
||||
|
|
|
|||
Loading…
Reference in New Issue