Skip to content
Merged
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
4 changes: 3 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2386,8 +2386,10 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_draw_cache_reuse_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0;APS5_VERIFY_DRAW_RECIPE=1;APS5_VERIFY_REGISTER_KEYS=1" SKIP_RETURN_CODE 77)
set_tests_properties(agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline PROPERTIES FAIL_REGULAR_EXPRESSION "FATAL:")
set_property(TEST agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off APPEND PROPERTY ENVIRONMENT "APS5_VERIFY_DRAW_RECIPE=1" "APS5_VERIFY_REGISTER_KEYS=1")
add_test(NAME agc_driver_draw_data_hit_user_words_verify COMMAND agc_driver_draw_data_hit_user_words_tests)
set_tests_properties(agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT "APS5_VERIFY_DRAW_DATA_HITS=1" SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct DispatchVariant {
std::vector<std::int8_t> runBases;
std::uint64_t relocatableWords = 0;
std::vector<std::pair<std::uint32_t, std::uint32_t>> shaderDataPatches;
std::uint64_t userDataMask = ~std::uint64_t{0};
std::shared_ptr<const ShaderRecompiler::ShaderVertexStageInfo> vertexInfo;

std::shared_ptr<ShaderMemory> memory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct StageCapture {
std::uint64_t forgetSerial = 0;
std::uint32_t pushOffset = 0;
ShaderRecompiler::SrtUserDataUse userDataUse;
std::uint64_t userDataMask = ~std::uint64_t{0};
};

struct RelocatableUserWord {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ std::shared_ptr<const ShaderRecompiler::RecompileResult> Driver::materializeDraw
}
stageCapture.capture = capture;
timing.Mark("materialize");
const auto walked = capture->userDataUse.asValue | capture->userDataUse.asBase;
auto mask = walked == 0 ? ~std::uint64_t{0} : walked | stageCapture.compiled->userDataRegisterReads;
if (vertexInfos[i]) {
for (const auto reg : {static_cast<std::uint32_t>(vertexInfos[i]->fetchAttribReg), static_cast<std::uint32_t>(vertexInfos[i]->fetchBufferReg)}) {
mask |= reg + 1u < 64u ? std::uint64_t{3} << reg : ~std::uint64_t{0};
}
}
stageCapture.userDataMask = mask;
phaseTiming.Phase(DrawRowRecompile);
return stageCapture.compiled;
}
Expand Down Expand Up @@ -157,6 +165,7 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP
variant->shader = programs[i].snapshot;
variant->forgetSerial = stageCapture.forgetSerial;
variant->pushOffset = stageCapture.pushOffset;
variant->userDataMask = stageCapture.userDataMask;
if (vertexInfos[i]) variant->vertexInfo = std::make_shared<const ShaderRecompiler::ShaderVertexStageInfo>(*vertexInfos[i]);

std::vector<ShaderRecompiler::MemoryRegion> regions(stageCapture.regions.begin(), stageCapture.regions.end());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,16 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::DrawCache);
std::optional<SampledReadScope> sampling;
for (std::size_t i = 0; i < programs.size(); ++i) {
if (roles[i] == Role::GeometryBack || programs[i].userData != dataEntry->decode->programs[i].userData) continue;
if (roles[i] == Role::GeometryBack || programs[i].userData.size() != dataEntry->decode->programs[i].userData.size()) continue;
const auto& now = programs[i].userData;
const auto& was = dataEntry->decode->programs[i].userData;
auto& candidates = dataCandidates[i];
for (const auto& variant : dataEntry->stages[i]) {
bool usedEqual = true;
for (std::size_t w = 0; w < now.size() && usedEqual; ++w) {
if (now[w] != was[w] && (w >= 64u || ((variant->userDataMask >> w) & 1u) != 0)) usedEqual = false;
}
if (!usedEqual) continue;
if ((i == 0 && variant->pushOffset != 0) || std::any_of(candidates.begin(), candidates.end(), [&](const auto& candidate) { return candidate.first->pushOffset == variant->pushOffset; })) continue;
std::vector<ShaderRecompiler::MemoryRegion> regions;
appendEntryRegions(*variant, regions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel
constexpr std::uint32_t FileMagic = 0x4c4a5041u;
// Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the
// layout guards above do not catch (a field renumbered or reinterpreted at the same size).
constexpr std::uint32_t FileFormat = 4u;
constexpr std::uint32_t FileFormat = 5u;
constexpr std::uint32_t ModuleRecord = 1u;
constexpr std::uint32_t GraphicsRecord = 2u;
constexpr std::uint32_t ComputeRecord = 3u;
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ void requireSameResult(const RecompileResult& left, const RecompileResult& right
require(left.shaderDataUserWords == right.shaderDataUserWords, prefix + "shader-data user words differ");
require(left.imageTableRanges == right.imageTableRanges && left.imageTableShader == right.imageTableShader && left.imageTableFaults == right.imageTableFaults && left.imageTablePoison.size() == right.imageTablePoison.size(), prefix + "image table invocation data differs");
require(left.imageTableReadRanges == right.imageTableReadRanges, prefix + "image table read ranges differ");
require(left.userDataRegisterReads == right.userDataRegisterReads, prefix + "user data register read mask differs");
for (std::size_t i = 0; i < left.imageTablePoison.size(); ++i) {
const auto& a = left.imageTablePoison[i];
const auto& b = right.imageTablePoison[i];
Expand Down Expand Up @@ -232,6 +233,7 @@ RecompileResult sampleResult() {
result.imageTableReadRanges = {{{0x1000u, 32u}, {0x1040u, 32u}}, {}, {{0x5000000000ull, 16u}}};
result.imageTableShader = 0x80003aa700ull;
result.imageTableFaults = 3u;
result.userDataRegisterReads = (std::uint64_t{1} << 63u) | (std::uint64_t{1} << 32u) | 0x25u;
return result;
}

Expand Down
233 changes: 233 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/DrawDataHitUserWords.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <initializer_list>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

namespace {

using AgcDriver::Graphics::Require;

alignas(256) constexpr std::array<std::uint32_t, 8> VertexCode{0x7e000208u, 0x7e020280u, 0x7e040280u, 0x7e0602f2u, 0xf80008cfu, 0x03020100u, 0xbf810000u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 11> PixelCode{0xf4080100u, 0xfa000000u, 0xbf8cc07fu, 0xe0300000u, 0x80010000u, 0xbf8c3f70u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 16> TriangleVertexCode{
0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u,
0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u,
};
alignas(256) constexpr std::array<std::uint32_t, 10> ArithmeticPixelCode{0xf4000100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0x06000002u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u};
alignas(256) std::array<float, 4> PixelConstant{};
alignas(256) std::array<std::byte, 1024> ColorMemory{};

constexpr std::size_t BlockBytes = 65536;

struct Header {
Shader shader{};
ShaderUserData userData{};
};

alignas(8) Header VertexHeader{};
alignas(8) Header PixelHeader{};
alignas(8) Header ArithmeticPixelHeader{};

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);
}

std::uint32_t* GuestBlock() {
#ifdef _WIN32
auto* block = static_cast<std::uint32_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
auto* block = static_cast<std::uint32_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
#endif
Require(block != nullptr, "data-only hit: cannot allocate a guest block");
std::memset(block, 0, BlockBytes);
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
return block;
}

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);
}

void PixelUserData(std::vector<std::uint32_t>& commands, const std::uint32_t* pointer, std::uint32_t unread) {
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
Append(commands, 0x76, {0x00c, static_cast<std::uint32_t>(address)});
Append(commands, 0x76, {0x00d, static_cast<std::uint32_t>(address >> 32u)});
Append(commands, 0x76, {0x00e, unread});
}

std::vector<std::uint32_t> Setup(bool triangle = false) {
const auto color = reinterpret_cast<std::uintptr_t>(ColorMemory.data());
const auto vertex = reinterpret_cast<std::uintptr_t>(triangle ? TriangleVertexCode.data() : VertexCode.data());
const auto pixel = reinterpret_cast<std::uintptr_t>(triangle ? ArithmeticPixelCode.data() : PixelCode.data());
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},
{0x318, static_cast<std::uint32_t>(color >> 8u)}, {0x390, static_cast<std::uint32_t>(color >> 40u)},
{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)},
};
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, triangle ? 0u : 1u << 1u}, {0x8c, 0},
{0x008, static_cast<std::uint32_t>(pixel >> 8u)}, {0x009, static_cast<std::uint32_t>(pixel >> 40u)}, {0x00b, 3u << 1u},
};
if (triangle) context.insert(context.end(), {{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}});
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});
return commands;
}

AgcDriver::DriverDetail::DrawEntryCounters Submit(std::vector<std::uint32_t> commands) {
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
AgcDriverWaitIdle_nid_postfix();
return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
}

std::string Counts(const AgcDriver::DriverDetail::DrawEntryCounters& counters) {
return std::to_string(counters.lookups) + " lookups, " + std::to_string(counters.dataHits) + " data-only hits, stages " + std::to_string(counters.dataStagesReused) + " reused / " + std::to_string(counters.dataStagesCompiled) + " compiled / " + std::to_string(counters.dataVerified) + " verified";
}

void UnreadUserWordsKeepTheStage(bool verify) {
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
auto* first = GuestBlock();
auto* second = GuestBlock();
const auto describe = [](std::uint32_t* table, std::uint32_t* data, float value) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
data[0] = std::bit_cast<std::uint32_t>(value);
table[0] = static_cast<std::uint32_t>(address);
table[1] = static_cast<std::uint32_t>(address >> 32u) & 0xffffu;
table[2] = 16;
table[3] = 0x4bfacu;
};
describe(first, first + 1024, 1.0f);
describe(second, second + 1024, 0.5f);

auto commands = Setup();
PixelUserData(commands, first, 0x11);
Append(commands, 0x2d, {3, 2});
auto counters = Submit(std::move(commands));
Require(counters.lookups == 1 && counters.dataHits == 0, "the first draw: " + Counts(counters));
commands.clear();
PixelUserData(commands, first, 0x11);
Append(commands, 0x2d, {3, 2});
counters = Submit(std::move(commands));
Require(counters.inserts != 0, "the repeated register key was not admitted: " + Counts(counters));

