mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(shader): reuse bound specialization fields for null buffer slots
Null buffer descriptors still split modules on unused specialization fields after buffer presence moved to run time. Learn the first valid bound fields per slot and use them for absent descriptors, retaining actual runtime presence and independent bound formats. Snapshot the learned fields once for result memo keys and materialization so identical null captures cannot retain a module made before learning. Cover direct, captured and prepared calls, null stores, and changed formats. Keep the process-local table and an off switch for main's module selection. AI-assisted: yes
This commit is contained in:
1 parent
1bc97b9527
commit
068f2b3ecf
9 files changed
+280
-33
No files matched your search
@@ -1401,6 +1401,10 @@ void verifyInvocationIsolation() {
|
||||
if ((descriptor[0] & 3u) == (original[0] & 3u) && (descriptor[1] & 0xffff0000u) == (original[1] & 0xffff0000u) && descriptor[3] == original[3] && (descriptor[0] != 0u || (descriptor[1] & 0xffffu) != 0u)) {
|
||||
require(first.spirv.data() == changed.spirv.data() && first.PipelineVariantId() == changed.PipelineVariantId(), "buffer address or size rebuilt the specialized module");
|
||||
}
|
||||
if (descriptor[2] == 0u) {
|
||||
require(first.spirv.data() == changed.spirv.data() && first.PipelineVariantId() == changed.PipelineVariantId(), "a null V# did not specialize with the bound slot's substitute");
|
||||
continue;
|
||||
}
|
||||
ComputeRequest fresh(false);
|
||||
fresh.userData = descriptor;
|
||||
requireSameResult(Recompile(fresh.request), changed, "reused binding plan against fresh compilation");
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_runtime_formatted_store PROPERTIES TIMEOUT 30)
|
||||
set_tests_properties(agc_driver_null_buffer_substitutes PROPERTIES TIMEOUT 30)
|
||||
foreach(mode IN ITEMS x1 x2 x3 x1_d16 x2_d16 x3_d16 x4_d16)
|
||||
string(REGEX REPLACE "_d16$" "" components "${mode}")
|
||||
set(arguments "--${components}")
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "Optimization/RequestMemoryView.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Lanes = 32;
|
||||
constexpr std::uint32_t InputBytes = Lanes * 4u;
|
||||
constexpr std::uint32_t Fill = 0xdeadbeefu;
|
||||
constexpr std::uint32_t Format32UInt = 20;
|
||||
constexpr std::uint32_t Format16UInt = 11;
|
||||
alignas(256) std::array<std::uint32_t, Lanes> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Lanes * 2u + 8u> Output{};
|
||||
|
||||
constexpr std::array<std::uint32_t, 16> LoadCode(std::uint32_t marker) {
|
||||
return {0x34020082u, 0xe0001000u, 0x80000201u, 0xe0301000u, 0x80000301u, 0xbf8c0000u,
|
||||
0xe0101000u, 0x80000201u, 0xe0701000u, 0x80000301u,
|
||||
0xe0701000u, 0x80010201u, 0xe0701080u + marker * 4u, 0x80010301u, 0xbf800000u | marker, 0xbf810000u};
|
||||
}
|
||||
|
||||
enum class Mode { Direct, Captured, Prepared };
|
||||
|
||||
alignas(256) constexpr std::array Codes{LoadCode(0u), LoadCode(1u), LoadCode(2u), LoadCode(3u), LoadCode(4u), LoadCode(5u)};
|
||||
|
||||
std::array<std::uint32_t, 4> Descriptor(const void* data, std::uint32_t records, std::uint32_t format, std::uint32_t stride = 0u) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
const auto oobSelect = stride != 0u ? 0u : 3u;
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), records, (0x01016facu & ~(0x7fu << 12u)) | (format << 12u) | (oobSelect << 28u)};
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint32_t, 4> GameNull{0x00000000u, 0x00080050u, 0x00000000u, 0x00005204u};
|
||||
constexpr std::array<std::uint32_t, 4> ZeroNull{0u, 0u, 0u, 0u};
|
||||
|
||||
std::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
struct Outcome {
|
||||
std::uint64_t pipeline = 0;
|
||||
std::vector<std::uint32_t> spirv;
|
||||
};
|
||||
|
||||
Outcome Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::uint32_t marker, const std::array<std::uint32_t, 4>& optional, const std::string& name, std::uint32_t mask, Mode mode) {
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) Input[lane] = 0x9e3779b9u * (lane + 1u) ^ 0x01234567u;
|
||||
Output.fill(Fill);
|
||||
std::vector<std::uint32_t> userData(optional.begin(), optional.end());
|
||||
const auto output = Descriptor(Output.data(), sizeof(Output), Format32UInt);
|
||||
userData.insert(userData.end(), output.begin(), output.end());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{Lanes, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = true;
|
||||
const auto result = [&] {
|
||||
if (mode == Mode::Direct) return ShaderRecompiler::Recompile(request);
|
||||
ShaderRecompiler::RequestMemoryView memoryView(request.context.memory);
|
||||
const auto runtime = memoryView.MakeRuntime(request.context.userData, request.shader.codeAddress);
|
||||
if (mode == Mode::Captured) return *ShaderRecompiler::Recompile(request, *ShaderRecompiler::CaptureResources(request, runtime));
|
||||
const auto handle = ShaderRecompiler::PrepareShader(request);
|
||||
const auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(request, handle);
|
||||
Require(invocation.has_value(), "the prepared shader refused its own request");
|
||||
return *invocation->Materialize(*invocation->Capture(runtime));
|
||||
}();
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
const bool present = optional[2] != 0u && (optional[0] != 0u || (optional[1] & 0xffffu) != 0u);
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {
|
||||
Require(Input[lane] == (0x9e3779b9u * (lane + 1u) ^ 0x01234567u), name + ": an absent store changed its backing memory");
|
||||
const auto formatted = present ? Input[lane] & mask : 0u;
|
||||
const auto raw = present ? Input[lane] : 0u;
|
||||
Require(Output[lane] == formatted, name + ": lane " + std::to_string(lane) + " buffer_load_format_x is " + Hex(Output[lane]) + ", expected " + Hex(formatted));
|
||||
Require(Output[Lanes + marker + lane] == raw, name + ": lane " + std::to_string(lane) + " buffer_load_dword is " + Hex(Output[Lanes + marker + lane]) + ", expected " + Hex(raw));
|
||||
}
|
||||
Outcome outcome;
|
||||
outcome.pipeline = result.PipelineVariantId();
|
||||
outcome.spirv = result.spirv.Copy();
|
||||
Require(outcome.pipeline != 0u && !outcome.spirv.empty(), name + ": no specialized module");
|
||||
return outcome;
|
||||
}
|
||||
|
||||
void BoundThenNull(AgcDriver::VulkanDevice& device, Mode mode) {
|
||||
const auto marker = static_cast<std::uint32_t>(mode) * 2u;
|
||||
const auto& code = Codes[marker];
|
||||
const auto bound = Run(device, code, marker, Descriptor(Input.data(), InputBytes, Format32UInt), "bound", 0xffffffffu, mode);
|
||||
const auto absent = Run(device, code, marker, GameNull, "null V#", 0xffffffffu, mode);
|
||||
Require(absent.pipeline == bound.pipeline && absent.spirv == bound.spirv, "a null V# in an optional slot made another module than the bound slot");
|
||||
const auto zero = Run(device, code, marker, ZeroNull, "zero V#", 0xffffffffu, mode);
|
||||
Require(zero.pipeline == bound.pipeline, "a zero V# made another module than the bound slot");
|
||||
auto zeroAddress = Descriptor(nullptr, InputBytes, Format32UInt);
|
||||
const auto addressless = Run(device, code, marker, zeroAddress, "zero address with records", 0xffffffffu, mode);
|
||||
Require(addressless.pipeline == bound.pipeline, "a zero-address V# did not reuse the bound module");
|
||||
auto zeroRecords = Descriptor(Input.data(), 0u, Format16UInt, InputBytes);
|
||||
const auto backedNull = Run(device, code, marker, zeroRecords, "zero records with backing memory", 0xffffffffu, mode);
|
||||
Require(backedNull.pipeline == bound.pipeline, "a zero-record V# did not reuse the bound module");
|
||||
const auto again = Run(device, code, marker, Descriptor(Input.data(), InputBytes, Format32UInt), "bound again", 0xffffffffu, mode);
|
||||
Require(again.pipeline == bound.pipeline, "the bound slot changed modules after a null V#");
|
||||
const auto reformatted = Run(device, code, marker, Descriptor(Input.data(), 1u, Format16UInt, InputBytes), "bound 16_UINT structured stride 128", 0xffffu, mode);
|
||||
Require(reformatted.pipeline != bound.pipeline && reformatted.spirv != bound.spirv, "a bound slot with another format and stride shared the substitute's module");
|
||||
const auto absentAfter = Run(device, code, marker, GameNull, "null V# after another format", 0xffffffffu, mode);
|
||||
Require(absentAfter.pipeline == bound.pipeline, "a null V# did not keep the first bound slot's substitute");
|
||||
}
|
||||
|
||||
void NullThenBound(AgcDriver::VulkanDevice& device, Mode mode) {
|
||||
const auto marker = static_cast<std::uint32_t>(mode) * 2u + 1u;
|
||||
const auto& code = Codes[marker];
|
||||
const auto first = Run(device, code, marker, GameNull, "null V# before any bound slot", 0xffffffffu, mode);
|
||||
const auto bound = Run(device, code, marker, Descriptor(Input.data(), InputBytes, Format32UInt), "bound after null", 0xffffffffu, mode);
|
||||
Require(first.pipeline != bound.pipeline, "a slot never seen bound did not specialize as absent");
|
||||
const auto repeated = Run(device, code, marker, GameNull, "same null V# after learning", 0xffffffffu, mode);
|
||||
Require(repeated.pipeline == bound.pipeline && repeated.spirv == bound.spirv, "the full-result memo retained the null module from before learning");
|
||||
const auto learned = Run(device, code, marker, ZeroNull, "zero V# after the slot was bound", 0xffffffffu, mode);
|
||||
Require(learned.pipeline == bound.pipeline && learned.spirv == bound.spirv, "a null V# did not take the substitute learned from the bound slot");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
std::printf("null buffer substitutes registrationModules=%d\n", device->Target().registrationModules);
|
||||
for (const auto mode : {Mode::Direct, Mode::Captured, Mode::Prepared}) {
|
||||
if (argc > 1 && ((std::string(argv[1]) == "--captured" && mode != Mode::Captured) || (std::string(argv[1]) == "--prepared" && mode != Mode::Prepared))) continue;
|
||||
BoundThenNull(*device, mode);
|
||||
NullThenBound(*device, mode);
|
||||
std::printf("null buffer substitutes mode=%u passed\n", static_cast<unsigned>(mode));
|
||||
}
|
||||
std::puts("null buffer substitute tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,42 @@
|
||||
#include "Recompiler.hpp"
|
||||
#include "IntermediateRepresentation/IrMetadata/CompiledShaderInfo.hpp"
|
||||
#include "Optimization/include/Optimization/BindingAllocator.hpp"
|
||||
#include "Optimization/include/Optimization/DescriptorBindingBuilder.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
class BufferSubstituteTable {
|
||||
public:
|
||||
explicit BufferSubstituteTable(std::vector<BufferSubstitute> slots) : slots(std::move(slots)) {}
|
||||
|
||||
[[nodiscard]] std::vector<BufferSubstitute> Slots() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return slots;
|
||||
}
|
||||
|
||||
void Learn(std::size_t slot, const BufferSubstitute& value) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (slot >= slots.size() || slots[slot].known) return;
|
||||
slots[slot] = value;
|
||||
slots[slot].known = true;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::vector<BufferSubstitute> slots;
|
||||
};
|
||||
|
||||
struct CompiledVariant {
|
||||
BindingLayout layout;
|
||||
CompiledShaderInfo info;
|
||||
CompiledBindingLayout bindings;
|
||||
CompiledShaderArtifact artifact;
|
||||
std::shared_ptr<BufferSubstituteTable> substitutes;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+13
-1
@@ -27,6 +27,18 @@ struct PreparedImageMetadata {
|
||||
RuntimeAbi::ResourceMetadata metadata;
|
||||
};
|
||||
|
||||
struct BufferSubstitute {
|
||||
std::uint32_t word1 = 0;
|
||||
std::uint32_t word3 = 0;
|
||||
std::uint32_t misalignment = 0;
|
||||
bool known = false;
|
||||
|
||||
bool operator==(const BufferSubstitute&) const = default;
|
||||
};
|
||||
|
||||
bool BufferDescriptorPresent(const DescriptorValue& descriptor);
|
||||
BufferSubstitute BufferSpecializationFields(const DescriptorValue& descriptor, std::span<const BufferSubstitute> substitutes, std::size_t index);
|
||||
|
||||
struct DescriptorBindingPlan {
|
||||
std::vector<PreparedDescriptorBinding> bindings;
|
||||
std::vector<PreparedImageMetadata> imageMetadata;
|
||||
@@ -37,7 +49,7 @@ struct DescriptorBindingPlan {
|
||||
|
||||
class DescriptorBindingBuilder {
|
||||
public:
|
||||
DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings = {}, bool registrationModules = false, bool fragmentDescriptorSet = false) const;
|
||||
DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings = {}, bool registrationModules = false, bool fragmentDescriptorSet = false, std::span<const BufferSubstitute> substitutes = {}) const;
|
||||
DescriptorBindingPlan Select(const DescriptorBindingPlan& plan, std::span<const std::uint32_t> bindings) const;
|
||||
void Populate(BindingAllocationResult& allocation, const CompiledBindingLayout& compiled, const DescriptorBindingPlan& plan, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads, const ResolvedImageTables* tables = nullptr) const;
|
||||
void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads, std::span<const std::uint8_t> exportMappings = {}) const;
|
||||
|
||||
@@ -305,7 +305,17 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
|
||||
allocation.specialization = std::move(plan.specialization);
|
||||
}
|
||||
|
||||
DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings, bool registrationModules, bool fragmentDescriptorSet) const {
|
||||
bool BufferDescriptorPresent(const DescriptorValue& descriptor) {
|
||||
return descriptor.dwords[2] != 0u && (descriptor.dwords[0] != 0u || (descriptor.dwords[1] & 0xffffu) != 0u);
|
||||
}
|
||||
|
||||
BufferSubstitute BufferSpecializationFields(const DescriptorValue& descriptor, std::span<const BufferSubstitute> substitutes, std::size_t index) {
|
||||
const bool present = BufferDescriptorPresent(descriptor);
|
||||
if (!present && !substitutes.empty()) return index < substitutes.size() && substitutes[index].known ? substitutes[index] : BufferSubstitute{};
|
||||
return {descriptor.dwords[1] & 0xffff0000u, descriptor.dwords[3], present ? descriptor.dwords[0] & 3u : 0u, present};
|
||||
}
|
||||
|
||||
DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings, bool registrationModules, bool fragmentDescriptorSet, std::span<const BufferSubstitute> substitutes) const {
|
||||
if (stage == IrShaderStage::Pixel && exportMappings.size() != 8u) fail("fragment runtime export mappings are missing");
|
||||
if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity");
|
||||
DescriptorBindingPlan plan;
|
||||
@@ -331,25 +341,25 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
|
||||
const auto& descriptor = snapshot.buffers.at(index);
|
||||
if (descriptor.dwordCount != 4u) fail("buffer specialization requires four descriptor words");
|
||||
const auto& buffer = info.buffers[index];
|
||||
const auto format = (descriptor.dwords[3] >> 12u) & 0x7fu;
|
||||
const bool present = descriptor.dwords[2] != 0u && (descriptor.dwords[0] != 0u || (descriptor.dwords[1] & 0xffffu) != 0u);
|
||||
if (buffer.descriptorFormatted && (format > static_cast<std::uint32_t>(IrBufferFormat::Format32_32_32_32Float) || (present && format == 0u))) fail("buffer specialization has an unsupported format");
|
||||
const auto misalignment = present ? descriptor.dwords[0] & 3u : 0u;
|
||||
const auto fields = BufferSpecializationFields(descriptor, substitutes, index);
|
||||
const auto format = (fields.word3 >> 12u) & 0x7fu;
|
||||
if (buffer.descriptorFormatted && (format > static_cast<std::uint32_t>(IrBufferFormat::Format32_32_32_32Float) || (fields.known && format == 0u))) fail("buffer specialization has an unsupported format");
|
||||
const auto misalignment = fields.misalignment;
|
||||
auto alignment = static_cast<std::uint32_t>(buffer.typedAlignment);
|
||||
if (buffer.descriptorFormatted && format != 0u) alignment = std::max(alignment, std::min(GetFormatInfo(static_cast<IrBufferFormat>(format)).byteSize, 4u));
|
||||
if (alignment == 0u || misalignment % alignment != 0u) fail("is a formatted buffer access through a V# whose base is not aligned to its element");
|
||||
if (registrationModules && buffer.descriptorFormatted) plan.specialization.push_back({PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords + 1u, descriptor.dwords[3]});
|
||||
if (registrationModules && buffer.descriptorFormatted) plan.specialization.push_back({PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords + 1u, fields.word3});
|
||||
if (registrationModules) plan.specialization.push_back({PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords + 3u, misalignment});
|
||||
if (!registrationModules) {
|
||||
const auto first = PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords;
|
||||
plan.specialization.push_back({first, descriptor.dwords[1] & 0xffff0000u});
|
||||
plan.specialization.push_back({first + 1u, descriptor.dwords[3]});
|
||||
plan.specialization.push_back({first, fields.word1});
|
||||
plan.specialization.push_back({first + 1u, fields.word3});
|
||||
plan.specialization.push_back({first + 3u, misalignment});
|
||||
}
|
||||
if (buffer.formattedReadMask != 0u) {
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) {
|
||||
if ((buffer.formattedReadMask & (1u << component)) == 0u) continue;
|
||||
const auto selector = (descriptor.dwords[3] >> (component * 3u)) & 7u;
|
||||
const auto selector = (fields.word3 >> (component * 3u)) & 7u;
|
||||
if (selector == 2u || selector == 3u) fail("buffer specialization has a reserved component selector");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <shared_mutex>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include "ControlFlow/include/ControlFlow/GraphBuilder.hpp"
|
||||
#include "ControlFlow/include/ControlFlow/Structurizer.hpp"
|
||||
@@ -478,6 +479,7 @@ bool diskPlans() {
|
||||
}
|
||||
|
||||
bool sameLayout(const BindingLayout& left, const BindingLayout& right);
|
||||
void attachSubstitutes(CompiledVariant& variant);
|
||||
|
||||
std::shared_ptr<SourceEntry> getSource(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr, bool reclaim = true, const SourceHandle* preparedHandle = nullptr) {
|
||||
static PrimedSharedMutex mutex;
|
||||
@@ -983,7 +985,12 @@ SpecializedSpirv registrationModule(std::uint64_t variantId, std::span<const std
|
||||
return {entry->module, std::move(built)};
|
||||
}
|
||||
|
||||
std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exports, bool fragmentDescriptorSet) {
|
||||
bool bufferSubstitutesEnabled() {
|
||||
static const bool enabled = std::getenv("APS5_NO_BUFFER_SUBSTITUTES") == nullptr;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exports, bool fragmentDescriptorSet, std::span<const BufferSubstitute> substitutes) {
|
||||
struct BindingPlanKeyStorage {};
|
||||
auto& key = HostThreadLocal<std::vector<std::uint64_t>, BindingPlanKeyStorage>();
|
||||
key.clear();
|
||||
@@ -991,10 +998,10 @@ std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant&
|
||||
for (std::size_t index = 0; index < variant.info.info.buffers.size(); ++index) {
|
||||
const auto& descriptor = snapshot.buffers.at(index);
|
||||
key.push_back(descriptor.dwordCount);
|
||||
key.push_back(descriptor.dwords[1] & 0xffff0000u);
|
||||
key.push_back(descriptor.dwords[3]);
|
||||
const bool present = descriptor.dwords[2] != 0u && (descriptor.dwords[0] != 0u || (descriptor.dwords[1] & 0xffffu) != 0u);
|
||||
key.push_back(present ? 4u | (descriptor.dwords[0] & 3u) : 0u);
|
||||
const auto fields = BufferSpecializationFields(descriptor, substitutes, index);
|
||||
key.push_back(fields.word1);
|
||||
key.push_back(fields.word3);
|
||||
key.push_back(fields.known ? 4u | fields.misalignment : 0u);
|
||||
}
|
||||
for (std::size_t index = 0; index < variant.info.info.images.size(); ++index) {
|
||||
const auto& descriptor = snapshot.images.at(index);
|
||||
@@ -1024,12 +1031,16 @@ std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant&
|
||||
}
|
||||
std::call_once(plan->ready, [&] {
|
||||
try {
|
||||
plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports, variant.artifact.registrationModule, fragmentDescriptorSet);
|
||||
plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports, variant.artifact.registrationModule, fragmentDescriptorSet, substitutes);
|
||||
} catch (...) {
|
||||
plan->failure = std::current_exception();
|
||||
}
|
||||
});
|
||||
if (plan->failure) std::rethrow_exception(plan->failure);
|
||||
for (std::size_t index = 0; index < substitutes.size(); ++index) {
|
||||
const auto& descriptor = snapshot.buffers.at(index);
|
||||
if (!substitutes[index].known && BufferDescriptorPresent(descriptor)) variant.substitutes->Learn(index, BufferSpecializationFields(descriptor, {}, index));
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
@@ -1106,7 +1117,9 @@ void recordMemoPatch(MemoPatch& patch, const DescriptorBindingPlan& plan, const
|
||||
patch.valid = layout.userDataRegisters.size() <= layout.DispatchThreadLimitDword() && (patch.userWords.empty() || patch.pushData || patch.shaderData != MemoPatch::NoBinding);
|
||||
}
|
||||
|
||||
RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& captured, bool registrationDefaults = false, MemoPatch* patch = nullptr) {
|
||||
RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& captured, bool registrationDefaults = false, MemoPatch* patch = nullptr, const std::vector<BufferSubstitute>* substituteSnapshot = nullptr) {
|
||||
const auto localSubstitutes = substituteSnapshot == nullptr && bufferSubstitutesEnabled() && variant.substitutes != nullptr ? variant.substitutes->Slots() : std::vector<BufferSubstitute>{};
|
||||
const auto substitutes = substituteSnapshot != nullptr ? std::span<const BufferSubstitute>(*substituteSnapshot) : std::span<const BufferSubstitute>(localSubstitutes);
|
||||
ResourceSnapshot merged;
|
||||
const auto& snapshot = mergeTargetImage(variant, request, captured, registrationDefaults, merged);
|
||||
RecompileResult result;
|
||||
@@ -1147,7 +1160,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
||||
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 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, substitutes);
|
||||
const DescriptorBindingPlan* bindingPlan = &plan->bindings;
|
||||
std::shared_ptr<PreparedModuleEntry> entry;
|
||||
struct LocalModuleKeyStorage {};
|
||||
@@ -1315,6 +1328,18 @@ bool sameLayout(const BindingLayout& left, const BindingLayout& right) {
|
||||
return left.descriptorSet == right.descriptorSet && left.firstBinding == right.firstBinding && left.pushConstantOffsetBytes == right.pushConstantOffsetBytes && left.pushConstantSizeBytes == right.pushConstantSizeBytes;
|
||||
}
|
||||
|
||||
void attachSubstitutes(CompiledVariant& variant) {
|
||||
const auto slots = variant.info.info.buffers.size();
|
||||
if (slots == 0u || variant.artifact.spirv.empty()) return;
|
||||
const auto hash = variant.artifact.spirv.ContentHash();
|
||||
static std::mutex mutex;
|
||||
static std::map<std::tuple<std::uint64_t, std::uint64_t, std::size_t>, std::shared_ptr<BufferSubstituteTable>> tables;
|
||||
std::lock_guard lock(mutex);
|
||||
auto& table = tables[{hash.low, hash.high, slots}];
|
||||
if (table == nullptr) table = std::make_shared<BufferSubstituteTable>(std::vector<BufferSubstitute>(slots));
|
||||
variant.substitutes = table;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SharedSpirv InternSpirv(std::vector<std::uint32_t> words) {
|
||||
@@ -1395,6 +1420,7 @@ std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source,
|
||||
if (ShaderDiskCache::Load(diskKey, loaded)) {
|
||||
loaded.layout = request.layout;
|
||||
loaded.artifact.variantId = nextVariantId();
|
||||
attachSubstitutes(loaded);
|
||||
variant = std::make_shared<const CompiledVariant>(std::move(loaded));
|
||||
}
|
||||
}
|
||||
@@ -1402,13 +1428,14 @@ std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source,
|
||||
static const bool layoutPair = std::getenv("APS5_NO_PERF_LAYOUT_PAIR") == nullptr;
|
||||
std::exception_ptr emissionFailure;
|
||||
try {
|
||||
if (layoutPair && preparation != nullptr) {
|
||||
variant = std::make_shared<const CompiledVariant>(PreparedInterface::Compile(source, request, *preparation, &emissionFailure));
|
||||
} else {
|
||||
auto compiled = [&] {
|
||||
if (layoutPair && preparation != nullptr) return PreparedInterface::Compile(source, request, *preparation, &emissionFailure);
|
||||
auto program = source.program != nullptr ? std::move(*source.program) : PrepareResourceProgram(request, preparation);
|
||||
source.program.reset();
|
||||
variant = std::make_shared<const CompiledVariant>(compileVariant(request, std::move(program), &emissionFailure));
|
||||
}
|
||||
return compileVariant(request, std::move(program), &emissionFailure);
|
||||
}();
|
||||
attachSubstitutes(compiled);
|
||||
variant = std::make_shared<const CompiledVariant>(std::move(compiled));
|
||||
} catch (...) {
|
||||
if (emissionFailure != nullptr && FailureMemo()) source.emissionFailures.push_back({request.shader.codeAddress, request.layout, emissionFailure});
|
||||
throw;
|
||||
@@ -1686,7 +1713,7 @@ void reportResultMemo() {
|
||||
// Everything materializeResult reads besides the variant: the snapshot (the descriptor words, the
|
||||
// flattened SRT, the user data, the uniform fill) and, for the vertex family, the V# table the
|
||||
// attributes are resolved from.
|
||||
std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapshot& snapshot, bool masked) {
|
||||
std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapshot& snapshot, bool masked, std::span<const BufferSubstitute> substitutes = {}) {
|
||||
std::uint64_t hash = 0xcbf29ce484222325ull;
|
||||
const auto mix = [&](std::uint64_t value) {
|
||||
hash ^= value;
|
||||
@@ -1706,16 +1733,24 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh
|
||||
};
|
||||
if (masked) {
|
||||
mix(snapshot.buffers.size());
|
||||
for (const auto& value : snapshot.buffers) {
|
||||
for (std::size_t index = 0; index < snapshot.buffers.size(); ++index) {
|
||||
const auto& value = snapshot.buffers[index];
|
||||
mix(value.dwordCount);
|
||||
mix(value.availableDwordMask);
|
||||
const bool present = value.dwords[2] != 0u && (value.dwords[0] != 0u || (value.dwords[1] & 0xffffu) != 0u);
|
||||
mix(present ? 4u | (value.dwords[0] & 3u) : 0u);
|
||||
mix(value.dwords[1] & 0xffff0000u);
|
||||
for (std::uint32_t i = 3; i < value.dwordCount && i < value.dwords.size(); ++i) mix(value.dwords[i]);
|
||||
const auto fields = BufferSpecializationFields(value, substitutes, index);
|
||||
mix(fields.known ? 4u | fields.misalignment : 0u);
|
||||
mix(fields.word1);
|
||||
if (value.dwordCount > 3u) mix(fields.word3);
|
||||
for (std::uint32_t i = 4; i < value.dwordCount && i < value.dwords.size(); ++i) mix(value.dwords[i]);
|
||||
}
|
||||
} else {
|
||||
mixDescriptors(snapshot.buffers);
|
||||
for (std::size_t index = 0; index < substitutes.size() && index < snapshot.buffers.size(); ++index) {
|
||||
const auto fields = BufferSpecializationFields(snapshot.buffers[index], substitutes, index);
|
||||
mix(fields.word1);
|
||||
mix(fields.word3);
|
||||
mix(fields.known ? 4u | fields.misalignment : 0u);
|
||||
}
|
||||
}
|
||||
mixDescriptors(snapshot.images);
|
||||
mixDescriptors(snapshot.samplers);
|
||||
@@ -1797,7 +1832,8 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh
|
||||
std::shared_ptr<const RecompileResult> materializePreparedMemoized(SourceEntry& source, const std::shared_ptr<const CompiledVariant>& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot, bool cacheHit, bool* memoHit) {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
const auto variantId = variant->artifact.variantId;
|
||||
const auto hash = snapshotHash(request, snapshot, false);
|
||||
const auto substitutes = bufferSubstitutesEnabled() && variant->substitutes != nullptr ? variant->substitutes->Slots() : std::vector<BufferSubstitute>{};
|
||||
const auto hash = snapshotHash(request, snapshot, false, substitutes);
|
||||
const auto index = (variantId * 0x9e3779b97f4a7c15ull) ^ hash;
|
||||
auto& counters = resultMemoCounters();
|
||||
{
|
||||
@@ -1815,7 +1851,7 @@ std::shared_ptr<const RecompileResult> materializePreparedMemoized(SourceEntry&
|
||||
}
|
||||
}
|
||||
const bool masking = MaskedResultMemo();
|
||||
const auto maskedHash = masking ? snapshotHash(request, snapshot, true) : 0u;
|
||||
const auto maskedHash = masking ? snapshotHash(request, snapshot, true, substitutes) : 0u;
|
||||
const auto maskedIndex = (variantId * 0x9e3779b97f4a7c15ull) ^ maskedHash;
|
||||
std::shared_ptr<const RecompileResult> donor;
|
||||
std::shared_ptr<const MemoPatch> donorPatch;
|
||||
@@ -1838,12 +1874,12 @@ std::shared_ptr<const RecompileResult> materializePreparedMemoized(SourceEntry&
|
||||
if (donor != nullptr) {
|
||||
result = std::make_shared<RecompileResult>(*donor);
|
||||
applyMemoPatch(*result, *donorPatch, snapshot);
|
||||
if (VerifyMaskedResultMemo()) verifyPatchedResult(*result, materializeResult(*variant, request, snapshot), variantId);
|
||||
if (VerifyMaskedResultMemo()) verifyPatchedResult(*result, materializeResult(*variant, request, snapshot, false, nullptr, &substitutes), variantId);
|
||||
if (profile) counters.patchNanoseconds.fetch_add(static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - started).count()), std::memory_order_relaxed);
|
||||
counters.patched.fetch_add(1, std::memory_order_relaxed);
|
||||
} else {
|
||||
if (masking) patch = std::make_shared<MemoPatch>();
|
||||
result = std::make_shared<RecompileResult>(materializeResult(*variant, request, snapshot, false, patch.get()));
|
||||
result = std::make_shared<RecompileResult>(materializeResult(*variant, request, snapshot, false, patch.get(), &substitutes));
|
||||
if (profile) counters.populateNanoseconds.fetch_add(static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - started).count()), std::memory_order_relaxed);
|
||||
counters.misses.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -1389,6 +1389,7 @@ constexpr std::string_view NeutralSwitches[] = {
|
||||
"APS5_NO_PERF_DOM_INTERSECTION",
|
||||
"APS5_NO_PERF_ITERATION_SELECTION",
|
||||
"APS5_NO_PERF_LAYOUT_PAIR",
|
||||
"APS5_NO_BUFFER_SUBSTITUTES",
|
||||
"APS5_NO_RESULT_MEMO",
|
||||
"APS5_NO_TABLE_RESOLUTION_REUSE",
|
||||
"APS5_NO_TABLE_SNAPSHOT_REUSE",
|
||||
@@ -1507,6 +1508,7 @@ public:
|
||||
if (DecodeEntry(bytes, key, *persisted, std::move(mapped)) != LoadStatus::Loaded) return nullptr;
|
||||
persisted->layout = variant.layout;
|
||||
persisted->artifact.variantId = variant.artifact.variantId;
|
||||
persisted->substitutes = variant.substitutes;
|
||||
return persisted;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti
|
||||
| `APS5_IMAGE_TABLE_STRICT` | string | `core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp` | | sets image-table materialization to its strict form; 0 leaves it off |
|
||||
| `APS5_NO_ATOMIC_BARRIER` | presence | `core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp` | | drops the device-scope acquire/release memory barrier emitted after atomics |
|
||||
| `APS5_NO_ATOMIC_ZERO_SKIP` | presence | `core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp` | | restores plain buffer atomics instead of skipping identity-operand atomics |
|
||||
| `APS5_NO_BUFFER_SUBSTITUTES` | presence | `core/shader/recompiler/Recompiler.cpp` | | specializes a null buffer V# with its own words instead of the fields its slot had the first time a dispatch of the same program bound it |
|
||||
| `APS5_NO_CODE_HASH_KEY` | presence | `core/shader/recompiler/CacheKey.hpp` | | appends every code word to the cache key instead of hashing the code |
|
||||
| `APS5_NO_FAILURE_MEMO` | presence | `core/shader/recompiler/Recompiler.cpp` | | rebuilds a program whose compilation failed instead of reusing the memoized failure |
|
||||
| `APS5_NO_IMAGE_TABLES` | presence | `core/shader/recompiler/Optimization/src/ResourceTracker.cpp` | | disables image tables, leaving table-loaded descriptors unplanned |
|
||||
|
||||
Reference in new issue
Block a user