Merge pull request #1732 from TheAggie/feat/agc-raw-graphics-programs

feat(agc): accept graphics programs without an AGC shader header
This commit is contained in:
Dean 2026-10-11 18:33:47 +13:00 committed by GitHub
commit f90ccd797a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 241 additions and 30 deletions

View File

@ -934,6 +934,8 @@ target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGE
target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(agc_driver_interpolation_modes_tests PRIVATE shader_recompiler_agc_driver)
target_include_directories(agc_driver_prepared_unregistered_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
)

View File

@ -81,6 +81,8 @@ struct StageCapture {
std::uint32_t pushOffset = 0;
};
bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry& registry);
}
#endif

View File

@ -114,6 +114,7 @@ private:
static std::size_t drawCacheEntries();
void accountDrawVariant(const DispatchVariant& variant, bool added);
void insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::shared_ptr<const DrawDecode> decode);
void eraseDrawEntry(std::uint64_t key, const std::shared_ptr<DrawEntry>& expected);
std::shared_ptr<const DrawRecipe> findDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages);
void attachDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages, std::shared_ptr<const DrawRecipe> recipe);
void reportDrawCache(DrawEntryCounters& counters);

View File

@ -84,7 +84,10 @@ struct ShaderSnapshot {
};
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address);
std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address);
std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list<std::uint8_t> types);
void NoteRawGraphicsDraw();
bool RawGraphicsDrawn();
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);

View File

@ -17,7 +17,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
PerformanceTimer timing("Driver.Dispatch");
const auto address = (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20d) & 0xffu) << 40u);
auto registeredShader = RegisteredProgram(*submission.shaders, address, {0});
if (!registeredShader) registeredShader = ReadRawComputeShader(address);
if (!registeredShader) registeredShader = ReadRawShader(address);
const auto& snapshot = *registeredShader;
require(snapshot.type == 0, "compute program refers to a non-compute shader");
const auto userCount = (readRegister(queue.shader, 0x213) >> 1u) & 0x1fu;

View File

