diff options
| author | bunnei <bunneidev@gmail.com> | 2015-07-22 23:25:30 -0400 | 
|---|---|---|
| committer | bunnei <bunneidev@gmail.com> | 2015-08-15 18:01:07 -0400 | 
| commit | 094ae6fadb57883e25d412443fcae987ddf240ef (patch) | |
| tree | 8817c9b0672bd02be3fa32a02f3f126f0ecfb664 /src/video_core | |
| parent | d67e2f78b77c0739caa8c6a915685aa8961a519c (diff) | |
Shader: Initial implementation of x86_x64 JIT compiler for Pica vertex shaders.
- Config: Add an option for selecting to use shader JIT or interpreter.
- Qt: Add a menu option for enabling/disabling the shader JIT.
Diffstat (limited to 'src/video_core')
| -rw-r--r-- | src/video_core/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/video_core/pica.cpp | 3 | ||||
| -rw-r--r-- | src/video_core/shader/shader.cpp | 42 | ||||
| -rw-r--r-- | src/video_core/shader/shader.h | 3 | ||||
| -rw-r--r-- | src/video_core/shader/shader_jit.cpp | 36 | ||||
| -rw-r--r-- | src/video_core/shader/shader_jit.h | 85 | ||||
| -rw-r--r-- | src/video_core/shader/shader_jit_fake.cpp | 91 | ||||
| -rw-r--r-- | src/video_core/shader/shader_jit_x64.cpp | 669 | ||||
| -rw-r--r-- | src/video_core/video_core.cpp | 1 | ||||
| -rw-r--r-- | src/video_core/video_core.h | 3 | 
10 files changed, 940 insertions, 3 deletions
| diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 2b859a077..544ed0297 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRCS              rasterizer.cpp              shader/shader.cpp              shader/shader_interpreter.cpp +            shader/shader_jit.cpp              utils.cpp              video_core.cpp              ) @@ -38,10 +39,19 @@ set(HEADERS              renderer_base.h              shader/shader.h              shader/shader_interpreter.h +            shader/shader_jit.h              utils.h              video_core.h              ) +if(_M_X86_64) +    set(SRCS ${SRCS} +            shader/shader_jit_x64.cpp) +else() +    set(SRCS ${SRCS} +            shader/shader_jit_fake.cpp) +endif() +  create_directory_groups(${SRCS} ${HEADERS})  add_library(video_core STATIC ${SRCS} ${HEADERS}) diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index 17cb66780..c73a8178e 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -6,6 +6,7 @@  #include <unordered_map>  #include "pica.h" +#include "shader/shader.h"  namespace Pica { @@ -84,6 +85,8 @@ void Init() {  }  void Shutdown() { +    Shader::Shutdown(); +      memset(&g_state, 0, sizeof(State));  } diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 13e22cb53..fa1f7cafe 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,21 +2,52 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include "common/logging/log.h" +#include <memory> +#include <unordered_map> + +#include "common/hash.h" +#include "common/make_unique.h"  #include "common/profiler.h"  #include "video_core/debug_utils/debug_utils.h"  #include "video_core/pica.h" +#include "video_core/video_core.h"  #include "shader.h"  #include "shader_interpreter.h" +#include "shader_jit.h"  namespace Pica {  namespace Shader { +#ifdef ARCHITECTURE_x86_64 + +static std::unordered_map<u64, CompiledShader*> shader_map; +static JitCompiler jit; +static CompiledShader* jit_shader; + +#endif // ARCHITECTURE_x86_64 +  void Setup(UnitState& state) { -    // TODO(bunnei): This will be used by the JIT in a subsequent patch +#ifdef ARCHITECTURE_x86_64 +    if (VideoCore::g_shader_jit_enabled) { +        u64 cache_key = (Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^ +            Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data)) ^ +            g_state.regs.vs.main_offset); + +        auto iter = shader_map.find(cache_key); +        if (iter != shader_map.end()) { +            jit_shader = iter->second; +        } else { +            jit_shader = jit.Compile(); +            shader_map.emplace(cache_key, jit_shader); +        } +    } +} + +void Shutdown() { +    shader_map.clear();  }  static Common::Profiling::TimingCategory shader_category("Vertex Shader"); @@ -54,7 +85,14 @@ OutputVertex Run(UnitState& state, const InputVertex& input, int num_attributes)      state.conditional_code[0] = false;      state.conditional_code[1] = false; +#ifdef ARCHITECTURE_x86_64 +    if (VideoCore::g_shader_jit_enabled) +        jit_shader(&state); +    else +        RunInterpreter(state); +#else      RunInterpreter(state); +#endif  #if PICA_DUMP_SHADERS      DebugUtils::DumpShader(setup.program_code.data(), state.debug.max_offset, setup.swizzle_data.data(), diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 38c00768d..5825e9983 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -149,6 +149,9 @@ struct UnitState {   */  void Setup(UnitState& state); +/// Performs any cleanup when the emulator is shutdown +void Shutdown(); +  /**   * Runs the currently setup shader   * @param state Shader unit state, must be setup per shader and per shader unit diff --git a/src/video_core/shader/shader_jit.cpp b/src/video_core/shader/shader_jit.cpp new file mode 100644 index 000000000..69fb7f6be --- /dev/null +++ b/src/video_core/shader/shader_jit.cpp @@ -0,0 +1,36 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/pica.h" + +#include "shader.h" +#include "shader_jit.h" + +namespace Pica { + +namespace Shader { + +JitShader::JitShader() : jitted(nullptr) { +} + +void JitShader::DoJit(JitCompiler& jit) { +    jitted = jit.Compile(); +} + +void JitShader::Run(UnitState& state) { +    if (jitted) +        jitted(&state); +} + +JitCompiler::JitCompiler() { +    AllocCodeSpace(1024 * 1024 * 4); +} + +void JitCompiler::Clear() { +    ClearCodeSpace(); +} + +} // namespace Shader + +} // namespace Pica diff --git a/src/video_core/shader/shader_jit.h b/src/video_core/shader/shader_jit.h new file mode 100644 index 000000000..f05b64a92 --- /dev/null +++ b/src/video_core/shader/shader_jit.h @@ -0,0 +1,85 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <nihstro/shader_bytecode.h> + +#if defined(_M_X86_64) +#include "common/x64_emitter.h" +#else +#include "common/fake_emitter.h" +#endif + +#include "video_core/pica.h" + +#include "shader.h" + +using nihstro::Instruction; +using nihstro::OpCode; +using nihstro::SwizzlePattern; + +namespace Pica { + +namespace Shader { + +using CompiledShader = void(void* state); + +/** + * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 + * code that can be executed on the host machine directly. + */ +class JitCompiler : public Gen::XCodeBlock { +public: +    JitCompiler(); + +    CompiledShader* Compile(); + +    void Clear(); + +    void Compile_ADD(Instruction instr); +    void Compile_DP3(Instruction instr); +    void Compile_DP4(Instruction instr); +    void Compile_MUL(Instruction instr); +    void Compile_FLR(Instruction instr); +    void Compile_MAX(Instruction instr); +    void Compile_MIN(Instruction instr); +    void Compile_RCP(Instruction instr); +    void Compile_RSQ(Instruction instr); +    void Compile_MOVA(Instruction instr); +    void Compile_MOV(Instruction instr); +    void Compile_SLTI(Instruction instr); +    void Compile_NOP(Instruction instr); +    void Compile_END(Instruction instr); +    void Compile_CALL(Instruction instr); +    void Compile_CALLC(Instruction instr); +    void Compile_CALLU(Instruction instr); +    void Compile_IF(Instruction instr); +    void Compile_LOOP(Instruction instr); +    void Compile_JMP(Instruction instr); +    void Compile_CMP(Instruction instr); +    void Compile_MAD(Instruction instr); + +private: +    void Compile_Block(unsigned stop); +    void Compile_NextInstr(unsigned* offset); + +#if defined(_M_X86_64) +    void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, Gen::X64Reg dest); +    void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); + +    void Compile_EvaluateCondition(Instruction instr); +    void Compile_UniformCondition(Instruction instr); +#endif + +    /// Pointer to the variable that stores the current Pica code offset. Used to handle nested code blocks. +    unsigned* offset_ptr = nullptr; + +    bool done = false; +    bool looping = false; +}; + +} // Shader + +} // Pica diff --git a/src/video_core/shader/shader_jit_fake.cpp b/src/video_core/shader/shader_jit_fake.cpp new file mode 100644 index 000000000..e1e79b733 --- /dev/null +++ b/src/video_core/shader/shader_jit_fake.cpp @@ -0,0 +1,91 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/fake_emitter.h" + +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_jit.h" + +namespace Pica { + +namespace Shader { + +using namespace FakeGen; + +void Jit::Comp_ADD(Instruction instr) { +} + +void Jit::Comp_DP3(Instruction instr) { +} + +void Jit::Comp_DP4(Instruction instr) { +} + +void Jit::Comp_MUL(Instruction instr) { +} + +void Jit::Comp_FLR(Instruction instr) { +} + +void Jit::Comp_MAX(Instruction instr) { +} + +void Jit::Comp_MIN(Instruction instr) { +} + +void Jit::Comp_MOVA(Instruction instr) { +} + +void Jit::Comp_MOV(Instruction instr) { +} + +void Jit::Comp_SLTI(Instruction instr) { +} + +void Jit::Comp_RCP(Instruction instr) { +} + +void Jit::Comp_RSQ(Instruction instr) { +} + +void Jit::Comp_NOP(Instruction instr) { +} + +void Jit::Comp_END(Instruction instr) { +} + +void Jit::Comp_CALL(Instruction instr) { +} + +void Jit::Comp_CALLC(Instruction instr) { +} + +void Jit::Comp_CALLU(Instruction instr) { +} + +void Jit::Comp_CMP(Instruction instr) { +} + +void Jit::Comp_MAD(Instruction instr) { +} + +void Jit::Comp_IF(Instruction instr) { +} + +void Jit::Comp_LOOP(Instruction instr) { +} + +void Jit::Comp_JMP(Instruction instr) { +} + +void Jit::Comp_NextInstr(unsigned* offset) { +} + +CompiledShader Jit::Compile() { +    return nullptr; +} + +} // namespace Shader + +} // namespace Pica diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp new file mode 100644 index 000000000..00c57afec --- /dev/null +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -0,0 +1,669 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <smmintrin.h> + +#include "common/abi.h" +#include "common/cpu_detect.h" +#include "common/x64_emitter.h" + +#include "shader.h" +#include "shader_jit.h" + +namespace Pica { + +namespace Shader { + +using namespace Gen; + +typedef void (JitCompiler::*JitFunction)(Instruction instr); + +const JitFunction instr_table[64] = { +    &JitCompiler::Compile_ADD,      // add +    &JitCompiler::Compile_DP3,      // dp3 +    &JitCompiler::Compile_DP4,      // dp4 +    nullptr,                        // dph +    nullptr,                        // unknown +    nullptr,                        // ex2 +    nullptr,                        // lg2 +    nullptr,                        // unknown +    &JitCompiler::Compile_MUL,      // mul +    nullptr,                        // lge +    nullptr,                        // slt +    &JitCompiler::Compile_FLR,      // flr +    &JitCompiler::Compile_MAX,      // max +    &JitCompiler::Compile_MIN,      // min +    &JitCompiler::Compile_RCP,      // rcp +    &JitCompiler::Compile_RSQ,      // rsq +    nullptr,                        // unknown +    nullptr,                        // unknown +    &JitCompiler::Compile_MOVA,     // mova +    &JitCompiler::Compile_MOV,      // mov +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // dphi +    nullptr,                        // unknown +    nullptr,                        // sgei +    &JitCompiler::Compile_SLTI,     // slti +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // unknown +    nullptr,                        // unknown +    &JitCompiler::Compile_NOP,      // nop +    &JitCompiler::Compile_END,      // end +    nullptr,                        // break +    &JitCompiler::Compile_CALL,     // call +    &JitCompiler::Compile_CALLC,    // callc +    &JitCompiler::Compile_CALLU,    // callu +    &JitCompiler::Compile_IF,       // ifu +    &JitCompiler::Compile_IF,       // ifc +    &JitCompiler::Compile_LOOP,     // loop +    nullptr,                        // emit +    nullptr,                        // sete +    &JitCompiler::Compile_JMP,      // jmpc +    &JitCompiler::Compile_JMP,      // jmpu +    &JitCompiler::Compile_CMP,      // cmp +    &JitCompiler::Compile_CMP,      // cmp +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // madi +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +    &JitCompiler::Compile_MAD,      // mad +}; + +// The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can +// be used as scratch registers within a compiler function. The other registers have designated +// purposes, as documented below: + +/// Pointer to the uniform memory +static const X64Reg UNIFORMS = R10; +/// The two 32-bit VS address offset registers set by the MOVA instruction +static const X64Reg ADDROFFS_REG = R11; +/// VS loop count register +static const X64Reg LOOPCOUNT_REG = R12; +/// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker) +static const X64Reg LOOPCOUNT = RSI; +/// Number to increment LOOPCOUNT_REG by on each loop iteration +static const X64Reg LOOPINC = RDI; +/// Result of the previous CMP instruction for the X-component comparison +static const X64Reg COND0 = R13; +/// Result of the previous CMP instruction for the Y-component comparison +static const X64Reg COND1 = R14; +/// Pointer to the UnitState instance for the current VS unit +static const X64Reg STATE = R15; +/// SIMD scratch register +static const X64Reg SCRATCH = XMM0; +/// Loaded with the first swizzled source register, otherwise can be used as a scratch register +static const X64Reg SRC1 = XMM1; +/// Loaded with the second swizzled source register, otherwise can be used as a scratch register +static const X64Reg SRC2 = XMM2; +/// Loaded with the third swizzled source register, otherwise can be used as a scratch register +static const X64Reg SRC3 = XMM3; +/// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one +static const X64Reg ONE = XMM14; +/// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR +static const X64Reg NEGBIT = XMM15; + +/// Raw constant for the source register selector that indicates no swizzling is performed +static const u8 NO_SRC_REG_SWIZZLE = 0x1b; +/// Raw constant for the destination register enable mask that indicates all components are enabled +static const u8 NO_DEST_REG_MASK = 0xf; + +/** + * Loads and swizzles a source register into the specified XMM register. + * @param instr VS instruction, used for determining how to load the source register + * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) + * @param src_reg SourceRegister object corresponding to the source register to load + * @param dest Destination XMM register to store the loaded, swizzled source register + */ +void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { +    X64Reg src_ptr; +    std::size_t src_offset; + +    if (src_reg.GetRegisterType() == RegisterType::FloatUniform) { +        src_ptr = UNIFORMS; +        src_offset = src_reg.GetIndex() * sizeof(float24) * 4; +    } else { +        src_ptr = STATE; +        src_offset = UnitState::InputOffset(src_reg); +    } + +    unsigned operand_desc_id; +    if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || +        instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { +        // The MAD and MADI instructions do not use the address offset registers, so loading the +        // source is a bit simpler here + +        operand_desc_id = instr.mad.operand_desc_id; + +        // Load the source +        MOVAPS(dest, MDisp(src_ptr, src_offset)); +    } else { +        operand_desc_id = instr.common.operand_desc_id; + +        const bool is_inverted = (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed)); +        unsigned offset_src = is_inverted ? 2 : 1; + +        if (src_num == offset_src && instr.common.address_register_index != 0) { +            switch (instr.common.address_register_index) { +            case 1: // address offset 1 +                MOV(32, R(RBX), R(ADDROFFS_REG)); +                break; +            case 2: // address offset 2 +                MOV(64, R(RBX), R(ADDROFFS_REG)); +                SHR(64, R(RBX), Imm8(32)); +                break; +            case 3: // adddress offet 3 +                MOV(64, R(RBX), R(LOOPCOUNT_REG)); +                break; +            default: +                UNREACHABLE(); +                break; +            } + +            MOVAPS(dest, MComplex(src_ptr, RBX, 1, src_offset)); +        } else { +            // Load the source +            MOVAPS(dest, MDisp(src_ptr, src_offset)); +        } +    } + +    SwizzlePattern swiz = { g_state.vs.swizzle_data[operand_desc_id] }; + +    // Generate instructions for source register swizzling as needed +    u8 sel = swiz.GetRawSelector(src_num); +    if (sel != NO_SRC_REG_SWIZZLE) { +        // Selector component order needs to be reversed for the SHUFPS instruction +        sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2); + +        // Shuffle inputs for swizzle +        SHUFPS(dest, R(dest), sel); +    } + +    // If the source register should be negated, flip the negative bit using XOR +    const bool negate[] = { swiz.negate_src1, swiz.negate_src2, swiz.negate_src3 }; +    if (negate[src_num - 1]) { +        XORPS(dest, R(NEGBIT)); +    } +} + +void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { +    DestRegister dest; +    unsigned operand_desc_id; +    if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || +        instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { +        operand_desc_id = instr.mad.operand_desc_id; +        dest = instr.mad.dest.Value(); +    } else { +        operand_desc_id = instr.common.operand_desc_id; +        dest = instr.common.dest.Value(); +    } + +    SwizzlePattern swiz = { g_state.vs.swizzle_data[operand_desc_id] }; + +    // If all components are enabled, write the result to the destination register +    if (swiz.dest_mask == NO_DEST_REG_MASK) { +        // Store dest back to memory +        MOVAPS(MDisp(STATE, UnitState::OutputOffset(dest)), src); + +    } else { +        // Not all components are enabled, so mask the result when storing to the destination register... +        MOVAPS(SCRATCH, MDisp(STATE, UnitState::OutputOffset(dest))); + +        if (Common::cpu_info.bSSE4_1) { +            u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); +            BLENDPS(SCRATCH, R(src), mask); +        } else { +            MOVAPS(XMM4, R(src)); +            UNPCKHPS(XMM4, R(SCRATCH)); // Unpack X/Y components of source and destination +            UNPCKLPS(SCRATCH, R(src)); // Unpack Z/W components of source and destination + +            // Compute selector to selectively copy source components to destination for SHUFPS instruction +            u8 sel = ((swiz.DestComponentEnabled(0) ? 1 : 0) << 0) | +                     ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) | +                     ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) | +                     ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6); +            SHUFPS(SCRATCH, R(XMM4), sel); +        } + +        // Store dest back to memory +        MOVAPS(MDisp(STATE, UnitState::OutputOffset(dest)), SCRATCH); +    } +} + +void JitCompiler::Compile_EvaluateCondition(Instruction instr) { +    // Note: NXOR is used below to check for equality +    switch (instr.flow_control.op) { +    case Instruction::FlowControlType::Or: +        MOV(32, R(RAX), R(COND0)); +        MOV(32, R(RBX), R(COND1)); +        XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); +        XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); +        OR(32, R(RAX), R(RBX)); +        break; + +    case Instruction::FlowControlType::And: +        MOV(32, R(RAX), R(COND0)); +        MOV(32, R(RBX), R(COND1)); +        XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); +        XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); +        AND(32, R(RAX), R(RBX)); +        break; + +    case Instruction::FlowControlType::JustX: +        MOV(32, R(RAX), R(COND0)); +        XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); +        break; + +    case Instruction::FlowControlType::JustY: +        MOV(32, R(RAX), R(COND1)); +        XOR(32, R(RAX), Imm32(instr.flow_control.refy.Value() ^ 1)); +        break; +    } +} + +void JitCompiler::Compile_UniformCondition(Instruction instr) { +    int offset = offsetof(decltype(g_state.vs.uniforms), b) + (instr.flow_control.bool_uniform_id * sizeof(bool)); +    CMP(sizeof(bool) * 8, MDisp(UNIFORMS, offset), Imm8(0)); +} + +void JitCompiler::Compile_ADD(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); +    ADDPS(SRC1, R(SRC2)); +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_DP3(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + +    if (Common::cpu_info.bSSE4_1) { +        DPPS(SRC1, R(SRC2), 0x7f); +    } else { +        MULPS(SRC1, R(SRC2)); + +        MOVAPS(SRC2, R(SRC1)); +        SHUFPS(SRC2, R(SRC2), _MM_SHUFFLE(1, 1, 1, 1)); + +        MOVAPS(SRC3, R(SRC1)); +        SHUFPS(SRC3, R(SRC3), _MM_SHUFFLE(2, 2, 2, 2)); + +        SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); +        ADDPS(SRC1, R(SRC2)); +        ADDPS(SRC1, R(SRC3)); +    } + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_DP4(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + +    if (Common::cpu_info.bSSE4_1) { +        DPPS(SRC1, R(SRC2), 0xff); +    } else { +        MULPS(SRC1, R(SRC2)); + +        MOVAPS(SRC2, R(SRC1)); +        SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY +        ADDPS(SRC1, R(SRC2)); + +        MOVAPS(SRC2, R(SRC1)); +        SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX +        ADDPS(SRC1, R(SRC2)); +    } + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_MUL(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); +    MULPS(SRC1, R(SRC2)); +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_FLR(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + +    if (Common::cpu_info.bSSE4_1) { +        ROUNDFLOORPS(SRC1, R(SRC1)); +    } else { +        CVTPS2DQ(SRC1, R(SRC1)); +        CVTDQ2PS(SRC1, R(SRC1)); +    } + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_MAX(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); +    MAXPS(SRC1, R(SRC2)); +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_MIN(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); +    MINPS(SRC1, R(SRC2)); +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_MOVA(Instruction instr) { +    SwizzlePattern swiz = { g_state.vs.swizzle_data[instr.common.operand_desc_id] }; + +    if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { +        return; // NoOp +    } + +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + +    // Convert floats to integers (only care about X and Y components) +    CVTPS2DQ(SRC1, R(SRC1)); + +    // Get result +    MOVQ_xmm(R(RAX), SRC1); +    SHL(64, R(RAX), Imm8(4)); // Multiply by 16 to be used as an offset later + +    // Handle destination enable +    if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) { +        MOV(64, R(ADDROFFS_REG), R(RAX)); // Overwrite both +    } else { +        if (swiz.DestComponentEnabled(0)) { +            // Preserve Y-component + +            // Clear low 32 bits of previous address register +            MOV(32, R(RBX), R(ADDROFFS_REG)); +            XOR(64, R(ADDROFFS_REG), R(RBX)); + +            // Clear high 32-bits of new address register +            MOV(32, R(RAX), R(RAX)); +        } else if (swiz.DestComponentEnabled(1)) { +            // Preserve X-component + +            // Clear high 32-bits of previous address register +            MOV(32, R(ADDROFFS_REG), R(ADDROFFS_REG)); + +            // Clear low 32 bits of new address register +            MOV(32, R(RBX), R(RAX)); +            XOR(64, R(RAX), R(RBX)); +        } + +        OR(64, R(ADDROFFS_REG), R(RAX)); // Combine result +    } +} + +void JitCompiler::Compile_MOV(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_SLTI(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); +    Compile_SwizzleSrc(instr, 1, instr.common.src2i, SRC2); + +    CMPSS(SRC1, R(SRC2), CMP_LT); +    ANDPS(SRC1, R(ONE)); + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_RCP(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + +    // TODO(bunnei): RCPPS is a pretty rough approximation, this might cause problems if Pica +    // performs this operation more accurately. This should be checked on hardware. +    RCPPS(SRC1, R(SRC1)); + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_RSQ(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); + +    // TODO(bunnei): RSQRTPS is a pretty rough approximation, this might cause problems if Pica +    // performs this operation more accurately. This should be checked on hardware. +    RSQRTPS(SRC1, R(SRC1)); + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_NOP(Instruction instr) { +} + +void JitCompiler::Compile_END(Instruction instr) { +    ABI_PopAllCalleeSavedRegsAndAdjustStack(); +    RET(); +    done = true; +} + +void JitCompiler::Compile_CALL(Instruction instr) { +    unsigned offset = instr.flow_control.dest_offset; +    while (offset < (instr.flow_control.dest_offset + instr.flow_control.num_instructions)) { +        Compile_NextInstr(&offset); +    } +} + +void JitCompiler::Compile_CALLC(Instruction instr) { +    Compile_EvaluateCondition(instr); +    FixupBranch b = J_CC(CC_Z, true); +    Compile_CALL(instr); +    SetJumpTarget(b); +} + +void JitCompiler::Compile_CALLU(Instruction instr) { +    Compile_UniformCondition(instr); +    FixupBranch b = J_CC(CC_Z, true); +    Compile_CALL(instr); +    SetJumpTarget(b); +} + +void JitCompiler::Compile_CMP(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); +    Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); + +    static const u8 cmp[] = { CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_NLE, CMP_NLT }; + +    if (instr.common.compare_op.x == instr.common.compare_op.y) { +        // Compare X-component and Y-component together +        CMPPS(SRC1, R(SRC2), cmp[instr.common.compare_op.x]); + +        MOVQ_xmm(R(COND0), SRC1); +        MOV(64, R(COND1), R(COND0)); +    } else { +        // Compare X-component +        MOVAPS(SCRATCH, R(SRC1)); +        CMPSS(SCRATCH, R(SRC2), cmp[instr.common.compare_op.x]); + +        // Compare Y-component +        CMPPS(SRC1, R(SRC2), cmp[instr.common.compare_op.y]); + +        MOVQ_xmm(R(COND0), SCRATCH); +        MOVQ_xmm(R(COND1), SRC1); +    } + +    SHR(32, R(COND0), Imm8(31)); +    SHR(64, R(COND1), Imm8(63)); +} + +void JitCompiler::Compile_MAD(Instruction instr) { +    Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); + +    if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { +        Compile_SwizzleSrc(instr, 2, instr.mad.src2i, SRC2); +        Compile_SwizzleSrc(instr, 3, instr.mad.src3i, SRC3); +    } else { +        Compile_SwizzleSrc(instr, 2, instr.mad.src2, SRC2); +        Compile_SwizzleSrc(instr, 3, instr.mad.src3, SRC3); +    } + +    if (Common::cpu_info.bFMA) { +        VFMADD213PS(SRC1, SRC2, R(SRC3)); +    } else { +        MULPS(SRC1, R(SRC2)); +        ADDPS(SRC1, R(SRC3)); +    } + +    Compile_DestEnable(instr, SRC1); +} + +void JitCompiler::Compile_IF(Instruction instr) { +    ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards if-statements not supported"); + +    // Evaluate the "IF" condition +    if (instr.opcode.Value() == OpCode::Id::IFU) { +        Compile_UniformCondition(instr); +    } else if (instr.opcode.Value() == OpCode::Id::IFC) { +        Compile_EvaluateCondition(instr); +    } +    FixupBranch b = J_CC(CC_Z, true); + +    // Compile the code that corresponds to the condition evaluating as true +    Compile_Block(instr.flow_control.dest_offset - 1); + +    // If there isn't an "ELSE" condition, we are done here +    if (instr.flow_control.num_instructions == 0) { +        SetJumpTarget(b); +        return; +    } + +    FixupBranch b2 = J(true); + +    SetJumpTarget(b); + +    // This code corresponds to the "ELSE" condition +    // Comple the code that corresponds to the condition evaluating as false +    Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions - 1); + +    SetJumpTarget(b2); +} + +void JitCompiler::Compile_LOOP(Instruction instr) { +    ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards loops not supported"); +    ASSERT_MSG(!looping, "Nested loops not supported"); + +    looping = true; + +    int offset = offsetof(decltype(g_state.vs.uniforms), i) + (instr.flow_control.int_uniform_id * sizeof(Math::Vec4<u8>)); +    MOV(32, R(LOOPCOUNT), MDisp(UNIFORMS, offset)); +    MOV(32, R(LOOPCOUNT_REG), R(LOOPCOUNT)); +    SHR(32, R(LOOPCOUNT_REG), Imm8(8)); +    AND(32, R(LOOPCOUNT_REG), Imm32(0xff)); // Y-component is the start +    MOV(32, R(LOOPINC), R(LOOPCOUNT)); +    SHR(32, R(LOOPINC), Imm8(16)); +    MOVZX(32, 8, LOOPINC, R(LOOPINC)); // Z-component is the incrementer +    MOVZX(32, 8, LOOPCOUNT, R(LOOPCOUNT)); // X-component is iteration count +    ADD(32, R(LOOPCOUNT), Imm8(1)); // Iteration count is X-component + 1 + +    auto loop_start = GetCodePtr(); + +    Compile_Block(instr.flow_control.dest_offset); + +    ADD(32, R(LOOPCOUNT_REG), R(LOOPINC)); // Increment LOOPCOUNT_REG by Z-component +    SUB(32, R(LOOPCOUNT), Imm8(1)); // Increment loop count by 1 +    J_CC(CC_NZ, loop_start); // Loop if not equal + +    looping = false; +} + +void JitCompiler::Compile_JMP(Instruction instr) { +    ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards jumps not supported"); + +    if (instr.opcode.Value() == OpCode::Id::JMPC) +        Compile_EvaluateCondition(instr); +    else if (instr.opcode.Value() == OpCode::Id::JMPU) +        Compile_UniformCondition(instr); +    else +        UNREACHABLE(); + +    FixupBranch b = J_CC(CC_NZ, true); + +    Compile_Block(instr.flow_control.dest_offset); + +    SetJumpTarget(b); +} + +void JitCompiler::Compile_Block(unsigned stop) { +    // Save current offset pointer +    unsigned* prev_offset_ptr = offset_ptr; +    unsigned offset = *prev_offset_ptr; + +    while (offset <= stop) +        Compile_NextInstr(&offset); + +    // Restore current offset pointer +    offset_ptr = prev_offset_ptr; +    *offset_ptr = offset; +} + +void JitCompiler::Compile_NextInstr(unsigned* offset) { +    offset_ptr = offset; + +    Instruction instr = *(Instruction*)&g_state.vs.program_code[(*offset_ptr)++]; +    OpCode::Id opcode = instr.opcode.Value(); +    auto instr_func = instr_table[static_cast<unsigned>(opcode)]; + +    if (instr_func) { +        // JIT the instruction! +        ((*this).*instr_func)(instr); +    } else { +        // Unhandled instruction +        LOG_CRITICAL(HW_GPU, "Unhandled instruction: 0x%02x (0x%08x)", instr.opcode.Value(), instr.hex); +    } +} + +CompiledShader* JitCompiler::Compile() { +    const u8* start = GetCodePtr(); +    const auto& code = g_state.vs.program_code; +    unsigned offset = g_state.regs.vs.main_offset; + +    ABI_PushAllCalleeSavedRegsAndAdjustStack(); + +    MOV(PTRBITS, R(STATE), R(ABI_PARAM1)); +    MOV(PTRBITS, R(UNIFORMS), ImmPtr(&g_state.vs.uniforms)); + +    // Zero address/loop  registers +    XOR(64, R(ADDROFFS_REG_0), R(ADDROFFS_REG_0)); +    XOR(64, R(ADDROFFS_REG_1), R(ADDROFFS_REG_1)); +    XOR(64, R(LOOPCOUNT_REG), R(LOOPCOUNT_REG)); + +    // Used to set a register to one +    static const __m128 one = { 1.f, 1.f, 1.f, 1.f }; +    MOV(PTRBITS, R(RAX), ImmPtr(&one)); +    MOVAPS(ONE, MDisp(RAX, 0)); + +    // Used to negate registers +    static const __m128 neg = { -0.f, -0.f, -0.f, -0.f }; +    MOV(PTRBITS, R(RAX), ImmPtr(&neg)); +    MOVAPS(NEGBIT, MDisp(RAX, 0)); + +    looping = false; +    done = false; +    while (offset < g_state.vs.program_code.size()) { +        Compile_NextInstr(&offset); +    } + +    return (CompiledShader*)start; +} + +} // namespace Shader + +} // namespace Pica diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 3becc4261..943fde5ee 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -23,6 +23,7 @@ EmuWindow*      g_emu_window    = nullptr;     ///< Frontend emulator window  RendererBase*   g_renderer      = nullptr;     ///< Renderer plugin  std::atomic<bool> g_hw_renderer_enabled; +std::atomic<bool> g_shader_jit_enabled;  /// Initialize the video core  void Init(EmuWindow* emu_window) { diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 14b33c9dd..2867bf03e 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -32,8 +32,9 @@ static const int kScreenBottomHeight    = 240;  ///< 3DS bottom screen height  extern RendererBase*   g_renderer;              ///< Renderer plugin  extern EmuWindow*      g_emu_window;            ///< Emu window -// TODO: Wrap this in a user settings struct along with any other graphics settings (often set from qt ui) +// TODO: Wrap these in a user settings struct along with any other graphics settings (often set from qt ui)  extern std::atomic<bool> g_hw_renderer_enabled; +extern std::atomic<bool> g_shader_jit_enabled;  /// Start the video core  void Start(); | 
