fix(agc): read comp-to-single DCC keys of sampled textures and storage images
This commit is contained in:
parent
d53960612f
commit
2fb6dccd75
|
|
@ -57,6 +57,18 @@ DccKeys ByteKeys(std::uint8_t key) {
|
|||
}
|
||||
}
|
||||
|
||||
bool SingleKeysReadable(const GuestTextureResource& resource) {
|
||||
return KeyBytes % BytesPerElement(resource.format) == 0;
|
||||
}
|
||||
|
||||
void ExpandSingleBlocks(std::span<std::byte> bytes, std::size_t elementBytes) {
|
||||
for (std::size_t block = 0; block < bytes.size(); block += KeyBytes) {
|
||||
const auto blockBytes = std::min<std::size_t>(KeyBytes, bytes.size() - block);
|
||||
auto* first = bytes.data() + block;
|
||||
for (auto filled = std::min(elementBytes, blockBytes); filled < blockBytes; filled *= 2) std::memcpy(first + filled, first, std::min(filled, blockBytes - filled));
|
||||
}
|
||||
}
|
||||
|
||||
// APS5_PROFILE_DRAW: key scans, their bytes and time, printed as [dcc] every 10 s, with the hits of
|
||||
// the GPU-store memo below and the "uncompressed" key stores by where they were made. Flush syncs
|
||||
// stay 0 (no key read syncs the recorder); the field is kept so the line's shape does not change.
|
||||
|
|
@ -449,7 +461,7 @@ DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t gue
|
|||
memoized = false;
|
||||
if (resource.dccAddress == 0) return DccKeys::Uncompressed;
|
||||
const auto keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized);
|
||||
if (keys == DccKeys::Uncompressed) return keys;
|
||||
if (keys == DccKeys::Uncompressed || (keys == DccKeys::ClearSingle && SingleKeysReadable(resource))) return keys;
|
||||
if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented");
|
||||
std::byte probe[16]{};
|
||||
if (!IsDccClear(keys) || !FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, std::span(probe, std::min<std::size_t>(sizeof(probe), BytesPerElement(resource.format))))) {
|
||||
|
|
@ -656,13 +668,15 @@ DccKeys ProvedCurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceByt
|
|||
void ReadTextureSurface(const GuestTextureResource& resource, DccKeys keys, std::span<std::byte> bytes) {
|
||||
// Named for the [hooksync] attribution: the read goes through the flush hook.
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureRead);
|
||||
if (keys == DccKeys::Uncompressed) GuestMemory::ReadCommitted(resource.baseAddress, bytes);
|
||||
if (keys == DccKeys::Uncompressed || keys == DccKeys::ClearSingle) GuestMemory::ReadCommitted(resource.baseAddress, bytes);
|
||||
else FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, bytes);
|
||||
if (keys == DccKeys::ClearSingle) ExpandSingleBlocks(bytes, BytesPerElement(resource.format));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
DccKeys EncodedKeys(const GuestTextureResource& resource, DccKeys keys) {
|
||||
if (keys == DccKeys::ClearSingle && SingleKeysReadable(resource)) return keys;
|
||||
std::byte probe[16]{};
|
||||
if (!IsDccClear(keys) || !FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, std::span(probe, std::min<std::size_t>(sizeof(probe), BytesPerElement(resource.format))))) return DccKeys::Uncompressed;
|
||||
return keys;
|
||||
|
|
|
|||
|
|
@ -572,6 +572,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
|||
clearThroughKeys = true;
|
||||
}
|
||||
}
|
||||
if (source != nullptr && resource.dccAddress != 0 && (keys.has_value() ? *keys : scanKeys()) == DccKeys::ClearSingle) source.reset();
|
||||
|
||||
// Otherwise a surface in host-imported memory is viewed through its cached storage image (made
|
||||
// here when there is none): its refresh after a CPU or GPU write is a GPU-direct detile from the
|
||||
|
|
@ -657,7 +658,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
|||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureCompare);
|
||||
return GuestMemory::EqualsCommitted(address, it->bytes);
|
||||
};
|
||||
if (*keys != DccKeys::Uncompressed || GuestMemory::UnchangedSince(address, it->bytes.size(), it->generation) || equalsCommitted()) {
|
||||
if ((*keys != DccKeys::Uncompressed && *keys != DccKeys::ClearSingle) || GuestMemory::UnchangedSince(address, it->bytes.size(), it->generation) || equalsCommitted()) {
|
||||
it->generation = generation;
|
||||
touchTexture(cache, it);
|
||||
logLookup({it->texture.get(), resource, guestBytes, *keys, generation, nullptr});
|
||||
|
|
@ -3233,7 +3234,7 @@ std::shared_ptr<Texture> ShaderResources::fastTexture(const ImageRecord& record)
|
|||
// Under fast-clear keys the view holds only while its image's results are still pending over
|
||||
// the surface; flushed, the clear the image cannot see makes the lookup take a snapshot.
|
||||
if (keys != DccKeys::Uncompressed && StorageTexture::FindPending(address, record.guestBytes) != record.source) return nullptr;
|
||||
} else if (keys == DccKeys::Uncompressed && !GuestMemory::UnchangedSince(address, bytes, record.entryGeneration)) {
|
||||
} else if ((keys == DccKeys::Uncompressed || keys == DccKeys::ClearSingle) && !GuestMemory::UnchangedSince(address, bytes, record.entryGeneration)) {
|
||||
return nullptr;
|
||||
}
|
||||
// The entry must still be the cache's, holding this object (an evicted object is not reused: the
|
||||
|
|
|
|||
|
|
@ -1730,7 +1730,7 @@ bool StorageTexture::Refresh() {
|
|||
// drop rule. Debug aid: APS5_KEYFLIP_DROP=1 drops as before.
|
||||
static const bool keyFlipDrop = std::getenv("APS5_KEYFLIP_DROP") != nullptr;
|
||||
static const bool traceKeys = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
|
||||
const bool keyFlip = keysChanged && blockUnits && !keyFlipDrop && IsDccClear(uploadedKeys) && keys == DccKeys::Uncompressed && stampedBlocks.size() >= trackedLayers;
|
||||
const bool keyFlip = keysChanged && blockUnits && !keyFlipDrop && (IsDccClear(uploadedKeys) || uploadedKeys == DccKeys::ClearSingle) && keys == DccKeys::Uncompressed && stampedBlocks.size() >= trackedLayers;
|
||||
if (traceKeys && keysChanged) {
|
||||
std::size_t pendingUnits = 0, stampedUnits = 0;
|
||||
for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) {
|
||||
|
|
@ -1763,7 +1763,7 @@ bool StorageTexture::Refresh() {
|
|||
stored[layer] = true;
|
||||
anyStored = true;
|
||||
}
|
||||
if (anyStored && !keysChanged && descriptor.dccAddress != 0 && IsDccClear(uploadedKeys)) stored.assign(trackedLayers, true);
|
||||
if (anyStored && !keysChanged && descriptor.dccAddress != 0 && (IsDccClear(uploadedKeys) || uploadedKeys == DccKeys::ClearSingle)) stored.assign(trackedLayers, true);
|
||||
if (pendingResults) {
|
||||
static std::atomic<int> reports{0};
|
||||
if (reports.fetch_add(1) < 8) std::fprintf(stderr, "[gpu] storage image 0x%llx: guest memory changed while GPU results were pending; keeping the CPU's blocks\n", static_cast<unsigned long long>(descriptor.baseAddress));
|
||||
|
|
@ -2787,7 +2787,7 @@ bool StorageTexture::keysFillMatches(std::uint64_t address, std::size_t bytes, b
|
|||
}
|
||||
|
||||
void StorageTexture::MarkDirty() {
|
||||
if (descriptor.dccAddress != 0 && IsDccClear(uploadedKeys) && !IsDccClear(filledKeys)) {
|
||||
if (descriptor.dccAddress != 0 && (IsDccClear(uploadedKeys) || uploadedKeys == DccKeys::ClearSingle) && !IsDccClear(filledKeys)) {
|
||||
traceKeyStore("first write", descriptor, guestBytes);
|
||||
MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -96,6 +98,33 @@ void convertedDccClearTests() {
|
|||
Require(read(20, 0x40) == DccKeys::Clear0001 && read(20, 0x80) == DccKeys::Clear1110, "format 20 must keep its 0001 and 1110 DCC clear codes");
|
||||
}
|
||||
|
||||
void singleDccKeysTests() {
|
||||
alignas(256) std::array<std::uint32_t, 256> texels{};
|
||||
for (std::size_t index = 0; index < texels.size(); ++index) texels[index] = 0x01000000u + static_cast<std::uint32_t>(index);
|
||||
alignas(64) std::array<std::uint8_t, sizeof(texels) / 256> keys{};
|
||||
keys.fill(0x10);
|
||||
GuestTextureResource resource{};
|
||||
resource.baseAddress = reinterpret_cast<std::uint64_t>(texels.data());
|
||||
resource.dccAddress = reinterpret_cast<std::uint64_t>(keys.data());
|
||||
const auto read = [&](std::uint32_t format) {
|
||||
resource.format = format;
|
||||
return TextureClearKeys(resource, sizeof(texels));
|
||||
};
|
||||
const auto* original = reinterpret_cast<const std::byte*>(texels.data());
|
||||
for (const std::uint32_t format : {1u, 56u, 77u}) {
|
||||
Require(read(format) == DccKeys::ClearSingle, "comp-to-single keys of format " + std::to_string(format) + " must read as single");
|
||||
const auto elementBytes = BytesPerElement(format);
|
||||
std::vector<std::byte> surface(sizeof(texels));
|
||||
ReadTextureSurface(resource, DccKeys::ClearSingle, surface);
|
||||
bool expanded = true;
|
||||
for (std::size_t offset = 0; offset < surface.size(); ++offset) expanded = expanded && surface[offset] == original[offset / 256 * 256 + offset % elementBytes];
|
||||
Require(expanded, "a read of format " + std::to_string(format) + " under comp-to-single keys must give every 256-byte block its first element");
|
||||
}
|
||||
Require(read(74) == DccKeys::Uncompressed, "comp-to-single keys of a format whose element does not divide 256 bytes must read as stored");
|
||||
keys[1] = 0xff;
|
||||
Require(read(56) == DccKeys::Uncompressed, "comp-to-single keys mixed with uncompressed ones must read as stored");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunTextureFormatTests() {
|
||||
|
|
@ -145,4 +174,5 @@ void RunTextureFormatTests() {
|
|||
for (const std::uint32_t format : {128u, 129u, 130u, 170u, 172u, 174u, 182u}) Require(IsSrgbTextureFormat(format), "guest format " + std::to_string(format) + " must be an sRGB texture format");
|
||||
for (const std::uint32_t format : {1u, 14u, 56u, 71u, 169u, 171u, 173u, 181u}) Require(!IsSrgbTextureFormat(format), "guest format " + std::to_string(format) + " must not be an sRGB texture format");
|
||||
convertedDccClearTests();
|
||||
singleDccKeysTests();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
|
|
@ -66,6 +67,14 @@ alignas(256) constexpr std::array<std::uint32_t, 6> ClearKeysCode{
|
|||
0x34020083, 0x7e040280, 0x7e060280, 0xe0741000, 0x80000201, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> SampleSecondTexelCode{
|
||||
0x34060084, 0x7e2802ff, 0x3c400000, 0x7e2a02ff, 0x3b800000, 0xf09c0f08, 0x00610a14, 0xbf8c3f70,
|
||||
0xe0781000, 0x80000a03, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::uint32_t, 4> PointSampler{0x00000092u, 0x00fff000u, 0u, 0u};
|
||||
constexpr std::size_t SampledOutputOffset = SurfaceBytes + 8192;
|
||||
|
||||
std::uint64_t AddressOf(const void* data) {
|
||||
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
|
||||
}
|
||||
|
|
@ -309,7 +318,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
|||
const std::array<std::uint32_t, 4> pattern{word, word, word, word};
|
||||
return device.FillBuffer(AddressOf(keys + offset), bytes, pattern);
|
||||
};
|
||||
if (!fill(0, KeyBytes, 0x10)) {
|
||||
if (!fill(0, KeyBytes, 0x5a)) {
|
||||
std::puts("the key range has no host import: pending key fills are not tested");
|
||||
return;
|
||||
}
|
||||
|
|
@ -317,7 +326,9 @@ void Run(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
|||
std::puts("host imports are compared, not watched: the write-watched block runs as unwatched");
|
||||
watched = false;
|
||||
}
|
||||
readPending(DccKeys::Uncompressed, false, "a pending fill of the keys with 0x10, which is no clear code");
|
||||
readPending(DccKeys::Uncompressed, false, "a pending fill of the keys with 0x5a, which is no clear code");
|
||||
Require(fill(0, KeyBytes, 0x10), "the comp-to-single fill of the keys was not recorded");
|
||||
readPending(DccKeys::ClearSingle, true, "a pending comp-to-single fill of the keys");
|
||||
Require(fill(0, KeyBytes / 2, 0x00) && fill(KeyBytes / 2, KeyBytes / 2, 0x00), "the 0000 fills of the key halves were not recorded");
|
||||
readPending(DccKeys::Clear0000, true, "pending 0000 fills over both halves of the keys");
|
||||
Require(fill(0, KeyBytes / 2, 0x40), "the 0001 fill of the first key half was not recorded");
|
||||
|
|
@ -369,6 +380,72 @@ void RunReusedKeys(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
|||
Require(Count(texels, SurfaceBytes, 0xff) == Threads * 4u, "reused keys: the write-back lost the image's results");
|
||||
}
|
||||
|
||||
void RunSingleKeys(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
auto* texels = block;
|
||||
auto* keys = block + KeysOffset;
|
||||
const auto surface = AddressOf(texels);
|
||||
std::vector<std::uint32_t> original(SurfaceBytes / 4);
|
||||
for (std::size_t index = 0; index < original.size(); ++index) original[index] = 0x01000000u + static_cast<std::uint32_t>(index);
|
||||
std::memcpy(texels, original.data(), SurfaceBytes);
|
||||
std::memset(keys, 0x10, KeyBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
const auto texture = TextureDescriptor(texels, keys);
|
||||
std::vector<std::uint32_t> writeData(16, 0u);
|
||||
std::copy(texture.begin(), texture.end(), writeData.begin() + 4);
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Dispatch(device, WriteCode, writeData);
|
||||
const auto image = StorageTexture::FindPending(surface, SurfaceBytes);
|
||||
Require(image != nullptr && image->UploadedKeys() == DccKeys::Uncompressed, "comp-to-single keys: the first write did not store the keys uncompressed");
|
||||
RequireKeys(keys, 0xff, "comp-to-single keys: the keys after the first write");
|
||||
StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test comp-to-single keys");
|
||||
device.WaitIdle();
|
||||
const AgcDriver::Graphics::ColorTargetLayout layout(Side, Side, AgcDriver::Graphics::ColorTileMode::RenderTarget, 4);
|
||||
std::size_t wrong = 0;
|
||||
for (std::uint32_t y = 0; y < Side; ++y) {
|
||||
for (std::uint32_t x = 0; x < Side; ++x) {
|
||||
const auto offset = layout.Offset(x, y);
|
||||
std::uint32_t texel = 0;
|
||||
std::memcpy(&texel, texels + offset, 4);
|
||||
const auto expected = y == 0 && x < Threads ? 0xffffffffu : original[offset / 256 * 64];
|
||||
if (texel != expected) ++wrong;
|
||||
}
|
||||
}
|
||||
char message[256];
|
||||
std::snprintf(message, sizeof(message), "comp-to-single keys: %zu of %u written-back texels are neither the kernel's write nor the first texel of their 256-byte block", wrong, Side * Side);
|
||||
Require(wrong == 0, message);
|
||||
}
|
||||
|
||||
void RunSingleKeysSampled(AgcDriver::VulkanDevice& device, std::uint8_t* block, const char* memory) {
|
||||
auto* texels = block;
|
||||
auto* keys = block + KeysOffset;
|
||||
auto* output = reinterpret_cast<std::uint32_t*>(block + SampledOutputOffset);
|
||||
const AgcDriver::Graphics::ColorTargetLayout layout(Side, Side, AgcDriver::Graphics::ColorTileMode::RenderTarget, 4);
|
||||
const auto first = layout.Offset(1, 0) / 256 * 256;
|
||||
std::memset(texels, 0x00, SurfaceBytes);
|
||||
std::memset(texels + first, 0xff, 4);
|
||||
std::memset(keys, 0x10, KeyBytes);
|
||||
const auto texture = TextureDescriptor(texels, keys);
|
||||
const auto results = BufferDescriptor(output, Threads * 16u);
|
||||
std::vector<std::uint32_t> userData(24, 0u);
|
||||
std::copy(results.begin(), results.end(), userData.begin());
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
std::copy(PointSampler.begin(), PointSampler.end(), userData.begin() + 12);
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
const auto sample = [&](std::uint32_t expected, const char* when) {
|
||||
std::fill(output, output + Threads * 4u, 0xdeadbeefu);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Dispatch(device, SampleSecondTexelCode, userData);
|
||||
for (std::uint32_t index = 0; index < Threads * 4u; ++index) {
|
||||
char message[256];
|
||||
std::snprintf(message, sizeof(message), "comp-to-single keys sampled %s from %s memory: component %u of thread %u is 0x%08x, expected 0x%08x", when, memory, index % 4u, index / 4u, output[index], expected);
|
||||
Require(output[index] == expected, message);
|
||||
}
|
||||
};
|
||||
sample(0x3f800000u, "before the first texel of the block changed");
|
||||
std::memset(texels + first, 0x00, 4);
|
||||
sample(0u, "after the first texel of the block changed");
|
||||
}
|
||||
|
||||
void RunUntrackedReuse(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
const auto address = AddressOf(block);
|
||||
std::memset(block, 0x55, BlockBytes);
|
||||
|
|
@ -510,6 +587,12 @@ int main() {
|
|||
std::optional<GuestBlock> reused;
|
||||
reused.emplace(false);
|
||||
RunReusedKeys(*device, reused->Data());
|
||||
std::optional<GuestBlock> single;
|
||||
single.emplace(false);
|
||||
RunSingleKeys(*device, single->Data());
|
||||
std::optional<GuestBlock> sampled;
|
||||
sampled.emplace(false);
|
||||
RunSingleKeysSampled(*device, sampled->Data(), "unwatched");
|
||||
#ifdef _WIN32
|
||||
shared.emplace(true, true);
|
||||
RunSharedImport(*device, shared->Data());
|
||||
|
|
@ -518,6 +601,9 @@ int main() {
|
|||
if (AgcDriver::GuestMemory::WriteWatched()) {
|
||||
watched.emplace(true);
|
||||
Run(*device, watched->Data());
|
||||
std::optional<GuestBlock> sampledWatched;
|
||||
sampledWatched.emplace(true);
|
||||
RunSingleKeysSampled(*device, sampledWatched->Data(), "write-watched");
|
||||
#ifndef _WIN32
|
||||
window.emplace(true);
|
||||
RunImportWindow(*device, window->Data());
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
- [16-bit float display buffers](../../../core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp) (libSceAgcDriver, libSceVideoOut) - pixel formats `0xC001000622000000` and `0xC001000600000000` are taken as R16G16B16A16 and B16G16R16A16 float, eight bytes per texel, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/graphics/host_gpu/renderer/image/imageInfo.h) maps them and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoOut/VideoOutTypes.cs) names them (`Rgba16Float`, `Bgra16Float`); PPSA11386 registers 3840x2160 tiled buffers of the first. The tiled layout is assumed to be the 8-byte SW_64KB_R_X equation of `TextureSwizzleEquations.hpp` applied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout; `ColorTargetLayout` applies the equations to in-block coordinates, which for 8-byte texels differs in every odd 64-row block row (address bit 11 takes y bit 6) and for 16-byte texels also differs, so a float display buffer written through `WriteColorTarget` and presented from guest memory would show those rows misplaced. The values are assumed to be linear light and are encoded with the sRGB curve for the 8-bit output. Bits 32-39 of these formats are `0x06`, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have `0x00`, its 10-bit PQ formats `0x04` (with `0x07` in bits 40-47) and its float BT.2100 PQ formats `0x07`: they show only that these formats are not the sRGB ones. NaN and negative values give 0, values of 1 and above give 255, alpha is linear. Neither the layout nor the transfer is verified against console or title frames, and HDR output is not modelled. DCC metadata on a float buffer throws.
|
||||
- [DCC key stores](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp) cover the console's DCC extent only for pipe-aligned DCC of single-level, single-layer 4-byte SW_64KB_R_X surfaces; other surfaces, and every texture and color target key read, cover one byte per 256 bytes.
|
||||
- [DCC key fills over other images](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) - a uniform fill at a live image's `dccAddress`, at least one key per 256 bytes long, is a key fill of that image even when other live images overlap it, but only while it is no longer than the image's key extent (`DccKeyCount`, the pipe-aligned count); a longer one is classified by its overlaps as before. The bound is inferred, not measured on a title: a longer fill also writes the bytes past the keys, which the overlapping image may own. Whether the title's bloom-target key fills stay within the extent was not checked (`APS5_TRACE_DCC_KEYS` prints the length of the fills classified as keys, not of the refused ones).
|
||||
- [Comp-to-single DCC keys](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - key 0x10 (`GFX9_DCC_CLEAR_SINGLE` in Mesa `ac_descriptors.h`) is expanded only by a CB DCC decompress pass over uniform keys of a non-linear target: every texel takes the element at the start of its 256-byte block, the top-left texel of the block where radv's `build_clear_dcc_comp_to_single_shader` stores the clear color. A fast-clear eliminate over such keys and mixed keys throw; textures and color targets that read them use their stored texels. "Uniform" is judged over the key bytes the driver reads, one per 256 surface bytes (see DCC key stores above), so a pipe-aligned surface whose keys differ past that range is expanded as if all were comp-to-single, as for the other clear codes. Not verified on hardware
|
||||
- [Comp-to-single DCC keys](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp) (libSceAgcDriver) - key 0x10 (`GFX9_DCC_CLEAR_SINGLE` in Mesa `ac_descriptors.h`) uniform over a surface makes every texel the element at the start of its 256-byte block, the top-left texel of the block where radv's `build_clear_dcc_comp_to_single_shader` stores the clear color. Sampled textures and storage images read that expansion (a storage image's first write stores the keys uncompressed, as under a clear code), and a CB DCC decompress pass stores it over the texels. A fast-clear eliminate over such keys throws, as a metadata pass over mixed keys does; color targets that render over such keys use their stored texels, and textures read mixed keys, and comp-to-single keys of a format whose element does not divide 256 bytes, as stored. "Uniform" is judged over the key bytes the driver reads, one per 256 surface bytes (see DCC key stores above), so a pipe-aligned surface whose keys differ past that range is expanded as if all were comp-to-single, as for the other clear codes. Not verified on hardware
|
||||
- [8_SRGB color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured.
|
||||
- [8_SRGB color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
|
||||
- [1_5_5_5 color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into A1R5G5B5. AMD formats keep the first component in the least significant bits (X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, as in the [fpPS4](https://github.com/red-prig/fpPS4) `kSurfaceFormat1_5_5_5` doc comment and as measured for textures in the packed 16-bit textures entry), while A1R5G5B5 keeps R in bits 10-14 and B in bits 0-4, so the pixel exports are written with X and Z exchanged, which makes the stored bits follow the guest layout. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets and compares every stored texel with the guest layout on the host device; the export direction is not measured on a PS5. Only UNORM with the standard component swap is accepted. Constant blend colors go through the same component-swap handling as other targets with that export mapping (`0xc6`), which is not tested for this format. PPSA06699 renders into this format.
|
||||
|
|
|
|||
Loading…
Reference in New Issue