fix(agc): fix missing register at DWORD 677 (wip)

This commit is contained in:
boykopovar 2026-09-15 12:38:01 +03:00
parent 77b96978c7
commit 215e3cbf48
5 changed files with 30 additions and 10 deletions

View File

@ -64,5 +64,6 @@ add_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL
Execution/src/GuestMemory.cpp
)
target_include_directories(agc_driver_graphics_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
target_link_libraries(agc_driver_graphics_tests PRIVATE libc)
set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_graphics_tests)

View File

@ -15,7 +15,7 @@ using Registers = std::map<std::uint32_t, std::uint32_t>;
struct QueueState {
Registers shader;
Registers context;
Registers userConfig;
Registers userConfig{{0x24b, 0}};
std::optional<Registers> savedContext;
std::array<std::uint32_t, 0x3000> constantRam{};
std::uint64_t indexBase = 0;

View File

@ -24,6 +24,8 @@
Missing required registers and unsupported active state raise exceptions. Unsupported resources are never replaced with empty buffers or dummy images. The renderer never substitutes a clear for a failed draw.
Primitive restart uses the PS5 user-config register `GE_MULTI_PRIM_IB_RESET_EN` at `0x24B`, not the legacy context register at `0x2A5`. Queue creation/reset initializes it to zero; context-only clear preserves it. This matches `UserConfig::m_primitive_reset_control` and `HwUcSetMultiPrimIbReset` in `raw/source/KytyPS5-main/src/graphics/guest_gpu`, and the user-config bank of the project's AGC register defaults. Missing entries still fail at read time. Legacy context stream-output registers are not required for the supported primitive-generation stage; transform-feedback SPIR-V remains unsupported.
The target's existing contents are uploaded before rendering. A render pass uses LOAD/STORE, and the finished image and writable shader buffers are copied back to guest memory after the GPU fence completes. Queue cleanup on an exceptional completion path must finish before Vulkan objects used by the queue can be destroyed; cleanup does not retry the draw.
This path deliberately creates transient resources per draw. It provides no resource or pipeline cache. It does not establish a tiled VideoOut rendering path: VideoOut currently expects tiled display memory, while this renderer accepts linear color surfaces only. Tiled rendering, textures/samplers, depth/stencil, additional PM4 draw variants and presentation extension compatibility remain separate work.

View File

@ -4,13 +4,18 @@
#include <bit>
#include <cmath>
#include <limits>
#include <sstream>
namespace AgcDriver::Graphics {
namespace {
std::uint32_t read(const Registers& registers, std::uint32_t offset) {
std::uint32_t read(const Registers& registers, std::uint32_t offset, const char* bank = "context") {
const auto it = registers.find(offset);
Require(it != registers.end(), "missing register at DWORD " + std::to_string(offset));
if (it == registers.end()) {
std::ostringstream message;
message << "missing register in " << bank << " bank at DWORD 0x" << std::hex << offset << " (" << std::dec << offset << ')';
throw std::runtime_error("AGC graphics: " + message.str());
}
return it->second;
}
@ -20,8 +25,8 @@ float readFloat(const Registers& registers, std::uint32_t offset) {
return value;
}
void zero(const Registers& registers, std::uint32_t offset, std::uint32_t mask, const char* name) {
Require((read(registers, offset) & mask) == 0, std::string(name) + " is unsupported");
void zero(const Registers& registers, std::uint32_t offset, std::uint32_t mask, const char* name, const char* bank = "context") {
Require((read(registers, offset, bank) & mask) == 0, std::string(name) + " is unsupported");
}
VkBlendFactor blendFactor(std::uint32_t value) {
@ -78,16 +83,14 @@ void intersect(VkRect2D& result, const Registers& registers, std::uint32_t offse
State DecodeState(const QueueState& queue) {
const auto& cx = queue.context;
State result{};
const auto primitive = read(queue.userConfig, 0x242);
const auto primitive = read(queue.userConfig, 0x242, "user-config");
switch (primitive) {
case 4: result.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; break;
case 5: result.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; break;
case 6: result.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; break;
default: throw std::runtime_error("AGC graphics: unsupported primitive type " + std::to_string(primitive));
}
zero(cx, 0x2a5, ~0u, "primitive restart");
zero(cx, 0x2e5, ~0u, "stream output");
zero(cx, 0x2e6, ~0u, "stream output buffers");
zero(queue.userConfig, 0x24b, ~0u, "primitive restart (GE_MULTI_PRIM_IB_RESET_EN)", "user-config");
Require(read(cx, 0x2d5) == 0x2000u, "only a wave64 primitive-generation vertex stage without tessellation or geometry amplification is supported");
zero(cx, 0x1b6, 0x8000u, "wave32 fragment shaders");
zero(cx, 0x207, ~0u, "clip distances, layer, viewport or auxiliary vertex exports");

View File

@ -14,7 +14,7 @@ AgcDriver::QueueState makeState() {
AgcDriver::QueueState queue;
queue.userConfig[0x242] = 4;
queue.context = {
{0x2a5, 0}, {0x2e5, 0}, {0x2e6, 0}, {0x2d5, 0x2000},
{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},
@ -55,11 +55,25 @@ void expectFailure(TAction action, std::string_view reason) {
}
void stateTests() {
AgcDriver::QueueState initial;
Require(initial.userConfig.at(0x24b) == 0, "primitive restart must be disabled in initial queue state");
initial.userConfig[0x24b] = 1;
initial.ClearContext();
Require(initial.userConfig.at(0x24b) == 1, "context clear must preserve user configuration");
initial = AgcDriver::QueueState{};
Require(initial.userConfig.at(0x24b) == 0, "queue reset must disable primitive restart");
auto queue = makeState();
auto state = AgcDriver::Graphics::DecodeState(queue);
Require(state.color.address == reinterpret_cast<std::uintptr_t>(colorMemory.data()) && state.color.bytes == colorMemory.size(), "render-target address or size changed");
Require(state.viewport.y == 4 && state.viewport.height == -4, "negative viewport height was lost");
Require(state.color.format == VK_FORMAT_R8G8B8A8_UNORM, "RGBA format changed");
queue.userConfig[0x24b] = 1;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "GE_MULTI_PRIM_IB_RESET_EN");
queue = makeState();
queue.userConfig.erase(0x24b);
queue.context[0x2a5] = 0;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "user-config bank at DWORD 0x24b");
queue = makeState();
queue.context[0x90] = 0x80010003;
queue.context[0x91] = 0x30020;
state = AgcDriver::Graphics::DecodeState(queue);