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
14 changes: 12 additions & 2 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1417,18 +1417,28 @@ target_include_directories(agc_driver_sample_multisample_target_tests PRIVATE $<

if(BUILD_TESTING)
add_test(NAME agc_driver_undithered_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --undithered-alpha-to-coverage-state)
add_test(NAME agc_driver_msaa_dcc_state COMMAND agc_driver_graphics_tests --multisample-dcc-state)
set_tests_properties(agc_driver_undithered_alpha_to_coverage_state PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --alpha-to-coverage-state)
set_tests_properties(agc_driver_alpha_to_coverage_state PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_dithered_alpha_to_coverage COMMAND agc_driver_alpha_to_coverage_tests --dithered8)
set(multisampledDccClearTests)
foreach(samples IN ITEMS 2 4 8)
set(testName "agc_driver_msaa_dcc_clear_${samples}")
add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-fast-clear ${samples})
list(APPEND multisampledDccClearTests ${testName})
endforeach()
set_tests_properties(agc_driver_msaa_dcc_state ${multisampledDccClearTests} PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(${multisampledDccClearTests} PROPERTIES SKIP_RETURN_CODE 77)
set_tests_properties(agc_driver_dithered_alpha_to_coverage PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_alpha_to_coverage_state agc_driver_undithered_alpha_to_coverage_state PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_alpha_to_coverage_state agc_driver_undithered_alpha_to_coverage_state agc_driver_msaa_dcc_state PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_dithered_alpha_to_coverage PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_dithered_alpha_to_coverage ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>

namespace AgcDriver::Graphics {
Expand Down Expand Up @@ -49,6 +50,8 @@ class MultisampledColorFill {
};

std::uint64_t MultisampledColorBytes(const ColorTarget& target);
std::size_t MultisampledDccBytes(const ColorTarget& target);
VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span<const std::byte> dcc, std::span<const std::byte> cmask);
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);
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct ColorTarget {
bool uintExport = false;
std::uint32_t pipeBankXor = 0;
std::uint32_t samples = 1;
std::uint32_t dccControl = 0;
};

struct DepthTarget {
Expand Down
112 changes: 105 additions & 7 deletions core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class MultisampleTarget {
public:
MultisampleTarget(const Context& context, const ColorTarget& target) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) {
this->context.bufferPool.reset();
if (target.dccAddress != 0) metadataValid = std::make_shared<std::atomic<bool>>(true);
const auto initial = initialDccClear(target);
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");
VkFormatProperties properties{};
Expand All @@ -78,9 +80,8 @@ class MultisampleTarget {
view = created.view;
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
recordToGeneral(commands, image);
const VkClearColorValue zero{};
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &zero, 1, &range);
context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &initial, 1, &range);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
});
Expand Down Expand Up @@ -176,6 +177,7 @@ class MultisampleTarget {
}