commands.clear();
PixelUserData(commands, first, 0x22);
Append(commands, 0x2d, {3, 2});
const auto before = counters;
counters = Submit(std::move(commands));
const auto reused = counters.dataStagesReused - before.dataStagesReused;
const auto compiled = counters.dataStagesCompiled - before.dataStagesCompiled;
Require(counters.dataHits == before.dataHits + 1, "a draw that changed only a user word no stage reads was not a data-only hit: " + Counts(counters));
if (verify) Require(compiled == 2 && counters.dataVerified - before.dataVerified == 2, "verify mode: a draw that changed only an unread user word did not build and compare both stages: " + Counts(counters));
else Require(reused == 2 && compiled == 0, "a draw that changed only a user word no stage reads did not reuse both stages: " + Counts(counters));

commands.clear();
PixelUserData(commands, second, 0x22);
Append(commands, 0x2d, {3, 2});
const auto moved = counters;
counters = Submit(std::move(commands));
Require(counters.dataHits == moved.dataHits + 1, "a draw that pointed the pixel stage at another buffer descriptor was not a data-only hit: " + Counts(counters));
Require(counters.dataStagesCompiled - moved.dataStagesCompiled >= 1, "a draw that pointed the pixel stage at another buffer descriptor reused the pixel stage: " + Counts(counters));
if (!verify) Require(counters.dataStagesReused - moved.dataStagesReused == 1, "a draw that changed only the pixel stage's descriptor pointer did not reuse the vertex stage: " + Counts(counters));
}

