Files
AnyPS5/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp
T
Scott SchusterandClaude Opus 5.5 b958425977 perf(shader): store multi-dword global writes with one BDA lookup
A global_store_dwordx2/x3/x4 was split into one StoreAddressU32 per
dword, each with its own write-pointer lookup and its own 4-byte store.
Across a wave those are strided partial writes, which are very slow to
host memory: RADV's clear_htile_mask kernel (one dwordx4 load and store
per thread over 640 KiB) took ~9 ms. Multi-dword stores are now one
StoreAddressU32xN; the emitter probes the whole span once and, when it
is mapped writable and 4-byte aligned, stores it as one vector (16- or
8-byte aligned) or as consecutive dwords, noting each written page.
Anything else takes the per-dword path as before, so faults and
range-spanning stores behave as they did. APS5_BDA_DWORD_STORES=1
restores per-dword stores.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:46:34 -05:00

109 lines
6.1 KiB
C++

#include "BdaShader.hpp"
#include "SpirvBackend/SpirvBda.hpp"
#include "SpirvBackend/SpirvMemory/SpirvTypes.hpp"
#include "SpirvBackend/SpirvMemory/SpirvConstants.hpp"
#include "IntermediateRepresentation/IrBuilder.hpp"
namespace {
template<typename TEmit>
std::vector<std::uint32_t> MakeShader(std::uint64_t address, bool coherent, bool stops, const TEmit& emit, std::uint32_t extracted = 0, bool writes = false) {
using namespace ShaderRecompiler;
IrProgram program;
program.Resources().stage = IrShaderStage::Compute;
program.Info().usesDma = true;
program.Info().bdaWrites = writes;
if (coherent) {
MemoryInfo memory;
memory.coherent = true;
program.Resources().memoryInfo = {memory};
}
SpirvEmitterState state(program, {});
EmitBaseHeader(state.module, program);
const auto define = [&](std::uint32_t binding) {
const auto variable = state.module.DefineGlobalVariable(TypeStorageBufferPointer(state), spv::StorageClassStorageBuffer);
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0u);
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding, binding);
return variable;
};
state.bdaPagetableVariable = define(0);
state.faultBufferVariable = define(1);
const auto output = define(2);
std::vector<std::uint32_t> interfaceVariables;
if (!stops) {
interfaceVariables.push_back(state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassInput, TypeU32(state)), spv::StorageClassInput));
state.module.AddAnnotation(spv::OpDecorate, interfaceVariables[0], spv::DecorationBuiltIn, spv::BuiltInLocalInvocationIndex);
}
state.bdaStopsInvocations = stops;
DefineGetBdaPointer(state);
const auto main = state.module.AllocateId();
state.module.AddFunction(spv::OpFunction, TypeVoid(state), main, spv::FunctionControlMaskNone, TypeFunction(state));
EmitLabel(state, state.module.AllocateId());
SpirvValueEmitContext ctx(state);
auto& instruction = extracted != 0u ? program.CreateValue(IrOpcode::LoadAddressU32x4, IrType::U32x4) : program.CreateValue(IrOpcode::LoadAddressU32, IrType::U32);
IrBuilder ir(program);
for (std::uint32_t dword = 0; dword < 4u; ++dword) {
if ((extracted & (1u << dword)) == 0u) continue;
auto& extract = program.CreateValue(IrOpcode::CompositeExtractU32x4, IrType::U32);
extract.AddArgument(&instruction);
extract.AddArgument(&ir.Constant(dword));
}
MemoryFlags flags{};
flags.pc = 0x1234;
instruction.SetFlags(flags);
auto base = BdaConstant(state, address);
if (!stops) {
const auto index = state.module.AllocateId();
state.module.AddFunction(spv::OpLoad, TypeU32(state), index, interfaceVariables[0]);
base = Binary(state, spv::OpIAdd, TypeScalarU64(state), base, Unary(state, spv::OpUConvert, TypeScalarU64(state), index));
}
const auto values = emit(ctx, instruction, base);
for (std::uint32_t index = 0; index < values.size(); ++index) state.module.AddFunction(spv::OpStore, BdaWord(state, output, ConstantU32(state, index)), values[index]);
state.module.AddFunction(spv::OpReturn);
state.module.AddFunction(spv::OpFunctionEnd);
state.module.AddExecutionMode(main, spv::ExecutionModeLocalSize, 1u, 1u, 1u);
state.module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", interfaceVariables);
return state.module.Finalize();
}
}
std::vector<std::uint32_t> MakeBdaTestShader(std::uint64_t address, std::uint32_t bits, std::int64_t offset) {
using namespace ShaderRecompiler;
return MakeShader(address, false, true, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) {
if (offset != 0) base = AddBdaAddress(ctx, instruction, base, BdaConstant(ctx.state, offset < 0 ? std::uint64_t{0} - static_cast<std::uint64_t>(offset) : static_cast<std::uint64_t>(offset)), offset < 0);
return std::vector<std::uint32_t>{EmitBdaRead(ctx, instruction, base, bits)};
});
}
std::vector<std::uint32_t> MakeBdaSpanReadTestShader(std::uint64_t address, std::uint32_t offset, std::uint32_t extracted, bool coherent, bool stops) {
using namespace ShaderRecompiler;
return MakeShader(address, coherent, stops, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) {
const auto values = EmitBdaDwordReads(ctx, instruction, base, offset, 4u);
return std::vector<std::uint32_t>(values.begin(), values.end());
}, extracted);
}
std::vector<std::uint32_t> MakeBdaDwordWriteTestShader(std::uint64_t address, std::uint32_t dwords, const std::uint32_t* values) {
using namespace ShaderRecompiler;
return MakeShader(address, false, true, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) {
auto& state = ctx.state;
std::vector<std::uint32_t> parts;
for (std::uint32_t dword = 0; dword < dwords; ++dword) parts.push_back(ConstantU32(state, values[dword]));
const auto composite = state.module.AllocateId();
if (dwords == 2u) state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 2u), composite, parts[0], parts[1]);
else if (dwords == 3u) state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3u), composite, parts[0], parts[1], parts[2]);
else state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4u), composite, parts[0], parts[1], parts[2], parts[3]);
EmitBdaDwordWrites(ctx, instruction, base, 0u, dwords, composite);
return std::vector<std::uint32_t>{ConstantU32(state, 1u)};
}, 0u, true);
}
std::vector<std::uint32_t> MakeBdaDwordReadTestShader(std::uint64_t address, std::uint32_t dwords, bool coherent, bool stops) {
using namespace ShaderRecompiler;
return MakeShader(address, coherent, stops, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) {
const auto values = EmitBdaDwordReads(ctx, instruction, base, 0u, dwords, true);
return std::vector<std::uint32_t>(values.begin(), values.begin() + dwords);
});
}