Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
48a7efc
feat(agc): merge partial writes into 4_4_4_4 UNORM color targets
JAB-dev Oct 18, 2026
b8a21d4
test(agc): draw merged 4_4_4_4 writes back to back and through the pr…
JAB-dev Oct 18, 2026
27c0004
fix(agc): merge kill shaders under late tests when the draw writes no…
JAB-dev Oct 18, 2026
8b37d04
fix(agc): write a merged 4_4_4_4 word only for pixels that survive th…
JAB-dev Oct 18, 2026
8d6d864
fix(agc): leave the merged 4_4_4_4 target out of the draw's attachments
JAB-dev Oct 18, 2026
f3ea4b2
test(agc): check 4_4_4_4 merges against fixed masks, same-draw overdr…
JAB-dev Oct 18, 2026
f4adaff
docs(agc): list the limits of merged single-sample 4_4_4_4 partial wr…
JAB-dev Oct 18, 2026
7d4776a
fix(agc): read and write the merged 4_4_4_4 target through a Coherent…
JAB-dev Oct 19, 2026
74376b9
fix(agc): refuse partial 4_4_4_4 writes under merged coverage
JAB-dev Oct 19, 2026
1babc10
fix(agc): refuse merged 4_4_4_4 writes from shaders that write memory…
JAB-dev Oct 19, 2026
7ca6e1e
fix(agc): refuse killing 4_4_4_4 merges while occlusion queries count…
JAB-dev Oct 19, 2026
6ccb8c0
fix(agc): refuse memory-writing 4_4_4_4 merges only where tests can r…
JAB-dev Oct 19, 2026
c5b42db
feat(agc): merge partial 4_4_4_4 writes into multisampled color targets
JAB-dev Oct 18, 2026
f0ea4fa
fix(agc): settle merged multisampled writes before the draws that follow
JAB-dev Oct 18, 2026
2e8ea10
test(agc): decode the 2-sample 4_4_4_4 merge draws from registers
JAB-dev Oct 18, 2026
6f0873c
docs(agc): list the limits of merged 2-sample 4_4_4_4 partial writes
JAB-dev Oct 18, 2026
3e8f797
fix(agc): refuse coverage input in a merged multisampled 4_4_4_4 draw
JAB-dev Oct 19, 2026
a1814c5
fix(agc): refuse per-pixel inputs that move when a 2-sample merge ite…
JAB-dev Oct 19, 2026
d2a5895
fix(agc): refuse 2-sample 4_4_4_4 merges of shaders that write memory
JAB-dev Oct 19, 2026
c21356d
test(agc): draw two 2-sample 4_4_4_4 merges back to back
JAB-dev Oct 19, 2026
69be01d
fix(agc): keep centre barycentrics of 2-sample merges whose samples s…
JAB-dev Oct 19, 2026
f5edd53
test(agc): merge partial 4_4_4_4 writes into 4- and 8-sample targets
JAB-dev Oct 19, 2026
8ed185a
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 21, 2026
4aaf26e
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 22, 2026
7576430
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 22, 2026
4287ca1
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 22, 2026
a4d9b21
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 22, 2026
a44cdd7
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 22, 2026
d201de3
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 23, 2026
870bacd
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 23, 2026
6ab7bd2
feat(shader): take shader disk cache format 46 for the 4_4_4_4 target…
JAB-dev Oct 23, 2026
8415906
Merge branch 'feat/agc-4-4-4-4-color-target' into feat/agc-4-4-4-4-pa…
JAB-dev Oct 23, 2026
3a66884
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 23, 2026
8d54357
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 23, 2026
aeb000e
Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat…
JAB-dev Oct 23, 2026
31aaf42
docs(debt): restore the Packed 16-bit textures entry from main
JAB-dev Oct 23, 2026
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
4 changes: 4 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,10 @@ target_include_directories(agc_driver_packed_unorm_color_target_tests PRIVATE $<

target_include_directories(agc_driver_packed_unorm4444_color_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_packed_unorm4444_partial_write_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_packed_unorm4444_multisample_partial_write_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_color1555_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_color565_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
product->pixel.targetExportPacking = Graphics::ExportPackings(product->state);
product->pixel.dualSourceBlend = product->state.dualSourceBlend;
product->pixel.reversedBlend = product->state.reversedBlend;
product->pixel.targetMerge = Graphics::ExportTargetMerge(product->state);
product->pixel.alphaToCoverage = product->state.alphaToCoverage;
product->pixel.quadPixelMask = Graphics::QuadPixelMask(product->state);
if (product->state.sampleLocationGrid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
for (const auto value : pixel.targetExportPacking) mix(static_cast<std::uint64_t>(value));
mix(pixel.dualSourceBlend);
mix(pixel.reversedBlend);
mix(pixel.targetMerge.target);
mix(pixel.targetMerge.mask);
mix(pixel.targetMerge.samples);
for (const auto word : pixel.targetMerge.descriptor) mix(word);
mix(pixel.targetMerge.depthStencilWrites);
mix(pixel.targetMerge.depthStencilTests);
mix(pixel.alphaToCoverage);
mix(static_cast<std::uint64_t>(pixel.rasterPrimitive));
mix(pixel.ditheredAlphaToCoverage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto& s = a.state;
const auto& t = b.state;
const auto sameColor = [](const Graphics::ColorTarget& x, const Graphics::ColorTarget& y) {
return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex;
return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex && x.partialWriteMask == y.partialWriteMask;
};
const auto sameBlend = [](const VkPipelineColorBlendAttachmentState& x, const VkPipelineColorBlendAttachmentState& y) {
return x.blendEnable == y.blendEnable && x.srcColorBlendFactor == y.srcColorBlendFactor && x.dstColorBlendFactor == y.dstColorBlendFactor && x.colorBlendOp == y.colorBlendOp && x.srcAlphaBlendFactor == y.srcAlphaBlendFactor && x.dstAlphaBlendFactor == y.dstAlphaBlendFactor && x.alphaBlendOp == y.alphaBlendOp && x.colorWriteMask == y.colorWriteMask;
Expand Down Expand Up @@ -262,8 +262,8 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto& y = b.programs[i];
if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset || x.tailContinuation != y.tailContinuation) return false;
}
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations || p.sampleOrder != q.sampleOrder) return false;
if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.truncatedCoverage != t.truncatedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false;
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.targetMerge != q.targetMerge || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations || p.sampleOrder != q.sampleOrder) return false;
if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.truncatedCoverage != t.truncatedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ || s.pixelKill != t.pixelKill) return false;
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ float EffectiveMinLod(const GuestTextureResource& resource);

