mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
feat(agc): remap a viewport depth range the device cannot express into the vertex shader
Without VK_EXT_depth_range_unrestricted a viewport depth range outside [0, 1] threw. Minecraft (PPSA17221) draws its world with scale 1 and offset 0 in negative-one-to-one clip space, so its depth is the raw z/w clamped by PA_SC_VPORT_ZMIN/ZMAX. On such a device the vertex shader now scales clip z so z/w is the console's depth, the pipeline clamps depth at the console's bounds, and two clip distances restore the near and far planes that depth clamping removes. Mode, scale and offset travel as pipeline specialization constants from the draw's vertex stage info, in both the per-draw and the registration-module path; mesh and tessellation draws with such a range still throw. The console's depth clamp bounds are clipped to [0, 1]: exact for a normalized depth target or none, and reported once for a tested or written float depth target, where a depth below 0 is stored as 0 (Minecraft clamps its float depth target to [-1, 1]). The shader validator accepts the remap's two extra clip distances next to the one a program may already export. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
0bb238b4a4
commit
53212b18ac
26 files changed
+356
-23
No files matched your search
@@ -2536,6 +2536,20 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_depth_range_remap_tests EXCLUDE_FROM_ALL tests/DepthRangeRemap.cpp)
|
||||
target_link_libraries(agc_depth_range_remap_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_depth_range_remap_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_depth_range_remap_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_depth_range_remap_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_depth_range_remap COMMAND agc_depth_range_remap_tests)
|
||||
set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_position_invariance_tests EXCLUDE_FROM_ALL tests/PositionInvariance.cpp)
|
||||
target_link_libraries(agc_position_invariance_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_position_invariance_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
||||
@@ -157,8 +157,8 @@ private:
|
||||
void reportDrawRelocation();
|
||||
static bool sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b);
|
||||
static bool sameDecode(const DrawDecode& a, const DrawDecode& b);
|
||||
std::shared_ptr<DrawDecode> decodeDraw(const QueueState& queue, const Submission& submission);
|
||||
void resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile);
|
||||
std::shared_ptr<DrawDecode> decodeDraw(const QueueState& queue, const Submission& submission, const Graphics::Context& context);
|
||||
void resolveDrawDecode(const QueueState& queue, const Submission& submission, const Graphics::Context& context, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile);
|
||||
void lookupDraw(const Submission& submission, const std::shared_ptr<VulkanDevice>& localDevice, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<DrawProgram>& programs, const std::vector<ShaderRecompiler::ProgramRole>& roles, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, bool useDrawEntries, bool registerKey, bool profile, std::uint64_t& drawKey, std::shared_ptr<DrawEntry>& entry, std::vector<std::shared_ptr<DispatchVariant>>& matched, std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool& drawHit, bool& verifyHit, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, const std::shared_ptr<DrawEntry>& dataEntry, DrawDataCandidates& dataCandidates, std::vector<StageDataHit>& stageData);
|
||||
std::shared_ptr<const ShaderRecompiler::RecompileResult> materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector<DrawProgram>& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<ShaderRecompiler::LinkedProgram>& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr<VulkanDevice>& localDevice, ShaderMemory& shaderMemory, std::vector<StageCapture>& stageCaptures, std::vector<bool>& recompiled, bool drawHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, const std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, std::string& rejected);
|
||||
static void recordStageDataPositions(DispatchVariant& variant, const ShaderRecompiler::ResourceCapture& capture, const std::vector<Graphics::DecodeRead>& decodeReads);
|
||||
|
||||
@@ -331,9 +331,7 @@ public:
|
||||
void EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
|
||||
bool PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span<const Graphics::CompiledShader> shaders);
|
||||
|
||||
private:
|
||||
VulkanDevice(const PresentationWindow* window, bool compileOnly);
|
||||
friend struct VulkanDeviceTestAccess;
|
||||
public:
|
||||
// The device's Graphics::Context: a copy of the one built at setup (its instance functions
|
||||
// resolved then, its function table filled then), or with APS5_NO_CONTEXT_CACHE=1 built anew.
|
||||
Graphics::Context graphicsContext() const;
|
||||
@@ -341,6 +339,10 @@ private:
|
||||
std::uint32_t computeSubgroupSize(const ShaderRecompiler::RecompileResult& shader) const;
|
||||
VkPipelineShaderStageCreateFlags computeStageFlags(const ShaderRecompiler::RecompileResult& shader) const;
|
||||
std::shared_ptr<ComputePipelineObjects> createComputePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span<const std::uint32_t> layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress);
|
||||
|
||||
private:
|
||||
VulkanDevice(const PresentationWindow* window, bool compileOnly);
|
||||
friend struct VulkanDeviceTestAccess;
|
||||
ShaderRecompiler::SpirvTarget buildTarget() const;
|
||||
Graphics::Context buildContext() const;
|
||||
// Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -162,7 +163,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
if (program.merged) InitializeMerged(queue, program, program.mergedPointer, program.mergedPointerRequired, false);
|
||||
}
|
||||
if (verifyDrawDataHits()) {
|
||||
const auto full = decodeDraw(queue, submission);
|
||||
const auto full = decodeDraw(queue, submission, localDevice->graphicsContext());
|
||||
if (!sameDecode(*refreshed, *full)) {
|
||||
std::size_t differing = 0;
|
||||
while (differing < full->programs.size() && differing < refreshed->programs.size() && full->programs[differing].userData == refreshed->programs[differing].userData) ++differing;
|
||||
@@ -173,7 +174,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
}
|
||||
decode = std::move(refreshed);
|
||||
}
|
||||
resolveDrawDecode(queue, submission, decode, registerKey, drawKey, profile);
|
||||
resolveDrawDecode(queue, submission, localDevice->graphicsContext(), decode, registerKey, drawKey, profile);
|
||||
const auto& graphics = decode->state;
|
||||
auto pixel = decode->pixel;
|
||||
std::vector<DrawProgram> programs = decode->programs;
|
||||
@@ -241,6 +242,11 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
decodeReads[i].clear();
|
||||
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr);
|
||||
if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports;
|
||||
if (vertexInfos[i]) {
|
||||
vertexInfos[i]->depthRemapMode = graphics.depthRemap.mode;
|
||||
vertexInfos[i]->depthRemapScale = std::bit_cast<std::uint32_t>(graphics.depthRemap.scale);
|
||||
vertexInfos[i]->depthRemapOffset = std::bit_cast<std::uint32_t>(graphics.depthRemap.offset);
|
||||
}
|
||||
};
|
||||
if (!registerKey) {
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i);
|
||||
|
||||
@@ -121,9 +121,10 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission) {
|
||||
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission, const Graphics::Context& context) {
|
||||
auto product = std::make_shared<DrawDecode>();
|
||||
product->state = Graphics::DecodeState(queue, VertexWorkgroupRequired(queue, *submission.shaders));
|
||||
Graphics::ApplyDepthRemap(context, product->state);
|
||||
DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true);
|
||||
product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue));
|
||||
product->pixel.targetExportPacking = Graphics::ExportPackings(product->state);
|
||||
@@ -143,11 +144,11 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
|
||||
return product;
|
||||
}
|
||||
|
||||
void Driver::resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile) {
|
||||
void Driver::resolveDrawDecode(const QueueState& queue, const Submission& submission, const Graphics::Context& context, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile) {
|
||||
if (decode == nullptr || verifyDrawRecipe()) {
|
||||
std::vector<Graphics::RegisterRead> readLog;
|
||||
const Graphics::RegisterReadLogScope logScope(verifyDrawRecipe() && registerKey ? &readLog : nullptr);
|
||||
auto fresh = decodeDraw(queue, submission);
|
||||
auto fresh = decodeDraw(queue, submission, context);
|
||||
if (verifyDrawRecipe() && registerKey) {
|
||||
std::uint64_t facadeMismatches = 0;
|
||||
for (const auto read : readLog) {
|
||||
|
||||
@@ -214,6 +214,8 @@ void Driver::traceDrawKeyLookup(const std::array<std::uint64_t, 5>& components,
|
||||
|
||||
bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) {
|
||||
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false;
|
||||
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded) return false;
|
||||
if (a.depthRemapMode != b.depthRemapMode || a.depthRemapScale != b.depthRemapScale || a.depthRemapOffset != b.depthRemapOffset) return false;
|
||||
for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) {
|
||||
if (a.resources[i].fields != b.resources[i].fields) return false;
|
||||
const auto& x = a.resourcesDst[i];
|
||||
|
||||
@@ -3495,6 +3495,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const {
|
||||
target.float64DenormPreserve = state->float64DenormPreserve;
|
||||
target.float16DenormPreserve = state->float16DenormPreserve;
|
||||
target.spannedWaves = state->spannedWaves;
|
||||
target.depthRangeRemap = !state->depthRangeUnrestricted;
|
||||
target.fixedPushSlots = state->graphicsPipelineLibrary;
|
||||
target.registrationModules = state->graphicsPipelineLibrary;
|
||||
target.robustBufferAccess2 = state->robustBufferAccess2;
|
||||
|
||||
@@ -125,6 +125,16 @@ struct State {
|
||||
VkViewport viewport;
|
||||
bool negativeOneToOne;
|
||||
bool depthClamp = false;
|
||||
float depthScale = 1;
|
||||
float depthOffset = 0;
|
||||
float depthClampMin = 0;
|
||||
float depthClampMax = 1;
|
||||
struct DepthRemap {
|
||||
std::uint32_t mode = 0;
|
||||
float scale = 1;
|
||||
float offset = 0;
|
||||
bool operator==(const DepthRemap&) const = default;
|
||||
} depthRemap;
|
||||
bool alphaToCoverage = false;
|
||||
bool ditheredAlphaToCoverage = false;
|
||||
bool perSampleShading = false;
|
||||
@@ -150,6 +160,7 @@ inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u)
|
||||
|
||||
ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
State DecodeState(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
void ApplyDepthRemap(const Context& context, State& state);
|
||||
std::array<VkSampleLocationEXT, 32> SampleLocations(const State& state);
|
||||
VkExtent2D SampleLocationGrid(const State& state);
|
||||
std::array<std::uint8_t, 8> ExportMappings(const State& state);
|
||||
|
||||
@@ -1019,6 +1019,29 @@ ReversedBlend TranslateReversedBlend(std::uint32_t blendControl, std::uint8_t co
|
||||
return {blend, routing->code};
|
||||
}
|
||||
|
||||
void ApplyDepthRemap(const Context& context, State& state) {
|
||||
const auto& viewport = state.viewport;
|
||||
const bool inRange = viewport.minDepth >= 0 && viewport.minDepth <= 1 && viewport.maxDepth >= 0 && viewport.maxDepth <= 1;
|
||||
if (context.depthRangeUnrestricted || inRange) return;
|
||||
Require(state.stages.path == ShaderPath::Vertex && !state.stages.mesh && !state.stages.tessellation, "viewport depth outside [0, 1] is remapped on the vertex path only");
|
||||
Require(context.depthClamp, "viewport depth outside [0, 1] needs depth clamping to stand in for the console's clamp");
|
||||
const float clampMin = std::clamp(state.depthClampMin, 0.0f, 1.0f);
|
||||
const float clampMax = std::clamp(state.depthClampMax, 0.0f, 1.0f);
|
||||
const bool floatDepth = state.depth.has_value() && (state.depth->format == VK_FORMAT_D32_SFLOAT || state.depth->format == VK_FORMAT_D32_SFLOAT_S8_UINT);
|
||||
if ((clampMin != state.depthClampMin || clampMax != state.depthClampMax) && floatDepth && (state.depthWrite || state.depthTest)) {
|
||||
static bool reported = false;
|
||||
if (!reported) {
|
||||
reported = true;
|
||||
std::fprintf(stderr, "[gpu] viewport depth clamp bounds [%g, %g] on a float depth target are clipped to [0, 1]: the device has no VK_EXT_depth_range_unrestricted\n", state.depthClampMin, state.depthClampMax);
|
||||
}
|
||||
}
|
||||
state.depthRemap = {state.negativeOneToOne ? 2u : 1u, state.depthScale, state.depthOffset};
|
||||
state.viewport.minDepth = clampMin;
|
||||
state.viewport.maxDepth = clampMax;
|
||||
state.negativeOneToOne = false;
|
||||
state.depthClamp = true;
|
||||
}
|
||||
|
||||
State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) throw std::runtime_error(reason);
|
||||
const auto& cx = queue.context;
|
||||
@@ -1232,6 +1255,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
Require(readFloat(cx, 0xb4) <= readFloat(cx, 0xb5), "inverted viewport depth clamp bounds");
|
||||
result.depthScale = zs;
|
||||
result.depthOffset = zo;
|
||||
result.depthClampMin = readFloat(cx, 0xb4);
|
||||
result.depthClampMax = readFloat(cx, 0xb5);
|
||||
result.viewport = collapsed ? VkViewport{xo, yo, 1, 1, minDepth, maxDepth} : VkViewport{xo - xs, yo - ys, 2 * xs, 2 * ys, minDepth, maxDepth};
|
||||
if (const auto renderControl = find(cx, 0x000); result.depth && renderControl != cx.end() && (renderControl->second & 1u) != 0) {
|
||||
result.viewport.minDepth = result.depth->clearDepth;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "Recompiler.hpp"
|
||||
#include "PipelineSpecialization.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
struct ModuleFacts {
|
||||
std::map<std::uint32_t, std::uint32_t> specializationDefaults;
|
||||
std::uint32_t clipDistanceLength = 0;
|
||||
bool clipDistanceBuiltIn = false;
|
||||
std::size_t multiplies = 0;
|
||||
};
|
||||
|
||||
ModuleFacts inspect(const ShaderRecompiler::RecompileResult& result) {
|
||||
ModuleFacts facts;
|
||||
const auto& words = result.spirv.Words();
|
||||
std::map<std::uint32_t, std::uint32_t> specIds;
|
||||
std::map<std::uint32_t, std::uint32_t> specValues;
|
||||
std::map<std::uint32_t, std::uint32_t> constants;
|
||||
std::map<std::uint32_t, std::uint32_t> arrayLengths;
|
||||
std::map<std::uint32_t, std::uint32_t> pointerTargets;
|
||||
std::map<std::uint32_t, std::uint32_t> variableTypes;
|
||||
std::set<std::uint32_t> clipVariables;
|
||||
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
|
||||
const auto op = static_cast<spv::Op>(words[at] & 0xffffu);
|
||||
const auto count = words[at] >> 16u;
|
||||
if (op == spv::OpDecorate && count == 4 && words[at + 2] == spv::DecorationSpecId) specIds[words[at + 1]] = words[at + 3];
|
||||
if (op == spv::OpDecorate && count == 4 && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInClipDistance) clipVariables.insert(words[at + 1]);
|
||||
if (op == spv::OpSpecConstant && count == 4) specValues[words[at + 2]] = words[at + 3];
|
||||
if (op == spv::OpConstant && count == 4) constants[words[at + 2]] = words[at + 3];
|
||||
if (op == spv::OpTypeArray && count == 4) arrayLengths[words[at + 1]] = words[at + 3];
|
||||
if (op == spv::OpTypePointer && count == 4) pointerTargets[words[at + 1]] = words[at + 3];
|
||||
if (op == spv::OpVariable && count >= 4) variableTypes[words[at + 2]] = words[at + 1];
|
||||
if (op == spv::OpFMul) ++facts.multiplies;
|
||||
}
|
||||
for (const auto& [id, specId] : specIds) {
|
||||
if (const auto value = specValues.find(id); value != specValues.end()) facts.specializationDefaults[specId] = value->second;
|
||||
}
|
||||
for (const auto variable : clipVariables) {
|
||||
facts.clipDistanceBuiltIn = true;
|
||||
const auto pointer = variableTypes.find(variable);
|
||||
if (pointer == variableTypes.end()) continue;
|
||||
const auto array = pointerTargets.find(pointer->second);
|
||||
if (array == pointerTargets.end()) continue;
|
||||
const auto length = arrayLengths.find(array->second);
|
||||
if (length == arrayLengths.end()) continue;
|
||||
const auto constant = constants.find(length->second);
|
||||
if (constant != constants.end()) facts.clipDistanceLength = constant->second;
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint32_t, 3> PositionExport{0xf80008cfu, 0u, 0xbf810000u};
|
||||
|
||||
ShaderRecompiler::RecompileRequest baseRequest(bool remap) {
|
||||
using namespace ShaderRecompiler;
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Vertex, 0x10000u, PositionExport, 0, {}};
|
||||
request.context.waveSize = 64;
|
||||
ShaderVertexStageInfo vertex{};
|
||||
vertex.depthRemapMode = 2u;
|
||||
vertex.depthRemapScale = std::bit_cast<std::uint32_t>(1.0f);
|
||||
vertex.depthRemapOffset = std::bit_cast<std::uint32_t>(0.0f);
|
||||
request.context.vertex = vertex;
|
||||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 64;
|
||||
static constexpr std::array<std::uint32_t, 2> capabilities{spv::CapabilityShader, spv::CapabilityClipDistance};
|
||||
request.target.supportedCapabilities = capabilities;
|
||||
request.target.depthRangeRemap = remap;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
return request;
|
||||
}
|
||||
|
||||
void require(bool condition, const char* what) {
|
||||
if (!condition) throw std::runtime_error(what);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
using namespace ShaderRecompiler;
|
||||
try {
|
||||
const auto remapped = inspect(Recompile(baseRequest(true)));
|
||||
const auto plain = inspect(Recompile(baseRequest(false)));
|
||||
require(remapped.clipDistanceBuiltIn && remapped.clipDistanceLength == plain.clipDistanceLength + 2u, "the remap did not add its two clip distances");
|
||||
require(remapped.multiplies >= plain.multiplies + 2u, "the remap does not scale the clip z and offset it by w");
|
||||
require(!plain.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !remapped.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 3u), "unexpected specialization constants");
|
||||
std::cout << "Depth range remap tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "Depth range remap test failed: " << error.what() << '\n';
|
||||
for (const bool remap : {true, false}) {
|
||||
const auto facts = inspect(Recompile(baseRequest(remap)));
|
||||
std::cerr << " remap=" << remap << ": spec ids";
|
||||
for (const auto& [id, value] : facts.specializationDefaults) std::cerr << ' ' << id << '=' << value;
|
||||
std::cerr << "; clip builtin " << facts.clipDistanceBuiltIn << " length " << facts.clipDistanceLength << "; fmul " << facts.multiplies << '\n';
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -3533,6 +3533,56 @@ void quadPixelMaskTests() {
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateSampleMask(context, varying, storing); }, "differs between the pixels of a quad");
|
||||
}
|
||||
|
||||
void DepthRemapTests() {
|
||||
auto queue = makeState();
|
||||
queue.context[0x204] = 0;
|
||||
queue.context[0x113] = std::bit_cast<std::uint32_t>(1.0f);
|
||||
queue.context[0x114] = 0;
|
||||
AgcDriver::Graphics::Context remapping{};
|
||||
remapping.depthClamp = true;
|
||||
auto state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.negativeOneToOne && state.viewport.minDepth == -1 && state.viewport.maxDepth == 1 && state.depthRemap.mode == 0, "a negative-one-to-one depth transform decoded with a remap");
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, state);
|
||||
Require(state.depthRemap.mode == 2 && state.depthRemap.scale == 1.0f && state.depthRemap.offset == 0.0f, "the remap did not take the viewport's depth scale and offset");
|
||||
Require(state.viewport.minDepth == 0 && state.viewport.maxDepth == 1 && !state.negativeOneToOne && state.depthClamp, "the remapped viewport does not clamp at the console's bounds in zero-to-one clip space");
|
||||
AgcDriver::Graphics::Context unrestricted{};
|
||||
unrestricted.depthRangeUnrestricted = true;
|
||||
auto kept = AgcDriver::Graphics::DecodeState(queue);
|
||||
AgcDriver::Graphics::ApplyDepthRemap(unrestricted, kept);
|
||||
Require(kept.depthRemap.mode == 0 && kept.viewport.minDepth == -1 && kept.negativeOneToOne && !kept.depthClamp, "an unrestricted device remapped the depth range");
|
||||
AgcDriver::Graphics::Context noClamp{};
|
||||
auto refused = AgcDriver::Graphics::DecodeState(queue);
|
||||
expectFailure([&] { AgcDriver::Graphics::ApplyDepthRemap(noClamp, refused); }, "depth clamping");
|
||||
queue.context[0x113] = std::bit_cast<std::uint32_t>(0.5f);
|
||||
queue.context[0x114] = std::bit_cast<std::uint32_t>(0.5f);
|
||||
auto inRange = AgcDriver::Graphics::DecodeState(queue);
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, inRange);
|
||||
Require(inRange.depthRemap.mode == 0 && inRange.negativeOneToOne && !inRange.depthClamp, "an in-range depth transform was remapped");
|
||||
queue.context[0x204] = 0x80000u;
|
||||
queue.context[0x113] = std::bit_cast<std::uint32_t>(2.0f);
|
||||
queue.context[0x114] = std::bit_cast<std::uint32_t>(-0.5f);
|
||||
queue.context[0xb4] = std::bit_cast<std::uint32_t>(0.25f);
|
||||
queue.context[0xb5] = std::bit_cast<std::uint32_t>(0.75f);
|
||||
auto bounded = AgcDriver::Graphics::DecodeState(queue);
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, bounded);
|
||||
Require(bounded.depthRemap.mode == 1 && bounded.depthRemap.scale == 2.0f && bounded.depthRemap.offset == -0.5f && bounded.viewport.minDepth == 0.25f && bounded.viewport.maxDepth == 0.75f, "a zero-to-one remap did not take the clamp bounds");
|
||||
queue.context[0xb4] = std::bit_cast<std::uint32_t>(-1.0f);
|
||||
queue.context[0xb5] = std::bit_cast<std::uint32_t>(2.0f);
|
||||
auto clipped = AgcDriver::Graphics::DecodeState(queue);
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, clipped);
|
||||
Require(clipped.depthRemap.mode == 1 && clipped.viewport.minDepth == 0 && clipped.viewport.maxDepth == 1, "clamp bounds beyond [0, 1] were not clipped to the representable depth range");
|
||||
auto unorm = AgcDriver::Graphics::DecodeState(queue);
|
||||
unorm.depth = AgcDriver::Graphics::DepthTarget{0x10000, 0, {64, 64}, VK_FORMAT_D16_UNORM, 0.0f, 0};
|
||||
unorm.depthTest = unorm.depthWrite = true;
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, unorm);
|
||||
Require(unorm.viewport.minDepth == 0 && unorm.viewport.maxDepth == 1, "a normalized depth target refused clipped clamp bounds");
|
||||
auto floating = AgcDriver::Graphics::DecodeState(queue);
|
||||
floating.depth = AgcDriver::Graphics::DepthTarget{0x10000, 0, {64, 64}, VK_FORMAT_D32_SFLOAT, 0.0f, 0};
|
||||
floating.depthWrite = true;
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, floating);
|
||||
Require(floating.depthRemap.mode == 1 && floating.viewport.minDepth == 0 && floating.viewport.maxDepth == 1 && floating.depthClamp, "a written float depth target refused clipped clamp bounds");
|
||||
}
|
||||
|
||||
void DepthClipTests() {
|
||||
auto queue = makeState();
|
||||
const auto direct = AgcDriver::Graphics::DecodeState(queue);
|
||||
@@ -8612,6 +8662,7 @@ int main(int argc, char** argv) {
|
||||
hardwareScreenOffsetTests();
|
||||
srgb8TargetTests();
|
||||
DepthClipTests();
|
||||
DepthRemapTests();
|
||||
DepthStencilTests();
|
||||
OneDimensionalColorTests();
|
||||
htileDepthClearTests();
|
||||
|
||||
@@ -3216,7 +3216,7 @@ void verifyPixelRequestSerialization() {
|
||||
minimal.context.pixel->layerExported = true;
|
||||
minimal.context.pixel->ditheredAlphaToCoverage = true;
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQSAAAAA=", "new requests did not use serialization version 32");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQSEAAAA=", "new requests did not use serialization version 33");
|
||||
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
|
||||
auto withFallbacks = minimal;
|
||||
withFallbacks.context.linkedCallFallbacks = fallbacks;
|
||||
@@ -3334,7 +3334,7 @@ void verifyPixelRequestSerialization() {
|
||||
for (std::size_t bytes = 0; bytes < 45u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSEAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSIAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
@@ -3483,24 +3483,28 @@ void verifyVertexInfoVersion19() {
|
||||
require(std::equal(maskedBytes.begin(), maskedBytes.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u), sampledBytes.begin()) && sampledBytes[maskOffset + 1u] == 0x1u && sampledBytes[maskOffset + 5u] == 0x2u && sampledBytes[maskOffset + 9u] == 0x4u && sampledBytes[maskOffset + 13u] == 0x8u, "the quad sample masks are not the four words after the quad pixel mask");
|
||||
auto version29 = sampledBytes;
|
||||
version29.pop_back();
|
||||
version29.pop_back();
|
||||
version29.erase(version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes), version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u));
|
||||
version29[4] = 29u;
|
||||
const auto replay29 = serializer.Deserialize(encodeBase64(version29));
|
||||
require(replay29.request.context.pixel.has_value() && replay29.request.context.pixel->quadSampleMask == std::array<std::uint16_t, 4>{0x1u, 0x2u, 0x4u, 0x8u} && replay29.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay29.request)) == sampledBytes, "a version 29 request read a reversed-order blend code or misread the fields after the quad sample masks");
|
||||
auto version28 = sampledBytes;
|
||||
version28.pop_back();
|
||||
version28.pop_back();
|
||||
version28.erase(version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u), version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u));
|
||||
version28[4] = 28u;
|
||||
const auto replay28 = serializer.Deserialize(encodeBase64(version28));
|
||||
require(replay28.request.context.pixel.has_value() && replay28.request.context.pixel->quadPixelMask == 0x2u && replay28.request.context.pixel->quadSampleMask == std::array<std::uint16_t, 4>{0xffffu, 0xffffu, 0xffffu, 0xffffu} && replay28.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay28.request)) == maskedBytes, "a version 28 request read quad sample masks or a reversed-order blend code, or misread the fields after them");
|
||||
auto version27 = maskedBytes;
|
||||
version27.pop_back();
|
||||
version27.pop_back();
|
||||
version27.erase(version27.begin() + static_cast<std::ptrdiff_t>(maskOffset), version27.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u));
|
||||
version27[4] = 27u;
|
||||
const auto replay27 = serializer.Deserialize(encodeBase64(version27));
|
||||
require(replay27.request.context.pixel.has_value() && replay27.request.context.pixel->quadPixelMask == 0xfu && replay27.request.context.pixel->reversedBlend == 0u && serializer.Serialize(replay27.request) == encodedPixel, "a version 27 request read a quad pixel mask or reversed-order blend code, or misread the fields after it");
|
||||
auto legacyPixel = plainPixel;
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset + 2u + sampleMaskBytes + 4u));
|
||||
legacyPixel[4] = 26u;
|
||||
const auto replay26 = serializer.Deserialize(encodeBase64(legacyPixel));
|
||||
|
||||
@@ -208,6 +208,9 @@ private:
|
||||
append(key, value.fetchBufferReg);
|
||||
append(key, value.fetchEmbedded);
|
||||
append(key, value.paClVsOutCntl);
|
||||
append(key, value.depthRemapMode);
|
||||
append(key, value.depthRemapScale);
|
||||
append(key, value.depthRemapOffset);
|
||||
if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count");
|
||||
append(key, value.resourcesNum);
|
||||
for (std::uint32_t i = 0; i < value.resourcesNum; ++i) {
|
||||
@@ -257,6 +260,7 @@ private:
|
||||
append(key, value.srgbDecodeFormats);
|
||||
append(key, value.narrowSubgroupClock);
|
||||
append(key, value.float64DenormPreserve);
|
||||
append(key, value.depthRangeRemap);
|
||||
append(key, value.registrationModules);
|
||||
append(key, value.registrationWordLimit);
|
||||
append(key, value.vertexSubgroups);
|
||||
|
||||
@@ -778,7 +778,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(32u);
|
||||
writer.WriteU32(33u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
@@ -804,6 +804,12 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
writer.WriteBool(request.target.float16DenormPreserve);
|
||||
writer.WriteU32Span(request.context.linkedCallFallbacks);
|
||||
writer.WriteBool(request.target.spannedWaves);
|
||||
writer.WriteBool(request.target.depthRangeRemap);
|
||||
if (request.context.vertex.has_value()) {
|
||||
writer.WriteU32(request.context.vertex->depthRemapMode);
|
||||
writer.WriteU32(request.context.vertex->depthRemapScale);
|
||||
writer.WriteU32(request.context.vertex->depthRemapOffset);
|
||||
}
|
||||
return base64Encode(buffer);
|
||||
}
|
||||
|
||||
@@ -812,7 +818,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
||||
Reader reader(decoded);
|
||||
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
|
||||
const auto version = reader.ReadU32();
|
||||
if (version < 1u || version > 32u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 33u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
DeserializedRequest result{};
|
||||
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
|
||||
result.request.context = readGuestContext(reader, result, version);
|
||||
@@ -849,6 +855,15 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
||||
result.request.context.linkedCallFallbacks = result.linkedCallFallbacks;
|
||||
}
|
||||
if (version >= 32u) result.request.target.spannedWaves = reader.ReadBool();
|
||||
if (version >= 33u) {
|
||||
result.request.target.depthRangeRemap = reader.ReadBool();
|
||||
if (result.vertex.has_value()) {
|
||||
result.vertex->depthRemapMode = reader.ReadU32();
|
||||
result.vertex->depthRemapScale = reader.ReadU32();
|
||||
result.vertex->depthRemapOffset = reader.ReadU32();
|
||||
result.request.context.vertex = result.vertex;
|
||||
}
|
||||
}
|
||||
if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,12 @@ struct ShaderStageRuntime {
|
||||
}
|
||||
};
|
||||
|
||||
struct ShaderDepthRemap {
|
||||
std::uint32_t mode = 0;
|
||||
std::uint32_t scaleBits = 0;
|
||||
std::uint32_t offsetBits = 0;
|
||||
};
|
||||
|
||||
struct ShaderClipSpaceTransform {
|
||||
float scale[2] = {};
|
||||
float offset[2] = {};
|
||||
@@ -137,6 +143,7 @@ struct ShaderVertexInputInfo {
|
||||
std::uint32_t hostSubgroupSize = 64;
|
||||
std::uint32_t paClVsOutCntl = 0;
|
||||
ShaderClipSpaceTransform clipSpace;
|
||||
ShaderDepthRemap depthRemap;
|
||||
ShaderMeshInputInfo mesh;
|
||||
ShaderTessellationInputInfo tess;
|
||||
bool fetchExternal = false;
|
||||
|
||||
@@ -30,6 +30,8 @@ inline constexpr std::uint32_t RasterPrimitive = 8233u;
|
||||
inline constexpr std::uint32_t LayerExported = 8234u;
|
||||
inline constexpr std::uint32_t StencilReferenceOffset = 8235u;
|
||||
inline constexpr std::uint32_t ReversedBlend = 8236u;
|
||||
inline constexpr std::uint32_t DepthRemapBase = 8240u;
|
||||
inline constexpr std::uint32_t DepthRemapWords = 3u;
|
||||
inline constexpr std::uint32_t HeapCountBase = 8256u;
|
||||
inline constexpr std::uint32_t MipCountBase = 8320u;
|
||||
inline constexpr std::uint32_t DescriptorIndexBase = 16384u;
|
||||
|
||||
@@ -527,6 +527,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program)
|
||||
targetOptions.nonConstantImageOffsets = request.target.nonConstantImageOffsets;
|
||||
targetOptions.narrowSubgroupClock = request.target.narrowSubgroupClock;
|
||||
targetOptions.float64DenormPreserve = request.target.float64DenormPreserve;
|
||||
targetOptions.depthRangeRemap = request.target.depthRangeRemap;
|
||||
targetOptions.registrationModules = registration;
|
||||
targetOptions.fragmentDescriptorSet = request.target.registrationModules;
|
||||
targetOptions.float16DenormPreserve = request.target.float16DenormPreserve;
|
||||
@@ -905,6 +906,12 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
||||
if (variant.info.info.rasterPrimitive) moduleKey.push_back(static_cast<std::uint32_t>(request.context.pixel->rasterPrimitive));
|
||||
if (variant.info.info.layerExported) moduleKey.push_back(request.context.pixel->layerExported ? 1u : 0u);
|
||||
}
|
||||
const bool depthRemap = request.target.depthRangeRemap && request.shader.stage == ShaderStage::Vertex && request.context.vertex.has_value();
|
||||
if (depthRemap) {
|
||||
moduleKey.push_back(request.context.vertex->depthRemapMode);
|
||||
moduleKey.push_back(request.context.vertex->depthRemapScale);
|
||||
moduleKey.push_back(request.context.vertex->depthRemapOffset);
|
||||
}
|
||||
result.vertexAttributes.reserve(result.vertexInputs.size());
|
||||
std::array<std::uint32_t, ShaderVertexStageInfo::MaxResources> vertexClasses{};
|
||||
std::array<std::uint32_t, ShaderVertexStageInfo::MaxResources / RuntimeAbi::VertexSelectorsPerDword> runtimeSelectors{};
|
||||
@@ -933,7 +940,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
||||
if (artifact.registrationModule) runtimeSelectors.at(input.location / RuntimeAbi::VertexSelectorsPerDword) |= (selectors & 0xfffu) << (input.location % RuntimeAbi::VertexSelectorsPerDword * 16u);
|
||||
}
|
||||
}
|
||||
if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel) {
|
||||
if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel || depthRemap) {
|
||||
{
|
||||
std::shared_lock lock(plan->mutex);
|
||||
if (const auto found = plan->modules.find(moduleKey); found != plan->modules.end()) entry = found->second;
|
||||
@@ -955,6 +962,9 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
||||
if (variant.info.info.rasterPrimitive) constants.push_back({PipelineSpecialization::RasterPrimitive, moduleKey[index++]});
|
||||
if (variant.info.info.layerExported) constants.push_back({PipelineSpecialization::LayerExported, moduleKey[index++]});
|
||||
}
|
||||
if (depthRemap) {
|
||||
for (std::uint32_t word = 0; word < PipelineSpecialization::DepthRemapWords; ++word) constants.push_back({PipelineSpecialization::DepthRemapBase + word, moduleKey[index++]});
|
||||
}
|
||||
if (!artifact.vertexInputPatches.empty()) {
|
||||
for (const auto& input : result.vertexInputs) {
|
||||
const auto selectors = moduleKey[index++];
|
||||
|
||||
@@ -196,6 +196,9 @@ struct ShaderVertexStageInfo {
|
||||
std::uint32_t fetchBufferReg;
|
||||
bool fetchEmbedded;
|
||||
std::uint32_t paClVsOutCntl = 0;
|
||||
std::uint32_t depthRemapMode = 0;
|
||||
std::uint32_t depthRemapScale = 0;
|
||||
std::uint32_t depthRemapOffset = 0;
|
||||
};
|
||||
|
||||
struct ShaderFloatMode {
|
||||
@@ -270,6 +273,7 @@ struct SpirvTarget {
|
||||
bool float16DenormPreserve = false;
|
||||
bool spannedWaves = false;
|
||||
bool robustBufferAccess2 = false;
|
||||
bool depthRangeRemap = false;
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
|
||||
@@ -22,6 +22,7 @@ struct SpirvTargetOptions {
|
||||
bool nonConstantImageOffsets = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
bool float64DenormPreserve = false;
|
||||
bool depthRangeRemap = false;
|
||||
bool registrationModules = false;
|
||||
bool fragmentDescriptorSet = false;
|
||||
bool float16DenormPreserve = false;
|
||||
|
||||
@@ -104,6 +104,8 @@ struct SpirvEmitterState {
|
||||
std::uint32_t spannedExchangeVariable = 0;
|
||||
bool narrowSubgroupClock = false;
|
||||
bool float64DenormPreserve = false;
|
||||
bool depthRangeRemap = false;
|
||||
std::uint32_t depthRemapClipIndex = 0;
|
||||
bool registrationModules = false;
|
||||
bool fragmentDescriptorSet = false;
|
||||
bool float16DenormPreserve = false;
|
||||
|
||||
@@ -257,6 +257,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
|
||||
state.nonConstantImageOffsets = target.nonConstantImageOffsets;
|
||||
state.narrowSubgroupClock = target.narrowSubgroupClock;
|
||||
state.float64DenormPreserve = target.float64DenormPreserve;
|
||||
state.depthRangeRemap = target.depthRangeRemap;
|
||||
state.registrationModules = target.registrationModules;
|
||||
state.fragmentDescriptorSet = target.fragmentDescriptorSet;
|
||||
state.float16DenormPreserve = target.float16DenormPreserve;
|
||||
|
||||
@@ -327,6 +327,11 @@ void DefineOutputs(SpirvEmitterState& state) {
|
||||
DefineMeshOutputs(state);
|
||||
return;
|
||||
}
|
||||
const bool depthRemap = state.depthRangeRemap && state.program.Resources().stage == IrShaderStage::Vertex;
|
||||
if (depthRemap) {
|
||||
state.depthRemapClipIndex = clipDistanceCount;
|
||||
clipDistanceCount += 2u;
|
||||
}
|
||||
const auto stage = state.program.Resources().stage;
|
||||
const bool exportsPosition = std::ranges::any_of(state.outputs, [](const SpirvOutputBinding& output) { return output.kind == StageOutputKind::Position; });
|
||||
if (exportsPosition && (stage == IrShaderStage::Vertex || stage == IrShaderStage::TessellationEvaluation)) {
|
||||
@@ -346,6 +351,7 @@ void DefineOutputs(SpirvEmitterState& state) {
|
||||
}
|
||||
return variable;
|
||||
};
|
||||
if (depthRemap) BuiltIn(state.clipDistanceVariable, F32ArrayType(state, clipDistanceCount), "gl_ClipDistance", spv::BuiltInClipDistance);
|
||||
for (auto& binding : state.outputs) {
|
||||
switch (binding.kind) {
|
||||
case StageOutputKind::Position:
|
||||
|
||||
@@ -515,6 +515,53 @@ std::uint32_t ConvertClipCoordinate(SpirvEmitterState& state, std::uint32_t coor
|
||||
return ndc;
|
||||
}
|
||||
|
||||
std::uint32_t RemapPositionDepth(SpirvEmitterState& state, std::uint32_t position) {
|
||||
const auto& remap = state.inputInfo.vertex->depthRemap;
|
||||
const auto f32 = TypeF32(state);
|
||||
const auto u32 = TypeU32(state);
|
||||
const auto boolean = TypeBool(state);
|
||||
std::array<std::uint32_t, 4> components{};
|
||||
for (std::uint32_t i = 0; i < 4u; i++) {
|
||||
components.at(i) = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeExtract, f32, components.at(i), position, i);
|
||||
}
|
||||
const auto mode = state.module.SpecializationConstant(u32, PipelineSpecialization::DepthRemapBase, remap.mode);
|
||||
const auto scaleBits = state.module.SpecializationConstant(u32, PipelineSpecialization::DepthRemapBase + 1u, remap.scaleBits);
|
||||
const auto offsetBits = state.module.SpecializationConstant(u32, PipelineSpecialization::DepthRemapBase + 2u, remap.offsetBits);
|
||||
const auto scale = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitcast, f32, scale, scaleBits);
|
||||
const auto offset = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitcast, f32, offset, offsetBits);
|
||||
const auto scaled = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFMul, f32, scaled, components[2], scale);
|
||||
const auto shifted = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFMul, f32, shifted, components[3], offset);
|
||||
const auto remapped = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFAdd, f32, remapped, scaled, shifted);
|
||||
const auto active = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpINotEqual, boolean, active, mode, ConstantU32(state, 0u));
|
||||
const auto z = Select(state, f32, active, remapped, components[2]);
|
||||
const auto symmetric = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpIEqual, boolean, symmetric, mode, ConstantU32(state, 2u));
|
||||
const auto nearSymmetric = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFAdd, f32, nearSymmetric, components[3], components[2]);
|
||||
const auto near = Select(state, f32, symmetric, nearSymmetric, components[2]);
|
||||
const auto far = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFSub, f32, far, components[3], components[2]);
|
||||
const auto inside = ConstantF32Value(state, 1.0f);
|
||||
const auto storeDistance = [&](std::uint32_t index, std::uint32_t distance) {
|
||||
const auto pointer = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, f32), pointer, state.clipDistanceVariable, ConstantU32(state, index));
|
||||
state.module.AddFunction(spv::OpStore, pointer, Select(state, f32, active, distance, inside));
|
||||
};
|
||||
if (state.clipDistanceVariable == 0u) throw std::runtime_error("depth remap clip distances were not declared");
|
||||
storeDistance(state.depthRemapClipIndex, near);
|
||||
storeDistance(state.depthRemapClipIndex + 1u, far);
|
||||
const auto converted = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4u), converted, components[0], components[1], z, components[3]);
|
||||
return converted;
|
||||
}
|
||||
|
||||
std::uint32_t ConvertPositionToClipSpace(SpirvEmitterState& state, std::uint32_t position) {
|
||||
if (state.inputInfo.vertex == nullptr) {
|
||||
throw std::runtime_error("vertex input info is missing for clip-space conversion");
|
||||
@@ -1535,6 +1582,9 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
||||
if (state.inputInfo.vertex->clipSpace.enabled) {
|
||||
value = ConvertPositionToClipSpace(state, value);
|
||||
}
|
||||
if (state.depthRangeRemap && state.program.Resources().stage == IrShaderStage::Vertex) {
|
||||
value = RemapPositionDepth(state, value);
|
||||
}
|
||||
}
|
||||
if (exp.kind == ExportTargetKind::Position && state.zeroPositionClipIndex) {
|
||||
StoreZeroPositionClipDistance(state, value);
|
||||
|
||||
@@ -171,6 +171,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
||||
vertexStorage.hostSubgroupSize = hostSubgroupSize;
|
||||
vertexStorage.fetchEmbedded = vertex.fetchEmbedded;
|
||||
vertexStorage.paClVsOutCntl = vertex.paClVsOutCntl;
|
||||
vertexStorage.depthRemap = {vertex.depthRemapMode, vertex.depthRemapScale, vertex.depthRemapOffset};
|
||||
vertexStorage.fetchExternal = false;
|
||||
vertexStorage.fetchAttribReg = static_cast<int>(vertex.fetchAttribReg);
|
||||
vertexStorage.fetchBufferReg = static_cast<int>(vertex.fetchBufferReg);
|
||||
|
||||
@@ -206,11 +206,11 @@ int main() {
|
||||
Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u);
|
||||
Require(request.target.float16DenormPreserve == preserve);
|
||||
auto upgraded = bytes;
|
||||
upgraded[4] = 32u;
|
||||
upgraded[4] = 33u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 5u, 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 13u, 0u);
|
||||
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
|
||||
auto truncated = bytes;
|
||||
truncated.pop_back();
|
||||
@@ -245,11 +245,11 @@ int main() {
|
||||
layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data";
|
||||
}
|
||||
Require(layerRejected);
|
||||
layered[4] = 32u;
|
||||
layered[4] = 33u;
|
||||
layered.insert(layered.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(layered);
|
||||
if (fixture.tessellation) layered.insert(layered.end() - 35u, 5u, 0u);
|
||||
layered.insert(layered.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
layered.insert(layered.end(), sizeof(std::uint64_t) + 1u + 13u, 0u);
|
||||
Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered));
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,7 @@ int main() {
|
||||
bytes[4] = 32u;
|
||||
bytes.insert(bytes.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(bytes);
|
||||
bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 13u, 0u);
|
||||
Require(serializer.Serialize(replay.request) == EncodeBase64(bytes));
|
||||
|
||||
auto truncated23 = DecodeBase64(encoded23);
|
||||
@@ -397,7 +397,7 @@ int main() {
|
||||
upgraded[4] = 32u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 259u, {0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 13u, 0u);
|
||||
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
|
||||
for (const bool dither : {false, true}) {
|
||||
if (!fixture.pixel && dither) continue;
|
||||
@@ -426,9 +426,9 @@ int main() {
|
||||
const auto replay = serializer.Deserialize(encoded);
|
||||
Require(replay.request.target.float16DenormPreserve == preserve);
|
||||
auto bytes = DecodeBase64(encoded);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 32u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 33u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
auto future = bytes;
|
||||
future[4] = 33u;
|
||||
future[4] = 34u;
|
||||
bool futureRejected = false;
|
||||
try {
|
||||
static_cast<void>(serializer.Deserialize(EncodeBase64(future)));
|
||||
|
||||
@@ -174,3 +174,4 @@
|
||||
- [Unclamped normalized color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted when blending is disabled (ENABLE clear or BLEND_BYPASS set). Vulkan converts unblended shader values and clamps to the normalized range automatically; blending into an unclamped normalized target remains unmodeled because Vulkan always clamps inputs prior to blending.
|
||||
|
||||
- [Thick SW_4KB_S_X volumes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) use AMD addrlib's GFX10 non-RB+ 16-pipe `GFX10_SW_4K_S3_X_PATINFO` equations for 1, 2, 4, 8 and 16-byte elements, including packed mip tails. The layout follows [AMD PAL c5e80007](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp); it has not been measured on console hardware. Other thick swizzle modes outside the supported families still throw. Mipmapped 3D color targets remain refused by the color-register decoder.
|
||||
- [Viewport depth range remap](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - on a device without `VK_EXT_depth_range_unrestricted` a viewport depth range outside [0, 1] (Minecraft PPSA17221 draws its world with `PA_CL_VPORT_ZSCALE` 1, `ZOFFSET` 0 in negative-one-to-one clip space) is folded into the vertex shader: `RemapPositionDepth` scales clip z so z/w is the console's depth, the pipeline's depth clamp applies the console's `PA_SC_VPORT_ZMIN/ZMAX` clipped to [0, 1] (exact for a normalized depth target or none; Minecraft clamps its float depth target to [-1, 1], so a depth below 0 is stored as 0 there, which only a later test or read at exactly 0 can tell, and the first such draw is reported once), and two extra `gl_ClipDistance` elements restore the near and far planes depth clamping removes. Mode, scale and offset are pipeline specialization constants (`PipelineSpecialization::DepthRemapBase`), so each distinct depth transform is one vertex module variant. The remap serves the vertex path only; mesh and tessellation draws with such a range still throw. Whether the console clamps before or after its depth bias and how it treats a clamp range narrower than the transform's are not measured.
|
||||
Reference in new issue
Block a user