diff --git a/CMakeLists.txt b/CMakeLists.txt index b6ed0e50e3..98d4f79846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1089,6 +1089,7 @@ list(APPEND CoreExtra Core/MIPS/x86/RegCacheFPU.cpp Core/MIPS/x86/RegCacheFPU.h GPU/Common/VertexDecoderX86.cpp + GPU/Software/SamplerX86.cpp ) list(APPEND CoreExtra @@ -1284,6 +1285,8 @@ set(GPU_SOURCES GPU/Software/Lighting.h GPU/Software/Rasterizer.cpp GPU/Software/Rasterizer.h + GPU/Software/Sampler.cpp + GPU/Software/Sampler.h GPU/Software/SoftGpu.cpp GPU/Software/SoftGpu.h GPU/Software/TransformUnit.cpp diff --git a/Common/x64Emitter.cpp b/Common/x64Emitter.cpp index 3d9b9c4e81..aff3983bc6 100644 --- a/Common/x64Emitter.cpp +++ b/Common/x64Emitter.cpp @@ -1673,6 +1673,11 @@ void XEmitter::PUNPCKLWD(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x61, void XEmitter::PUNPCKLDQ(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x62, dest, arg);} void XEmitter::PUNPCKLQDQ(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x6C, dest, arg);} +void XEmitter::PUNPCKHBW(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x68, dest, arg);} +void XEmitter::PUNPCKHWD(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x69, dest, arg);} +void XEmitter::PUNPCKHDQ(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x6A, dest, arg);} +void XEmitter::PUNPCKHQDQ(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0x6D, dest, arg);} + void XEmitter::PSRLW(X64Reg reg, int shift) { WriteSSEOp(0x66, 0x71, (X64Reg)2, R(reg)); @@ -1736,6 +1741,11 @@ void XEmitter::PSRAD(X64Reg reg, int shift) Write8(shift); } +void XEmitter::PMULLW(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0xD5, dest, arg);} +void XEmitter::PMULHW(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0xE5, dest, arg);} +void XEmitter::PMULHUW(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0xE4, dest, arg);} +void XEmitter::PMULUDQ(X64Reg dest, const OpArg &arg) {WriteSSEOp(0x66, 0xF4, dest, arg);} + void XEmitter::WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes) { if (!cpu_info.bSSSE3) diff --git a/Common/x64Emitter.h b/Common/x64Emitter.h index bcfc592bdd..5f3e27ebc0 100644 --- a/Common/x64Emitter.h +++ b/Common/x64Emitter.h @@ -756,6 +756,11 @@ public: void PUNPCKLDQ(X64Reg dest, const OpArg &arg); void PUNPCKLQDQ(X64Reg dest, const OpArg &arg); + void PUNPCKHBW(X64Reg dest, const OpArg &arg); + void PUNPCKHWD(X64Reg dest, const OpArg &arg); + void PUNPCKHDQ(X64Reg dest, const OpArg &arg); + void PUNPCKHQDQ(X64Reg dest, const OpArg &arg); + void PTEST(X64Reg dest, OpArg arg); void PAND(X64Reg dest, OpArg arg); void PANDN(X64Reg dest, OpArg arg); @@ -834,6 +839,11 @@ public: void PSRAW(X64Reg reg, int shift); void PSRAD(X64Reg reg, int shift); + void PMULLW(X64Reg dest, const OpArg &arg); + void PMULHW(X64Reg dest, const OpArg &arg); + void PMULHUW(X64Reg dest, const OpArg &arg); + void PMULUDQ(X64Reg dest, const OpArg &arg); + // SSE4: data type conversions void PMOVSXBW(X64Reg dest, OpArg arg); void PMOVSXBD(X64Reg dest, OpArg arg); diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index e2b334b662..e0d65a3296 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -1075,7 +1075,6 @@ static const float MEMORY_ALIGNED16(byColor565[4]) = { 255.0f / 31.0f, 255.0f / void VertexDecoderJitCache::Jit_Color565Morph() { MOV(PTRBITS, R(tempReg1), ImmPtr(&gstate_c.morphWeights[0])); - MOV(32, R(tempReg2), Imm32(1)); MOVDQA(XMM5, M(color565Mask)); MOVAPS(XMM6, M(byColor565)); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index dce71c3d73..dcf471a173 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -250,6 +250,7 @@ + @@ -350,6 +351,8 @@ + + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 8d92e6f620..2499cfab12 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -259,6 +259,9 @@ Common + + Software + @@ -510,5 +513,11 @@ Vulkan + + Software + + + Software + \ No newline at end of file diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index e1c5e0c9d8..cc7eaa5df0 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -31,6 +31,7 @@ #include "GPU/Common/TextureDecoder.h" #include "GPU/Software/SoftGpu.h" #include "GPU/Software/Rasterizer.h" +#include "GPU/Software/Sampler.h" #if defined(_M_SSE) #include @@ -110,51 +111,6 @@ static inline Vec4 Interpolate(const float &c0, const float &c1, const fl return Interpolate(c0, c1, c2, w0.Cast(), w1.Cast(), w2.Cast(), wsum_recip); } -template -static inline int GetPixelDataOffset(unsigned int row_pitch_bytes, unsigned int u, unsigned int v) -{ - if (!gstate.isTextureSwizzled()) - return (v * (row_pitch_bytes * texel_size_bits >> 3)) + (u * texel_size_bits >> 3); - - const int tile_size_bits = 32; - const int tiles_in_block_horizontal = 4; - const int tiles_in_block_vertical = 8; - - int texels_per_tile = tile_size_bits / texel_size_bits; - int tile_u = u / texels_per_tile; - int tile_idx = (v % tiles_in_block_vertical) * (tiles_in_block_horizontal) + - // TODO: not sure if the *texel_size_bits/8 factor is correct - (v / tiles_in_block_vertical) * ((row_pitch_bytes*texel_size_bits/(tile_size_bits))*tiles_in_block_vertical) + - (tile_u % tiles_in_block_horizontal) + - (tile_u / tiles_in_block_horizontal) * (tiles_in_block_horizontal*tiles_in_block_vertical); - - return tile_idx * (tile_size_bits / 8) + ((u % texels_per_tile) * texel_size_bits) / 8; -} - -static inline u32 LookupColor(unsigned int index, unsigned int level) -{ - const bool mipmapShareClut = gstate.isClutSharedForMipmaps(); - const int clutSharingOffset = mipmapShareClut ? 0 : level * 16; - - switch (gstate.getClutPaletteFormat()) { - case GE_CMODE_16BIT_BGR5650: - return RGB565ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); - - case GE_CMODE_16BIT_ABGR5551: - return RGBA5551ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); - - case GE_CMODE_16BIT_ABGR4444: - return RGBA4444ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); - - case GE_CMODE_32BIT_ABGR8888: - return clut[index + clutSharingOffset]; - - default: - ERROR_LOG_REPORT(G3D, "Software: Unsupported palette format: %x", gstate.getClutPaletteFormat()); - return 0; - } -} - static inline u8 ClampFogDepth(float fogdepth) { if (fogdepth <= 0.0f) return 0; @@ -374,123 +330,6 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& } } -struct Nearest4 { - MEMORY_ALIGNED16(u32 v[4]); - - operator u32() const { - return v[0]; - } -}; - -template -inline static Nearest4 SampleNearest(int level, int u[N], int v[N], const u8 *srcptr, int texbufw) -{ - Nearest4 res; - if (!srcptr) { - memset(res.v, 0, sizeof(res.v)); - return res; - } - - GETextureFormat texfmt = gstate.getTextureFormat(); - - // TODO: Should probably check if textures are aligned properly... - - switch (texfmt) { - case GE_TFMT_4444: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); - res.v[i] = RGBA4444ToRGBA8888(*(const u16 *)src); - } - return res; - - case GE_TFMT_5551: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); - res.v[i] = RGBA5551ToRGBA8888(*(const u16 *)src); - } - return res; - - case GE_TFMT_5650: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); - res.v[i] = RGB565ToRGBA8888(*(const u16 *)src); - } - return res; - - case GE_TFMT_8888: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<32>(texbufw, u[i], v[i]); - res.v[i] = *(const u32 *)src; - } - return res; - - case GE_TFMT_CLUT32: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<32>(texbufw, u[i], v[i]); - u32 val = src[0] + (src[1] << 8) + (src[2] << 16) + (src[3] << 24); - res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); - } - return res; - - case GE_TFMT_CLUT16: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); - u16 val = src[0] + (src[1] << 8); - res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); - } - return res; - - case GE_TFMT_CLUT8: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<8>(texbufw, u[i], v[i]); - u8 val = *src; - res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); - } - return res; - - case GE_TFMT_CLUT4: - for (int i = 0; i < N; ++i) { - const u8 *src = srcptr + GetPixelDataOffset<4>(texbufw, u[i], v[i]); - u8 val = (u[i] & 1) ? (src[0] >> 4) : (src[0] & 0xF); - // Only CLUT4 uses separate mipmap palettes. - res.v[i] = LookupColor(gstate.transformClutIndex(val), level); - } - return res; - - case GE_TFMT_DXT1: - for (int i = 0; i < N; ++i) { - const DXT1Block *block = (const DXT1Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); - u32 data[4 * 4]; - DecodeDXT1Block(data, block, 4, 4, false); - res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; - } - return res; - - case GE_TFMT_DXT3: - for (int i = 0; i < N; ++i) { - const DXT3Block *block = (const DXT3Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); - u32 data[4 * 4]; - DecodeDXT3Block(data, block, 4, 4); - res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; - } - return res; - - case GE_TFMT_DXT5: - for (int i = 0; i < N; ++i) { - const DXT5Block *block = (const DXT5Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); - u32 data[4 * 4]; - DecodeDXT5Block(data, block, 4, 4); - res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; - } - return res; - - default: - ERROR_LOG_REPORT(G3D, "Software: Unsupported texture format: %x", texfmt); - memset(res.v, 0, sizeof(res.v)); - return res; - } -} - // NOTE: These likely aren't endian safe static inline u32 GetPixelColor(int x, int y) { @@ -1164,39 +1003,7 @@ inline void DrawSinglePixel(const DrawingCoords &p, u16 z, u8 fog, const Vec4 SampleLinear(int texlevel, int u[4], int v[4], int frac_u, int frac_v, const u8 *tptr, int bufw) { -#if defined(_M_SSE) - Nearest4 c = SampleNearest<4>(texlevel, u, v, tptr, bufw); - - const __m128i z = _mm_setzero_si128(); - - __m128i cvec = _mm_load_si128((const __m128i *)c.v); - __m128i tvec = _mm_unpacklo_epi8(cvec, z); - tvec = _mm_mullo_epi16(tvec, _mm_set1_epi16(0x100 - frac_v)); - __m128i bvec = _mm_unpackhi_epi8(cvec, z); - bvec = _mm_mullo_epi16(bvec, _mm_set1_epi16(frac_v)); - - // This multiplies the left and right sides. We shift right after, although this may round down... - __m128i rowmult = _mm_set_epi16(frac_u, frac_u, frac_u, frac_u, 0x100 - frac_u, 0x100 - frac_u, 0x100 - frac_u, 0x100 - frac_u); - __m128i tmp = _mm_mulhi_epu16(_mm_add_epi16(tvec, bvec), rowmult); - - // Now we need to add the left and right sides together. - __m128i res = _mm_add_epi16(tmp, _mm_shuffle_epi32(tmp, _MM_SHUFFLE(3, 2, 3, 2))); - return Vec4(_mm_unpacklo_epi16(res, z)); -#else - Nearest4 nearest = SampleNearest<4>(texlevel, u, v, tptr, bufw); - Vec4 texcolor_tl = Vec4::FromRGBA(nearest.v[0]); - Vec4 texcolor_tr = Vec4::FromRGBA(nearest.v[1]); - Vec4 texcolor_bl = Vec4::FromRGBA(nearest.v[2]); - Vec4 texcolor_br = Vec4::FromRGBA(nearest.v[3]); - // 0x100 causes a slight bias to tl, but without it we'd have to divide by 255 * 255. - 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); -#endif -} - -static inline void ApplyTexturing(Vec4 &prim_color, float s, float t, int texlevel, int frac_texlevel, bool bilinear, u8 *texptr[], int texbufw[]) { +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[]) { int u[8] = {0}, v[8] = {0}; // 1.23.8 fixed point int frac_u[2], frac_v[2]; @@ -1214,9 +1021,9 @@ static inline void ApplyTexturing(Vec4 &prim_color, float s, float t, int t GetTexelCoordinates(texlevel + 1, s, t, u[1], v[1]); } - texcolor0 = Vec4::FromRGBA(SampleNearest<1>(texlevel, u, v, tptr0, bufw0)); + texcolor0 = Vec4::FromRGBA(sampler.nearest(u[0], v[0], tptr0, bufw0, texlevel)); if (frac_texlevel) { - texcolor1 = Vec4::FromRGBA(SampleNearest<1>(texlevel + 1, u + 1, v + 1, tptr1, bufw1)); + texcolor1 = Vec4::FromRGBA(sampler.nearest(u[1], v[1], tptr1, bufw1, texlevel + 1)); } } else { GetTexelCoordinatesQuad(texlevel, s, t, u, v, frac_u[0], frac_v[0]); @@ -1224,9 +1031,9 @@ static inline void ApplyTexturing(Vec4 &prim_color, float s, float t, int t GetTexelCoordinatesQuad(texlevel + 1, s, t, u + 4, v + 4, frac_u[1], frac_v[1]); } - texcolor0 = SampleLinear(texlevel, u, v, frac_u[0], frac_v[0], tptr0, bufw0); + texcolor0 = Vec4::FromRGBA(sampler.linear(u, v, frac_u[0], frac_v[0], tptr0, bufw0, texlevel)); if (frac_texlevel) { - texcolor1 = SampleLinear(texlevel + 1, u + 4, v + 4, frac_u[1], frac_v[1], tptr1, bufw1); + texcolor1 = Vec4::FromRGBA(sampler.linear(u + 4, v + 4, frac_u[1], frac_v[1], tptr1, bufw1, texlevel + 1)); } } @@ -1299,7 +1106,7 @@ static inline void CalculateSamplingParams(const float ds, const float dt, const } } -static inline void ApplyTexturing(Vec4 *prim_color, const Vec4 &s, const Vec4 &t, int maxTexLevel, u8 *texptr[], int texbufw[]) { +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]; @@ -1309,7 +1116,7 @@ static inline void ApplyTexturing(Vec4 *prim_color, const Vec4 &s, c CalculateSamplingParams(ds, dt, maxTexLevel, level, levelFrac, bilinear); for (int i = 0; i < 4; ++i) { - ApplyTexturing(prim_color[i], s[i], t[i], level, levelFrac, bilinear, texptr, texbufw); + ApplyTexturing(sampler, prim_color[i], s[i], t[i], level, levelFrac, bilinear, texptr, texbufw); } } @@ -1438,6 +1245,8 @@ void DrawTriangleSlice( // This is common, and when we interpolate, we lose accuracy. const bool flatZ = v0.screenpos.z == v1.screenpos.z && v0.screenpos.z == v2.screenpos.z; + Sampler::Funcs sampler = Sampler::GetFuncs(); + for (pprime.y = minY + hy1 * 32; pprime.y < minY + hy2 * 32; pprime.y += 32, w0_base = e0.StepY(w0_base), w1_base = e1.StepY(w1_base), @@ -1495,7 +1304,7 @@ void DrawTriangleSlice( GetTextureCoordinates(v0, v1, v2, w0, w1, w2, wsum_recip, s, t); } - ApplyTexturing(prim_color, s, t, maxTexLevel, texptr, texbufw); + ApplyTexturing(sampler, prim_color, s, t, maxTexLevel, texptr, texbufw); } if (!clearMode) { @@ -1605,6 +1414,8 @@ void DrawPoint(const VertexData &v0) bool clearMode = gstate.isModeClear(); + Sampler::Funcs sampler = Sampler::GetFuncs(); + if (gstate.isTextureMapEnabled() && !clearMode) { int texbufw[8] = {0}; @@ -1642,7 +1453,7 @@ void DrawPoint(const VertexData &v0) int texLevelFrac; bool bilinear; CalculateSamplingParams(0.0f, 0.0f, maxTexLevel, texLevel, texLevelFrac, bilinear); - ApplyTexturing(prim_color, s, t, texLevel, texLevelFrac, bilinear, texptr, texbufw); + ApplyTexturing(sampler, prim_color, s, t, texLevel, texLevelFrac, bilinear, texptr, texbufw); } if (!clearMode) @@ -1708,6 +1519,8 @@ void DrawLine(const VertexData &v0, const VertexData &v1) } } + Sampler::Funcs sampler = Sampler::GetFuncs(); + float x = a.x > b.x ? a.x - 1 : a.x; float y = a.y > b.y ? a.y - 1 : a.y; float z = a.z; @@ -1772,7 +1585,7 @@ void DrawLine(const VertexData &v0, const VertexData &v1) texBilinear = true; } - ApplyTexturing(prim_color, s, t, texLevel, texLevelFrac, texBilinear, texptr, texbufw); + ApplyTexturing(sampler, prim_color, s, t, texLevel, texLevelFrac, texBilinear, texptr, texbufw); } if (!clearMode) @@ -1825,10 +1638,12 @@ bool GetCurrentTexture(GPUDebugBuffer &buffer, int level) int texbufw = GetTextureBufw(level, texaddr, texfmt); u8 *texptr = Memory::GetPointer(texaddr); + Sampler::Funcs sampler = Sampler::GetFuncs(); + u32 *row = (u32 *)buffer.GetData(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - row[x] = SampleNearest<1>(level, &x, &y, texptr, texbufw); + row[x] = sampler.nearest(x, y, texptr, texbufw, level); } row += w; } diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp new file mode 100644 index 0000000000..82ecaeb6e0 --- /dev/null +++ b/GPU/Software/Sampler.cpp @@ -0,0 +1,430 @@ +// Copyright (c) 2017- 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 +#include +#include "Common/ColorConv.h" +#include "Core/Reporting.h" +#include "GPU/Common/TextureDecoder.h" +#include "GPU/GPUState.h" +#include "GPU/Software/Sampler.h" + +#if defined(_M_SSE) +#include +#endif + +using namespace Math3D; + +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); + +std::mutex jitCacheLock; +SamplerJitCache *jitCache = nullptr; + +void Init() { + jitCache = new SamplerJitCache(); +} + +void Shutdown() { + delete jitCache; + jitCache = nullptr; +} + +bool DescribeCodePtr(const u8 *ptr, std::string &name) { + if (!jitCache->IsInSpace(ptr)) { + return false; + } + + name = jitCache->DescribeCodePtr(ptr); + return true; +} + +NearestFunc GetNearestFunc() { + SamplerID id; + jitCache->ComputeSamplerID(&id, false); + NearestFunc jitted = jitCache->GetNearest(id); + if (jitted) { + return jitted; + } + + return &SampleNearest; +} + +LinearFunc GetLinearFunc() { + SamplerID id; + jitCache->ComputeSamplerID(&id, true); + LinearFunc jitted = jitCache->GetLinear(id); + if (jitted) { + return jitted; + } + + return &SampleLinear; +} + +SamplerJitCache::SamplerJitCache() +#if PPSSPP_ARCH(ARM64) + : fp(this) +#endif +{ + // 256k should be enough. + AllocCodeSpace(1024 * 64 * 4); + + // Add some random code to "help" MSVC's buggy disassembler :( +#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64)) + using namespace Gen; + for (int i = 0; i < 100; i++) { + MOV(32, R(EAX), R(EBX)); + RET(); + } +#elif defined(ARM) + BKPT(0); + BKPT(0); +#endif +} + +void SamplerJitCache::Clear() { + ClearCodeSpace(0); + cache_.clear(); + addresses_.clear(); +} + +void SamplerJitCache::ComputeSamplerID(SamplerID *id_out, bool linear) { + SamplerID id{}; + + 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(); + if (gstate.isTextureFormatIndexed()) { + id.clutfmt = gstate.getClutPaletteFormat(); + id.hasClutMask = gstate.getClutIndexMask() != 0xFF; + id.hasClutShift = gstate.getClutIndexShift() != 0; + 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; +} + +std::string SamplerJitCache::DescribeSamplerID(const SamplerID &id) { + std::string name; + switch ((GETextureFormat)id.texfmt) { + case GE_TFMT_5650: name = "5650"; break; + case GE_TFMT_5551: name = "5551"; break; + case GE_TFMT_4444: name = "4444"; break; + case GE_TFMT_8888: name = "8888"; break; + case GE_TFMT_CLUT4: name = "CLUT4"; break; + case GE_TFMT_CLUT8: name = "CLUT8"; break; + case GE_TFMT_CLUT16: name = "CLUT16"; break; + case GE_TFMT_CLUT32: name = "CLUT32"; break; + case GE_TFMT_DXT1: name = "DXT1"; break; + case GE_TFMT_DXT3: name = "DXT3"; break; + case GE_TFMT_DXT5: name = "DXT5"; break; + } + switch ((GEPaletteFormat)id.clutfmt) { + case GE_CMODE_16BIT_BGR5650: + switch ((GETextureFormat)id.texfmt) { + case GE_TFMT_CLUT4: + case GE_TFMT_CLUT8: + case GE_TFMT_CLUT16: + case GE_TFMT_CLUT32: + name += ":C5650"; + break; + default: + // Ignore 0 clutfmt when no clut. + break; + } + break; + case GE_CMODE_16BIT_ABGR5551: name += ":C5551"; break; + case GE_CMODE_16BIT_ABGR4444: name += ":C4444"; break; + case GE_CMODE_32BIT_ABGR8888: name += ":C8888"; break; + } + if (id.swizzle) { + name += ":SWZ"; + } + if (!id.useSharedClut) { + name += ":MIP"; + } + if (id.hasInvalidPtr) { + name += ":INV"; + } + if (id.hasClutMask) { + name += ":CMASK"; + } + if (id.hasClutShift) { + name += ":CSHF"; + } + if (id.hasClutOffset) { + name += ":COFF"; + } + if (id.linear) { + name += ":LERP"; + } + return name; +} + +std::string SamplerJitCache::DescribeCodePtr(const u8 *ptr) { + ptrdiff_t dist = 0x7FFFFFFF; + SamplerID found{}; + for (const auto &it : addresses_) { + ptrdiff_t it_dist = ptr - it.second; + if (it_dist >= 0 && it_dist < dist) { + found = it.first; + dist = it_dist; + } + } + + return DescribeSamplerID(found); +} + +NearestFunc SamplerJitCache::GetNearest(const SamplerID &id) { + std::lock_guard guard(jitCacheLock); + + auto it = cache_.find(id); + if (it != cache_.end()) { + return it->second; + } + + // TODO: What should be the min size? Can we even hit this? + if (GetSpaceLeft() < 16384) { + Clear(); + } + + // TODO +#ifdef _M_X64 + addresses_[id] = GetCodePointer(); + NearestFunc func = Compile(id); + cache_[id] = func; + return func; +#else + return nullptr; +#endif +} + +LinearFunc SamplerJitCache::GetLinear(const SamplerID &id) { + std::lock_guard guard(jitCacheLock); + + auto it = cache_.find(id); + if (it != cache_.end()) { + return (LinearFunc)it->second; + } + + // TODO: What should be the min size? Can we even hit this? + if (GetSpaceLeft() < 16384) { + Clear(); + } + + // TODO +#ifdef _M_X64 + addresses_[id] = GetCodePointer(); + LinearFunc func = CompileLinear(id); + cache_[id] = (NearestFunc)func; + return func; +#else + return nullptr; +#endif +} + +template +static inline int GetPixelDataOffset(unsigned int row_pitch_pixels, unsigned int u, unsigned int v) +{ + if (!gstate.isTextureSwizzled()) + return (v * (row_pitch_pixels * texel_size_bits >> 3)) + (u * texel_size_bits >> 3); + + const int tile_size_bits = 32; + const int tiles_in_block_horizontal = 4; + const int tiles_in_block_vertical = 8; + + int texels_per_tile = tile_size_bits / texel_size_bits; + int tile_u = u / texels_per_tile; + int tile_idx = (v % tiles_in_block_vertical) * (tiles_in_block_horizontal) + + // TODO: not sure if the *texel_size_bits/8 factor is correct + (v / tiles_in_block_vertical) * ((row_pitch_pixels*texel_size_bits/(tile_size_bits))*tiles_in_block_vertical) + + (tile_u % tiles_in_block_horizontal) + + (tile_u / tiles_in_block_horizontal) * (tiles_in_block_horizontal*tiles_in_block_vertical); + + return tile_idx * (tile_size_bits / 8) + ((u % texels_per_tile) * texel_size_bits) / 8; +} + +static inline u32 LookupColor(unsigned int index, unsigned int level) +{ + const bool mipmapShareClut = gstate.isClutSharedForMipmaps(); + const int clutSharingOffset = mipmapShareClut ? 0 : level * 16; + + switch (gstate.getClutPaletteFormat()) { + case GE_CMODE_16BIT_BGR5650: + return RGB565ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); + + case GE_CMODE_16BIT_ABGR5551: + return RGBA5551ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); + + case GE_CMODE_16BIT_ABGR4444: + return RGBA4444ToRGBA8888(reinterpret_cast(clut)[index + clutSharingOffset]); + + case GE_CMODE_32BIT_ABGR8888: + return clut[index + clutSharingOffset]; + + default: + ERROR_LOG_REPORT(G3D, "Software: Unsupported palette format: %x", gstate.getClutPaletteFormat()); + return 0; + } +} + +struct Nearest4 { + MEMORY_ALIGNED16(u32 v[4]); + + operator u32() const { + return v[0]; + } +}; + +template +inline static Nearest4 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)); + return res; + } + + GETextureFormat texfmt = gstate.getTextureFormat(); + + // TODO: Should probably check if textures are aligned properly... + + switch (texfmt) { + case GE_TFMT_4444: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); + res.v[i] = RGBA4444ToRGBA8888(*(const u16 *)src); + } + return res; + + case GE_TFMT_5551: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); + res.v[i] = RGBA5551ToRGBA8888(*(const u16 *)src); + } + return res; + + case GE_TFMT_5650: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); + res.v[i] = RGB565ToRGBA8888(*(const u16 *)src); + } + return res; + + case GE_TFMT_8888: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<32>(texbufw, u[i], v[i]); + res.v[i] = *(const u32 *)src; + } + return res; + + case GE_TFMT_CLUT32: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<32>(texbufw, u[i], v[i]); + u32 val = src[0] + (src[1] << 8) + (src[2] << 16) + (src[3] << 24); + res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); + } + return res; + + case GE_TFMT_CLUT16: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<16>(texbufw, u[i], v[i]); + u16 val = src[0] + (src[1] << 8); + res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); + } + return res; + + case GE_TFMT_CLUT8: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<8>(texbufw, u[i], v[i]); + u8 val = *src; + res.v[i] = LookupColor(gstate.transformClutIndex(val), 0); + } + return res; + + case GE_TFMT_CLUT4: + for (int i = 0; i < N; ++i) { + const u8 *src = srcptr + GetPixelDataOffset<4>(texbufw, u[i], v[i]); + u8 val = (u[i] & 1) ? (src[0] >> 4) : (src[0] & 0xF); + // Only CLUT4 uses separate mipmap palettes. + res.v[i] = LookupColor(gstate.transformClutIndex(val), level); + } + return res; + + case GE_TFMT_DXT1: + for (int i = 0; i < N; ++i) { + const DXT1Block *block = (const DXT1Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); + u32 data[4 * 4]; + DecodeDXT1Block(data, block, 4, 4, false); + res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; + } + return res; + + case GE_TFMT_DXT3: + for (int i = 0; i < N; ++i) { + const DXT3Block *block = (const DXT3Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); + u32 data[4 * 4]; + DecodeDXT3Block(data, block, 4, 4); + res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; + } + return res; + + case GE_TFMT_DXT5: + for (int i = 0; i < N; ++i) { + const DXT5Block *block = (const DXT5Block *)srcptr + (v[i] / 4) * (texbufw / 4) + (u[i] / 4); + u32 data[4 * 4]; + DecodeDXT5Block(data, block, 4, 4); + res.v[i] = data[4 * (v[i] % 4) + (u[i] % 4)]; + } + return res; + + default: + ERROR_LOG_REPORT(G3D, "Software: Unsupported texture format: %x", texfmt); + memset(res.v, 0, sizeof(res.v)); + return res; + } +} + +static u32 SampleNearest(int u, int v, const u8 *tptr, int bufw, int level) { + return SampleNearest<1>(&u, &v, tptr, bufw, level); +} + +static u32 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]); + Vec4 texcolor_tr = Vec4::FromRGBA(c.v[1]); + Vec4 texcolor_bl = Vec4::FromRGBA(c.v[2]); + 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(); +} + +}; diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h new file mode 100644 index 0000000000..0e7604dc63 --- /dev/null +++ b/GPU/Software/Sampler.h @@ -0,0 +1,142 @@ +// Copyright (c) 2017- 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/. + +#pragma once + +#include "ppsspp_config.h" + +#include +#if PPSSPP_ARCH(ARM) +#include "Common/ArmEmitter.h" +#elif PPSSPP_ARCH(ARM64) +#include "Common/Arm64Emitter.h" +#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) +#include "Common/x64Emitter.h" +#elif PPSSPP_ARCH(MIPS) +#include "Common/MipsEmitter.h" +#else +#include "Common/FakeEmitter.h" +#endif +#include "GPU/Math3D.h" + +struct SamplerID { + SamplerID() : fullKey(0) { + } + + union { + u32 fullKey; + struct { + uint8_t texfmt : 4; + uint8_t clutfmt : 2; + uint8_t : 2; + bool swizzle : 1; + bool useSharedClut : 1; + bool hasClutMask : 1; + bool hasClutShift : 1; + bool hasClutOffset : 1; + bool hasInvalidPtr : 1; + bool linear : 1; + }; + }; + + bool operator == (const SamplerID &other) const { + return fullKey == other.fullKey; + } +}; + +namespace std { + +template <> +struct hash { + std::size_t operator()(const SamplerID &k) const { + return hash()(k.fullKey); + } +}; + +}; + +namespace Sampler { + +typedef u32 (*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); +LinearFunc GetLinearFunc(); + +struct Funcs { + NearestFunc nearest; + LinearFunc linear; +}; +static inline Funcs GetFuncs() { + Funcs f; + f.nearest = GetNearestFunc(); + f.linear = GetLinearFunc(); + return f; +} + +void Init(); +void Shutdown(); + +bool DescribeCodePtr(const u8 *ptr, std::string &name); + +#if PPSSPP_ARCH(ARM) +class SamplerJitCache : public ArmGen::ARMXCodeBlock { +#elif PPSSPP_ARCH(ARM64) +class SamplerJitCache : public Arm64Gen::ARM64CodeBlock { +#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) +class SamplerJitCache : public Gen::XCodeBlock { +#elif PPSSPP_ARCH(MIPS) +class SamplerJitCache : public MIPSGen::MIPSCodeBlock { +#else +class SamplerJitCache : public FakeGen::FakeXCodeBlock { +#endif +public: + SamplerJitCache(); + + void ComputeSamplerID(SamplerID *id_out, bool linear); + + // Returns a pointer to the code to run. + NearestFunc GetNearest(const SamplerID &id); + LinearFunc GetLinear(const SamplerID &id); + void Clear(); + + std::string DescribeCodePtr(const u8 *ptr); + std::string DescribeSamplerID(const SamplerID &id); + +private: + NearestFunc Compile(const SamplerID &id); + LinearFunc CompileLinear(const SamplerID &id); + + bool Jit_ReadTextureFormat(const SamplerID &id); + bool Jit_GetTexData(const SamplerID &id, int bitsPerTexel); + bool Jit_GetTexDataSwizzled(const SamplerID &id, int bitsPerTexel); + bool Jit_GetTexDataSwizzled4(); + bool Jit_Decode5650(); + bool Jit_Decode5551(); + bool Jit_Decode4444(); + bool Jit_TransformClutIndex(const SamplerID &id, int bitsPerIndex); + bool Jit_ReadClutColor(const SamplerID &id); + +#if PPSSPP_ARCH(ARM64) + Arm64Gen::ARM64FloatEmitter fp; +#endif + + std::unordered_map cache_; + std::unordered_map addresses_; +}; + +}; diff --git a/GPU/Software/SamplerX86.cpp b/GPU/Software/SamplerX86.cpp new file mode 100644 index 0000000000..38acfd72b1 --- /dev/null +++ b/GPU/Software/SamplerX86.cpp @@ -0,0 +1,666 @@ +// Copyright (c) 2017- 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(X86) || PPSSPP_ARCH(AMD64) + +#include +#include "Common/x64Emitter.h" +#include "Common/CPUDetect.h" +#include "GPU/GPUState.h" +#include "GPU/Software/Sampler.h" +#include "GPU/ge_constants.h" + +using namespace Gen; + +extern u32 clut[4096]; + +namespace Sampler { + +#ifdef _WIN32 +static const X64Reg arg1Reg = RCX; +static const X64Reg arg2Reg = RDX; +static const X64Reg arg3Reg = R8; +static const X64Reg arg4Reg = R9; +// 5 and 6 are on the stack. +#else +static const X64Reg arg1Reg = RDI; +static const X64Reg arg2Reg = RSI; +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; +static const X64Reg fpScratchReg4 = XMM4; +static const X64Reg fpScratchReg5 = XMM5; + +NearestFunc SamplerJitCache::Compile(const SamplerID &id) { + BeginWrite(); + const u8 *start = AlignCode16(); + + // Early exit on !srcPtr. + FixupBranch zeroSrc; + if (id.hasInvalidPtr) { + CMP(PTRBITS, R(srcReg), Imm8(0)); + FixupBranch nonZeroSrc = J_CC(CC_NZ); + XOR(32, R(RAX), R(RAX)); + zeroSrc = J(true); + SetJumpTarget(nonZeroSrc); + } + + if (!Jit_ReadTextureFormat(id)) { + EndWrite(); + SetCodePtr(const_cast(start)); + return nullptr; + } + + if (id.hasInvalidPtr) { + SetJumpTarget(zeroSrc); + } + + RET(); + + EndWrite(); + return (NearestFunc)start; +} + +static const float MEMORY_ALIGNED16(by256[4]) = { 1.0f / 256.0f, 1.0f / 256.0f, 1.0f / 256.0f, 1.0f / 256.0f, }; +static const float MEMORY_ALIGNED16(ones[4]) = { 1.0f, 1.0f, 1.0f, 1.0f, }; + +LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { + _assert_msg_(G3D, id.linear, "Linear should be set on sampler id"); + BeginWrite(); + + // 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)) { + EndWrite(); + SetCodePtr(const_cast(nearest)); + return nullptr; + } + + RET(); + + // 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 + // + // 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. + PUSH(arg4Reg); + PUSH(arg3Reg); + // 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; + MOV(64, R(R14), MDisp(RSP, argOffset)); + MOV(32, R(R15), MDisp(RSP, argOffset + 8)); + // level is at argOffset + 12. +#else + MOV(64, R(R14), R(arg5Reg)); + MOV(32, R(R15), R(arg6Reg)); + // level is at 24 + 48 + 8. +#endif + + // Early exit on !srcPtr. + FixupBranch zeroSrc; + if (id.hasInvalidPtr) { + CMP(PTRBITS, R(R14), Imm8(0)); + FixupBranch nonZeroSrc = J_CC(CC_NZ); + XOR(32, R(RAX), R(RAX)); + zeroSrc = J(true); + SetJumpTarget(nonZeroSrc); + } + + // At this point: + // R12=uptr, R13=vptr, stack+24=frac_u, stack+32=frac_v, R14=src, R15=bufw, stack+X=level + + auto doNearestCall = [&](int off) { + MOV(32, R(uReg), MDisp(R12, off)); + MOV(32, R(vReg), MDisp(R13, off)); + MOV(64, R(srcReg), R(R14)); + MOV(32, R(bufwReg), R(R15)); + // Leave level, we just always load from RAM. Separate CLUTs is uncommon. + + CALL(nearest); + MOV(32, MDisp(RSP, off), R(resultReg)); + }; + + doNearestCall(0); + doNearestCall(4); + doNearestCall(8); + doNearestCall(12); + + // Convert TL, TR, BL, BR to floats for easier blending. + if (!cpu_info.bSSE4_1) { + PXOR(XMM0, R(XMM0)); + } + + MOVD_xmm(fpScratchReg1, MDisp(RSP, 0)); + MOVD_xmm(fpScratchReg2, MDisp(RSP, 4)); + MOVD_xmm(fpScratchReg3, MDisp(RSP, 8)); + MOVD_xmm(fpScratchReg4, MDisp(RSP, 12)); + + if (cpu_info.bSSE4_1) { + PMOVZXBD(fpScratchReg1, R(fpScratchReg1)); + PMOVZXBD(fpScratchReg2, R(fpScratchReg2)); + PMOVZXBD(fpScratchReg3, R(fpScratchReg3)); + PMOVZXBD(fpScratchReg4, R(fpScratchReg4)); + } else { + PUNPCKLBW(fpScratchReg1, R(XMM0)); + PUNPCKLBW(fpScratchReg2, R(XMM0)); + PUNPCKLBW(fpScratchReg3, R(XMM0)); + PUNPCKLBW(fpScratchReg4, R(XMM0)); + PUNPCKLWD(fpScratchReg1, R(XMM0)); + PUNPCKLWD(fpScratchReg2, R(XMM0)); + PUNPCKLWD(fpScratchReg3, R(XMM0)); + PUNPCKLWD(fpScratchReg4, R(XMM0)); + } + CVTDQ2PS(fpScratchReg1, R(fpScratchReg1)); + CVTDQ2PS(fpScratchReg2, R(fpScratchReg2)); + CVTDQ2PS(fpScratchReg3, R(fpScratchReg3)); + CVTDQ2PS(fpScratchReg4, R(fpScratchReg4)); + + // Okay, now multiply the R sides by frac_u, and L by (256 - frac_u)... + MOVD_xmm(fpScratchReg5, MDisp(RSP, 24)); + CVTDQ2PS(fpScratchReg5, R(fpScratchReg5)); + SHUFPS(fpScratchReg5, R(fpScratchReg5), _MM_SHUFFLE(0, 0, 0, 0)); + MULPS(fpScratchReg5, M(by256)); + MOVAPS(XMM0, M(ones)); + SUBPS(XMM0, R(fpScratchReg5)); + + MULPS(fpScratchReg1, R(XMM0)); + MULPS(fpScratchReg2, R(fpScratchReg5)); + MULPS(fpScratchReg3, R(XMM0)); + MULPS(fpScratchReg4, R(fpScratchReg5)); + + // Now set top=fpScratchReg1, bottom=fpScratchReg3. + ADDPS(fpScratchReg1, R(fpScratchReg2)); + ADDPS(fpScratchReg3, R(fpScratchReg4)); + + // Next, time for frac_v. + MOVD_xmm(fpScratchReg5, MDisp(RSP, 32)); + CVTDQ2PS(fpScratchReg5, R(fpScratchReg5)); + SHUFPS(fpScratchReg5, R(fpScratchReg5), _MM_SHUFFLE(0, 0, 0, 0)); + MULPS(fpScratchReg5, M(by256)); + MOVAPS(XMM0, M(ones)); + SUBPS(XMM0, R(fpScratchReg5)); + + MULPS(fpScratchReg1, R(XMM0)); + MULPS(fpScratchReg3, R(fpScratchReg5)); + + // Still at the 255 scale, now we're interpolated. + 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); + + if (id.hasInvalidPtr) { + SetJumpTarget(zeroSrc); + } + + ADD(64, R(RSP), Imm8(24)); + POP(arg3Reg); + POP(arg4Reg); + POP(R12); + POP(R13); + POP(R14); + POP(R15); + + RET(); + + EndWrite(); + return (LinearFunc)start; +} + +bool SamplerJitCache::Jit_ReadTextureFormat(const SamplerID &id) { + GETextureFormat fmt = (GETextureFormat)id.texfmt; + bool success = true; + switch (fmt) { + case GE_TFMT_5650: + success = Jit_GetTexData(id, 16); + if (success) + success = Jit_Decode5650(); + break; + + case GE_TFMT_5551: + success = Jit_GetTexData(id, 16); + if (success) + success = Jit_Decode5551(); + break; + + case GE_TFMT_4444: + success = Jit_GetTexData(id, 16); + if (success) + success = Jit_Decode4444(); + break; + + case GE_TFMT_8888: + success = Jit_GetTexData(id, 32); + break; + + case GE_TFMT_CLUT32: + success = Jit_GetTexData(id, 32); + if (success) + success = Jit_TransformClutIndex(id, 32); + if (success) + success = Jit_ReadClutColor(id); + break; + + case GE_TFMT_CLUT16: + success = Jit_GetTexData(id, 16); + if (success) + success = Jit_TransformClutIndex(id, 16); + if (success) + success = Jit_ReadClutColor(id); + break; + + case GE_TFMT_CLUT8: + success = Jit_GetTexData(id, 8); + if (success) + success = Jit_TransformClutIndex(id, 8); + if (success) + success = Jit_ReadClutColor(id); + break; + + case GE_TFMT_CLUT4: + success = Jit_GetTexData(id, 4); + if (success) + success = Jit_TransformClutIndex(id, 4); + if (success) + success = Jit_ReadClutColor(id); + break; + + // TODO: DXT? + default: + success = false; + } + + return success; +} + +bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) { + if (id.swizzle) { + return Jit_GetTexDataSwizzled(id, bitsPerTexel); + } + + // srcReg might be EDX, so let's copy that before we multiply. + switch (bitsPerTexel) { + case 32: + case 16: + case 8: + LEA(64, tempReg1, MComplex(srcReg, uReg, bitsPerTexel / 8, 0)); + break; + + case 4: { + XOR(32, R(tempReg2), R(tempReg2)); + 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)); + SetJumpTarget(skip); + LEA(64, tempReg1, MRegSum(srcReg, uReg)); + break; + } + + default: + return false; + } + + MOV(32, R(EAX), R(vReg)); + MUL(32, R(bufwReg)); + + switch (bitsPerTexel) { + case 32: + case 16: + case 8: + MOVZX(32, bitsPerTexel, resultReg, MComplex(tempReg1, RAX, bitsPerTexel / 8, 0)); + break; + + case 4: { + SHR(32, R(RAX), Imm8(1)); + MOV(8, R(resultReg), MRegSum(tempReg1, RAX)); + // RCX is now free. + MOV(8, R(RCX), R(tempReg2)); + SHR(8, R(resultReg), R(RCX)); + // Zero out any bits not shifted off. + AND(32, R(resultReg), Imm8(0x0F)); + break; + } + + default: + return false; + } + + return true; +} + +bool SamplerJitCache::Jit_GetTexDataSwizzled4() { + // Get the horizontal tile pos into tempReg1. + LEA(32, tempReg1, MScaled(uReg, SCALE_4, 0)); + // Note: imm8 sign extends negative. + AND(32, R(tempReg1), 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 srcReg, since we'll need it at some point. + ADD(64, R(tempReg1), R(srcReg)); + + // Now find the vertical tile pos, and add to tempReg1. + SHR(32, R(vReg), Imm8(3)); + LEA(32, EAX, MScaled(bufwReg, SCALE_4, 0)); + MUL(32, R(vReg)); + ADD(64, R(tempReg1), R(EAX)); + + // 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)); + 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)); + + return true; +} + +bool SamplerJitCache::Jit_GetTexDataSwizzled(const SamplerID &id, int bitsPerTexel) { + if (bitsPerTexel == 4) { + // Specialized implementation. + return Jit_GetTexDataSwizzled4(); + } + + LEA(32, tempReg1, MScaled(vReg, SCALE_4, 0)); + AND(32, R(tempReg1), Imm8(31)); + AND(32, R(vReg), Imm8(~7)); + + MOV(32, R(tempReg2), R(uReg)); + MOV(32, R(resultReg), R(uReg)); + switch (bitsPerTexel) { + case 32: + SHR(32, R(resultReg), Imm8(2)); + break; + case 16: + SHR(32, R(vReg), Imm8(1)); + SHR(32, R(tempReg2), 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(resultReg), Imm8(4)); + break; + default: + return false; + } + AND(32, R(tempReg2), Imm8(3)); + SHL(32, R(resultReg), Imm8(5)); + ADD(32, R(tempReg1), R(tempReg2)); + ADD(32, R(tempReg1), R(resultReg)); + + // We may clobber srcReg in the MUL, so let's grab it now. + LEA(64, tempReg1, MComplex(srcReg, tempReg1, SCALE_4, 0)); + + LEA(32, EAX, MScaled(bufwReg, SCALE_4, 0)); + MUL(32, R(vReg)); + + switch (bitsPerTexel) { + case 32: + MOV(bitsPerTexel, R(resultReg), MRegSum(tempReg1, EAX)); + 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)); + break; + case 8: + AND(32, R(uReg), Imm8(3)); + ADD(32, R(EAX), R(uReg)); + MOVZX(32, bitsPerTexel, resultReg, MRegSum(tempReg1, EAX)); + break; + default: + return false; + } + + return true; +} + +bool SamplerJitCache::Jit_Decode5650() { + MOV(32, R(tempReg2), R(resultReg)); + AND(32, R(tempReg2), 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)); + + // 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)); + + // Now's as good a time to put in A as any. + OR(32, R(tempReg2), 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)); + // 2 to account for resultReg being preshifted, 4 for expansion. + SHR(32, R(tempReg1), Imm8(2 + 4)); + OR(32, R(resultReg), R(tempReg1)); + AND(32, R(resultReg), Imm32(0x0000FF00)); + OR(32, R(resultReg), R(tempReg2));; + + 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)); + + MOV(32, R(tempReg1), R(resultReg)); + AND(32, R(tempReg1), Imm32(0x00007C00)); + SHL(32, R(tempReg1), Imm8(6)); + OR(32, R(tempReg2), R(tempReg1)); + + // 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)); + // Chop off the bits that were shifted out. + AND(32, R(tempReg1), Imm32(0x00070707)); + OR(32, R(tempReg2), R(tempReg1)); + + // For A, we shift it to a single bit, and then subtract and XOR. + // That's probably the simplest way to expand it... + SHR(32, R(resultReg), Imm8(15)); + // If it was 0, it's now -1, otherwise it's 0. Easy. + SUB(32, R(resultReg), Imm8(1)); + XOR(32, R(resultReg), Imm32(0xFF000000)); + AND(32, R(resultReg), Imm32(0xFF000000)); + OR(32, R(resultReg), R(tempReg2)); + + return true; +} + +static const u32 MEMORY_ALIGNED16(color4444mask[4]) = { 0xf00ff00f, 0xf00ff00f, 0xf00ff00f, 0xf00ff00f, }; + +bool SamplerJitCache::Jit_Decode4444() { + MOVD_xmm(fpScratchReg1, R(resultReg)); + PUNPCKLBW(fpScratchReg1, R(fpScratchReg1)); + PAND(fpScratchReg1, M(color4444mask)); + 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); + return true; +} + +bool SamplerJitCache::Jit_TransformClutIndex(const SamplerID &id, int bitsPerIndex) { + GEPaletteFormat fmt = (GEPaletteFormat)id.clutfmt; + if (!id.hasClutShift && !id.hasClutMask && !id.hasClutOffset) { + // This is simple - just mask if necessary. + if (bitsPerIndex > 8) { + AND(32, R(resultReg), Imm32(0x000000FF)); + } + return true; + } + + MOV(PTRBITS, R(tempReg1), ImmPtr(&gstate.clutformat)); + MOV(32, R(tempReg1), MatR(tempReg1)); + + // Shift = (clutformat >> 2) & 0x1F + if (id.hasClutShift) { + MOV(32, R(RCX), R(tempReg1)); + SHR(32, R(RCX), Imm8(2)); + AND(32, R(RCX), Imm8(0x1F)); + SHR(32, R(resultReg), R(RCX)); + } + + // 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)); + } + + // We need to wrap any entries beyond the first 1024 bytes. + u32 offsetMask = fmt == GE_CMODE_32BIT_ABGR8888 ? 0x00FF : 0x01FF; + + // We must mask to 0xFF before ORing 0x100 in 16 bit CMODEs. + // But skip if we'll mask 0xFF after offset anyway. + if (bitsPerIndex > 8 && (!id.hasClutOffset || offsetMask != 0x00FF)) { + AND(32, R(resultReg), Imm32(0x000000FF)); + } + + // 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)); + AND(32, R(resultReg), Imm32(offsetMask)); + } + return true; +} + +bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) { + 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 + 12 + 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 + // We need to multiply by 16 and add, LEA allows us to copy too. + LEA(32, tempReg2, MScaled(levelReg, SCALE_4, 0)); +#endif + } + + // Second step of the multiply by 16 (since we only multiplied by 4 before.) + LEA(64, resultReg, MComplex(resultReg, tempReg2, SCALE_4, 0)); + } + + MOV(PTRBITS, R(tempReg1), ImmPtr(clut)); + + switch ((GEPaletteFormat)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: + return false; + } +} + +}; + +#endif diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 081d20f9fb..94ec5b59a8 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -31,9 +31,10 @@ #include "profiler/profiler.h" #include "thin3d/thin3d.h" +#include "GPU/Software/Rasterizer.h" +#include "GPU/Software/Sampler.h" #include "GPU/Software/SoftGpu.h" #include "GPU/Software/TransformUnit.h" -#include "GPU/Software/Rasterizer.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferCommon.h" @@ -99,6 +100,7 @@ SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw) displayStride_ = 512; displayFormat_ = GE_FORMAT_8888; + Sampler::Init(); drawEngine_ = new SoftwareDrawEngine(); drawEngineCommon_ = drawEngine_; } @@ -127,6 +129,8 @@ SoftGPU::~SoftGPU() { samplerNearest = nullptr; samplerLinear->Release(); samplerLinear = nullptr; + + Sampler::Shutdown(); } void SoftGPU::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { @@ -991,3 +995,12 @@ bool SoftGPU::GetCurrentSimpleVertices(int count, std::vector &v { return drawEngine_->transformUnit.GetCurrentSimpleVertices(count, vertices, indices); } + +bool SoftGPU::DescribeCodePtr(const u8 *ptr, std::string &name) { + std::string subname; + if (Sampler::DescribeCodePtr(ptr, subname)) { + name = "SamplerJit:" + subname; + return true; + } + return false; +} diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 0e2a6922b7..3b0dd6b7b5 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -91,6 +91,8 @@ public: bool GetCurrentClut(GPUDebugBuffer &buffer) override; bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) override; + bool DescribeCodePtr(const u8 *ptr, std::string &name) override; + protected: void FastRunLoop(DisplayList &list) override; void ProcessEvent(GPUEvent ev) override; diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 0200d09d61..7e797889b9 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -344,6 +344,7 @@ + @@ -397,6 +398,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9f7fe881d9..7b3dd28a06 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -28,7 +28,8 @@ ARCH_FILES := \ $(SRC)/Core/MIPS/x86/JitSafeMem.cpp \ $(SRC)/Core/MIPS/x86/RegCache.cpp \ $(SRC)/Core/MIPS/x86/RegCacheFPU.cpp \ - $(SRC)/GPU/Common/VertexDecoderX86.cpp + $(SRC)/GPU/Common/VertexDecoderX86.cpp \ + $(SRC)/GPU/Software/SamplerX86.cpp endif ifeq ($(TARGET_ARCH_ABI),x86_64) @@ -48,7 +49,8 @@ ARCH_FILES := \ $(SRC)/Core/MIPS/x86/JitSafeMem.cpp \ $(SRC)/Core/MIPS/x86/RegCache.cpp \ $(SRC)/Core/MIPS/x86/RegCacheFPU.cpp \ - $(SRC)/GPU/Common/VertexDecoderX86.cpp + $(SRC)/GPU/Common/VertexDecoderX86.cpp \ + $(SRC)/GPU/Software/SamplerX86.cpp endif ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a) @@ -244,6 +246,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Software/Clipper.cpp \ $(SRC)/GPU/Software/Lighting.cpp \ $(SRC)/GPU/Software/Rasterizer.cpp.arm \ + $(SRC)/GPU/Software/Sampler.cpp \ $(SRC)/GPU/Software/SoftGpu.cpp \ $(SRC)/GPU/Software/TransformUnit.cpp \ $(SRC)/Core/ELF/ElfReader.cpp \