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:
commit
f90ccd797a
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ struct StageCapture {
|
|||
std::uint32_t pushOffset = 0;
|
||||
};
|
||||
|
||||
bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry& registry);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue