[shader_recompiler] use reusable stable_vector<> instead of object pools

Signed-off-by: lizzie <lizzie@eden-emu.dev>
This commit is contained in:
lizzie 2026-03-23 20:37:56 +00:00
parent 772e38cb8d
commit 4fe8b533d8
17 changed files with 313 additions and 491 deletions

View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
@ -234,13 +234,12 @@ add_library(shader_recompiler STATIC
ir_opt/texture_pass.cpp
ir_opt/vendor_workaround_pass.cpp
ir_opt/verification_pass.cpp
object_pool.h
profile.h
program_header.h
runtime_info.h
shader_info.h
varying_state.h
shader_pool.h
)
target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit::sirit SPIRV-Tools::SPIRV-Tools)

View file

@ -14,7 +14,7 @@
namespace Shader::IR {
Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
Block::Block(boost::container::stable_vector<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
Block::~Block() = default;
@ -23,13 +23,12 @@ void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
}
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
Inst* const inst{inst_pool->Create(base_inst)};
Inst* const inst{&inst_pool->emplace_back(base_inst)};
return instructions.insert(insertion_point, *inst);
}
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op,
std::initializer_list<Value> args, u32 flags) {
Inst* const inst{inst_pool->Create(op, flags)};
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args, u32 flags) {
Inst* const inst{&inst_pool->emplace_back(op, flags)};
const auto result_it{instructions.insert(insertion_point, *inst)};
if (inst->NumArgs() != args.size()) {
@ -53,12 +52,10 @@ void Block::AddBranch(Block* block) {
block->imm_predecessors.push_back(this);
}
static std::string BlockToIndex(const std::map<const Block*, size_t>& block_to_index,
Block* block) {
if (const auto it{block_to_index.find(block)}; it != block_to_index.end()) {
static std::string BlockToIndex(const std::map<const Block*, size_t>& block_to_index, Block* block) {
if (const auto it{block_to_index.find(block)}; it != block_to_index.end())
return fmt::format("{{Block ${}}}", it->second);
}
return fmt::format("$<unknown block {:016x}>", reinterpret_cast<u64>(block));
return fmt::format("$<unknown block {:016x}>", u64(block));
}
static size_t InstIndex(std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@ -13,11 +13,11 @@
#include <bit>
#include <numeric>
#include <boost/intrusive/list.hpp>
#include <boost/container/stable_vector.hpp>
#include "common/common_types.h"
#include "shader_recompiler/frontend/ir/condition.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/object_pool.h"
namespace Shader::IR {
@ -30,7 +30,7 @@ public:
using reverse_iterator = InstructionList::reverse_iterator;
using const_reverse_iterator = InstructionList::const_reverse_iterator;
explicit Block(ObjectPool<Inst>& inst_pool_);
explicit Block(boost::container::stable_vector<Inst>& inst_pool_);
~Block();
Block(const Block&) = delete;
@ -170,7 +170,7 @@ public:
private:
/// Memory pool for instruction list
ObjectPool<Inst>* inst_pool;
boost::container::stable_vector<Inst>* inst_pool;
/// List of instructions in this block
InstructionList instructions;

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@ -174,11 +174,10 @@ bool Block::Contains(Location pc) const noexcept {
return pc >= begin && pc < end;
}
Function::Function(ObjectPool<Block>& block_pool, Location start_address)
: entrypoint{start_address} {
Function::Function(boost::container::stable_vector<Block>& block_pool, Location start_address) : entrypoint{start_address} {
Label& label{labels.emplace_back()};
label.address = start_address;
label.block = block_pool.Create(Block{});
label.block = &block_pool.emplace_back(Block{});
label.block->begin = start_address;
label.block->end = start_address;
label.block->end_class = EndClass::Branch;
@ -187,12 +186,13 @@ Function::Function(ObjectPool<Block>& block_pool, Location start_address)
label.block->branch_false = nullptr;
}
CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address,
bool exits_to_dispatcher_)
: env{env_}, block_pool{block_pool_}, program_start{start_address}, exits_to_dispatcher{
exits_to_dispatcher_} {
CFG::CFG(Environment& env_, boost::container::stable_vector<Block>& block_pool_, Location start_address, bool exits_to_dispatcher_)
: env{env_}
, block_pool{block_pool_}
, program_start{start_address}
, exits_to_dispatcher{exits_to_dispatcher_} {
if (exits_to_dispatcher) {
dispatch_block = block_pool.Create(Block{});
dispatch_block = &block_pool.emplace_back(Block{});
dispatch_block->begin = {};
dispatch_block->end = {};
dispatch_block->end_class = EndClass::Exit;
@ -371,7 +371,7 @@ void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc,
return;
}
// Create a virtual block and a conditional block
Block* const conditional_block{block_pool.Create()};
Block* const conditional_block{&block_pool.emplace_back()};
Block virtual_block{};
virtual_block.begin = block->begin.Virtual();
virtual_block.end = block->begin.Virtual();
@ -546,7 +546,7 @@ Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function
if (label_it != function.labels.end()) {
return label_it->block;
}
Block* const new_block{block_pool.Create()};
Block* const new_block{&block_pool.emplace_back()};
new_block->begin = pc;
new_block->end = pc;
new_block->end_class = EndClass::Branch;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -9,6 +12,7 @@
#include <vector>
#include <boost/container/small_vector.hpp>
#include <boost/container/stable_vector.hpp>
#include <boost/intrusive/set.hpp>
#include "shader_recompiler/environment.h"
@ -17,7 +21,6 @@
#include "shader_recompiler/frontend/maxwell/instruction.h"
#include "shader_recompiler/frontend/maxwell/location.h"
#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/object_pool.h"
namespace Shader::Maxwell::Flow {
@ -96,7 +99,7 @@ struct Label {
};
struct Function {
explicit Function(ObjectPool<Block>& block_pool, Location start_address);
explicit Function(boost::container::stable_vector<Block>& block_pool, Location start_address);
Location entrypoint;
boost::container::small_vector<Label, 16> labels;
@ -110,8 +113,7 @@ class CFG {
};
public:
explicit CFG(Environment& env, ObjectPool<Block>& block_pool, Location start_address,
bool exits_to_dispatcher = false);
explicit CFG(Environment& env, boost::container::stable_vector<Block>& block_pool, Location start_address, bool exits_to_dispatcher = false);
CFG& operator=(const CFG&) = delete;
CFG(const CFG&) = delete;
@ -138,27 +140,20 @@ private:
/// Inspect already visited blocks.
/// Return true when the block has already been visited
bool InspectVisitedBlocks(FunctionId function_id, const Label& label);
AnalysisState AnalyzeInst(Block* block, FunctionId function_id, Location pc);
void AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, EndClass insn_end_class,
IR::Condition cond);
void AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, EndClass insn_end_class, IR::Condition cond);
/// Return true when the branch instruction is confirmed to be a branch
bool AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst,
Opcode opcode);
void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst,
bool is_absolute);
AnalysisState AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute,
FunctionId function_id);
bool AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst, Opcode opcode);
void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst, bool is_absolute);
AnalysisState AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute, FunctionId function_id);
AnalysisState AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, Instruction inst);
/// Return the branch target block id
Block* AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id);
Environment& env;
ObjectPool<Block>& block_pool;
boost::container::stable_vector<Block>& block_pool;
boost::container::small_vector<Function, 1> functions;
Location program_start;
bool exits_to_dispatcher{};

