Merge pull request #15275 from unknownbrackets/samplerjit-avx2

Use AVX2 gather for samplerjit
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2022-01-03 09:27:44 +01:00
commit b2bb0be05d
7 files changed
+549 -246

No files matched your search

+19 -13
View File
@@ -1701,16 +1701,15 @@ void XEmitter::PSRLQ(X64Reg reg, int shift)
Write8(shift);
}
void XEmitter::PSRLQ(X64Reg reg, OpArg arg)
{
WriteSSEOp(0x66, 0xd3, reg, arg);
}
void XEmitter::PSRLDQ(X64Reg reg, int shift) {
WriteSSEOp(0x66, 0x73, (X64Reg)3, R(reg));
Write8(shift);
}
void XEmitter::PSRLW(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xD1, reg, arg); }
void XEmitter::PSRLD(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xD2, reg, arg); }
void XEmitter::PSRLQ(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xD3, reg, arg); }
void XEmitter::PSLLW(X64Reg reg, int shift)
{
WriteSSEOp(0x66, 0x71, (X64Reg)6, R(reg));
@@ -1734,6 +1733,10 @@ void XEmitter::PSLLDQ(X64Reg reg, int shift) {
Write8(shift);
}
void XEmitter::PSLLW(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xF1, reg, arg); }
void XEmitter::PSLLD(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xF2, reg, arg); }
void XEmitter::PSLLQ(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xF3, reg, arg); }
void XEmitter::PSRAW(X64Reg reg, int shift)
{
WriteSSEOp(0x66, 0x71, (X64Reg)4, R(reg));
@@ -1746,6 +1749,9 @@ void XEmitter::PSRAD(X64Reg reg, int shift)
Write8(shift);
}
void XEmitter::PSRAW(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xE1, reg, arg); }
void XEmitter::PSRAD(X64Reg reg, OpArg arg) { WriteSSEOp(0x66, 0xE2, reg, arg); }
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);}
@@ -2240,20 +2246,20 @@ void XEmitter::VGATHERQPD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VGATHER cannot have overlapped registers");
WriteAVX2Op(bits, 0x66, 0x3893, regOp1, regOp2, arg);
}
void XEmitter::VGATHERDD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VGATHER cannot have overlapped registers");
void XEmitter::VPGATHERDD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VPGATHER cannot have overlapped registers");
WriteAVX2Op(bits, 0x66, 0x3890, regOp1, regOp2, arg);
}
void XEmitter::VGATHERQD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VGATHER cannot have overlapped registers");
void XEmitter::VPGATHERQD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VPGATHER cannot have overlapped registers");
WriteAVX2Op(bits, 0x66, 0x3891, regOp1, regOp2, arg);
}
void XEmitter::VGATHERDQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VGATHER cannot have overlapped registers");
void XEmitter::VPGATHERDQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VPGATHER cannot have overlapped registers");
WriteAVX2Op(bits, 0x66, 0x3890, regOp1, regOp2, arg, 0, 1);
}
void XEmitter::VGATHERQQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VGATHER cannot have overlapped registers");
void XEmitter::VPGATHERQQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2) {
_assert_msg_(regOp1 != regOp2 && !arg.IsIndexedReg(regOp1) && !arg.IsIndexedReg(regOp2), "VPGATHER cannot have overlapped registers");
WriteAVX2Op(bits, 0x66, 0x3891, regOp1, regOp2, arg, 0, 1);
}
+20 -5
View File
@@ -847,16 +847,31 @@ public:
void PSRLW(X64Reg reg, int shift);
void PSRLD(X64Reg reg, int shift);
void PSRLQ(X64Reg reg, int shift);
void PSRLQ(X64Reg reg, OpArg arg);
void PSRLDQ(X64Reg reg, int shift);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSRLW(X64Reg reg, OpArg arg);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSRLD(X64Reg reg, OpArg arg);
// Note: both values shifted by lowest 64-bit in XMM arg.
void PSRLQ(X64Reg reg, OpArg arg);
void PSLLW(X64Reg reg, int shift);
void PSLLD(X64Reg reg, int shift);
void PSLLQ(X64Reg reg, int shift);
void PSLLDQ(X64Reg reg, int shift);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSLLW(X64Reg reg, OpArg arg);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSLLD(X64Reg reg, OpArg arg);
// Note: both values shifted by lowest 64-bit in XMM arg.
void PSLLQ(X64Reg reg, OpArg arg);
void PSRAW(X64Reg reg, int shift);
void PSRAD(X64Reg reg, int shift);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSRAW(X64Reg reg, OpArg arg);
// Note: all values shifted by lowest 64-bit in XMM arg.
void PSRAD(X64Reg reg, OpArg arg);
void PMULLW(X64Reg dest, const OpArg &arg);
void PMULHW(X64Reg dest, const OpArg &arg);
@@ -1239,10 +1254,10 @@ public:
void VGATHERDPD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERQPS(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERQPD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERDD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERQD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERDQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VGATHERQQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VPGATHERDD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VPGATHERQD(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VPGATHERDQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VPGATHERQQ(int bits, X64Reg regOp1, OpArg arg, X64Reg regOp2);
void VPSLLVD(int bits, X64Reg regOp1, X64Reg regOp2, OpArg arg);
void VPSLLVQ(int bits, X64Reg regOp1, X64Reg regOp2, OpArg arg);
+20 -11
View File
@@ -17,8 +17,10 @@
#include "Common/Data/Convert/ColorConv.h"
#include "Common/StringUtils.h"
#include "GPU/Software/FuncId.h"
#include "Core/MemMap.h"
#include "GPU/Common/TextureDecoder.h"
#include "GPU/GPUState.h"
#include "GPU/Software/FuncId.h"
static_assert(sizeof(SamplerID) == sizeof(SamplerID::fullKey), "Bad sampler ID size");
#ifdef SOFTPIXEL_USE_CACHE
@@ -361,18 +363,24 @@ void ComputeSamplerID(SamplerID *id_out) {
SamplerID id{};
id.useStandardBufw = true;
id.hasStandardMips = true;
id.overReadSafe = true;
int maxLevel = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() : 0;
int lastWidth = -1;
for (int i = 0; i <= maxLevel; ++i) {
if (gstate.getTextureAddress(i) == 0)
uint32_t addr = gstate.getTextureAddress(i);
if (!Memory::IsValidAddress(addr))
id.hasInvalidPtr = true;
int bufw = GetTextureBufw(i, addr, gstate.getTextureFormat());
int bitspp = textureBitsPerPixel[gstate.getTextureFormat()];
// We use a 16 byte minimum for all small bufws, so allow those as standard.
int w = gstate.getTextureWidth(i);
if (w != (gstate.texbufwidth[i] & 0x00001FFF))
if (w != bufw && w * bitspp > 128)
id.useStandardBufw = false;
if (lastWidth != -1 && lastWidth != w * 2)
id.hasStandardMips = false;
lastWidth = w;
int h = gstate.getTextureHeight(i);
int bytes = h * (bufw * bitspp) / 8;
if (bitspp < 32 && !Memory::IsValidAddress(addr + bytes + (32 - bitspp) / 8))
id.overReadSafe = false;
}
id.hasAnyMips = maxLevel != 0;
@@ -458,9 +466,10 @@ std::string DescribeSamplerID(const SamplerID &id) {
if (!id.useStandardBufw) {
name += ":BUFW";
}
if (!id.hasStandardMips) {
name += ":XMIP";
} else if (id.hasAnyMips) {
if (!id.overReadSafe) {
name += ":XRD";
}
if (id.hasAnyMips) {
name += ":MIP";
}
if (id.linear) {
+1 -1
View File
@@ -176,8 +176,8 @@ struct SamplerID {
uint8_t texFunc : 3;
bool useTextureAlpha : 1;
bool useColorDoubling : 1;
bool hasStandardMips : 1;
bool hasAnyMips : 1;
bool overReadSafe : 1;
bool fetch : 1;
};
};
+1
View File
@@ -103,6 +103,7 @@ struct RegCache {
VEC_RESULT1 = 0x0002,
VEC_U1 = 0x0003,
VEC_V1 = 0x0004,
VEC_INDEX = 0x0005,
GEN_SRC_ALPHA = 0x0100,
GEN_GSTATE = 0x0101,
+4
View File
@@ -101,6 +101,10 @@ private:
bool Jit_PrepareDataOffsets(const SamplerID &id, Rasterizer::RegCache::Reg uReg, Rasterizer::RegCache::Reg vReg, bool level1);
bool Jit_PrepareDataDirectOffsets(const SamplerID &id, Rasterizer::RegCache::Reg uReg, Rasterizer::RegCache::Reg vReg, bool level1, int bitsPerTexel);
bool Jit_PrepareDataSwizzledOffsets(const SamplerID &id, Rasterizer::RegCache::Reg uReg, Rasterizer::RegCache::Reg vReg, bool level1, int bitsPerTexel);
bool Jit_FetchQuad(const SamplerID &id, bool level1);
bool Jit_GetDataQuad(const SamplerID &id, bool level1, int bitsPerTexel);
bool Jit_TransformClutIndexQuad(const SamplerID &id, int bitsPerIndex);
bool Jit_ReadClutQuad(const SamplerID &id, bool level1);
bool Jit_BlendQuad(const SamplerID &id, bool level1);
bool Jit_DecodeQuad(const SamplerID &id, bool level1);
bool Jit_Decode5650Quad(const SamplerID &id, Rasterizer::RegCache::Reg quadReg);
+484 -216
View File
@@ -411,77 +411,71 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) {
BeginWrite();
Describe("Init");
// Set the stackArgPos_ so we can use it in the nearest part too.
#if PPSSPP_PLATFORM(WINDOWS)
// RET + shadow space + 8 byte space for color arg (the Win32 ABI is kinda ugly.)
stackArgPos_ = 8 + 32 + 8;
// Plus 32 for R12-R15.
stackArgPos_ += 32;
// Plus XMM6-XMM9 and 8 to align.
stackArgPos_ += 16 * 4 + 8;
#else
stackArgPos_ = 32;
#endif
regCache_.SetupABI({
RegCache::GEN_ARG_U,
RegCache::GEN_ARG_V,
RegCache::GEN_ARG_TEXPTR,
RegCache::GEN_ARG_BUFW,
RegCache::GEN_ARG_LEVEL,
// Avoid clobber.
RegCache::GEN_ARG_LEVELFRAC,
});
regCache_.ChangeReg(RAX, RegCache::GEN_RESULT);
auto lockReg = [&](X64Reg r, RegCache::Purpose p) {
regCache_.ChangeReg(r, p);
regCache_.ForceRetain(p);
};
lockReg(XMM0, RegCache::VEC_ARG_U);
lockReg(XMM1, RegCache::VEC_ARG_V);
lockReg(XMM5, RegCache::VEC_RESULT);
#if !PPSSPP_PLATFORM(WINDOWS)
if (id.hasAnyMips) {
lockReg(XMM6, RegCache::VEC_U1);
lockReg(XMM7, RegCache::VEC_V1);
lockReg(XMM8, RegCache::VEC_RESULT1);
}
lockReg(XMM9, RegCache::VEC_ARG_COLOR);
#endif
// We don't use stackArgPos_ here, this is just for DXT.
stackArgPos_ = -1;
// Let's drop some helpful constants here.
WriteConstantPool(id);
// We'll first write the nearest sampler, which we will CALL.
// This may differ slightly based on the "linear" flag.
const u8 *nearest = AlignCode16();
const u8 *nearest = nullptr;
if (id.TexFmt() >= GE_TFMT_DXT1) {
regCache_.SetupABI({
RegCache::GEN_ARG_U,
RegCache::GEN_ARG_V,
RegCache::GEN_ARG_TEXPTR,
RegCache::GEN_ARG_BUFW,
RegCache::GEN_ARG_LEVEL,
// Avoid clobber.
RegCache::GEN_ARG_LEVELFRAC,
});
regCache_.ChangeReg(RAX, RegCache::GEN_RESULT);
auto lockReg = [&](X64Reg r, RegCache::Purpose p) {
regCache_.ChangeReg(r, p);
regCache_.ForceRetain(p);
};
lockReg(XMM0, RegCache::VEC_ARG_U);
lockReg(XMM1, RegCache::VEC_ARG_V);
lockReg(XMM5, RegCache::VEC_RESULT);
#if !PPSSPP_PLATFORM(WINDOWS)
if (id.hasAnyMips) {
lockReg(XMM6, RegCache::VEC_U1);
lockReg(XMM7, RegCache::VEC_V1);
lockReg(XMM8, RegCache::VEC_RESULT1);
}
lockReg(XMM9, RegCache::VEC_ARG_COLOR);
#endif
if (!Jit_ReadTextureFormat(id)) {
regCache_.Reset(false);
EndWrite();
ResetCodePtr(GetOffset(nearest));
return nullptr;
// 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;
}
Describe("Init");
RET();
regCache_.ForceRelease(RegCache::VEC_ARG_U);
regCache_.ForceRelease(RegCache::VEC_ARG_V);
regCache_.ForceRelease(RegCache::VEC_RESULT);
auto unlockOptReg = [&](RegCache::Purpose p) {
if (regCache_.Has(p))
regCache_.ForceRelease(p);
};
unlockOptReg(RegCache::GEN_ARG_LEVEL);
unlockOptReg(RegCache::GEN_ARG_LEVELFRAC);
unlockOptReg(RegCache::VEC_U1);
unlockOptReg(RegCache::VEC_V1);
unlockOptReg(RegCache::VEC_RESULT1);
unlockOptReg(RegCache::VEC_ARG_COLOR);
regCache_.Reset(true);
}
Describe("Init");
RET();
regCache_.ForceRelease(RegCache::VEC_ARG_U);
regCache_.ForceRelease(RegCache::VEC_ARG_V);
regCache_.ForceRelease(RegCache::VEC_RESULT);
auto unlockOptReg = [&](RegCache::Purpose p) {
if (regCache_.Has(p))
regCache_.ForceRelease(p);
};
unlockOptReg(RegCache::GEN_ARG_LEVEL);
unlockOptReg(RegCache::GEN_ARG_LEVELFRAC);
unlockOptReg(RegCache::VEC_U1);
unlockOptReg(RegCache::VEC_V1);
unlockOptReg(RegCache::VEC_RESULT1);
unlockOptReg(RegCache::VEC_ARG_COLOR);
regCache_.Reset(true);
// Now the actual linear func, which is exposed externally.
const u8 *start = AlignCode16();
Describe("Init");
@@ -549,13 +543,15 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) {
regCache_.ForceRelease(RegCache::GEN_ARG_LEVELFRAC);
}
// Save prim color for later in a different XMM too.
X64Reg primColorReg = regCache_.Find(RegCache::VEC_ARG_COLOR);
MOVDQA(XMM9, R(primColorReg));
regCache_.Unlock(primColorReg, RegCache::VEC_ARG_COLOR);
regCache_.ForceRelease(RegCache::VEC_ARG_COLOR);
regCache_.ChangeReg(XMM9, RegCache::VEC_ARG_COLOR);
regCache_.ForceRetain(RegCache::VEC_ARG_COLOR);
// Save prim color for later in a different XMM too if we're using the nearest helper.
if (nearest != nullptr) {
X64Reg primColorReg = regCache_.Find(RegCache::VEC_ARG_COLOR);
MOVDQA(XMM9, R(primColorReg));
regCache_.Unlock(primColorReg, RegCache::VEC_ARG_COLOR);
regCache_.ForceRelease(RegCache::VEC_ARG_COLOR);
regCache_.ChangeReg(XMM9, RegCache::VEC_ARG_COLOR);
regCache_.ForceRetain(RegCache::VEC_ARG_COLOR);
}
// We also want to save src and bufw for later. Might be in a reg already.
if (regCache_.Has(RegCache::GEN_ARG_TEXPTR_PTR)) {
@@ -686,11 +682,15 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) {
regCache_.Unlock(vecResultReg, level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
};
Describe("Calls");
doNearestCall(0, false);
doNearestCall(4, false);
doNearestCall(8, false);
doNearestCall(12, false);
if (nearest != nullptr) {
Describe("Calls");
doNearestCall(0, false);
doNearestCall(4, false);
doNearestCall(8, false);
doNearestCall(12, false);
} else {
success = success && Jit_FetchQuad(id, false);
}
if (id.hasAnyMips) {
Describe("MipsCalls");
@@ -713,10 +713,15 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) {
ADD(32, MDisp(RSP, stackArgPos_ + 16), Imm8(1));
}
doNearestCall(0, true);
doNearestCall(4, true);
doNearestCall(8, true);
doNearestCall(12, true);
if (nearest != nullptr) {
Describe("MipsCalls");
doNearestCall(0, true);
doNearestCall(4, true);
doNearestCall(8, true);
doNearestCall(12, true);
} else {
success = success && Jit_FetchQuad(id, true);
}
SetJumpTarget(skip);
}
@@ -931,6 +936,368 @@ RegCache::Reg SamplerJitCache::GetGState() {
return regCache_.Find(RegCache::GEN_GSTATE);
}
bool SamplerJitCache::Jit_FetchQuad(const SamplerID &id, bool level1) {
bool success = true;
switch (id.TexFmt()) {
case GE_TFMT_5650:
case GE_TFMT_5551:
case GE_TFMT_4444:
success = Jit_GetDataQuad(id, level1, 16);
// Mask away the high bits, if loaded via AVX2.
if (cpu_info.bAVX2) {
X64Reg destReg = regCache_.Find(level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
PSLLD(destReg, 16);
PSRLD(destReg, 16);
regCache_.Unlock(destReg, level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
}
break;
case GE_TFMT_8888:
success = Jit_GetDataQuad(id, level1, 32);
break;
case GE_TFMT_CLUT32:
success = Jit_GetDataQuad(id, level1, 32);
if (success)
success = Jit_TransformClutIndexQuad(id, 32);
if (success)
success = Jit_ReadClutQuad(id, level1);
break;
case GE_TFMT_CLUT16:
success = Jit_GetDataQuad(id, level1, 16);
if (success)
success = Jit_TransformClutIndexQuad(id, 16);
if (success)
success = Jit_ReadClutQuad(id, level1);
break;
case GE_TFMT_CLUT8:
success = Jit_GetDataQuad(id, level1, 8);
if (success)
success = Jit_TransformClutIndexQuad(id, 8);
if (success)
success = Jit_ReadClutQuad(id, level1);
break;
case GE_TFMT_CLUT4:
success = Jit_GetDataQuad(id, level1, 4);
if (success)
success = Jit_TransformClutIndexQuad(id, 4);
if (success)
success = Jit_ReadClutQuad(id, level1);
break;
case GE_TFMT_DXT1:
case GE_TFMT_DXT3:
case GE_TFMT_DXT5:
// No SIMD version currently, should use nearest helper path.
success = false;
break;
default:
success = false;
}
return success;
}
bool SamplerJitCache::Jit_GetDataQuad(const SamplerID &id, bool level1, int bitsPerTexel) {
Describe("DataQuad");
bool success = true;
X64Reg baseReg = regCache_.Alloc(RegCache::GEN_ARG_TEXPTR);
X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR_PTR);
MOV(64, R(baseReg), MDisp(srcReg, level1 ? 8 : 0));
regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR_PTR);
X64Reg destReg = INVALID_REG;
if (id.TexFmt() >= GE_TFMT_CLUT4 && id.TexFmt() <= GE_TFMT_CLUT32)
destReg = regCache_.Alloc(RegCache::VEC_INDEX);
else
destReg = regCache_.Find(level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
X64Reg byteOffsetReg = regCache_.Find(level1 ? RegCache::VEC_V1 : RegCache::VEC_ARG_V);
if (cpu_info.bAVX2 && id.overReadSafe) {
// We have to set a mask for which values to load. Load all 4.
// Note this is overwritten with zeroes by the gather instruction.
X64Reg maskReg = regCache_.Alloc(RegCache::VEC_TEMP0);
PCMPEQD(maskReg, R(maskReg));
VPGATHERDD(128, destReg, MComplex(baseReg, byteOffsetReg, SCALE_1, 0), maskReg);
regCache_.Release(maskReg, RegCache::VEC_TEMP0);
} else {
if (bitsPerTexel != 32)
PXOR(destReg, R(destReg));
// Grab each value separately... try to use the right memory access size.
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
if (cpu_info.bSSE4_1) {
for (int i = 0; i < 4; ++i) {
PEXTRD(R(temp2Reg), byteOffsetReg, i);
if (bitsPerTexel <= 8)
PINSRB(destReg, MComplex(baseReg, temp2Reg, SCALE_1, 0), i * 4);
else if (bitsPerTexel == 16)
PINSRW(destReg, MComplex(baseReg, temp2Reg, SCALE_1, 0), i * 2);
else if (bitsPerTexel == 32)
PINSRD(destReg, MComplex(baseReg, temp2Reg, SCALE_1, 0), i);
}
} else {
for (int i = 0; i < 4; ++i) {
MOVD_xmm(R(temp2Reg), byteOffsetReg);
if (i != 3)
PSRLDQ(byteOffsetReg, 4);
if (bitsPerTexel <= 8) {
MOVZX(32, 8, temp2Reg, MComplex(baseReg, temp2Reg, SCALE_2, 0));
PINSRW(destReg, R(temp2Reg), i * 2);
} else if (bitsPerTexel == 16) {
PINSRW(destReg, MComplex(baseReg, temp2Reg, SCALE_2, 0), i * 2);
} else if (bitsPerTexel == 32) {
if (i == 0) {
MOVD_xmm(destReg, MComplex(baseReg, temp2Reg, SCALE_2, 0));
} else {
// Maybe a temporary would be better, but this path should be rare.
PINSRW(destReg, MComplex(baseReg, temp2Reg, SCALE_2, 0), i * 2);
PINSRW(destReg, MComplex(baseReg, temp2Reg, SCALE_2, 2), i * 2 + 1);
}
}
}
}
regCache_.Release(temp2Reg, RegCache::GEN_TEMP2);
}
regCache_.Unlock(byteOffsetReg, level1 ? RegCache::VEC_V1 : RegCache::VEC_ARG_V);
regCache_.Release(baseReg, RegCache::GEN_ARG_TEXPTR);
if (bitsPerTexel == 4) {
// Take only lowest bit, multiply by 4 with shifting.
X64Reg uReg = regCache_.Find(level1 ? RegCache::VEC_U1 : RegCache::VEC_ARG_U);
// Next, shift away based on the odd U bits.
if (cpu_info.bAVX2) {
// This is really convenient with AVX. Just make the bit into a shift amount.
PSLLD(uReg, 31);
PSRLD(uReg, 29);
VPSRLVD(128, destReg, destReg, R(uReg));
} else {
// This creates a mask - FFFFFFFF to shift, zero otherwise.
PSLLD(uReg, 31);
PSRAD(uReg, 31);
X64Reg unshiftedReg = regCache_.Alloc(RegCache::VEC_TEMP0);
MOVDQA(unshiftedReg, R(destReg));
PSRLD(destReg, 4);
// Mask destReg (shifted) and reverse uReg to unshifted masked.
PAND(destReg, R(uReg));
PANDN(uReg, R(unshiftedReg));
// Now combine.
POR(destReg, R(uReg));
regCache_.Release(unshiftedReg, RegCache::VEC_TEMP0);
}
regCache_.Unlock(uReg, level1 ? RegCache::VEC_U1 : RegCache::VEC_ARG_U);
}
if (id.TexFmt() >= GE_TFMT_CLUT4 && id.TexFmt() <= GE_TFMT_CLUT32)
regCache_.Unlock(destReg, RegCache::VEC_INDEX);
else
regCache_.Unlock(destReg, level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
return success;
}
bool SamplerJitCache::Jit_TransformClutIndexQuad(const SamplerID &id, int bitsPerIndex) {
Describe("TrCLUTQuad");
GEPaletteFormat fmt = id.ClutFmt();
if (!id.hasClutShift && !id.hasClutMask && !id.hasClutOffset) {
// This is simple - just mask.
X64Reg indexReg = regCache_.Find(RegCache::VEC_INDEX);
// Mask to 8 bits for CLUT8/16/32, 4 bits for CLUT4.
PSLLD(indexReg, bitsPerIndex >= 8 ? 24 : 28);
PSRLD(indexReg, bitsPerIndex >= 8 ? 24 : 28);
regCache_.Unlock(indexReg, RegCache::VEC_INDEX);
return true;
}
X64Reg indexReg = regCache_.Find(RegCache::VEC_INDEX);
bool maskedIndex = false;
// Okay, first load the actual gstate clutformat bits we'll use.
X64Reg formatReg = regCache_.Alloc(RegCache::VEC_TEMP0);
X64Reg gstateReg = GetGState();
if (cpu_info.bAVX2 && !id.hasClutShift)
VPBROADCASTD(128, formatReg, MDisp(gstateReg, offsetof(GPUgstate, clutformat)));
else
MOVD_xmm(formatReg, MDisp(gstateReg, offsetof(GPUgstate, clutformat)));
regCache_.Unlock(gstateReg, RegCache::GEN_GSTATE);
// Shift = (clutformat >> 2) & 0x1F
if (id.hasClutShift) {
// Before shifting, let's mask if needed (we always read 32 bits.)
// We have to do this here, because the bits should be zero even if F is used as a mask.
if (bitsPerIndex < 32) {
PSLLD(indexReg, 32 - bitsPerIndex);
PSRLD(indexReg, 32 - bitsPerIndex);
maskedIndex = true;
}
X64Reg shiftReg = regCache_.Alloc(RegCache::VEC_TEMP1);
// Shift against walls to get 5 bits after the rightmost 2.
if (cpu_info.bAVX) {
VPSLLD(128, shiftReg, formatReg, 32 - 7);
} else {
MOVDQA(shiftReg, R(formatReg));
PSLLD(shiftReg, 32 - 7);
}
PSRLD(shiftReg, 32 - 5);
// The other lanes are zero, so we can use PSRLD.
PSRLD(indexReg, R(shiftReg));
regCache_.Release(shiftReg, RegCache::VEC_TEMP1);
}
// With shifting done, we need the format in each lane.
if (!cpu_info.bAVX2 || id.hasClutShift)
PSHUFD(formatReg, R(formatReg), _MM_SHUFFLE(0, 0, 0, 0));
// Mask = (clutformat >> 8) & 0xFF
if (id.hasClutMask) {
X64Reg maskReg = regCache_.Alloc(RegCache::VEC_TEMP1);
// If it was CLUT4, grab only 4 bits of the mask.
if (cpu_info.bAVX) {
VPSLLD(128, maskReg, formatReg, bitsPerIndex == 4 ? 20 : 16);
} else {
MOVDQA(maskReg, R(formatReg));
PSLLD(maskReg, bitsPerIndex == 4 ? 20 : 16);
}
PSRLD(maskReg, bitsPerIndex == 4 ? 28 : 24);
PAND(indexReg, R(maskReg));
regCache_.Release(maskReg, RegCache::VEC_TEMP1);
} else if (!maskedIndex || bitsPerIndex > 8) {
// Apply the fixed 8 bit mask (or the CLUT4 mask if we didn't shift.)
PSLLD(indexReg, maskedIndex || bitsPerIndex >= 8 ? 24 : 28);
PSRLD(indexReg, maskedIndex || bitsPerIndex >= 8 ? 24 : 28);
}
// Offset = (clutformat >> 12) & 0x01F0
if (id.hasClutOffset) {
// Use walls to extract the 5 bits at 16, and then put them shifted left by 4.
int offsetBits = fmt == GE_CMODE_32BIT_ABGR8888 ? 4 : 5;
PSRLD(formatReg, 16);
PSLLD(formatReg, 32 - offsetBits);
PSRLD(formatReg, 32 - offsetBits - 4);
POR(indexReg, R(formatReg));
}
regCache_.Release(formatReg, RegCache::VEC_TEMP0);
regCache_.Unlock(indexReg, RegCache::VEC_INDEX);
return true;
}
bool SamplerJitCache::Jit_ReadClutQuad(const SamplerID &id, bool level1) {
Describe("ReadCLUTQuad");
X64Reg indexReg = regCache_.Find(RegCache::VEC_INDEX);
if (!id.useSharedClut) {
X64Reg vecLevelReg = regCache_.Alloc(RegCache::VEC_TEMP0);
if (regCache_.Has(RegCache::GEN_ARG_LEVEL)) {
X64Reg levelReg = regCache_.Find(RegCache::GEN_ARG_LEVEL);
MOVD_xmm(vecLevelReg, R(levelReg));
regCache_.Unlock(levelReg, RegCache::GEN_ARG_LEVEL);
} else {
#if PPSSPP_PLATFORM(WINDOWS)
if (cpu_info.bAVX2) {
VPBROADCASTD(128, vecLevelReg, MDisp(RSP, stackArgPos_ + 16));
} else {
MOVD_xmm(vecLevelReg, MDisp(RSP, stackArgPos_ + 16));
PSHUFD(vecLevelReg, R(vecLevelReg), _MM_SHUFFLE(0, 0, 0, 0));
}
#else
_assert_(false);
#endif
}
// Now we multiply by 16, and add.
PSLLD(vecLevelReg, 4);
PADDD(indexReg, R(vecLevelReg));
regCache_.Release(vecLevelReg, RegCache::VEC_TEMP0);
}
X64Reg clutBaseReg = regCache_.Alloc(RegCache::GEN_TEMP1);
MOV(PTRBITS, R(clutBaseReg), ImmPtr(clut));
X64Reg resultReg = regCache_.Find(level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
X64Reg maskReg = regCache_.Alloc(RegCache::VEC_TEMP0);
if (cpu_info.bAVX2 && id.overReadSafe)
PCMPEQD(maskReg, R(maskReg));
switch (id.ClutFmt()) {
case GE_CMODE_16BIT_BGR5650:
case GE_CMODE_16BIT_ABGR5551:
case GE_CMODE_16BIT_ABGR4444:
if (cpu_info.bAVX2 && id.overReadSafe) {
VPGATHERDD(128, resultReg, MComplex(clutBaseReg, indexReg, SCALE_2, 0), maskReg);
// Clear out the top 16 bits.
PCMPEQD(maskReg, R(maskReg));
PSRLD(maskReg, 16);
PAND(resultReg, R(maskReg));
} else {
PXOR(resultReg, R(resultReg));
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
if (cpu_info.bSSE4_1) {
for (int i = 0; i < 4; ++i) {
PEXTRD(R(temp2Reg), indexReg, i);
PINSRW(resultReg, MComplex(clutBaseReg, temp2Reg, SCALE_2, 0), i * 2);
}
} else {
for (int i = 0; i < 4; ++i) {
MOVD_xmm(R(temp2Reg), indexReg);
if (i != 3)
PSRLDQ(indexReg, 4);
PINSRW(resultReg, MComplex(clutBaseReg, temp2Reg, SCALE_2, 0), i * 2);
}
}
regCache_.Release(temp2Reg, RegCache::GEN_TEMP2);
}
break;
case GE_CMODE_32BIT_ABGR8888:
if (cpu_info.bAVX2 && id.overReadSafe) {
VPGATHERDD(128, resultReg, MComplex(clutBaseReg, indexReg, SCALE_4, 0), maskReg);
} else {
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
if (cpu_info.bSSE4_1) {
for (int i = 0; i < 4; ++i) {
PEXTRD(R(temp2Reg), indexReg, i);
PINSRD(resultReg, MComplex(clutBaseReg, temp2Reg, SCALE_4, 0), i);
}
} else {
for (int i = 0; i < 4; ++i) {
MOVD_xmm(R(temp2Reg), indexReg);
if (i != 3)
PSRLDQ(indexReg, 4);
if (i == 0) {
MOVD_xmm(resultReg , MComplex(clutBaseReg, temp2Reg, SCALE_4, 0));
} else {
MOVD_xmm(maskReg, MComplex(clutBaseReg, temp2Reg, SCALE_4, 0));
PSLLDQ(maskReg, 4 * i);
POR(resultReg, R(maskReg));
}
}
}
regCache_.Release(temp2Reg, RegCache::GEN_TEMP2);
}
break;
}
regCache_.Release(maskReg, RegCache::VEC_TEMP0);
regCache_.Unlock(resultReg, level1 ? RegCache::VEC_RESULT1 : RegCache::VEC_RESULT);
regCache_.Release(clutBaseReg, RegCache::GEN_TEMP1);
regCache_.Release(indexReg, RegCache::VEC_INDEX);
return true;
}
bool SamplerJitCache::Jit_BlendQuad(const SamplerID &id, bool level1) {
Describe(level1 ? "BlendQuadMips" : "BlendQuad");
@@ -1003,11 +1370,16 @@ bool SamplerJitCache::Jit_BlendQuad(const SamplerID &id, bool level1) {
regCache_.Release(multLRReg, RegCache::VEC_TEMP1);
// Shrink to 16-bit, it's more convenient for later.
PACKSSDW(quadReg, R(quadReg));
if (level1) {
PACKSSDW(quadReg, R(quadReg));
regCache_.Unlock(quadReg, RegCache::VEC_RESULT1);
} else {
MOVDQA(XMM0, R(quadReg));
if (cpu_info.bAVX) {
VPACKSSDW(128, XMM0, quadReg, R(quadReg));
} else {
PACKSSDW(quadReg, R(quadReg));
MOVDQA(XMM0, R(quadReg));
}
regCache_.Unlock(quadReg, RegCache::VEC_RESULT);
regCache_.ForceRelease(RegCache::VEC_RESULT);
@@ -1324,19 +1696,19 @@ bool SamplerJitCache::Jit_ReadTextureFormat(const SamplerID &id) {
switch (fmt) {
case GE_TFMT_5650:
success = Jit_GetTexData(id, 16);
if (success && !id.linear)
if (success)
success = Jit_Decode5650();
break;
case GE_TFMT_5551:
success = Jit_GetTexData(id, 16);
if (success && !id.linear)
if (success)
success = Jit_Decode5551();
break;
case GE_TFMT_4444:
success = Jit_GetTexData(id, 16);
if (success && !id.linear)
if (success)
success = Jit_Decode4444();
break;
@@ -1790,55 +2162,8 @@ bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) {
if (id.swizzle) {
return Jit_GetTexDataSwizzled(id, bitsPerTexel);
}
if (id.linear) {
Describe("TexDataL");
// We can throw away bufw immediately. Maybe even earlier?
regCache_.ForceRelease(RegCache::GEN_ARG_BUFW);
X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT);
X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR);
X64Reg byteIndexReg = regCache_.Find(RegCache::GEN_ARG_V);
bool success = true;
switch (bitsPerTexel) {
case 32:
case 16:
case 8:
MOVZX(32, bitsPerTexel, resultReg, MRegSum(srcReg, byteIndexReg));
break;
case 4:
MOV(8, R(resultReg), MRegSum(srcReg, byteIndexReg));
break;
default:
success = false;
break;
}
// Okay, srcReg and byteIndexReg have done their jobs.
regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR);
regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR);
regCache_.Unlock(byteIndexReg, RegCache::GEN_ARG_V);
regCache_.ForceRelease(RegCache::GEN_ARG_V);
if (bitsPerTexel == 4) {
X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U);
SHR(32, R(uReg), Imm8(1));
FixupBranch skip = J_CC(CC_NC);
SHR(32, R(resultReg), Imm8(4));
SetJumpTarget(skip);
// Zero out any bits not shifted off.
AND(32, R(resultReg), Imm8(0x0F));
regCache_.Unlock(uReg, RegCache::GEN_ARG_U);
}
regCache_.ForceRelease(RegCache::GEN_ARG_U);
regCache_.Unlock(resultReg, RegCache::GEN_RESULT);
return success;
}
_assert_msg_(!id.linear, "Should not use this path for linear")
Describe("TexData");
X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1);
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
@@ -1922,39 +2247,8 @@ bool SamplerJitCache::Jit_GetTexData(const SamplerID &id, int bitsPerTexel) {
}
bool SamplerJitCache::Jit_GetTexDataSwizzled4(const SamplerID &id) {
if (id.linear) {
Describe("TexDataS4L");
// We can throw away bufw immediately. Maybe even earlier?
regCache_.ForceRelease(RegCache::GEN_ARG_BUFW);
X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U);
X64Reg byteIndexReg = regCache_.Find(RegCache::GEN_ARG_V);
X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR);
X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT);
MOV(8, R(resultReg), MRegSum(srcReg, byteIndexReg));
SHR(32, R(uReg), Imm8(1));
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);
// We're all done with each of these regs, now.
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(byteIndexReg, RegCache::GEN_ARG_V);
regCache_.ForceRelease(RegCache::GEN_ARG_V);
return true;
}
Describe("TexDataS4");
_assert_msg_(!id.linear, "Should not use this path for linear")
X64Reg temp1Reg = regCache_.Alloc(RegCache::GEN_TEMP1);
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
X64Reg uReg = regCache_.Find(RegCache::GEN_ARG_U);
@@ -2018,41 +2312,7 @@ bool SamplerJitCache::Jit_GetTexDataSwizzled(const SamplerID &id, int bitsPerTex
}
bool success = true;
if (id.linear) {
Describe("TexDataSL");
// We can throw away bufw immediately. Maybe even earlier?
regCache_.ForceRelease(RegCache::GEN_ARG_BUFW);
// We've also baked uReg into vReg.
regCache_.ForceRelease(RegCache::GEN_ARG_U);
X64Reg byteIndexReg = regCache_.Find(RegCache::GEN_ARG_V);
X64Reg srcReg = regCache_.Find(RegCache::GEN_ARG_TEXPTR);
X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT);
switch (bitsPerTexel) {
case 32:
MOV(bitsPerTexel, R(resultReg), MRegSum(srcReg, byteIndexReg));
break;
case 16:
MOVZX(32, bitsPerTexel, resultReg, MRegSum(srcReg, byteIndexReg));
break;
case 8:
MOVZX(32, bitsPerTexel, resultReg, MRegSum(srcReg, byteIndexReg));
break;
default:
success = false;
break;
}
regCache_.Unlock(resultReg, RegCache::GEN_RESULT);
// The pointer and offset have done their duty.
regCache_.Unlock(srcReg, RegCache::GEN_ARG_TEXPTR);
regCache_.ForceRelease(RegCache::GEN_ARG_TEXPTR);
regCache_.Unlock(byteIndexReg, RegCache::GEN_ARG_V);
regCache_.ForceRelease(RegCache::GEN_ARG_V);
return success;
}
_assert_msg_(!id.linear, "Should not use this path for linear")
Describe("TexDataS");
X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT);
@@ -2657,8 +2917,12 @@ bool SamplerJitCache::Jit_PrepareDataSwizzledOffsets(const SamplerID &id, RegCac
// Divide vvec by 8 in a temp.
X64Reg vMultReg = regCache_.Alloc(RegCache::VEC_TEMP1);
MOVDQA(vMultReg, R(vReg));
PSRLD(vMultReg, 3);
if (cpu_info.bAVX) {
VPSRLD(128, vMultReg, vReg, 3);
} else {
MOVDQA(vMultReg, R(vReg));
PSRLD(vMultReg, 3);
}
// And now multiply by bufw. May be able to use a shift in a common case.
int shiftAmount = 32 - clz32_nonzero(bitsPerTexel - 1);
@@ -2700,16 +2964,24 @@ bool SamplerJitCache::Jit_PrepareDataSwizzledOffsets(const SamplerID &id, RegCac
// Now get ((uvec / texels_per_tile) / 4) * 32 * 4 aka (uvec / (128 / bitsPerTexel)) << 7.
X64Reg uCopyReg = regCache_.Alloc(RegCache::VEC_TEMP0);
MOVDQA(uCopyReg, R(uReg));
PSRLD(uCopyReg, 7 + clz32_nonzero(bitsPerTexel - 1) - 32);
if (cpu_info.bAVX) {
VPSRLD(128, uCopyReg, uReg, 7 + clz32_nonzero(bitsPerTexel - 1) - 32);
} else {
MOVDQA(uCopyReg, R(uReg));
PSRLD(uCopyReg, 7 + clz32_nonzero(bitsPerTexel - 1) - 32);
}
PSLLD(uCopyReg, 7);
// Add it in to our running total.
PADDD(vReg, R(uCopyReg));
if (bitsPerTexel == 4) {
// Finally, we want (uvec & 31) / 2. Use a 16-bit wall.
MOVDQA(uCopyReg, R(uReg));
PSLLW(uCopyReg, 11);
if (cpu_info.bAVX) {
VPSLLW(128, uCopyReg, uReg, 11);
} else {
MOVDQA(uCopyReg, R(uReg));
PSLLW(uCopyReg, 11);
}
PSRLD(uCopyReg, 12);
// With that, this is our byte offset. uvec & 1 has which half.
PADDD(vReg, R(uCopyReg));
@@ -3116,6 +3388,7 @@ bool SamplerJitCache::Jit_TransformClutIndex(const SamplerID &id, int bitsPerInd
bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) {
Describe("ReadCLUT");
X64Reg resultReg = regCache_.Find(RegCache::GEN_RESULT);
_assert_msg_(!id.linear, "Should not use this path for linear");
if (!id.useSharedClut) {
X64Reg temp2Reg = regCache_.Alloc(RegCache::GEN_TEMP2);
@@ -3130,13 +3403,8 @@ bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) {
regCache_.ForceRelease(RegCache::GEN_ARG_LEVEL);
} else {
#if PPSSPP_PLATFORM(WINDOWS)
if (id.linear) {
// Extra 8 to account for call.
MOV(32, R(temp2Reg), MDisp(RSP, stackArgPos_ + 8 + 16));
} else {
// The argument was saved on the stack.
MOV(32, R(temp2Reg), MDisp(RSP, 40));
}
// The argument was saved on the stack.
MOV(32, R(temp2Reg), MDisp(RSP, 40));
#else
_assert_(false);
#endif
@@ -3168,13 +3436,13 @@ bool SamplerJitCache::Jit_ReadClutColor(const SamplerID &id) {
switch (id.ClutFmt()) {
case GE_CMODE_16BIT_BGR5650:
return id.linear || Jit_Decode5650();
return Jit_Decode5650();
case GE_CMODE_16BIT_ABGR5551:
return id.linear || Jit_Decode5551();
return Jit_Decode5551();
case GE_CMODE_16BIT_ABGR4444:
return id.linear || Jit_Decode4444();
return Jit_Decode4444();
case GE_CMODE_32BIT_ABGR8888:
return true;