diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index ebb9709fd8..3a38692c7e 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -205,60 +205,35 @@ static inline void GetTexelCoordinatesQuad(int level, float in_s, float in_t, in } static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) { - switch (gstate.getUVGenMode()) { - case GE_TEXMAP_TEXTURE_COORDS: - case GE_TEXMAP_UNKNOWN: - case GE_TEXMAP_ENVIRONMENT_MAP: - case GE_TEXMAP_TEXTURE_MATRIX: - { - // TODO: What happens if vertex has no texture coordinates? - // Note that for environment mapping, texture coordinates have been calculated during lighting - float q0 = 1.f / v0.clippos.w; - float q1 = 1.f / v1.clippos.w; - float wq0 = p * q0; - float wq1 = (1.0f - p) * q1; + // All UV gen modes, by the time they get here, behave the same. - float q_recip = 1.0f / (wq0 + wq1); - s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip; - t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip; - } - break; - default: - ERROR_LOG_REPORT(G3D, "Software: Unsupported texture mapping mode %x!", gstate.getUVGenMode()); - s = 0.0f; - t = 0.0f; - break; - } + // TODO: What happens if vertex has no texture coordinates? + // Note that for environment mapping, texture coordinates have been calculated during lighting + float q0 = 1.f / v0.clippos.w; + float q1 = 1.f / v1.clippos.w; + float wq0 = p * q0; + float wq1 = (1.0f - p) * q1; + + float q_recip = 1.0f / (wq0 + wq1); + s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip; + t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip; } -static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const VertexData& v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) -{ - switch (gstate.getUVGenMode()) { - case GE_TEXMAP_TEXTURE_COORDS: - case GE_TEXMAP_UNKNOWN: - case GE_TEXMAP_ENVIRONMENT_MAP: - case GE_TEXMAP_TEXTURE_MATRIX: - { - // TODO: What happens if vertex has no texture coordinates? - // Note that for environment mapping, texture coordinates have been calculated during lighting - float q0 = 1.f / v0.clippos.w; - float q1 = 1.f / v1.clippos.w; - float q2 = 1.f / v2.clippos.w; - Vec4 wq0 = w0.Cast() * q0; - Vec4 wq1 = w1.Cast() * q1; - Vec4 wq2 = w2.Cast() * q2; +static inline void GetTextureCoordinates(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) { + // All UV gen modes, by the time they get here, behave the same. - Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal(); - s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip); - t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip); - } - break; - default: - ERROR_LOG_REPORT(G3D, "Software: Unsupported texture mapping mode %x!", gstate.getUVGenMode()); - s = Vec4::AssignToAll(0.0f); - t = Vec4::AssignToAll(0.0f); - break; - } + // TODO: What happens if vertex has no texture coordinates? + // Note that for environment mapping, texture coordinates have been calculated during lighting. + float q0 = 1.f / v0.clippos.w; + float q1 = 1.f / v1.clippos.w; + float q2 = 1.f / v2.clippos.w; + Vec4 wq0 = w0.Cast() * q0; + Vec4 wq1 = w1.Cast() * q1; + Vec4 wq2 = w2.Cast() * q2; + + Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal(); + s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip); + t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip); } static inline void SetPixelDepth(int x, int y, u16 value) @@ -290,8 +265,10 @@ static inline bool IsRightSideOrFlatBottomLine(const Vec2& vertex, const Ve } } -Vec4 GetTextureFunctionOutput(const Vec4& prim_color, const Vec4& texcolor) -{ +Vec4IntResult SOFTRAST_CALL GetTextureFunctionOutput(Vec4IntArg prim_color_in, Vec4IntArg texcolor_in) { + const Vec4 prim_color = prim_color_in; + const Vec4 texcolor = texcolor_in; + Vec3 out_rgb; int out_a; @@ -314,7 +291,7 @@ Vec4 GetTextureFunctionOutput(const Vec4& prim_color, const Vec4& } if (rgba) { - return Vec4(out_rgb.ivec); + return ToVec4IntResult(Vec4(out_rgb.ivec)); } else { out_a = prim_color.a(); } @@ -366,7 +343,7 @@ Vec4 GetTextureFunctionOutput(const Vec4& prim_color, const Vec4& out_a = 0; } - return Vec4(out_rgb.r(), out_rgb.g(), out_rgb.b(), out_a); + return ToVec4IntResult(Vec4(out_rgb, out_a)); } static inline Vec3 GetSourceFactor(GEBlendSrcFactor factor, const Vec4 &source, const Vec4 &dst) { @@ -559,44 +536,45 @@ Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &sourc } } -static inline void ApplyTexturing(Sampler::Funcs sampler, Vec4 &prim_color, float s, float t, int texlevel, int frac_texlevel, bool bilinear, u8 *texptr[], int texbufw[]) { +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; Vec4 texcolor1; - const u8 *tptr0 = texptr[texlevel]; - int bufw0 = texbufw[texlevel]; - const u8 *tptr1 = texptr[texlevel + 1]; - int bufw1 = texbufw[texlevel + 1]; + const u8 *tptr0 = texptr[mayHaveMipLevels ? texlevel : 0]; + int bufw0 = texbufw[mayHaveMipLevels ? texlevel : 0]; + const u8 *tptr1 = texptr[mayHaveMipLevels ? texlevel + 1 : 0]; + int bufw1 = texbufw[mayHaveMipLevels ? texlevel + 1 : 0]; if (!bilinear) { // Nearest filtering only. Round texcoords. - GetTexelCoordinates(texlevel, s, t, u[0], v[0]); - if (frac_texlevel) { + GetTexelCoordinates(mayHaveMipLevels ? texlevel : 0, s, t, u[0], v[0]); + if (mayHaveMipLevels && frac_texlevel) { GetTexelCoordinates(texlevel + 1, s, t, u[1], v[1]); } - texcolor0 = Vec4::FromRGBA(sampler.nearest(u[0], v[0], tptr0, bufw0, texlevel)); - if (frac_texlevel) { - texcolor1 = Vec4::FromRGBA(sampler.nearest(u[1], v[1], tptr1, bufw1, texlevel + 1)); + texcolor0 = Vec4(sampler.nearest(u[0], v[0], tptr0, bufw0, mayHaveMipLevels ? texlevel : 0)); + if (mayHaveMipLevels && frac_texlevel) { + texcolor1 = Vec4(sampler.nearest(u[1], v[1], tptr1, bufw1, texlevel + 1)); } } else { - GetTexelCoordinatesQuad(texlevel, s, t, u, v, frac_u[0], frac_v[0]); - if (frac_texlevel) { + GetTexelCoordinatesQuad(mayHaveMipLevels ? texlevel : 0, s, t, u, v, frac_u[0], frac_v[0]); + if (mayHaveMipLevels && frac_texlevel) { GetTexelCoordinatesQuad(texlevel + 1, s, t, u + 4, v + 4, frac_u[1], frac_v[1]); } - texcolor0 = Vec4::FromRGBA(sampler.linear(u, v, frac_u[0], frac_v[0], tptr0, bufw0, texlevel)); - if (frac_texlevel) { - texcolor1 = Vec4::FromRGBA(sampler.linear(u + 4, v + 4, frac_u[1], frac_v[1], tptr1, bufw1, texlevel + 1)); + texcolor0 = Vec4(sampler.linear(u, 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)); } } - if (frac_texlevel) { + if (mayHaveMipLevels && frac_texlevel) { texcolor0 = (texcolor1 * frac_texlevel + texcolor0 * (256 - frac_texlevel)) / 256; } - prim_color = GetTextureFunctionOutput(prim_color, texcolor0); + return GetTextureFunctionOutput(prim_color, ToVec4IntArg(texcolor0)); } // Produces a signed 1.23.8 value. @@ -614,6 +592,7 @@ static int TexLog2(float delta) { return useful - 127 * 256; } +template static inline void CalculateSamplingParams(const float ds, const float dt, const int maxTexLevel, int &level, int &levelFrac, bool &filt) { const int width = gstate.getTextureWidth(0); const int height = gstate.getTextureHeight(0); @@ -638,19 +617,21 @@ static inline void CalculateSamplingParams(const float ds, const float dt, const // Add in the bias (used in all modes), expanding to 8 bits of fraction. detail += gstate.getTexLevelOffset16() << 4; - if (detail > 0 && maxTexLevel > 0) { - bool mipFilt = gstate.isMipmapFilteringEnabled(); + if (mayHaveMipLevels) { + if (detail > 0 && maxTexLevel > 0) { + bool mipFilt = gstate.isMipmapFilteringEnabled(); - int level8 = std::min(detail, maxTexLevel * 256); - if (!mipFilt) { - // Round up at 1.5. - level8 += 128; + int level8 = std::min(detail, maxTexLevel * 256); + if (!mipFilt) { + // Round up at 1.5. + level8 += 128; + } + level = level8 >> 8; + levelFrac = mipFilt ? level8 & 0xFF : 0; + } else { + level = 0; + levelFrac = 0; } - level = level8 >> 8; - levelFrac = mipFilt ? level8 & 0xFF : 0; - } else { - level = 0; - levelFrac = 0; } if (g_Config.iTexFiltering == TEX_FILTER_FORCE_LINEAR) { @@ -662,6 +643,7 @@ static inline void CalculateSamplingParams(const float ds, const float dt, const } } +template static inline void ApplyTexturing(Sampler::Funcs sampler, Vec4 *prim_color, const Vec4 &s, const Vec4 &t, int maxTexLevel, u8 *texptr[], int texbufw[]) { float ds = s[1] - s[0]; float dt = t[2] - t[0]; @@ -669,10 +651,10 @@ static inline void ApplyTexturing(Sampler::Funcs sampler, Vec4 *prim_color, int level; int levelFrac; bool bilinear; - CalculateSamplingParams(ds, dt, maxTexLevel, level, levelFrac, bilinear); + CalculateSamplingParams(ds, dt, maxTexLevel, level, levelFrac, bilinear); for (int i = 0; i < 4; ++i) { - ApplyTexturing(sampler, prim_color[i], s[i], t[i], level, levelFrac, bilinear, texptr, texbufw); + prim_color[i] = ApplyTexturing(sampler, ToVec4IntArg(prim_color[i]), s[i], t[i], level, levelFrac, bilinear, texptr, texbufw); } } @@ -751,7 +733,7 @@ static inline Vec4 EdgeRecip(const Vec4 &w0, const Vec4 &w1, co #endif } -template +template void DrawTriangleSlice( const VertexData& v0, const VertexData& v1, const VertexData& v2, int x1, int y1, int x2, int y2, @@ -763,15 +745,10 @@ void DrawTriangleSlice( Vec4 bias1 = Vec4::AssignToAll(IsRightSideOrFlatBottomLine(v1.screenpos.xy(), v2.screenpos.xy(), v0.screenpos.xy()) ? -1 : 0); Vec4 bias2 = Vec4::AssignToAll(IsRightSideOrFlatBottomLine(v2.screenpos.xy(), v0.screenpos.xy(), v1.screenpos.xy()) ? -1 : 0); - int texbufw[8] = {0}; + int texbufw[hasMipLevels ? 8 : 1] = {0}; - int maxTexLevel = gstate.getTextureMaxLevel(); - u8 *texptr[8] = {NULL}; - - if (!gstate.isMipmapEnabled()) { - // No mipmapping enabled - maxTexLevel = 0; - } + int maxTexLevel = hasMipLevels ? gstate.getTextureMaxLevel() : 0; + u8 *texptr[hasMipLevels ? 8 : 1] = {NULL}; if (gstate.isTextureMapEnabled() && !clearMode) { GETextureFormat texfmt = gstate.getTextureFormat(); @@ -865,7 +842,7 @@ void DrawTriangleSlice( GetTextureCoordinates(v0, v1, v2, w0, w1, w2, wsum_recip, s, t); } - ApplyTexturing(sampler, prim_color, s, t, maxTexLevel, texptr, texbufw); + ApplyTexturing(sampler, prim_color, s, t, maxTexLevel, texptr, texbufw); } if (!clearMode) { @@ -945,39 +922,27 @@ void DrawTriangle(const VertexData& v0, const VertexData& v1, const VertexData& PixelFuncID pixelID; ComputePixelFuncID(&pixelID); Rasterizer::SingleFunc drawPixel = Rasterizer::GetSingleFunc(pixelID); + const bool hasMipLevels = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() > 0 : false; + + auto drawSlice = (hasMipLevels ? + (gstate.isModeClear() ? &DrawTriangleSlice : &DrawTriangleSlice) : + (gstate.isModeClear() ? &DrawTriangleSlice : &DrawTriangleSlice) + ); const int MIN_LINES_PER_THREAD = 4; if (rangeY >= 12 && rangeX >= rangeY * 4) { - if (gstate.isModeClear()) { - auto bound = [&](int a, int b) -> void { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, false, a, b, pixelID, drawPixel); - }; - ParallelRangeLoop(&g_threadManager, bound, 0, rangeX, MIN_LINES_PER_THREAD); - } else { - auto bound = [&](int a, int b) -> void { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, false, a, b, pixelID, drawPixel); - }; - ParallelRangeLoop(&g_threadManager, bound, 0, rangeX, MIN_LINES_PER_THREAD); - } + auto bound = [&](int a, int b) -> void { + drawSlice(v0, v1, v2, minX, minY, maxX, maxY, false, a, b, pixelID, drawPixel); + }; + ParallelRangeLoop(&g_threadManager, bound, 0, rangeX, MIN_LINES_PER_THREAD); } else if (rangeY >= 12 && rangeX >= 12) { - if (gstate.isModeClear()) { - auto bound = [&](int a, int b) -> void { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, true, a, b, pixelID, drawPixel); - }; - ParallelRangeLoop(&g_threadManager, bound, 0, rangeY, MIN_LINES_PER_THREAD); - } else { - auto bound = [&](int a, int b) -> void { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, true, a, b, pixelID, drawPixel); - }; - ParallelRangeLoop(&g_threadManager, bound, 0, rangeY, MIN_LINES_PER_THREAD); - } + auto bound = [&](int a, int b) -> void { + drawSlice(v0, v1, v2, minX, minY, maxX, maxY, true, a, b, pixelID, drawPixel); + }; + ParallelRangeLoop(&g_threadManager, bound, 0, rangeY, MIN_LINES_PER_THREAD); } else { - if (gstate.isModeClear()) { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, true, 0, rangeY, pixelID, drawPixel); - } else { - DrawTriangleSlice(v0, v1, v2, minX, minY, maxX, maxY, true, 0, rangeY, pixelID, drawPixel); - } + drawSlice(v0, v1, v2, minX, minY, maxX, maxY, true, 0, rangeY, pixelID, drawPixel); } } @@ -1037,8 +1002,8 @@ void DrawPoint(const VertexData &v0) int texLevel; int texLevelFrac; bool bilinear; - CalculateSamplingParams(0.0f, 0.0f, maxTexLevel, texLevel, texLevelFrac, bilinear); - ApplyTexturing(sampler, prim_color, s, t, texLevel, texLevelFrac, bilinear, texptr, texbufw); + CalculateSamplingParams(0.0f, 0.0f, maxTexLevel, texLevel, texLevelFrac, bilinear); + prim_color = ApplyTexturing(sampler, ToVec4IntArg(prim_color), s, t, texLevel, texLevelFrac, bilinear, texptr, texbufw); } if (!pixelID.clearMode) @@ -1324,7 +1289,7 @@ void DrawLine(const VertexData &v0, const VertexData &v1) int texLevel; int texLevelFrac; bool texBilinear; - CalculateSamplingParams(ds, dt, maxTexLevel, texLevel, texLevelFrac, texBilinear); + CalculateSamplingParams(ds, dt, maxTexLevel, texLevel, texLevelFrac, texBilinear); if (gstate.isAntiAliasEnabled()) { // TODO: This is a niave and wrong implementation. @@ -1335,7 +1300,7 @@ void DrawLine(const VertexData &v0, const VertexData &v1) texBilinear = true; } - ApplyTexturing(sampler, prim_color, s, t, texLevel, texLevelFrac, texBilinear, texptr, texbufw); + prim_color = ApplyTexturing(sampler, ToVec4IntArg(prim_color), s, t, texLevel, texLevelFrac, texBilinear, texptr, texbufw); } if (!pixelID.clearMode) @@ -1392,7 +1357,7 @@ bool GetCurrentTexture(GPUDebugBuffer &buffer, int level) u32 *row = (u32 *)buffer.GetData(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - row[x] = sampler.nearest(x, y, texptr, texbufw, level); + row[x] = Vec4(sampler.nearest(x, y, texptr, texbufw, level)).ToRGBA(); } row += w; } diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index 0d7e2e91f4..e72cfaba98 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -18,6 +18,7 @@ #pragma once #include "GPU/Software/FuncId.h" +#include "GPU/Software/RasterizerRegCache.h" #include "GPU/Software/TransformUnit.h" // for DrawingCoords struct GPUDebugBuffer; @@ -35,6 +36,6 @@ bool GetCurrentTexture(GPUDebugBuffer &buffer, int level); // Shared functions with RasterizerRectangle.cpp Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &source, const Vec4 &dst); -Vec4 GetTextureFunctionOutput(const Vec4& prim_color, const Vec4& texcolor); +Vec4IntResult SOFTRAST_CALL GetTextureFunctionOutput(Vec4IntArg prim_color, Vec4IntArg texcolor); } // namespace Rasterizer diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 657ff93aab..aab7d634b7 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -153,7 +153,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { int s = s_start; u16 *pixel = fb.Get16Ptr(pos0.x, y, gstate.FrameBufStride()); for (int x = pos0.x; x < pos1.x; x++) { - u32 tex_color = nearestFunc(s, t, texptr, texbufw, 0); + u32 tex_color = Vec4(nearestFunc(s, t, texptr, texbufw, 0)).ToRGBA(); if (tex_color & 0xFF000000) { DrawSinglePixel5551(pixel, tex_color, pixelID); } @@ -171,7 +171,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { u16 *pixel = fb.Get16Ptr(pos0.x, y, gstate.FrameBufStride()); for (int x = pos0.x; x < pos1.x; x++) { Vec4 prim_color = v1.color0; - Vec4 tex_color = Vec4::FromRGBA(nearestFunc(s, t, texptr, texbufw, 0)); + Vec4 tex_color = nearestFunc(s, t, texptr, texbufw, 0); prim_color = ModulateRGBA(prim_color, tex_color); if (prim_color.a() > 0) { DrawSinglePixel5551(pixel, prim_color.ToRGBA(), pixelID); @@ -191,8 +191,8 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { // Not really that fast but faster than triangle. for (int x = pos0.x; x < pos1.x; x++) { Vec4 prim_color = v1.color0; - Vec4 tex_color = Vec4::FromRGBA(nearestFunc(s, t, texptr, texbufw, 0)); - prim_color = GetTextureFunctionOutput(prim_color, tex_color); + Vec4 tex_color = nearestFunc(s, t, texptr, texbufw, 0); + prim_color = GetTextureFunctionOutput(ToVec4IntArg(prim_color), ToVec4IntArg(tex_color)); drawPixel(x, y, z, 255, ToVec4IntArg(prim_color), pixelID); s += ds; } diff --git a/GPU/Software/RasterizerRegCache.cpp b/GPU/Software/RasterizerRegCache.cpp index a57e356533..973226f869 100644 --- a/GPU/Software/RasterizerRegCache.cpp +++ b/GPU/Software/RasterizerRegCache.cpp @@ -335,6 +335,28 @@ void RegCache::GrabReg(Reg r, Purpose p, bool &needsSwap, Reg swapReg, Purpose s _assert_msg_(false, "softjit GrabReg() reg that isn't there"); } +bool RegCache::ChangeReg(Reg r, Purpose p) { + for (auto ® : regs) { + if (reg.reg != r) + continue; + if ((reg.purpose & FLAG_GEN) != (p & FLAG_GEN)) + continue; + + if (reg.purpose == p) + return true; + _assert_msg_(!Has(p), "softjit ChangeReg() duplicate purpose (%04X)", p); + + if (reg.locked != 0 || reg.forceRetained) + return false; + + reg.purpose = p; + return true; + } + + _assert_msg_(false, "softjit ChangeReg() reg that isn't there"); + return false; +} + RegCache::RegStatus *RegCache::FindReg(Reg r, Purpose p) { for (auto ® : regs) { if (reg.reg == r && reg.purpose == p) { diff --git a/GPU/Software/RasterizerRegCache.h b/GPU/Software/RasterizerRegCache.h index b2ec5a9d4e..f16c11b71f 100644 --- a/GPU/Software/RasterizerRegCache.h +++ b/GPU/Software/RasterizerRegCache.h @@ -64,13 +64,25 @@ typedef FakeGen::FakeXCodeBlock CodeBlock; // We also have the types of things that end up in regs. #if PPSSPP_ARCH(ARM64) 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 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) typedef __m128i Vec4IntArg; +typedef __m128i Vec4IntResult; +typedef __m128 Vec4FloatArg; static inline Vec4IntArg ToVec4IntArg(const Math3D::Vec4 &a) { return a.ivec; } +static inline Vec4IntResult ToVec4IntResult(const Math3D::Vec4 &a) { return a.ivec; } +static inline Vec4FloatArg ToVec4FloatArg(const Math3D::Vec4 &a) { return a.vec; } #else typedef const Math3D::Vec4 &Vec4IntArg; +typedef Math3D::Vec4 Vec4IntResult; +typedef const Math3D::Vec4 &Vec4FloatArg; static inline Vec4IntArg ToVec4IntArg(const Math3D::Vec4 &a) { return a; } +static inline Vec4IntResult ToVec4IntResult(const Math3D::Vec4 &a) { return a; } +static inline Vec4FloatArg ToVec4FloatArg(const Math3D::Vec4 &a) { return a; } #endif #if PPSSPP_ARCH(AMD64) && PPSSPP_PLATFORM(WINDOWS) && (defined(_MSC_VER) || defined(__clang__) || defined(__INTEL_COMPILER)) @@ -85,6 +97,7 @@ struct RegCache { FLAG_TEMP = 0x1000, VEC_ZERO = 0x0000, + VEC_RESULT = 0x0001, GEN_SRC_ALPHA = 0x0100, GEN_GSTATE = 0x0101, @@ -92,12 +105,23 @@ struct RegCache { GEN_STENCIL = 0x0103, GEN_COLOR_OFF = 0x0104, GEN_DEPTH_OFF = 0x0105, + GEN_RESULT = 0x0106, + GEN_SHIFTVAL = 0x0107, GEN_ARG_X = 0x0180, GEN_ARG_Y = 0x0181, GEN_ARG_Z = 0x0182, GEN_ARG_FOG = 0x0183, GEN_ARG_ID = 0x0184, + GEN_ARG_U = 0x0185, + GEN_ARG_V = 0x0186, + GEN_ARG_TEXPTR = 0x0187, + GEN_ARG_BUFW = 0x0188, + GEN_ARG_LEVEL = 0x0189, + GEN_ARG_U_PTR = 0x018A, + GEN_ARG_V_PTR = 0x018B, + GEN_ARG_FRAC_U = 0x018C, + GEN_ARG_FRAC_V = 0x018D, VEC_ARG_COLOR = 0x0080, VEC_ARG_MASK = 0x0081, @@ -170,6 +194,8 @@ struct RegCache { // For getting a specific reg. WARNING: May return a locked reg, so you have to check. void GrabReg(Reg r, Purpose p, bool &needsSwap, Reg swapReg, Purpose swapPurpose); + // For setting the purpose of a specific reg. Returns false if it is locked. + bool ChangeReg(Reg r, Purpose p); private: RegStatus *FindReg(Reg r, Purpose p); diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index 155f1359b2..812c90ae53 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -23,6 +23,7 @@ #include "Core/Reporting.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/GPUState.h" +#include "GPU/Software/RasterizerRegCache.h" #include "GPU/Software/Sampler.h" #if defined(_M_SSE) @@ -30,13 +31,14 @@ #endif using namespace Math3D; +using namespace Rasterizer; extern u32 clut[4096]; namespace Sampler { -static u32 SampleNearest(int u, int v, const u8 *tptr, int bufw, int level); -static u32 SampleLinear(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int level); +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); std::mutex jitCacheLock; SamplerJitCache *jitCache = nullptr; @@ -111,10 +113,17 @@ void SamplerJitCache::Clear() { void SamplerJitCache::ComputeSamplerID(SamplerID *id_out, bool linear) { SamplerID id{}; + int maxLevel = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() : 0; + for (int i = 0; i <= maxLevel; ++i) { + if (gstate.getTextureAddress(i) == 0) { + id.hasInvalidPtr = true; + } + } + id.texfmt = gstate.getTextureFormat(); id.swizzle = gstate.isTextureSwizzled(); // Only CLUT4 can use separate CLUTs per mimap. - id.useSharedClut = gstate.getTextureFormat() != GE_TFMT_CLUT4 || !gstate.isMipmapEnabled() || gstate.isClutSharedForMipmaps(); + id.useSharedClut = gstate.getTextureFormat() != GE_TFMT_CLUT4 || maxLevel == 0 || !gstate.isMipmapEnabled() || gstate.isClutSharedForMipmaps(); if (gstate.isTextureFormatIndexed()) { id.clutfmt = gstate.getClutPaletteFormat(); id.hasClutMask = gstate.getClutIndexMask() != 0xFF; @@ -122,12 +131,6 @@ void SamplerJitCache::ComputeSamplerID(SamplerID *id_out, bool linear) { id.hasClutOffset = gstate.getClutIndexStartPos() != 0; } id.linear = linear; - int maxLevel = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() : 0; - for (int i = 0; i <= maxLevel; ++i) { - if (gstate.getTextureAddress(i) == 0) { - id.hasInvalidPtr = true; - } - } *id_out = id; } @@ -305,8 +308,7 @@ struct Nearest4 { }; template -inline static Nearest4 SampleNearest(int u[N], int v[N], const u8 *srcptr, int texbufw, int level) -{ +inline static Nearest4 SOFTRAST_CALL SampleNearest(int u[N], int v[N], const u8 *srcptr, int texbufw, int level) { Nearest4 res; if (!srcptr) { memset(res.v, 0, sizeof(res.v)); @@ -407,11 +409,12 @@ inline static Nearest4 SampleNearest(int u[N], int v[N], const u8 *srcptr, int t } } -static u32 SampleNearest(int u, int v, const u8 *tptr, int bufw, int level) { - return SampleNearest<1>(&u, &v, tptr, bufw, level); +static Vec4IntResult SOFTRAST_CALL SampleNearest(int u, int v, const u8 *tptr, int bufw, int level) { + Nearest4 c = SampleNearest<1>(&u, &v, tptr, bufw, level); + return ToVec4IntResult(Vec4::FromRGBA(c.v[0])); } -static u32 SampleLinear(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int texlevel) { +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); Vec4 texcolor_tl = Vec4::FromRGBA(c.v[0]); @@ -420,7 +423,7 @@ static u32 SampleLinear(int u[4], int v[4], int frac_u, int frac_v, const u8 *tp Vec4 texcolor_br = Vec4::FromRGBA(c.v[3]); Vec4 t = texcolor_tl * (0x100 - frac_u) + texcolor_tr * frac_u; Vec4 b = texcolor_bl * (0x100 - frac_u) + texcolor_br * frac_u; - return ((t * (0x100 - frac_v) + b * frac_v) / (256 * 256)).ToRGBA(); + return ToVec4IntResult((t * (0x100 - frac_v) + b * frac_v) / (256 * 256)); } }; diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h index a14c3e5520..72f6ffc569 100644 --- a/GPU/Software/Sampler.h +++ b/GPU/Software/Sampler.h @@ -26,10 +26,10 @@ namespace Sampler { -typedef u32 (*NearestFunc)(int u, int v, const u8 *tptr, int bufw, int level); +typedef Rasterizer::Vec4IntResult (SOFTRAST_CALL *NearestFunc)(int u, int v, const u8 *tptr, int bufw, int level); NearestFunc GetNearestFunc(); -typedef u32 (*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)(int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw, int level); LinearFunc GetLinearFunc(); struct Funcs { @@ -84,6 +84,7 @@ private: std::unordered_map cache_; std::unordered_map addresses_; + Rasterizer::RegCache regCache_; }; }; diff --git a/GPU/Software/SamplerX86.cpp b/GPU/Software/SamplerX86.cpp index a6b2a66349..e455b82749 100644 --- a/GPU/Software/SamplerX86.cpp +++ b/GPU/Software/SamplerX86.cpp @@ -26,6 +26,7 @@ #include "GPU/ge_constants.h" using namespace Gen; +using namespace Rasterizer; extern u32 clut[4096]; @@ -44,19 +45,8 @@ static const X64Reg arg3Reg = RDX; static const X64Reg arg4Reg = RCX; static const X64Reg arg5Reg = R8; static const X64Reg arg6Reg = R9; - -static const X64Reg levelReg = arg5Reg; #endif -static const X64Reg resultReg = RAX; -static const X64Reg tempReg1 = R10; -static const X64Reg tempReg2 = R11; - -static const X64Reg uReg = arg1Reg; -static const X64Reg vReg = arg2Reg; -static const X64Reg srcReg = arg3Reg; -static const X64Reg bufwReg = arg4Reg; - static const X64Reg fpScratchReg1 = XMM1; static const X64Reg fpScratchReg2 = XMM2; static const X64Reg fpScratchReg3 = XMM3; @@ -64,32 +54,67 @@ static const X64Reg fpScratchReg4 = XMM4; static const X64Reg fpScratchReg5 = XMM5; NearestFunc SamplerJitCache::Compile(const SamplerID &id) { + regCache_.SetupABI({ + RegCache::GEN_ARG_U, + 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_RESULT); + BeginWrite(); const u8 *start = AlignCode16(); // Early exit on !srcPtr. FixupBranch zeroSrc; if (id.hasInvalidPtr) { + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); CMP(PTRBITS, R(srcReg), Imm8(0)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + FixupBranch nonZeroSrc = J_CC(CC_NZ); - XOR(32, R(RAX), R(RAX)); + X64Reg vecResultReg = regCache_.Find(RegCache::VEC_RESULT); + PXOR(vecResultReg, R(vecResultReg)); + regCache_.Unlock(vecResultReg, RegCache::VEC_RESULT); zeroSrc = J(true); SetJumpTarget(nonZeroSrc); } // This reads the pixel data into resultReg from the args. if (!Jit_ReadTextureFormat(id)) { + regCache_.Reset(false); EndWrite(); ResetCodePtr(GetOffset(start)); return nullptr; } + X64Reg vecResultReg = regCache_.Find(RegCache::VEC_RESULT); + + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + MOVD_xmm(vecResultReg, R(resultReg)); + regCache_.Release(resultReg, RegCache::GEN_RESULT); + + if (cpu_info.bSSE4_1) { + PMOVZXBD(vecResultReg, R(vecResultReg)); + } else { + X64Reg vecTempReg = regCache_.Find(RegCache::VEC_TEMP0); + PXOR(vecTempReg, R(vecTempReg)); + PUNPCKLBW(vecResultReg, R(vecTempReg)); + PUNPCKLWD(vecResultReg, R(vecTempReg)); + regCache_.Unlock(vecTempReg, RegCache::VEC_TEMP0); + } + regCache_.Unlock(vecResultReg, RegCache::VEC_RESULT); + if (id.hasInvalidPtr) { SetJumpTarget(zeroSrc); } RET(); + regCache_.Reset(true); + EndWrite(); return (NearestFunc)start; } @@ -101,11 +126,20 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { _assert_msg_(id.linear, "Linear should be set on sampler id"); BeginWrite(); + regCache_.SetupABI({ + RegCache::GEN_ARG_U, + RegCache::GEN_ARG_V, + RegCache::GEN_ARG_TEXPTR, + RegCache::GEN_ARG_BUFW, + }); + regCache_.ChangeReg(RAX, RegCache::GEN_RESULT); + // We'll first write the nearest sampler, which we will CALL. // This may differ slightly based on the "linear" flag. const u8 *nearest = AlignCode16(); if (!Jit_ReadTextureFormat(id)) { + regCache_.Reset(false); EndWrite(); ResetCodePtr(GetOffset(nearest)); return nullptr; @@ -113,6 +147,8 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { RET(); + regCache_.Reset(true); + // Now the actual linear func, which is exposed externally. const u8 *start = AlignCode16(); @@ -153,7 +189,7 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { if (id.hasInvalidPtr) { CMP(PTRBITS, R(R14), Imm8(0)); FixupBranch nonZeroSrc = J_CC(CC_NZ); - XOR(32, R(RAX), R(RAX)); + PXOR(XMM0, R(XMM0)); zeroSrc = J(true); SetJumpTarget(nonZeroSrc); } @@ -163,6 +199,12 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { // This stores the result on the stack for later processing. auto doNearestCall = [&](int off) { + static const X64Reg uReg = arg1Reg; + static const X64Reg vReg = arg2Reg; + static const X64Reg srcReg = arg3Reg; + static const X64Reg bufwReg = arg4Reg; + static const X64Reg resultReg = RAX; + MOV(32, R(uReg), MDisp(R12, off)); MOV(32, R(vReg), MDisp(R13, off)); MOV(64, R(srcReg), R(R14)); @@ -215,15 +257,17 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { if (RipAccessible(by256)) { MULPS(fpScratchReg5, M(by256)); } else { - MOV(PTRBITS, R(tempReg1), ImmPtr(by256)); - MULPS(fpScratchReg5, MatR(tempReg1)); + X64Reg tempReg = RAX; + MOV(PTRBITS, R(tempReg), ImmPtr(by256)); + MULPS(fpScratchReg5, MatR(tempReg)); } if (RipAccessible(ones)) { MOVAPS(XMM0, M(ones)); } else { - MOV(PTRBITS, R(tempReg1), ImmPtr(ones)); - MOVAPS(XMM0, MatR(tempReg1)); + X64Reg tempReg = RAX; + MOV(PTRBITS, R(tempReg), ImmPtr(ones)); + MOVAPS(XMM0, MatR(tempReg)); } SUBPS(XMM0, R(fpScratchReg5)); @@ -243,14 +287,16 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { if (RipAccessible(ones)) { MULPS(fpScratchReg5, M(by256)); } else { - MOV(PTRBITS, R(tempReg1), ImmPtr(by256)); - MULPS(fpScratchReg5, MatR(tempReg1)); + X64Reg tempReg = RAX; + MOV(PTRBITS, R(tempReg), ImmPtr(by256)); + MULPS(fpScratchReg5, MatR(tempReg)); } if (RipAccessible(ones)) { MOVAPS(XMM0, M(ones)); } else { - MOV(PTRBITS, R(tempReg1), ImmPtr(ones)); - MOVAPS(XMM0, MatR(tempReg1)); + X64Reg tempReg = RAX; + MOV(PTRBITS, R(tempReg), ImmPtr(ones)); + MOVAPS(XMM0, MatR(tempReg)); } SUBPS(XMM0, R(fpScratchReg5)); @@ -261,10 +307,7 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { ADDPS(fpScratchReg1, R(fpScratchReg3)); // Time to convert back to a single 32 bit value. - CVTPS2DQ(fpScratchReg1, R(fpScratchReg1)); - PACKSSDW(fpScratchReg1, R(fpScratchReg1)); - PACKUSWB(fpScratchReg1, R(fpScratchReg1)); - MOVD_xmm(R(resultReg), fpScratchReg1); + CVTPS2DQ(XMM0, R(fpScratchReg1)); if (id.hasInvalidPtr) { SetJumpTarget(zeroSrc); @@ -377,85 +420,109 @@ bool SamplerJitCache::Jit_GetDXT1Color(const SamplerID &id, int blockSize, int a // blockPos = src + (blockSize*v/4 * bufw/4 + blockSize*u/4) // Copy u (we'll need it later), and round down to the nearest 4 after scaling. - LEA(32, tempReg1, MScaled(uReg, blockSize / 4, 0)); - AND(32, R(tempReg1), Imm32(blockSize == 8 ? ~7 : ~15)); + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg srcBaseReg = regCache_.Alloc(RegCache::GEN_TEMP0); + LEA(32, srcBaseReg, MScaled(uReg, blockSize / 4, 0)); + AND(32, R(srcBaseReg), Imm32(blockSize == 8 ? ~7 : ~15)); // Add in srcReg already, since we'll be multiplying soon. - ADD(64, R(tempReg1), R(srcReg)); + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + ADD(64, R(srcBaseReg), R(srcReg)); - LEA(32, tempReg2, MScaled(vReg, blockSize / 4, 0)); - AND(32, R(tempReg2), Imm32(blockSize == 8 ? ~7 : ~15)); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + X64Reg srcOffsetReg = regCache_.Alloc(RegCache::GEN_TEMP1); + LEA(32, srcOffsetReg, MScaled(vReg, blockSize / 4, 0)); + AND(32, R(srcOffsetReg), Imm32(blockSize == 8 ? ~7 : ~15)); // Modify bufw in place and then multiply. + X64Reg bufwReg = regCache_.Find(RegCache::GEN_ARG_BUFW); SHR(32, R(bufwReg), Imm8(2)); - IMUL(32, tempReg2, R(bufwReg)); + IMUL(32, srcOffsetReg, R(bufwReg)); + regCache_.Unlock(bufwReg, RegCache::GEN_ARG_BUFW); + // We no longer need bufwReg. + regCache_.ForceRelease(RegCache::GEN_ARG_BUFW); // And now let's chop off the offset for u and v. AND(32, R(uReg), Imm32(3)); AND(32, R(vReg), Imm32(3)); - // Okay, at this point tempReg1 + tempReg2 = blockPos. To free up regs, put back in srcReg. - LEA(64, srcReg, MRegSum(tempReg1, tempReg2)); + // Okay, at this point srcBaseReg + srcOffsetReg = blockPos. To free up regs, put back in srcReg. + LEA(64, srcReg, MRegSum(srcBaseReg, srcOffsetReg)); + regCache_.Release(srcBaseReg, RegCache::GEN_TEMP0); + regCache_.Release(srcOffsetReg, RegCache::GEN_TEMP1); // The colorIndex is simply the 2 bits at blockPos + (v & 3), shifted right by (u & 3) twice. - MOVZX(32, 8, tempReg1, MRegSum(srcReg, vReg)); + X64Reg colorIndexReg = regCache_.Alloc(RegCache::GEN_TEMP0); + MOVZX(32, 8, colorIndexReg, MRegSum(srcReg, vReg)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + if (uReg == ECX) { - SHR(32, R(tempReg1), R(CL)); - SHR(32, R(tempReg1), R(CL)); + SHR(32, R(colorIndexReg), R(CL)); + SHR(32, R(colorIndexReg), R(CL)); } else { + bool hasRCX = regCache_.ChangeReg(RCX, RegCache::GEN_SHIFTVAL); + _assert_(hasRCX); LEA(32, ECX, MScaled(uReg, SCALE_2, 0)); - SHR(32, R(tempReg1), R(CL)); + SHR(32, R(colorIndexReg), R(CL)); } - AND(32, R(tempReg1), Imm32(3)); + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + AND(32, R(colorIndexReg), Imm32(3)); + + X64Reg color1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg color2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); // For colorIndex 0 or 1, we'll simply take the 565 color and convert. - CMP(32, R(tempReg1), Imm32(1)); + CMP(32, R(colorIndexReg), Imm32(1)); FixupBranch handleSimple565 = J_CC(CC_BE); // Otherwise, it depends if color1 or color2 is higher, so fetch them. - MOVZX(32, 16, bufwReg, MDisp(srcReg, 4)); - MOVZX(32, 16, tempReg2, MDisp(srcReg, 6)); + srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + MOVZX(32, 16, color1Reg, MDisp(srcReg, 4)); + MOVZX(32, 16, color2Reg, MDisp(srcReg, 6)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); - CMP(32, R(bufwReg), R(tempReg2)); + CMP(32, R(color1Reg), R(color2Reg)); FixupBranch handleMix23 = J_CC(CC_A, true); // If we're still here, then colorIndex is either 3 for 0 (easy) or 2 for 50% mix. XOR(32, R(resultReg), R(resultReg)); - CMP(32, R(tempReg1), Imm32(3)); + CMP(32, R(colorIndexReg), Imm32(3)); FixupBranch finishZero = J_CC(CC_E, true); // We'll need more regs. Grab two more. PUSH(R12); PUSH(R13); - // At this point, bufwReg=c1, tempReg2=c2, resultReg=FREE, tempReg1=FREE, R12=FREE, R13=FREE + // At this point, resultReg, colorIndexReg, R12, and R13 can be used as temps. // We'll add, then shift from 565 a bit less to "divide" by 2 for a 50/50 mix. // Start with summing R, then shift into position. - MOV(32, R(resultReg), R(bufwReg)); + MOV(32, R(resultReg), R(color1Reg)); AND(32, R(resultReg), Imm32(0x0000F800)); - MOV(32, R(tempReg1), R(tempReg2)); - AND(32, R(tempReg1), Imm32(0x0000F800)); - LEA(32, R12, MRegSum(resultReg, tempReg1)); + MOV(32, R(colorIndexReg), R(color2Reg)); + AND(32, R(colorIndexReg), Imm32(0x0000F800)); + LEA(32, R12, MRegSum(resultReg, colorIndexReg)); // The position is 9, instead of 8, due to doubling. SHR(32, R(R12), Imm8(9)); // For G, summing leaves it 4 right (doubling made it not need more.) - MOV(32, R(resultReg), R(bufwReg)); + MOV(32, R(resultReg), R(color1Reg)); AND(32, R(resultReg), Imm32(0x000007E0)); - MOV(32, R(tempReg1), R(tempReg2)); - AND(32, R(tempReg1), Imm32(0x000007E0)); - LEA(32, resultReg, MRegSum(resultReg, tempReg1)); + MOV(32, R(colorIndexReg), R(color2Reg)); + AND(32, R(colorIndexReg), Imm32(0x000007E0)); + LEA(32, resultReg, MRegSum(resultReg, colorIndexReg)); SHL(32, R(resultReg), Imm8(5 - 1)); // Now add G and R together. OR(32, R(resultReg), R(R12)); // At B, we're free to modify the regs in place, finally. - AND(32, R(bufwReg), Imm32(0x0000001F)); - AND(32, R(tempReg2), Imm32(0x0000001F)); - LEA(32, tempReg1, MRegSum(bufwReg, tempReg2)); + AND(32, R(color1Reg), Imm32(0x0000001F)); + AND(32, R(color2Reg), Imm32(0x0000001F)); + LEA(32, colorIndexReg, MRegSum(color1Reg, color2Reg)); // We shift left 2 into position (not 3 due to doubling), then 16 more into the B slot. - SHL(32, R(tempReg1), Imm8(16 + 2)); + SHL(32, R(colorIndexReg), Imm8(16 + 2)); // And combine into the result. - OR(32, R(resultReg), R(tempReg1)); + OR(32, R(resultReg), R(colorIndexReg)); POP(R13); POP(R12); @@ -463,45 +530,47 @@ bool SamplerJitCache::Jit_GetDXT1Color(const SamplerID &id, int blockSize, int a // Simply load the 565 color, and convert to 0888. SetJumpTarget(handleSimple565); - MOVZX(32, 16, tempReg1, MComplex(srcReg, tempReg1, SCALE_2, 4)); + srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + MOVZX(32, 16, colorIndexReg, MComplex(srcReg, colorIndexReg, SCALE_2, 4)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); // Start with R, shifting it into place. - MOV(32, R(resultReg), R(tempReg1)); + MOV(32, R(resultReg), R(colorIndexReg)); AND(32, R(resultReg), Imm32(0x0000F800)); SHR(32, R(resultReg), Imm8(8)); // Then take G and shift it too. - MOV(32, R(tempReg2), R(tempReg1)); - AND(32, R(tempReg2), Imm32(0x000007E0)); - SHL(32, R(tempReg2), Imm8(5)); + MOV(32, R(color2Reg), R(colorIndexReg)); + AND(32, R(color2Reg), Imm32(0x000007E0)); + SHL(32, R(color2Reg), Imm8(5)); // And now combine with R, shifting that in the process. - OR(32, R(resultReg), R(tempReg2)); + OR(32, R(resultReg), R(color2Reg)); // Modify B in place and OR in. - AND(32, R(tempReg1), Imm32(0x0000001F)); - SHL(32, R(tempReg1), Imm8(16 + 3)); - OR(32, R(resultReg), R(tempReg1)); + AND(32, R(colorIndexReg), Imm32(0x0000001F)); + SHL(32, R(colorIndexReg), Imm8(16 + 3)); + OR(32, R(resultReg), R(colorIndexReg)); FixupBranch finish565 = J(true); - // Here we'll mix color1 and color2 by 2/3 (which gets the 2 depends on tempReg1.) + // Here we'll mix color1 and color2 by 2/3 (which gets the 2 depends on colorIndexReg.) SetJumpTarget(handleMix23); // We'll need more regs. Grab two more to keep the stack aligned. PUSH(R12); PUSH(R13); - // If tempReg1 is 2, it's bufwReg * 2 + tempReg2, but if tempReg1 is 3, it's reversed. + // If colorIndexReg is 2, it's color1Reg * 2 + color2Reg, but if colorIndexReg is 3, it's reversed. // Let's swap the regs in that case. - CMP(32, R(tempReg1), Imm32(2)); + CMP(32, R(colorIndexReg), Imm32(2)); FixupBranch skipSwap23 = J_CC(CC_E); - XCHG(32, R(tempReg2), R(bufwReg)); + XCHG(32, R(color2Reg), R(color1Reg)); SetJumpTarget(skipSwap23); // Start off with R, adding together first... - MOV(32, R(resultReg), R(bufwReg)); + MOV(32, R(resultReg), R(color1Reg)); AND(32, R(resultReg), Imm32(0x0000F800)); - MOV(32, R(tempReg1), R(tempReg2)); - AND(32, R(tempReg1), Imm32(0x0000F800)); - LEA(32, resultReg, MComplex(tempReg1, resultReg, SCALE_2, 0)); + MOV(32, R(colorIndexReg), R(color2Reg)); + AND(32, R(colorIndexReg), Imm32(0x0000F800)); + LEA(32, resultReg, MComplex(colorIndexReg, resultReg, SCALE_2, 0)); // We'll overflow if we divide here, so shift into place already. SHR(32, R(resultReg), Imm8(8)); // Now we divide that by 3, by actually multiplying by AAAB and shifting off. @@ -510,11 +579,11 @@ bool SamplerJitCache::Jit_GetDXT1Color(const SamplerID &id, int blockSize, int a SHR(32, R(R12), Imm8(17)); // Now add up G. We leave this in place and shift right more. - MOV(32, R(resultReg), R(bufwReg)); + MOV(32, R(resultReg), R(color1Reg)); AND(32, R(resultReg), Imm32(0x000007E0)); - MOV(32, R(tempReg1), R(tempReg2)); - AND(32, R(tempReg1), Imm32(0x000007E0)); - LEA(32, resultReg, MComplex(tempReg1, resultReg, SCALE_2, 0)); + MOV(32, R(colorIndexReg), R(color2Reg)); + AND(32, R(colorIndexReg), Imm32(0x000007E0)); + LEA(32, resultReg, MComplex(colorIndexReg, resultReg, SCALE_2, 0)); // Again, multiply and now we use AAAB, this time masking. IMUL(32, resultReg, R(resultReg), Imm32(0x0000AAAB)); SHR(32, R(resultReg), Imm8(17 - 5)); @@ -523,22 +592,29 @@ bool SamplerJitCache::Jit_GetDXT1Color(const SamplerID &id, int blockSize, int a OR(32, R(resultReg), R(R12)); // Now for B, it starts in the lowest place so we'll need to mask. - AND(32, R(bufwReg), Imm32(0x0000001F)); - AND(32, R(tempReg2), Imm32(0x0000001F)); - LEA(32, tempReg1, MComplex(tempReg2, bufwReg, SCALE_2, 0)); + AND(32, R(color1Reg), Imm32(0x0000001F)); + AND(32, R(color2Reg), Imm32(0x0000001F)); + LEA(32, colorIndexReg, MComplex(color2Reg, color1Reg, SCALE_2, 0)); // Instead of shifting left, though, we multiply by a bit more. - IMUL(32, tempReg1, R(tempReg1), Imm32(0x0002AAAB)); - AND(32, R(tempReg1), Imm32(0x00FF0000)); - OR(32, R(resultReg), R(tempReg1)); + IMUL(32, colorIndexReg, R(colorIndexReg), Imm32(0x0002AAAB)); + AND(32, R(colorIndexReg), Imm32(0x00FF0000)); + OR(32, R(resultReg), R(colorIndexReg)); POP(R13); POP(R12); + regCache_.Release(colorIndexReg, RegCache::GEN_TEMP0); + regCache_.Release(color1Reg, RegCache::GEN_TEMP1); + regCache_.Release(color2Reg, RegCache::GEN_TEMP2); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + SetJumpTarget(finishMix50); SetJumpTarget(finish565); // In all these cases, it's time to add in alpha. Zero doesn't get it. if (alpha != 0) { + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); OR(32, R(resultReg), Imm32(alpha << 24)); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); } SetJumpTarget(finishZero); @@ -548,62 +624,107 @@ bool SamplerJitCache::Jit_GetDXT1Color(const SamplerID &id, int blockSize, int a bool SamplerJitCache::Jit_ApplyDXTAlpha(const SamplerID &id) { GETextureFormat fmt = id.TexFmt(); + + // At this point, srcReg points at the block, and u/v are offsets inside it. + + bool success = false; if (fmt == GE_TFMT_DXT3) { - MOVZX(32, 16, tempReg1, MComplex(srcReg, vReg, SCALE_2, 8)); + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + + if (uReg != RCX) { + regCache_.ChangeReg(RCX, RegCache::GEN_SHIFTVAL); + _assert_(regCache_.Has(RegCache::GEN_SHIFTVAL)); + } + + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + MOVZX(32, 16, temp1Reg, MComplex(srcReg, vReg, SCALE_2, 8)); + // Still depending on it being GEN_SHIFTVAL or GEN_ARG_U above. LEA(32, RCX, MScaled(uReg, SCALE_4, 0)); - SHR(32, R(tempReg1), R(CL)); - SHL(32, R(tempReg1), Imm8(28)); - OR(32, R(resultReg), R(tempReg1)); - return true; + SHR(32, R(temp1Reg), R(CL)); + SHL(32, R(temp1Reg), Imm8(28)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + OR(32, R(resultReg), R(temp1Reg)); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + + success = true; + + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR); + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + regCache_.ForceRelease(RegCache::GEN_ARG_U); + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + regCache_.ForceRelease(RegCache::GEN_ARG_V); } else if (fmt == GE_TFMT_DXT5) { + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + + if (uReg != RCX) + regCache_.ChangeReg(RCX, RegCache::GEN_SHIFTVAL); + // Let's figure out the alphaIndex bit offset so we can read the right byte. // bitOffset = (u + v * 4) * 3; LEA(32, uReg, MComplex(uReg, vReg, SCALE_4, 0)); LEA(32, uReg, MComplex(uReg, uReg, SCALE_2, 0)); + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + regCache_.ForceRelease(RegCache::GEN_ARG_V); + + X64Reg alphaIndexReg = regCache_.Alloc(RegCache::GEN_TEMP0); + X64Reg alpha1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg alpha2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + // And now the byte offset and bit from there, from those. - MOV(32, R(vReg), R(uReg)); - SHR(32, R(vReg), Imm8(3)); + MOV(32, R(alphaIndexReg), R(uReg)); + SHR(32, R(alphaIndexReg), Imm8(3)); AND(32, R(uReg), Imm32(7)); // Load 16 bits and mask, in case it straddles bytes. - MOVZX(32, 16, vReg, MComplex(srcReg, vReg, SCALE_1, 8)); - // If not, it's in bufwReg. - if (uReg != RCX) + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + MOVZX(32, 16, alphaIndexReg, MComplex(srcReg, alphaIndexReg, SCALE_1, 8)); + // If not, it's in what was bufwReg. + if (uReg != RCX) { + _assert_(regCache_.Has(RegCache::GEN_SHIFTVAL)); MOV(32, R(RCX), R(uReg)); - SHR(32, R(vReg), R(CL)); - AND(32, R(vReg), Imm32(7)); + } + SHR(32, R(alphaIndexReg), R(CL)); + AND(32, R(alphaIndexReg), Imm32(7)); - // Okay, now check for 0 or 1 alphaIndex in tempReg1, those are simple. - CMP(32, R(vReg), Imm32(1)); + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + regCache_.ForceRelease(RegCache::GEN_ARG_U); + + // Okay, now check for 0 or 1 alphaIndex in alphaIndexReg, those are simple. + CMP(32, R(alphaIndexReg), Imm32(1)); FixupBranch handleSimple = J_CC(CC_BE, true); // Now load a1 and a2, since the rest depend on those values. Frees up srcReg. - MOVZX(32, 8, tempReg1, MDisp(srcReg, 14)); - MOVZX(32, 8, tempReg2, MDisp(srcReg, 15)); + MOVZX(32, 8, alpha1Reg, MDisp(srcReg, 14)); + MOVZX(32, 8, alpha2Reg, MDisp(srcReg, 15)); - CMP(32, R(tempReg1), R(tempReg2)); + CMP(32, R(alpha1Reg), R(alpha2Reg)); FixupBranch handleLerp8 = J_CC(CC_A); // Okay, check for zero or full alpha, at alphaIndex 6 or 7. XOR(32, R(srcReg), R(srcReg)); - CMP(32, R(vReg), Imm32(6)); + CMP(32, R(alphaIndexReg), Imm32(6)); FixupBranch finishZero = J_CC(CC_E, true); // Remember, MOV doesn't affect flags. MOV(32, R(srcReg), Imm32(0xFF)); FixupBranch finishFull = J_CC(CC_A, true); // At this point, we're handling a 6-step lerp between alpha1 and alpha2. - SHL(32, R(vReg), Imm8(8)); + SHL(32, R(alphaIndexReg), Imm8(8)); // Prepare a multiplier in uReg and multiply alpha1 by it. MOV(32, R(uReg), Imm32(6 << 8)); - SUB(32, R(uReg), R(vReg)); - IMUL(32, tempReg1, R(uReg)); - // And now the same for alpha2, using vReg. - SUB(32, R(vReg), Imm32(1 << 8)); - IMUL(32, tempReg2, R(vReg)); + SUB(32, R(uReg), R(alphaIndexReg)); + IMUL(32, alpha1Reg, R(uReg)); + // And now the same for alpha2, using alphaIndexReg. + SUB(32, R(alphaIndexReg), Imm32(1 << 8)); + IMUL(32, alpha2Reg, R(alphaIndexReg)); // Let's skip a step and sum before dividing by 5, also adding the 31. - LEA(32, srcReg, MComplex(tempReg1, tempReg2, SCALE_1, 5 * 31)); + LEA(32, srcReg, MComplex(alpha1Reg, alpha2Reg, SCALE_1, 5 * 31)); // To divide by 5, we will actually multiply by 0x3334 and shift. IMUL(32, srcReg, Imm32(0x3334)); SHR(32, R(srcReg), Imm8(24)); @@ -611,30 +732,34 @@ bool SamplerJitCache::Jit_ApplyDXTAlpha(const SamplerID &id) { // This will be a 8-step lerp between alpha1 and alpha2. SetJumpTarget(handleLerp8); - SHL(32, R(vReg), Imm8(8)); + SHL(32, R(alphaIndexReg), Imm8(8)); // Prepare a multiplier in uReg and multiply alpha1 by it. MOV(32, R(uReg), Imm32(8 << 8)); - SUB(32, R(uReg), R(vReg)); - IMUL(32, tempReg1, R(uReg)); - // And now the same for alpha2, using vReg. - SUB(32, R(vReg), Imm32(1 << 8)); - IMUL(32, tempReg2, R(vReg)); + SUB(32, R(uReg), R(alphaIndexReg)); + IMUL(32, alpha1Reg, R(uReg)); + // And now the same for alpha2, using alphaIndexReg. + SUB(32, R(alphaIndexReg), Imm32(1 << 8)); + IMUL(32, alpha2Reg, R(alphaIndexReg)); // And divide by 7 together here too, also adding the 31. - LEA(32, srcReg, MComplex(tempReg1, tempReg2, SCALE_1, 7 * 31)); + LEA(32, srcReg, MComplex(alpha1Reg, alpha2Reg, SCALE_1, 7 * 31)); // Our magic constant here is 0x124A, but it's a bit more complex than just a shift. - IMUL(32, tempReg1, R(srcReg), Imm32(0x124A)); - SHR(32, R(tempReg1), Imm8(15)); - SUB(32, R(srcReg), R(tempReg1)); + IMUL(32, alpha1Reg, R(srcReg), Imm32(0x124A)); + SHR(32, R(alpha1Reg), Imm8(15)); + SUB(32, R(srcReg), R(alpha1Reg)); SHR(32, R(srcReg), Imm8(1)); - ADD(32, R(srcReg), R(tempReg1)); + ADD(32, R(srcReg), R(alpha1Reg)); SHR(32, R(srcReg), Imm8(10)); FixupBranch finishLerp8 = J(); SetJumpTarget(handleSimple); // Just load the specified alpha byte. - MOVZX(32, 8, srcReg, MComplex(srcReg, vReg, SCALE_1, 14)); + MOVZX(32, 8, srcReg, MComplex(srcReg, alphaIndexReg, SCALE_1, 14)); + + regCache_.Release(alphaIndexReg, RegCache::GEN_TEMP0); + regCache_.Release(alpha1Reg, RegCache::GEN_TEMP1); + regCache_.Release(alpha2Reg, RegCache::GEN_TEMP2); SetJumpTarget(finishFull); SetJumpTarget(finishZero); @@ -642,12 +767,17 @@ bool SamplerJitCache::Jit_ApplyDXTAlpha(const SamplerID &id) { SetJumpTarget(finishLerp8); SHL(32, R(srcReg), Imm8(24)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); OR(32, R(resultReg), R(srcReg)); - return true; + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + success = true; + + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR); } - _dbg_assert_(false); - return false; + _dbg_assert_(success); + return success; } bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) { @@ -655,44 +785,65 @@ bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) { return Jit_GetTexDataSwizzled(id, bitsPerTexel); } - // srcReg might be EDX, so let's copy that before we multiply. + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + + // srcReg might be EDX, so let's copy and uReg that before we multiply. + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + bool success = true; switch (bitsPerTexel) { case 32: case 16: case 8: - LEA(64, tempReg1, MComplex(srcReg, uReg, bitsPerTexel / 8, 0)); + LEA(64, temp1Reg, MComplex(srcReg, uReg, bitsPerTexel / 8, 0)); break; case 4: { - XOR(32, R(tempReg2), R(tempReg2)); + XOR(32, R(temp2Reg), R(temp2Reg)); SHR(32, R(uReg), Imm8(1)); FixupBranch skip = J_CC(CC_NC); // Track whether we shifted a 1 off or not. - MOV(32, R(tempReg2), Imm32(4)); + MOV(32, R(temp2Reg), Imm32(4)); SetJumpTarget(skip); - LEA(64, tempReg1, MRegSum(srcReg, uReg)); + LEA(64, temp1Reg, MRegSum(srcReg, uReg)); break; } default: - return false; + success = false; + break; } + // All done with u and texptr. + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR); + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + regCache_.ForceRelease(RegCache::GEN_ARG_U); - MOV(32, R(EAX), R(vReg)); - MUL(32, R(bufwReg)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + MOV(32, R(resultReg), R(vReg)); + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + regCache_.ForceRelease(RegCache::GEN_ARG_V); + + X64Reg bufwReg = regCache_.Find(RegCache::GEN_ARG_BUFW); + IMUL(32, resultReg, R(bufwReg)); + regCache_.Unlock(bufwReg, RegCache::GEN_ARG_BUFW); + // We can throw bufw away, now. + regCache_.ForceRelease(RegCache::GEN_ARG_BUFW); switch (bitsPerTexel) { case 32: case 16: case 8: - MOVZX(32, bitsPerTexel, resultReg, MComplex(tempReg1, RAX, bitsPerTexel / 8, 0)); + MOVZX(32, bitsPerTexel, resultReg, MComplex(temp1Reg, resultReg, bitsPerTexel / 8, 0)); break; case 4: { - SHR(32, R(RAX), Imm8(1)); - MOV(8, R(resultReg), MRegSum(tempReg1, RAX)); + SHR(32, R(resultReg), Imm8(1)); + MOV(8, R(resultReg), MRegSum(temp1Reg, resultReg)); // RCX is now free. - MOV(8, R(RCX), R(tempReg2)); + MOV(8, R(RCX), R(temp2Reg)); SHR(8, R(resultReg), R(RCX)); // Zero out any bits not shifted off. AND(32, R(resultReg), Imm8(0x0F)); @@ -700,42 +851,70 @@ bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) { } default: - return false; + success = false; + break; } - return true; + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + return success; } bool SamplerJitCache::Jit_GetTexDataSwizzled4() { - // Get the horizontal tile pos into tempReg1. - LEA(32, tempReg1, MScaled(uReg, SCALE_4, 0)); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + + // Get the horizontal tile pos into temp1Reg. + LEA(32, temp1Reg, MScaled(uReg, SCALE_4, 0)); // Note: imm8 sign extends negative. - AND(32, R(tempReg1), Imm8(~127)); + AND(32, R(temp1Reg), Imm8(~127)); - // Add vertical offset inside tile to tempReg1. - LEA(32, tempReg2, MScaled(vReg, SCALE_4, 0)); - AND(32, R(tempReg2), Imm8(31)); - LEA(32, tempReg1, MComplex(tempReg1, tempReg2, SCALE_4, 0)); + // Add vertical offset inside tile to temp1Reg. + LEA(32, temp2Reg, MScaled(vReg, SCALE_4, 0)); + AND(32, R(temp2Reg), Imm8(31)); + LEA(32, temp1Reg, MComplex(temp1Reg, temp2Reg, SCALE_4, 0)); // Add srcReg, since we'll need it at some point. - ADD(64, R(tempReg1), R(srcReg)); + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + ADD(64, R(temp1Reg), R(srcReg)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR); - // Now find the vertical tile pos, and add to tempReg1. + // Now find the vertical tile pos, and add to temp1Reg. SHR(32, R(vReg), Imm8(3)); - LEA(32, EAX, MScaled(bufwReg, SCALE_4, 0)); - MUL(32, R(vReg)); - ADD(64, R(tempReg1), R(EAX)); + X64Reg bufwReg = regCache_.Find(RegCache::GEN_ARG_BUFW); + LEA(32, temp2Reg, MScaled(bufwReg, SCALE_4, 0)); + regCache_.Unlock(bufwReg, RegCache::GEN_ARG_BUFW); + // We can throw bufw away, now. + regCache_.ForceRelease(RegCache::GEN_ARG_BUFW); + + IMUL(32, temp2Reg, R(vReg)); + ADD(64, R(temp1Reg), R(temp2Reg)); + // We no longer have a good value in vReg. + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + regCache_.ForceRelease(RegCache::GEN_ARG_V); // Last and possible also least, the horizontal offset inside the tile. AND(32, R(uReg), Imm8(31)); SHR(32, R(uReg), Imm8(1)); - MOV(8, R(resultReg), MRegSum(tempReg1, uReg)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + MOV(8, R(resultReg), MRegSum(temp1Reg, uReg)); FixupBranch skipNonZero = J_CC(CC_NC); // If the horizontal offset was odd, take the upper 4. SHR(8, R(resultReg), Imm8(4)); SetJumpTarget(skipNonZero); // Zero out the rest of the bits. AND(32, R(resultReg), Imm8(0x0F)); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + // This destroyed u as well. + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + regCache_.ForceRelease(RegCache::GEN_ARG_U); + + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); return true; } @@ -745,11 +924,18 @@ bool SamplerJitCache::Jit_GetTexDataSwizzled(const SamplerID &id, int bitsPerTex return Jit_GetTexDataSwizzled4(); } - LEA(32, tempReg1, MScaled(vReg, SCALE_4, 0)); - AND(32, R(tempReg1), Imm8(31)); + bool success = true; + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U); + X64Reg vReg = regCache_.Find(RegCache::GEN_ARG_V); + + LEA(32, temp1Reg, MScaled(vReg, SCALE_4, 0)); + AND(32, R(temp1Reg), Imm8(31)); AND(32, R(vReg), Imm8(~7)); - MOV(32, R(tempReg2), R(uReg)); + MOV(32, R(temp2Reg), R(uReg)); MOV(32, R(resultReg), R(uReg)); switch (bitsPerTexel) { case 32: @@ -757,105 +943,133 @@ bool SamplerJitCache::Jit_GetTexDataSwizzled(const SamplerID &id, int bitsPerTex break; case 16: SHR(32, R(vReg), Imm8(1)); - SHR(32, R(tempReg2), Imm8(1)); + SHR(32, R(temp2Reg), Imm8(1)); SHR(32, R(resultReg), Imm8(3)); break; case 8: SHR(32, R(vReg), Imm8(2)); - SHR(32, R(tempReg2), Imm8(2)); + SHR(32, R(temp2Reg), Imm8(2)); SHR(32, R(resultReg), Imm8(4)); break; default: - return false; + success = false; + break; } - AND(32, R(tempReg2), Imm8(3)); + AND(32, R(temp2Reg), Imm8(3)); SHL(32, R(resultReg), Imm8(5)); - ADD(32, R(tempReg1), R(tempReg2)); - ADD(32, R(tempReg1), R(resultReg)); + ADD(32, R(temp1Reg), R(temp2Reg)); + ADD(32, R(temp1Reg), R(resultReg)); - // We may clobber srcReg in the MUL, so let's grab it now. - LEA(64, tempReg1, MComplex(srcReg, tempReg1, SCALE_4, 0)); + // We may clobber srcReg in the multiply, so let's grab it now. + X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR); + LEA(64, temp1Reg, MComplex(srcReg, temp1Reg, SCALE_4, 0)); + regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR); + regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR); - LEA(32, EAX, MScaled(bufwReg, SCALE_4, 0)); - MUL(32, R(vReg)); + X64Reg bufwReg = regCache_.Find(RegCache::GEN_ARG_BUFW); + LEA(32, resultReg, MScaled(bufwReg, SCALE_4, 0)); + regCache_.Unlock(bufwReg, RegCache::GEN_ARG_BUFW); + // We can throw bufw away, now. + regCache_.ForceRelease(RegCache::GEN_ARG_BUFW); + + IMUL(32, resultReg, R(vReg)); + // We no longer have a good value in vReg. + regCache_.Unlock(vReg, RegCache::GEN_ARG_V); + regCache_.ForceRelease(RegCache::GEN_ARG_V); switch (bitsPerTexel) { case 32: - MOV(bitsPerTexel, R(resultReg), MRegSum(tempReg1, EAX)); + MOV(bitsPerTexel, R(resultReg), MRegSum(temp1Reg, resultReg)); break; case 16: AND(32, R(uReg), Imm8(1)); - // Multiply by two by just adding twice. - ADD(32, R(EAX), R(uReg)); - ADD(32, R(EAX), R(uReg)); - MOVZX(32, bitsPerTexel, resultReg, MRegSum(tempReg1, EAX)); + LEA(32, resultReg, MComplex(resultReg, uReg, SCALE_2, 0)); + MOVZX(32, bitsPerTexel, resultReg, MRegSum(temp1Reg, resultReg)); break; case 8: AND(32, R(uReg), Imm8(3)); - ADD(32, R(EAX), R(uReg)); - MOVZX(32, bitsPerTexel, resultReg, MRegSum(tempReg1, EAX)); + ADD(32, R(resultReg), R(uReg)); + MOVZX(32, bitsPerTexel, resultReg, MRegSum(temp1Reg, resultReg)); break; default: - return false; + success = false; + break; } - return true; + regCache_.Unlock(uReg, RegCache::GEN_ARG_U); + regCache_.ForceRelease(RegCache::GEN_ARG_U); + + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); + return success; } bool SamplerJitCache::Jit_Decode5650() { - MOV(32, R(tempReg2), R(resultReg)); - AND(32, R(tempReg2), Imm32(0x0000001F)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + + MOV(32, R(temp2Reg), R(resultReg)); + AND(32, R(temp2Reg), Imm32(0x0000001F)); // B (we do R and B at the same time, they're both 5.) - MOV(32, R(tempReg1), R(resultReg)); - AND(32, R(tempReg1), Imm32(0x0000F800)); - SHL(32, R(tempReg1), Imm8(5)); - OR(32, R(tempReg2), R(tempReg1)); + MOV(32, R(temp1Reg), R(resultReg)); + AND(32, R(temp1Reg), Imm32(0x0000F800)); + SHL(32, R(temp1Reg), Imm8(5)); + OR(32, R(temp2Reg), R(temp1Reg)); // Expand 5 -> 8. At this point we have 00BB00RR. - MOV(32, R(tempReg1), R(tempReg2)); - SHL(32, R(tempReg2), Imm8(3)); - SHR(32, R(tempReg1), Imm8(2)); - OR(32, R(tempReg2), R(tempReg1)); - AND(32, R(tempReg2), Imm32(0x00FF00FF)); + MOV(32, R(temp1Reg), R(temp2Reg)); + SHL(32, R(temp2Reg), Imm8(3)); + SHR(32, R(temp1Reg), Imm8(2)); + OR(32, R(temp2Reg), R(temp1Reg)); + AND(32, R(temp2Reg), Imm32(0x00FF00FF)); // Now's as good a time to put in A as any. - OR(32, R(tempReg2), Imm32(0xFF000000)); + OR(32, R(temp2Reg), Imm32(0xFF000000)); // Last, we need to align, extract, and expand G. // 3 to align to G, and then 2 to expand to 8. SHL(32, R(resultReg), Imm8(3 + 2)); AND(32, R(resultReg), Imm32(0x0000FC00)); - MOV(32, R(tempReg1), R(resultReg)); + MOV(32, R(temp1Reg), R(resultReg)); // 2 to account for resultReg being preshifted, 4 for expansion. - SHR(32, R(tempReg1), Imm8(2 + 4)); - OR(32, R(resultReg), R(tempReg1)); + SHR(32, R(temp1Reg), Imm8(2 + 4)); + OR(32, R(resultReg), R(temp1Reg)); AND(32, R(resultReg), Imm32(0x0000FF00)); - OR(32, R(resultReg), R(tempReg2)); + OR(32, R(resultReg), R(temp2Reg)); + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); return true; } bool SamplerJitCache::Jit_Decode5551() { - MOV(32, R(tempReg2), R(resultReg)); - MOV(32, R(tempReg1), R(resultReg)); - AND(32, R(tempReg2), Imm32(0x0000001F)); - AND(32, R(tempReg1), Imm32(0x000003E0)); - SHL(32, R(tempReg1), Imm8(3)); - OR(32, R(tempReg2), R(tempReg1)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); - MOV(32, R(tempReg1), R(resultReg)); - AND(32, R(tempReg1), Imm32(0x00007C00)); - SHL(32, R(tempReg1), Imm8(6)); - OR(32, R(tempReg2), R(tempReg1)); + MOV(32, R(temp2Reg), R(resultReg)); + MOV(32, R(temp1Reg), R(resultReg)); + AND(32, R(temp2Reg), Imm32(0x0000001F)); + AND(32, R(temp1Reg), Imm32(0x000003E0)); + SHL(32, R(temp1Reg), Imm8(3)); + OR(32, R(temp2Reg), R(temp1Reg)); + + MOV(32, R(temp1Reg), R(resultReg)); + AND(32, R(temp1Reg), Imm32(0x00007C00)); + SHL(32, R(temp1Reg), Imm8(6)); + OR(32, R(temp2Reg), R(temp1Reg)); // Expand 5 -> 8. After this is just A. - MOV(32, R(tempReg1), R(tempReg2)); - SHL(32, R(tempReg2), Imm8(3)); - SHR(32, R(tempReg1), Imm8(2)); + MOV(32, R(temp1Reg), R(temp2Reg)); + SHL(32, R(temp2Reg), Imm8(3)); + SHR(32, R(temp1Reg), Imm8(2)); // Chop off the bits that were shifted out. - AND(32, R(tempReg1), Imm32(0x00070707)); - OR(32, R(tempReg2), R(tempReg1)); + AND(32, R(temp1Reg), Imm32(0x00070707)); + OR(32, R(temp2Reg), R(temp1Reg)); // For A, we shift it to a single bit, and then subtract and XOR. // That's probably the simplest way to expand it... @@ -864,29 +1078,44 @@ bool SamplerJitCache::Jit_Decode5551() { SUB(32, R(resultReg), Imm8(1)); XOR(32, R(resultReg), Imm32(0xFF000000)); AND(32, R(resultReg), Imm32(0xFF000000)); - OR(32, R(resultReg), R(tempReg2)); + OR(32, R(resultReg), R(temp2Reg)); + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); return true; } alignas(16) static const u32 color4444mask[4] = { 0xf00ff00f, 0xf00ff00f, 0xf00ff00f, 0xf00ff00f, }; bool SamplerJitCache::Jit_Decode4444() { - MOVD_xmm(fpScratchReg1, R(resultReg)); - PUNPCKLBW(fpScratchReg1, R(fpScratchReg1)); + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + X64Reg vecTemp1Reg = regCache_.Alloc(RegCache::VEC_TEMP1); + X64Reg vecTemp2Reg = regCache_.Alloc(RegCache::VEC_TEMP2); + X64Reg vecTemp3Reg = regCache_.Alloc(RegCache::VEC_TEMP3); + + MOVD_xmm(vecTemp1Reg, R(resultReg)); + PUNPCKLBW(vecTemp1Reg, R(vecTemp1Reg)); if (RipAccessible(color4444mask)) { - PAND(fpScratchReg1, M(color4444mask)); + PAND(vecTemp1Reg, M(color4444mask)); } else { - MOV(PTRBITS, R(tempReg1), ImmPtr(color4444mask)); - PAND(fpScratchReg1, MatR(tempReg1)); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + MOV(PTRBITS, R(temp1Reg), ImmPtr(color4444mask)); + PAND(vecTemp1Reg, MatR(temp1Reg)); + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); } - MOVSS(fpScratchReg2, R(fpScratchReg1)); - MOVSS(fpScratchReg3, R(fpScratchReg1)); - PSRLW(fpScratchReg2, 4); - PSLLW(fpScratchReg3, 4); - POR(fpScratchReg1, R(fpScratchReg2)); - POR(fpScratchReg1, R(fpScratchReg3)); - MOVD_xmm(R(resultReg), fpScratchReg1); + MOVSS(vecTemp2Reg, R(vecTemp1Reg)); + MOVSS(vecTemp3Reg, R(vecTemp1Reg)); + PSRLW(vecTemp2Reg, 4); + PSLLW(vecTemp3Reg, 4); + POR(vecTemp1Reg, R(vecTemp2Reg)); + POR(vecTemp1Reg, R(vecTemp3Reg)); + MOVD_xmm(R(resultReg), vecTemp1Reg); + + regCache_.Release(vecTemp1Reg, RegCache::VEC_TEMP1); + regCache_.Release(vecTemp2Reg, RegCache::VEC_TEMP2); + regCache_.Release(vecTemp3Reg, RegCache::VEC_TEMP3); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); return true; } @@ -895,17 +1124,26 @@ bool SamplerJitCache::Jit_TransformClutIndex(const SamplerID &id, int bitsPerInd if (!id.hasClutShift && !id.hasClutMask && !id.hasClutOffset) { // This is simple - just mask if necessary. if (bitsPerIndex > 8) { + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); AND(32, R(resultReg), Imm32(0x000000FF)); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); } return true; } - MOV(PTRBITS, R(tempReg1), ImmPtr(&gstate.clutformat)); - MOV(32, R(tempReg1), MatR(tempReg1)); + bool hasRCX = regCache_.ChangeReg(RCX, RegCache::GEN_SHIFTVAL); + _assert_msg_(hasRCX, "Could not obtain RCX, locked?"); + + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + MOV(PTRBITS, R(temp1Reg), ImmPtr(&gstate.clutformat)); + MOV(32, R(temp1Reg), MatR(temp1Reg)); + + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); // Shift = (clutformat >> 2) & 0x1F if (id.hasClutShift) { - MOV(32, R(RCX), R(tempReg1)); + _assert_(regCache_.Has(RegCache::GEN_SHIFTVAL)); + MOV(32, R(RCX), R(temp1Reg)); SHR(32, R(RCX), Imm8(2)); AND(32, R(RCX), Imm8(0x1F)); SHR(32, R(resultReg), R(RCX)); @@ -913,9 +1151,11 @@ bool SamplerJitCache::Jit_TransformClutIndex(const SamplerID &id, int bitsPerInd // Mask = (clutformat >> 8) & 0xFF if (id.hasClutMask) { - MOV(32, R(tempReg2), R(tempReg1)); - SHR(32, R(tempReg2), Imm8(8)); - AND(32, R(resultReg), R(tempReg2)); + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + MOV(32, R(temp2Reg), R(temp1Reg)); + SHR(32, R(temp2Reg), Imm8(8)); + AND(32, R(resultReg), R(temp2Reg)); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); } // We need to wrap any entries beyond the first 1024 bytes. @@ -929,59 +1169,84 @@ bool SamplerJitCache::Jit_TransformClutIndex(const SamplerID &id, int bitsPerInd // Offset = (clutformat >> 12) & 0x01F0 if (id.hasClutOffset) { - SHR(32, R(tempReg1), Imm8(16)); - SHL(32, R(tempReg1), Imm8(4)); - OR(32, R(resultReg), R(tempReg1)); + SHR(32, R(temp1Reg), Imm8(16)); + SHL(32, R(temp1Reg), Imm8(4)); + OR(32, R(resultReg), R(temp1Reg)); AND(32, R(resultReg), Imm32(offsetMask)); } + + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); return true; } bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) { + X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT); + if (!id.useSharedClut) { - // TODO: Need to load from RAM, always. - if (id.linear) { -#ifdef _WIN32 - const int argOffset = 24 + 48 + 8 + 32; - // Extra 8 to account for CALL. - MOV(32, R(tempReg2), MDisp(RSP, argOffset + 16 + 8)); -#else - // Extra 8 to account for CALL. - MOV(32, R(tempReg2), MDisp(RSP, 24 + 48 + 8 + 8)); -#endif - LEA(32, tempReg2, MScaled(tempReg2, SCALE_4, 0)); - } else { -#ifdef _WIN32 - // The argument was saved on the stack. - MOV(32, R(tempReg2), MDisp(RSP, 40)); - LEA(32, tempReg2, MScaled(tempReg2, SCALE_4, 0)); -#else + X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2); + + if (regCache_.Has(RegCache::GEN_ARG_LEVEL)) { + X64Reg levelReg = regCache_.Find(RegCache::GEN_ARG_LEVEL); // We need to multiply by 16 and add, LEA allows us to copy too. - LEA(32, tempReg2, MScaled(levelReg, SCALE_4, 0)); + LEA(32, temp2Reg, MScaled(levelReg, SCALE_4, 0)); + regCache_.Unlock(levelReg, RegCache::GEN_ARG_LEVEL); + regCache_.ForceRelease(RegCache::GEN_ARG_LEVEL); + } else { + if (id.linear) { +#ifdef _WIN32 + const int argOffset = 24 + 48 + 8 + 32; + // Extra 8 to account for CALL. + MOV(32, R(temp2Reg), MDisp(RSP, argOffset + 16 + 8)); +#else + // Extra 8 to account for CALL. + MOV(32, R(temp2Reg), MDisp(RSP, 24 + 48 + 8 + 8)); #endif + } else { +#ifdef _WIN32 + // The argument was saved on the stack. + MOV(32, R(temp2Reg), MDisp(RSP, 40)); +#else + _assert_(false); +#endif + } + LEA(32, temp2Reg, MScaled(temp2Reg, SCALE_4, 0)); } // Second step of the multiply by 16 (since we only multiplied by 4 before.) - LEA(64, resultReg, MComplex(resultReg, tempReg2, SCALE_4, 0)); + LEA(64, resultReg, MComplex(resultReg, temp2Reg, SCALE_4, 0)); + regCache_.Release(temp2Reg, RegCache::GEN_TEMP2); } - MOV(PTRBITS, R(tempReg1), ImmPtr(clut)); + X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1); + MOV(PTRBITS, R(temp1Reg), ImmPtr(clut)); + + switch (id.ClutFmt()) { + case GE_CMODE_16BIT_BGR5650: + case GE_CMODE_16BIT_ABGR5551: + case GE_CMODE_16BIT_ABGR4444: + MOVZX(32, 16, resultReg, MComplex(temp1Reg, resultReg, SCALE_2, 0)); + break; + + case GE_CMODE_32BIT_ABGR8888: + MOV(32, R(resultReg), MComplex(temp1Reg, resultReg, SCALE_4, 0)); + break; + } + + regCache_.Release(temp1Reg, RegCache::GEN_TEMP1); + regCache_.Unlock(resultReg, RegCache::GEN_RESULT); switch (id.ClutFmt()) { case GE_CMODE_16BIT_BGR5650: - MOVZX(32, 16, resultReg, MComplex(tempReg1, resultReg, SCALE_2, 0)); return Jit_Decode5650(); case GE_CMODE_16BIT_ABGR5551: - MOVZX(32, 16, resultReg, MComplex(tempReg1, resultReg, SCALE_2, 0)); return Jit_Decode5551(); case GE_CMODE_16BIT_ABGR4444: - MOVZX(32, 16, resultReg, MComplex(tempReg1, resultReg, SCALE_2, 0)); return Jit_Decode4444(); case GE_CMODE_32BIT_ABGR8888: - MOV(32, R(resultReg), MComplex(tempReg1, resultReg, SCALE_4, 0)); return true; default: