Merge pull request #4715 from Lorkydey/fix/agc-inactive-vertex-point-size
fix(agc): accept inactive vertex point size on non-point draws
This commit is contained in:
commit
803cbf2f81
|
|
@ -101,6 +101,28 @@ VkConservativeRasterizationModeEXT decodeConservativeRasterization(const QueueSt
|
|||
// DecodeState would): the masks whose set bits are unsupported, and the depth-control verdict.
|
||||
constexpr std::uint32_t LayerExports = (1u << 18u) | (1u << 19u) | (1u << 21u) | (1u << 24u);
|
||||
constexpr std::uint32_t ClipCullExports = 0xffffu | (1u << 22u) | (1u << 23u);
|
||||
|
||||
std::uint32_t inactiveVertexPointSize(const QueueState& queue) {
|
||||
const auto stages = find(queue.context, 0x2d5);
|
||||
const auto primitive = find(queue.userConfig, 0x242, RegisterBank::UserConfig);
|
||||
const auto raster = find(queue.context, 0x205);
|
||||
if (stages == queue.context.end() || primitive == queue.userConfig.end() || raster == queue.context.end()) return 0;
|
||||
if ((stages->second & 0x24u) != 0) return 0;
|
||||
if ((raster->second & 8u) != 0 && (raster->second & 0x7e0u) != 0x240u) return 0;
|
||||
switch (primitive->second) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 17:
|
||||
return 1u << 16u;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::uint32_t DepthControlMask = ~0x007007f0u;
|
||||
// EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader
|
||||
// to run, which it always does here.
|
||||
|
|
@ -712,7 +734,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
|||
}
|
||||
APS5_LOG_OUT_DEBUG("Topology=%u", static_cast<unsigned>(result.topology));
|
||||
result.primitiveRestart = read(queue.userConfig, 0x24b, RegisterBank::UserConfig) != 0 && !result.rectList && result.topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
zero(cx, 0x207, ~(LayerExports | ClipCullExports), "layer, viewport or auxiliary vertex exports");
|
||||
zero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports");
|
||||
Require(std::popcount(read(cx, 0x207) & 0xffffu) <= 8, "more than eight clip and cull distances are unsupported");
|
||||
result.paClVsOutCntl = read(cx, 0x207);
|
||||
result.samples = decodeSamples(cx);
|
||||
|
|
@ -1274,7 +1296,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
if (value(queue.userConfig, 0x242, primitive) && (primitive & 0x3fu) != 1 && (primitive & 0x3fu) != 2 && (primitive & 0x3fu) != 3 && (primitive & 0x3fu) != 4 && (primitive & 0x3fu) != 5 && (primitive & 0x3fu) != 6) return "AGC graphics: primitive restart is only supported for point, line and triangle topologies";
|
||||
if (value(cx, 0x103, resetIndex) && (resetIndex & 0xffffu) != 0xffffu) return "AGC graphics: primitive restart index other than all ones is unsupported";
|
||||
}
|
||||
if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason;
|
||||
if (value(cx, 0x207, word) && std::popcount(word & 0xffffu) > 8) return require(false, "more than eight clip and cull distances are unsupported");
|
||||
if (value(cx, 0x200, word)) {
|
||||
const auto renderControl = find(cx, 0x000);
|
||||
|
|
|
|||
|
|
@ -101,6 +101,90 @@ void expectFailure(TAction action, std::string_view reason) {
|
|||
throw std::runtime_error("expected graphics rejection: " + std::string(reason));
|
||||
}
|
||||
|
||||
void inactiveVertexPointSizeTests() {
|
||||
const auto pointSizeState = [] {
|
||||
auto queue = makeState();
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2;
|
||||
queue.context[0x207] = 0x01210000;
|
||||
return queue;
|
||||
};
|
||||
for (const auto primitive : {2u, 4u, 5u, 6u, 7u, 17u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.userConfig[0x242] = primitive;
|
||||
for (const auto raster : {0u, 0x240u, 0x248u}) {
|
||||
queue.context[0x205] = raster;
|
||||
const auto rejection = AgcDriver::Graphics::DrawRejection(queue, false);
|
||||
Require(rejection.empty(), "inactive vertex point size failed the draw precheck: " + rejection);
|
||||
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
|
||||
}
|
||||
}
|
||||
for (const auto raster : {0u, 0x240u, 0x248u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.userConfig[0x242] = 3;
|
||||
queue.context[0x205] = raster;
|
||||
const auto rejection = AgcDriver::Graphics::DrawRejection(queue, false);
|
||||
Require(rejection.empty(), "inactive line-strip point size failed the draw precheck: " + rejection);
|
||||
const auto state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP && !state.primitiveRestart, "inactive point size changed line-strip decoding");
|
||||
Require(AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Line, "inactive point size changed the line primitive class");
|
||||
queue.userConfig[0x24b] = 1;
|
||||
queue.context[0x103] = 0xffffffffu;
|
||||
const auto indexedRejection = AgcDriver::Graphics::DrawRejection(queue, true);
|
||||
Require(indexedRejection.empty(), "inactive line-strip point size with primitive restart failed the draw precheck: " + indexedRejection);
|
||||
const auto indexedState = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(indexedState.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP && indexedState.primitiveRestart, "inactive point size changed indexed line-strip decoding");
|
||||
}
|
||||
const auto rejected = [](AgcDriver::QueueState queue) {
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).find("auxiliary vertex exports") != std::string::npos, "unsupported vertex exports passed the draw precheck");
|
||||
try {
|
||||
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
|
||||
} catch (const std::runtime_error&) {
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("potentially active point size passed the state decoder");
|
||||
};
|
||||
for (const auto primitive : {1u, 9u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.userConfig[0x242] = primitive;
|
||||
rejected(queue);
|
||||
}
|
||||
for (const auto stages : {0x2004u, 0x2020u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.context[0x2d5] = stages;
|
||||
rejected(queue);
|
||||
}
|
||||
for (const auto raster : {8u, 0x208u, 0x48u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.context[0x205] = raster;
|
||||
rejected(queue);
|
||||
}
|
||||
for (const auto flag : {1u << 17u, 1u << 20u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.context[0x207] = 0x01210000 | flag;
|
||||
rejected(queue);
|
||||
}
|
||||
for (const auto distances : {1u, 0x100u, 0x00c000ffu, 0x00c0ff00u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.context[0x207] |= distances;
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "inactive point size rejected supported clip/cull distances");
|
||||
Require(AgcDriver::Graphics::DecodeState(queue).paClVsOutCntl == queue.context[0x207], "inactive point size discarded clip/cull distance state");
|
||||
}
|
||||
{
|
||||
auto queue = pointSizeState();
|
||||
queue.context[0x207] |= 0x004001ffu;
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).find("more than eight") != std::string::npos, "inactive point size bypassed the clip/cull count limit");
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "more than eight clip and cull distances");
|
||||
}
|
||||
for (const auto offset : {0x2d5u, 0x205u}) {
|
||||
auto queue = pointSizeState();
|
||||
queue.context.erase(offset);
|
||||
rejected(queue);
|
||||
}
|
||||
auto queue = pointSizeState();
|
||||
queue.userConfig.erase(0x242);
|
||||
rejected(queue);
|
||||
}
|
||||
|
||||
void hdrComponentSwapTests() {
|
||||
using namespace AgcDriver::Graphics;
|
||||
for (const auto number : {0u, 1u, 4u, 7u}) {
|
||||
|
|
@ -1474,6 +1558,17 @@ void DepthStencilTests() {
|
|||
queue.context[0x207] = (1u << 18u) | (1u << 21u) | (1u << 24u);
|
||||
const auto layered = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(layered.color.depthSlice == 1u && layered.color.layers == 3u && layered.renderLayers == 3u && layered.layerExports == (1u << 18u), "a color view of three 3D depth slices did not render three layers from the vertex layer export");
|
||||
{
|
||||
auto pointSize = queue;
|
||||
pointSize.context[0x1b3] = pointSize.context[0x1b4] = 2;
|
||||
Require(AgcDriver::Graphics::DrawRejection(pointSize, false).empty(), "layered volume test state failed the draw precheck");
|
||||
pointSize.context[0x207] |= 1u << 16u;
|
||||
const auto rejection = AgcDriver::Graphics::DrawRejection(pointSize, false);
|
||||
Require(rejection.empty(), "inactive point size rejected a layered volume draw: " + rejection);
|
||||
const auto decoded = AgcDriver::Graphics::DecodeState(pointSize);
|
||||
Require(decoded.color.depthSlice == layered.color.depthSlice && decoded.color.layers == layered.color.layers && decoded.renderLayers == layered.renderLayers && decoded.layerExports == layered.layerExports, "inactive point size changed layered volume routing");
|
||||
Require(decoded.paClVsOutCntl == pointSize.context[0x207], "inactive point size discarded the layered export state");
|
||||
}
|
||||
queue.context[0x31b] = 1u | (8u << 13u);
|
||||
Require(AgcDriver::Graphics::DecodeState(queue).renderLayers == 3u, "a color view past the 3D surface was not clamped to its last slice");
|
||||
queue.context[0x207] = 0;
|
||||
|
|
@ -5783,6 +5878,7 @@ int main(int argc, char** argv) {
|
|||
UnwrittenSampleLocationTests();
|
||||
ShaderStageTests();
|
||||
NggVertexWorkgroupTests();
|
||||
inactiveVertexPointSizeTests();
|
||||
TuningFieldTests();
|
||||
PixelInputLayoutTests();
|
||||
UnexportedPixelInputTests();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
constexpr std::byte Kept{0x40};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 20> VertexCode{
|
||||
0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u,
|
||||
0x7e060280u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) std::array<std::byte, 1024> ClearMemory{};
|
||||
alignas(256) std::array<std::byte, 1024> EnabledMemory{};
|
||||
|
||||
struct Header {
|
||||
Shader shader{};
|
||||
ShaderUserData userData{};
|
||||
};
|
||||
|
||||
alignas(8) Header VertexHeader{};
|
||||
alignas(8) Header PixelHeader{};
|
||||
|
||||
void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) {
|
||||
header.shader.file_header = 0x34333231u;
|
||||
header.shader.version = 0x18u;
|
||||
header.shader.header_size = sizeof(Header);
|
||||
header.shader.shader_size = static_cast<std::uint32_t>(bytes);
|
||||
header.shader.code = code;
|
||||
header.shader.type = type;
|
||||
header.shader.user_data = &header.userData;
|
||||
AgcDriverRegisterShader_nid_postfix(&header.shader);
|
||||
}
|
||||
|
||||
void Append(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::initializer_list<std::uint32_t> payload) {
|
||||
commands.push_back(0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u));
|
||||
commands.insert(commands.end(), payload);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> DrawBoth() {
|
||||
const auto vertex = reinterpret_cast<std::uintptr_t>(VertexCode.data());
|
||||
const auto pixel = reinterpret_cast<std::uintptr_t>(PixelCode.data());
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> context{
|
||||
{0x2d5, 0x2000},
|
||||
{0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800},
|
||||
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0},
|
||||
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
|
||||
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
|
||||
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
|
||||
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
|
||||
{0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
|
||||
{0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u},
|
||||
{0x3b8, 0x9000000}, {0x1e0, 0},
|
||||
{0xc, 0}, {0xd, 0x40040},
|
||||
{0x81, 0x80000000}, {0x82, 0x40040},
|
||||
{0x90, 0x80000000}, {0x91, 0x40040},
|
||||
{0x94, 0x80000000}, {0x95, 0x40040},
|
||||
{0x10f, std::bit_cast<std::uint32_t>(32.0f)}, {0x110, std::bit_cast<std::uint32_t>(32.0f)},
|
||||
{0x111, std::bit_cast<std::uint32_t>(-2.0f)}, {0x112, std::bit_cast<std::uint32_t>(2.0f)},
|
||||
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0},
|
||||
{0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0},
|
||||
};
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> shader{
|
||||
{0xc8, static_cast<std::uint32_t>(vertex >> 8u)}, {0xc9, static_cast<std::uint32_t>(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0},
|
||||
{0x008, static_cast<std::uint32_t>(pixel >> 8u)}, {0x009, static_cast<std::uint32_t>(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
};
|
||||
std::vector<std::uint32_t> commands;
|
||||
Append(commands, 0x79, {0x242, 4});
|
||||
for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value});
|
||||
for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value});
|
||||
const auto draw = [&](std::array<std::byte, 1024>& memory, std::uint32_t exports) {
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(memory.data());
|
||||
Append(commands, 0x69, {0x318, static_cast<std::uint32_t>(color >> 8u)});
|
||||
Append(commands, 0x69, {0x390, static_cast<std::uint32_t>(color >> 40u)});
|
||||
Append(commands, 0x69, {0x207, exports});
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
};
|
||||
draw(ClearMemory, 0x01200000);
|
||||
draw(EnabledMemory, 0x01210000);
|
||||
return commands;
|
||||
}
|
||||
|
||||
void InactivePointSizeDrawsLikeDisabled() {
|
||||
ClearMemory.fill(Kept);
|
||||
EnabledMemory.fill(Kept);
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
auto commands = DrawBoth();
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
const auto kept = static_cast<std::size_t>(std::count(ClearMemory.begin(), ClearMemory.end(), Kept));
|
||||
Require(kept != 0 && kept != ClearMemory.size(), "the triangle without USE_VTX_POINT_SIZE covered " + std::to_string(ClearMemory.size() - kept) + " of " + std::to_string(ClearMemory.size()) + " bytes, expected an edge through the target");
|
||||
for (std::size_t index = 0; index < ClearMemory.size(); ++index) {
|
||||
Require(EnabledMemory[index] == ClearMemory[index], "byte " + std::to_string(index) + " is " + std::to_string(static_cast<unsigned>(EnabledMemory[index])) + " with USE_VTX_POINT_SIZE on a triangle draw and " + std::to_string(static_cast<unsigned>(ClearMemory[index])) + " without it");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
}
|
||||
InactivePointSizeDrawsLikeDisabled();
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::puts("USE_VTX_POINT_SIZE on a triangle draw drew the same pixels as the state without it");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
### Unknown function info
|
||||
|
||||
- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw.
|
||||
- [sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the five validation calls return `0x8A6C1000` without reading their arguments or writing caller memory, and `sceAgcDriverGetShaderDebuggingStatus` returns 1, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L250-L291) reads them from Sony's libSceAgcDriver.sprx: each validation export is `mov eax, 0x8a6c1000; ret` in the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them
|
||||
- [Indices far past every vertex fetch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of `0xffffffff` with primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice
|
||||
- [Depth clear draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a draw with `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` is taken to write `DB_DEPTH_CLEAR` to every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured
|
||||
|
|
|
|||
Loading…
Reference in New Issue