diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 004f06c57..615b0d15e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -788,6 +788,14 @@ if(BUILD_TESTING) set_tests_properties(image_sample_gather PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(pixel_ancillary_tests ${LIBS_INCLUDE_DIR}/tests/PixelAncillary.cpp) + target_link_libraries(pixel_ancillary_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(pixel_ancillary_tests) + add_test(NAME pixel_ancillary COMMAND pixel_ancillary_tests) + if(WIN32) + set_tests_properties(pixel_ancillary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_indexed_draw_tests ${LIBS_INCLUDE_DIR}/tests/GuestIndexedDraw.cpp) target_include_directories(guest_indexed_draw_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_indexed_draw_tests PRIVATE libSceAgc libSceAgcDriver libc) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index c4bf9972a..a3c85be11 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -894,6 +894,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u if (enabled.shaderImageGatherExtended) state->capabilities.push_back(spv::CapabilityImageGatherExtended); enabled.shaderResourceMinLod = available.shaderResourceMinLod; if (enabled.shaderResourceMinLod) state->capabilities.push_back(spv::CapabilityMinLod); + enabled.sampleRateShading = available.sampleRateShading; + enabled.geometryShader = available.geometryShader; + if (enabled.geometryShader) state->capabilities.push_back(spv::CapabilityGeometry); + enabled.shaderClipDistance = available.shaderClipDistance; if (enabled.shaderStorageImageWriteWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageWriteWithoutFormat); if (enabled.shaderStorageImageReadWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageReadWithoutFormat); // Bindless image tables index an image array with a wave-uniform runtime slot. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index cab2912f0..53288a665 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -72,6 +72,19 @@ constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u); +bool zFormatSupported(std::uint32_t format) { + return format == 0u || format == 1u || format == 2u || format == 3u || format == 9u; +} + +std::uint32_t shaderControlMask(std::uint32_t zFormat) { + constexpr std::uint32_t zExportEnable = 0x1u; + constexpr std::uint32_t maskExportEnable = 0x100u; + auto mask = ShaderControlMask; + if (zFormat != 0u && zFormatSupported(zFormat)) mask &= ~zExportEnable; + if (zFormat == 9u) mask &= ~maskExportEnable; + return mask; +} + // Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth // target as if their tests always passed (wrong occlusion, but the draws run), so stages that // depth-test everything can exercise the draw paths before depth targets exist. @@ -469,7 +482,7 @@ State DecodeState(const QueueState& queue) { Require((depthControl & 8u) == 0 || result.depth.has_value(), "depth bounds without a depth surface"); Require((depthControl & 0xc0000000u) == 0, "depth-conditional color writes are unsupported"); } - zero(cx, 0x203, ShaderControlMask, "depth export, shader coverage or ordered fragment execution"); + zero(cx, 0x203, shaderControlMask(read(cx, 0x1c4)), "depth export, shader coverage or ordered fragment execution"); zero(cx, 0x2dc, AlphaToCoverageMask, "alpha-to-coverage"); zero(cx, 0x2f8, ~0u, "multisampling or coverage conversion"); zero(cx, 0x292, ScanModeMask, "scan conversion mode"); @@ -515,7 +528,7 @@ State DecodeState(const QueueState& queue) { // CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written. if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl)); - zero(cx, 0x1c4, ~0u, "depth or sample-mask export"); + zero(cx, 0x1c4, zFormatSupported(read(cx, 0x1c4)) ? 0u : ~0u, "depth or sample-mask export"); const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u; APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat); // SPI_SHADER_POS_FORMAT: POS0 must be a 4-component position; later vectors carry the misc/clip @@ -758,7 +771,9 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (auto reason = require((word & 8u) == 0 || surface, "depth bounds without a depth surface"); !reason.empty()) return reason; if (auto reason = require((word & 0xc0000000u) == 0, "depth-conditional color writes are unsupported"); !reason.empty()) return reason; } - if (auto reason = nonzero(cx, 0x203, ShaderControlMask, "depth export, shader coverage or ordered fragment execution"); !reason.empty()) return reason; + std::uint32_t zFormat = 0; + static_cast(value(cx, 0x1c4, zFormat)); + if (auto reason = nonzero(cx, 0x203, shaderControlMask(zFormat), "depth export, shader coverage or ordered fragment execution"); !reason.empty()) return reason; if (auto reason = nonzero(cx, 0x2dc, AlphaToCoverageMask, "alpha-to-coverage"); !reason.empty()) return reason; if (auto reason = nonzero(cx, 0x2f8, ~0u, "multisampling or coverage conversion"); !reason.empty()) return reason; if (auto reason = nonzero(cx, 0x292, ScanModeMask, "scan conversion mode"); !reason.empty()) return reason; @@ -775,7 +790,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason; std::uint32_t targetMask = 0, shaderMask = 0; if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word); - if (auto reason = nonzero(cx, 0x1c4, ~0u, "depth or sample-mask export"); !reason.empty()) return reason; + if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason; if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue); for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) { if (find(cx, offset) != cx.end()) continue; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 66757ee7d..6e73e36dc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -582,6 +582,35 @@ void DepthStencilTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DCC 3D color targets"); } +// SPI_SHADER_Z_FORMAT (0x1c4) and the export enables of DB_SHADER_CONTROL (0x203): Z export needs a +// format with a depth channel (1, 2, 3 or 32_ABGR 9), the sample mask needs 32_ABGR, and the +// formats the export path does not lay out are refused. A missing 0x1c4 gives no verdict here. +void ZExportTests() { + constexpr std::uint32_t zExportEnable = 0x1u; + constexpr std::uint32_t maskExportEnable = 0x100u; + const auto withExports = [](std::uint32_t format, std::uint32_t exports) { + auto queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + queue.context[0x1c4] = format; + queue.context[0x203] = 0x800u | exports; + return queue; + }; + for (const std::uint32_t format : {1u, 2u, 3u, 9u}) { + const auto queue = withExports(format, zExportEnable); + Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a Z export with format " + std::to_string(format) + " was rejected"); + } + Require(!AgcDriver::Graphics::DrawRejection(withExports(0, zExportEnable), true).empty(), "a Z export without a Z format was accepted"); + Require(AgcDriver::Graphics::DrawRejection(withExports(9, zExportEnable | maskExportEnable), true).empty(), "a sample-mask export with 32_ABGR was rejected"); + Require(!AgcDriver::Graphics::DrawRejection(withExports(1, zExportEnable | maskExportEnable), true).empty(), "a sample-mask export with 32_R was accepted"); + for (std::uint32_t format = 4; format <= 8; ++format) { + Require(!AgcDriver::Graphics::DrawRejection(withExports(format, 0), true).empty(), "Z format " + std::to_string(format) + " was accepted"); + } + auto absent = withExports(0, 0); + absent.context.erase(0x1c4); + Require(AgcDriver::Graphics::DrawRejection(absent, true).empty(), "a missing SPI_SHADER_Z_FORMAT was rejected (or threw) in the precheck"); +} + void DepthBoundsBiasTests() { const auto bits = [](float value) { std::uint32_t word = 0; @@ -2041,6 +2070,7 @@ int main() { hardwareScreenOffsetTests(); DepthClipTests(); DepthStencilTests(); + ZExportTests(); DepthBoundsBiasTests(); conservativeZExportTests(); DisabledColorTests(); diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp new file mode 100644 index 000000000..2d6f10dad --- /dev/null +++ b/core/libs/tests/PixelAncillary.cpp @@ -0,0 +1,64 @@ +#include "IntermediateRepresentation/IrBuilder.hpp" +#include "Optimization/ConstantFolder.hpp" +#include "Optimization/DeadCodeEliminator.hpp" +#include "Optimization/ShaderInfoCollector.hpp" +#include +#include +#include + +using namespace ShaderRecompiler; +static void Require(bool value) { if (!value) throw std::runtime_error("packed pixel ancillary regression"); } +static IrValue& Build(IrProgram& program, IrOpcode opcode, std::uint32_t offset, std::uint32_t count) { + program.Resources().stage = IrShaderStage::Pixel; + program.Resources().resourceTrackingComplete = true; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + IrBuilder builder(program); + builder.SetInsertionPoint(block); + auto& ancillary = builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::PackedAncillary)), &builder.Constant(0u)}); + auto& user = opcode == IrOpcode::BitwiseOr32 ? builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset)}) + : builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset), &builder.Constant(count)}); + static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&user})); + static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); + return user; +} +static void Lower(IrProgram& program) { + ConstantFolder().Fold(program); + DeadCodeEliminator().RemoveIdentities(program); + DeadCodeEliminator().Eliminate(program); + const ShaderPixelInputInfo pixel {}; + ShaderInfoCollector().Collect(program, ShaderStageInputInfo {nullptr, &pixel, nullptr}); +} +static void Extract(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, StageInputKind kind, std::uint32_t fieldOffset) { + IrProgram program; + auto& user = Build(program, opcode, offset, count); + Lower(program); + const IrValue* field = user.Argument(0)->Resolve(); + Require(field->Opcode() == IrOpcode::GetBuiltin); + Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == kind); + Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); + Require(user.Argument(2)->Resolve()->ImmediateU32() == count); +} +static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) { + IrProgram program; + static_cast(Build(program, opcode, offset, count)); + try { + Lower(program); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()).find("unsupported live use") != std::string::npos); + return; + } + Require(false); +} +int main() { + Extract(IrOpcode::BitFieldUExtract, 8u, 4u, StageInputKind::SampleId, 0u); + Extract(IrOpcode::BitFieldUExtract, 9u, 2u, StageInputKind::SampleId, 1u); + Extract(IrOpcode::BitFieldUExtract, 16u, 13u, StageInputKind::Layer, 0u); + Extract(IrOpcode::BitFieldSExtract, 20u, 9u, StageInputKind::Layer, 4u); + Refused(IrOpcode::BitwiseOr32, 1u, 0u); + Refused(IrOpcode::BitFieldUExtract, 2u, 4u); + Refused(IrOpcode::BitFieldUExtract, 10u, 4u); + Refused(IrOpcode::BitFieldUExtract, 13u, 2u); + Refused(IrOpcode::BitFieldUExtract, 16u, 14u); +} diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index 8f87d21de..a05a6c7a8 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -2,8 +2,11 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include +#include #include #include +#include +#include namespace ShaderRecompiler { namespace { @@ -181,12 +184,68 @@ bool foldCompositeExtract(IrBuilder& builder, IrValue& inst, IrOpcode construct, return false; } +void collectUses(IrValue& value, std::vector& uses) { + for (const IrUse& use : value.OperandUses()) { + if (use.user->Opcode() == IrOpcode::Identity) { + collectUses(*use.user, uses); + } else { + uses.push_back(use); + } + } +} + +bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) { + auto& kind = resolveArg(ancillary, 0); + if (!isImmediate(kind, IrType::U32) || static_cast(kind.ImmediateU32()) != StageInputKind::PackedAncillary) { + return false; + } + std::vector uses; + collectUses(ancillary, uses); + std::array fields {}; + const auto field = [&](std::size_t index) -> IrValue& { + if (fields[index] == nullptr) { + const auto builtin = index == 0u ? StageInputKind::SampleId : StageInputKind::Layer; + IrValue& created = program.CreateValue(IrOpcode::GetBuiltin, IrType::U32); + created.AddArgument(&builder.Constant(static_cast(builtin))); + created.AddArgument(&builder.Constant(0u)); + ancillary.Parent()->InsertInstructionBefore(&ancillary, &created); + fields[index] = &created; + } + return *fields[index]; + }; + constexpr std::array, 2> ranges {{{8u, 4u}, {16u, 13u}}}; + bool lowered = false; + for (const IrUse& use : uses) { + IrValue& user = *use.user; + if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) { + continue; + } + auto& offset = resolveArg(user, 1); + auto& count = resolveArg(user, 2); + if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u) { + continue; + } + const auto range = std::ranges::find_if(ranges, [&](const auto& candidate) { + return offset.ImmediateU32() >= candidate.first && count.ImmediateU32() <= candidate.second && + offset.ImmediateU32() - candidate.first <= candidate.second - count.ImmediateU32(); + }); + if (range == ranges.end()) { + continue; + } + user.ReplaceArgument(0, &field(static_cast(range - ranges.begin()))); + user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - range->first)); + lowered = true; + } + return lowered; +} + } // namespace bool ConstantFolder::tryFoldValue(IrProgram& program, IrValue& value) const { IrBuilder builder(program); switch (value.Opcode()) { case IrOpcode::Phi: return foldPhi(value); + case IrOpcode::GetBuiltin: return lowerPackedAncillary(program, builder, value); case IrOpcode::SelectU1: case IrOpcode::SelectF32: case IrOpcode::SelectU32: return foldSelect(value); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index 27eea3e66..25654e7f7 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -137,10 +137,13 @@ void DefineModule(SpirvEmitterState& state) { if (state.cullDistanceVariable != 0) { state.module.EmitCapability(spv::CapabilityCullDistance); } - if (state.layerVariable != 0 || InputVariableForKind(state, StageInputKind::Layer) != 0) { + if (state.layerVariable != 0) { state.module.RequireVersion(0x00010500u); state.module.EmitCapability(spv::CapabilityShaderLayer); } + if (InputVariableForKind(state, StageInputKind::Layer) != 0) { + state.module.EmitCapability(spv::CapabilityGeometry); + } if (state.viewportIndexVariable != 0) { state.module.RequireVersion(0x00010500u); state.module.EmitCapability(spv::CapabilityShaderViewportIndex);