@ -81,6 +81,13 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
++drawEntryCounters.absent;
}
}
if (decode != nullptr && !DecodeProgramsCurrent(*decode, *submission.shaders)) {
std::lock_guard cacheLock(drawCacheMutex);
eraseDrawEntry(drawKey, entry);
++drawEntryCounters.absent;
entry = nullptr;
decode = nullptr;
}
phaseTiming.Phase(DrawRowKeyLookupValidate);
resolveDrawDecode(queue, submission, decode, registerKey, drawKey, profile);
@ -123,7 +130,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
std::vector<ShaderRecompiler::LinkedProgram> linked;
for (std::size_t i = 0; i < programs.size(); ++i) {
const auto& program = programs[i];
memory.insert(memory.end(), program.memory.begin(), program.memory.end());
for (const auto& region : program.memory) {
if (!region.bytes.empty()) memory.push_back(region);
}
linked.push_back({roles[i], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
}
timing.Mark("prepare");

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp"
#include <algorithm>
#include <cstdlib>
namespace AgcDriver::DriverDetail {
@ -107,6 +108,22 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<Disp
}
}
void Driver::eraseDrawEntry(std::uint64_t key, const std::shared_ptr<DrawEntry>& expected) {
const auto found = drawCache.find(key);
if (found == drawCache.end() || found->second != expected) return;
for (const auto& variants : found->second->stages) {
for (const auto& variant : variants) accountDrawVariant(*variant, false);
}
drawOrder.erase(found->second->order);
drawCache.erase(found);
}
bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry&) {
return std::all_of(decode.programs.begin(), decode.programs.end(), [&](const DrawProgram& program) {
return !program.snapshot->header.empty() || ReadRawShader(program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t)) == program.snapshot;
});
}
std::shared_ptr<const DrawRecipe> Driver::findDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages) {
std::shared_ptr<const std::vector<DrawRecipeRecord>> records;
{

View File

@ -28,10 +28,11 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) {
const bool nullPixel = stage == Stage::Fragment && (address == 0 || pixelSkipped);
if (nullPixel) address = NullPixelProgramAddress();
const auto registered = RegisteredProgram(registry, address, {type});
require(registered != nullptr, "graphics program does not belong to a compatible registered shader");
const auto& snapshot = *registered;
const auto program = ProgramSnapshot(registry, address, {type});
const auto& snapshot = *program;
require((address - snapshot.codeAddress) % sizeof(std::uint32_t) == 0, "graphics entry point is not dword aligned");
require(snapshot.header.empty() || snapshot.type == type, "graphics program refers to an incompatible shader binary type");
if (!nullPixel && snapshot.header.empty()) NoteRawGraphicsDraw();
if (!nullPixel) Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, rsrc2);
const auto resources = nullPixel ? 0u : ReadGraphicsRegister(queue.shader, rsrc2);
const auto userCount = ((resources >> 1u) & 0x1fu) | (((resources >> 27u) & 1u) << 5u);
@ -43,7 +44,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
8,
{},
{{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}},
registered,
program,
codeOffset
};
for (std::uint32_t i = 0; i < userCount; ++i) {
@ -86,9 +87,8 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain);
} else if (graphics.stages.path == Graphics::ShaderPath::Geometry) {
const auto frontAddress = programAddress(0xc8);
const auto snapshot = RegisteredProgram(registry, frontAddress, {2, 4});
require(snapshot != nullptr, "geometry front program is not registered");
const auto type = snapshot->type;
const auto front = ProgramSnapshot(registry, frontAddress, {2, 4});
const auto type = front->header.empty() ? std::uint8_t{2} : front->type;
require(type == 2 || type == 4, "invalid geometry front binary type");
append(0xc8, type, Stage::Mesh, 0x8b, 0x8c, Role::Main);
initializeMerged(programs.back(), 0x82, type == 4);

View File

@ -11,6 +11,7 @@
#include "CompiledVariant.hpp"
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstdio>
#include <cstddef>
@ -37,7 +38,7 @@ std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnosti
}
}
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address) {
std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
GuestMemory::CheckRange(reinterpret_cast<const void*>(address), sizeof(std::uint32_t), 256);
static std::mutex cacheMutex;
static std::list<std::shared_ptr<const ShaderSnapshot>> cache;
@ -100,7 +101,25 @@ std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address
if (snapshot.code.size() == available) break;
}
}
throw std::runtime_error("AGC driver: raw compute program has no reachable end within mapped code or the size limit");
throw std::runtime_error("AGC driver: raw shader program has no reachable end within mapped code or the size limit");
}
std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list<std::uint8_t> types) {
if (auto registered = RegisteredProgram(shaders, address, types)) return registered;
require(shaders.empty(), "graphics program does not belong to a compatible registered shader");
return ReadRawShader(address);
}
namespace {
std::atomic<bool> rawGraphicsDrawn{false};
}
void NoteRawGraphicsDraw() {
rawGraphicsDrawn.store(true, std::memory_order_relaxed);
}
bool RawGraphicsDrawn() {
return rawGraphicsDrawn.load(std::memory_order_relaxed);
}
namespace {
@ -273,8 +292,12 @@ void ReportPreparedAtUse(const ShaderSnapshot& snapshot, const ShaderRecompiler:
if (!snapshot.prepared->deferred) APS5_LOG_ERR("Shader 0x%llx stage %u has no artifact prepared at registration for the state of this draw or dispatch; preparing it at use", static_cast<unsigned long long>(request.shader.codeAddress), static_cast<std::uint32_t>(request.shader.stage));
return;
}
if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader");
APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast<unsigned long long>(snapshot.codeAddress));
if (request.shader.stage == ShaderRecompiler::ShaderStage::Compute) {
if (snapshot.type != 0) throw std::runtime_error("AGC driver: unregistered program is not a compute shader");
APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast<unsigned long long>(snapshot.codeAddress));
return;
}
APS5_LOG_ERR("Graphics shader 0x%llx was not registered; preparing its artifact at first use", static_cast<unsigned long long>(snapshot.codeAddress));
}
}
@ -956,6 +979,7 @@ void Driver::RegisterShader(const Shader* shader) {
PerformanceTimer timing("Shader.Register");
ShaderPreparationTransaction transaction;
CheckFailure();
require(!RawGraphicsDrawn(), "a shader was registered after graphics programs were drawn without registration");
GuestMemory::CheckRange(shader, sizeof(Shader), 1);
Shader fields;
std::memcpy(&fields, static_cast<const void*>(shader), sizeof(Shader));

View File

@ -163,6 +163,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
}
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads, bool staticAbi) {
if (header.empty()) return {};
if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: shader header is smaller than the fixed AGC header");
Shader shader;
std::memcpy(&shader, header.data(), sizeof(Shader));

View File

@ -613,6 +613,14 @@ void ColorPipeBankXorTests() {
}
}
void HeaderlessVertexStageTests() {
const std::array<std::uint32_t, 4> userData{1, 2, 3, 4};
const auto info = AgcDriver::Graphics::DecodeVertexStageInfo({}, 0, userData);
Require(!info.fetchEmbedded && info.resourcesNum == 0, "a vertex program without an AGC header received an embedded fetch table");
const std::array<std::byte, 8> truncated{};
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeVertexStageInfo(truncated, 0x1000, userData)); }, "smaller than the fixed AGC header");
}
void PixelInputLayoutTests() {
using ShaderRecompiler::PixelInput;
using ShaderRecompiler::PixelInputVgpr;
@ -4038,6 +4046,7 @@ int main() {
TuningFieldTests();
PixelInputLayoutTests();
ColorPipeBankXorTests();
HeaderlessVertexStageTests();
ComputeScratchTests();
shaderUserDataTailPaddingTests();
InitialContextTests();

View File

@ -1,6 +1,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "execution/VulkanTestDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp"
#include "prx/libc/include/GuestHeap.hpp"
@ -565,6 +566,42 @@ void testRegisteredFloatMode() {
}
}
void testProgramSnapshots() {
alignas(256) static std::array<std::uint32_t, 64> registered{};
registered.fill(0xbf800000);
registered[0] = 0xbf810000;
alignas(256) static std::array<std::uint32_t, 64> raw{};
raw.fill(0xbf800000);
raw[1] = 0xbf810000;
const auto registeredAddress = reinterpret_cast<std::uintptr_t>(registered.data());
const auto rawAddress = reinterpret_cast<std::uintptr_t>(raw.data());
AgcDriver::DriverDetail::ShaderRegistry shaders;
AgcDriver::DriverDetail::ShaderSnapshot snapshot{registeredAddress, 0x1000, 2, {}, {}};
snapshot.code.assign(registered.begin(), registered.end());
snapshot.header.resize(sizeof(Shader));
const auto entry = std::make_shared<const AgcDriver::DriverDetail::ShaderSnapshot>(std::move(snapshot));
shaders[registeredAddress] = {entry};
check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress, {2}) == entry, "a registered program was not resolved to its registration");
check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress + 4 * 8, {2}) == entry, "an entry inside registered code was not resolved to its registration");
check(!expectFailure([&] { static_cast<void>(AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress, {2})); }).empty(), "an unregistered program was read raw while shaders are registered");
const auto unregistered = AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2});
check(unregistered != entry && unregistered->codeAddress == rawAddress && unregistered->header.empty() && unregistered->code.size() == 2, "an empty registry did not read the program as raw code");
check(AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2}) == unregistered, "unchanged raw code lost its snapshot identity");
check(!expectFailure([] { static_cast<void>(AgcDriver::DriverDetail::ProgramSnapshot({}, 0, {2})); }).empty(), "an unmapped unregistered program was accepted");
AgcDriver::DriverDetail::DrawDecode decode{};
decode.programs.resize(2);
decode.programs[0].snapshot = unregistered;
decode.programs[1].snapshot = entry;
decode.programs[1].codeOffset = 8;
check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "an unchanged decode was not current");
registered[8] = 0xbf810000;
check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten registered program made a decode stale");
raw[0] = 0xbf810000;
check(!AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten unregistered program left its decode current");
decode.programs[0].snapshot = AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2});
check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a decode with the rewritten program was not current");
}
void testWorkerFailure() {
std::array<std::uint32_t, 5> words{0xc0031500, 1, 1, 1, 0x41};
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
@ -593,20 +630,20 @@ int main() {
rawCode[1] = 0xbf810000;
rawCode[2] = 0xbf810000;
const auto rawAddress = reinterpret_cast<std::uintptr_t>(rawCode.data());
const auto literal = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress);
const auto literal = AgcDriver::DriverDetail::ReadRawShader(rawAddress);
check(literal->code.size() == 3 && literal->header.empty(), "raw compute stopped at an instruction literal");
check(AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress) == literal, "unchanged raw compute code lost its snapshot identity");
check(AgcDriver::DriverDetail::ReadRawShader(rawAddress) == literal, "unchanged raw compute code lost its snapshot identity");
rawCode[0] = 0xbf820002;
rawCode[1] = 0xbf810000;
rawCode[2] = 0xbf800000;
rawCode[3] = 0xbf810000;
const auto branched = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress);
const auto branched = AgcDriver::DriverDetail::ReadRawShader(rawAddress);
check(branched->code.size() == 4 && literal->code[0] == 0xbe8003ff, "raw compute lost branch targets or modified an earlier snapshot");
check(branched != literal, "changed raw compute code reused a stale snapshot");
std::array<std::shared_ptr<const AgcDriver::DriverDetail::ShaderSnapshot>, 8> concurrent;
std::vector<std::thread> readers;
for (auto& snapshot : concurrent) readers.emplace_back([&snapshot, rawAddress] {
snapshot = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress);
snapshot = AgcDriver::DriverDetail::ReadRawShader(rawAddress);
});
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == branched, "concurrent raw compute readers lost snapshot reuse");
@ -615,23 +652,23 @@ int main() {
const auto firstAddress = reinterpret_cast<std::uintptr_t>(programs.front().data());
readers.clear();
for (auto& snapshot : concurrent) readers.emplace_back([&snapshot, firstAddress] {
snapshot = AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress);
snapshot = AgcDriver::DriverDetail::ReadRawShader(firstAddress);
});
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == concurrent.front(), "concurrent raw compute misses duplicated snapshots");
const auto evicted = AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress);
const auto evicted = AgcDriver::DriverDetail::ReadRawShader(firstAddress);
for (std::size_t i = 1; i < programs.size(); ++i)
AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000,
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000,
"raw compute cache eviction lost snapshot lifetime or exceeded its entry limit");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress + 4); }).empty(), "raw compute accepted a misaligned entry");
check(!expectFailure([] { AgcDriver::DriverDetail::ReadRawComputeShader(0); }).empty(), "raw compute accepted an unmapped entry");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(rawAddress + 4); }).empty(), "raw compute accepted a misaligned entry");
check(!expectFailure([] { AgcDriver::DriverDetail::ReadRawShader(0); }).empty(), "raw compute accepted an unmapped entry");
alignas(256) std::array<std::uint32_t, 128> straddling{};
straddling.fill(0xbf800000);
straddling[63] = 0xf4000000;
straddling[64] = 0xfa000000;
straddling[65] = 0xbf810000;
const auto straddled = AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast<std::uintptr_t>(straddling.data()));
const auto straddled = AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(straddling.data()));
check(straddled->code.size() == 66 && straddled->code[64] == 0xfa000000, "raw compute stopped at a memory instruction across its first read window");
#ifdef _WIN32
auto* mapping = static_cast<std::uint32_t*>(VirtualAlloc(nullptr, 8192, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
@ -641,21 +678,22 @@ int main() {
auto* boundedCode = mapping + 1024 - 64;
std::fill_n(boundedCode, 64, 0xbf800000u);
const auto boundedAddress = reinterpret_cast<std::uintptr_t>(boundedCode);
const auto unterminated = expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); });
const auto unterminated = expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); });
boundedCode[63] = 0xbf810000;
const auto bounded = AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress);
const auto bounded = AgcDriver::DriverDetail::ReadRawShader(boundedAddress);
check(VirtualProtect(mapping + 1024, 4096, PAGE_READWRITE, &protection) != 0, "cannot extend raw compute mapping");
boundedCode[63] = 0xbf800000;
boundedCode[64] = 0xbf810000;
const auto extended = AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress);
const auto extended = AgcDriver::DriverDetail::ReadRawShader(boundedAddress);
check(extended != bounded && extended->code.size() == 65, "raw compute reused code before its end changed");
check(VirtualProtect(mapping + 1024, 4096, PAGE_NOACCESS, &protection) != 0, "cannot revoke cached raw compute tail");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); }).empty(), "raw compute reused an inaccessible cached tail");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); }).empty(), "raw compute reused an inaccessible cached tail");
check(VirtualProtect(mapping, 4096, PAGE_NOACCESS, &protection) != 0, "cannot revoke cached raw compute code");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); }).empty(), "raw compute reused inaccessible cached code");
check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); }).empty(), "raw compute reused inaccessible cached code");
check(VirtualFree(mapping, 0, MEM_RELEASE) != 0, "cannot release raw compute boundary test");
check(!unterminated.empty() && bounded->code.size() == 64, "raw compute crossed inaccessible memory or missed its last instruction");
#endif
testProgramSnapshots();
testEvents();
testValidation();
testClearState();