View file

@ -13,143 +13,19 @@
#include <fmt/ranges.h>
#include <boost/intrusive/list.hpp>
#include <ranges>
#include "shader_recompiler/shader_pool.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/object_pool.h"
namespace Shader::Maxwell {
namespace {
struct Statement;
// Use normal_link because we are not guaranteed to destroy the tree in order
using ListBaseHook =
boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
using Tree = boost::intrusive::list<Statement,
// Allow using Statement without a definition
boost::intrusive::base_hook<ListBaseHook>,
// Avoid linear complexity on splice, size is never called
boost::intrusive::constant_time_size<false>>;
using Node = Tree::iterator;
enum class StatementType {
Code,
Goto,
Label,
If,
Loop,
Break,
Return,
Kill,
Unreachable,
Function,
Identity,
Not,
Or,
SetVariable,
SetIndirectBranchVariable,
Variable,
IndirectBranchCond,
};
bool HasChildren(StatementType type) {
switch (type) {
case StatementType::If:
case StatementType::Loop:
case StatementType::Function:
return true;
default:
return false;
}
}
struct Goto {};
struct Label {};
struct If {};
struct Loop {};
struct Break {};
struct Return {};
struct Kill {};
struct Unreachable {};
struct FunctionTag {};
struct Identity {};
struct Not {};
struct Or {};
struct SetVariable {};
struct SetIndirectBranchVariable {};
struct Variable {};
struct IndirectBranchCond {};
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 26495) // Always initialize a member variable, expected in Statement
#endif
struct Statement : ListBaseHook {
Statement(const Flow::Block* block_, Statement* up_)
: block{block_}, up{up_}, type{StatementType::Code} {}
Statement(Goto, Statement* cond_, Node label_, Statement* up_)
: label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
Statement(If, Statement* cond_, Tree&& children_, Statement* up_)
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_)
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
Statement(Break, Statement* cond_, Statement* up_)
: cond{cond_}, up{up_}, type{StatementType::Break} {}
Statement(Return, Statement* up_) : up{up_}, type{StatementType::Return} {}
Statement(Kill, Statement* up_) : up{up_}, type{StatementType::Kill} {}
Statement(Unreachable, Statement* up_) : up{up_}, type{StatementType::Unreachable} {}
Statement(FunctionTag) : children{}, type{StatementType::Function} {}
Statement(Identity, IR::Condition cond_, Statement* up_)
: guest_cond{cond_}, up{up_}, type{StatementType::Identity} {}
Statement(Not, Statement* op_, Statement* up_) : op{op_}, up{up_}, type{StatementType::Not} {}
Statement(Or, Statement* op_a_, Statement* op_b_, Statement* up_)
: op_a{op_a_}, op_b{op_b_}, up{up_}, type{StatementType::Or} {}
Statement(SetVariable, u32 id_, Statement* op_, Statement* up_)
: op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
Statement(SetIndirectBranchVariable, IR::Reg branch_reg_, s32 branch_offset_, Statement* up_)
: branch_offset{branch_offset_},
branch_reg{branch_reg_}, up{up_}, type{StatementType::SetIndirectBranchVariable} {}
Statement(Variable, u32 id_, Statement* up_)
: id{id_}, up{up_}, type{StatementType::Variable} {}
Statement(IndirectBranchCond, u32 location_, Statement* up_)
: location{location_}, up{up_}, type{StatementType::IndirectBranchCond} {}
~Statement() {
if (HasChildren(type)) {
std::destroy_at(&children);
}
}
union {
const Flow::Block* block;
Node label;
Tree children;
IR::Condition guest_cond;
Statement* op;
Statement* op_a;
u32 location;
s32 branch_offset;
};
union {
Statement* cond;
Statement* op_b;
u32 id;
IR::Reg branch_reg;
};
Statement* up{};
StatementType type;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
std::string DumpExpr(const Statement* stmt) {
switch (stmt->type) {
@ -314,7 +190,9 @@ bool NeedsLift(Node goto_stmt, Node label_stmt) noexcept {
class GotoPass {
public:
explicit GotoPass(Flow::CFG& cfg, ObjectPool<Statement>& stmt_pool) : pool{stmt_pool} {
explicit GotoPass(Flow::CFG& cfg, ShaderPools& pools_)
: pools{pools_}
{
std::vector gotos{BuildTree(cfg)};
const auto end{gotos.rend()};
for (auto goto_stmt = gotos.rbegin(); goto_stmt != end; ++goto_stmt) {
@ -385,16 +263,14 @@ private:
return gotos;
}
void BuildTree(Flow::CFG& cfg, Flow::Function& function, u32& label_id,
std::vector<Node>& gotos, Node function_insert_point,
std::optional<Node> return_label) {
Statement* const false_stmt{pool.Create(Identity{}, IR::Condition{false}, &root_stmt)};
void BuildTree(Flow::CFG& cfg, Flow::Function& function, u32& label_id, std::vector<Node>& gotos, Node function_insert_point, std::optional<Node> return_label) {
Statement* const false_stmt{&pools.stmt.emplace_back(Identity{}, IR::Condition{false}, &root_stmt)};
Tree& root{root_stmt.children};
ankerl::unordered_dense::map<Flow::Block*, Node> local_labels;
local_labels.reserve(function.blocks.size());
for (Flow::Block& block : function.blocks) {
Statement* const label{pool.Create(Label{}, label_id, &root_stmt)};
Statement* const label{&pools.stmt.emplace_back(Label{}, label_id, &root_stmt)};
const Node label_it{root.insert(function_insert_point, *label)};
local_labels.emplace(&block, label_it);
++label_id;
@ -406,46 +282,39 @@ private:
// Reset goto variables before the first block and after its respective label
const auto make_reset_variable{[&]() -> Statement& {
return *pool.Create(SetVariable{}, label->id, false_stmt, &root_stmt);
return pools.stmt.emplace_back(SetVariable{}, label->id, false_stmt, &root_stmt);
}};
root.push_front(make_reset_variable());
root.insert(ip, make_reset_variable());
root.insert(ip, *pool.Create(&block, &root_stmt));
root.insert(ip, pools.stmt.emplace_back(&block, &root_stmt));
switch (block.end_class) {
case Flow::EndClass::Branch: {
Statement* const always_cond{
pool.Create(Identity{}, IR::Condition{true}, &root_stmt)};
Statement* const always_cond{&pools.stmt.emplace_back(Identity{}, IR::Condition{true}, &root_stmt)};
if (block.cond == IR::Condition{true}) {
const Node true_label{local_labels.at(block.branch_true)};
gotos.push_back(
root.insert(ip, *pool.Create(Goto{}, always_cond, true_label, &root_stmt)));
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, true_label, &root_stmt)));
} else if (block.cond == IR::Condition{false}) {
const Node false_label{local_labels.at(block.branch_false)};
gotos.push_back(root.insert(
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, false_label, &root_stmt)));
} else {
const Node true_label{local_labels.at(block.branch_true)};
const Node false_label{local_labels.at(block.branch_false)};
Statement* const true_cond{pool.Create(Identity{}, block.cond, &root_stmt)};
gotos.push_back(
root.insert(ip, *pool.Create(Goto{}, true_cond, true_label, &root_stmt)));
gotos.push_back(root.insert(
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
Statement* const true_cond{&pools.stmt.emplace_back(Identity{}, block.cond, &root_stmt)};
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, true_cond, true_label, &root_stmt)));
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, false_label, &root_stmt)));
}
break;
}
case Flow::EndClass::IndirectBranch:
root.insert(ip, *pool.Create(SetIndirectBranchVariable{}, block.branch_reg,
block.branch_offset, &root_stmt));
root.insert(ip, pools.stmt.emplace_back(SetIndirectBranchVariable{}, block.branch_reg, block.branch_offset, &root_stmt));
for (const Flow::IndirectBranch& indirect : block.indirect_branches) {
const Node indirect_label{local_labels.at(indirect.block)};
Statement* cond{
pool.Create(IndirectBranchCond{}, indirect.address, &root_stmt)};
Statement* goto_stmt{pool.Create(Goto{}, cond, indirect_label, &root_stmt)};
Statement* cond{&pools.stmt.emplace_back(IndirectBranchCond{}, indirect.address, &root_stmt)};
Statement* goto_stmt{&pools.stmt.emplace_back(Goto{}, cond, indirect_label, &root_stmt)};
gotos.push_back(root.insert(ip, *goto_stmt));
}
root.insert(ip, *pool.Create(Unreachable{}, &root_stmt));
root.insert(ip, pools.stmt.emplace_back(Unreachable{}, &root_stmt));
break;
case Flow::EndClass::Call: {
Flow::Function& call{cfg.Functions()[block.function_call]};
@ -454,16 +323,16 @@ private:
break;
}
case Flow::EndClass::Exit:
root.insert(ip, *pool.Create(Return{}, &root_stmt));
root.insert(ip, pools.stmt.emplace_back(Return{}, &root_stmt));
break;
case Flow::EndClass::Return: {
Statement* const always_cond{pool.Create(Identity{}, block.cond, &root_stmt)};
auto goto_stmt{pool.Create(Goto{}, always_cond, return_label.value(), &root_stmt)};
Statement* const always_cond{&pools.stmt.emplace_back(Identity{}, block.cond, &root_stmt)};
auto goto_stmt{&pools.stmt.emplace_back(Goto{}, always_cond, return_label.value(), &root_stmt)};
gotos.push_back(root.insert(ip, *goto_stmt));
break;
}
case Flow::EndClass::Kill:
root.insert(ip, *pool.Create(Kill{}, &root_stmt));
root.insert(ip, pools.stmt.emplace_back(Kill{}, &root_stmt));
break;
}
}
@ -479,8 +348,8 @@ private:
Tree& body{goto_stmt->up->children};
Tree if_body;
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_stmt);
Statement* const cond{pool.Create(Not{}, goto_stmt->cond, &root_stmt)};
Statement* const if_stmt{pool.Create(If{}, cond, std::move(if_body), goto_stmt->up)};
Statement* const cond{&pools.stmt.emplace_back(Not{}, goto_stmt->cond, &root_stmt)};
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, cond, std::move(if_body), goto_stmt->up)};
UpdateTreeUp(if_stmt);
body.insert(goto_stmt, *if_stmt);
body.erase(goto_stmt);
@ -491,7 +360,7 @@ private:
Tree loop_body;
loop_body.splice(loop_body.begin(), body, label_stmt, goto_stmt);
Statement* const cond{goto_stmt->cond};
Statement* const loop{pool.Create(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
Statement* const loop{&pools.stmt.emplace_back(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
UpdateTreeUp(loop);
body.insert(goto_stmt, *loop);
body.erase(goto_stmt);
@ -516,15 +385,15 @@ private:
const u32 label_id{label->id};
Statement* const goto_cond{goto_stmt->cond};
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
Statement* const set_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, parent)};
body.insert(goto_stmt, *set_var);
Tree if_body;
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_nested_stmt);
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const neg_var{pool.Create(Not{}, variable, &root_stmt)};
Statement* const variable{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const neg_var{&pools.stmt.emplace_back(Not{}, variable, &root_stmt)};
if (!if_body.empty()) {
Statement* const if_stmt{pool.Create(If{}, neg_var, std::move(if_body), parent)};
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, neg_var, std::move(if_body), parent)};
UpdateTreeUp(if_stmt);
body.insert(goto_stmt, *if_stmt);
}
@ -533,8 +402,7 @@ private:
switch (label_nested_stmt->type) {
case StatementType::If:
// Update nested if condition
label_nested_stmt->cond =
pool.Create(Or{}, variable, label_nested_stmt->cond, &root_stmt);
label_nested_stmt->cond = &pools.stmt.emplace_back(Or{}, variable, label_nested_stmt->cond, &root_stmt);
break;
case StatementType::Loop:
break;
@ -542,7 +410,7 @@ private:
throw LogicError("Invalid inward movement");
}
Tree& nested_tree{label_nested_stmt->children};
Statement* const new_goto{pool.Create(Goto{}, variable, label, &*label_nested_stmt)};
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, variable, label, &*label_nested_stmt)};
return nested_tree.insert(nested_tree.begin(), *new_goto);
}
@ -556,16 +424,16 @@ private:
Tree loop_body;
loop_body.splice(loop_body.begin(), body, label_nested_stmt, goto_stmt);
SanitizeNoBreaks(loop_body);
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const loop_stmt{pool.Create(Loop{}, variable, std::move(loop_body), parent)};
Statement* const variable{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const loop_stmt{&pools.stmt.emplace_back(Loop{}, variable, std::move(loop_body), parent)};
UpdateTreeUp(loop_stmt);
body.insert(goto_stmt, *loop_stmt);
Statement* const new_goto{pool.Create(Goto{}, variable, label, loop_stmt)};
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, variable, label, loop_stmt)};
loop_stmt->children.push_front(*new_goto);
const Node new_goto_node{loop_stmt->children.begin()};
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
Statement* const set_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
loop_stmt->children.push_back(*set_var);
body.erase(goto_stmt);
@ -577,22 +445,22 @@ private:
Tree& body{parent->children};
const u32 label_id{goto_stmt->label->id};
Statement* const goto_cond{goto_stmt->cond};
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, &*parent)};
Statement* const set_goto_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, &*parent)};
body.insert(goto_stmt, *set_goto_var);
Tree if_body;
if_body.splice(if_body.begin(), body, std::next(goto_stmt), body.end());
if_body.pop_front();
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const neg_cond{pool.Create(Not{}, cond, &root_stmt)};
Statement* const if_stmt{pool.Create(If{}, neg_cond, std::move(if_body), &*parent)};
Statement* const cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const neg_cond{&pools.stmt.emplace_back(Not{}, cond, &root_stmt)};
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, neg_cond, std::move(if_body), &*parent)};
UpdateTreeUp(if_stmt);
body.insert(goto_stmt, *if_stmt);
body.erase(goto_stmt);
Statement* const new_cond{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const new_goto{pool.Create(Goto{}, new_cond, goto_stmt->label, parent->up)};
Statement* const new_cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, new_cond, goto_stmt->label, parent->up)};
Tree& parent_tree{parent->up->children};
return parent_tree.insert(std::next(parent), *new_goto);
}
@ -602,21 +470,21 @@ private:
Tree& body{parent->children};
const u32 label_id{goto_stmt->label->id};
Statement* const goto_cond{goto_stmt->cond};
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const break_stmt{pool.Create(Break{}, cond, parent)};
Statement* const set_goto_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, parent)};
Statement* const cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const break_stmt{&pools.stmt.emplace_back(Break{}, cond, parent)};
body.insert(goto_stmt, *set_goto_var);
body.insert(goto_stmt, *break_stmt);
body.erase(goto_stmt);
const Node loop{Tree::s_iterator_to(*goto_stmt->up)};
Statement* const new_goto_cond{pool.Create(Variable{}, label_id, &root_stmt)};
Statement* const new_goto{pool.Create(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
Statement* const new_goto_cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
Tree& parent_tree{loop->up->children};
return parent_tree.insert(std::next(loop), *new_goto);
}
ObjectPool<Statement>& pool;
ShaderPools& pools;
Statement root_stmt{FunctionTag{}};
};
@ -652,11 +520,11 @@ private:
class TranslatePass {
public:
TranslatePass(ObjectPool<IR::Inst>& inst_pool_, ObjectPool<IR::Block>& block_pool_,
ObjectPool<Statement>& stmt_pool_, Environment& env_, Statement& root_stmt,
IR::AbstractSyntaxList& syntax_list_, const HostTranslateInfo& host_info)
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, env{env_},
syntax_list{syntax_list_} {
TranslatePass(ShaderPools& pools_, Environment& env_, Statement& root_stmt, IR::AbstractSyntaxList& syntax_list_, const HostTranslateInfo& host_info)
: pools{pools_}
, env{env_}
, syntax_list{syntax_list_}
{
Visit(root_stmt, nullptr, nullptr);
IR::Block& first_block{*syntax_list.front().data.block};
@ -674,7 +542,7 @@ private:
if (current_block) {
return;
}
current_block = block_pool.Create(inst_pool);
current_block = &pools.block.emplace_back(pools.inst);
auto& node{syntax_list.emplace_back()};
node.type = IR::AbstractSyntaxNode::Type::Block;
node.data.block = current_block;
@ -740,7 +608,7 @@ private:
break;
}
case StatementType::Loop: {
IR::Block* const loop_header_block{block_pool.Create(inst_pool)};
IR::Block* const loop_header_block{&pools.block.emplace_back(pools.inst)};
if (current_block) {
current_block->AddBranch(loop_header_block);
}
@ -748,7 +616,7 @@ private:
header_node.type = IR::AbstractSyntaxNode::Type::Block;
header_node.data.block = loop_header_block;
IR::Block* const continue_block{block_pool.Create(inst_pool)};
IR::Block* const continue_block{&pools.block.emplace_back(pools.inst)};
IR::Block* const merge_block{MergeBlock(parent, stmt)};
const size_t loop_node_index{syntax_list.size()};
@ -814,7 +682,7 @@ private:
}
case StatementType::Return: {
ensure_block();
IR::Block* return_block{block_pool.Create(inst_pool)};
IR::Block* return_block{&pools.block.emplace_back(pools.inst)};
IR::IREmitter{*return_block}.Epilogue();
current_block->AddBranch(return_block);
@ -862,10 +730,10 @@ private:
Statement* merge_stmt{TryFindForwardBlock(stmt)};
if (!merge_stmt) {
// Create a merge block we can visit later
merge_stmt = stmt_pool.Create(&dummy_flow_block, &parent);
merge_stmt = &pools.stmt.emplace_back(&dummy_flow_block, &parent);
parent.children.insert(std::next(Tree::s_iterator_to(stmt)), *merge_stmt);
}
return block_pool.Create(inst_pool);
return &pools.block.emplace_back(pools.inst);
}
void DemoteCombinationPass() {
@ -973,9 +841,7 @@ private:
asl.insert(next_it_2, demote_if_node);
}
ObjectPool<Statement>& stmt_pool;
ObjectPool<IR::Inst>& inst_pool;
ObjectPool<IR::Block>& block_pool;
ShaderPools& pools;
Environment& env;
IR::AbstractSyntaxList& syntax_list;
bool uses_demote_to_helper{};
@ -983,15 +849,12 @@ private:
};
} // Anonymous namespace
IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
Environment& env, Flow::CFG& cfg,
const HostTranslateInfo& host_info) {
ObjectPool<Statement> stmt_pool{64};
GotoPass goto_pass{cfg, stmt_pool};
IR::AbstractSyntaxList BuildASL(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
GotoPass goto_pass{cfg, pools};
Statement& root{goto_pass.RootStatement()};
IR::AbstractSyntaxList syntax_list;
TranslatePass{inst_pool, block_pool, stmt_pool, env, root, syntax_list, host_info};
stmt_pool.ReleaseContents();
TranslatePass pass{pools, env, root, syntax_list, host_info};
pools.stmt.clear();
return syntax_list;
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -8,15 +11,13 @@
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/object_pool.h"
namespace Shader {
struct HostTranslateInfo;
namespace Maxwell {
[[nodiscard]] IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool, Environment& env,
Flow::CFG& cfg, const HostTranslateInfo& host_info);
struct ShaderPools;
[[nodiscard]] IR::AbstractSyntaxList BuildASL(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info);
} // namespace Maxwell
} // namespace Shader

View file

@ -10,6 +10,7 @@
#include <queue>
#include "common/settings.h"
#include "shader_recompiler/shader_pool.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
@ -240,10 +241,9 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf
} // Anonymous namespace
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
IR::Program TranslateProgram(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
IR::Program program;
program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, host_info);
program.syntax_list = BuildASL(pools, env, cfg, host_info);
program.blocks = GenerateBlocks(program.syntax_list);
program.post_order_blocks = PostOrder(program.syntax_list.front());
program.stage = env.ShaderStage();
@ -413,11 +413,7 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run
}
}
IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology) {
IR::Program GenerateGeometryPassthrough(ShaderPools& pools, const HostTranslateInfo& host_info, IR::Program& source_program, Shader::OutputTopology output_topology) {
IR::Program program;
program.stage = Stage::Geometry;
program.output_topology = output_topology;
@ -429,16 +425,15 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
program.info.stores.Set(IR::Attribute::Layer, true);
program.info.stores.Set(source_program.info.emulated_layer, false);
IR::Block* current_block = block_pool.Create(inst_pool);
IR::Block* current_block = &pools.block.emplace_back(pools.inst);
auto& node{program.syntax_list.emplace_back()};
node.type = IR::AbstractSyntaxNode::Type::Block;
node.data.block = current_block;
IR::IREmitter ir{*current_block};
EmitGeometryPassthrough(ir, program, program.info.stores, true,
source_program.info.emulated_layer);
EmitGeometryPassthrough(ir, program, program.info.stores, true, source_program.info.emulated_layer);
IR::Block* return_block{block_pool.Create(inst_pool)};
IR::Block* return_block{&pools.block.emplace_back(pools.inst)};
IR::IREmitter{*return_block}.Epilogue();
current_block->AddBranch(return_block);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -7,7 +10,7 @@
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/object_pool.h"
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
#include "shader_recompiler/runtime_info.h"
namespace Shader {
@ -16,22 +19,17 @@ struct HostTranslateInfo;
namespace Shader::Maxwell {
[[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool, Environment& env,
Flow::CFG& cfg, const HostTranslateInfo& host_info);
struct ShaderPools;
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b,
Environment& env_vertex_b);
[[nodiscard]] IR::Program TranslateProgram(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info);
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b, Environment& env_vertex_b);
void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_info);
// Maxwell v1 and older Nvidia cards don't support setting gl_Layer from non-geometry stages.
// This creates a workaround by setting the layer as a generic output and creating a
// passthrough geometry shader that reads the generic and sets the layer.
[[nodiscard]] IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology);
[[nodiscard]] IR::Program GenerateGeometryPassthrough(ShaderPools& pools, const HostTranslateInfo& host_info, IR::Program& source_program, Shader::OutputTopology output_topology);
} // namespace Shader::Maxwell

