shader_jit: Optimize GeometryEmitter SETEMIT state

The `SETEMIT`/`SETE` instruction only actually encodes 4 bits of possible state,
but this is currently expanded into three separate bytes of
data(four with padding) and requires three separate byte-writes for the x64 and
a64 JITs to write into. These 4 bits from the instruction can instead be
compacted into a singular 1-byte write from the JIT by encoding these 4 bits of
state into a singular byte at JIT-time, and unpacking this data is instead done
by `GeometryEmitter::Emit`. This also allows the serializer to use a singular
byte for all 3 fields now as well.
This commit is contained in:
Wunkolo 2026-04-02 22:18:44 -07:00 committed by PabloMK7
parent 60f331b43b
commit 4cbd75b413
5 changed files with 34 additions and 25 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -29,15 +29,15 @@ void ShaderUnit::WriteOutput(const ShaderRegs& config, AttributeBuffer& buffer)
}
void GeometryEmitter::Emit(std::span<Common::Vec4<f24>, 16> output_regs) {
ASSERT(vertex_id < 3);
ASSERT(emit_state.vertex_id < 3);
u32 output_index{};
for (u32 reg : Common::BitSet<u32>(output_mask)) {
buffer[vertex_id][output_index++] = output_regs[reg];
buffer[emit_state.vertex_id][output_index++] = output_regs[reg];
}
if (prim_emit) {
if (winding) {
if (emit_state.prim_emit) {
if (emit_state.winding) {
handlers->winding_setter();
}
for (std::size_t i = 0; i < buffer.size(); ++i) {

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -75,11 +75,18 @@ struct GeometryEmitter {
void Emit(std::span<Common::Vec4<f24>, 16> output_regs);
public:
std::array<AttributeBuffer, 3> buffer;
u8 vertex_id;
bool prim_emit;
bool winding;
union EmitState {
struct {
bool winding : 1;
bool prim_emit : 1;
u8 vertex_id : 2;
};
u8 raw;
} emit_state;
static_assert(sizeof(emit_state) == 1);
u32 output_mask;
std::array<AttributeBuffer, 3> buffer;
Handlers* handlers;
private:
@ -87,9 +94,7 @@ private:
template <class Archive>
void serialize(Archive& ar, const u32 file_version) {
ar & buffer;
ar & vertex_id;
ar & prim_emit;
ar & winding;
ar & emit_state.raw;
ar & output_mask;
}
};

View File

@ -671,9 +671,9 @@ static void RunInterpreter(const ShaderSetup& setup, ShaderUnit& state,
case OpCode::Id::SETEMIT: {
auto* emitter = state.emitter_ptr;
ASSERT_MSG(emitter, "Execute SETEMIT on VS");
emitter->vertex_id = instr.setemit.vertex_id;
emitter->prim_emit = instr.setemit.prim_emit != 0;
emitter->winding = instr.setemit.winding != 0;
emitter->emit_state.vertex_id = instr.setemit.vertex_id;
emitter->emit_state.prim_emit = instr.setemit.prim_emit != 0;
emitter->emit_state.winding = instr.setemit.winding != 0;
break;
}

View File

@ -865,12 +865,13 @@ void JitShader::Compile_SETE(Instruction instr) {
l(have_emitter);
MOV(XSCRATCH1.toW(), instr.setemit.vertex_id);
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, vertex_id)));
MOV(XSCRATCH1.toW(), instr.setemit.prim_emit);
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, prim_emit)));
MOV(XSCRATCH1.toW(), instr.setemit.winding);
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, winding)));
const GeometryEmitter::EmitState new_state{
.winding = instr.setemit.winding != 0,
.prim_emit = instr.setemit.prim_emit != 0,
.vertex_id = static_cast<uint8_t>(instr.setemit.vertex_id),
};
MOV(XSCRATCH1.toW(), new_state.raw);
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, emit_state)));
l(end);
}

View File

@ -905,9 +905,12 @@ void JitShader::Compile_SETE(Instruction instr) {
jmp(end);
L(have_emitter);
mov(byte[rax + offsetof(GeometryEmitter, vertex_id)], instr.setemit.vertex_id);
mov(byte[rax + offsetof(GeometryEmitter, prim_emit)], instr.setemit.prim_emit);
mov(byte[rax + offsetof(GeometryEmitter, winding)], instr.setemit.winding);
const GeometryEmitter::EmitState new_state{
.winding = instr.setemit.winding != 0,
.prim_emit = instr.setemit.prim_emit != 0,
.vertex_id = static_cast<uint8_t>(instr.setemit.vertex_id),
};
mov(byte[rax + offsetof(GeometryEmitter, emit_state)], new_state.raw);
L(end);
}