// 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 lsxScratchReg3 = V10; static const LoongArch64Reg lsxScratchReg4 = V11; 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_TcU8MorphToFloat, &VertexDecoderJitCache::Jit_TcU8MorphToFloat}, {&VertexDecoder::Step_TcU16MorphToFloat, &VertexDecoderJitCache::Jit_TcU16MorphToFloat}, {&VertexDecoder::Step_TcFloatMorph, &VertexDecoderJitCache::Jit_TcFloatMorph}, {&VertexDecoder::Step_TcU8PrescaleMorph, &VertexDecoderJitCache::Jit_TcU8PrescaleMorph}, {&VertexDecoder::Step_TcU16PrescaleMorph, &VertexDecoderJitCache::Jit_TcU16PrescaleMorph}, {&VertexDecoder::Step_TcFloatPrescaleMorph, &VertexDecoderJitCache::Jit_TcFloatPrescaleMorph}, {&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_NormalS8MorphSkin, &VertexDecoderJitCache::Jit_NormalS8MorphSkin}, {&VertexDecoder::Step_NormalS16MorphSkin, &VertexDecoderJitCache::Jit_NormalS16MorphSkin}, {&VertexDecoder::Step_NormalFloatMorphSkin, &VertexDecoderJitCache::Jit_NormalFloatMorphSkin}, {&VertexDecoder::Step_PosS8Morph, &VertexDecoderJitCache::Jit_PosS8Morph}, {&VertexDecoder::Step_PosS16Morph, &VertexDecoderJitCache::Jit_PosS16Morph}, {&VertexDecoder::Step_PosFloatMorph, &VertexDecoderJitCache::Jit_PosFloatMorph}, {&VertexDecoder::Step_PosS8MorphSkin, &VertexDecoderJitCache::Jit_PosS8MorphSkin}, {&VertexDecoder::Step_PosS16MorphSkin, &VertexDecoderJitCache::Jit_PosS16MorphSkin}, {&VertexDecoder::Step_PosFloatMorphSkin, &VertexDecoderJitCache::Jit_PosFloatMorphSkin}, {&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); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[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_Color8888Morph() { LI(tempReg1, &gstate_c.morphWeights[0]); VXOR_V(lsxScratchReg4, lsxScratchReg4, lsxScratchReg4); bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; FLD_S((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + dec_->coloff); VILVL_B(reg, lsxScratchReg4, reg); VILVL_H(reg, lsxScratchReg4, reg); VFFINT_S_W(reg, reg); // And now the weight. VLDREPL_W(lsxScratchReg3, tempReg1, n * sizeof(float)); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg,lsxScratchReg2); } else { first = false; } } Jit_WriteMorphColor(dec_->decFmt.c0off); } void VertexDecoderJitCache::Jit_Color4444Morph() { LI(tempReg1, &gstate_c.morphWeights[0]); VXOR_V(lsxScratchReg4, lsxScratchReg4, lsxScratchReg4); LI(tempReg2, 0xf00ff00f); // color 4444 mask VREPLGR2VR_W(V8, tempReg2); LI(tempReg3, 255.0f / 15.0f); // by color 4444 VREPLGR2VR_W(V9, tempReg2); bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; FLD_S((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + dec_->coloff); VILVL_B(reg, reg, reg); VAND_V(reg, reg, V8); VEXTRINS_W(lsxScratchReg3, reg, 0); VSLLI_H(lsxScratchReg3, lsxScratchReg3, 4); VOR_V(reg, reg,lsxScratchReg3); VSRLI_W(reg, reg, 4); VILVL_B(reg, lsxScratchReg4, reg); VILVL_H(reg, lsxScratchReg4, reg); VFFINT_S_W(reg, reg); VFMUL_S(reg, reg, V9); // And now the weight. VLDREPL_W(lsxScratchReg3, tempReg1, n * sizeof(float)); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg,lsxScratchReg2); } else { first = false; } } Jit_WriteMorphColor(dec_->decFmt.c0off); } // The mask is intentionally in reverse order (but skips A.) alignas(16) static const u32 color565Mask[4] = { 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000, }; alignas(16) static const float byColor565[4] = { 255.0f / 31.0f, 255.0f / 63.0f, 255.0f / 31.0f, 255.0f / 1.0f, }; void VertexDecoderJitCache::Jit_Color565Morph() { LI(tempReg1, &gstate_c.morphWeights[0]); LI(tempReg2, &color565Mask[0]); VLD(V8, tempReg2, 0); LI(tempReg2, &byColor565[0]); VLD(V9, tempReg2, 0); bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg3; // Spread it out into each lane. We end up with it reversed (R high, A low.) // Below, we shift out each lane from low to high and reverse them. VLDREPL_W(lsxScratchReg2, srcReg, dec_->onesize_ * n + dec_->coloff); VAND_V(lsxScratchReg2, lsxScratchReg2, V8); // Alpha handled in Jit_WriteMorphColor. // Blue first. VEXTRINS_W(reg, lsxScratchReg2, 0); VSRLI_W(reg, reg, 6); VSHUF4I_W(reg, reg, 3 << 6); // Green, let's shift it into the right lane first. VEXTRINS_W(reg, lsxScratchReg2, 1); VSRLI_W(reg, reg, 5); VSHUF4I_W(reg, reg, (3 << 6 | 2 << 4)); // Last one, red. VEXTRINS_W(reg, lsxScratchReg2, 2); VFFINT_S_W(reg, reg); VFMUL_S(reg, reg, V9); // And now the weight. VLDREPL_W(lsxScratchReg2, tempReg1, n * sizeof(float)); VFMUL_S(reg, reg, lsxScratchReg2); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg, lsxScratchReg3); } else { first = false; } } Jit_WriteMorphColor(dec_->decFmt.c0off, false); } // The mask is intentionally in reverse order. alignas(16) static const u32 color5551Mask[4] = { 0x00008000, 0x00007c00, 0x000003e0, 0x0000001f, }; alignas(16) static const float byColor5551[4] = { 255.0f / 31.0f, 255.0f / 31.0f, 255.0f / 31.0f, 255.0f / 1.0f, }; void VertexDecoderJitCache::Jit_Color5551Morph() { LI(tempReg1, &gstate_c.morphWeights[0]); LI(tempReg2, &color5551Mask[0]); VLD(V8, tempReg2, 0); LI(tempReg2, &byColor5551[0]); VLD(V9, tempReg2, 0); bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg3; // Spread it out into each lane. VLDREPL_W(lsxScratchReg2, srcReg, dec_->onesize_ * n + dec_->coloff); VAND_V(lsxScratchReg2, lsxScratchReg2, V8); // Alpha first. VEXTRINS_W(reg, lsxScratchReg2, 0); VSRLI_W(reg, reg, 5); VSHUF4I_W(reg, reg, 0); // Blue, let's shift it into the right lane first. VEXTRINS_W(reg, lsxScratchReg2, 1); VSRLI_W(reg, reg, 5); VSHUF4I_W(reg, reg, 3 << 6); // Green. VEXTRINS_W(reg, lsxScratchReg2, 2); VSRLI_W(reg, reg, 5); VSHUF4I_W(reg, reg, (3 << 6 | 2 << 4)); // Last one, red. VEXTRINS_W(reg, lsxScratchReg2, 3); VFFINT_S_W(reg, reg); VFMUL_S(reg, reg, V9); // And now the weight. VLDREPL_W(lsxScratchReg2, tempReg1, n * sizeof(float)); VFMUL_S(reg, reg, lsxScratchReg2); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg, lsxScratchReg3); } else { first = false; } } Jit_WriteMorphColor(dec_->decFmt.c0off); } void VertexDecoderJitCache::Jit_WriteMorphColor(int outOff, bool checkAlpha) { // Pack back into a u32, with saturation. VFTINT_W_S(lsxScratchReg, lsxScratchReg); VSSRLNI_H_W(lsxScratchReg, lsxScratchReg, 0); VSSRLNI_BU_H(lsxScratchReg, lsxScratchReg, 0); VPICKVE2GR_W(tempReg1, lsxScratchReg, 0); // TODO: Could be optimize with a SLLI on fullAlphaReg SLLI_D(tempReg2, fullAlphaReg, 24); if (checkAlpha) { SLTU(tempReg3, tempReg1, tempReg2); FixupBranch skip = BEQZ(tempReg3); XOR(fullAlphaReg, fullAlphaReg, fullAlphaReg); SetJumpTarget(skip); } else { // Force alpha to full if we're not checking it. OR(tempReg1, tempReg1, tempReg2); } ST_W(tempReg1, dstReg, outOff); } 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 ? src : dst, greater ? dst : src); MOVE(dst, src); SetJumpTarget(skip); }; updateSide(tempReg1, false, boundsMinUReg); updateSide(tempReg1, true, boundsMaxUReg); updateSide(tempReg2, false, boundsMinVReg); updateSide(tempReg2, true, boundsMaxVReg); VINSGR2VR_H(lsxScratchReg, tempReg1, 0); VINSGR2VR_H(lsxScratchReg, tempReg2, 1); VSLLWIL_WU_HU(lsxScratchReg, lsxScratchReg, 0); 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_TcAnyMorph(int bits) { LI(tempReg1, &gstate_c.morphWeights[0]); VXOR_V(lsxScratchReg4, lsxScratchReg4, lsxScratchReg4); bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; // Load the actual values and convert to float. if (bits == 32) { // Two floats FLD_D((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + dec_->tcoff); } else { if (bits == 8) { LD_HU(tempReg2, srcReg, dec_->onesize_ * n + dec_->tcoff); VINSGR2VR_W(reg, tempReg2, 0); VILVL_B(reg, lsxScratchReg4, reg); } else { FLD_S((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + dec_->tcoff); } VILVL_H(reg, lsxScratchReg4, reg); VFFINT_S_W(reg, reg); } // And now scale by the weight. VLDREPL_W(lsxScratchReg3, tempReg1, n * sizeof(float)); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg,lsxScratchReg2); } else { first = false; } } } void VertexDecoderJitCache::Jit_TcU8MorphToFloat() { Jit_TcAnyMorph(8); // They were all added (weighted) pre-normalize, we normalize once here. VFMUL_S(lsxScratchReg, lsxScratchReg, by128LSX); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } void VertexDecoderJitCache::Jit_TcU16MorphToFloat() { Jit_TcAnyMorph(16); // They were all added (weighted) pre-normalize, we normalize once here. VFMUL_S(lsxScratchReg, lsxScratchReg, by32768LSX); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } void VertexDecoderJitCache::Jit_TcFloatMorph() { Jit_TcAnyMorph(32); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } void VertexDecoderJitCache::Jit_TcU8PrescaleMorph() { Jit_TcAnyMorph(8); // The scale takes into account the u8 normalization. VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } void VertexDecoderJitCache::Jit_TcU16PrescaleMorph() { Jit_TcAnyMorph(16); // The scale takes into account the u16 normalization. VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } void VertexDecoderJitCache::Jit_TcFloatPrescaleMorph() { Jit_TcAnyMorph(32); VFMADD_S(lsxScratchReg, lsxScratchReg, lsxScaleOffsetReg, lsxOffsetScaleReg); FST_D(fpSrc[0], dstReg, dec_->decFmt.uvoff); } 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); } void VertexDecoderJitCache::Jit_AnyS8Morph(int srcoff, int dstoff) { LI(tempReg1, &gstate_c.morphWeights[0]); // Sum into lsxScratchReg. bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; // Okay, first convert to floats. FLD_S((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + srcoff); VSLLWIL_H_B(reg, reg, 0); VSLLWIL_W_H(reg, reg, 0); VFFINT_S_W(reg, reg); // Now, It's time to multiply by the weight and 1.0f/128.0f. VLDREPL_W(lsxScratchReg3, tempReg1, sizeof(float) * n); VFMUL_S(lsxScratchReg3, lsxScratchReg3, by128LSX); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg, lsxScratchReg2); } else { first = false; } } if (dstoff >= 0) VST(lsxScratchReg, dstReg, dstoff); } void VertexDecoderJitCache::Jit_AnyS16Morph(int srcoff, int dstoff) { LI(tempReg1, &gstate_c.morphWeights[0]); // Sum into lsxScratchReg. bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; // Okay, first convert to floats. FLD_D((LoongArch64Reg)(DecodeReg(reg) + F0), srcReg, dec_->onesize_ * n + srcoff); VSLLWIL_W_H(reg, reg, 0); VFFINT_S_W(reg, reg); // Now, It's time to multiply by the weight and 1.0f/32768.0f. VLDREPL_W(lsxScratchReg3, tempReg1, sizeof(float) * n); VFMUL_S(lsxScratchReg3, lsxScratchReg3, by32768LSX); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg, lsxScratchReg2); } else { first = false; } } if (dstoff >= 0) VST(lsxScratchReg, dstReg, dstoff); } void VertexDecoderJitCache::Jit_AnyFloatMorph(int srcoff, int dstoff) { LI(tempReg1, &gstate_c.morphWeights[0]); // Sum into lsxScratchReg. bool first = true; for (int n = 0; n < dec_->morphcount; ++n) { const LoongArch64Reg reg = first ? lsxScratchReg : lsxScratchReg2; VLD(reg, srcReg, dec_->onesize_ * n + srcoff); VLDREPL_W(lsxScratchReg3, tempReg1, sizeof(float) * n); VFMUL_S(reg, reg, lsxScratchReg3); if (!first) { VFADD_S(lsxScratchReg, lsxScratchReg, lsxScratchReg2); } else { first = false; } } if (dstoff >= 0) VST(lsxScratchReg, dstReg, dstoff); } void VertexDecoderJitCache::Jit_PosS8Morph() { Jit_AnyS8Morph(dec_->posoff, dec_->decFmt.posoff); } void VertexDecoderJitCache::Jit_PosS16Morph() { Jit_AnyS16Morph(dec_->posoff, dec_->decFmt.posoff); } void VertexDecoderJitCache::Jit_PosFloatMorph() { Jit_AnyFloatMorph(dec_->posoff, dec_->decFmt.posoff); } void VertexDecoderJitCache::Jit_PosS8MorphSkin() { Jit_AnyS8Morph(dec_->posoff, -1); Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosS16MorphSkin() { Jit_AnyS16Morph(dec_->posoff, -1); Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosFloatMorphSkin() { Jit_AnyFloatMorph(dec_->posoff, -1); Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_NormalS8Morph() { Jit_AnyS8Morph(dec_->nrmoff, dec_->decFmt.nrmoff); } void VertexDecoderJitCache::Jit_NormalS16Morph() { Jit_AnyS16Morph(dec_->nrmoff, dec_->decFmt.nrmoff); } void VertexDecoderJitCache::Jit_NormalFloatMorph() { Jit_AnyFloatMorph(dec_->nrmoff, dec_->decFmt.nrmoff); } void VertexDecoderJitCache::Jit_NormalS8MorphSkin() { Jit_AnyS8Morph(dec_->nrmoff, -1); Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); } void VertexDecoderJitCache::Jit_NormalS16MorphSkin() { Jit_AnyS16Morph(dec_->nrmoff, -1); Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); } void VertexDecoderJitCache::Jit_NormalFloatMorphSkin() { Jit_AnyFloatMorph(dec_->nrmoff, -1); Jit_WriteMatrixMul(dec_->decFmt.nrmoff, false); } #endif // PPSSPP_ARCH(LOONGARCH64)