diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index fd2d9a1b61..369b396d63 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -149,26 +149,56 @@ static inline void ApplyTexelClamp(int out_u[N], int out_v[N], const int u[N], c } } -template -static inline void ApplyTexelClampQuad(int out_u[N * 4], int out_v[N * 4], const int u[N], const int v[N], int width, int height) { +static inline Vec4IntResult SOFTRAST_CALL ApplyTexelClampQuad(bool clamp, Vec4IntArg vec, int width) { + Vec4 result = vec; +#ifdef _M_SSE + if (clamp) { + // First, clamp to zero. + __m128i negmask = _mm_cmpgt_epi32(_mm_setzero_si128(), result.ivec); + result.ivec = _mm_andnot_si128(negmask, result.ivec); + + // Now the high bound. + __m128i bound = _mm_set1_epi32(width - 1); + __m128i goodmask = _mm_cmpgt_epi32(bound, result.ivec); + // Clear the ones that were too high, then or in the high bound to those. + result.ivec = _mm_and_si128(goodmask, result.ivec); + result.ivec = _mm_or_si128(result.ivec, _mm_andnot_si128(goodmask, bound)); + } else { + result.ivec = _mm_and_si128(result.ivec, _mm_set1_epi32(width - 1)); + } +#else if (gstate.isTexCoordClampedS()) { - for (int i = 0; i < N * 4; ++i) { - out_u[i] = ClampUV(u[i >> 2] + (i & 1), width); + for (int i = 0; i < 4; ++i) { + result[i] = ClampUV(result[i], width); } } else { - for (int i = 0; i < N * 4; ++i) { - out_u[i] = WrapUV(u[i >> 2] + (i & 1), width); - } - } - if (gstate.isTexCoordClampedT()) { - for (int i = 0; i < N * 4; ++i) { - out_v[i] = ClampUV(v[i >> 2] + ((i >> 1) & 1), height); - } - } else { - for (int i = 0; i < N * 4; ++i) { - out_v[i] = WrapUV(v[i >> 2] + ((i >> 1) & 1), height); + for (int i = 0; i < 4; ++i) { + result[i] = WrapUV(result[i], width); } } +#endif + + return ToVec4IntResult(result); +} + +static inline Vec4IntResult SOFTRAST_CALL ApplyTexelClampQuadS(bool clamp, int u, int width) { +#ifdef _M_SSE + __m128i uvec = _mm_add_epi32(_mm_set1_epi32(u), _mm_set_epi32(1, 0, 1, 0)); + return ApplyTexelClampQuad(clamp, uvec, width); +#else + Vec4 result = Vec4::AssignToAll(u) + Vec4(0, 1, 0, 1); + return ApplyTexelClampQuad(clamp, ToVec4IntArg(result), width); +#endif +} + +static inline Vec4IntResult SOFTRAST_CALL ApplyTexelClampQuadT(bool clamp, int v, int height) { +#ifdef _M_SSE + __m128i vvec = _mm_add_epi32(_mm_set1_epi32(v), _mm_set_epi32(1, 1, 0, 0)); + return ApplyTexelClampQuad(clamp, vvec, height); +#else + Vec4 result = Vec4::AssignToAll(v) + Vec4(0, 0, 1, 1); + return ApplyTexelClampQuad(clamp, ToVec4IntArg(result), height); +#endif } static inline void GetTexelCoordinates(int level, float s, float t, int& out_u, int& out_v) @@ -185,23 +215,26 @@ static inline void GetTexelCoordinates(int level, float s, float t, int& out_u, ApplyTexelClamp<1>(&out_u, &out_v, &base_u, &base_v, width, height); } -static inline void GetTexelCoordinatesQuad(int level, float in_s, float in_t, int u[4], int v[4], int &frac_u, int &frac_v) -{ - // 8 bits of fractional UV +static inline Vec4IntResult SOFTRAST_CALL GetTexelCoordinatesQuadS(int level, float in_s, int &frac_u) { int width = gstate.getTextureWidth(level); - int height = gstate.getTextureHeight(level); int base_u = (int)(in_s * width * 256.0f + 0.375f) - 128; - int base_v = (int)(in_t * height * 256.0f + 0.375f) - 128; - frac_u = (int)(base_u) & 0xff; - frac_v = (int)(base_v) & 0xff; - base_u >>= 8; + + // Need to generate and individually wrap/clamp the four sample coordinates. Ugh. + return ApplyTexelClampQuadS(gstate.isTexCoordClampedS(), base_u, width); +} + +static inline Vec4IntResult SOFTRAST_CALL GetTexelCoordinatesQuadT(int level, float in_t, int &frac_v) { + int height = gstate.getTextureHeight(level); + + int base_v = (int)(in_t * height * 256.0f + 0.375f) - 128; + frac_v = (int)(base_v) & 0xff; base_v >>= 8; // Need to generate and individually wrap/clamp the four sample coordinates. Ugh. - ApplyTexelClampQuad<1>(u, v, &base_u, &base_v, width, height); + return ApplyTexelClampQuadT(gstate.isTexCoordClampedT(), base_v, height); } static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) { @@ -562,7 +595,6 @@ Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &sourc template static inline Vec4IntResult SOFTRAST_CALL ApplyTexturing(Sampler::Funcs sampler, Vec4IntArg prim_color, float s, float t, int texlevel, int frac_texlevel, bool bilinear, u8 *texptr[], int texbufw[]) { - int u[8] = {0}, v[8] = {0}; // 1.23.8 fixed point int frac_u[2], frac_v[2]; Vec4 texcolor0; @@ -573,6 +605,8 @@ static inline Vec4IntResult SOFTRAST_CALL ApplyTexturing(Sampler::Funcs sampler, int bufw1 = texbufw[mayHaveMipLevels ? texlevel + 1 : 0]; if (!bilinear) { + int u[8] = { 0 }, v[8] = { 0 }; // 1.23.8 fixed point + // Nearest filtering only. Round texcoords. GetTexelCoordinates(mayHaveMipLevels ? texlevel : 0, s, t, u[0], v[0]); if (mayHaveMipLevels && frac_texlevel) { @@ -584,14 +618,17 @@ static inline Vec4IntResult SOFTRAST_CALL ApplyTexturing(Sampler::Funcs sampler, texcolor1 = Vec4(sampler.nearest(u[1], v[1], tptr1, bufw1, texlevel + 1)); } } else { - GetTexelCoordinatesQuad(mayHaveMipLevels ? texlevel : 0, s, t, u, v, frac_u[0], frac_v[0]); + Vec4IntResult u = GetTexelCoordinatesQuadS(mayHaveMipLevels ? texlevel : 0, s, frac_u[0]); + Vec4IntResult v = GetTexelCoordinatesQuadT(mayHaveMipLevels ? texlevel : 0, t, frac_v[0]); + Vec4IntResult u1, v1; if (mayHaveMipLevels && frac_texlevel) { - GetTexelCoordinatesQuad(texlevel + 1, s, t, u + 4, v + 4, frac_u[1], frac_v[1]); + u1 = GetTexelCoordinatesQuadS(texlevel + 1, s, frac_u[1]); + v1 = GetTexelCoordinatesQuadT(texlevel + 1, t, frac_v[1]); } - texcolor0 = Vec4(sampler.linear(u, v, frac_u[0], frac_v[0], tptr0, bufw0, mayHaveMipLevels ? texlevel : 0)); + texcolor0 = Vec4(sampler.linear(ToVec4IntArg(u), ToVec4IntArg(v), frac_u[0], frac_v[0], tptr0, bufw0, mayHaveMipLevels ? texlevel : 0)); if (mayHaveMipLevels && frac_texlevel) { - texcolor1 = Vec4(sampler.linear(u + 4, v + 4, frac_u[1], frac_v[1], tptr1, bufw1, texlevel + 1)); + texcolor1 = Vec4(sampler.linear(ToVec4IntArg(u1), ToVec4IntArg(v1), frac_u[1], frac_v[1], tptr1, bufw1, texlevel + 1)); } } diff --git a/GPU/Software/RasterizerRegCache.h b/GPU/Software/RasterizerRegCache.h index f16c11b71f..472ef8c2ff 100644 --- a/GPU/Software/RasterizerRegCache.h +++ b/GPU/Software/RasterizerRegCache.h @@ -67,6 +67,7 @@ typedef int32x4_t Vec4IntArg; typedef int32x4_t Vec4IntResult; typedef float32x4_t Vec4FloatArg; static inline Vec4IntArg ToVec4IntArg(const Math3D::Vec4 &a) { return vld1q_s32(a.AsArray()); } +static inline Vec4IntArg ToVec4IntArg(const Vec4IntResult &a) { return a; } static inline Vec4IntResult ToVec4IntResult(const Math3D::Vec4 &a) { return vld1q_s32(a.AsArray()); } static inline Vec4FloatArg ToVec4FloatArg(const Math3D::Vec4 &a) { return vld1q_f32(a.AsArray()); } #elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) @@ -74,6 +75,7 @@ typedef __m128i Vec4IntArg; typedef __m128i Vec4IntResult; typedef __m128 Vec4FloatArg; static inline Vec4IntArg ToVec4IntArg(const Math3D::Vec4 &a) { return a.ivec; } +static inline Vec4IntArg ToVec4IntArg(const Vec4IntResult &a) { return a; } static inline Vec4IntResult ToVec4IntResult(const Math3D::Vec4 &a) { return a.ivec; } static inline Vec4FloatArg ToVec4FloatArg(const Math3D::Vec4 &a) { return a.vec; } #else @@ -124,6 +126,8 @@ struct RegCache { GEN_ARG_FRAC_V = 0x018D, VEC_ARG_COLOR = 0x0080, VEC_ARG_MASK = 0x0081, + VEC_ARG_U = 0x0082, + VEC_ARG_V = 0x0083, VEC_TEMP0 = 0x1000, VEC_TEMP1 = 0x1001, diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index 812c90ae53..59bff23efe 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -38,7 +38,7 @@ extern u32 clut[4096]; namespace Sampler { static Vec4IntResult SOFTRAST_CALL SampleNearest(int u, int v, const u8 *tptr, int bufw, int level); -static Vec4IntResult SOFTRAST_CALL SampleLinear(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int level); +static Vec4IntResult SOFTRAST_CALL SampleLinear(Vec4IntArg u, Vec4IntArg v, int frac_u, int frac_v, const u8 *tptr, int bufw, int level); std::mutex jitCacheLock; SamplerJitCache *jitCache = nullptr; @@ -308,7 +308,7 @@ struct Nearest4 { }; template -inline static Nearest4 SOFTRAST_CALL SampleNearest(int u[N], int v[N], const u8 *srcptr, int texbufw, int level) { +inline static Nearest4 SOFTRAST_CALL SampleNearest(const int u[N], const int v[N], const u8 *srcptr, int texbufw, int level) { Nearest4 res; if (!srcptr) { memset(res.v, 0, sizeof(res.v)); @@ -414,8 +414,10 @@ static Vec4IntResult SOFTRAST_CALL SampleNearest(int u, int v, const u8 *tptr, i return ToVec4IntResult(Vec4::FromRGBA(c.v[0])); } -static Vec4IntResult SOFTRAST_CALL SampleLinear(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int texlevel) { - Nearest4 c = SampleNearest<4>(u, v, tptr, bufw, texlevel); +static Vec4IntResult SOFTRAST_CALL SampleLinear(Vec4IntArg u_in, Vec4IntArg v_in, int frac_u, int frac_v, const u8 *tptr, int bufw, int texlevel) { + const Vec4 u = u_in; + const Vec4 v = v_in; + Nearest4 c = SampleNearest<4>(u.AsArray(), v.AsArray(), tptr, bufw, texlevel); Vec4 texcolor_tl = Vec4::FromRGBA(c.v[0]); Vec4 texcolor_tr = Vec4::FromRGBA(c.v[1]); diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h index 489dd80cb2..efb207cfa4 100644 --- a/GPU/Software/Sampler.h +++ b/GPU/Software/Sampler.h @@ -36,7 +36,7 @@ namespace Sampler { typedef Rasterizer::Vec4IntResult (SOFTRAST_CALL *NearestFunc)(int u, int v, const u8 *tptr, int bufw, int level); NearestFunc GetNearestFunc(); -typedef Rasterizer::Vec4IntResult (SOFTRAST_CALL *LinearFunc)(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int level); +typedef Rasterizer::Vec4IntResult (SOFTRAST_CALL *LinearFunc)(Rasterizer::Vec4IntArg u, Rasterizer::Vec4IntArg v, int frac_u, int frac_v, const u8 *tptr, int bufw, int level); LinearFunc GetLinearFunc(); struct Funcs { diff --git a/GPU/Software/SamplerX86.cpp b/GPU/Software/SamplerX86.cpp index b411a5b2f8..181cccf3db 100644 --- a/GPU/Software/SamplerX86.cpp +++ b/GPU/Software/SamplerX86.cpp @@ -131,8 +131,13 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { RegCache::GEN_ARG_V, RegCache::GEN_ARG_TEXPTR, RegCache::GEN_ARG_BUFW, + RegCache::GEN_ARG_LEVEL, }); regCache_.ChangeReg(RAX, RegCache::GEN_RESULT); + regCache_.ChangeReg(XMM0, RegCache::VEC_ARG_U); + regCache_.ForceRetain(RegCache::VEC_ARG_U); + regCache_.ChangeReg(XMM1, RegCache::VEC_ARG_V); + regCache_.ForceRetain(RegCache::VEC_ARG_V); // We'll first write the nearest sampler, which we will CALL. // This may differ slightly based on the "linear" flag. @@ -147,41 +152,46 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { RET(); + regCache_.ForceRelease(RegCache::VEC_ARG_U); + regCache_.ForceRelease(RegCache::VEC_ARG_V); + if (regCache_.Has(RegCache::GEN_ARG_LEVEL)) + regCache_.ForceRelease(RegCache::GEN_ARG_LEVEL); regCache_.Reset(true); // Now the actual linear func, which is exposed externally. const u8 *start = AlignCode16(); // NOTE: This doesn't use the general register mapping. - // POSIX: arg1=uptr, arg2=vptr, arg3=frac_u, arg4=frac_v, arg5=src, arg6=bufw, stack+8=level - // Win64: arg1=uptr, arg2=vptr, arg3=frac_u, arg4=frac_v, stack+40=src, stack+48=bufw, stack+56=level + // POSIX: XMM0=uvec, XMM1=vvec, arg1=frac_u, arg2=frac_v, arg3=src, arg4=bufw, arg5=level + // Win64: XMM0=uvec, XMM1=vvec, arg3=frac_u, arg4=frac_v, stack+40=src, stack+48=bufw, stack+56=level // // We map these to nearest CALLs, with order: u, v, src, bufw, level // Let's start by saving a bunch of registers. PUSH(R15); PUSH(R14); - PUSH(R13); - PUSH(R12); // Won't need frac_u/frac_v for a while. +#ifdef _WIN32 PUSH(arg4Reg); PUSH(arg3Reg); +#else + PUSH(arg2Reg); + PUSH(arg1Reg); +#endif // Extra space to restore alignment and save resultReg for lerp. // TODO: Maybe use XMMs instead? SUB(64, R(RSP), Imm8(24)); - MOV(64, R(R12), R(arg1Reg)); - MOV(64, R(R13), R(arg2Reg)); #ifdef _WIN32 - // First arg now starts at 24 (extra space) + 48 (pushed stack) + 8 (ret address) + 32 (shadow space) - const int argOffset = 24 + 48 + 8 + 32; + // First arg now starts at 24 (extra space) + 32 (pushed stack) + 8 (ret address) + 32 (shadow space) + const int argOffset = 24 + 32 + 8 + 32; MOV(64, R(R14), MDisp(RSP, argOffset)); MOV(32, R(R15), MDisp(RSP, argOffset + 8)); // level is at argOffset + 16. #else - MOV(64, R(R14), R(arg5Reg)); - MOV(32, R(R15), R(arg6Reg)); - // level is at 24 + 48 + 8. + MOV(64, R(R14), R(arg3Reg)); + MOV(32, R(R15), R(arg4Reg)); + // level is in arg5Reg, which is convenient. #endif // Early exit on !srcPtr. @@ -195,7 +205,7 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { } // At this point: - // R12=uptr, R13=vptr, stack+24=frac_u, stack+32=frac_v, R14=src, R15=bufw, stack+X=level + // XMM0=uvec, XMM1=vvec, stack+24=frac_u, stack+32=frac_v, R14=src, R15=bufw, stack+X=level // This stores the result on the stack for later processing. auto doNearestCall = [&](int off) { @@ -205,12 +215,16 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { static const X64Reg bufwReg = arg4Reg; static const X64Reg resultReg = RAX; - MOV(32, R(uReg), MDisp(R12, off)); - MOV(32, R(vReg), MDisp(R13, off)); + MOVD_xmm(R(uReg), XMM0); + MOVD_xmm(R(vReg), XMM1); + MOV(64, R(srcReg), R(R14)); MOV(32, R(bufwReg), R(R15)); // Leave level, we just always load from RAM. Separate CLUTs is uncommon. + PSRLDQ(XMM0, 4); + PSRLDQ(XMM1, 4); + CALL(nearest); MOV(32, MDisp(RSP, off), R(resultReg)); }; @@ -316,8 +330,6 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { ADD(64, R(RSP), Imm8(24)); POP(arg3Reg); POP(arg4Reg); - POP(R12); - POP(R13); POP(R14); POP(R15); @@ -1203,11 +1215,13 @@ bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) { // We need to multiply by 16 and add, LEA allows us to copy too. LEA(32, temp2Reg, MScaled(levelReg, SCALE_4, 0)); regCache_.Unlock(levelReg, RegCache::GEN_ARG_LEVEL); - regCache_.ForceRelease(RegCache::GEN_ARG_LEVEL); + // Don't release if we're reusing it. + if (!regCache_.Has(RegCache::VEC_ARG_U)) + regCache_.ForceRelease(RegCache::GEN_ARG_LEVEL); } else { if (id.linear) { #ifdef _WIN32 - const int argOffset = 24 + 48 + 8 + 32; + const int argOffset = 24 + 32 + 8 + 32; // Extra 8 to account for CALL. MOV(32, R(temp2Reg), MDisp(RSP, argOffset + 16 + 8)); #else