mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-31 12:35:51 +00:00
Merge pull request #11305 from joedeangelis533-bot/review/tessellation-depth-upstream-20261025
feat(agc): remap unsupported tessellation viewport depth ranges
This commit is contained in:
11 files changed
+191
-27
No files matched your search
@@ -2503,6 +2503,16 @@ target_include_directories(agc_driver_color565_target_tests PRIVATE $<TARGET_PRO
|
||||
|
||||
target_include_directories(agc_driver_depth_range_remap_draw_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_tessellation_depth_range_remap COMMAND agc_driver_tessellation_tests --depth-remap)
|
||||
set_tests_properties(agc_driver_tessellation_depth_range_remap PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_tessellation_depth_range_remap PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(agc_driver_depth_bounds_emulation_draw_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
foreach(stage IN ITEMS vert frag)
|
||||
|
||||
@@ -73,6 +73,9 @@ std::shared_ptr<const ShaderRecompiler::RecompileResult> Driver::materializeDraw
|
||||
{0, 0, pushOffset, Graphics::StagePushLimit(graphics.stages, pushOffset, graphics.depthRemap.mode != 0u) - pushOffset % Graphics::PipelinePushSlotBytes},
|
||||
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, Graphics::StageMeshConfiguration(graphics.stages, program.binary.stage), graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}, program.binary.stage == Stage::Mesh ? meshBarycentrics : std::nullopt}
|
||||
};
|
||||
if (graphics.depthRemap.mode != 0u && pushOffset < Graphics::PipelinePushSlotBytes) {
|
||||
request.layout.pushConstantSizeBytes = std::min(request.layout.pushConstantSizeBytes, ShaderRecompiler::DepthRemapPushOffsetBytes - pushOffset);
|
||||
}
|
||||
const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
|
||||
auto invocation = [&] {
|
||||
try {
|
||||
|
||||
@@ -1802,7 +1802,10 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
|
||||
const auto wave = Graphics::StageWaveSize(decoded.state.stages, program.binary.stage);
|
||||
pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots);
|
||||
const auto offset = pushOffset;
|
||||
const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes;
|
||||
auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes;
|
||||
if (decoded.state.depthRemap.mode != 0u && offset < Graphics::PipelinePushSlotBytes) {
|
||||
room = std::min(room, ShaderRecompiler::DepthRemapPushOffsetBytes - offset);
|
||||
}
|
||||
if (deferUnprepared && DeferFailedRegistration(*program.snapshot)) {
|
||||
prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr});
|
||||
continue;
|
||||
@@ -1818,6 +1821,7 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
|
||||
}
|
||||
if (vertex) {
|
||||
vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports;
|
||||
vertex->depthRemapMode = decoded.state.depthRemap.mode;
|
||||
Graphics::ApplyPointSize(decoded.state, *vertex);
|
||||
}
|
||||
ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, Graphics::StageMeshConfiguration(decoded.state.stages, program.binary.stage), decoded.state.stages.tessellation, {}}};
|
||||
|
||||
@@ -1431,7 +1431,9 @@ 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");
|
||||
const bool vertex = state.stages.path == ShaderPath::Vertex && !state.stages.tessellation;
|
||||
const bool tessellation = state.stages.path == ShaderPath::Tessellation && state.stages.tessellation.has_value();
|
||||
Require((vertex || tessellation) && !state.stages.mesh, "viewport depth outside [0, 1] is remapped on the vertex and tessellation paths 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);
|
||||
|
||||
@@ -75,11 +75,13 @@ ModuleFacts inspect(const ShaderRecompiler::RecompileResult& result) {
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint32_t, 3> PositionExport{0xf80008cfu, 0u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 5> DomainPositionExport{0xf8000941u, 0u, 0xf80008cfu, 0x06050605u, 0xbf810000u};
|
||||
|
||||
ShaderRecompiler::RecompileRequest baseRequest(bool remap) {
|
||||
ShaderRecompiler::RecompileRequest baseRequest(bool remap, ShaderRecompiler::ShaderStage stage = ShaderRecompiler::ShaderStage::Vertex) {
|
||||
using namespace ShaderRecompiler;
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Vertex, 0x10000u, PositionExport, 0, {}};
|
||||
const auto code = stage == ShaderStage::TessellationEvaluation ? std::span<const std::uint32_t>(DomainPositionExport) : std::span<const std::uint32_t>(PositionExport);
|
||||
request.shader = {stage, 0x10000u, code, 0, {}};
|
||||
request.context.waveSize = 64;
|
||||
ShaderVertexStageInfo vertex{};
|
||||
vertex.depthRemapMode = 2u;
|
||||
@@ -87,9 +89,13 @@ ShaderRecompiler::RecompileRequest baseRequest(bool remap) {
|
||||
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};
|
||||
static constexpr std::array<std::uint32_t, 3> capabilities{spv::CapabilityShader, spv::CapabilityClipDistance, spv::CapabilityTessellation};
|
||||
request.target.supportedCapabilities = capabilities;
|
||||
request.target.depthRangeRemap = remap;
|
||||
if (stage == ShaderStage::TessellationEvaluation) {
|
||||
request.target.tessellation = TessellationTargetLimits{32u, 128u, 128u, 128u, 4096u, 128u, 128u};
|
||||
request.graphics = GraphicsCompileContext{0u, {}, std::nullopt, TessellationConfiguration{3u, 3u, 1u, 2u, 2u}, {0u, 3u, 0u, 1u}};
|
||||
}
|
||||
request.layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes;
|
||||
request.useCache = false;
|
||||
return request;
|
||||
@@ -99,9 +105,9 @@ void require(bool condition, const char* what) {
|
||||
if (!condition) throw std::runtime_error(what);
|
||||
}
|
||||
|
||||
void preparedTransformTests() {
|
||||
void preparedTransformTests(ShaderRecompiler::ShaderStage stage) {
|
||||
using namespace ShaderRecompiler;
|
||||
auto request = baseRequest(true);
|
||||
auto request = baseRequest(true, stage);
|
||||
request.useCache = true;
|
||||
const auto handle = PrepareShader(request);
|
||||
const auto materialize = [&](std::uint32_t mode) {
|
||||
@@ -120,15 +126,15 @@ void preparedTransformTests() {
|
||||
require(!PreparedShaderInvocation::TryCreate(other, handle).has_value(), "a prepared vertex shader matched a request of another depth remap mode");
|
||||
}
|
||||
|
||||
void pushedTransformTests() {
|
||||
void pushedTransformTests(ShaderRecompiler::ShaderStage stage) {
|
||||
using namespace ShaderRecompiler;
|
||||
const auto remapped = Recompile(baseRequest(true));
|
||||
const auto plain = Recompile(baseRequest(false));
|
||||
const auto remapped = Recompile(baseRequest(true, stage));
|
||||
const auto plain = Recompile(baseRequest(false, stage));
|
||||
const auto facts = inspect(remapped);
|
||||
require(!facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "the depth remap scale or offset is a specialization constant");
|
||||
require(facts.pushConstantVariable && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u) && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u + 1u), "the remapped vertex shader does not read the scale and offset from the push block");
|
||||
require(!inspect(plain).pushConstantVariable, "a vertex shader without a depth remap gained a push constant block");
|
||||
auto crowded = baseRequest(true);
|
||||
auto crowded = baseRequest(true, stage);
|
||||
crowded.layout.pushConstantSizeBytes = DepthRemapPushOffsetBytes + 4u;
|
||||
bool refused = false;
|
||||
try {
|
||||
@@ -139,18 +145,41 @@ void pushedTransformTests() {
|
||||
require(refused, "a remapped vertex shader with push data over the depth remap words was accepted");
|
||||
}
|
||||
|
||||
void clipDistanceCapacityTests(ShaderRecompiler::ShaderStage stage) {
|
||||
using namespace ShaderRecompiler;
|
||||
static constexpr std::array<std::uint32_t, 7> vertex{0xf80008cfu, 0u, 0xf80008dfu, 0u, 0xf80008efu, 0u, 0xbf810000u};
|
||||
static constexpr std::array<std::uint32_t, 9> domain{0xf8000941u, 0u, 0xf80008cfu, 0x05050505u, 0xf80008dfu, 0x05050505u, 0xf80008efu, 0x05050505u, 0xbf810000u};
|
||||
auto request = baseRequest(true, stage);
|
||||
request.shader.code = stage == ShaderStage::TessellationEvaluation ? std::span<const std::uint32_t>(domain) : std::span<const std::uint32_t>(vertex);
|
||||
request.context.vertex->paClVsOutCntl = 0xc0003fu;
|
||||
require(inspect(Recompile(request)).clipDistanceLength == 8u, "the remap did not preserve six guest clip distances beside its two planes");
|
||||
for (const auto control : {0xc0007fu, 0xc0403fu, 0xc000ffu}) {
|
||||
request.context.vertex->paClVsOutCntl = control;
|
||||
bool refused = false;
|
||||
try {
|
||||
static_cast<void>(Recompile(request));
|
||||
} catch (const std::runtime_error& error) {
|
||||
refused = std::string(error.what()).find("two free clip-distance components") != std::string::npos;
|
||||
}
|
||||
require(refused, "a remap that exceeds the combined clip and cull distance capacity was accepted");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
preparedTransformTests();
|
||||
pushedTransformTests();
|
||||
for (const auto stage : {ShaderStage::Vertex, ShaderStage::TessellationEvaluation}) {
|
||||
const auto remapped = inspect(Recompile(baseRequest(true, stage)));
|
||||
const auto plain = inspect(Recompile(baseRequest(false, stage)));
|
||||
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");
|
||||
preparedTransformTests(stage);
|
||||
pushedTransformTests(stage);
|
||||
clipDistanceCapacityTests(stage);
|
||||
}
|
||||
std::cout << "Depth range remap tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -4727,6 +4727,17 @@ void DepthRemapTests() {
|
||||
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");
|
||||
auto tessellated = AgcDriver::Graphics::DecodeState(queue);
|
||||
tessellated.stages.path = AgcDriver::Graphics::ShaderPath::Tessellation;
|
||||
tessellated.stages.tessellation = ShaderRecompiler::TessellationConfiguration{3u, 3u, 1u, 2u, 2u};
|
||||
auto nativeTessellation = tessellated;
|
||||
AgcDriver::Graphics::ApplyDepthRemap(unrestricted, nativeTessellation);
|
||||
Require(nativeTessellation.depthRemap.mode == 0 && nativeTessellation.viewport.minDepth == -0.5f && nativeTessellation.viewport.maxDepth == 1.5f, "an unrestricted tessellation draw was remapped");
|
||||
AgcDriver::Graphics::ApplyDepthRemap(remapping, tessellated);
|
||||
Require(tessellated.depthRemap.mode == 1 && tessellated.depthRemap.scale == 2.0f && tessellated.depthRemap.offset == -0.5f && tessellated.depthClamp, "a tessellation draw did not remap its depth range");
|
||||
auto combined = nativeTessellation;
|
||||
combined.stages.mesh = ShaderRecompiler::MeshConfiguration{};
|
||||
expectFailure([&] { AgcDriver::Graphics::ApplyDepthRemap(remapping, combined); }, "vertex and tessellation paths only");
|
||||
}
|
||||
|
||||
void DepthBoundsEmulationTests() {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@@ -565,7 +566,7 @@ struct Stages {
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
};
|
||||
|
||||
ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span<const std::uint32_t> code, std::uint32_t& pushCursor, const PatchDraw& patches = Triangles) {
|
||||
ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span<const std::uint32_t> code, std::uint32_t& pushCursor, const PatchDraw& patches = Triangles, std::uint32_t depthRemapMode = 0u) {
|
||||
static const std::array<std::uint32_t, 8> domainUserData{};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const bool hull = stage == ShaderStage::TessellationControl;
|
||||
@@ -582,10 +583,12 @@ ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& t
|
||||
pixel = info;
|
||||
} else {
|
||||
vertex = ShaderRecompiler::ShaderVertexStageInfo{};
|
||||
vertex->depthRemapMode = depthRemapMode;
|
||||
graphics = ShaderRecompiler::GraphicsCompileContext{hull ? 0u : 8u, {}, std::nullopt, patches.configuration, {reinterpret_cast<std::uintptr_t>(patches.indices.data()), static_cast<std::uint32_t>(patches.indices.size()), 2, patches.instances}};
|
||||
}
|
||||
auto layout = stage == ShaderStage::Fragment ? PixelPushLayout(pushCursor, target) : ShaderRecompiler::BindingLayout{0, 0, pushCursor, AgcDriver::Graphics::PipelinePushSlotBytes - pushCursor};
|
||||
if (layout.pushConstantOffsetBytes < AgcDriver::Graphics::PipelinePushSlotBytes) layout.pushConstantSizeBytes = ShaderRecompiler::TessellationLevelPushOffsetBytes - layout.pushConstantOffsetBytes;
|
||||
if (depthRemapMode != 0u && layout.pushConstantOffsetBytes < AgcDriver::Graphics::PipelinePushSlotBytes) layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes - layout.pushConstantOffsetBytes;
|
||||
const ShaderRecompiler::RecompileRequest request{
|
||||
{stage, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{64, hull ? 0u : 8u, userData, std::nullopt, pixel, vertex, memory},
|
||||
@@ -634,7 +637,17 @@ std::uint32_t Row(float y) {
|
||||
return static_cast<std::uint32_t>((1.0f - y) * 0.5f * Height);
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags cull, VkFrontFace front, const PatchDraw& patches = Triangles) {
|
||||
struct DepthDraw {
|
||||
std::uint64_t address;
|
||||
bool write;
|
||||
bool negativeOneToOne = false;
|
||||
float scale = 1.0f;
|
||||
float offset = 0.0f;
|
||||
float clampMin = 0.0f;
|
||||
float clampMax = 1.0f;
|
||||
};
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags cull, VkFrontFace front, const PatchDraw& patches = Triangles, const DepthDraw* depth = nullptr) {
|
||||
for (std::size_t i = 0; i < Pixels.size(); i += 4) {
|
||||
for (std::size_t c = 0; c < 4; ++c) Pixels[i + c] = std::byte{Background[c]};
|
||||
}
|
||||
@@ -665,10 +678,95 @@ void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
if (depth != nullptr) {
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{depth->address, 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0u};
|
||||
state.depthTest = true;
|
||||
state.depthWrite = depth->write;
|
||||
state.depthCompare = depth->write ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_LESS;
|
||||
state.depthScale = depth->scale;
|
||||
state.depthOffset = depth->offset;
|
||||
state.depthClampMin = depth->clampMin;
|
||||
state.depthClampMax = depth->clampMax;
|
||||
state.negativeOneToOne = depth->negativeOneToOne;
|
||||
state.viewport.minDepth = depth->negativeOneToOne ? depth->offset - depth->scale : depth->offset;
|
||||
state.viewport.maxDepth = depth->offset + depth->scale;
|
||||
auto context = device.graphicsContext();
|
||||
context.depthRangeUnrestricted = false;
|
||||
AgcDriver::Graphics::ApplyDepthRemap(context, state);
|
||||
}
|
||||
device.Draw(state, {reinterpret_cast<std::uintptr_t>(patches.indices.data()), static_cast<std::uint32_t>(patches.indices.size()), 2, patches.instances, 0, true, 0, patches.firstInstance}, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void RequireDepthRangeRemap(AgcDriver::VulkanDevice& device) {
|
||||
auto target = device.Target();
|
||||
Require(device.graphicsContext().depthClamp, "tessellation depth remap needs depth clamping");
|
||||
target.depthRangeRemap = true;
|
||||
const auto compile = [&](float z, std::uint32_t mode) {
|
||||
std::vector<std::uint32_t> domain(DomainCode.begin(), DomainCode.end());
|
||||
const auto literal = std::ranges::find(domain, 0x7e260280u);
|
||||
Require(literal != domain.end(), "the domain fixture has no constant depth");
|
||||
*literal = 0x7e2602ffu;
|
||||
domain.insert(literal + 1, std::bit_cast<std::uint32_t>(z));
|
||||
std::uint32_t cursor = 0u;
|
||||
return Stages{
|
||||
Compile(target, ShaderStage::Local, LocalCode, cursor),
|
||||
Compile(target, ShaderStage::TessellationControl, HullCode, cursor),
|
||||
Compile(target, ShaderStage::TessellationEvaluation, domain, cursor, Triangles, mode),
|
||||
Compile(target, ShaderStage::Fragment, PixelCode, cursor),
|
||||
};
|
||||
};
|
||||
const auto covered = [] {
|
||||
std::uint32_t count = 0u;
|
||||
for (std::size_t offset = 0; offset < Pixels.size(); offset += 4u) {
|
||||
if (Pixels[offset] == std::byte{255}) ++count;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
Draw(device, compile(0.5f, 0u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
const auto expected = covered();
|
||||
Require(expected != 0u, "the depth fixture drew no tessellated patches");
|
||||
struct Case {
|
||||
bool symmetric;
|
||||
float scale;
|
||||
float offset;
|
||||
float clampMin;
|
||||
float clampMax;
|
||||
float z;
|
||||
bool drawn;
|
||||
float probe;
|
||||
bool passes;
|
||||
};
|
||||
constexpr std::array<Case, 14> cases{{
|
||||
{true, 1.0f, 0.0f, 0.0f, 1.0f, 0.75f, true, 0.5f, true},
|
||||
{true, 1.0f, 0.0f, 0.0f, 1.0f, -0.5f, true, 0.25f, false},
|
||||
{true, 1.0f, 0.0f, 0.0f, 1.0f, -1.25f, false, 0.5f, true},
|
||||
{true, 1.0f, 0.0f, 0.0f, 1.0f, 1.25f, false, 0.5f, true},
|
||||
{false, 2.0f, -0.5f, 0.0f, 1.0f, 0.25f, true, 0.125f, false},
|
||||
{false, 2.0f, -0.5f, 0.0f, 1.0f, 0.75f, true, 0.875f, true},
|
||||
{false, 2.0f, -0.5f, 0.0f, 1.0f, -0.125f, false, 0.5f, true},
|
||||
{false, 2.0f, -0.5f, 0.0f, 1.0f, 1.125f, false, 0.5f, true},
|
||||
{false, 2.0f, -0.5f, 0.25f, 0.75f, 0.4f, true, 0.35f, false},
|
||||
{false, 2.0f, -0.5f, 0.25f, 0.75f, 0.6f, true, 0.65f, true},
|
||||
{false, 2.0f, -0.5f, 0.25f, 0.75f, 0.2f, true, 0.2f, true},
|
||||
{false, 2.0f, -0.5f, 0.25f, 0.75f, 0.8f, true, 0.8f, false},
|
||||
{false, -2.0f, 1.5f, 0.0f, 1.0f, 0.4f, true, 0.65f, true},
|
||||
{false, -2.0f, 1.5f, 0.0f, 1.0f, 0.6f, true, 0.35f, false},
|
||||
}};
|
||||
alignas(4096) static std::array<std::array<float, Width * Height>, cases.size()> depths{};
|
||||
for (std::size_t index = 0; index < cases.size(); ++index) {
|
||||
const auto& test = cases[index];
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(depths[index].data()));
|
||||
const DepthDraw write{address, true, test.symmetric, test.scale, test.offset, test.clampMin, test.clampMax};
|
||||
Draw(device, compile(test.z, test.symmetric ? 2u : 1u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, Triangles, &write);
|
||||
Require(covered() == (test.drawn ? expected : 0u), "tessellation remap clipping differs in case " + std::to_string(index));
|
||||
const DepthDraw probe{address, false};
|
||||
Draw(device, compile(test.probe, 0u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, Triangles, &probe);
|
||||
Require(covered() == (test.passes ? expected : 0u), "tessellation remap stored depth differs in case " + std::to_string(index));
|
||||
}
|
||||
std::puts("Tessellation depth remap: 14 clipping and stored-depth cases passed");
|
||||
}
|
||||
|
||||
std::uint32_t CoveredPixels(std::uint32_t patch, bool interior) {
|
||||
std::uint32_t count = 0;
|
||||
for (std::uint32_t y = Row(0.5f); y <= Row(-0.5f) && y < Height; ++y) {
|
||||
@@ -1095,7 +1193,7 @@ void RequireRegisteredMergedStages() {
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
@@ -1104,6 +1202,10 @@ int main() {
|
||||
std::puts("tessellation: skipped, the device has no tessellation shaders");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--depth-remap") {
|
||||
RequireDepthRangeRemap(*device);
|
||||
return 0;
|
||||
}
|
||||
RequireSingleControlPointHulls(target);
|
||||
ExpectRefusal(target, ShaderStage::TessellationControl, HullUnguardedFactors, "not written by control point 0 alone");
|
||||
ExpectRefusal(target, ShaderStage::TessellationControl, HullSquaredAddress, "not a whole number of control points");
|
||||
|
||||
@@ -1152,10 +1152,10 @@ 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();
|
||||
const bool depthRemap = request.target.depthRangeRemap && (request.shader.stage == ShaderStage::Vertex || request.shader.stage == ShaderStage::TessellationEvaluation) && request.context.vertex.has_value();
|
||||
if (depthRemap) {
|
||||
if (request.context.vertex->depthRemapMode != 0u && request.layout.pushConstantOffsetBytes % (RuntimeAbi::PushConstantDwords * 4u) + request.layout.pushConstantSizeBytes > DepthRemapPushOffsetBytes) {
|
||||
throw std::runtime_error("a remapped vertex shader's push data must end before the depth remap words");
|
||||
throw std::runtime_error("a remapped raster shader's push data must end before the depth remap words");
|
||||
}
|
||||
moduleKey.push_back(request.context.vertex->depthRemapMode);
|
||||
}
|
||||
|
||||
@@ -368,8 +368,11 @@ void DefineOutputs(SpirvEmitterState& state) {
|
||||
DefineMeshOutputs(state);
|
||||
return;
|
||||
}
|
||||
const bool depthRemap = state.depthRangeRemap && state.program.Resources().stage == IrShaderStage::Vertex;
|
||||
const bool depthRemap = state.depthRangeRemap && (state.program.Resources().stage == IrShaderStage::Vertex || state.program.Resources().stage == IrShaderStage::TessellationEvaluation);
|
||||
if (depthRemap) {
|
||||
if (clipDistanceCount + cullDistanceCount > MaxClipDistances - 2u) {
|
||||
FailEmit("depth remap needs two free clip-distance components");
|
||||
}
|
||||
state.depthRemapClipIndex = clipDistanceCount;
|
||||
clipDistanceCount += 2u;
|
||||
}
|
||||
@@ -460,7 +463,7 @@ void DefineOutputs(SpirvEmitterState& state) {
|
||||
void DefineDescriptors(SpirvEmitterState& state) {
|
||||
const IrBindingLayout& layout = state.program.Metadata().bindings;
|
||||
const IrShaderStage stage = state.program.Resources().stage;
|
||||
if (layout.UsesPushData() || state.stencilReplay || stage == IrShaderStage::Mesh || stage == IrShaderStage::TessellationControl || (state.depthRangeRemap && stage == IrShaderStage::Vertex)) {
|
||||
if (layout.UsesPushData() || state.stencilReplay || stage == IrShaderStage::Mesh || stage == IrShaderStage::TessellationControl || (state.depthRangeRemap && (stage == IrShaderStage::Vertex || stage == IrShaderStage::TessellationEvaluation))) {
|
||||
const auto type = PushConstantBlockType(state);
|
||||
state.pushConstantVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant);
|
||||
state.module.AddName(type, "BufferResource");
|
||||
|
||||
@@ -2075,7 +2075,7 @@ 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) {
|
||||
if (state.depthRangeRemap && (state.program.Resources().stage == IrShaderStage::Vertex || state.program.Resources().stage == IrShaderStage::TessellationEvaluation)) {
|
||||
value = RemapPositionDepth(state, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
- [Multisample image readback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleReadback.cpp) fetches unsigned R8/R16/R32/R32G32/R32G32B32A32 or D32 depth samples through a 2D array view in GENERAL, in layer/y/x/fragment order. The caller owns producer completion, image transitions and resource lifetimes. It implements Vulkan per-sample fetches, not guest-memory publication, metadata expansion or PS5 layout equivalence; D32 exceptional-value preservation has not been measured. Other formats, sample counts and invalid ranges throw.
|
||||
- [Multisample image upload](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp) does not model guest layouts, native ownership or CMASK/FMASK/DCC publication; console equivalence and large-title initialization costs remain unmeasured. It stores 1/2/4/8/16-byte elements from a storage buffer into an unsigned 2D multisample array view in layer/y/x/fragment order. It requires enabled multisample storage images and formatless image writes, 2/4/8 fragments and valid aligned buffer and dispatch ranges. The caller supplies the matching storage view in GENERAL and owns barriers, completion and resource lifetimes.
|
||||
- [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. The mode is a pipeline specialization constant (`PipelineSpecialization::DepthRemapBase`); scale and offset are read from the draw's push block (two words at byte 104, `DepthRemapPushOffsetBytes`, so a remapped draw's stage push data ends before it), so draws that differ only in them share one vertex module and one pipeline. 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.
|
||||
- [Viewport depth range remap](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - mesh draws with a viewport depth range outside [0, 1] still throw on devices without `VK_EXT_depth_range_unrestricted`, including combined tessellation and geometry draws. The vertex and tessellation fallback clips `PA_SC_VPORT_ZMIN/ZMAX` to [0, 1], so a negative depth on a float target is stored as 0: Minecraft PPSA17221 requests [-1, 1], which a later test or read at 0 can distinguish. The fallback needs depth clamping and two free components out of the eight combined clip/cull distances. Console clamp/bias ordering and clamp ranges narrower than the viewport transform remain unmeasured.
|
||||
- [Benchmark rewrite failure test off Windows](../../../core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp) (libSceAgcDriver) - `ReplaceGzipFile` checks that a write failing at `<name>.tmp` keeps the previous `aps5-bench.json.gz` only on non-Windows builds: the `std::ios_base::failure` thrown inside the MinGW ofstream did not unwind to the test's catch, so Windows CI covers only the failed move.
|
||||
|
||||
- [Sampled array windows](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver) - a sampled 2D array, 1D array or cube view past slice 0 is built from its viewed slices only (`ViewedArrayWindow`). The window needs a thin surface without DCC metadata, or with keys that resolve over the whole surface to Uncompressed (the window then drops the metadata; keys are rescanned at each lookup). For a 64 KiB XOR swizzle it starts at the last slice at or before the base whose swizzle slice bits are zero (R_X 1-byte and Z_X 4-byte: a multiple of 16); when that is slice 0 the view builds slices 0 to its last slice. A window and a whole-array view of the same surface are separate images over overlapping memory, kept coherent by the existing overlap rules, at the cost of both. PPSA26344 samples a cube array at slices 4299..7232 at 1467 MiB instead of about 3.8 GB; PPSA01341 samples slice 636 of a DCC 2D array at 52 MiB instead of 2.5 GiB.
|
||||
|
||||
Reference in new issue
Block a user