unsigned BrightestColorByte() {
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(ColorMemory.data()), ColorMemory.size());
unsigned brightest = 0;
for (const auto value : ColorMemory) brightest = std::max(brightest, static_cast<unsigned>(std::to_integer<std::uint8_t>(value)));
return brightest;
}

void ReadUserWordsChangeTheOutput() {
Registered(VertexHeader, TriangleVertexCode.data(), sizeof(TriangleVertexCode), 2);
Registered(ArithmeticPixelHeader, ArithmeticPixelCode.data(), sizeof(ArithmeticPixelCode), 1);
PixelConstant[0] = 0.25f;
auto* table = reinterpret_cast<std::uint32_t*>(PixelConstant.data());
const auto draw = [&](float word, bool setup) {
auto commands = setup ? Setup(true) : std::vector<std::uint32_t>{};
PixelUserData(commands, table, std::bit_cast<std::uint32_t>(word));
Append(commands, 0x2d, {3, 2});
Submit(std::move(commands));
return BrightestColorByte();
};
draw(0.25f, true);
const auto first = draw(0.25f, false);
const auto before = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
const auto second = draw(0.5f, false);
const auto after = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
const auto closeTo = [](unsigned value, unsigned expected) { return value + 2 >= expected && value <= expected + 2; };
Require(closeTo(first, 128), "a pixel stage adding user word 2 (0.25) to a loaded 0.25 wrote " + std::to_string(first) + ", expected 128");
Require(closeTo(second, 191), "a pixel stage adding user word 2 changed to 0.5 wrote " + std::to_string(second) + ", expected 191 (" + Counts(after) + ", before: " + Counts(before) + ")");
}

}

int main() {
try {
bool libraries = false;
{
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
libraries = device->GraphicsPipelineLibraries();
}
if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped;
const bool verify = std::getenv("APS5_VERIFY_DRAW_DATA_HITS") != nullptr;
UnreadUserWordsKeepTheStage(verify);
ReadUserWordsChangeTheOutput();
AgcDriverShutdown_nid_postfix();
std::cout << "Data-only hits " << (verify ? "verified" : "reused") << " the stages whose read user words did not change\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
AgcDriverShutdown_nid_postfix();
return 1;
}
}
4 changes: 4 additions & 0 deletions core/shader/recompiler/Recompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,10 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
.stencilReferenceDword = artifact.stencilReferenceDword,
.variantId = artifact.variantId
};
for (const auto reg : variant.bindings.layout.userDataRegisters) {
const auto index = reg - request.context.userDataBaseRegister;
result.userDataRegisterReads |= reg >= request.context.userDataBaseRegister && index < 64u ? std::uint64_t{1} << index : ~std::uint64_t{0};
}
const auto plan = preparedBindingPlan(variant, snapshot, request.context.pixel ? std::span<const std::uint8_t>(request.context.pixel->targetExportMapping) : std::span<const std::uint8_t>{}, request.target.registrationModules);
const DescriptorBindingPlan* bindingPlan = &plan->bindings;
std::shared_ptr<PreparedModuleEntry> entry;
Expand Down
1 change: 1 addition & 0 deletions core/shader/recompiler/Recompiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ struct ShaderInvocation {
std::vector<std::byte> pushConstants;
bool readsDepthRemapPush = false;
std::vector<VertexAttribute> vertexAttributes;
std::uint64_t userDataRegisterReads = 0;
std::uint32_t poisonedSrtReads = 0;
std::vector<ImageTableEntryPoison> imageTablePoison;
std::vector<std::pair<std::uint64_t, std::uint64_t>> imageTableRanges;
Expand Down
Loading
Loading