recompiler: allow a shader to be refused instead of aborting

A shader the recompiler cannot translate is a gap in emulation, not a reason to
take the process down: skipping the draw or dispatch that wanted it costs some
rendering, while aborting costs the title.

CompileOptions gains non_fatal and CompileResult gains unsupported. With
non_fatal set, the three places the recompile path gives up - an opcode with no
IR translation, a CFG that will not build, and resource tracking that cannot
resolve a descriptor - record the failure and unwind rather than calling EXIT,
and Recompile returns a result marked unsupported. The caller skips that draw or
dispatch. Nothing changes when non_fatal is clear.

Marvel's Spider-Man: Miles Morales meets an untranslatable compute shader on its
third frame, so a strict build cannot pass the opening logo. Astro's Playroom is
also stopped by a shader today and reaches a later, different failure with this
in place.
This commit is contained in:
Mac
2026-08-31 23:40:50 +05:30
parent a2cafb2f2c
commit ce782ef77f
6 changed files with 122 additions and 4 deletions
@@ -6,6 +6,7 @@
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
@@ -465,6 +466,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
} // namespace
CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& options) {
Frontend::SetTranslationNonFatal(options.non_fatal);
if (code.empty()) {
EXIT("shader recompiler input is empty\n");
}
@@ -508,6 +510,11 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
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 (options.non_fatal && cfg.unsupported && !cfg.irreducible) {
CompileResult unsupported_result;
unsupported_result.unsupported = true;
return unsupported_result;
}
bool dispatcher_fallback = false;
std::string dispatcher_reason;
if (cfg.irreducible) {
@@ -584,6 +591,11 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options);
if (options.non_fatal && Frontend::TranslationUnsupported()) {
CompileResult unsupported_result;
unsupported_result.unsupported = true;
return unsupported_result;
}
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
@@ -612,7 +624,11 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
}
IR::BuildSrtPlan(ir);
IR::EliminateDeadCode(ir.blocks);
IR::TrackResources(ir);
if (!IR::TrackResources(ir) && options.non_fatal) {
CompileResult unsupported_result;
unsupported_result.unsupported = true;
return unsupported_result;
}
IR::EliminateDeadCode(ir.blocks);
if (options.stage == ShaderType::Vertex) {
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
@@ -26,6 +26,7 @@ struct CompileOptions {
IR::SrtMemoryReader read_specialization_memory = nullptr;
void* read_memory_data = nullptr;
ShaderStageInputInfo input_info;
bool non_fatal = false;
};
struct CompileResult {
@@ -34,6 +35,7 @@ struct CompileResult {
std::string ir_dump;
IR::Program program;
IR::ResourceSnapshot resources;
bool unsupported = false;
};
[[nodiscard]] CompileResult Recompile(std::span<const uint32_t> code,
@@ -3,16 +3,23 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
current_opcode = inst.opcode;
current_pc = inst.pc;
if (TranslationUnsupportedFlag()) {
return;
}
switch (inst.opcode) {
case Decoder::Opcode::UNKNOWN:
case Decoder::Opcode::COUNT:
EXIT("decoded opcode has no IR translation at pc 0x%08x", inst.pc);
case Decoder::Opcode::UNSUPPORTED:
EXIT("unsupported decoded instruction: %s", Decoder::InstructionToString(inst).c_str());
if (TranslationNonFatalFlag()) {
TranslationUnsupportedFlag() = true;
return;
}
EXIT("decoded opcode has no IR translation at pc 0x%08x", inst.pc);
default: break;
}
@@ -43,6 +50,10 @@ void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
}
if (!translated) {
if (TranslationNonFatalFlag()) {
TranslationUnsupportedFlag() = true;
return;
}
EXIT("opcode %s at pc 0x%08x has no IR translation",
Decoder::InstructionToString(inst).c_str(), inst.pc);
}
@@ -231,6 +231,7 @@ private:
bool V_CNDMASK_B32(const Decoder::Instruction& inst);
bool PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
bool S_CMP_MASK_ANY(const Decoder::Instruction& inst, bool not_equal);
void S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec,
bool negate_source, bool write_64);
void ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in);
@@ -276,4 +277,27 @@ private:
uint32_t current_wave_size = 64;
};
inline bool& TranslationNonFatalFlag() {
static thread_local bool value = false;
return value;
}
inline bool& TranslationUnsupportedFlag() {
static thread_local bool value = false;
return value;
}
inline void SetTranslationNonFatal(bool enabled) {
TranslationNonFatalFlag() = enabled;
TranslationUnsupportedFlag() = false;
}
inline bool TranslationUnsupported() {
return TranslationUnsupportedFlag();
}
inline bool TranslationNonFatal() {
return TranslationNonFatalFlag();
}
} // namespace Libs::Graphics::ShaderRecompiler::Frontend