Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <bit>

namespace AgcDriver::DriverDetail {

Expand All @@ -26,7 +27,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
mix(graphics.stages.tessellation.has_value());
if (graphics.stages.tessellation) {
const auto& tess = *graphics.stages.tessellation;
for (const auto value : {tess.inputControlPoints, tess.outputControlPoints, tess.domain, tess.partitioning, tess.outputTopology}) mix(value);
for (const auto value : {tess.inputControlPoints, tess.outputControlPoints, tess.domain, tess.partitioning, tess.outputTopology, std::bit_cast<std::uint32_t>(tess.minLevel), std::bit_cast<std::uint32_t>(tess.maxLevel), tess.patchesPerGroup}) mix(value);
}
mix(graphics.rectList);
mix(programs.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
if (x.has_value() != y.has_value()) return false;
if (!x) return true;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology && x->minLevel == y->minLevel && x->maxLevel == y->maxLevel && x->patchesPerGroup == y->patchesPerGroup;
};
if (s.stages.path != t.stages.path || s.stages.registerValue != t.stages.registerValue || s.stages.vertexWaveSize != t.stages.vertexWaveSize || s.stages.fragmentWaveSize != t.stages.fragmentWaveSize || !sameMesh(s.stages.mesh, t.stages.mesh) || !sameTess(s.stages.tessellation, t.stages.tessellation)) return false;
if (!sameColor(s.color, t.color) || s.colors.size() != t.colors.size() || s.blends.size() != t.blends.size()) return false;
Expand Down
4 changes: 4 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <chrono>
#include <functional>
#include <optional>
Expand Down Expand Up @@ -701,6 +702,9 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
add(tessellation.domain);
add(tessellation.partitioning);
add(tessellation.outputTopology);
add(std::bit_cast<std::uint32_t>(tessellation.minLevel));
add(std::bit_cast<std::uint32_t>(tessellation.maxLevel));
add(tessellation.patchesPerGroup);
}
}
return keyed;
Expand Down
3 changes: 3 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
append(key, tessellation.domain);
append(key, tessellation.partitioning);
append(key, tessellation.outputTopology);
append(key, tessellation.minLevel);
append(key, tessellation.maxLevel);
append(key, tessellation.patchesPerGroup);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,12 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
mode(spv::ExecutionModeOutputVertices, {state.rectList ? 4u : state.stages.tessellation->outputControlPoints});
Require(module.modes.size() == 1, "unsupported tessellation-control execution mode");
} else if (evaluation) {
mode(state.rectList ? spv::ExecutionModeQuads : spv::ExecutionModeTriangles, {});
Require(state.rectList || state.stages.tessellation.has_value(), "tessellation configuration is missing");
const bool quads = state.rectList || state.stages.tessellation->domain == 2u;
const bool counterClockwise = !state.rectList && state.stages.tessellation->outputTopology == 3u;
mode(quads ? spv::ExecutionModeQuads : spv::ExecutionModeTriangles, {});
mode(state.rectList ? spv::ExecutionModeSpacingEqual : spv::ExecutionModeSpacingFractionalOdd, {});
mode(spv::ExecutionModeVertexOrderCw, {});
mode(counterClockwise ? spv::ExecutionModeVertexOrderCcw : spv::ExecutionModeVertexOrderCw, {});
Require(module.modes.size() == 3, "unsupported tessellation-evaluation execution mode");
} else if (geometry) {
mode(spv::ExecutionModeTriangles, {});
Expand Down
7 changes: 4 additions & 3 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,12 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
validate((value & ~0x0207ed0du) == 0 && (value & 0x02002000u) == 0x02002000u && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing");
const auto config = read(queue.context, 0x2d6);
const auto parameters = read(queue.context, 0x2db);
ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 7u, (parameters >> 5u) & 7u};
ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 7u, (parameters >> 5u) & 7u, readFloat(queue.context, 0x287), readFloat(queue.context, 0x286), config & 0xffu};
validate(tess.inputControlPoints != 0 && tess.inputControlPoints <= 32 && tess.outputControlPoints != 0 && tess.outputControlPoints <= 32, "invalid tessellation control-point counts");
validate(tess.patchesPerGroup != 0, "VGT_LS_HS_CONFIG.NUM_PATCHES is zero");
if ((parameters & ~0x000600ffu) != 0) Require(false, zeroMessage(0x2db, parameters, "VGT_TF_PARAM donut, factor detection, memory type or reserved state"));
validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path");
Require(readFloat(queue.context, 0x287) <= 1.0f && readFloat(queue.context, 0x286) >= 63.0f, "tessellation level clamps inside the fractional-odd range [1, 63] are unsupported");
validate((tess.domain == 1 || tess.domain == 2) && tess.partitioning == 2 && (tess.outputTopology == 2 || tess.outputTopology == 3), "only triangle and quad domains with fractional-odd partitioning and triangle output are supported by the reference path");
Require(tess.minLevel >= 0.0f && tess.minLevel <= 63.0f && tess.minLevel <= tess.maxLevel && tess.maxLevel >= 1.0f, "tessellation level clamps need 0 <= VGT_HOS_MIN_TESS_LEVEL <= 63, VGT_HOS_MAX_TESS_LEVEL >= 1 and the minimum at most the maximum");
result.tessellation = tess;
} else {
validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state");
Expand Down
20 changes: 15 additions & 5 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,24 +530,34 @@ void ShaderStageTests() {
}
Require(stages.path == AgcDriver::Graphics::ShaderPath::Tessellation && stages.tessellation, "tessellation routing changed");
const auto& tessellation = *stages.tessellation;
Require(tessellation.inputControlPoints == 3 && tessellation.outputControlPoints == 3 && tessellation.domain == 1 && tessellation.partitioning == 2 && tessellation.outputTopology == 2, "the passthrough triangle-patch configuration decoded differently");
for (const auto [parameters, reason] : {std::pair{0x44049u, "VGT_TF_PARAM"}, std::pair{0x140049u, "VGT_TF_PARAM"}, std::pair{0x2040049u, "VGT_TF_PARAM"}, std::pair{0x40059u, "only triangular"}, std::pair{0x40069u, "only triangular"}, std::pair{0x4006au, "only triangular"}}) {
Require(tessellation.inputControlPoints == 3 && tessellation.outputControlPoints == 3 && tessellation.domain == 1 && tessellation.partitioning == 2 && tessellation.outputTopology == 2 && tessellation.minLevel == 1.0f && tessellation.maxLevel == 64.0f && tessellation.patchesPerGroup == 85, "the passthrough triangle-patch configuration decoded differently");
for (const auto [parameters, reason] : {std::pair{0x44049u, "VGT_TF_PARAM"}, std::pair{0x140049u, "VGT_TF_PARAM"}, std::pair{0x2040049u, "VGT_TF_PARAM"}, std::pair{0x40059u, "fractional-odd partitioning"}, std::pair{0x4004du, "fractional-odd partitioning"}, std::pair{0x40048u, "fractional-odd partitioning"}, std::pair{0x40029u, "fractional-odd partitioning"}, std::pair{0x4004bu, "fractional-odd partitioning"}}) {
queue.context[0x2db] = parameters;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, reason);
}
queue.context[0x2db] = 0x40049;
for (const auto [maximum, minimum] : {std::pair{0x41100000u, 0x3f800000u}, std::pair{0x42800000u, 0x40000000u}}) {
queue.context[0x2d6] = 0x1043f;
queue.context[0x2db] = 0x4006a;
queue.context[0x286] = 0x41100000;
const auto quads = AgcDriver::Graphics::DecodeState(queue).stages.tessellation;
Require(quads && quads->inputControlPoints == 4 && quads->outputControlPoints == 4 && quads->domain == 2 && quads->partitioning == 2 && quads->outputTopology == 3 && quads->minLevel == 1.0f && quads->maxLevel == 9.0f && quads->patchesPerGroup == 63, "counter-clockwise quad patches with HOS levels [1, 9] and 63 patches per group decoded differently");
queue.context[0x2db] = 0x40069;
Require(AgcDriver::Graphics::DecodeState(queue).stages.tessellation->outputTopology == 3, "counter-clockwise triangle patches decoded differently");
for (const auto [maximum, minimum, reason] : {std::tuple{0x3f800000u, 0x40000000u, "tessellation level clamps"}, std::tuple{0x41100000u, 0xbf800000u, "tessellation level clamps"}, std::tuple{0x3f000000u, 0x00000000u, "tessellation level clamps"}, std::tuple{0x42800000u, 0x42800000u, "tessellation level clamps"}, std::tuple{0x7fc00000u, 0x3f800000u, "non-finite register"}, std::tuple{0x7f800000u, 0x3f800000u, "non-finite register"}}) {
queue.context[0x286] = maximum;
queue.context[0x287] = minimum;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "tessellation level clamps");
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, reason);
}
queue.context[0x2d6] = 0xc355;
queue.context[0x2db] = 0x40049;
queue.context[0x286] = 0x42800000;
queue.context[0x287] = 0x3f800000;
queue.context[0x2d5] = 0x202d;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "combined tessellation and geometry");
queue.context[0x2d5] = 0x0200210d;
queue.context[0x2d6] = 0;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "control-point counts");
queue.context[0x2d6] = 0xc300;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "NUM_PATCHES is zero");
queue = makeState();
for (const auto value : {0x2003u, 0x2018u, 0x20c0u, 0x80002000u}) {
queue.context[0x2d5] = value;
Expand Down
33 changes: 31 additions & 2 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,35 @@ void verifyMeshConfiguration() {
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
RecompileCacheKey::Build(other, key);
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration");
RecompileRequest hull{};
hull.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}};
hull.context.waveSize = 64;
hull.graphics = GraphicsCompileContext{0u, {}, std::nullopt, TessellationConfiguration{4u, 4u, 2u, 2u, 3u, 1.0f, 9.0f, 63u}, {}};
const auto hullReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(hull));
const auto* replayed = hullReplay.request.graphics && hullReplay.request.graphics->tessellation ? &*hullReplay.request.graphics->tessellation : nullptr;
require(replayed != nullptr && replayed->domain == 2u && replayed->outputTopology == 3u && replayed->minLevel == 1.0f && replayed->maxLevel == 9.0f && replayed->patchesPerGroup == 63u, "the quad tessellation configuration, its level clamps or its patches per group were lost in serialization");
auto clamped = hull;
clamped.graphics->tessellation->maxLevel = 5.0f;
RecompileCacheKey::Build(hull, key);
const auto hullKey = key;
RecompileCacheKey::Build(clamped, key);
require(key != hullKey && RecompileCacheKey::ContextHash(hull) != RecompileCacheKey::ContextHash(clamped), "the hull cache keys ignore the tessellation level clamps");
hull.shader.stage = ShaderStage::TessellationEvaluation;
clamped.shader.stage = ShaderStage::TessellationEvaluation;
RecompileCacheKey::Build(hull, key);
const auto domainKey = key;
RecompileCacheKey::Build(clamped, key);
require(key == domainKey, "the domain cache key depends on the tessellation level clamps that only hull programs apply");
auto grouped = hull;
grouped.graphics->tessellation->patchesPerGroup = 62u;
for (const auto stage : {ShaderStage::Local, ShaderStage::TessellationControl, ShaderStage::TessellationEvaluation}) {
hull.shader.stage = stage;
grouped.shader.stage = stage;
RecompileCacheKey::Build(hull, key);
const auto patchKey = key;
RecompileCacheKey::Build(grouped, key);
require((key != patchKey) == (stage != ShaderStage::Local), "only the hull and domain cache keys may depend on the patches per group, which bound their relative patch index");
}
const std::array<std::uint32_t, 8> users{};
request.context.waveSize = 32u;
request.context.userData = users;
Expand Down Expand Up @@ -1625,12 +1654,12 @@ void verifyPixelRequestSerialization() {
minimal.context.waveSize = 64;
minimal.context.pixel = ShaderPixelStageInfo{};
const auto encoded = serializer.Serialize(minimal);
require(requestPrefix(encoded, 8u) == "NVNQQRAAAAA=", "new requests did not use serialization version 16");
require(requestPrefix(encoded, 8u) == "NVNQQRIAAAA=", "new requests did not use serialization version 18");
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
for (std::size_t bytes = 0; bytes < 18u; ++bytes) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source or alpha-to-coverage flag was accepted");
}
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQREAAAA="}) {
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRMAAAA="}) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
}
}
Expand Down
Loading
Loading