Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ struct GuestTextureResource {
std::uint32_t minLod = 0;
std::uint32_t allocatedMipCount = 0;
std::uint32_t samples = 1;
std::uint64_t fmaskCmaskAddress = 0;
std::uint32_t gcnPitch = 0;
bool gcnPow2Pad = false;
bool gcnLinearGeneral = false;
std::uint32_t linearPitch = 0;
std::uint64_t fmaskCmaskAddress = 0;
};

constexpr bool IsGcnMicroTiled(TextureTileMode mode) {
Expand Down
19 changes: 15 additions & 4 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2116,7 +2116,8 @@ ColorTarget DecodeGcnColorBuffer(const Registers& cx, std::uint32_t slot) {
const auto decoded = DecodeColorFormat((info >> 2u) & 0x1fu, number, swap);
const auto attrib = read(cx, 0x31d + stride);
color.samples = 1u << ((attrib >> 12u) & 7u);
Require(color.samples == 1u, "multisampled GCN color targets are not implemented");
Require(color.samples <= 8u, "GCN color targets with more than eight samples are not implemented");
Require((1u << ((attrib >> 15u) & 3u)) == color.samples, "GCN color targets with fewer fragments than samples (EQAA) are not implemented");
Require((info & 0x3u) == 0 && (info & 0x80u) == 0, "endian-swapped or linear-general GCN color targets are not implemented");
Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported");
const auto view = read(cx, 0x31b + stride);
Expand Down Expand Up @@ -2147,7 +2148,13 @@ ColorTarget DecodeGcnColorBuffer(const Registers& cx, std::uint32_t slot) {
const auto clear = find(cx, 0x323 + word + stride);
color.clearWords[word] = clear == cx.end() ? 0u : clear->second;
}
Require(((info >> 13u) & 1u) == 0, "PS4 color target fast clear (CMASK) is not implemented");
Require(color.samples > 1u || ((info >> 13u) & 1u) == 0, "PS4 color target fast clear (CMASK) is not implemented");
if (color.samples > 1u && (info & 0x6000u) != 0) {
color.cmaskAddress = static_cast<std::uint64_t>(read(cx, 0x31f + stride)) << 8u;
Require(color.cmaskAddress != 0, "a multisampled GCN color target with fast clear or FMASK compression has no CMASK address");
color.cmaskBytes = (static_cast<std::size_t>(read(cx, 0x320 + stride) & 0x3fffu) + 1u) * 128u;
color.cmaskFastClear = (info & 0x2000u) != 0;
}
GuestMemory::CheckRange(reinterpret_cast<const void*>(color.address), color.bytes, layout.Alignment(), true);
return color;
}
Expand Down Expand Up @@ -2358,7 +2365,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo

namespace {

bool drawCovers(const Registers& cx, VkExtent2D extent) {
bool drawCovers(const Registers& cx, VkExtent2D extent, const ColorTarget* gcnTarget = nullptr) {
const auto viewportControl = read(cx, 0x206);
if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl));
const auto xs = std::fabs(readFloat(cx, 0x10f));
Expand All @@ -2370,6 +2377,10 @@ bool drawCovers(const Registers& cx, VkExtent2D extent) {
intersect(covered, cx, 0x81, false);
intersect(covered, cx, 0x90, false);
if ((read(cx, 0x292) & 2u) != 0) intersect(covered, cx, 0x94, false);
if (gcnTarget != nullptr && covered.offset.x == 0 && covered.offset.y == 0 && xo - xs <= 0.0f && yo - ys <= 0.0f) {
const VkExtent2D visible{std::min({extent.width, covered.extent.width, static_cast<std::uint32_t>(std::max(0.0f, xo + xs))}), std::min({extent.height, covered.extent.height, static_cast<std::uint32_t>(std::max(0.0f, yo + ys))})};
if (visible.width != 0 && visible.height != 0 && ColorTargetLayout(visible.width, visible.height, gcnTarget->tileMode, gcnTarget->elementBytes).Bytes() == gcnTarget->bytes) extent = visible;
}
const auto width = static_cast<float>(extent.width);
const auto height = static_cast<float>(extent.height);
const bool viewportCovers = xo - xs <= 0.0f && xo + xs >= width && yo - ys <= 0.0f && yo + ys >= height;
Expand Down Expand Up @@ -2434,7 +2445,7 @@ std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue
const auto samples = resolve && rasterizerSamples && multisampleRaster ? decodeSamples(cx) : 1u;
if (!resolve) zero(cx, 0x2f8, ~0u, "multisampling or coverage conversion");
zero(cx, 0x80, ~0u, "window offset");
const auto requireCovered = [&](const ColorTarget& target) { Require(drawCovers(cx, target.extent), "CB metadata pass over part of a color target"); };
const auto requireCovered = [&](const ColorTarget& target) { Require(drawCovers(cx, target.extent, ColorTileModeIsGcn(target.tileMode) ? &target : nullptr), "CB metadata pass over part of a color target"); };
if (resolve) {
const auto source = DecodeColorBuffer(cx, 0);
const auto destination = DecodeColorBuffer(cx, 1);
Expand Down
151 changes: 151 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/GcnColorTarget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include "prx/libc/include/GuestArena.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <stdexcept>
#include <string>

namespace {

int failures = 0;

void Check(bool condition, const char* what) {
if (condition) return;
std::fprintf(stderr, "%s\n", what);
++failures;
}

template <typename Function>
std::string Failure(Function function) {
try {
function();
} catch (const std::exception& error) {
return error.what();
}
return {};
}

constexpr std::uint32_t Width = 64;
constexpr std::uint32_t Height = 64;
constexpr std::uint32_t Format8888 = 10u;
constexpr std::uint32_t BlendClamp = 1u << 15u;
constexpr std::uint32_t FastClear = 1u << 13u;
constexpr std::uint32_t Compression = 1u << 14u;
constexpr std::uint32_t Thin1D = 13u;

constexpr std::size_t ColorBytes = Width * Height * 4u;
constexpr std::size_t CmaskBytes = 512u;
constexpr std::size_t MemoryBytes = 65536u;
std::byte* colorMemory = nullptr;
std::byte* cmaskMemory = nullptr;

std::byte* LowMemory(std::size_t bytes) {
#ifdef _WIN32
auto* block = static_cast<std::byte*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536));
if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
return block;
#else
for (std::uintptr_t hint = std::uintptr_t{1} << 32u; hint < (std::uintptr_t{1} << 40u); hint += std::uintptr_t{1} << 32u) {
void* mapped = mmap(reinterpret_cast<void*>(hint), bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (mapped != MAP_FAILED) return static_cast<std::byte*>(mapped);
}
return nullptr;
#endif
}

std::uint64_t AddressOf(const void* data) {
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
}

void SetTarget(AgcDriver::Registers& cx, std::uint32_t samples, std::uint32_t fragments, std::uint32_t flags, bool cmask = true) {
cx[0x318] = static_cast<std::uint32_t>(AddressOf(colorMemory) >> 8u);
cx[0x319] = Width / 8u - 1u;
cx[0x31a] = Width * Height / 64u - 1u;
cx[0x31b] = 0u;
cx[0x31c] = (Format8888 << 2u) | BlendClamp | flags;
cx[0x31d] = Thin1D | (static_cast<std::uint32_t>(std::countr_zero(samples)) << 12u) | (static_cast<std::uint32_t>(std::countr_zero(fragments)) << 15u);
cx[0x31f] = cmask ? static_cast<std::uint32_t>(AddressOf(cmaskMemory) >> 8u) : 0u;
cx[0x320] = static_cast<std::uint32_t>(CmaskBytes / 128u - 1u);
}

AgcDriver::Graphics::ColorTarget Decode(std::uint32_t samples, std::uint32_t fragments, std::uint32_t flags, bool cmask = true) {
AgcDriver::Registers cx;
SetTarget(cx, samples, fragments, flags, cmask);
return AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
}

void MultisampledTargetsDecode() {
const auto cleared = Decode(4u, 4u, FastClear | Compression);
Check(cleared.samples == 4u, "a 4x GCN color target did not decode with 4 samples");
Check(cleared.cmaskAddress == AddressOf(cmaskMemory) && cleared.cmaskBytes == CmaskBytes, "a 4x GCN color target lost its CMASK address or size");
Check(cleared.cmaskFastClear, "a 4x GCN color target with FAST_CLEAR is not a CMASK fast clear");
const auto compressed = Decode(8u, 8u, Compression);
Check(compressed.samples == 8u && compressed.cmaskAddress == AddressOf(cmaskMemory) && !compressed.cmaskFastClear, "an 8x FMASK-compressed GCN color target decoded wrong");
const auto plain = Decode(2u, 2u, 0u);
Check(plain.samples == 2u && plain.cmaskAddress == 0u, "a 2x GCN color target without compression took a CMASK");
Check(Failure([] { Decode(4u, 4u, FastClear, false); }).find("has no CMASK address") != std::string::npos, "a 4x GCN fast clear without a CMASK address was accepted");
}

std::string FmaskDecompress(std::uint32_t visibleHeight) {
AgcDriver::QueueState queue;
auto& cx = queue.context;
SetTarget(cx, 4u, 4u, Compression);
cx[0x202] = 0xcc0000u | (5u << 4u);
cx[0x010] = 0u;
cx[0x011] = 0u;
cx[0x8e] = 0xfu;
cx[0x292] = 0u;
cx[0x10f] = std::bit_cast<std::uint32_t>(Width / 2.0f);
cx[0x110] = std::bit_cast<std::uint32_t>(Width / 2.0f);
cx[0x111] = std::bit_cast<std::uint32_t>(visibleHeight / 2.0f);
cx[0x112] = std::bit_cast<std::uint32_t>(visibleHeight / 2.0f);
cx[0x90] = 0u;
cx[0x91] = (visibleHeight << 16u) | Width;
return Failure([&] {
const auto pass = AgcDriver::Graphics::DecodeColorMetadataPass(queue);
if (!pass || pass->targets.size() != 1u || pass->targets[0].samples != 4u) throw std::runtime_error("the FMASK decompress did not decode its 4x target");
});
}

void PaddedTargetsAreCovered() {
Check(FmaskDecompress(Height).empty(), "an FMASK decompress over a whole 4x GCN target was refused");
const auto padded = FmaskDecompress(Height - 4u);
Check(padded.empty(), ("an FMASK decompress over the rows of a 4x GCN target its tile padding does not add was refused: " + padded).c_str());
Check(FmaskDecompress(Height - 16u).find("over part of a color target") != std::string::npos, "an FMASK decompress over part of a 4x GCN target was accepted");
}

}

int main() {
try {
AgcDriverUseGcnShaders_nid_postfix();
colorMemory = LowMemory(MemoryBytes);
if (colorMemory == nullptr || AddressOf(colorMemory) + MemoryBytes > (std::uint64_t{1} << 40u)) {
std::fprintf(stderr, "cannot allocate test memory below 1 TiB\n");
return 1;
}
cmaskMemory = colorMemory + ColorBytes;
MultisampledTargetsDecode();
PaddedTargetsAreCovered();
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
if (failures != 0) return 1;
std::puts("GCN multisampled color targets decode with their CMASK, and a metadata pass over the visible rows of a padded target is covered");
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
add_test_executable(agc_gcn_color_target_tests EXCLUDE_FROM_ALL tests/GcnColorTarget.cpp)
target_include_directories(agc_gcn_color_target_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_gcn_color_target_tests PRIVATE libSceAgcDriver libkernel libc)
set_target_properties(agc_gcn_color_target_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_gcn_color_target_tests)

if(BUILD_TESTING)
set_target_properties(agc_gcn_color_target_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_gcn_color_target COMMAND agc_gcn_color_target_tests)
set_tests_properties(agc_gcn_color_target PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_gcn_color_target PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
1 change: 1 addition & 0 deletions docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
- [PS4 (GCN) tiling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver, PS4) - not checked on a console. Surfaces with `GuestTextureResource::gcnPitch` set use the GB_TILE_MODE and GB_MACROTILE_MODE tables of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/video_core/amdgpu/tiling.cpp) and addrlib's `ComputeSurfaceAddrFromCoordMicroTiled` and `MacroTiled`: 8x8 micro tiles in thin, depth or display element order, macro tiles with pipe config P8_32x32_16x16 (P8_32x32_8x16 for the two PRT thin modes), bank width 1 and a 256-byte pipe interleave, depth micro tiles split into slices a whole surface apart with the bank rotated by (banks / 2 + 1) per slice, PRT pipe, and for GB_TILE_MODE 11 and 16 bank, taken from the coordinates inside the macro tile, and LINEAR_GENERAL rows of exactly the pitch. [TextureTiling.cpp](../../../core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp) compares it with a separate implementation of them.
- [PS4 (GCN) 2D PRT tiling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver, PS4) - not checked on a console. GB_TILE_MODE 6, 7, 12 and 17 take the bank from the coordinates inside the macro tile, as shadPS4 does; AMD's addrlib (Mesa 393bf0d1467c) and fpPS4 (04cefd43e6fd) use the whole coordinates for the bank of these 2D PRT modes and take it inside the macro tile only for modes 11 and 16, which fpPS4 documents as the ones that can alias virtual pages to one physical page.
- [PS4 (GCN) thick and layered surfaces](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver, PS4) - thick modes (GB_TILE_MODE 15 and 19 to 26) throw, and so does a storage image or a depth plane of a layered surface with several mip levels. A sampled layered surface with several mip levels is stored level by level, and a 3D surface in a thin mode the same way, its level n holding max(1, depth >> n) slices, rounded up to a power of two with `POW2_PAD` as its width and height are, following [ac_surface](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/amd/common/ac_surface.c#L776) and addrlib's [PostComputeMipLevel](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/amd/addrlib/src/core/addrlib1.cpp#L3818-L3842). Neither is checked on a console; [GcnDetile.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/GcnDetile.cpp) runs the GPU detiler over every mode that does not throw.
- [PS4 (GCN) multisampled color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver, PS4) - a GCN color target with 2, 4 or 8 samples and as many fragments (CB_COLOR_ATTRIB NUM_SAMPLES and NUM_FRAGMENTS) becomes a host-resident multisampled target, as on the AGC path: its samples stay on the host. With FAST_CLEAR or COMPRESSION set, its CMASK (CB_COLOR_CMASK, CMASK_SLICE tile max + 1 times 128 bytes) is taken for fast clears; FMASK contents are not modeled. Fewer fragments than samples (EQAA), more than 8 samples and a single-sample fast clear throw. A CB metadata pass (FMASK decompress or resolve) over a GCN target may cover only its visible rows and columns: the extent the viewport and scissor cover counts when the tile mode pads it to the target's size. None of it is checked on a console.
- [PS4 (GCN) array layer banks](../../../core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp) (libSceAgcDriver, PS4) - not checked on a console. The bank of each layer of a tiled 2D array is rotated by (banks / 2 - 1) times the layer index outside the PRT modes, XORed in as AMD's address library and shadPS4 (`tiling.comp`, 0614ce324be0) do; fpPS4 (`ps4_tiling.pas`, 04cefd43e6fd) adds it to the bank instead.
- [PS4 flips without a resident image](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver, PS4) - a tiled flip with no resident image, such as one before the title draws into the buffer, is decoded on the CPU one texel at a time through `ColorTargetLayout::Offset`, about 116 ms at 1920x1080 ([measured on #9673](https://github.com/boykopovar/AnyPS5/pull/9673)). It then takes the same upload, HDR10 scaler, frame dump and screenshot path as a linear buffer.
- [PS4 render targets and display buffers](../../../core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp) (libSceAgcDriver, PS4) - a tiled PS4 display buffer is read as Display2DThin with rows padded to 128 elements, as shadPS4's `image_info.cpp` does. A PS4 render target spans its surface padded to whole macro tiles (1920x1080 at 32 bits is 1920x1088), the display buffer is its top-left corner, and a texture over that corner with the same pitch, tiling and texel size is a [GPU copy](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) of it. A PS4 `DB_Z_INFO` names a GB_TILE_MODE index; the depth image stays on the GPU and HTILE is kept, as for the PS5. shadPS4 (`image_info.cpp`, 0614ce324be0) rounds the width of a tiled display buffer up to a multiple of 128 itself, whereas `DisplayBufferSize` requires the pitch to be one already. A PS4 depth target spans `DB_DEPTH_SIZE`; a smaller level-0 depth view reads a plane copy of its corner, a stencil one guest memory.
Expand Down
Loading