perf(agc): avoid guest memory uploads for rect-list fault buffers

This commit is contained in:
boykopovar 2026-09-24 14:57:39 +03:00
parent fa6d023b1b
commit 2539e9787f
10 changed files with 126 additions and 5 deletions

View File

@ -7,6 +7,7 @@ namespace AgcDriver::Graphics {
class BdaResources {
public:
explicit BdaResources(const Context& context);
BdaResources(const Context& context, const GuestBufferMemory& memory);
VkDescriptorBufferInfo Table() const;
VkDescriptorBufferInfo Fault() const;
@ -15,7 +16,7 @@ public:
private:
std::unique_ptr<Buffer> table;
std::unique_ptr<Buffer> fault;
std::size_t tableBytes;
std::size_t tableBytes = 0;
};
}

View File

@ -51,6 +51,7 @@ private:
GuestBufferMemory guestMemory;
std::unique_ptr<BdaResources> bda;
bool usesBda = false;
bool usesFaultBuffer = false;
VkDescriptorSetLayout _layout = VK_NULL_HANDLE;
VkDescriptorSet _set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;

View File

@ -6,22 +6,27 @@
namespace AgcDriver::Graphics {
BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory) {
BdaResources::BdaResources(const Context& context) {
static_assert(std::endian::native == std::endian::little);
Require(sizeof(ShaderRecompiler::BdaAbi::Fault) <= context.limits.maxStorageBufferRange, "BDA fault buffer exceeds storage buffer range limit");
fault = std::make_unique<Buffer>(context, sizeof(ShaderRecompiler::BdaAbi::Fault), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
std::memset(fault->Bytes().data(), 0, fault->Bytes().size());
}
BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory) : BdaResources(context) {
const auto ranges = memory.AddressRanges();
Require(ranges.size() <= std::numeric_limits<std::uint32_t>::max(), "BDA table range count overflow");
Require(ranges.size() <= (std::numeric_limits<std::size_t>::max() - sizeof(ShaderRecompiler::BdaAbi::Header)) / sizeof(ShaderRecompiler::BdaAbi::Range), "BDA table size overflow");
tableBytes = sizeof(ShaderRecompiler::BdaAbi::Header) + ranges.size() * sizeof(ShaderRecompiler::BdaAbi::Range);
Require(tableBytes <= context.limits.maxStorageBufferRange && sizeof(ShaderRecompiler::BdaAbi::Fault) <= context.limits.maxStorageBufferRange, "BDA descriptors exceed storage buffer range limit");
table = std::make_unique<Buffer>(context, tableBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
fault = std::make_unique<Buffer>(context, sizeof(ShaderRecompiler::BdaAbi::Fault), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
const ShaderRecompiler::BdaAbi::Header header{ShaderRecompiler::BdaAbi::Version, static_cast<std::uint32_t>(ranges.size()), sizeof(ShaderRecompiler::BdaAbi::Range), 0};
std::memcpy(table->Bytes().data(), &header, sizeof(header));
if (!ranges.empty()) std::memcpy(table->Bytes().data() + sizeof(header), ranges.data(), ranges.size() * sizeof(ranges.front()));
std::memset(fault->Bytes().data(), 0, fault->Bytes().size());
}
VkDescriptorBufferInfo BdaResources::Table() const {
Require(table != nullptr, "BDA page table was not requested");
return {table->Handle(), 0, tableBytes};
}

View File

@ -16,7 +16,9 @@ void ShaderResources::prepareAddressBindings(std::span<const CompiledShader> sha
const bool rectListFault = shader.stage == ShaderRecompiler::ShaderStage::TessellationControl && tables == 0 && faults == 1;
Require((tables == faults || rectListFault) && tables <= 1 && faults <= 1, "invalid BDA table and fault descriptors");
Require(shader.program->bdaAbiVersion == (faults == 0 ? 0u : ShaderRecompiler::BdaAbi::Version), "incompatible BDA ABI version");
usesBda = usesBda || faults != 0;
// Rect-list validation needs a fault buffer, but never accesses guest addresses.
usesBda = usesBda || tables != 0;
usesFaultBuffer = usesFaultBuffer || faults != 0;
}
if (usesBda) {
Require(context.bufferDeviceAddress, "buffer device address is not enabled");

View File

@ -114,6 +114,7 @@ void ShaderResources::build(std::span<const CompiledShader> shaders, const Color
Require(storageBuffers <= context.limits.maxDescriptorSetStorageBuffers, "pipeline descriptors exceed device limits");
guestMemory.Upload(usesBda);
if (usesBda) bda = std::make_unique<BdaResources>(context, guestMemory);
else if (usesFaultBuffer) bda = std::make_unique<BdaResources>(context);
std::vector<VkDescriptorSetLayoutBinding> description;
for (const auto& binding : bindings) description.push_back(binding.layout);
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};

View File

@ -58,6 +58,28 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
auto disabled = context;
disabled.bufferDeviceAddress = false;
reject([&] { ShaderResources resources(disabled, compiled, snapshots); }, "not enabled");
{
// A rect-list fault buffer must not require BDA or consume guest snapshots.
ShaderRecompiler::RecompileResult control;
control.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version;
control.bindings = {binding(Role::FaultBuffer, 5)};
auto writable = binding(Role::GuestBuffers, 6);
writable.guestDescriptor = {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32) & 0xffffu, sizeof(guest), 0x31000000u};
control.bindings.push_back(writable);
const std::array<CompiledShader, 1> stages{{{ShaderRecompiler::ShaderStage::TessellationControl, &control, 0}}};
const std::array<GuestMemorySnapshot, 1> unusedSnapshots{{{0, source}}};
ShaderResources resources(disabled, stages, ColorTarget{}, 0, 0, unusedSnapshots);
const auto fault = access.bytes(access.descriptor(5).buffer);
for (const auto byte : fault) Require(byte == std::byte{}, "rect-list fault buffer was not initialized");
const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::InvalidRectangle, 0, 0, 0, 0, 0};
std::memcpy(fault.data(), &report, sizeof(report));
reject([&] { resources.WriteBack(); }, "rect-list requires");
std::memset(fault.data(), 0, fault.size());
changed = 456;
std::memcpy(access.bytes(access.descriptor(6).buffer).data() + sizeof(std::uint32_t), &changed, sizeof(changed));
resources.WriteBack();
Require(guest[1] == changed, "rect-list fault-only path lost guest buffer writes");
}
{
ShaderResources resources(context, compiled, snapshots);
const auto table = access.bytes(access.descriptor(4).buffer);

View File

@ -0,0 +1,15 @@
#ifndef CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVATOMICS_HPP
#define CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVATOMICS_HPP
#include "SpirvBackend/SpirvMemory/SpirvEmitterState.hpp"
#include <cstdint>
namespace ShaderRecompiler {
void EmitDeviceAtomicMemoryBarrier(SpirvEmitterState& state);
std::uint32_t EmitFloatAtomicReplacement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t source, bool maxValue);
std::uint32_t EmitShaderDataDwordLoad(SpirvEmitterState& state, std::uint32_t dwordIndex);
}
#endif

View File

@ -0,0 +1,16 @@
#ifndef CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVFORMATCONVERSION_HPP
#define CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVFORMATCONVERSION_HPP
#include "SpirvBackend/SpirvMemory/SpirvEmitterState.hpp"
#include <cstdint>
namespace ShaderRecompiler {
bool IsSignedFormatComponent(SpirvFormatComponentType type);
std::uint32_t EmitUFloatToF32Bits(SpirvEmitterState& state, std::uint32_t raw, std::uint32_t bits);
std::uint32_t NormalizeFormatComponent(SpirvEmitterState& state, const SpirvBufferFormatInfo& info, std::uint32_t component, std::uint32_t raw);
std::uint32_t EmitTBufferBitcastU32ToI32(SpirvEmitterState& state, std::uint32_t value);
}
#endif

View File

@ -0,0 +1,33 @@
#ifndef CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVINTERFACE_HPP
#define CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVINTERFACE_HPP
#include "SpirvBackend/SpirvMemory/SpirvEmitterState.hpp"
#include "SpirvBackend/SpirvMemory/SpirvModule.hpp"
#include "IntermediateRepresentation/IrValue.hpp"
#include <cstdint>
namespace ShaderRecompiler {
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const BindingAllocationResult& bindings);
void DefineModule(SpirvEmitterState& state);
std::uint32_t DefineInterfaceVariable(SpirvEmitterState& state, std::uint32_t type, std::uint32_t storage, const char* name);
std::uint32_t ExecutionModelForStage(IrShaderStage stage);
std::uint32_t GlslStd450(SpirvEmitterState& state);
std::uint32_t InputVariableForKind(const SpirvEmitterState& state, StageInputKind kind);
const SpirvInputBinding* SpirvInputBindingForParameter(const SpirvEmitterState& state, std::uint32_t location);
std::uint32_t PixelParameterLocation(const SpirvEmitterState& state, std::uint32_t attr);
bool PixelParameterIsFlat(const SpirvEmitterState& state, std::uint32_t attr);
bool PixelParameterIsCustom(const SpirvEmitterState& state, std::uint32_t attr);
VertexInputScalarKind VertexParameterScalarKind(const SpirvEmitterState& state, std::uint32_t location);
std::uint32_t VertexParameterComponentCount(const SpirvInputBinding& input);
std::uint32_t VertexParameterScalarType(SpirvEmitterState& state, VertexInputScalarKind kind);
std::uint32_t OutputVariableForExport(const SpirvEmitterState& state, const ExportInfo& exp);
}
#endif

View File

@ -0,0 +1,25 @@
#ifndef CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVMEMORYACCESS_HPP
#define CORE_SPIRVBACKEND_INCLUDE_SPIRVBACKEND_SPIRVMEMORYACCESS_HPP
#include "SpirvBackend/SpirvMemory/SpirvEmitterState.hpp"
#include <cstdint>
namespace ShaderRecompiler {
std::uint32_t LdsDwordCount(const SpirvEmitterState& state);
std::uint32_t StorageBufferPackedStride(const SpirvEmitterState& state, const MemoryInfo& mem);
IrBufferFormat StorageBufferFormat(const SpirvEmitterState& state, const MemoryInfo& mem);
void EmitMemoryOffsets(SpirvEmitterState& state);
MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem);
MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType);
std::uint32_t EmitMemoryElementIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t rawIndex);
std::uint32_t EmitMemoryElementInBounds(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t index);
std::uint32_t EmitMemoryElementPointer(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t index);
std::uint32_t EmitStorageBufferElementPointer(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t index, std::uint32_t pointerType);
}
#endif