View file

@ -1,106 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <type_traits>
#include <utility>
namespace Shader {
template <typename T>
requires std::is_destructible_v<T>
class ObjectPool {
public:
explicit ObjectPool(size_t chunk_size = 8192) : new_chunk_size{chunk_size} {
node = &chunks.emplace_back(new_chunk_size);
}
template <typename... Args>
requires std::is_constructible_v<T, Args...>
[[nodiscard]] T* Create(Args&&... args) {
return std::construct_at(Memory(), std::forward<Args>(args)...);
}
void ReleaseContents() {
if (chunks.empty()) {
return;
}
Chunk& root{chunks.front()};
if (root.used_objects == root.num_objects) {
// Root chunk has been filled, squash allocations into it
const size_t total_objects{root.num_objects + new_chunk_size * (chunks.size() - 1)};
chunks.clear();
chunks.emplace_back(total_objects);
} else {
root.Release();
chunks.resize(1);
}
chunks.shrink_to_fit();
node = &chunks.front();
}
private:
struct NonTrivialDummy {
NonTrivialDummy() noexcept {}
};
union Storage {
Storage() noexcept {}
~Storage() noexcept {}
NonTrivialDummy dummy{};
T object;
};
struct Chunk {
explicit Chunk() = default;
explicit Chunk(size_t size)
: num_objects{size}, storage{std::make_unique<Storage[]>(size)} {}
Chunk& operator=(Chunk&& rhs) noexcept {
Release();
used_objects = std::exchange(rhs.used_objects, 0);
num_objects = std::exchange(rhs.num_objects, 0);
storage = std::move(rhs.storage);
return *this;
}
Chunk(Chunk&& rhs) noexcept
: used_objects{std::exchange(rhs.used_objects, 0)},
num_objects{std::exchange(rhs.num_objects, 0)}, storage{std::move(rhs.storage)} {}
~Chunk() {
Release();
}
void Release() {
std::destroy_n(storage.get(), used_objects);
used_objects = 0;
}
size_t used_objects{};
size_t num_objects{};
std::unique_ptr<Storage[]> storage;
};
[[nodiscard]] T* Memory() {
Chunk* const chunk{FreeChunk()};
return &chunk->storage[chunk->used_objects++].object;
}
[[nodiscard]] Chunk* FreeChunk() {
if (node->used_objects != node->num_objects) {
return node;
}
node = &chunks.emplace_back(new_chunk_size);
return node;
}
Chunk* node{};
std::vector<Chunk> chunks;
size_t new_chunk_size{};
};
} // namespace Shader