bool IsNullTextureResource(std::span<const std::uint32_t> words);
GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words);
std::array<std::uint32_t, 8> EncodeTextureResource(const GuestTextureResource& resource);
bool DescriptorSingleLevel(std::span<const std::uint32_t> words);
bool FirstLayerDescriptor(std::span<const std::uint32_t> words, std::array<std::uint32_t, 8>& narrowed);
bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension);
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class Pipeline {
VkPipeline secondChannel = VK_NULL_HANDLE;
std::size_t attachments = 0;
std::size_t colorAttachments = 0;
std::vector<bool> boundAttachments;
std::uint32_t boundColorAttachments = 0;
bool depthBounds = false;
bool depthBias = false;
bool libraries = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class Recorder {
// Submits and waits for every batch, running completions in order.
void Sync();
void CountSamples();
bool CountingSamples() const { return countingSamples; }
bool RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress record, VkDeviceAddress arguments, std::span<const std::uint32_t, 7> rules);
std::uint64_t SamplesTotal();
bool DumpSamples(VkDeviceAddress target, std::uint64_t address = 0);
Expand Down
7 changes: 7 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct ColorTarget {
std::uint32_t dccControl = 0;
std::uint64_t fmaskAddress = 0;
std::uint32_t fmaskPipeBankXor = 0;
std::uint16_t partialWriteMask = 0;
};

struct DepthTarget {
Expand Down Expand Up @@ -162,6 +163,7 @@ struct State {
float pointSizeMax = 0.0f;
std::array<std::uint16_t, 4> quadSampleMask{0xffffu, 0xffffu, 0xffffu, 0xffffu};
bool pixelEarlyZ = false;
bool pixelKill = false;
};

// PA_CL_VS_OUT_CNTL bits that place position exports (clip and cull distances, the misc and the two
Expand All @@ -186,6 +188,11 @@ bool SampleMaskFollowsOrder(const State& state, const SampleOrder& order);
std::array<std::uint16_t, 4> QuadSampleMasks(const State& state, const SampleOrder& order);
std::array<std::uint8_t, 8> ExportMappings(const State& state);
std::array<ShaderRecompiler::ColorExportPacking, 8> ExportPackings(const State& state);
std::uint16_t PackedWriteMask(std::uint8_t componentMapping, std::uint32_t exportedMask);
GuestTextureResource TargetMergeSurface(const ColorTarget& color);
bool DepthStencilWrites(const State& state);
bool DepthStencilTests(const State& state);
ShaderRecompiler::TargetMerge ExportTargetMerge(const State& state);
ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state);
bool PartialSampleMask(const State& state);
bool UniformSampleMask(const State& state);
Expand Down
24 changes: 23 additions & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std:
mix(state.blends.size());
for (const auto& color : state.colors) mix(color.exportIndex);
}
for (const auto& color : state.colors) {
if (color.partialWriteMask != 0u) mix(0x6d65726765000000ull | color.exportIndex);
}
mix(state.renderExtent.width);
mix(state.renderExtent.height);
mix(state.samples);
Expand Down Expand Up @@ -1322,6 +1325,10 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
APS5_LOG_CHARS_OUT_DEBUG("Index buffer range OK");
Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
if (draw.indexed) RequireNoMultisampleTarget(draw.indexAddress, static_cast<std::size_t>(indexBytes), "an index buffer read");
if (state.pixelKill && !state.pixelEarlyZ && std::ranges::any_of(state.colors, [](const ColorTarget& color) { return color.partialWriteMask != 0; })) {
const auto* recorder = Recorder::Active();
Require(recorder == nullptr || !recorder->CountingSamples(), "a partial write of a packed color target merges inside the pixel shader after early depth and stencil tests, which a shader that kills pixels under late tests cannot use while occlusion queries count samples: the early tests would count the pixels it kills");
}
APS5_LOG_CHARS_OUT_DEBUG("ValidateShaders");
if (recipe != nullptr) {
inputs.fragmentOutputs = recipe->fragmentOutputs;
Expand Down Expand Up @@ -2500,6 +2507,10 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
if (color.samples > 1) {
binding.multisampled = true;
timer.phase(PhaseSetup);
if (color.partialWriteMask != 0) {
static_cast<void>(MultisampleTargetView(context, color));
static_cast<void>(MultisampleTargetStorageView(context, TargetMergeSurface(color)));
}
targetViews.push_back(MultisampleTargetView(context, color, binding.multisampleOwner, &binding.multisampleImage));
timer.phase(PhaseReadTarget);
continue;
Expand Down Expand Up @@ -2731,7 +2742,18 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
const bool multisampled = prepared.multisampled = std::any_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.multisampled; });
Require(lean || !multisampled, "multisampled draws are only rendered as recorded draws");
for (const auto& binding : targets) {
if (binding.multisampled) Require(std::find(resources->MultisampleSources().begin(), resources->MultisampleSources().end(), binding.color.surfaceAddress) == resources->MultisampleSources().end(), "sampling a multisampled color target the draw renders to is not implemented");
if (!binding.multisampled) continue;
const auto& sources = resources->MultisampleSources();
const auto reads = std::count(sources.begin(), sources.end(), binding.color.surfaceAddress);
Require(reads == 0 || (reads == 1 && binding.color.partialWriteMask != 0), "sampling a multisampled color target the draw renders to is not implemented");
Require(binding.color.partialWriteMask == 0 || (reads == 1 && MultisampleTargetStorable(context, binding.color)), "the partial write of a multisampled packed color target does not store into the target's image");
if (binding.color.partialWriteMask != 0) static_cast<void>(MultisampleTargetStorageView(context, TargetMergeSurface(binding.color)));
}
for (const auto& binding : targets) {
if (binding.color.partialWriteMask == 0 || binding.multisampled) continue;
Require(lean && binding.resident != nullptr && !binding.proxied, "a partial write of a packed color target is merged into its resident image by a recorded draw only");
const auto images = resources->StorageImages();
Require(std::ranges::any_of(images, [&](const std::pair<VkImage, bool>& image) { return image.first == binding.resident->Image() && image.second; }), "the partial write of a packed color target does not store into the target's resident image");
}
if (!lean && !resolved.moved.empty()) {
resolved.resources = std::make_shared<ShaderResources>(context, shaders, state.color, static_cast<std::uint32_t>(state.colors.size()), draw.indexAddress, static_cast<std::size_t>(indexBytes), snapshots);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <optional>
#include <stdexcept>
#include <string>
Expand Down Expand Up @@ -292,6 +293,43 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
return result;
}

