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 @@ -2,6 +2,8 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <algorithm>
#include <spirv/unified1/spirv.hpp>

namespace AgcDriver::DriverDetail {

Expand Down Expand Up @@ -128,6 +130,11 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
product->pixel.alphaToCoverage = product->state.alphaToCoverage;
product->pixel.quadPixelMask = Graphics::QuadPixelMask(product->state);
product->pixel.quadSampleMask = Graphics::QuadSampleMasks(product->state);
if (product->pixel.stencilRefExportEnable && Graphics::StencilReferenceUnused(product->state)) {
const auto localDevice = device.Load();
const auto capabilities = localDevice == nullptr ? std::vector<std::uint32_t>{} : localDevice->Target().supportedCapabilities;
if (std::ranges::find(capabilities, static_cast<std::uint32_t>(spv::CapabilityStencilExportEXT)) == capabilities.end()) product->pixel.stencilRefExportEnable = false;
}
if (product->pixel.ancillary) product->pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product->state);
if (product->pixel.ancillary) product->pixel.layerExported = product->state.layerExports != 0;
product->pixel.ditheredAlphaToCoverage = product->state.ditheredAlphaToCoverage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <stdexcept>
#include <thread>
#include "Optimization/ShaderStageInputInfo.hpp"
#include <spirv/unified1/spirv.hpp>
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"