View file

@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <boost/container/stable_vector.hpp>
#include <boost/intrusive/list.hpp>
#include "shader_recompiler/frontend/maxwell/control_flow.h"
namespace Shader::Maxwell {
struct Statement;
// Use normal_link because we are not guaranteed to destroy the tree in order
using ListBaseHook = boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
using Tree = boost::intrusive::list<Statement,
// Allow using Statement without a definition
boost::intrusive::base_hook<ListBaseHook>,
// Avoid linear complexity on splice, size is never called
boost::intrusive::constant_time_size<false>>;
using Node = Tree::iterator;
enum class StatementType {
Code,
Goto,
Label,
If,
Loop,
Break,
Return,
Kill,
Unreachable,
Function,
Identity,
Not,
Or,
SetVariable,
SetIndirectBranchVariable,
Variable,
IndirectBranchCond,
};
[[nodiscard]] inline bool HasChildren(StatementType type) {
switch (type) {
case StatementType::If:
case StatementType::Loop:
case StatementType::Function:
return true;
default:
return false;
}
}
struct Goto {};
struct Label {};
struct If {};
struct Loop {};
struct Break {};
struct Return {};
struct Kill {};
struct Unreachable {};
struct FunctionTag {};
struct Identity {};
struct Not {};
struct Or {};
struct SetVariable {};
struct SetIndirectBranchVariable {};
struct Variable {};
struct IndirectBranchCond {};
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 26495) // Always initialize a member variable, expected in Statement
#endif
struct Statement : ListBaseHook {
Statement(const Flow::Block* block_, Statement* up_) : block{block_}, up{up_}, type{StatementType::Code} {}
Statement(Goto, Statement* cond_, Node label_, Statement* up_) : label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
Statement(If, Statement* cond_, Tree&& children_, Statement* up_) : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_) : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
Statement(Break, Statement* cond_, Statement* up_) : cond{cond_}, up{up_}, type{StatementType::Break} {}
Statement(Return, Statement* up_) : up{up_}, type{StatementType::Return} {}
Statement(Kill, Statement* up_) : up{up_}, type{StatementType::Kill} {}
Statement(Unreachable, Statement* up_) : up{up_}, type{StatementType::Unreachable} {}
Statement(FunctionTag) : children{}, type{StatementType::Function} {}
Statement(Identity, IR::Condition cond_, Statement* up_) : guest_cond{cond_}, up{up_}, type{StatementType::Identity} {}
Statement(Not, Statement* op_, Statement* up_) : op{op_}, up{up_}, type{StatementType::Not} {}
Statement(Or, Statement* op_a_, Statement* op_b_, Statement* up_) : op_a{op_a_}, op_b{op_b_}, up{up_}, type{StatementType::Or} {}
Statement(SetVariable, u32 id_, Statement* op_, Statement* up_) : op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
Statement(SetIndirectBranchVariable, IR::Reg branch_reg_, s32 branch_offset_, Statement* up_) : branch_offset{branch_offset_}, branch_reg{branch_reg_}, up{up_}, type{StatementType::SetIndirectBranchVariable} {}
Statement(Variable, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Variable} {}
Statement(IndirectBranchCond, u32 location_, Statement* up_) : location{location_}, up{up_}, type{StatementType::IndirectBranchCond} {}
~Statement() {
if (HasChildren(type)) {
std::destroy_at(&children);
}
}
union {
const Flow::Block* block;
Node label;
Tree children;
IR::Condition guest_cond;
Statement* op;
Statement* op_a;
u32 location;
s32 branch_offset;
};
union {
Statement* cond;
Statement* op_b;
u32 id;
IR::Reg branch_reg;
};
Statement* up{};
StatementType type;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
struct ShaderPools {
void clear() {
flow_block.clear();
block.clear();
inst.clear();
stmt.clear();
}
boost::container::stable_vector<Shader::IR::Inst> inst{};
boost::container::stable_vector<Shader::IR::Block> block{};
boost::container::stable_vector<Shader::Maxwell::Flow::Block> flow_block{};
boost::container::stable_vector<Shader::Maxwell::Statement> stmt;
};
}