std::array<std::uint32_t, 8> EncodeTextureResource(const GuestTextureResource& resource) {
const bool multisampled = resource.dimension == TextureDimension::k2DMsaa;
Require((resource.dimension == TextureDimension::k2D && resource.samples == 1u) || (multisampled && resource.samples >= 2u && resource.samples <= 16u && std::has_single_bit(resource.samples)), "only 2D and multisampled 2D surfaces have a texture descriptor encoding");
Require(resource.mipCount == 1u && resource.allocatedMipCount <= 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.depthOrLastArray == 0u && resource.baseArray == 0u && resource.dccAddress == 0u && resource.minLod == 0u && !resource.iterate256 && resource.bcSwizzle == 0u, "only single-level, single-layer surfaces without metadata have a texture descriptor encoding");
Require(resource.width >= 1u && resource.width <= 0x4000u && resource.height >= 1u && resource.height <= 0x4000u && resource.format < 0x200u, "the surface's extent or format has no texture descriptor encoding");
const auto xorMask = resource.tileMode == TextureTileMode::kS4KBX || resource.tileMode == TextureTileMode::kD4KBX ? 0xfffull : 0xffffull;
Require(XorSwizzleMode(resource.tileMode) != 0 ? (resource.baseAddress & xorMask) == 0 && (resource.pipeBankXor & ~xorMask) == 0 : resource.pipeBankXor == 0, "the surface's pipe and bank XOR has no texture descriptor encoding");
const auto base = (resource.baseAddress | resource.pipeBankXor) >> 8u;
Require(base != 0 && (resource.baseAddress & 0xffu) == 0 && base <= 0xffffffffffull, "the surface's address has no texture descriptor encoding");
std::uint32_t tileMode = 0;
switch (resource.tileMode) {
case TextureTileMode::kLinear: tileMode = 0x00; break;
case TextureTileMode::kStandard256B: tileMode = 0x01; break;
case TextureTileMode::kD256B: tileMode = 0x02; break;
case TextureTileMode::kStandard4KB: tileMode = 0x05; break;
case TextureTileMode::kD4KB: tileMode = 0x06; break;
case TextureTileMode::kStandard64KB: tileMode = 0x09; break;
case TextureTileMode::kD64KB: tileMode = 0x0a; break;
case TextureTileMode::kS64KBT: tileMode = 0x11; break;
case TextureTileMode::kD64KBT: tileMode = 0x12; break;
case TextureTileMode::kS4KBX: tileMode = 0x15; break;
case TextureTileMode::kD4KBX: tileMode = 0x16; break;
case TextureTileMode::kZ64KBX: tileMode = 0x18; break;
case TextureTileMode::kS64KBX: tileMode = 0x19; break;
case TextureTileMode::kD64KBX: tileMode = 0x1a; break;
case TextureTileMode::kR64KBX: tileMode = 0x1b; break;
}
const auto sampleLevel = static_cast<std::uint32_t>(std::countr_zero(resource.samples));
std::array<std::uint32_t, 8> words{};
words[0] = static_cast<std::uint32_t>(base);
words[1] = static_cast<std::uint32_t>(base >> 32u) | (resource.format << 20u) | (((resource.width - 1u) & 3u) << 30u);
words[2] = ((resource.width - 1u) >> 2u) | ((resource.height - 1u) << 14u);
words[3] = resource.dstSelX | (resource.dstSelY << 3u) | (resource.dstSelZ << 6u) | (resource.dstSelW << 9u) | (sampleLevel << 16u) | (tileMode << 20u) | ((multisampled ? 14u : 9u) << 28u);
words[5] = sampleLevel << 4u;
return words;
}

float EffectiveMinLod(const GuestTextureResource& resource) {
if (resource.minLod <= resource.baseLevel * 256u) return 0.0f;
return std::min(static_cast<float>(resource.minLod) / 256.0f, static_cast<float>(resource.lastLevel));
Expand Down
Loading
Loading