View File

@ -244,6 +244,7 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path
Require(program.snapshot->prepared->entries.size() == predicted + 1, "an artifact prepared at a draw was not reused");
}
ShaderRegistry invalidRegistry;
invalidRegistry[1] = {std::make_shared<const ShaderSnapshot>(ShaderSnapshot{1, 0, 2, {}, {}})};
DrawDecode invalid{};
invalid.state = prepared.state;
Reject([&] { DecodeGraphicsPrograms(invalid, queue, invalidRegistry, true, true); }, "registered");

View File

@ -0,0 +1,104 @@
#include "VulkanTestDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp"
#include "SceShaders.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "Optimization/ResourceProgram.hpp"
#include <array>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
alignas(256) constexpr std::array<std::uint32_t, 64> VertexCode{0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 64> PixelCode{0xc8020002u, 0xc8060102u, 0xc80a0202u, 0xc80e0302u, 0xf800180fu, 0x03020100u, 0xbf810000u};
void Bind(AgcDriver::QueueState& queue, const void* code, std::uint32_t program, std::uint32_t resources) {
const auto address = reinterpret_cast<std::uintptr_t>(code);
queue.shader[program] = static_cast<std::uint32_t>(address >> 8u);
queue.shader[program + 1u] = static_cast<std::uint32_t>(address >> 40u);
queue.shader[resources] = 16u << 1u;
}
void Check(AgcDriver::VulkanDevice& device) {
using namespace AgcDriver::DriverDetail;
using namespace ShaderRecompiler;
AgcDriver::QueueState queue{};
queue.context[0x8e] = 0xfu;
queue.context[0x8f] = 0xfu;
Bind(queue, VertexCode.data(), 0xc8u, 0x8bu);
Bind(queue, PixelCode.data(), 0x8u, 0xbu);
DrawDecode draw{};
draw.state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex;
draw.state.stages.vertexWaveSize = 32u;
draw.state.stages.fragmentWaveSize = 32u;
draw.pixel.wave32 = true;
draw.pixel.interpolatorCount = 2;
draw.pixel.interpolatorSettings[0] = 0x403u;
draw.pixel.interpolatorSettings[1] = 0x220u;
draw.pixel.targetOutputMode[0] = 9;
draw.pixel.targetExportMapping.fill(0xe4u);
const ShaderRegistry registry;
DecodeGraphicsPrograms(draw, queue, registry, false, true);
Require(draw.programs.size() == 2 && draw.roles.back() == ProgramRole::Fragment, "unregistered graphics programs did not decode as a vertex and a fragment stage");
for (const auto& program : draw.programs) Require(program.snapshot != nullptr && program.snapshot->header.empty() && program.snapshot->prepared->entries.empty(), "an unregistered graphics program was not read as raw code");
Require(RawGraphicsDrawn(), "a draw of unregistered graphics programs was not noted");
bool refused = false;
try {
Driver::Get().RegisterShader(nullptr);
} catch (const std::exception& error) {
refused = std::string(error.what()).find("registered after graphics programs were drawn") != std::string::npos;
}
Require(refused, "a shader registration after unregistered graphics draws was accepted");
const auto target = device.Target();
std::vector<LinkedProgram> linked;
std::vector<MemoryRegion> memory;
for (std::size_t index = 0; index < draw.programs.size(); ++index) {
const auto& program = draw.programs[index];
linked.push_back({draw.roles[index], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
memory.insert(memory.end(), program.memory.begin(), program.memory.end());
}
std::array<std::uint64_t, 2> variants{};
for (std::size_t index = 0; index < draw.programs.size(); ++index) {
const auto& program = draw.programs[index];
const bool pixel = program.binary.stage == ShaderStage::Fragment;
std::optional<ShaderVertexStageInfo> vertex;
if (!pixel) vertex = AgcDriver::Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
RecompileRequest request{program.binary, {32u, program.firstUserSgpr, program.userData, {}, pixel ? std::optional(draw.pixel) : std::nullopt, vertex, memory}, target, {0, 0, 0, 128u}, GraphicsCompileContext{program.firstUserSgpr, linked, std::nullopt, std::nullopt, {0, 3, 4, 1}}};
static_cast<void>(InvocationFor(*program.snapshot, program.codeOffset, request));
Require(program.snapshot->prepared->entries.size() == 1, "an unregistered graphics program was not prepared at first use");
variants[index] = GetPreparedArtifact(*program.snapshot->prepared->entries.front().handle).variantId;
static_cast<void>(InvocationFor(*program.snapshot, program.codeOffset, request));
Require(program.snapshot->prepared->entries.size() == 1, "a prepared unregistered graphics program was prepared again");
request.layout.pushConstantOffsetBytes = 4;
request.layout.pushConstantSizeBytes -= 4;
static_cast<void>(InvocationFor(*program.snapshot, program.codeOffset, request));
Require(program.snapshot->prepared->entries.size() == 1, "a relocated push-constant block prepared an unregistered graphics program again");
}
ResolvePreparedGraphics(*draw.programs[0].snapshot, draw.programs[1].snapshot, 17, target);
const auto rectangle = PreparedRectangle(*draw.programs[0].snapshot, variants[0], variants[1]);
Require(!rectangle.control.spirv.empty() && !rectangle.evaluation.spirv.empty(), "unregistered graphics programs did not prepare rectangle shaders");
}
}
int main() {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Check(*device);
std::cout << "prepared unregistered graphics tests passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}