View file

@ -43,7 +43,6 @@ using Shader::Backend::GLASM::EmitGLASM;
using Shader::Backend::GLSL::EmitGLSL;
using Shader::Backend::SPIRV::EmitSPIRV;
using Shader::Maxwell::ConvertLegacyToGeneric;
using Shader::Maxwell::GenerateGeometryPassthrough;
using Shader::Maxwell::MergeDualVertexPrograms;
using Shader::Maxwell::TranslateProgram;
using VideoCommon::ComputeEnvironment;
@ -298,7 +297,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
ComputePipelineKey key;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
queue_work([this, key, env_ = std::move(env), &state, &callback](Context* ctx) mutable {
ctx->pools.ReleaseContents();
ctx->pools.clear();
auto pipeline{CreateComputePipeline(ctx->pools, key, env_, true)};
std::scoped_lock lock{state.mutex};
if (pipeline) {
@ -319,7 +318,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
for (auto& env : envs_) {
env_ptrs.push_back(&env);
}
ctx->pools.ReleaseContents();
ctx->pools.clear();
auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false, true)};
std::scoped_lock lock{state.mutex};
if (pipeline) {
@ -433,9 +432,8 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
GraphicsEnvironments environments;
GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
main_pools.ReleaseContents();
auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(),
use_asynchronous_shaders)};
main_pools.clear();
auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), use_asynchronous_shaders)};
if (!pipeline || shader_cache_filename.empty()) {
return pipeline;
}
@ -449,10 +447,7 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
return pipeline;
}
std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
std::span<Shader::Environment* const> envs, bool use_shader_workers,
bool force_context_flush) try {
std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineKey& key, std::span<Shader::Environment* const> envs, bool use_shader_workers, bool force_context_flush) try {
auto hash = key.Hash();
LOG_INFO(Render_OpenGL, "0x{:016x}", hash);
size_t env_index{};
@ -465,12 +460,10 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
Shader::IR::Program* layer_source_program{};
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const bool is_emulated_stage = layer_source_program != nullptr
&& index == u32(Maxwell::ShaderType::Geometry);
const bool is_emulated_stage = layer_source_program != nullptr && index == u32(Maxwell::ShaderType::Geometry);
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
auto topology = MaxwellToOutputTopology(key.gs_input_topology);
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology);
programs[index] = Shader::Maxwell::GenerateGeometryPassthrough(pools, host_info, *layer_source_program, topology);
continue;
}
if (key.unique_hashes[index] == 0) {
@ -488,13 +481,13 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
programs[index] = TranslateProgram(pools, env, cfg, host_info);
total_storage_buffers += Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors);
} else {
// VertexB path when VertexA is present.
auto& program_va{programs[0]};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program_vb{TranslateProgram(pools, env, cfg, host_info)};
total_storage_buffers += Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors);
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
@ -561,7 +554,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
env.SetCachedSize(shader->size_bytes);
main_pools.ReleaseContents();
main_pools.clear();
auto pipeline{CreateComputePipeline(main_pools, key, env)};
if (!pipeline || shader_cache_filename.empty()) {
return pipeline;
@ -571,9 +564,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
return pipeline;
}
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env,
bool force_context_flush) try {
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, bool force_context_flush) try {
auto hash = key.Hash();
LOG_INFO(Render_OpenGL, "0x{:016x}", hash);
@ -583,7 +574,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program{TranslateProgram(pools, env, cfg, host_info)};
const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)};
Shader::RuntimeInfo info;
info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();