void ApplyFastClear(const ColorTarget& use) {
checkDccCurrent(use);
VkClearColorValue clear{};
if (!TakeMultisampledClear(cmask, colorFill, use, clear)) return;
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
Expand Down Expand Up @@ -278,6 +280,7 @@ class MultisampleTarget {

Context context;
std::shared_ptr<std::atomic<bool>> active = std::make_shared<std::atomic<bool>>(true);
std::shared_ptr<std::atomic<bool>> metadataValid;
ColorTarget target;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
Expand All @@ -296,6 +299,55 @@ class MultisampleTarget {
VkImageView storageView = VK_NULL_HANDLE;
bool storageWritten = false;
std::map<std::array<std::uint32_t, 4>, std::shared_ptr<Texture>> textures;
std::vector<GuestMemory::UnchangedQuery> dccRanges;

VkClearColorValue initialDccClear(const ColorTarget& use) {
if (use.dccAddress == 0) return {};
const auto dccBytes = MultisampledDccBytes(use);
Require((use.dccAddress & 4095u) == 0, "multisampled DCC metadata base alignment is not implemented");
Require(use.cmaskAddress == 0 ? use.cmaskBytes == 0 : use.cmaskBytes == CmaskBytes(use.extent.width, use.extent.height), "multisampled DCC CMASK footprint is invalid");
const auto colorBytes = MultisampledColorBytes(use);
Require(colorBytes != 0, "multisampled DCC color footprint is not implemented");
dccRanges.push_back({use.address, static_cast<std::size_t>(colorBytes), 0});
dccRanges.push_back({use.dccAddress, dccBytes, 0});
if (use.cmaskAddress != 0) dccRanges.push_back({use.cmaskAddress, use.cmaskBytes, 0});
for (auto& range : dccRanges) {
GuestMemory::CheckRange(reinterpret_cast<const void*>(range.address), range.bytes, 256);
Require(GuestMemory::Watched(range.address, range.bytes), "initial multisampled DCC contents require watched guest memory");
range.generation = GuestMemory::CollectWritesUncached(range.address, range.bytes);
Require(range.generation != 0, "initial multisampled DCC contents have no write generation");
}
for (std::size_t first = 0; first < dccRanges.size(); ++first) {
for (std::size_t second = first + 1; second < dccRanges.size(); ++second) {
const auto& a = dccRanges[first];
const auto& b = dccRanges[second];
Require(a.address >= b.address + b.bytes || b.address >= a.address + a.bytes, "overlapping multisampled DCC color or metadata ranges are not implemented");
}
}
std::vector<std::byte> dcc(dccRanges[1].bytes);
GuestMemory::Read(use.dccAddress, dcc);
std::vector<std::byte> cmask(use.cmaskBytes);
if (!cmask.empty()) GuestMemory::Read(use.cmaskAddress, cmask);
const auto clear = MultisampledDccClear(use, dcc, cmask);
Require(GuestMemory::UnchangedSinceAll(dccRanges), "initial multisampled DCC contents changed during import");
return clear;
}

void checkDccCurrent(const ColorTarget& use) const {
if (dccRanges.empty()) {
Require(use.dccAddress == 0, "adding DCC to a resident multisampled color target is not implemented");
return;
}
Require(metadataValid->load(std::memory_order_acquire), "multisampled DCC metadata was released while its color target remains resident");
static_cast<void>(MultisampledDccBytes(use));
Require(use.address == target.address && use.dccAddress == target.dccAddress && use.dccControl == target.dccControl && use.dccAlphaOnMsb == target.dccAlphaOnMsb && use.dccPipeAligned == target.dccPipeAligned && use.cmaskAddress == target.cmaskAddress && use.cmaskBytes == target.cmaskBytes && use.tileMode == target.tileMode && use.elementBytes == target.elementBytes && use.pipeBankXor == target.pipeBankXor, "multisampled DCC target layout or metadata changed");
for (const auto& range : dccRanges) {
Require(GuestMemory::Watched(range.address, range.bytes), "multisampled DCC target reuse requires watched guest memory");
GuestMemory::CollectWritesUncached(range.address, range.bytes);
Require(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation), "writes to a resident multisampled DCC target or its metadata are not implemented");
if (auto* recorder = Recorder::Active(); recorder != nullptr) Require(!recorder->PendingWriteOverlaps(range.address, range.bytes), "pending writes to a resident multisampled DCC target or its metadata are not implemented");
}
}

void release() noexcept {
textures.clear();
Expand All @@ -312,6 +364,7 @@ struct GuardedRange {
std::uint64_t bytes;
std::uint32_t samples;
std::shared_ptr<std::atomic<bool>> active;
std::shared_ptr<std::atomic<bool>> metadataValid;
};

class PrimedSharedMutex : public std::shared_mutex {
Expand Down Expand Up @@ -345,14 +398,16 @@ void retireUnmapped(std::uintptr_t address, std::size_t bytes) {
RetireUnmappedDepth_nid_no_patch(address, bytes);
std::unique_lock lock(guardedMutex());
std::erase_if(guarded(), [&](const auto& range) {
if (range.metadataValid && address < range.address + range.bytes && range.address < address + bytes) range.metadataValid->store(false, std::memory_order_release);
if (range.address < address || range.address - address > bytes || range.bytes > bytes - (range.address - address)) return false;
range.active->store(false, std::memory_order_release);
if (!range.metadataValid) range.active->store(false, std::memory_order_release);
return true;
});
std::erase_if(guarded(), [](const auto& range) { return range.metadataValid && !range.active->load(std::memory_order_acquire); });
anyGuarded().store(!guarded().empty(), std::memory_order_release);
}

void guard(const Context& context, const ColorTarget& target, const std::shared_ptr<std::atomic<bool>>& active) {
void guard(const Context& context, const ColorTarget& target, const std::shared_ptr<std::atomic<bool>>& active, const std::shared_ptr<std::atomic<bool>>& metadataValid) {
auto bytes = std::max<std::uint64_t>(target.bytes * target.samples, static_cast<std::uint64_t>(target.extent.width) * target.extent.height * target.elementBytes * target.samples);
if (target.tileMode == ColorTileMode::RenderTarget && std::has_single_bit(target.elementBytes) && std::has_single_bit(target.samples)) {
const auto log2Samples = std::countr_zero(target.samples);
Expand All @@ -365,6 +420,10 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_
}
std::unique_lock lock(guardedMutex());
guarded().push_back({context.device, target.address, bytes, target.samples, active});
if (target.dccAddress != 0) {
guarded().push_back({context.device, target.dccAddress, MultisampledDccBytes(target), target.samples, active, metadataValid});
if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid});
}
anyGuarded().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
}
Expand Down Expand Up @@ -394,8 +453,8 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& use) {
}
targets().push_back(std::make_unique<MultisampleTarget>(context, use));
auto& created = *targets().back();
guard(context, use, created.active);
if (use.cmaskFastClear) created.cmask.SeedKeys(CurrentDccKeys(use.cmaskAddress, static_cast<std::uint64_t>(use.cmaskBytes) * 256u));
guard(context, use, created.active, created.metadataValid);
if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedKeys(CurrentDccKeys(use.cmaskAddress, static_cast<std::uint64_t>(use.cmaskBytes) * 256u));
created.ApplyFastClear(use);
return created;
}
Expand Down Expand Up @@ -444,7 +503,7 @@ MultisampleTarget& createFilled(const Context& context, const GuestTextureResour
if (!MultisampledFillClearColor(use, value, clear)) refuse(std::string("whose color memory ") + range + " holds " + held + ", which has no exact clear value in its format");
targets().push_back(std::make_unique<MultisampleTarget>(context, use));
auto& created = *targets().back();
guard(context, use, created.active);
guard(context, use, created.active, created.metadataValid);
created.colorFill.NoteFill(use.address, static_cast<std::size_t>(bytes), value);
created.fromTexture = true;
return created;
Expand Down Expand Up @@ -519,6 +578,45 @@ std::uint64_t MultisampledColorBytes(const ColorTarget& target) {
return ((target.extent.width + blockWidth - 1) / blockWidth) * ((target.extent.height + blockHeight - 1) / blockHeight) << blockLog2;
}

std::size_t MultisampledDccBytes(const ColorTarget& target) {
Require(target.samples == 2 || target.samples == 4 || target.samples == 8, "unsupported multisampled DCC fragment count");
Require(target.tileMode == ColorTileMode::RenderTarget && target.dccPipeAligned, "multisampled DCC requires pipe-aligned SW_64KB_R_X");
Require(std::has_single_bit(target.elementBytes) && target.elementBytes <= 16, "unsupported multisampled DCC element size");
Require(target.extent.width != 0 && target.extent.width <= 16384 && target.extent.height != 0 && target.extent.height <= 16384, "invalid multisampled DCC extent");
Require(target.mipCount == 1 && target.mip == 0 && !target.mipTail && target.depth == 1 && target.depthSlice == 0 && target.arrayLastSlice == 0 && target.arraySlice == 0 && target.layers == 1, "multisampled DCC array, volume or mip contents are not implemented");
const auto bits = 19u - static_cast<std::uint32_t>(std::countr_zero(target.elementBytes));
const std::uint64_t width = std::uint64_t{1} << ((bits + 1u) / 2u);
const std::uint64_t height = std::uint64_t{1} << (bits / 2u);
return static_cast<std::size_t>(((target.extent.width + width - 1) / width) * ((target.extent.height + height - 1) / height) * 4096u);
}

VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span<const std::byte> dcc, std::span<const std::byte> cmask) {
Require(target.dccAddress != 0 && (target.dccControl & 0x3ff80u) == 0 && (target.pipeBankXor & ~0xcf00u) == 0, "multisampled DCC address, color transform, lossy mode or XOR is not implemented");
Require(dcc.size() == MultisampledDccBytes(target), "multisampled DCC metadata snapshot size mismatch");
Require(std::ranges::all_of(dcc, [&](std::byte key) { return key == dcc.front(); }), "mixed multisampled DCC metadata is not implemented");
Require(target.cmaskAddress == 0 ? cmask.empty() && target.cmaskBytes == 0 : cmask.size() == target.cmaskBytes && target.cmaskBytes == CmaskBytes(target.extent.width, target.extent.height), "multisampled DCC CMASK snapshot size mismatch");
if (!cmask.empty()) {
const auto code = std::to_integer<std::uint32_t>(cmask.front());
const auto expanded = target.samples == 2 ? 0xddu : target.samples == 4 ? 0xeeu : 0xffu;
Require(std::ranges::all_of(cmask, [&](std::byte key) { return key == cmask.front(); }) && (code == 0xccu || code == expanded), "multisampled DCC CMASK compression or register clear is not implemented");
}
DccKeys keys;
switch (std::to_integer<std::uint32_t>(dcc.front())) {
case 0x00u: keys = DccKeys::Clear0000; break;
case 0x40u: keys = DccKeys::Clear0001; break;
case 0x80u: keys = DccKeys::Clear1110; break;
case 0xc0u: keys = DccKeys::Clear1111; break;
default: throw std::runtime_error("initial multisampled DCC contents are not a supported uniform fast clear");
}
std::array<std::byte, 16> texels{};
Require(FillDccClear(target.format, keys, target.dccAlphaOnMsb, texels), "multisampled DCC clear format is not implemented");
std::array<std::uint32_t, 4> pattern{};
std::memcpy(pattern.data(), texels.data(), texels.size());
VkClearColorValue clear{};
Require(ClearColorForTexel(target.format, target.elementBytes, pattern, clear), "multisampled DCC fast clear has no exact host value");
return clear;
}

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;
Expand Down
6 changes: 5 additions & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,6 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
Require(color.samples <= 8u, "color targets with more than eight samples are unsupported");
Require((1u << ((attrib >> 15u) & 3u)) == color.samples, "color targets with fewer fragments than samples (EQAA) are unsupported");
const auto multisampleInfo = color.samples > 1 ? 0x0c006000u : 0u;
Require(allowMultisampleDcc || color.samples == 1 || (info & 0x10000000u) == 0, "DCC over a multisampled color target is unsupported");
// ROUND_MODE (bit 18) only affects unorm rounding. With DCC_ENABLE (bit 28) the target is written
if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u | 0x00002000u | multisampleInfo)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")");
Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported");
Expand Down Expand Up @@ -1540,6 +1539,11 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
color.dccAddress = ((dccHigh == cx.end() ? 0ull : static_cast<std::uint64_t>(dccHigh->second & 0xffu)) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x325 + stride)) << 8u);
color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap);
color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0;
if (color.samples > 1) {
if (!allowMultisampleDcc) Require(color.tileMode == ColorTileMode::RenderTarget && color.dccAddress != 0 && color.dccPipeAligned && !volume && color.arrayLastSlice == 0 && slice == 0, "multisampled DCC targets require pipe-aligned single-layer SW_64KB_R_X");
color.dccControl = read(cx, 0x31e + stride);
if (!allowMultisampleDcc) Require((color.dccControl & 0x3ff80u) == 0, "multisampled DCC color transforms or lossy modes are not implemented");
}
} else {
static bool reported = false;
if (!reported) {
Expand Down
Loading
Loading