namespace AgcDriver::DriverDetail {
Expand Down Expand Up @@ -797,6 +798,8 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
const auto count = RegisterValue(state.context, 0x1b6) & 0x3fu;
if (registration && count != 0) return nullptr;
pixel = Graphics::DecodePixelStageInfo(state.context, {});
const auto& capabilities = device.Target().supportedCapabilities;
if (pixel->stencilRefExportEnable && std::ranges::find(capabilities, static_cast<std::uint32_t>(spv::CapabilityStencilExportEXT)) == capabilities.end()) return nullptr;
wave = pixel->wave32 ? 32u : 64u;
} else {
const auto routing = RegisterValue(state.context, 0x2d5);
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 @@ -248,6 +248,7 @@ inline constexpr std::array<DrawKeyRange, 50> DrawKeyRegisters{{
{RegisterBank::UserConfig, 0x242, 1}, {RegisterBank::UserConfig, 0x24b, 1}, {RegisterBank::UserConfig, 0x25b, 1},
}};
bool SplitsAlternateAlpha(const State& state);
bool StencilReferenceUnused(const State& state);
// Whether DrawKeyRegisters holds the read.
bool DrawKeyCovers(RegisterRead read);

Expand Down
20 changes: 20 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,26 @@ bool SplitsAlternateAlpha(const State& state) {
return false;
}

bool StencilReferenceUnused(const State& state) {
if (!state.depth || !state.stencilTest) return true;
if (state.depthTest && (state.depthCompare < VK_COMPARE_OP_NEVER || state.depthCompare > VK_COMPARE_OP_ALWAYS)) return false;
const auto faceUnused = [&](const VkStencilOpState& face) {
if (face.compareOp < VK_COMPARE_OP_NEVER || face.compareOp > VK_COMPARE_OP_ALWAYS) return false;
for (const auto op : {face.failOp, face.passOp, face.depthFailOp}) {
if (op < VK_STENCIL_OP_KEEP || op > VK_STENCIL_OP_DECREMENT_AND_WRAP) return false;
}
if (face.compareOp != VK_COMPARE_OP_ALWAYS && face.compareOp != VK_COMPARE_OP_NEVER && (face.compareMask & 0xffu) != 0) return false;
if ((face.writeMask & 0xffu) == 0) return true;
if (state.depthBoundsTest) return face.failOp != VK_STENCIL_OP_REPLACE && face.passOp != VK_STENCIL_OP_REPLACE && face.depthFailOp != VK_STENCIL_OP_REPLACE;
const bool passes = face.compareOp == VK_COMPARE_OP_ALWAYS || face.compareOp == VK_COMPARE_OP_EQUAL || face.compareOp == VK_COMPARE_OP_LESS_OR_EQUAL || face.compareOp == VK_COMPARE_OP_GREATER_OR_EQUAL;
if (!passes) return face.failOp != VK_STENCIL_OP_REPLACE;
const bool depthPass = !state.depthTest || state.depthCompare != VK_COMPARE_OP_NEVER;
const bool depthFail = state.depthTest && state.depthCompare != VK_COMPARE_OP_ALWAYS;
return (!depthPass || face.passOp != VK_STENCIL_OP_REPLACE) && (!depthFail || face.depthFailOp != VK_STENCIL_OP_REPLACE);
};
return faceUnused(state.stencilFront) && faceUnused(state.stencilBack);
}

bool DrawKeyCovers(RegisterRead read) {
// One bit per register offset of each bank, set from the table once.
static const auto covered = [] {
Expand Down
152 changes: 152 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5580,6 +5580,153 @@ void stencilRefExportTests() {
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
}

bool stencilComparison(VkCompareOp op, std::uint32_t reference, std::uint32_t stencil) {
switch (op) {
case VK_COMPARE_OP_NEVER: return false;
case VK_COMPARE_OP_LESS: return reference < stencil;
case VK_COMPARE_OP_EQUAL: return reference == stencil;
case VK_COMPARE_OP_LESS_OR_EQUAL: return reference <= stencil;
case VK_COMPARE_OP_GREATER: return reference > stencil;
case VK_COMPARE_OP_NOT_EQUAL: return reference != stencil;
case VK_COMPARE_OP_GREATER_OR_EQUAL: return reference >= stencil;
case VK_COMPARE_OP_ALWAYS: return true;
default: throw std::runtime_error("invalid stencil comparison in the reference oracle");
}
}

std::pair<std::uint32_t, bool> stencilReferenceOutcome(const AgcDriver::Graphics::State& state, const VkStencilOpState& face, std::uint32_t reference, std::uint32_t stencil, bool depthPass, bool boundsPass) {
if (state.depthBoundsTest && !boundsPass) return {stencil, false};
const bool stencilPass = stencilComparison(face.compareOp, reference & face.compareMask & 0xffu, stencil & face.compareMask & 0xffu);
if (!state.depthTest || state.depthCompare == VK_COMPARE_OP_ALWAYS) depthPass = true;
if (state.depthTest && state.depthCompare == VK_COMPARE_OP_NEVER) depthPass = false;
const auto op = !stencilPass ? face.failOp : !depthPass ? face.depthFailOp : face.passOp;
auto updated = stencil;
switch (op) {
case VK_STENCIL_OP_KEEP: break;
case VK_STENCIL_OP_ZERO: updated = 0; break;
case VK_STENCIL_OP_REPLACE: updated = reference; break;
case VK_STENCIL_OP_INCREMENT_AND_CLAMP: updated = std::min(stencil + 1u, 255u); break;
case VK_STENCIL_OP_DECREMENT_AND_CLAMP: updated = stencil == 0 ? 0 : stencil - 1u; break;
case VK_STENCIL_OP_INVERT: updated = ~stencil; break;
case VK_STENCIL_OP_INCREMENT_AND_WRAP: updated = stencil + 1u; break;
case VK_STENCIL_OP_DECREMENT_AND_WRAP: updated = stencil - 1u; break;
default: throw std::runtime_error("invalid stencil operation in the reference oracle");
}
const auto writeMask = face.writeMask & 0xffu;
return {((stencil & ~writeMask) | (updated & writeMask)) & 0xffu, stencilPass && depthPass};
}

void stencilReferenceUnusedTests() {
using AgcDriver::Graphics::StencilReferenceUnused;
AgcDriver::Graphics::State state{};
state.depth.emplace();
state.depth->format = VK_FORMAT_D32_SFLOAT_S8_UINT;
state.stencilTest = true;
state.stencilFront = {VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, 37u};
state.stencilBack = state.stencilFront;
std::size_t accepted = 0;
std::size_t countermodels = 0;
const auto verify = [&](const auto& candidate, bool unused, bool witness, std::string_view name) {
Require(StencilReferenceUnused(candidate) == unused, "wrong unused stencil reference decision for " + std::string(name));
bool changed = false;
for (const auto& face : {candidate.stencilFront, candidate.stencilBack}) {
for (std::uint32_t stencil = 0; stencil < 256u; ++stencil) {
for (const bool depthPass : {false, true}) {
for (const bool boundsPass : {false, true}) {
const auto fixed = stencilReferenceOutcome(candidate, face, face.reference, stencil, depthPass, boundsPass);
for (std::uint32_t reference = 0; reference < 256u; ++reference) {
changed |= stencilReferenceOutcome(candidate, face, reference, stencil, depthPass, boundsPass) != fixed;
}
}
}
}
}
Require(!unused || !changed, "dropping the stencil reference changes a sample outcome for " + std::string(name));
Require(!witness || changed, "the dependent stencil reference has no countermodel for " + std::string(name));
accepted += unused;
countermodels += witness;
};
verify(state, true, false, "ALWAYS with KEEP");
for (const auto op : {VK_STENCIL_OP_ZERO, VK_STENCIL_OP_INCREMENT_AND_CLAMP, VK_STENCIL_OP_DECREMENT_AND_CLAMP, VK_STENCIL_OP_INVERT, VK_STENCIL_OP_INCREMENT_AND_WRAP, VK_STENCIL_OP_DECREMENT_AND_WRAP}) {
auto independent = state;
independent.stencilFront.passOp = op;
independent.stencilBack = independent.stencilFront;
verify(independent, true, false, "reference-independent update");
}
for (std::uint32_t comparison = 0; comparison < 8u; ++comparison) {
auto masked = state;
masked.stencilFront.compareOp = static_cast<VkCompareOp>(comparison);
masked.stencilFront.compareMask = 0x100u;
masked.stencilBack = masked.stencilFront;
verify(masked, true, false, "effective zero compare mask");
if (comparison != VK_COMPARE_OP_ALWAYS && comparison != VK_COMPARE_OP_NEVER) {
auto dependent = masked;
dependent.stencilBack.compareMask = 0x80u;
dependent.stencilBack.writeMask = 0;
verify(dependent, false, true, "dependent back-face comparison with no writes");
}
}
auto replace = state;
replace.stencilFront.passOp = VK_STENCIL_OP_REPLACE;
verify(replace, false, true, "reachable front pass REPLACE");
replace.stencilFront.passOp = VK_STENCIL_OP_KEEP;
replace.stencilBack.failOp = VK_STENCIL_OP_REPLACE;
verify(replace, true, false, "unreachable back fail REPLACE");
replace.stencilBack.compareOp = VK_COMPARE_OP_NEVER;
verify(replace, false, true, "reachable back fail REPLACE");
replace.stencilBack.failOp = VK_STENCIL_OP_KEEP;
replace.stencilBack.passOp = VK_STENCIL_OP_REPLACE;
replace.stencilBack.depthFailOp = VK_STENCIL_OP_REPLACE;
verify(replace, true, false, "NEVER with unreachable depth operations");
replace = state;
replace.stencilBack.passOp = VK_STENCIL_OP_REPLACE;
replace.stencilBack.writeMask = 0x100u;
verify(replace, true, false, "effective zero write mask");
replace.stencilBack.writeMask = 0x80u;
verify(replace, false, true, "partially writable REPLACE");
replace = state;
replace.stencilBack.depthFailOp = VK_STENCIL_OP_REPLACE;
verify(replace, true, false, "disabled depth test");
replace.depthTest = true;
replace.depthCompare = VK_COMPARE_OP_ALWAYS;
verify(replace, true, false, "always passing depth test");
replace.depthCompare = VK_COMPARE_OP_LESS;
verify(replace, false, true, "possibly failing depth test");
replace.depthCompare = VK_COMPARE_OP_NEVER;
verify(replace, false, true, "always failing depth test");
replace.stencilBack.depthFailOp = VK_STENCIL_OP_KEEP;
replace.stencilBack.passOp = VK_STENCIL_OP_REPLACE;
verify(replace, true, false, "unreachable depth pass REPLACE");
replace = state;
replace.depthBoundsTest = true;
verify(replace, true, false, "bounds with reference-independent operations");
replace.stencilBack.depthFailOp = VK_STENCIL_OP_REPLACE;
Require(!StencilReferenceUnused(replace), "depth bounds admitted an unchecked REPLACE operation");
auto clear = state;
clear.stencilFront = {VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, 7u};
clear.stencilBack = clear.stencilFront;
verify(clear, false, true, "clear replacement state");
for (const bool front : {false, true}) {
auto invalid = state;
(front ? invalid.stencilFront : invalid.stencilBack).compareOp = static_cast<VkCompareOp>(8u);
Require(!StencilReferenceUnused(invalid), "an unknown stencil comparison lost its export");
invalid = state;
(front ? invalid.stencilFront : invalid.stencilBack).passOp = static_cast<VkStencilOp>(8u);
Require(!StencilReferenceUnused(invalid), "an unknown stencil operation lost its export");
}
auto invalidDepth = state;
invalidDepth.depthTest = true;
invalidDepth.depthCompare = static_cast<VkCompareOp>(8u);
Require(!StencilReferenceUnused(invalidDepth), "an unknown depth comparison lost its stencil export");
auto inactive = clear;
inactive.stencilTest = false;
Require(StencilReferenceUnused(inactive), "a disabled stencil test retained the export");
inactive.stencilTest = true;
inactive.depth.reset();
Require(StencilReferenceUnused(inactive), "an absent attachment retained the export");
std::cout << "Unused stencil reference sample equivalence passed: " << accepted << " accepted states, " << countermodels << " dependent countermodels\n";
}

std::set<std::string> bufferViews(std::span<const std::uint32_t> words) {
std::set<std::string> views;
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
Expand Down Expand Up @@ -6753,6 +6900,10 @@ int main(int argc, char** argv) {
setenv("APS5_HEAP_MIRROR_MIB", "4", 1);
#endif
try {
if (argc == 2 && std::string_view(argv[1]) == "--unused-stencil-reference") {
stencilReferenceUnusedTests();
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--sparse-buffer-holes") {
bool passed = false;
{
Expand Down Expand Up @@ -6881,6 +7032,7 @@ int main(int argc, char** argv) {
earlyFragmentTestsTests();
orderedPixelShaderTests();
stencilRefExportTests();
stencilReferenceUnusedTests();
aliasedBufferViewTests();
LineStripTests();
ProvokingVertexTests();
Expand Down
Loading
Loading