View file

@ -12,6 +12,7 @@
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/shader_pool.h"
#include "shader_recompiler/profile.h"
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
#include "video_core/renderer_opengl/gl_graphics_pipeline.h"
@ -51,20 +52,9 @@ private:
[[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
std::span<Shader::Environment* const> envs, bool use_shader_workers,
bool force_context_flush = false);
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key,
const VideoCommon::ShaderInfo* shader);
std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderContext::ShaderPools& pools,
const ComputePipelineKey& key,
Shader::Environment& env,
bool force_context_flush = false);
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineKey& key, std::span<Shader::Environment* const> envs, bool use_shader_workers, bool force_context_flush = false);
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader);
std::unique_ptr<ComputePipeline> CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, bool force_context_flush = false);
std::unique_ptr<ShaderWorker> CreateWorkers() const;
Core::Frontend::EmuWindow& emu_window;
@ -81,7 +71,7 @@ private:
GraphicsPipelineKey graphics_key{};
GraphicsPipeline* current_pipeline{};
ShaderContext::ShaderPools main_pools;
Shader::Maxwell::ShaderPools main_pools;
ankerl::unordered_dense::map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
ankerl::unordered_dense::map<ComputePipelineKey, std::unique_ptr<ComputePipeline>> compute_cache;

