diff --git a/CMakeLists.txt b/CMakeLists.txt index cce706767e..a21c3d12ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1838,6 +1838,7 @@ list(APPEND CoreExtra Core/MIPS/LoongArch64/LoongArch64Jit.h Core/MIPS/LoongArch64/LoongArch64RegCache.cpp Core/MIPS/LoongArch64/LoongArch64RegCache.h + GPU/Common/VertexDecoderLoongArch64.cpp ) if(NOT MOBILE_DEVICE) diff --git a/Core/MIPS/LoongArch64/LoongArch64RegCache.cpp b/Core/MIPS/LoongArch64/LoongArch64RegCache.cpp index d62b53ad9d..05c4b49c1c 100644 --- a/Core/MIPS/LoongArch64/LoongArch64RegCache.cpp +++ b/Core/MIPS/LoongArch64/LoongArch64RegCache.cpp @@ -550,7 +550,7 @@ bool LoongArch64RegCache::Transfer1ToVec(IRNativeReg nreg, IRNativeReg dest, IRR emit_->VILVL_W(EncodeRegToV(cur[2]), EncodeRegToV(cur[3]), EncodeRegToV(cur[2])); emit_->FLD_D( SCRATCHF1, CTXREG, GetMipsRegOffset(first + 0)); emit_->VEXTRINS_D(EncodeRegToV(cur[3]), EncodeRegToV(SCRATCHF1), 0); - emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), EncodeRegToV(cur[3])); + emit_->VILVL_D(EncodeRegToV(destReg), EncodeRegToV(cur[2]), EncodeRegToV(cur[3])); } else if (blendMask == 0b0100) { // y = yw##, w = z###, x = xz##, dest = xyzw. emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[3]), EncodeRegToV(cur[1])); diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 5dff55e06e..a8a04defa9 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -1562,6 +1562,8 @@ std::string VertexDecoder::GetString(DebugShaderStringType stringType) const { lines = DisassembleX86((const u8 *)jitted_, jittedSize_); #elif PPSSPP_ARCH(RISCV64) lines = DisassembleRV64((const u8 *)jitted_, jittedSize_); +#elif PPSSPP_ARCH(LOONGARCH64) + lines= DisassembleLA64((const u8 *)jitted_, jittedSize_); #else // No disassembler defined #endif diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index cca574aec4..67e5de4960 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -39,6 +39,8 @@ #include "Common/x64Emitter.h" #elif PPSSPP_ARCH(RISCV64) #include "Common/RiscVEmitter.h" +#elif PPSSPP_ARCH(LOONGARCH64) +#include "Common/LoongArch64Emitter.h" #else #include "Common/FakeEmitter.h" #endif @@ -530,6 +532,8 @@ private: #define VERTEXDECODER_JIT_BACKEND Gen::XCodeBlock #elif PPSSPP_ARCH(RISCV64) #define VERTEXDECODER_JIT_BACKEND RiscVGen::RiscVCodeBlock +#elif PPSSPP_ARCH(LOONGARCH64) +#define VERTEXDECODER_JIT_BACKEND LoongArch64Gen::LoongArch64CodeBlock #endif diff --git a/GPU/Common/VertexDecoderLoongArch64.cpp b/GPU/Common/VertexDecoderLoongArch64.cpp new file mode 100644 index 0000000000..a13c822b96 --- /dev/null +++ b/GPU/Common/VertexDecoderLoongArch64.cpp @@ -0,0 +1,884 @@ +// Copyright (c) 2025- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "ppsspp_config.h" +#if PPSSPP_ARCH(LOONGARCH64) + +#include "Common/CPUDetect.h" +#include "Common/Log.h" +#include "Core/Config.h" +#include "Common/LoongArch64Emitter.h" +#include "Core/MIPS/JitCommon/JitCommon.h" +#include "GPU/GPUState.h" +#include "GPU/Common/VertexDecoderCommon.h" + +alignas(16) static float bones[16 * 8]; + +alignas(16) static const float by128_11[4] = { + 1.0f / 128.0f, 1.0f / 128.0f, 1.0f, 1.0f, +}; + +alignas(16) static const float by32768_11[4] = { + 1.0f / 32768.0f, 1.0f / 32768.0f, 1.0f, 1.0f, +}; + +static const float by128 = 1.0f / 128.0f; +static const float by32768 = 1.0f / 32768.0f; +static const float const65535 = 65535.0f; + +using namespace LoongArch64Gen; + +static const LoongArch64Reg srcReg = R4; // a0 +static const LoongArch64Reg dstReg = R5; // a1 +static const LoongArch64Reg counterReg = R6; // a2 + +static const LoongArch64Reg tempReg1 = R7; // a3 +static const LoongArch64Reg tempReg2 = R8; // a4 +static const LoongArch64Reg tempReg3 = R9; // a5 +static const LoongArch64Reg scratchReg = R10; // a6 + +static const LoongArch64Reg morphBaseReg = R12; // t0 + +static const LoongArch64Reg fullAlphaReg = R11; // a7 +static const LoongArch64Reg boundsMinUReg = R17; +static const LoongArch64Reg boundsMinVReg = R18; +static const LoongArch64Reg boundsMaxUReg = R19; +static const LoongArch64Reg boundsMaxVReg = R20; + +static const LoongArch64Reg fpScratchReg = F4; +static const LoongArch64Reg fpScratchReg2 = F5; +static const LoongArch64Reg fpScratchReg3 = F6; +static const LoongArch64Reg fpScratchReg4 = F7; + +static const LoongArch64Reg lsxScratchReg = V2; +static const LoongArch64Reg lsxScratchReg2 = V3; + +static const LoongArch64Reg fpSrc[4] = {F2, F3, F10, F11}; + +static const LoongArch64Reg lsxScaleOffsetReg = V0; +static const LoongArch64Reg lsxOffsetScaleReg = V1; + +static const LoongArch64Reg srcLSX = V8; +static const LoongArch64Reg accLSX = V9; + +static const LoongArch64Reg by128LSX = V14; +static const LoongArch64Reg by32768LSX = V15; + +static const LoongArch64Reg lsxWeightRegs[2] = { V12, V13 }; + +// We need to save these fregs when using them. (for example, skinning) +static constexpr LoongArch64Reg regs_to_save_fp[]{ F24, F25, F26, F27, F28, F29, F30, F31 }; + +// V4-V7 is the generated matrix that we multiply things by. +// V8, V9 are accumulators/scratch for matrix mul. +// V10, V11 are more scratch for matrix mul. +// V12, V13 are weight regs. +// V14, V15 are by128 and by32768 regs. +// V16+ are free-for-all for matrices. In 16 registers, we can fit 4 4x4 matrices. + +static const JitLookup jitLookup[] = { + {&VertexDecoder::Step_WeightsU8, &VertexDecoderJitCache::Jit_WeightsU8}, + {&VertexDecoder::Step_WeightsU16, &VertexDecoderJitCache::Jit_WeightsU16}, + {&VertexDecoder::Step_WeightsFloat, &VertexDecoderJitCache::Jit_WeightsFloat}, + {&VertexDecoder::Step_WeightsU8Skin, &VertexDecoderJitCache::Jit_WeightsU8Skin}, + {&VertexDecoder::Step_WeightsU16Skin, &VertexDecoderJitCache::Jit_WeightsU16Skin}, + {&VertexDecoder::Step_WeightsFloatSkin, &VertexDecoderJitCache::Jit_WeightsFloatSkin}, + + {&VertexDecoder::Step_TcFloat, &VertexDecoderJitCache::Jit_TcFloat}, + {&VertexDecoder::Step_TcU8ToFloat, &VertexDecoderJitCache::Jit_TcU8ToFloat}, + {&VertexDecoder::Step_TcU16ToFloat, &VertexDecoderJitCache::Jit_TcU16ToFloat}, + + {&VertexDecoder::Step_TcU8Prescale, &VertexDecoderJitCache::Jit_TcU8Prescale}, + {&VertexDecoder::Step_TcU16Prescale, &VertexDecoderJitCache::Jit_TcU16Prescale}, + {&VertexDecoder::Step_TcFloatPrescale, &VertexDecoderJitCache::Jit_TcFloatPrescale}, + + {&VertexDecoder::Step_TcFloatThrough, &VertexDecoderJitCache::Jit_TcFloatThrough}, + {&VertexDecoder::Step_TcU16ThroughToFloat, &VertexDecoderJitCache::Jit_TcU16ThroughToFloat}, + + {&VertexDecoder::Step_NormalS8, &VertexDecoderJitCache::Jit_NormalS8}, + {&VertexDecoder::Step_NormalS16, &VertexDecoderJitCache::Jit_NormalS16}, + {&VertexDecoder::Step_NormalFloat, &VertexDecoderJitCache::Jit_NormalFloat}, + + {&VertexDecoder::Step_NormalS8Skin, &VertexDecoderJitCache::Jit_NormalS8Skin}, + {&VertexDecoder::Step_NormalS16Skin, &VertexDecoderJitCache::Jit_NormalS16Skin}, + {&VertexDecoder::Step_NormalFloatSkin, &VertexDecoderJitCache::Jit_NormalFloatSkin}, + + {&VertexDecoder::Step_Color8888, &VertexDecoderJitCache::Jit_Color8888}, + {&VertexDecoder::Step_Color4444, &VertexDecoderJitCache::Jit_Color4444}, + {&VertexDecoder::Step_Color565, &VertexDecoderJitCache::Jit_Color565}, + {&VertexDecoder::Step_Color5551, &VertexDecoderJitCache::Jit_Color5551}, + + {&VertexDecoder::Step_PosS8Through, &VertexDecoderJitCache::Jit_PosS8Through}, + {&VertexDecoder::Step_PosS16Through, &VertexDecoderJitCache::Jit_PosS16Through}, + {&VertexDecoder::Step_PosFloatThrough, &VertexDecoderJitCache::Jit_PosFloatThrough}, + + {&VertexDecoder::Step_PosS8, &VertexDecoderJitCache::Jit_PosS8}, + {&VertexDecoder::Step_PosS16, &VertexDecoderJitCache::Jit_PosS16}, + {&VertexDecoder::Step_PosFloat, &VertexDecoderJitCache::Jit_PosFloat}, + + {&VertexDecoder::Step_PosS8Skin, &VertexDecoderJitCache::Jit_PosS8Skin}, + {&VertexDecoder::Step_PosS16Skin, &VertexDecoderJitCache::Jit_PosS16Skin}, + {&VertexDecoder::Step_PosFloatSkin, &VertexDecoderJitCache::Jit_PosFloatSkin}, + + /* + {&VertexDecoder::Step_NormalS8Morph, &VertexDecoderJitCache::Jit_NormalS8Morph}, + {&VertexDecoder::Step_NormalS16Morph, &VertexDecoderJitCache::Jit_NormalS16Morph}, + {&VertexDecoder::Step_NormalFloatMorph, &VertexDecoderJitCache::Jit_NormalFloatMorph}, + + {&VertexDecoder::Step_PosS8Morph, &VertexDecoderJitCache::Jit_PosS8Morph}, + {&VertexDecoder::Step_PosS16Morph, &VertexDecoderJitCache::Jit_PosS16Morph}, + {&VertexDecoder::Step_PosFloatMorph, &VertexDecoderJitCache::Jit_PosFloatMorph}, + + {&VertexDecoder::Step_Color8888Morph, &VertexDecoderJitCache::Jit_Color8888Morph}, + {&VertexDecoder::Step_Color4444Morph, &VertexDecoderJitCache::Jit_Color4444Morph}, + {&VertexDecoder::Step_Color565Morph, &VertexDecoderJitCache::Jit_Color565Morph}, + {&VertexDecoder::Step_Color5551Morph, &VertexDecoderJitCache::Jit_Color5551Morph}, + */ +}; + +JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int32_t *jittedSize) { + dec_ = &dec; + + BeginWrite(4096); + const u8 *start = AlignCode16(); + + int saveSize = (64 / 8) * (int)ARRAY_SIZE(regs_to_save_fp); + int saveOffset = 0; + + bool log = false; + bool prescaleStep = false; + bool updateTexBounds = false; + bool posThroughStep = false; + + // Look for prescaled texcoord steps + for (int i = 0; i < dec.numSteps_; i++) { + if (dec.steps_[i] == &VertexDecoder::Step_TcU8Prescale || + dec.steps_[i] == &VertexDecoder::Step_TcU16Prescale || + dec.steps_[i] == &VertexDecoder::Step_TcFloatPrescale) { + prescaleStep = true; + } + if (dec.steps_[i] == &VertexDecoder::Step_TcU8PrescaleMorph || + dec.steps_[i] == &VertexDecoder::Step_TcU16PrescaleMorph || + dec.steps_[i] == &VertexDecoder::Step_TcFloatPrescaleMorph) { + prescaleStep = true; + } + if (dec.steps_[i] == &VertexDecoder::Step_TcU16ThroughToFloat) { + updateTexBounds = true; + } + } + + // Set rounding mode to RZ (Rounding to Zero) + SLTUI(scratchReg, R_ZERO, 1); + MOVGR2FCSR(FCSR3, scratchReg); + + QuickFLI(32, F14, by128, scratchReg); + QuickFLI(32, F15, by32768, scratchReg); + VREPLVEI_W(by128LSX, by128LSX, 0); + VREPLVEI_W(by32768LSX, by32768LSX, 0); + + // We need to save callee saved fregs when skinning + if (dec.skinInDecode) { + if (saveSize & 0xF) + saveSize += 8; + _assert_msg_((saveSize & 0xF) == 0, "Stack must be kept aligned"); + ADDI_D(R_SP, R_SP, -saveSize); + for (LoongArch64Reg r : regs_to_save_fp) { + FST_D(r, R_SP, saveOffset); + saveOffset += 64 / 8; + } + _assert_(saveOffset <= saveSize); + } + + // Keep the scale/offset in a few fp registers if we need it. + if (prescaleStep) { + VLD(lsxScaleOffsetReg, R7, 0); + if ((dec.VertexType() & GE_VTYPE_TC_MASK) == GE_VTYPE_TC_8BIT) { + LI(scratchReg, &by128_11[0]); + VLD(lsxScratchReg, scratchReg, 0); + VFMUL_S(lsxScaleOffsetReg, lsxScaleOffsetReg, lsxScratchReg); + } else if ((dec.VertexType() & GE_VTYPE_TC_MASK) == GE_VTYPE_TC_16BIT) { + LI(scratchReg, &by32768_11[0]); + VLD(lsxScratchReg, scratchReg, 0); + VFMUL_S(lsxScaleOffsetReg, lsxScaleOffsetReg, lsxScratchReg); + } + VSHUF4I_W(lsxOffsetScaleReg, lsxScaleOffsetReg, (1 << 6 | 0 << 4 | 3 << 2 | 2)); + } + + // Add code to convert matrices to 4x4. + // Later we might want to do this when the matrices are loaded instead. + if (dec.skinInDecode) { + // Copying from R7 to R8 + LI(R7, &gstate.boneMatrix[0]); + // This is only used with more than 4 weights, and points to the first of them. + if (dec.nweights > 4) + LI(R8, &bones[16 * 4]); + + // Construct a mask to zero out the top lane with. + VOR_V(V3, V3, V3); + VORN_V(V3, V3, V3); + VINSGR2VR_W(V3, LoongArch64Gen::R_ZERO, 3); + + for (int i = 0; i < dec.nweights; i++) { + // This loads V4, V5, V6, V7 with 12 floats. + // And sort those floats into 4 regs: ABCD EFGH IJKL -> ABC0 DEF0 GHI0 JKL0. + // TODO: Is unaligned load worth it? + VLD(V4, R7, 0); + VLD(V5, R7, 12); + VLD(V6, R7, 24); + VLD(V7, R7,36); + ADDI_D(R7, R7, 48); + + LoongArch64Reg matrixRow[4]{ V4, V5, V6, V7 }; + // First four matrices are in registers Q16+. + if (i < 4) { + for (int w = 0; w < 4; ++w) + matrixRow[w] = (LoongArch64Reg)(V16 + i * 4 + w); + } + // Zero out the top lane of each one with the mask created above. + VAND_V(matrixRow[0], V4, V3); + VAND_V(matrixRow[1], V5, V3); + VAND_V(matrixRow[2], V6, V3); + VAND_V(matrixRow[3], V7, V3); + + if (i >= 4) { + VST(matrixRow[0], R8, 0); + VST(matrixRow[1], R8, 16); + VST(matrixRow[2], R8, 32); + VST(matrixRow[3], R8, 48); + ADDI_D(R8, R8, 64); + } + } + } + + if (dec.col) { + // Or LB and skip the conditional? This is probably cheaper. + LI(fullAlphaReg, 0xFF); + } + + if (updateTexBounds) { + LI(tempReg1, &gstate_c.vertBounds.minU); + LD_H(boundsMinUReg, tempReg1, offsetof(KnownVertexBounds, minU)); + LD_H(boundsMaxUReg, tempReg1, offsetof(KnownVertexBounds, maxU)); + LD_H(boundsMinVReg, tempReg1, offsetof(KnownVertexBounds, minV)); + LD_H(boundsMaxVReg, tempReg1, offsetof(KnownVertexBounds, maxV)); + } + + const u8 *loopStart = GetCodePtr(); + for (int i = 0; i < dec.numSteps_; i++) { + if (!CompileStep(dec, i)) { + EndWrite(); + // Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed. + ResetCodePtr(GetOffset(start)); + char temp[1024]{}; + dec.ToString(temp, true); + ERROR_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %d: %s", i, temp); + return nullptr; + } + } + + ADDI_D(srcReg, srcReg, dec.VertexSize()); + ADDI_D(dstReg, dstReg, dec.decFmt.stride); + ADDI_D(counterReg, counterReg, -1); + BLT(R_ZERO, counterReg, loopStart); + + if (dec.col) { + LI(tempReg1, &gstate_c.vertexFullAlpha); + FixupBranch skip = BNEZ(fullAlphaReg); + ST_B(fullAlphaReg, tempReg1, 0); + SetJumpTarget(skip); + } + + if (updateTexBounds) { + LI(tempReg1, &gstate_c.vertBounds.minU); + ST_H(boundsMinUReg, tempReg1, offsetof(KnownVertexBounds, minU)); + ST_H(boundsMaxUReg, tempReg1, offsetof(KnownVertexBounds, maxU)); + ST_H(boundsMinVReg, tempReg1, offsetof(KnownVertexBounds, minV)); + ST_H(boundsMaxVReg, tempReg1, offsetof(KnownVertexBounds, maxV)); + } + + if (dec.skinInDecode) { + saveOffset = 0; + for (LoongArch64Reg r : regs_to_save_fp) { + FLD_D(r, R_SP, saveOffset); + saveOffset += 64 / 8; + } + ADDI_D(R_SP, R_SP, saveSize); + } + + RET(); + + FlushIcache(); + + if (log) { + char temp[1024]{}; + dec.ToString(temp, true); + INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); + std::vector lines = DisassembleLA64(start, (int)(GetCodePtr() - start)); + for (auto line : lines) { + INFO_LOG(Log::JIT, "%s", line.c_str()); + } + INFO_LOG(Log::JIT, "=========="); + } + + *jittedSize = (int)(GetCodePtr() - start); + EndWrite(); + return (JittedVertexDecoder)start; +} + +bool VertexDecoderJitCache::CompileStep(const VertexDecoder &dec, int step) { + // See if we find a matching JIT function. + for (size_t i = 0; i < ARRAY_SIZE(jitLookup); i++) { + if (dec.steps_[step] == jitLookup[i].func) { + ((*this).*jitLookup[i].jitFunc)(); + return true; + } + } + return false; +} + +void VertexDecoderJitCache::Jit_ApplyWeights() { + // We construct a matrix in V4-V7 + if (dec_->nweights > 4) { + LI(scratchReg, bones + 16 * 4); + } + for (int i = 0; i < dec_->nweights; i++) { + switch (i) { + case 0: + VREPLVEI_W(lsxScratchReg, lsxWeightRegs[0], 0); + VFMUL_S(V4, V16, lsxScratchReg); + VFMUL_S(V5, V17, lsxScratchReg); + VFMUL_S(V6, V18, lsxScratchReg); + VFMUL_S(V7, V19, lsxScratchReg); + break; + case 1: + VREPLVEI_W(lsxScratchReg, lsxWeightRegs[0], 1); + VFMADD_S(V4, V20, lsxScratchReg, V4); + VFMADD_S(V5, V21, lsxScratchReg, V5); + VFMADD_S(V6, V22, lsxScratchReg, V6); + VFMADD_S(V7, V23, lsxScratchReg, V7); + break; + case 2: + VREPLVEI_W(lsxScratchReg, lsxWeightRegs[0], 2); + VFMADD_S(V4, V24, lsxScratchReg, V4); + VFMADD_S(V5, V25, lsxScratchReg, V5); + VFMADD_S(V6, V26, lsxScratchReg, V6); + VFMADD_S(V7, V27, lsxScratchReg, V7); + break; + case 3: + VREPLVEI_W(lsxScratchReg, lsxWeightRegs[0], 3); + VFMADD_S(V4, V28, lsxScratchReg, V4); + VFMADD_S(V5, V29, lsxScratchReg, V5); + VFMADD_S(V6, V30, lsxScratchReg, V6); + VFMADD_S(V7, V31, lsxScratchReg, V7); + break; + default: + // Matrices 4+ need to be loaded from memory. + VLD(V8, scratchReg, 0); + VLD(V9, scratchReg, 16); + VLD(V10, scratchReg, 32); + VLD(V11, scratchReg, 48); + ADDI_D(scratchReg, scratchReg, 64); + VREPLVEI_W(lsxScratchReg, lsxWeightRegs[i >> 2], i & 3); + VFMADD_S(V4, V8, lsxScratchReg, V4); + VFMADD_S(V5, V9, lsxScratchReg, V5); + VFMADD_S(V6, V10, lsxScratchReg, V6); + VFMADD_S(V7, V11, lsxScratchReg, V7); + break; + } + } +} + +void VertexDecoderJitCache::Jit_WeightsU8() { + // Basic implementation - a byte at a time. + // TODO: Could optimize with unaligned load/store + int j; + for (j = 0; j < dec_->nweights; j++) { + LD_B(tempReg1, srcReg, dec_->weightoff + j); + ST_B(tempReg1, dstReg, dec_->decFmt.w0off + j); + } + while (j & 3) { + ST_B(R_ZERO, dstReg, dec_->decFmt.w0off + j); + j++; + } +} + +void VertexDecoderJitCache::Jit_WeightsU16() { + // Basic implementation - a short at a time. + // TODO: Could optimize with unaligned load/store + int j; + for (j = 0; j < dec_->nweights; j++) { + LD_H(tempReg1, srcReg, dec_->weightoff + j * 2); + ST_H(tempReg1, dstReg, dec_->decFmt.w0off + j * 2); + } + while (j & 3) { + ST_H(R_ZERO, dstReg, dec_->decFmt.w0off + j * 2); + j++; + } +} + +void VertexDecoderJitCache::Jit_WeightsFloat() { + int j; + for (j = 0; j < dec_->nweights; j++) { + LD_W(tempReg1, srcReg, dec_->weightoff + j * 4); + ST_W(tempReg1, dstReg, dec_->decFmt.w0off + j * 4); + } + while (j & 3) { // Zero additional weights rounding up to 4. + ST_W(R_ZERO, dstReg, dec_->decFmt.w0off + j * 4); + j++; + } +} + +void VertexDecoderJitCache::Jit_WeightsU8Skin() { + // Weight is first so srcReg is correct. + switch (dec_->nweights) { + case 1: LD_BU(scratchReg, srcReg, 0); break; + case 2: LD_HU(scratchReg, srcReg, 0); break; + default: + // For 3, we over read, for over 4, we read more later. + LD_WU(scratchReg, srcReg, 0); + break; + } + + VINSGR2VR_D(lsxScratchReg, scratchReg, 0); + VSLLWIL_HU_BU(lsxScratchReg, lsxScratchReg, 0); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_WU(lsxWeightRegs[0], lsxScratchReg); + VFMUL_S(lsxWeightRegs[0], lsxWeightRegs[0], by128LSX); + + if (dec_->nweights > 4) { + switch (dec_->nweights) { + case 5: LD_BU(scratchReg, srcReg, 4); break; + case 6: LD_HU(scratchReg, srcReg, 4); break; + case 7: + case 8: + LD_WU(scratchReg, srcReg, 4); + break; + } + VINSGR2VR_D(lsxScratchReg, scratchReg, 0); + VSLLWIL_HU_BU(lsxScratchReg, lsxScratchReg, 0); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_WU(lsxWeightRegs[1], lsxScratchReg); + VFMUL_S(lsxWeightRegs[1], lsxWeightRegs[1], by128LSX); + } + Jit_ApplyWeights(); +} + +void VertexDecoderJitCache::Jit_WeightsU16Skin() { + switch (dec_->nweights) { + case 1: LD_HU(scratchReg, srcReg, 0); break; + case 2: LD_WU(scratchReg, srcReg, 0); break; + default: + // For 3, we over read, for over 4, we read more later. + LD_D(scratchReg, srcReg, 0); + break; + } + VINSGR2VR_D(lsxScratchReg, scratchReg, 0); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_WU(lsxWeightRegs[0], lsxScratchReg); + VFMUL_S(lsxWeightRegs[0], lsxWeightRegs[0], by32768LSX); + + if (dec_->nweights > 4) { + switch (dec_->nweights) { + case 5: LD_HU(scratchReg, srcReg, 0); break; + case 6: LD_WU(scratchReg, srcReg, 0); break; + case 7: + case 8: + LD_D(scratchReg, srcReg, 0); + break; + } + VINSGR2VR_D(lsxScratchReg, scratchReg, 0); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_WU(lsxWeightRegs[1], lsxScratchReg); + VFMUL_S(lsxWeightRegs[1], lsxWeightRegs[1], by32768LSX); + } + Jit_ApplyWeights(); +} + +void VertexDecoderJitCache::Jit_WeightsFloatSkin() { + switch (dec_->nweights) { + case 1: + FLD_S(F12, srcReg, 0); // Load 32-bits to lsxWeightRegs[0] + break; + case 2: + FLD_D(F12, srcReg, 0); // Load 64-bits to lsxWeightRegs[0] + break; + case 3: + case 4: + VLD(lsxWeightRegs[0], srcReg, 0); + break; + + case 5: + VLD(lsxWeightRegs[0], srcReg, 0); + FLD_S(F13, srcReg, 16); // Load 32-bits to lsxWeightRegs[1] + break; + case 6: + VLD(lsxWeightRegs[0], srcReg, 0); + FLD_D(F13, srcReg, 16); // Load 64-bits to lsxWeightRegs[1] + break; + case 7: + case 8: + VLD(lsxWeightRegs[0], srcReg, 0); + VLD(lsxWeightRegs[1], srcReg, 16); + break; + } + Jit_ApplyWeights(); +} + +void VertexDecoderJitCache::Jit_Color8888() { + LD_WU(tempReg1, srcReg, dec_->coloff); + + // Set tempReg2=-1 if full alpha, 0 otherwise. + SRLI_D(tempReg2, tempReg1, 24); + SLTUI(tempReg2, tempReg2, 0xFF); + ADDI_D(tempReg2, tempReg2, -1); + + // Now use that as a mask to clear fullAlpha. + AND(fullAlphaReg, fullAlphaReg, tempReg2); + + ST_W(tempReg1, dstReg, dec_->decFmt.c0off); +} + +void VertexDecoderJitCache::Jit_Color4444() { + LD_HU(tempReg1, srcReg, dec_->coloff); + + // Red... + ANDI(tempReg2, tempReg1, 0x0F); + // Move green left to position 8. + ANDI(tempReg3, tempReg1, 0xF0); + SLLI_D(tempReg3, tempReg3, 4); + OR(tempReg2, tempReg2, tempReg3); + // For blue, we modify tempReg1 since immediates are sign extended after 11 bits. + SRLI_D(tempReg1, tempReg1, 8); + ANDI(tempReg3, tempReg1, 0x0F); + SLLI_D(tempReg3, tempReg3, 16); + OR(tempReg2, tempReg2, tempReg3); + // And now alpha, moves 20 to get to 24. + ANDI(tempReg3, tempReg1, 0xF0); + SLLI_D(tempReg3, tempReg3, 20); + OR(tempReg2, tempReg2, tempReg3); + + // Now we swizzle. + SLLI_D(tempReg3, tempReg2, 4); + OR(tempReg2, tempReg2, tempReg3); + + // Color is down, now let's say the fullAlphaReg flag from tempReg1 (still has alpha.) + // Set tempReg1=-1 if full alpha, 0 otherwise. + SLTUI(tempReg1, tempReg1, 0xF0); + ADDI_D(tempReg1, tempReg1, -1); + + // Now use that as a mask to clear fullAlpha. + AND(fullAlphaReg, fullAlphaReg, tempReg1); + + ST_W(tempReg2, dstReg, dec_->decFmt.c0off); +} + +void VertexDecoderJitCache::Jit_Color565() { + LD_HU(tempReg1, srcReg, dec_->coloff); + + // Start by extracting green. + SRLI_D(tempReg2, tempReg1, 5); + ANDI(tempReg2, tempReg2, 0x3F); + // And now swizzle 6 -> 8, using a wall to clear bits. + SRLI_D(tempReg3, tempReg2, 4); + SLLI_D(tempReg3, tempReg3, 8); + SLLI_D(tempReg2, tempReg2, 2 + 8); + OR(tempReg2, tempReg2, tempReg3); + + // Now pull blue out using a wall to isolate it. + SRLI_D(tempReg3, tempReg1, 11); + // And now isolate red and combine them. + ANDI(tempReg1, tempReg1, 0x1F); + SLLI_D(tempReg3, tempReg3, 16); + OR(tempReg1, tempReg1, tempReg3); + // Now we swizzle them together. + SRLI_D(tempReg3, tempReg1, 2); + SLLI_D(tempReg1, tempReg1, 3); + OR(tempReg1, tempReg1, tempReg3); + // But we have to clear the bits now which is annoying. + LI(tempReg3, 0x00FF00FF); + AND(tempReg1, tempReg1, tempReg3); + + // Now add green back in, and then make an alpha FF and add it too. + OR(tempReg1, tempReg1, tempReg2); + LI(tempReg3, (s32)0xFF000000); + OR(tempReg1, tempReg1, tempReg3); + + ST_W(tempReg1, dstReg, dec_->decFmt.c0off); +} + +void VertexDecoderJitCache::Jit_Color5551() { + LD_HU(tempReg1, srcReg, dec_->coloff); + + // Separate each color. + SRLI_D(tempReg2, tempReg1, 5); + SRLI_D(tempReg3, tempReg1, 10); + + // Set scratchReg to -1 if the alpha bit is set. + SLLI_W(scratchReg, tempReg1, 16); + SRAI_W(scratchReg, scratchReg, 31); + // Now we can mask the flag. + AND(fullAlphaReg, fullAlphaReg, scratchReg); + + // Let's move alpha into position. + SLLI_D(scratchReg, scratchReg, 24); + + // Mask each. + ANDI(tempReg1, tempReg1, 0x1F); + ANDI(tempReg2, tempReg2, 0x1F); + ANDI(tempReg3, tempReg3, 0x1F); + // And shift into position. + SLLI_D(tempReg2, tempReg2, 8); + SLLI_D(tempReg3, tempReg3, 16); + // Combine RGB together. + OR(tempReg1, tempReg1, tempReg2); + OR(tempReg1, tempReg1, tempReg3); + // Swizzle our 5 -> 8 + SRLI_D(tempReg2, tempReg1, 2); + SLLI_D(tempReg1, tempReg1, 3); + // Mask out the overflow in tempReg2 and combine. + LI(tempReg3, 0x00070707); + AND(tempReg2, tempReg2, tempReg3); + OR(tempReg1, tempReg1, tempReg2); + + // Add in alpha and we're done. + OR(tempReg1, tempReg1, scratchReg); + + ST_W(tempReg1, dstReg, dec_->decFmt.c0off); +} + +void VertexDecoderJitCache::Jit_TcU16ThroughToFloat() { + LD_HU(tempReg1, srcReg, dec_->tcoff + 0); + LD_HU(tempReg2, srcReg, dec_->tcoff + 2); + + auto updateSide = [&](LoongArch64Reg src, bool greater, LoongArch64Reg dst) { + FixupBranch skip = BLT(greater ? dst : src, greater ? src : dst); + MOVE(dst, src); + SetJumpTarget(skip); + }; + + updateSide(tempReg1, false, boundsMinUReg); + updateSide(tempReg1, true, boundsMaxUReg); + updateSide(tempReg2, false, boundsMinVReg); + updateSide(tempReg2, true, boundsMaxVReg); + + VINSGR2VR_W(lsxScratchReg, tempReg1, 0); + VINSGR2VR_W(lsxScratchReg, tempReg2, 1); + VFFINT_S_WU(lsxScratchReg, lsxScratchReg); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); +} + +void VertexDecoderJitCache::Jit_TcFloatThrough() { + // Just copy 64 bits. Might be nice if we could detect misaligned load perf. + LD_W(tempReg1, srcReg, dec_->tcoff); + LD_W(tempReg2, srcReg, dec_->tcoff + 4); + ST_W(tempReg1, dstReg, dec_->decFmt.uvoff); + ST_W(tempReg2, dstReg, dec_->decFmt.uvoff + 4); +} + +void VertexDecoderJitCache::Jit_TcFloat() { + // Just copy 64 bits. Might be nice if we could detect misaligned load perf. + LD_W(tempReg1, srcReg, dec_->tcoff); + LD_W(tempReg2, srcReg, dec_->tcoff + 4); + ST_W(tempReg1, dstReg, dec_->decFmt.uvoff); + ST_W(tempReg2, dstReg, dec_->decFmt.uvoff + 4); +} + +void VertexDecoderJitCache::Jit_TcU8Prescale() { + LD_HU(scratchReg, srcReg, dec_->tcoff); + VINSGR2VR_H(lsxScratchReg, scratchReg, 0); + VSLLWIL_HU_BU(lsxScratchReg, lsxScratchReg, 0); // Widen to 16-bit + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); // Widen to 32-bit + VFFINT_S_WU(lsxScratchReg, lsxScratchReg); + VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); // save the lower 64-bit of lsxScratchReg +} + +void VertexDecoderJitCache::Jit_TcU8ToFloat() { + LD_HU(scratchReg, srcReg, dec_->tcoff); + VINSGR2VR_H(lsxScratchReg, scratchReg, 0); + VSLLWIL_HU_BU(lsxScratchReg, lsxScratchReg, 0); // Widen to 16-bit + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); // Widen to 32-bit + VFFINT_S_WU(lsxScratchReg, lsxScratchReg); + VFMUL_S(lsxScratchReg, lsxScratchReg, by128LSX); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); // save the lower 64-bit of lsxScratchReg +} + +void VertexDecoderJitCache::Jit_TcU16Prescale() { + FLD_S(fpSrc[0], srcReg, dec_->tcoff); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); // Widen to 32-bit + VFFINT_S_WU(lsxScratchReg, lsxScratchReg); + VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); // save the lower 64-bit of lsxScratchReg +} + +void VertexDecoderJitCache::Jit_TcU16ToFloat() { + FLD_S(fpSrc[0], srcReg, dec_->tcoff); + VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); // Widen to 32-bit + VFFINT_S_WU(lsxScratchReg, lsxScratchReg); + VFMUL_S(lsxScratchReg, lsxScratchReg, by32768LSX); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); // save the lower 64-bit of lsxScratchReg +} + +void VertexDecoderJitCache::Jit_TcFloatPrescale() { + FLD_D(fpSrc[0], srcReg, dec_->tcoff); // load to the lower 64-bit of lsxScratchReg + VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); + FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); // save the lower 64-bit of lsxScratchReg +} + +void VertexDecoderJitCache::Jit_PosS8() { + Jit_AnyS8ToFloat(dec_->posoff); + VST(lsxScratchReg, dstReg, dec_->decFmt.posoff); +} + +void VertexDecoderJitCache::Jit_PosS16() { + Jit_AnyS16ToFloat(dec_->posoff); + VST(lsxScratchReg, dstReg, dec_->decFmt.posoff); +} + +void VertexDecoderJitCache::Jit_PosFloat() { + // Just copy 12 bytes, play with over read/write later. + LD_W(tempReg1, srcReg, dec_->posoff + 0); + LD_W(tempReg2, srcReg, dec_->posoff + 4); + LD_W(tempReg3, srcReg, dec_->posoff + 8); + ST_W(tempReg1, dstReg, dec_->decFmt.posoff + 0); + ST_W(tempReg2, dstReg, dec_->decFmt.posoff + 4); + ST_W(tempReg3, dstReg, dec_->decFmt.posoff + 8); +} + +void VertexDecoderJitCache::Jit_PosS8Through() { + // 8-bit positions in throughmode always decode to 0, depth included. + ST_W(R_ZERO, dstReg, dec_->decFmt.posoff + 0); + ST_W(R_ZERO, dstReg, dec_->decFmt.posoff + 4); + ST_W(R_ZERO, dstReg, dec_->decFmt.posoff + 8); +} + +void VertexDecoderJitCache::Jit_PosS16Through() { + // Start with X and Y (which are signed.) + LD_H(tempReg1, srcReg, dec_->posoff + 0); + LD_H(tempReg2, srcReg, dec_->posoff + 2); + // This one, Z, has to be unsigned. + LD_HU(tempReg3, srcReg, dec_->posoff + 4); + MOVGR2FR_W(fpSrc[0], tempReg1); + MOVGR2FR_W(fpSrc[1], tempReg2); + MOVGR2FR_W(fpSrc[2], tempReg3); + FFINT_S_W(fpSrc[0], fpSrc[0]); + FFINT_S_W(fpSrc[1], fpSrc[1]); + FFINT_S_W(fpSrc[2], fpSrc[2]); + FST_S(fpSrc[0], dstReg, dec_->decFmt.posoff + 0); + FST_S(fpSrc[1], dstReg, dec_->decFmt.posoff + 4); + FST_S(fpSrc[2], dstReg, dec_->decFmt.posoff + 8); +} + +void VertexDecoderJitCache::Jit_PosFloatThrough() { + // Start by copying 8 bytes, then handle Z separately to clamp it. + LD_W(tempReg1, srcReg, dec_->posoff + 0); + LD_W(tempReg2, srcReg, dec_->posoff + 4); + FLD_S(fpSrc[2], srcReg, dec_->posoff + 8); + ST_W(tempReg1, dstReg, dec_->decFmt.posoff + 0); + ST_W(tempReg2, dstReg, dec_->decFmt.posoff + 4); + + // Load the constant zero and clamp. + MOVGR2FR_W(fpScratchReg, R_ZERO); + // Is it worth a seperate reg? + LI(scratchReg, const65535); + MOVGR2FR_W(fpScratchReg2, scratchReg); + FMAX_S(fpSrc[2], fpSrc[2], fpScratchReg); + FMIN_S(fpSrc[2], fpSrc[2], fpScratchReg2); + FST_S(fpSrc[2], dstReg, dec_->decFmt.posoff + 8); +} + +void VertexDecoderJitCache::Jit_NormalS8() { + LD_W(tempReg1, srcReg, dec_->nrmoff + 0); + BSTRINS_D(tempReg1, R_ZERO, 31, 24); + ST_W(tempReg1, dstReg, dec_->decFmt.nrmoff + 0); +} + +// Copy 6 bytes and then 2 zeroes. +void VertexDecoderJitCache::Jit_NormalS16() { + LD_D(tempReg1, srcReg, dec_->nrmoff + 0); + BSTRINS_D(tempReg1, R_ZERO, 63, 48); + ST_D(tempReg1, dstReg, dec_->decFmt.nrmoff + 0); +} + +void VertexDecoderJitCache::Jit_NormalFloat() { + // Just copy 12 bytes, play with over read/write later. + LD_D(tempReg1, srcReg, dec_->nrmoff + 0); + LD_W(tempReg2, srcReg, dec_->nrmoff + 8); + ST_D(tempReg1, dstReg, dec_->decFmt.nrmoff + 0); + ST_W(tempReg2, dstReg, dec_->decFmt.nrmoff + 8); +} + +void VertexDecoderJitCache::Jit_NormalS8Skin() { + Jit_AnyS8ToFloat(dec_->nrmoff); + Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); +} + +void VertexDecoderJitCache::Jit_NormalS16Skin() { + Jit_AnyS16ToFloat(dec_->nrmoff); + Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); +} + +void VertexDecoderJitCache::Jit_NormalFloatSkin() { + VLD(lsxScratchReg, srcReg, dec_->nrmoff); + Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); +} + +void VertexDecoderJitCache::Jit_PosS8Skin() { + Jit_AnyS8ToFloat(dec_->posoff); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); +} + +void VertexDecoderJitCache::Jit_PosS16Skin() { + Jit_AnyS16ToFloat(dec_->posoff); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); +} + +void VertexDecoderJitCache::Jit_PosFloatSkin() { + VLD(lsxScratchReg, srcReg, dec_->posoff); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); +} + +void VertexDecoderJitCache::Jit_AnyS8ToFloat(int srcoff) { + FLD_S(fpSrc[0], srcReg, srcoff); // Directly load to lsxScratchReg. + VSLLWIL_H_B(lsxScratchReg, lsxScratchReg, 0); + VSLLWIL_W_H(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_W(lsxScratchReg, lsxScratchReg); + VFMUL_S(lsxScratchReg, lsxScratchReg, by128LSX); +} + +void VertexDecoderJitCache::Jit_AnyS16ToFloat(int srcoff) { + FLD_D(fpSrc[0], srcReg, srcoff); // Directly load to lsxScratchReg. + VSLLWIL_W_H(lsxScratchReg, lsxScratchReg, 0); + VFFINT_S_W(lsxScratchReg, lsxScratchReg); + VFMUL_S(lsxScratchReg, lsxScratchReg, by32768LSX); +} + +void VertexDecoderJitCache::Jit_WriteMatrixMul(int outOff, bool pos) { + // Multiply lsxScratchReg with the matrix sitting in V4-V7. + VREPLVEI_W(lsxScratchReg2, lsxScratchReg, 0); + VFMUL_S(accLSX, V4, lsxScratchReg2); + VREPLVEI_W(lsxScratchReg2, lsxScratchReg, 1); + VFMADD_S(accLSX, V5, lsxScratchReg2, accLSX); + VREPLVEI_W(lsxScratchReg2, lsxScratchReg, 2); + VFMADD_S(accLSX, V6, lsxScratchReg2, accLSX); + if (pos) { + VFADD_S(accLSX, accLSX, V7); + } + VST(accLSX, dstReg, outOff); +} + +#endif // PPSSPP_ARCH(LOONGARCH64) \ No newline at end of file diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 6cedc14bf3..aab1d12f9c 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -408,6 +408,7 @@ + true diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 924eb98457..04c674b20a 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -524,6 +524,9 @@ Common + + Common + diff --git a/GPU/Software/RasterizerRegCache.cpp b/GPU/Software/RasterizerRegCache.cpp index 851f6d1ceb..52ef2ae667 100644 --- a/GPU/Software/RasterizerRegCache.cpp +++ b/GPU/Software/RasterizerRegCache.cpp @@ -147,6 +147,8 @@ void RegCache::SetupABI(const std::vector &args, bool forceRetain) { #endif #elif PPSSPP_ARCH(RISCV64) _assert_msg_(false, "Not yet implemented (no vector calling standard yet)"); +#elif PPSSPP_ARCH(LOONGARCH64) + _assert_msg_(false, "Not yet implemented"); #elif PPSSPP_ARCH(MIPS) _assert_msg_(false, "Not yet implemented"); #else diff --git a/GPU/Software/RasterizerRegCache.h b/GPU/Software/RasterizerRegCache.h index 20c9263687..252d3c09f2 100644 --- a/GPU/Software/RasterizerRegCache.h +++ b/GPU/Software/RasterizerRegCache.h @@ -45,6 +45,8 @@ #include "Common/MipsEmitter.h" #elif PPSSPP_ARCH(RISCV64) #include "Common/RiscVEmitter.h" +#elif PPSSPP_ARCH(LOONGARCH64) +#include "Common/LoongArch64Emitter.h" #else #include "Common/FakeEmitter.h" #endif @@ -63,6 +65,8 @@ typedef Gen::XCodeBlock BaseCodeBlock; typedef MIPSGen::MIPSCodeBlock BaseCodeBlock; #elif PPSSPP_ARCH(RISCV64) typedef RiscVGen::RiscVCodeBlock BaseCodeBlock; +#elif PPSSPP_ARCH(LOONGARCH64) +typedef LoongArch64Gen::LoongArch64CodeBlock BaseCodeBlock; #else typedef FakeGen::FakeXCodeBlock BaseCodeBlock; #endif @@ -175,6 +179,9 @@ struct RegCache { #elif PPSSPP_ARCH(RISCV64) typedef RiscVGen::RiscVReg Reg; static constexpr Reg REG_INVALID_VALUE = RiscVGen::INVALID_REG; +#elif PPSSPP_ARCH(LOONGARCH64) + typedef LoongArch64Gen::LoongArch64Reg Reg; + static constexpr Reg REG_INVALID_VALUE = LoongArch64Gen::INVALID_REG; #else typedef int Reg; static constexpr Reg REG_INVALID_VALUE = -1;