Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include <algorithm>
#include <array>
#include <spirv/unified1/spirv.hpp>
Expand Down Expand Up @@ -193,6 +194,10 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
Graphics::NormalizeUnusedViewportDepth(product->state, product->pixel);
Graphics::ApplyDepthRemap(context, product->state);
Graphics::ApplyDepthBoundsEmulation(context, product->state, product->pixel);
if (product->state.depthBoundsDiscard) {
require(std::ranges::any_of(product->programs, [](const DrawProgram& program) { return program.binary.stage == ShaderRecompiler::ShaderStage::Fragment; }), "the depth bounds test without a pixel program needs the depthBounds feature");
product->pixel.depthBoundsDiscard = {true, Graphics::DepthBoundsSourceWords(*product->state.depth)};
}
product->pixel.targetExportPacking = Graphics::ExportPackings(product->state);
product->pixel.dualSourceBlend = product->state.dualSourceBlend;
product->pixel.reversedBlend = product->state.reversedBlend;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
mix(blend.writeMask);
for (const auto word : blend.constants) mix(word);
}
mix(pixel.depthBoundsDiscard.enabled);
for (const auto word : pixel.depthBoundsDiscard.descriptor) mix(word);
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 @@ -263,7 +263,7 @@ 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.targetMerges != q.targetMerges || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.ditheredAlphaSamples != q.ditheredAlphaSamples || 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 (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.targetMerges != q.targetMerges || p.depthBoundsDiscard != q.depthBoundsDiscard || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.ditheredAlphaSamples != q.ditheredAlphaSamples || 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 @@ -101,6 +101,7 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const
std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip);
bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage);
std::array<std::uint32_t, 8> DepthCopySourceWords(const DepthTarget& target);
std::array<std::uint32_t, 8> DepthBoundsSourceWords(const DepthTarget& target);
class DepthCopySourceScope {
public:
explicit DepthCopySourceScope(bool active);
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 @@ -126,6 +126,7 @@ struct State {
bool depthCopySource = false;
VkCompareOp depthCompare = VK_COMPARE_OP_ALWAYS;
bool depthBoundsTest = false;
bool depthBoundsDiscard = false;
float minDepthBounds = 0.0f;
float maxDepthBounds = 1.0f;
bool depthBias = false;
Expand Down
6 changes: 6 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3956,6 +3956,12 @@ std::array<std::uint32_t, 8> DepthCopySourceWords(const DepthTarget& target) {
return {static_cast<std::uint32_t>(base), static_cast<std::uint32_t>((base >> 32u) & 255u) | (22u << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (type << 28u) | (target.depthSwizzle << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, word6, static_cast<std::uint32_t>(metadata >> 8u)};
}

std::array<std::uint32_t, 8> DepthBoundsSourceWords(const DepthTarget& target) {
auto words = DepthCopySourceWords(target);
words[3] = (words[3] & ~0xfffu) | 4u | (5u << 3u) | (6u << 6u) | (7u << 9u);
return words;
}

bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage) {
std::lock_guard lock(surfacesMutex());
for (auto& [key, entry] : layeredStorages()) {
Expand Down
12 changes: 12 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,16 @@ void patchDepthRemapPush(std::array<std::byte, PipelinePushConstantBytes>& block
std::memcpy(block.data() + ShaderRecompiler::DepthRemapPushOffsetBytes, words.data(), sizeof(words));
}

void patchDepthBoundsPush(std::array<std::byte, PipelinePushConstantBytes>& block, const State& state, std::span<const CompiledShader> shaders) {
if (!state.depthBoundsDiscard) return;
const auto fragment = std::ranges::find_if(shaders, [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::Fragment; });
Require(fragment != shaders.end() && fragment->program->depthBoundsDword != 0xffffffffu, "the emulated depth bounds test has no pixel program reading its bounds");
const auto offset = fragment->pushConstantOffset + fragment->program->depthBoundsDword * sizeof(std::uint32_t);
const std::array<float, 2> bounds{state.minDepthBounds, state.maxDepthBounds};
Require(offset <= block.size() - sizeof(bounds), "the depth bounds lie outside the push block");
std::memcpy(block.data() + offset, bounds.data(), sizeof(bounds));
}

void recordStencilReplay(const Context& context, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, const DrawInputs& inputs, std::span<const std::pair<std::uint32_t, std::shared_ptr<Pipeline>>> replay, std::span<const CompiledShader> shaders, const ShaderResources& resources, const std::array<std::byte, PipelinePushConstantBytes>* bytes = nullptr, VkShaderStageFlags stages = 0) {
State selected = state;
auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders);
Expand All @@ -2496,6 +2506,7 @@ void recordStencilReplay(const Context& context, VkCommandBuffer commands, const
stages = PushConstantStages(shaders);
}
patchDepthRemapPush(block, state);
patchDepthBoundsPush(block, state, shaders);
const auto fragment = std::ranges::find_if(shaders, [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::Fragment && !shader.program->stencilReferences.empty(); });
Require(fragment != shaders.end(), "stencil replay fragment stage is missing");
const auto referenceOffset = fragment->pushConstantOffset + fragment->program->stencilReferenceDword * sizeof(std::uint32_t);
Expand Down Expand Up @@ -2536,6 +2547,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const
stages = PushConstantStages(shaders);
}
patchDepthRemapPush(block, state);
patchDepthBoundsPush(block, state, shaders);
if (state.stages.tessellation) {
for (const auto& shader : shaders) Require(shader.program->pushConstants.empty() || shader.pushConstantOffset >= PipelinePushSlotBytes || shader.pushConstantOffset + shader.program->pushConstants.size() <= ShaderRecompiler::TessellationLevelPushOffsetBytes, "a stage's push constants overlap the tessellation levels");
const std::array<float, ShaderRecompiler::TessellationLevelPushBytes / 4> levels{state.stages.minTessellationLevel, state.stages.maxTessellationLevel};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2279,7 +2279,8 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size();
allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic;
const auto& push = shader.program->pushConstants;
const auto pushDataBytes = shader.program->stencilReferences.empty() ? push.size() : shader.program->stencilReferenceDword * sizeof(std::uint32_t);
const auto driverDword = std::min(shader.program->stencilReferences.empty() ? 0xffffffffu : shader.program->stencilReferenceDword, shader.program->depthBoundsDword);
const auto pushDataBytes = driverDword == 0xffffffffu ? push.size() : driverDword * sizeof(std::uint32_t);
if (pushDataBytes != 0u) {
const auto position = shader.program->memoryOffsetDword * 4u + element;
Require(position < pushDataBytes, "guest buffer offset lies outside the shader's push constants");
Expand Down
18 changes: 12 additions & 6 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1427,14 +1427,20 @@ ReversedBlend TranslateReversedBlend(std::uint32_t blendControl, std::uint8_t co
void ApplyDepthBoundsEmulation(const Context& context, State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel) {
if (!state.depthBoundsTest || !state.depth || context.depthBounds) return;
ValidateDepthBounds(context, state);
Require(!context.depthRangeUnrestricted, "the depth bounds test without the depthBounds feature is emulated only where stored depth stays in [0, 1]");
Require(state.depth->format == VK_FORMAT_D32_SFLOAT || state.depth->format == VK_FORMAT_D32_SFLOAT_S8_UINT, "the depth bounds test on a normalized depth target needs the depthBounds feature");
Require(!state.depthTest && !state.depthWrite, "the depth bounds test with depth testing needs the depthBounds feature");
Require(!pixel.posZ && !pixel.depthExportEnable, "the depth bounds test from a pixel program that reads or exports depth needs the depthBounds feature");
const bool floatDepth = state.depth->format == VK_FORMAT_D32_SFLOAT || state.depth->format == VK_FORMAT_D32_SFLOAT_S8_UINT;
const auto keepsOnFail = [](const VkStencilOpState& face) { return (face.writeMask & 0xffu) == 0 || (face.failOp == VK_STENCIL_OP_KEEP && face.depthFailOp == VK_STENCIL_OP_KEEP); };
Require(!state.stencilTest || (keepsOnFail(state.stencilFront) && keepsOnFail(state.stencilBack)), "the depth bounds test with a stencil test that writes on a failed test needs the depthBounds feature");
const bool upper = state.minDepthBounds <= 0.0f;
Require(upper || state.maxDepthBounds >= 1.0f, "the depth bounds test with both bounds inside (0, 1) needs the depthBounds feature");
const bool oneSided = (upper || state.maxDepthBounds >= 1.0f) && !context.depthRangeUnrestricted && floatDepth && !state.depthTest && !state.depthWrite && !pixel.posZ && !pixel.depthExportEnable && (!state.stencilTest || (keepsOnFail(state.stencilFront) && keepsOnFail(state.stencilBack)));
if (!oneSided) {
Require(floatDepth, "the depth bounds test on a normalized depth target needs the depthBounds feature");
Require(state.depth->samples == 1u, "the depth bounds test on a multisampled depth target needs the depthBounds feature");
Require(!state.depthWrite, "the depth bounds test in a draw that writes depth needs the depthBounds feature");
const auto keeps = [](const VkStencilOpState& face) { return (face.writeMask & 0xffu) == 0 || (face.failOp == VK_STENCIL_OP_KEEP && face.passOp == VK_STENCIL_OP_KEEP && face.depthFailOp == VK_STENCIL_OP_KEEP); };
Require(!state.stencilTest || (keeps(state.stencilFront) && keeps(state.stencilBack)), "the depth bounds test in a draw that writes stencil needs the depthBounds feature");
state.depthBoundsTest = false;
state.depthBoundsDiscard = true;
return;
}
const float bound = upper ? state.maxDepthBounds : state.minDepthBounds;
state.depthBoundsTest = false;
state.depthTest = true;
Expand Down
54 changes: 48 additions & 6 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5162,14 +5162,36 @@ void DepthBoundsEmulationTests() {
change(state, context, program);
expectFailure([&] { AgcDriver::Graphics::ApplyDepthBoundsEmulation(context, state, program); }, expected);
};
refuses("both bounds inside", [](auto& state, auto&, auto&) { state.minDepthBounds = 0.25f; });
refuses("with depth testing", [](auto& state, auto&, auto&) { state.depthTest = true; });
refuses("reads or exports depth", [](auto&, auto&, auto& program) { program.posZ = true; });
refuses("reads or exports depth", [](auto&, auto&, auto& program) { program.depthExportEnable = true; });
const auto discards = [&](const char* what, auto change) {
auto state = base;
state.minDepthBounds = 0.0f;
state.maxDepthBounds = 0.5f;
auto context = emulating;
auto program = pixel;
change(state, context, program);
const auto before = state;
AgcDriver::Graphics::ApplyDepthBoundsEmulation(context, state, program);
auto expected = before;
expected.depthBoundsTest = false;
expected.depthBoundsDiscard = true;
Require(state.depthBoundsDiscard && !state.depthBoundsTest && state.depthTest == expected.depthTest && state.depthCompare == expected.depthCompare && state.depthBias == expected.depthBias && state.viewport.minDepth == expected.viewport.minDepth && state.viewport.maxDepth == expected.viewport.maxDepth && state.minDepthBounds == expected.minDepthBounds && state.maxDepthBounds == expected.maxDepthBounds, what);
};
discards("bounds inside (0, 1) were not tested by the pixel program", [](auto& state, auto&, auto&) { state.minDepthBounds = 0.25f; });
discards("a depth bounds test with depth testing was not tested by the pixel program", [](auto& state, auto&, auto&) {
state.depthTest = true;
state.depthCompare = VK_COMPARE_OP_GREATER_OR_EQUAL;
});
discards("a pixel program that reads depth did not test the depth bounds itself", [](auto&, auto&, auto& program) { program.posZ = true; });
discards("a pixel program that exports depth did not test the depth bounds itself", [](auto&, auto&, auto& program) { program.depthExportEnable = true; });
discards("a device with an unrestricted depth range did not test the depth bounds in the pixel program", [](auto&, auto& context, auto&) { context.depthRangeUnrestricted = true; });
discards("a stencil test that keeps every value did not test the depth bounds in the pixel program", [](auto& state, auto&, auto&) {
state.minDepthBounds = 0.25f;
state.stencilTest = true;
state.stencilFront.writeMask = state.stencilBack.writeMask = 0xffu;
});
refuses("normalized depth target", [](auto& state, auto&, auto&) { state.depth->format = VK_FORMAT_D16_UNORM; });
refuses("stays in [0, 1]", [](auto&, auto& context, auto&) { context.depthRangeUnrestricted = true; });
refuses("depth bounds outside [0, 1]", [](auto& state, auto&, auto&) { state.maxDepthBounds = 1.5f; });
refuses("writes on a failed test", [](auto& state, auto&, auto&) {
refuses("writes stencil", [](auto& state, auto&, auto&) {
state.stencilTest = true;
state.stencilFront.writeMask = 0xffu;
state.stencilFront.failOp = VK_STENCIL_OP_REPLACE;
Expand Down Expand Up @@ -10312,6 +10334,26 @@ void stencilRefExportTests() {
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(pushedRequest)); }, "unused push constant dword");
pushedRequest.layout.pushConstantSizeBytes = 8u;
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(pushedRequest)); }, "crosses a stage slot");
{
const AgcDriver::Graphics::DepthTarget boundedDepth{0x10000, 0, {64, 64}, VK_FORMAT_D32_SFLOAT, 0.0f, 0};
for (const auto [shaderControl, code, stencil] : {std::tuple{0x53u, std::span<const std::uint32_t>(pushedDepthStencil), true}, std::tuple{0x51u, std::span<const std::uint32_t>(pushedDepthStencil), false}}) {
auto boundedRequest = stencilRefExportRequest(shaderControl, plain, code);
boundedRequest.context.userData = pushedDepth;
boundedRequest.context.pixel->depthBoundsDiscard = {true, AgcDriver::Graphics::DepthBoundsSourceWords(boundedDepth)};
const auto driverDwords = stencil ? 3u : 2u;
for (const auto [dwords, shaderDataPushed] : {std::pair{driverDwords + 1u, true}, std::pair{driverDwords, false}}) {
boundedRequest.layout = {0u, 0u, 0u, dwords * 4u};
const auto bounded = ShaderRecompiler::Recompile(boundedRequest);
const auto first = shaderDataPushed ? 1u : 0u;
Require(bounded.depthBoundsDword == first + (stencil ? 1u : 0u) && bounded.stencilReferenceDword == (stencil ? first : 0xffffffffu) && bounded.pushConstants.size() == (first + driverDwords) * sizeof(std::uint32_t), "the depth bounds dwords were not placed after the shader data that fits beside them, or at the stage start without it");
Require(shaderDataPushed || std::ranges::any_of(bounded.bindings, [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::ShaderData; }), "shader data that leaves no room for the depth bounds dwords did not move to its storage buffer");
const auto handle = ShaderRecompiler::PrepareShader(boundedRequest);
Require(ShaderRecompiler::MatchesPreparedShader(boundedRequest, *handle), "a depth bounds artifact did not match the push range it was prepared for");
}
boundedRequest.layout = {0u, 0u, 0u, (driverDwords - 1u) * 4u};
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(boundedRequest)); }, "requires two unused push constant dwords");
}
}
const std::array<std::uint32_t, 4> noStencilExport{0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u};
auto noStencilRequest = stencilRefExportRequest(0x52u, plain, noStencilExport);
noStencilRequest.layout.pushConstantSizeBytes = 0u;
Expand Down
Loading
Loading