mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
UFC 5 in-game (#883)
* shader: release descriptor dependencies * shader: simplify shared-exit control flow * shader: fix scalar memory loads * shader: fix LDS counter bounds * renderer: skip inactive pixel shaders * shader: respect FP32 mode in comparisons * renderer: fix sampler border colors * shader: resolve descriptors from control flow * shader: resolve descriptors before resource planning * shader: handle scalar guards for image bounds * shader: handle reduced guards for image loops * shader: fix wave guards and material indices * shader: preserve scalar buffer offset alignment and carry * shader: prove masked loop bounds across iteration transitions * shader: preserve source and IR before resource tracking failures * shader: track material keys selected from active lanes * shader: remove obsolete post-dominator analysis * shader: follow bounds guards through control-flow joins
This commit is contained in:
+9
-9
@@ -14,10 +14,10 @@ SamplerCache::~SamplerCache() {
|
||||
}
|
||||
}
|
||||
|
||||
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r, bool integer_border) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const SamplerKey key {r.fields[0], r.fields[1], r.fields[2], r.fields[3]};
|
||||
const SamplerKey key {r.fields[0], r.fields[1], r.fields[2], r.fields[3], integer_border};
|
||||
if (auto iter = m_samplers.find(key); iter != m_samplers.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
@@ -93,23 +93,23 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
return vk::SamplerAddressMode::eClampToBorder;
|
||||
};
|
||||
|
||||
vk::BorderColor border = vk::BorderColor::eIntTransparentBlack;
|
||||
vk::BorderColor border = integer_border ? vk::BorderColor::eIntTransparentBlack
|
||||
: vk::BorderColor::eFloatTransparentBlack;
|
||||
switch (static_cast<Prospero::SamplerBorderColor>(r.BorderColorType())) {
|
||||
case Prospero::SamplerBorderColor::kTransBlack:
|
||||
border = vk::BorderColor::eIntTransparentBlack;
|
||||
break;
|
||||
case Prospero::SamplerBorderColor::kTransBlack: break;
|
||||
case Prospero::SamplerBorderColor::kOpaqueBlack:
|
||||
border = vk::BorderColor::eIntOpaqueBlack;
|
||||
border = integer_border ? vk::BorderColor::eIntOpaqueBlack
|
||||
: vk::BorderColor::eFloatOpaqueBlack;
|
||||
break;
|
||||
case Prospero::SamplerBorderColor::kOpaqueWhite:
|
||||
border = vk::BorderColor::eIntOpaqueWhite;
|
||||
border = integer_border ? vk::BorderColor::eIntOpaqueWhite
|
||||
: vk::BorderColor::eFloatOpaqueWhite;
|
||||
break;
|
||||
case Prospero::SamplerBorderColor::kFromTable:
|
||||
LOGF(
|
||||
"temporary: approximating table border color as transparent black, index = %" PRIu16
|
||||
"\n",
|
||||
r.BorderColorPtr());
|
||||
border = vk::BorderColor::eIntTransparentBlack;
|
||||
break;
|
||||
default: EXIT("unknown border color: %d", static_cast<int>(r.BorderColorType()));
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,10 +24,10 @@ public:
|
||||
~SamplerCache();
|
||||
KYTY_CLASS_NO_COPY(SamplerCache);
|
||||
|
||||
vk::Sampler GetSampler(const ShaderSamplerResource& r);
|
||||
vk::Sampler GetSampler(const ShaderSamplerResource& r, bool integer_border);
|
||||
|
||||
private:
|
||||
using SamplerKey = std::array<uint32_t, 4>;
|
||||
using SamplerKey = std::array<uint32_t, 5>;
|
||||
|
||||
struct SamplerKeyHash {
|
||||
std::size_t operator()(const SamplerKey& key) const {
|
||||
|
||||
@@ -738,14 +738,15 @@ static vk::Sampler NativeSampler(RenderContext& context,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
uint32_t index,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderSamplerResource>(value);
|
||||
if (!program.info.samplers[index].depth_compare) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderSamplerResource>(value);
|
||||
const auto& sampler = program.info.samplers[index];
|
||||
if (!sampler.depth_compare) {
|
||||
descriptor.fields[0] &= ~(0x7u << 12u);
|
||||
}
|
||||
if (program.info.samplers[index].force_point_filtering) {
|
||||
if (sampler.force_point_filtering) {
|
||||
descriptor.SetPointFiltering();
|
||||
}
|
||||
return context.GetSamplerCache().GetSampler(descriptor);
|
||||
return context.GetSamplerCache().GetSampler(descriptor, sampler.integer_border);
|
||||
}
|
||||
|
||||
static vk::DescriptorBufferInfo NativeUpload(RenderContext& context,
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <span>
|
||||
#include <spirv-tools/libspirv.hpp>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <xxhash.h>
|
||||
@@ -116,33 +115,22 @@ void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
}
|
||||
|
||||
void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
|
||||
std::span<const uint32_t> code, const std::string& decoded_dump) {
|
||||
std::span<const uint32_t> code) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(code.empty());
|
||||
static std::atomic_int id = 0;
|
||||
const auto base = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(base.parent_path());
|
||||
for (const auto& [suffix, data, size]: {
|
||||
std::tuple {".bin", static_cast<const void*>(code.data()), code.size_bytes()},
|
||||
std::tuple {".rdna2", static_cast<const void*>(decoded_dump.data()),
|
||||
decoded_dump.size()},
|
||||
}) {
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
auto path = base;
|
||||
path += suffix;
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
} else {
|
||||
file.Write(data, size);
|
||||
}
|
||||
const auto path = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}.bin", id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
return;
|
||||
}
|
||||
file.Write(code.data(), code.size_bytes());
|
||||
}
|
||||
|
||||
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
@@ -225,25 +213,13 @@ struct PipelineCache::ProgramCache {
|
||||
|
||||
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
|
||||
|
||||
Permutation CompilePermutation(const ShaderParams& params,
|
||||
Permutation CompilePermutation(const char* stage_name,
|
||||
const ShaderRecompiler::CompileOptions& options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
const char* stage_name = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex: stage_name = "vs"; break;
|
||||
case ShaderType::Mesh: stage_name = "ms"; break;
|
||||
case ShaderType::Local: stage_name = "ls"; break;
|
||||
case ShaderType::TessellationControl: stage_name = "hs"; break;
|
||||
case ShaderType::TessellationEvaluation: stage_name = "ds"; break;
|
||||
case ShaderType::Pixel: stage_name = "ps"; break;
|
||||
case ShaderType::Compute: stage_name = "cs"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
}
|
||||
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
|
||||
specialization, push_data_start_dword);
|
||||
DumpShaderOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
|
||||
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
|
||||
@@ -321,14 +297,15 @@ struct PipelineCache::ProgramCache {
|
||||
stage_input.compute = &input_info;
|
||||
}
|
||||
const char* label = nullptr;
|
||||
const char* stage_name = nullptr;
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
|
||||
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
|
||||
case ShaderType::Local: label = "ShaderRecompiler LS"; break;
|
||||
case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; break;
|
||||
case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; break;
|
||||
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
|
||||
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
|
||||
case ShaderType::Vertex: label = "ShaderRecompiler VS"; stage_name = "vs"; break;
|
||||
case ShaderType::Mesh: label = "ShaderRecompiler MS"; stage_name = "ms"; break;
|
||||
case ShaderType::Local: label = "ShaderRecompiler LS"; stage_name = "ls"; break;
|
||||
case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; stage_name = "hs"; break;
|
||||
case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; stage_name = "ds"; break;
|
||||
case ShaderType::Pixel: label = "ShaderRecompiler PS"; stage_name = "ps"; break;
|
||||
case ShaderType::Compute: label = "ShaderRecompiler CS"; stage_name = "cs"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
}
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
@@ -351,6 +328,7 @@ struct PipelineCache::ProgramCache {
|
||||
} else {
|
||||
options.wave_size = input_info.wave_size;
|
||||
}
|
||||
DumpShaderOriginal(stage_name, options.shader_hash, params.code);
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
if (translated.skip_dispatch) {
|
||||
entry = programs.try_emplace(lookup_key, ShaderRecompiler::IR::ResourcePlan {}).first;
|
||||
@@ -365,7 +343,7 @@ struct PipelineCache::ProgramCache {
|
||||
entry->second.specialization));
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation(
|
||||
params, options, std::move(translated), entry->second.specialization, push_data_cursor));
|
||||
stage_name, options, std::move(translated), entry->second.specialization, push_data_cursor));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = &entry->second.resources};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
|
||||
@@ -575,12 +575,17 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool DrawHasActivePixelShader(const CommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
|
||||
return buffer.GetShaders().GetPs().ps_regs.data_addr != 0 &&
|
||||
(has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs));
|
||||
static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = 0;
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if (sh_regs.target_output_mode[slot] != 0 &&
|
||||
render_target_mask_slot(write_mask, slot) != 0) {
|
||||
output_mask |= 1u << slot;
|
||||
}
|
||||
}
|
||||
return output_mask;
|
||||
}
|
||||
|
||||
enum class CbColorMode : uint8_t {
|
||||
@@ -847,7 +852,7 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
DrawRenderState& state) {
|
||||
uint32_t color_output_mask, DrawRenderState& state) {
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
@@ -861,7 +866,7 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
target_export_mapping {};
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
const auto& rt = ctx.GetRenderTarget(slot);
|
||||
if (rt.base.addr != 0 && render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0) {
|
||||
if ((color_output_mask & (1u << slot)) != 0 && rt.base.addr != 0) {
|
||||
target_export_mapping[slot] =
|
||||
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order)
|
||||
@@ -880,8 +885,12 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state) {
|
||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||
RefreshShaders(buffer, draw, state);
|
||||
const auto& shader_regs = buffer.GetRegisters().GetShaderRegisters();
|
||||
const auto color_output_mask = DrawColorOutputMask(buffer.GetRegisters());
|
||||
state.ps_active = buffer.GetShaders().GetPs().ps_regs.data_addr != 0 &&
|
||||
(color_output_mask != 0 ||
|
||||
PixelShaderHasDepthOrCoverageSideEffects(shader_regs));
|
||||
RefreshShaders(buffer, draw, color_output_mask, state);
|
||||
uint32_t mrt_mask = 0;
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
@@ -890,6 +899,7 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
||||
}
|
||||
}
|
||||
}
|
||||
mrt_mask &= color_output_mask;
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -548,30 +547,40 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
auto cfg = CFG::BuildGraph(decoded);
|
||||
auto native_cfg = CFG::BuildGraph(decoded);
|
||||
CFG::Graph structured_cfg;
|
||||
auto* selected_cfg = &native_cfg;
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(cfg.back_edges.size()), phase_ms());
|
||||
if (cfg.irreducible) {
|
||||
LogDispatcherFallback(options, cfg, "build");
|
||||
static_cast<uint64_t>(native_cfg.blocks.size()),
|
||||
static_cast<uint64_t>(native_cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(native_cfg.back_edges.size()), phase_ms());
|
||||
if (native_cfg.irreducible) {
|
||||
LogDispatcherFallback(options, native_cfg, "build");
|
||||
} else {
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::Structurize(cfg)) {
|
||||
LogDispatcherFallback(options, cfg, "structurize");
|
||||
structured_cfg = CFG::Structurize(native_cfg);
|
||||
if (structured_cfg.unsupported) {
|
||||
native_cfg.unsupported = true;
|
||||
native_cfg.failure_kind = structured_cfg.failure_kind;
|
||||
native_cfg.failure_block = structured_cfg.failure_block;
|
||||
native_cfg.unsupported_reason = structured_cfg.unsupported_reason;
|
||||
LogDispatcherFallback(options, native_cfg, "structurize");
|
||||
} else {
|
||||
selected_cfg = &structured_cfg;
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
static_cast<uint64_t>(cfg.blocks.size()));
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()));
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()),
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()),
|
||||
static_cast<uint64_t>(selected_cfg->natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
const auto& cfg = *selected_cfg;
|
||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
||||
options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) {
|
||||
@@ -614,15 +623,21 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
}
|
||||
LowerTessellationMemory(ir, options);
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
IR::TrackResources(ir);
|
||||
std::string cfg_dump;
|
||||
if (options.dump_ir) {
|
||||
cfg_dump = CFG::GraphToString(cfg);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR before resource tracking:\n%s", GetDumpLabel(options),
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
}
|
||||
IR::TrackResources(ir, decoded, native_cfg);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
TranslateResult result;
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.cfg_dump = CFG::GraphToString(cfg);
|
||||
result.cfg_dump = std::move(cfg_dump);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -634,6 +649,35 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
const auto emit_begin = std::chrono::steady_clock::now();
|
||||
auto& ir = translated.program;
|
||||
IR::ApplyResourceSpecialization(ir, specialization);
|
||||
// The resource plan owns host descriptor evaluation now. Keep only dependencies consumed
|
||||
// by GPU memory operations; bound descriptor dwords must not retain shader instructions.
|
||||
for (auto& inst: ir.value_storage) {
|
||||
inst.Invalidate();
|
||||
}
|
||||
for (auto* block: ir.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
uint32_t first = 0;
|
||||
if (op == IR::ValueOpcode::GetBufferResource) {
|
||||
if (std::ranges::any_of(inst.Uses(), [&](const IR::Use& use) {
|
||||
return ir.memory_info[use.user->Flags<IR::MemoryFlags>().index].kind ==
|
||||
IR::ResourceKind::IndirectBuffer;
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
} else if (op == IR::ValueOpcode::GetImageResource) {
|
||||
const auto resource = inst.Flags<uint32_t>();
|
||||
first = resource < ir.info.images.size() &&
|
||||
ir.info.images[resource].indirect_root == resource ? 1u : 0u;
|
||||
} else if (op != IR::ValueOpcode::GetSamplerResource) {
|
||||
continue;
|
||||
}
|
||||
for (size_t index = first; index < inst.NumArgs(); index++) {
|
||||
inst.SetArg(index, IR::Value(0u));
|
||||
}
|
||||
}
|
||||
}
|
||||
ir.value_storage.clear();
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
|
||||
|
||||
@@ -62,12 +62,7 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
Expect(Kind::BdaPagetable);
|
||||
Expect(Kind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
if (IR::UsesFlattenedSrt(program)) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
|
||||
|
||||
@@ -203,9 +203,7 @@ uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
|
||||
const auto bits = state.builder.AllocateId();
|
||||
const auto abs_bits = state.builder.AllocateId();
|
||||
const auto sign_bits = state.builder.AllocateId();
|
||||
const auto non_zero = state.builder.AllocateId();
|
||||
const auto subnormal = state.builder.AllocateId();
|
||||
const auto flush = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
@@ -213,12 +211,9 @@ uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
|
||||
ConstantU32(state, 0x7fffffffu));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), sign_bits, bits,
|
||||
ConstantU32(state, 0x80000000u));
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), non_zero, abs_bits,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), subnormal, abs_bits,
|
||||
ConstantU32(state, 0x00800000u));
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), flush, non_zero, subnormal);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, flush, sign_bits, bits);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, subnormal, sign_bits, bits);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), ret, selected);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -636,6 +636,29 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Arg(inst, 1));
|
||||
}
|
||||
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.other_half == nullptr) return ctx.Arg(inst, 0);
|
||||
// A native scalar branch makes one decision for both emulated wave halves.
|
||||
if (ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
const auto kind = inst.Flags<CFG::BranchCondition>();
|
||||
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto combined = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr,
|
||||
TypeU32(ctx.state), combined, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBool(ctx.state), result, combined,
|
||||
ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
return ctx.Ballot(predicate);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
#include <bit>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
// Operand signatures distinguish SPIR-V IDs (uint32_t), raw literals/values
|
||||
// (IR::Value), and instructions whose metadata or operand loads must stay lazy.
|
||||
@@ -112,18 +114,34 @@ EMIT_NATIVE(LogicalOr, OpLogicalOr, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalAnd, OpLogicalAnd, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalXor, OpLogicalNotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalNot, OpLogicalNot, U1, uint32_t)
|
||||
EMIT_NATIVE(FPOrdEqual32, OpFOrdEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordEqual32, OpFUnordEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdNotEqual32, OpFOrdNotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordNotEqual32, OpFUnordNotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdLessThan32, OpFOrdLessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordLessThan32, OpFUnordLessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdGreaterThan32, OpFOrdGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordGreaterThan32, OpFUnordGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdLessThanEqual32, OpFOrdLessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordLessThanEqual32, OpFUnordLessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdGreaterThanEqual32, OpFOrdGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordGreaterThanEqual32, OpFUnordGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
template <spv::Op opcode>
|
||||
uint32_t EmitFloatCompare32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
// SPIR-V's DenormFlushToZero mode does not require comparison operands to flush.
|
||||
const bool flush = inst.Flags<IR::FPCompareFlags>().flush_input_denorms;
|
||||
const auto operand = [&](size_t index) {
|
||||
const auto value = inst.Arg(index);
|
||||
if (flush && value.IsImmediate()) {
|
||||
const auto bits = std::bit_cast<uint32_t>(value.F32Value());
|
||||
return ConstantF32(ctx.state, (bits & 0x7fffffffu) < 0x00800000u
|
||||
? bits & 0x80000000u : bits);
|
||||
}
|
||||
const auto id = ctx.Arg(inst, index);
|
||||
return flush ? EmitFlushF32DenormToSignedZero(ctx.state, id) : id;
|
||||
};
|
||||
return EmitNative<opcode, IR::Type::U1>(ctx.state, operand(0), operand(1));
|
||||
}
|
||||
inline constexpr auto EmitFPOrdEqual32 = EmitFloatCompare32<spv::OpFOrdEqual>;
|
||||
inline constexpr auto EmitFPUnordEqual32 = EmitFloatCompare32<spv::OpFUnordEqual>;
|
||||
inline constexpr auto EmitFPOrdNotEqual32 = EmitFloatCompare32<spv::OpFOrdNotEqual>;
|
||||
inline constexpr auto EmitFPUnordNotEqual32 = EmitFloatCompare32<spv::OpFUnordNotEqual>;
|
||||
inline constexpr auto EmitFPOrdLessThan32 = EmitFloatCompare32<spv::OpFOrdLessThan>;
|
||||
inline constexpr auto EmitFPUnordLessThan32 = EmitFloatCompare32<spv::OpFUnordLessThan>;
|
||||
inline constexpr auto EmitFPOrdGreaterThan32 = EmitFloatCompare32<spv::OpFOrdGreaterThan>;
|
||||
inline constexpr auto EmitFPUnordGreaterThan32 = EmitFloatCompare32<spv::OpFUnordGreaterThan>;
|
||||
inline constexpr auto EmitFPOrdLessThanEqual32 = EmitFloatCompare32<spv::OpFOrdLessThanEqual>;
|
||||
inline constexpr auto EmitFPUnordLessThanEqual32 = EmitFloatCompare32<spv::OpFUnordLessThanEqual>;
|
||||
inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32<spv::OpFOrdGreaterThanEqual>;
|
||||
inline constexpr auto EmitFPUnordGreaterThanEqual32 = EmitFloatCompare32<spv::OpFUnordGreaterThanEqual>;
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
|
||||
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
|
||||
@@ -182,6 +200,7 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& in
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||
uint32_t EmitLaneId(EmitterState& state);
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
@@ -146,7 +146,12 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem
|
||||
ctx.Fail(inst, "has no address base pair");
|
||||
return ConstantDeviceAddress(state, 0);
|
||||
}
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), ctx.Arg(*handle, 1));
|
||||
auto base_low = ctx.Arg(*handle, 0);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
base_low = Binary(state, spv::OpBitwiseAnd, TypeU32(state), base_low,
|
||||
ConstantU32(state, ~3u));
|
||||
}
|
||||
const auto base = DeviceAddressFromWords(state, base_low, ctx.Arg(*handle, 1));
|
||||
address = Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), low));
|
||||
}
|
||||
@@ -1099,16 +1104,27 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.half == 1) {
|
||||
return ctx.other_half->Def(IR::Value(const_cast<IR::Inst*>(&inst)));
|
||||
}
|
||||
const auto m0 = ctx.Arg(inst, 0);
|
||||
const auto base =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), m0, ConstantU32(state, 16));
|
||||
const auto size =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), m0, ConstantU32(state, 0xffffu));
|
||||
const auto address = Binary(state, spv::OpIAdd, TypeU32(state), base,
|
||||
ConstantU32(state, ctx.Memory(inst).offset));
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto offset = mem.offset & 0xfffcu;
|
||||
auto address = ConstantU32(state, offset);
|
||||
uint32_t region_bounds = 0;
|
||||
if (mem.kind == IR::ResourceKind::Gds) {
|
||||
const auto m0 = ctx.Arg(inst, 0);
|
||||
const auto base =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), m0, ConstantU32(state, 16));
|
||||
const auto size =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), m0, ConstantU32(state, 0xffffu));
|
||||
address = Binary(state, spv::OpIAdd, TypeU32(state), base, address);
|
||||
// The entire M0 region must fit the PS5's 48 KiB GDS partition.
|
||||
region_bounds = AndCondition(
|
||||
state, Binary(state, spv::OpULessThan, TypeBool(state),
|
||||
ConstantU32(state, offset + 3u), size),
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), base, size),
|
||||
ConstantU32(state, 0xc000u)));
|
||||
}
|
||||
const auto raw_index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto index = EmitMemoryElementIndex(state, access, raw_index);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
@@ -1127,19 +1143,17 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
: first;
|
||||
const auto is_first = Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
EmitSubgroupLocalInvocationId(state), source_lane);
|
||||
const auto storage_bounds = EmitMemoryElementInBounds(state, access, index);
|
||||
const auto m0_bounds =
|
||||
mem.kind == IR::ResourceKind::Gds
|
||||
? Binary(state, spv::OpINotEqual, TypeBool(state), size, ConstantU32(state, 0))
|
||||
: Binary(state, spv::OpULessThan, TypeBool(state),
|
||||
ConstantU32(state, ctx.Memory(inst).offset + 3u), size);
|
||||
auto bounds = EmitMemoryElementInBounds(state, access, index);
|
||||
if (mem.kind == IR::ResourceKind::Gds) {
|
||||
bounds = AndCondition(state, bounds, region_bounds);
|
||||
}
|
||||
const auto condition = AndCondition(
|
||||
state, is_first,
|
||||
AndCondition(state,
|
||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
||||
ConstantU32(state, 0))
|
||||
: exec,
|
||||
AndCondition(state, storage_bounds, m0_bounds)));
|
||||
bounds));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(append ? spv::OpAtomicIAdd : spv::OpAtomicISub, TypeU32(state),
|
||||
@@ -1173,10 +1187,11 @@ void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (mem.planning_only) return;
|
||||
auto& state = ctx.state;
|
||||
mem.kind = IR::ResourceKind::ScalarBuffer;
|
||||
const auto address = Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
|
||||
ConstantU32(state, mem.offset));
|
||||
const auto index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), ctx.Arg(inst, 1),
|
||||
ConstantU32(state, 2));
|
||||
if (mem.offset >= 4u) {
|
||||
index = Binary(state, spv::OpIAdd, TypeU32(state), index, ConstantU32(state, mem.offset >> 2u));
|
||||
}
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto element = EmitMemoryElementIndex(state, access, index);
|
||||
const auto condition = EmitMemoryElementInBounds(state, access, element);
|
||||
@@ -1198,6 +1213,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
value = LoadWideBuffer(ctx, inst, buffer_components);
|
||||
else if (shared_components > 1u)
|
||||
value = LoadWideShared(ctx, inst, shared_components);
|
||||
else if (mem.kind == IR::ResourceKind::ScalarAddress)
|
||||
value = LoadBdaDword(ctx, GuestAddress(ctx, inst, mem));
|
||||
else if (address_info.access == IR::AddressAccess::Read &&
|
||||
mem.kind != IR::ResourceKind::Scratch)
|
||||
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
|
||||
|
||||
@@ -97,30 +97,6 @@ void EmitReturn(ValueEmitContext& ctx) {
|
||||
ctx.state.builder.AddFunction(spv::OpReturn);
|
||||
}
|
||||
|
||||
uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
|
||||
// Scalar-instruction conditions already test the full wave's raw register values.
|
||||
if (ctx.other_half == nullptr ||
|
||||
info.terminator.condition == CFG::BranchCondition::ScalarInstruction ||
|
||||
info.terminator.condition == CFG::BranchCondition::GotoVariable) {
|
||||
return ctx.Def(info.condition);
|
||||
}
|
||||
const auto ballot = ctx.Ballot(info.condition);
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const auto kind = info.terminator.condition;
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero ||
|
||||
kind == CFG::BranchCondition::SccZero;
|
||||
const auto combined =
|
||||
EmitBinaryU32(ctx.state, zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual, TypeBool(ctx.state),
|
||||
result, combined, ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& program = ctx.state.program;
|
||||
@@ -160,7 +136,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
const auto condition = BranchCondition(ctx, info);
|
||||
const auto condition = ctx.Def(info.condition);
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
|
||||
ctx.Label(true_block), ctx.Label(false_block));
|
||||
@@ -193,7 +169,7 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
const auto selected = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, BranchCondition(ctx, info),
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
|
||||
return selected;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,17 @@ enum class BranchCondition {
|
||||
ExecZero,
|
||||
ExecNonZero,
|
||||
ScalarInstruction,
|
||||
GotoVariable,
|
||||
Expression,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct ConditionExpression {
|
||||
enum class Op { Constant, Variable, Native, Not, Or };
|
||||
Op op;
|
||||
uint32_t lhs;
|
||||
uint32_t rhs = UINT32_MAX;
|
||||
};
|
||||
|
||||
enum class TerminatorKind { Branch, ConditionalBranch, IndirectBranch, Return, Unsupported };
|
||||
|
||||
enum class FailureKind {
|
||||
@@ -48,8 +55,7 @@ struct Terminator {
|
||||
std::vector<uint32_t> indirect_targets;
|
||||
std::vector<uint32_t> indirect_selector_values;
|
||||
std::vector<uint32_t> indirect_selector_targets;
|
||||
uint32_t goto_variable = UINT32_MAX;
|
||||
int32_t goto_value = -1;
|
||||
uint32_t expression = UINT32_MAX;
|
||||
bool loop_header = false;
|
||||
};
|
||||
|
||||
@@ -59,10 +65,11 @@ struct BasicBlock {
|
||||
uint32_t end_pc = 0;
|
||||
uint32_t inst_begin = 0;
|
||||
uint32_t inst_end = 0;
|
||||
struct Assignment { uint32_t variable; uint32_t expression; };
|
||||
std::vector<Assignment> assignments;
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> dominators;
|
||||
std::vector<uint32_t> post_dominators;
|
||||
Terminator terminator;
|
||||
};
|
||||
|
||||
@@ -73,12 +80,9 @@ struct BackEdge {
|
||||
};
|
||||
|
||||
struct NaturalLoop {
|
||||
uint32_t header = UINT32_MAX;
|
||||
uint32_t latch = UINT32_MAX;
|
||||
uint32_t merge = UINT32_MAX;
|
||||
uint32_t continue_block = UINT32_MAX;
|
||||
uint32_t header = UINT32_MAX;
|
||||
uint32_t latch = UINT32_MAX;
|
||||
std::vector<uint32_t> body_blocks;
|
||||
std::vector<uint32_t> exit_blocks;
|
||||
};
|
||||
|
||||
struct StronglyConnectedComponent {
|
||||
@@ -89,6 +93,7 @@ struct StronglyConnectedComponent {
|
||||
|
||||
struct Graph {
|
||||
std::vector<BasicBlock> blocks;
|
||||
std::vector<ConditionExpression> expressions;
|
||||
std::vector<BackEdge> back_edges;
|
||||
std::vector<NaturalLoop> natural_loops;
|
||||
std::vector<StronglyConnectedComponent> components;
|
||||
@@ -105,14 +110,12 @@ struct Graph {
|
||||
const BasicBlock* FindBlockByPc(uint32_t pc) const;
|
||||
BasicBlock* FindBlockByPc(uint32_t pc);
|
||||
bool Dominates(uint32_t dominator, uint32_t block) const;
|
||||
bool PostDominates(uint32_t post_dominator, uint32_t block) const;
|
||||
uint32_t FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_b) const;
|
||||
};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
// Commits structured control flow on success; preserves the original graph with
|
||||
// failure diagnostics on failure. failure_block is an original block ID or UINT32_MAX.
|
||||
bool Structurize(Graph& graph);
|
||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
@@ -44,7 +44,8 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
|
||||
half ? IR::Value(ReadF16AsF32(inst.src0)) : ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto rhs =
|
||||
half ? IR::Value(ReadF16AsF32(inst.src1)) : ReadOperand(inst.src1, IR::Type::F32);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false, cmpx);
|
||||
const IR::FPCompareFlags flags{.flush_input_denorms = !half && flush_f32_inputs};
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx) {
|
||||
|
||||
@@ -235,6 +235,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
return index == 0u ? decoded.src0 : MakeImmediate(decoded.offset & 0xffffu);
|
||||
case Decoder::Opcode::DS_CONSUME:
|
||||
case Decoder::Opcode::DS_APPEND:
|
||||
return decoded.gds ? MakeM0Operand() : MakeImmediate(0);
|
||||
case Decoder::Opcode::DS_READ_ADDTID_B32: return MakeM0Operand();
|
||||
case Decoder::Opcode::DS_WRITE_ADDTID_B32:
|
||||
return index == 0u ? decoded.src1 : MakeM0Operand();
|
||||
@@ -845,8 +846,7 @@ void Translator::DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec(), ir.GetExecLo(),
|
||||
ir.GetExecHi()},
|
||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
}
|
||||
|
||||
|
||||
@@ -317,6 +317,7 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
case Decoder::OperandKind::Sgpr: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
ir.SetScalarReg(reg, value);
|
||||
program.scalar_writes.push_back({current_pc, reg});
|
||||
ir.SetScalarMaskTag(reg, IR::U1(IR::Value(false)));
|
||||
if (IR::RegIndex(reg) > 0u) {
|
||||
ir.SetScalarMaskTag(static_cast<IR::ScalarReg>(IR::RegIndex(reg) - 1u),
|
||||
@@ -699,11 +700,13 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
|
||||
IR::U1(IR::Value(false)));
|
||||
}
|
||||
ir.SetScalarReg(reg, mask[0]);
|
||||
program.scalar_writes.push_back({current_pc, reg});
|
||||
// A wave32 VALU mask destination must not overwrite the neighboring SGPR.
|
||||
if ((write_64 || program.wave_size == 64u) &&
|
||||
IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
|
||||
const auto high = static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u);
|
||||
ir.SetScalarReg(high, mask[1]);
|
||||
program.scalar_writes.push_back({current_pc, high});
|
||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||
}
|
||||
@@ -746,55 +749,61 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu
|
||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
||||
}
|
||||
|
||||
void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info) {
|
||||
if (source.terminator.goto_value >= 0) {
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
EXIT("block %u sets an invalid goto variable", source.id);
|
||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::BlockInfo& info) {
|
||||
const auto native_condition = [&](CFG::BranchCondition kind) -> IR::U1 {
|
||||
IR::U1 condition;
|
||||
switch (kind) {
|
||||
case CFG::BranchCondition::Always: condition = IR::U1(IR::Value(true)); break;
|
||||
case CFG::BranchCondition::SccZero: condition = ir.LogicalNot(ir.GetScc()); break;
|
||||
case CFG::BranchCondition::SccNonZero: condition = ir.GetScc(); break;
|
||||
case CFG::BranchCondition::VccZero: condition = ir.LogicalNot(ir.GetVcc()); break;
|
||||
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
|
||||
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
|
||||
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
|
||||
case CFG::BranchCondition::ScalarInstruction:
|
||||
EXIT_IF(instruction_branch_condition.IsEmpty());
|
||||
condition = instruction_branch_condition;
|
||||
break;
|
||||
default: EXIT("block %u has an invalid native branch condition", source.id);
|
||||
}
|
||||
ir.SetGotoVariable(source.terminator.goto_variable,
|
||||
IR::U1(IR::Value(source.terminator.goto_value != 0)));
|
||||
return IR::U1(ir.Emit(IR::ValueOpcode::ConditionRef, {condition}, kind));
|
||||
};
|
||||
const auto expression = [&](auto&& self, uint32_t index) -> IR::U1 {
|
||||
const auto& value = graph.expressions.at(index);
|
||||
switch (value.op) {
|
||||
case CFG::ConditionExpression::Op::Constant: return IR::U1(IR::Value(value.lhs != 0));
|
||||
case CFG::ConditionExpression::Op::Variable: return ir.GetGotoVariable(value.lhs);
|
||||
case CFG::ConditionExpression::Op::Native:
|
||||
return native_condition(static_cast<CFG::BranchCondition>(value.lhs));
|
||||
case CFG::ConditionExpression::Op::Not: return ir.LogicalNot(self(self, value.lhs));
|
||||
case CFG::ConditionExpression::Op::Or:
|
||||
return ir.LogicalOr(self(self, value.lhs), self(self, value.rhs));
|
||||
}
|
||||
EXIT("invalid CFG condition expression");
|
||||
};
|
||||
for (const auto& assignment: source.assignments) {
|
||||
ir.SetGotoVariable(assignment.variable, expression(expression, assignment.expression));
|
||||
}
|
||||
if (source.terminator.kind == CFG::TerminatorKind::IndirectBranch) {
|
||||
if (source.terminator.indirect_selector_code != UINT32_MAX) {
|
||||
info.indirect_target = ReadScalarCode(source.terminator.indirect_selector_code);
|
||||
} else if (source.terminator.indirect_pc_sgpr != UINT32_MAX) {
|
||||
info.indirect_target =
|
||||
ir.GetScalarReg(static_cast<IR::ScalarReg>(source.terminator.indirect_pc_sgpr));
|
||||
const auto& term = source.terminator;
|
||||
if (term.kind == CFG::TerminatorKind::IndirectBranch) {
|
||||
if (term.indirect_selector_code != UINT32_MAX) {
|
||||
info.indirect_target = ReadScalarCode(term.indirect_selector_code);
|
||||
} else if (term.indirect_pc_sgpr != UINT32_MAX) {
|
||||
info.indirect_target = ir.GetScalarReg(static_cast<IR::ScalarReg>(term.indirect_pc_sgpr));
|
||||
} else {
|
||||
EXIT("block %u has no indirect branch selector", source.id);
|
||||
}
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {info.indirect_target});
|
||||
return;
|
||||
}
|
||||
if (source.terminator.kind != CFG::TerminatorKind::ConditionalBranch) {
|
||||
return;
|
||||
if (term.kind == CFG::TerminatorKind::ConditionalBranch) {
|
||||
const auto condition = term.expression != UINT32_MAX
|
||||
? expression(expression, term.expression)
|
||||
: native_condition(term.condition);
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
}
|
||||
// EXEC and VCC are invocation-local Boolean masks. Branching on that Boolean lets inactive
|
||||
// invocations leave the region without reconstructing a host-subgroup mask.
|
||||
IR::U1 condition;
|
||||
switch (source.terminator.condition) {
|
||||
case CFG::BranchCondition::Always: condition = IR::U1(IR::Value(true)); break;
|
||||
case CFG::BranchCondition::SccZero: condition = ir.LogicalNot(ir.GetScc()); break;
|
||||
case CFG::BranchCondition::SccNonZero: condition = ir.GetScc(); break;
|
||||
case CFG::BranchCondition::VccZero: condition = ir.LogicalNot(ir.GetVcc()); break;
|
||||
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
|
||||
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
|
||||
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
|
||||
case CFG::BranchCondition::ScalarInstruction:
|
||||
EXIT_IF(instruction_branch_condition.IsEmpty());
|
||||
condition = instruction_branch_condition;
|
||||
break;
|
||||
case CFG::BranchCondition::GotoVariable:
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
EXIT("block %u reads an invalid goto variable", source.id);
|
||||
}
|
||||
condition = ir.GetGotoVariable(source.terminator.goto_variable);
|
||||
break;
|
||||
case CFG::BranchCondition::Unknown:
|
||||
EXIT("block %u has an unknown branch condition", source.id);
|
||||
}
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1272,9 +1281,11 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
builtin(IR::StageInputKind::InstanceIndex));
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0;
|
||||
for (const auto& cfg_block: cfg.blocks) {
|
||||
const auto typed_index = block_indices.at(cfg_block.id);
|
||||
Translator translator(result, result.blocks[typed_index], vector_limit);
|
||||
Translator translator(result, result.blocks[typed_index], vector_limit, flush_f32_inputs);
|
||||
for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) {
|
||||
const auto& instruction = decoded.instructions[index];
|
||||
if (IsCodeTableLoad(cfg, instruction.pc)) {
|
||||
@@ -1297,7 +1308,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
translator.TranslateInstruction(instruction);
|
||||
}
|
||||
translator.AddBranchCondition(cfg_block, result.block_info[typed_index]);
|
||||
translator.AddBranchCondition(cfg, cfg_block, result.block_info[typed_index]);
|
||||
}
|
||||
IR::ValidateProgram(result, false);
|
||||
return result;
|
||||
|
||||
@@ -9,13 +9,15 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
class Translator {
|
||||
public:
|
||||
Translator(IR::Program& program, IR::Block* block, uint32_t vector_limit)
|
||||
: program(program), ir(block), current_vector_limit(vector_limit) {}
|
||||
Translator(IR::Program& program, IR::Block* block, uint32_t vector_limit,
|
||||
bool flush_f32_inputs)
|
||||
: program(program), ir(block), current_vector_limit(vector_limit),
|
||||
flush_f32_inputs(flush_f32_inputs) {}
|
||||
|
||||
void TranslateInstruction(const Decoder::Instruction& inst);
|
||||
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count, const ShaderBufferResource& resource);
|
||||
void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
|
||||
private:
|
||||
const Decoder::Operand& SourceAt(const Decoder::Instruction& inst, uint32_t index);
|
||||
@@ -261,6 +263,7 @@ private:
|
||||
Decoder::Opcode current_opcode = Decoder::Opcode::UNKNOWN;
|
||||
uint32_t current_pc = 0;
|
||||
uint32_t current_vector_limit = 1;
|
||||
bool flush_f32_inputs;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
|
||||
@@ -81,7 +81,8 @@ bool EquivalentValue(const ResourcePlan& program, Value left, Value right,
|
||||
program.memory_info[li] != program.memory_info[ri]) {
|
||||
return false;
|
||||
}
|
||||
} else if (lhs->Flags<uint64_t>() != rhs->Flags<uint64_t>()) {
|
||||
} else if (lhs->GetOpcode() != ValueOpcode::ReadConst &&
|
||||
lhs->Flags<uint64_t>() != rhs->Flags<uint64_t>()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < lhs->NumArgs(); index++) {
|
||||
|
||||
@@ -147,6 +147,7 @@ struct SamplerResource {
|
||||
uint32_t first_use_pc = 0;
|
||||
bool force_point_filtering = false;
|
||||
bool depth_compare = false;
|
||||
bool integer_border = false;
|
||||
|
||||
bool operator==(const SamplerResource& other) const = default;
|
||||
};
|
||||
@@ -582,10 +583,11 @@ struct Program: ResourcePlan {
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<BlockInfo> block_info;
|
||||
struct ScalarWrite { uint32_t pc; ScalarReg reg; };
|
||||
std::vector<ScalarWrite> scalar_writes;
|
||||
// Typed memory and export instructions reference shader-local metadata by dense index.
|
||||
// Decoder-only details (such as NSA register numbers) have already become IR operands.
|
||||
std::vector<ExportInfo> export_info;
|
||||
std::vector<Value> dynamic_reads;
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
|
||||
@@ -52,6 +52,10 @@ struct PermlaneFlags {
|
||||
static_assert(sizeof(PermlaneFlags) <= sizeof(uint64_t));
|
||||
static_assert(std::is_trivially_copyable_v<PermlaneFlags>);
|
||||
|
||||
struct FPCompareFlags {
|
||||
bool flush_input_denorms = false;
|
||||
};
|
||||
|
||||
struct MemoryFlags {
|
||||
uint32_t index = 0;
|
||||
uint32_t pc = 0;
|
||||
|
||||
@@ -4,6 +4,7 @@ VALUE_OPCODE(Identity, Opaque, Opaque)
|
||||
VALUE_OPCODE(Void, Void)
|
||||
VALUE_OPCODE(Reference, Void, U1)
|
||||
VALUE_OPCODE(ReferenceU32, Void, U32)
|
||||
VALUE_OPCODE(ConditionRef, U1, U1)
|
||||
|
||||
// Translation and register-state pseudo operations.
|
||||
VALUE_OPCODE(GetUserData, U32, ScalarReg)
|
||||
@@ -274,8 +275,8 @@ VALUE_OPCODE(SharedAtomicUMax32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicAnd32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U1, U32, U32)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U1, U32, U32)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
||||
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||
|
||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||
|
||||
@@ -85,6 +85,16 @@ bool CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffe
|
||||
return uses_gds;
|
||||
}
|
||||
|
||||
bool UsesFlattenedSrt(const Program& program) {
|
||||
return std::ranges::any_of(program.blocks, [](const Block* block) {
|
||||
return std::ranges::any_of(*block, [](const Inst& inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::ReadConst;
|
||||
});
|
||||
}) || std::ranges::any_of(program.info.images, [](const ImageResource& image) {
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
}
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
if (!program.shader_info_complete || program.binding_layout_complete) {
|
||||
EXIT("shader binding layout failed: %s", !program.shader_info_complete
|
||||
@@ -145,12 +155,7 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
AddBinding(next, DescriptorBindingKind::BdaPagetable);
|
||||
AddBinding(next, DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const ImageResource& image) {
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
if (UsesFlattenedSrt(program)) {
|
||||
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0);
|
||||
|
||||
// Collect live direct buffer indices and report whether the shader accesses GDS.
|
||||
bool CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers);
|
||||
bool UsesFlattenedSrt(const Program& program);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
|
||||
@@ -630,6 +630,11 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case ValueOpcode::ConditionRef: {
|
||||
const auto value = Arg(inst, 0);
|
||||
if (IsImmediate(value, Type::U1)) Replace(inst, value);
|
||||
return;
|
||||
}
|
||||
case ValueOpcode::LogicalNot: {
|
||||
const auto value = Arg(inst, 0);
|
||||
if (IsImmediate(value, Type::U1)) {
|
||||
|
||||
@@ -107,14 +107,16 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) {
|
||||
: Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
bool RequiresPointSampler(const ImageResource& image) {
|
||||
return image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
enum class SamplerClass : uint8_t { Float, Integer, PointInteger };
|
||||
|
||||
bool RequiresPointSampler(const ResourceSpecialization::Image& image) {
|
||||
return image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
template <typename Image>
|
||||
SamplerClass ClassifySampler(const Image& image) {
|
||||
if (image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
return SamplerClass::PointInteger;
|
||||
}
|
||||
return image.numeric_class == Prospero::TextureNumericClass::Uint ? SamplerClass::Integer
|
||||
: SamplerClass::Float;
|
||||
}
|
||||
|
||||
bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
@@ -223,9 +225,9 @@ void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* sou
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadScalarTable(uint64_t base, uint64_t size, uint32_t dynamic_offset,
|
||||
bool ReadScalarTable(uint64_t base, uint64_t size, uint64_t dynamic_offset,
|
||||
const SrtRuntime& runtime, std::span<uint32_t> words) {
|
||||
const auto offset = static_cast<uint64_t>(dynamic_offset) & ~uint64_t {3};
|
||||
const auto offset = dynamic_offset & ~uint64_t {3};
|
||||
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
|
||||
std::ranges::fill(words.subspan(count), 0u);
|
||||
if (count == 0u) {
|
||||
@@ -304,26 +306,24 @@ bool MaterializeIndirectImage(const ResourcePlan& program,
|
||||
material.Stride() != indirect.selector_stride) {
|
||||
return false;
|
||||
}
|
||||
// Enumerate every wrapped scalar-buffer offset that can pass the descriptor bounds.
|
||||
const auto step = std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u);
|
||||
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
|
||||
const auto limit = std::min<uint64_t>(UINT32_MAX, material.GetSize() + 3u);
|
||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
||||
// The first aligned offset includes the immediate added after shader U32 arithmetic.
|
||||
const auto step = std::max<uint64_t>(4u,
|
||||
std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u));
|
||||
const uint64_t first = indirect.selector_offset;
|
||||
const auto size = material.GetSize();
|
||||
const auto limit = std::min(first + (uint64_t {1} << 32u) - step, size >= 4u ? size - 4u : 0u);
|
||||
const auto probe_count = size >= 4u && first <= limit ? (limit - first) / step + 1u : 0u;
|
||||
if (probe_count > MaxIndirectImageProbes) {
|
||||
return false;
|
||||
}
|
||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
keys.push_back(0u);
|
||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
||||
for (uint64_t probe = 0, offset = first; probe < probe_count; ++probe, offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarTable(material.Base48(), material.GetSize(),
|
||||
static_cast<uint32_t>(offset), runtime, {&key, 1})) {
|
||||
if (!ReadScalarTable(material.Base48(), size, offset, runtime, {&key, 1})) {
|
||||
return false;
|
||||
}
|
||||
keys.push_back(key);
|
||||
if (limit - offset < step) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::ranges::sort(keys);
|
||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||
@@ -380,8 +380,13 @@ bool MaterializeIndirectImage(const ResourcePlan& program,
|
||||
} // namespace
|
||||
|
||||
struct SamplerPlan {
|
||||
std::array<uint32_t, ShaderInfo::MaxSamplers> point_sampler {};
|
||||
uint32_t sampler_count = 0;
|
||||
struct Binding {
|
||||
uint32_t source;
|
||||
SamplerClass type;
|
||||
};
|
||||
std::array<std::array<uint32_t, 3>, ShaderInfo::MaxSamplers> mapping;
|
||||
std::array<Binding, ShaderInfo::MaxSamplers> bindings;
|
||||
uint32_t sampler_count = 0;
|
||||
};
|
||||
|
||||
struct ImageRemap {
|
||||
@@ -595,11 +600,9 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
if (!BuildSamplerPlan(program.info, specialization.images, sampler_plan)) {
|
||||
return SpecializationFail("specialized sampler layout exceeds its resource limit");
|
||||
}
|
||||
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
|
||||
const auto target = sampler_plan.point_sampler[index];
|
||||
if (target != UINT32_MAX && target >= program.info.samplers.size()) {
|
||||
snapshot.samplers.push_back(snapshot.samplers[index]);
|
||||
}
|
||||
for (uint32_t index = static_cast<uint32_t>(program.info.samplers.size());
|
||||
index < sampler_plan.sampler_count; index++) {
|
||||
snapshot.samplers.push_back(snapshot.samplers[sampler_plan.bindings[index].source]);
|
||||
}
|
||||
ImageRemap(specialization).Apply(snapshot.images);
|
||||
return true;
|
||||
@@ -607,29 +610,29 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
|
||||
template <typename Images>
|
||||
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan) {
|
||||
if (base.samplers.size() > plan.point_sampler.size()) {
|
||||
if (base.samplers.size() > plan.mapping.size()) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, ShaderInfo::MaxSamplers> usage {};
|
||||
plan.point_sampler.fill(UINT32_MAX);
|
||||
plan.sampler_count = static_cast<uint32_t>(base.samplers.size());
|
||||
for (const auto& pair: base.sampled_pairs) {
|
||||
if (pair.image >= images.size() || pair.sampler >= base.samplers.size()) {
|
||||
return false;
|
||||
}
|
||||
usage[pair.sampler] |= RequiresPointSampler(images[pair.image]) ? 2u : 1u;
|
||||
usage[pair.sampler] |= 1u << static_cast<uint32_t>(ClassifySampler(images[pair.image]));
|
||||
}
|
||||
for (uint32_t index = 0; index < base.samplers.size(); index++) {
|
||||
if ((usage[index] & 2u) == 0u) {
|
||||
continue;
|
||||
}
|
||||
if ((usage[index] & 1u) == 0u) {
|
||||
plan.point_sampler[index] = index;
|
||||
} else {
|
||||
if (plan.sampler_count >= ShaderInfo::MaxSamplers) {
|
||||
return false;
|
||||
}
|
||||
plan.point_sampler[index] = plan.sampler_count++;
|
||||
auto& mapping = plan.mapping[index];
|
||||
mapping.fill(UINT32_MAX);
|
||||
const auto classes = usage[index] == 0u ? 1u : usage[index];
|
||||
bool first = true;
|
||||
for (uint32_t type = 0; type < mapping.size(); type++) {
|
||||
if ((classes & (1u << type)) == 0u) continue;
|
||||
const auto target = first ? index : plan.sampler_count++;
|
||||
if (target >= ShaderInfo::MaxSamplers) return false;
|
||||
mapping[type] = target;
|
||||
plan.bindings[target] = {index, static_cast<SamplerClass>(type)};
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1105,25 +1108,18 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
auto samplers = program.info.samplers;
|
||||
auto sampled_pairs = program.info.sampled_pairs;
|
||||
samplers.reserve(sampler_plan.sampler_count);
|
||||
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
|
||||
const auto target = sampler_plan.point_sampler[index];
|
||||
if (target == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
if (target == index) {
|
||||
samplers[index].force_point_filtering = true;
|
||||
} else {
|
||||
EXIT_IF(target != samplers.size());
|
||||
auto sampler = samplers[index];
|
||||
sampler.force_point_filtering = true;
|
||||
samplers.push_back(std::move(sampler));
|
||||
for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) {
|
||||
const auto& binding = sampler_plan.bindings[index];
|
||||
if (index >= program.info.samplers.size()) {
|
||||
samplers.push_back(program.info.samplers[binding.source]);
|
||||
}
|
||||
samplers[index].force_point_filtering = binding.type == SamplerClass::PointInteger;
|
||||
samplers[index].integer_border = binding.type != SamplerClass::Float;
|
||||
}
|
||||
for (auto& pair: sampled_pairs) {
|
||||
if (RequiresPointSampler(images[pair.image])) {
|
||||
EXIT_IF(sampler_plan.point_sampler[pair.sampler] == UINT32_MAX);
|
||||
pair.sampler = sampler_plan.point_sampler[pair.sampler];
|
||||
}
|
||||
const auto type = static_cast<uint32_t>(ClassifySampler(images[pair.image]));
|
||||
pair.sampler = sampler_plan.mapping[pair.sampler][type];
|
||||
EXIT_IF(pair.sampler == UINT32_MAX);
|
||||
samplers[pair.sampler].depth_compare |= images[pair.image].depth_compare;
|
||||
}
|
||||
|
||||
@@ -1201,10 +1197,11 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
inst.ReplaceUsesWith(Value(&*result));
|
||||
continue;
|
||||
}
|
||||
if (image_opcode.needs_sampler && RequiresPointSampler(image) &&
|
||||
if (image_opcode.needs_sampler &&
|
||||
memory.sampler < program.info.samplers.size()) {
|
||||
EXIT_IF(sampler_plan.point_sampler[memory.sampler] == UINT32_MAX);
|
||||
memory.sampler = sampler_plan.point_sampler[memory.sampler];
|
||||
const auto type = static_cast<uint32_t>(ClassifySampler(image));
|
||||
memory.sampler = sampler_plan.mapping[memory.sampler][type];
|
||||
EXIT_IF(memory.sampler == UINT32_MAX);
|
||||
}
|
||||
EXIT_IF(image.indirect_root == memory.resource &&
|
||||
inst.GetOpcode() != ValueOpcode::ImageSampleRaw);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,8 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in
|
||||
// descriptor_sources, then writes dense indices to handle flags and MemoryInfo.
|
||||
void TrackResources(Program& program);
|
||||
// Resolves native descriptor sources, plans their scalar reads, and assigns dense resource bindings.
|
||||
void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
@@ -23,21 +22,6 @@ namespace {
|
||||
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "vertex";
|
||||
case ShaderType::Pixel: return "pixel";
|
||||
case ShaderType::Fetch: return "fetch";
|
||||
case ShaderType::Compute: return "compute";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Diagnostic(const ResourcePlan& program, uint32_t pc, const std::string& message) {
|
||||
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
|
||||
StageName(program.stage), pc, message);
|
||||
}
|
||||
|
||||
bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
|
||||
if (base > AddressMask) {
|
||||
return false;
|
||||
@@ -89,6 +73,7 @@ bool IsRuntimeSelect(ValueOpcode op) {
|
||||
|
||||
bool IsRuntimeUniformOp(ValueOpcode op) {
|
||||
switch (op) {
|
||||
case ValueOpcode::ConditionRef:
|
||||
case ValueOpcode::BitCastU32F32:
|
||||
case ValueOpcode::BitCastF32U32:
|
||||
case ValueOpcode::ConvertU32F32:
|
||||
@@ -312,151 +297,6 @@ private:
|
||||
std::unordered_set<const Inst*> m_validated_dependencies;
|
||||
};
|
||||
|
||||
class PlanBuilder {
|
||||
public:
|
||||
explicit PlanBuilder(Program& program): m_program(program) {}
|
||||
|
||||
void Run() {
|
||||
m_program.srt_reads.clear();
|
||||
m_program.dynamic_reads.clear();
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
if (flags.index < m_program.memory_info.size()) {
|
||||
const auto kind = m_program.memory_info[flags.index].kind;
|
||||
const bool crosswired = (op == ValueOpcode::LoadAddressU32 &&
|
||||
kind == ResourceKind::ScalarBuffer) ||
|
||||
(op == ValueOpcode::ReadConstBuffer &&
|
||||
kind == ResourceKind::ScalarAddress);
|
||||
if (crosswired) {
|
||||
Fail(flags.pc,
|
||||
fmt::format("{} has incompatible scalar memory metadata",
|
||||
ValueOpcodeName(op)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsDescriptorHandle(inst.GetOpcode())) {
|
||||
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
||||
Collect(inst.Arg(index), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_program, inst) &&
|
||||
inst.Arg(1).Resolve().IsImmediate() &&
|
||||
ValidateRuntimeValue(m_program, Value(&inst))) {
|
||||
Collect(Value(&inst), inst.Flags<MemoryFlags>().pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
PatchReads();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Patch {
|
||||
Inst* inst = nullptr;
|
||||
uint32_t slot = 0;
|
||||
bool keep = false;
|
||||
};
|
||||
|
||||
[[noreturn]] void Fail(uint32_t pc, const std::string& message) const {
|
||||
const auto diagnostic = Diagnostic(m_program, pc, message);
|
||||
EXIT("shader SRT planning failed: %s", diagnostic.c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void Collect(Value value, uint32_t use_pc) {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) {
|
||||
return;
|
||||
}
|
||||
auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr) {
|
||||
Fail(use_pc, "invalid typed planning value");
|
||||
}
|
||||
const auto cycle = std::ranges::find(m_visiting, inst);
|
||||
if (cycle != m_visiting.end()) {
|
||||
const auto contains_phi = std::any_of(cycle, m_visiting.end(), [](const Inst* value) {
|
||||
return value->GetOpcode() == ValueOpcode::Phi;
|
||||
});
|
||||
if (contains_phi) {
|
||||
return;
|
||||
}
|
||||
Fail(use_pc, fmt::format("cyclic typed planning value {} without a phi",
|
||||
ValueOpcodeName(inst->GetOpcode())));
|
||||
}
|
||||
if (std::ranges::find(m_visited, inst) != m_visited.end()) {
|
||||
return;
|
||||
}
|
||||
m_visiting.push_back(inst);
|
||||
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
||||
Collect(inst->Arg(index), use_pc);
|
||||
}
|
||||
m_visiting.pop_back();
|
||||
m_visited.push_back(inst);
|
||||
if (!IsRawRead(m_program, *inst)) {
|
||||
return;
|
||||
}
|
||||
const auto offset = inst->Arg(1).Resolve();
|
||||
if (!offset.IsImmediate() || offset.GetType() != Type::U32) {
|
||||
if (std::ranges::find(m_program.dynamic_reads, value) ==
|
||||
m_program.dynamic_reads.end()) {
|
||||
m_program.dynamic_reads.push_back(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (uint32_t slot = 0; slot < m_program.srt_reads.size(); slot++) {
|
||||
if (EquivalentValue(m_program, value, m_program.srt_reads[slot].value)) {
|
||||
m_patches.push_back({inst, slot, false});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto slot = static_cast<uint32_t>(m_program.srt_reads.size());
|
||||
m_program.srt_reads.push_back({value, slot});
|
||||
m_patches.push_back({inst, slot, true});
|
||||
}
|
||||
|
||||
void PatchReads() {
|
||||
for (const auto& patch: m_patches) {
|
||||
auto* block = patch.inst->Parent();
|
||||
auto& list = block->Instructions();
|
||||
auto where =
|
||||
std::ranges::find_if(list, [&](const Inst& inst) { return &inst == patch.inst; });
|
||||
const auto resource =
|
||||
Value(&*block->PrependNewInst(where, ValueOpcode::GetSrtResource));
|
||||
const auto flat = Value(&*block->PrependNewInst(where, ValueOpcode::ReadConst,
|
||||
{resource, Value(patch.slot)}));
|
||||
const auto uses = patch.inst->Uses();
|
||||
for (const auto& use: uses) {
|
||||
use.user->SetArg(use.operand, flat);
|
||||
}
|
||||
for (auto& info: m_program.block_info) {
|
||||
if (info.condition.Resolve() == Value(patch.inst)) {
|
||||
info.condition = flat;
|
||||
}
|
||||
if (info.indirect_target.Resolve() == Value(patch.inst)) {
|
||||
info.indirect_target = flat;
|
||||
}
|
||||
}
|
||||
if (patch.keep) {
|
||||
const auto memory = patch.inst->Flags<MemoryFlags>().index;
|
||||
if (memory < m_program.memory_info.size()) {
|
||||
m_program.memory_info[memory].planning_only = true;
|
||||
}
|
||||
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(patch.inst)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Program& m_program;
|
||||
std::vector<Inst*> m_visiting;
|
||||
std::vector<Inst*> m_visited;
|
||||
std::vector<Patch> m_patches;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -616,16 +456,15 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
return false;
|
||||
}
|
||||
const auto byte_offset =
|
||||
static_cast<uint64_t>(immediate) + static_cast<uint32_t>(offset);
|
||||
const auto aligned = byte_offset & ~uint64_t {3};
|
||||
(static_cast<uint64_t>(immediate) & ~uint64_t {3}) + (static_cast<uint32_t>(offset) & ~3u);
|
||||
const auto stride = (static_cast<uint32_t>(high) >> 16u) & 0x3fffu;
|
||||
const auto size = stride == 0u
|
||||
? static_cast<uint64_t>(static_cast<uint32_t>(records))
|
||||
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
|
||||
if (aligned > size || size - aligned < sizeof(uint32_t)) {
|
||||
if (byte_offset > size || size - byte_offset < sizeof(uint32_t)) {
|
||||
return false;
|
||||
}
|
||||
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
|
||||
address = (base & ~uint64_t {3}) + byte_offset;
|
||||
} else {
|
||||
const auto relative = (immediate & ~int64_t {3}) +
|
||||
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
|
||||
@@ -780,14 +619,18 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::FPOrdLessThanEqual32:
|
||||
if (binary()) {
|
||||
result = Float32(a) <= Float32(b);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::FPOrdGreaterThanEqual32:
|
||||
if (binary()) {
|
||||
result = Float32(a) >= Float32(b);
|
||||
const auto operand = [&](uint64_t bits) {
|
||||
if (inst.Flags<FPCompareFlags>().flush_input_denorms &&
|
||||
(bits & 0x7fffffffu) < 0x00800000u) {
|
||||
bits &= 0x80000000u;
|
||||
}
|
||||
return Float32(bits);
|
||||
};
|
||||
result = inst.GetOpcode() == ValueOpcode::FPOrdLessThanEqual32
|
||||
? operand(a) <= operand(b)
|
||||
: operand(a) >= operand(b);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -970,6 +813,7 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::ConditionRef: return Arg(inst, 0, result);
|
||||
case ValueOpcode::LogicalNot:
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = a == 0u;
|
||||
@@ -1061,13 +905,5 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValue
|
||||
return RuntimeValidator(program, type).Run(value);
|
||||
}
|
||||
|
||||
void BuildSrtPlan(Program& program) {
|
||||
if (program.resource_tracking_complete) {
|
||||
EXIT("shader SRT planning failed: cannot rebuild SRT after resource tracking");
|
||||
}
|
||||
program.srt_plan_complete = false;
|
||||
PlanBuilder(program).Run();
|
||||
program.srt_plan_complete = true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -21,9 +21,6 @@ struct SrtRuntime {
|
||||
|
||||
enum class RuntimeValueType { Any, Integer };
|
||||
|
||||
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
|
||||
// dynamic offsets remain explicit and are never assigned a fake slot.
|
||||
void BuildSrtPlan(Program& program);
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
RuntimeValueType type = RuntimeValueType::Any);
|
||||
// Uses the strict reader for values that affect shader specialization.
|
||||
|
||||
Reference in New Issue
Block a user