View file

@ -1,33 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <boost/container/stable_vector.hpp>
#include "core/frontend/emu_window.h"
#include "core/frontend/graphics_context.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
namespace OpenGL::ShaderContext {
struct ShaderPools {
void ReleaseContents() {
flow_block.ReleaseContents();
block.ReleaseContents();
inst.ReleaseContents();
}
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
Shader::ObjectPool<Shader::IR::Block> block{32};
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
};
struct Context {
explicit Context(Core::Frontend::EmuWindow& emu_window)
: gl_context{emu_window.CreateSharedContext()}, scoped{*gl_context} {}
explicit Context(Core::Frontend::EmuWindow& emu_window) : gl_context{emu_window.CreateSharedContext()}, scoped{*gl_context} {}
std::unique_ptr<Core::Frontend::GraphicsContext> gl_context;
Core::Frontend::GraphicsContext::Scoped scoped;
ShaderPools pools;
Shader::Maxwell::ShaderPools pools;
};
} // namespace OpenGL::ShaderContext

View file

@ -48,15 +48,6 @@
namespace Vulkan {
namespace {
using Shader::Backend::SPIRV::EmitSPIRV;
using Shader::Maxwell::ConvertLegacyToGeneric;
using Shader::Maxwell::GenerateGeometryPassthrough;
using Shader::Maxwell::MergeDualVertexPrograms;
using Shader::Maxwell::TranslateProgram;
using VideoCommon::ComputeEnvironment;
using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
constexpr u32 CACHE_VERSION = 16;
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
@ -568,12 +559,12 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
if (device.IsKhrPipelineExecutablePropertiesEnabled()) {
state.statistics = std::make_unique<PipelineStatistics>(device);
}
const auto load_compute{[&](std::ifstream& file, FileEnvironment env) {
const auto load_compute{[&](std::ifstream& file, VideoCommon::FileEnvironment env) {
ComputePipelineCacheKey key;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
workers.QueueWork([this, key, env_ = std::move(env), &state, &callback]() mutable {
ShaderPools pools;
Shader::Maxwell::ShaderPools pools;
auto pipeline{CreateComputePipeline(pools, key, env_, state.statistics.get(), false)};
std::scoped_lock lock{state.mutex};
if (pipeline) {
@ -586,7 +577,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
});
++state.total;
}};
const auto load_graphics{[&](std::ifstream& file, std::vector<FileEnvironment> envs) {
const auto load_graphics{[&](std::ifstream& file, std::vector<VideoCommon::FileEnvironment> envs) {
GraphicsPipelineCacheKey key;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
@ -604,7 +595,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
return;
}
workers.QueueWork([this, key, envs_ = std::move(envs), &state, &callback]() mutable {
ShaderPools pools;
Shader::Maxwell::ShaderPools pools;
boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
for (auto& env : envs_) {
env_ptrs.push_back(&env);
@ -682,10 +673,7 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const
return nullptr;
}
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
bool build_in_parallel) try {
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineCacheKey& key, std::span<Shader::Environment* const> envs, PipelineStatistics* statistics, bool build_in_parallel) try {
auto hash = key.Hash();
LOG_INFO(Render_Vulkan, "0x{:016x}", hash);
size_t env_index{0};
@ -697,12 +685,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
Shader::IR::Program* layer_source_program{};
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const bool is_emulated_stage = layer_source_program != nullptr &&
index == static_cast<u32>(Maxwell::ShaderType::Geometry);
const bool is_emulated_stage = layer_source_program != nullptr && index == u32(Maxwell::ShaderType::Geometry);
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
auto topology = MaxwellToOutputTopology(key.state.topology);
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology);
programs[index] = Shader::Maxwell::GenerateGeometryPassthrough(pools, host_info, *layer_source_program, topology);
continue;
}
if (key.unique_hashes[index] == 0) {
@ -711,16 +697,16 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
Shader::Environment& env{*envs[env_index]};
++env_index;
const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
const u32 cfg_offset{u32(env.StartAddress() + sizeof(Shader::ProgramHeader))};
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
programs[index] = Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info);
} else {
// VertexB path when VertexA is present.
auto& program_va{programs[0]};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
auto program_vb{Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info)};
programs[index] = Shader::Maxwell::MergeDualVertexPrograms(program_va, program_vb, env);
}
if (Settings::values.dump_shaders) {
@ -750,8 +736,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
infos[stage_index] = &program.info;
const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage, device)};
ConvertLegacyToGeneric(program, runtime_info);
const std::vector<u32> code{EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)};
Shader::Maxwell::ConvertLegacyToGeneric(program, runtime_info);
const std::vector<u32> code{Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)};
device.SaveShader(code);
modules[stage_index] = BuildShader(device, code);
@ -799,14 +785,14 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
GraphicsEnvironments environments;
GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
main_pools.ReleaseContents();
main_pools.clear();
auto pipeline{
CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), nullptr, true)};
if (!pipeline || pipeline_cache_filename.empty()) {
return pipeline;
}
serialization_thread.QueueWork([this, key = graphics_key, envs = std::move(environments.envs)] {
boost::container::static_vector<const GenericEnvironment*, Maxwell::MaxShaderProgram>
boost::container::static_vector<const VideoCommon::GenericEnvironment*, Maxwell::MaxShaderProgram>
env_ptrs;
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
if (key.unique_hashes[index] != 0) {
@ -822,24 +808,22 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
const ComputePipelineCacheKey& key, const ShaderInfo* shader) {
const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
const auto& qmd{kepler_compute->launch_description};
ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
VideoCommon::ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
env.SetCachedSize(shader->size_bytes);
main_pools.ReleaseContents();
main_pools.clear();
auto pipeline{CreateComputePipeline(main_pools, key, env, nullptr, true)};
if (!pipeline || pipeline_cache_filename.empty()) {
return pipeline;
}
serialization_thread.QueueWork([this, key, env_ = std::move(env)] {
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
SerializePipeline(key, std::array<const VideoCommon::GenericEnvironment*, 1>{&env_},
pipeline_cache_filename, CACHE_VERSION);
});
return pipeline;
}
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
PipelineStatistics* statistics, bool build_in_parallel) try {
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, PipelineStatistics* statistics, bool build_in_parallel) try {
auto hash = key.Hash();
if (device.HasBrokenCompute()) {
LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", hash);
@ -855,11 +839,9 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program{Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info)};
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
const bool needs_shared_mem_clamp = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
LOG_WARNING(Render_Vulkan,
@ -869,7 +851,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
max_shared_memory / 1024);
program.shared_memory_size = max_shared_memory;
}
const std::vector<u32> code{EmitSPIRV(profile, program, this->optimize_spirv_output)};
const std::vector<u32> code{Shader::Backend::SPIRV::EmitSPIRV(profile, program, this->optimize_spirv_output)};
device.SaveShader(code);
vk::ShaderModule spv_module{BuildShader(device, code)};

View file

@ -11,17 +11,20 @@
#include <filesystem>
#include <memory>
#include <type_traits>
#include <ankerl/unordered_dense.h>
#include <vector>
#include <ankerl/unordered_dense.h>
#include <boost/container/stable_vector.hpp>
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/object_pool.h"
#include "shader_recompiler/profile.h"
#include "shader_recompiler/shader_pool.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
@ -88,18 +91,6 @@ class Scheduler;
using VideoCommon::ShaderInfo;
struct ShaderPools {
void ReleaseContents() {
flow_block.ReleaseContents();
block.ReleaseContents();
inst.ReleaseContents();
}
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
Shader::ObjectPool<Shader::IR::Block> block{32};
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
};
class PipelineCache : public VideoCommon::ShaderCache {
public:
explicit PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device,
@ -124,14 +115,14 @@ private:
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineCacheKey& key,
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
bool build_in_parallel);
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineCacheKey& key,
const ShaderInfo* shader);
std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderPools& pools,
std::unique_ptr<ComputePipeline> CreateComputePipeline(Shader::Maxwell::ShaderPools& pools,
const ComputePipelineCacheKey& key,
Shader::Environment& env,
PipelineStatistics* statistics,
@ -161,7 +152,7 @@ private:
ankerl::unordered_dense::map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache;
ankerl::unordered_dense::map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
ShaderPools main_pools;
Shader::Maxwell::ShaderPools main_pools;
Shader::Profile profile;
Shader::HostTranslateInfo host_info;

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@ -11,7 +11,6 @@
#include "common/assert.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/object_pool.h"
#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
@ -236,7 +235,7 @@ const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu
} else {
// Slow path, not really hit on commercial games
// Build a control flow graph to get the real shader size
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
boost::container::stable_vector<Shader::Maxwell::Flow::Block> flow_block;
Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()};
info->unique_hash = env.CalculateHash();
info->size_bytes = env.ReadSizeBytes();