Files
ppsspp/GPU/Common/VertexDecoderLoongArch64.cpp
Henrik Rydgård 5e16bf907b VertexDecoder: Improve logging for missing formats. Add missing convert function.
The missing function is mainly used in D3D11, which can be used on
Windows for ARM64. It's not necesssary for the other backends, which is
why it used to be missing in the ARM64 vertex decoder.

Also fix a minor memory leak in AtracCtx2.
2025-09-24 10:52:09 -06:00

1298 lines
43 KiB
C++

// 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<std::string> 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)