CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/GPU/Software/FuncId.cpp
Views: 1401
// Copyright (c) 2021- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/Data/Convert/ColorConv.h"18#include "Common/StringUtils.h"19#include "Core/MemMap.h"20#include "GPU/Common/TextureDecoder.h"21#include "GPU/GPUState.h"22#include "GPU/Software/FuncId.h"2324static_assert(sizeof(SamplerID) == sizeof(SamplerID::fullKey) + sizeof(SamplerID::cached) + sizeof(SamplerID::pad), "Bad sampler ID size");25static_assert(sizeof(PixelFuncID) == sizeof(PixelFuncID::fullKey) + sizeof(PixelFuncID::cached), "Bad pixel func ID size");2627static inline GEComparison OptimizeRefByteCompare(GEComparison func, u8 ref) {28// Not equal tests are easier.29if (ref == 0 && func == GE_COMP_GREATER)30return GE_COMP_NOTEQUAL;31if (ref == 0xFF && func == GE_COMP_LESS)32return GE_COMP_NOTEQUAL;3334// Sometimes games pointlessly use tests like these.35if (ref == 0 && func == GE_COMP_GEQUAL)36return GE_COMP_ALWAYS;37if (ref == 0xFF && func == GE_COMP_LEQUAL)38return GE_COMP_ALWAYS;39return func;40}4142static inline PixelBlendFactor OptimizeAlphaFactor(uint32_t color) {43if (color == 0x00000000)44return PixelBlendFactor::ZERO;45if (color == 0x00FFFFFF)46return PixelBlendFactor::ONE;47return PixelBlendFactor::FIX;48}4950void ComputePixelFuncID(PixelFuncID *id) {51id->fullKey = 0;5253// TODO: Could this be minz > 0x0000 || maxz < 0xFFFF? Maybe unsafe, depending on verts...54id->applyDepthRange = !gstate.isModeThrough();55// Dither happens even in clear mode.56id->dithering = gstate.isDitherEnabled();57id->fbFormat = gstate.FrameBufFormat();58id->useStandardStride = gstate.FrameBufStride() == 512;59id->applyColorWriteMask = gstate.getColorMask() != 0;6061id->clearMode = gstate.isModeClear();62if (id->clearMode) {63id->colorTest = gstate.isClearModeColorMask();64id->stencilTest = gstate.isClearModeAlphaMask() && gstate.FrameBufFormat() != GE_FORMAT_565;65id->depthWrite = gstate.isClearModeDepthMask();66id->depthTestFunc = GE_COMP_ALWAYS;67id->alphaTestFunc = GE_COMP_ALWAYS;68} else {69id->colorTest = gstate.isColorTestEnabled() && gstate.getColorTestFunction() != GE_COMP_ALWAYS;70if (gstate.isStencilTestEnabled() && gstate.getStencilTestFunction() == GE_COMP_ALWAYS) {71// If stencil always passes, force off when we won't write any stencil bits.72bool stencilWrite = (gstate.pmska & 0xFF) != 0xFF && gstate.FrameBufFormat() != GE_FORMAT_565;73if (gstate.isDepthTestEnabled() && gstate.getDepthTestFunction() != GE_COMP_ALWAYS)74id->stencilTest = stencilWrite && (gstate.getStencilOpZPass() != GE_STENCILOP_KEEP || gstate.getStencilOpZFail() != GE_STENCILOP_KEEP);75else76id->stencilTest = stencilWrite && gstate.getStencilOpZPass() != GE_STENCILOP_KEEP;77} else {78id->stencilTest = gstate.isStencilTestEnabled();79}80id->depthWrite = gstate.isDepthTestEnabled() && gstate.isDepthWriteEnabled();81id->depthTestFunc = gstate.isDepthTestEnabled() ? gstate.getDepthTestFunction() : GE_COMP_ALWAYS;8283if (id->stencilTest) {84id->stencilTestRef = gstate.getStencilTestRef() & gstate.getStencilTestMask();85id->stencilTestFunc = OptimizeRefByteCompare(gstate.getStencilTestFunction(), id->stencilTestRef);86id->hasStencilTestMask = gstate.getStencilTestMask() != 0xFF && gstate.FrameBufFormat() != GE_FORMAT_565;8788// Stencil can't be written on 565, and any invalid op acts like KEEP, which is 0.89if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpSFail() <= GE_STENCILOP_DECR)90id->sFail = gstate.getStencilOpSFail();91if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpZFail() <= GE_STENCILOP_DECR)92id->zFail = gstate.isDepthTestEnabled() ? gstate.getStencilOpZFail() : GE_STENCILOP_KEEP;93if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpZPass() <= GE_STENCILOP_DECR)94id->zPass = gstate.getStencilOpZPass();9596// Normalize REPLACE 00 to ZERO, especially if using a mask.97if (gstate.getStencilTestRef() == 0) {98if (id->SFail() == GE_STENCILOP_REPLACE)99id->sFail = GE_STENCILOP_ZERO;100if (id->ZFail() == GE_STENCILOP_REPLACE)101id->zFail = GE_STENCILOP_ZERO;102if (id->ZPass() == GE_STENCILOP_REPLACE)103id->zPass = GE_STENCILOP_ZERO;104}105106// For 5551, DECR is also the same as ZERO.107if (id->FBFormat() == GE_FORMAT_5551) {108if (id->SFail() == GE_STENCILOP_DECR)109id->sFail = GE_STENCILOP_ZERO;110if (id->ZFail() == GE_STENCILOP_DECR)111id->zFail = GE_STENCILOP_ZERO;112if (id->ZPass() == GE_STENCILOP_DECR)113id->zPass = GE_STENCILOP_ZERO;114}115116// And same for sFail if there's no stencil test. Prefer KEEP, though.117if (id->StencilTestFunc() == GE_COMP_ALWAYS) {118if (id->DepthTestFunc() == GE_COMP_ALWAYS)119id->zFail = GE_STENCILOP_KEEP;120id->sFail = GE_STENCILOP_KEEP;121// Always doesn't need a mask.122id->stencilTestRef = gstate.getStencilTestRef();123id->hasStencilTestMask = false;124125// Turn off stencil testing if it's doing nothing.126if (id->SFail() == GE_STENCILOP_KEEP && id->ZFail() == GE_STENCILOP_KEEP && id->ZPass() == GE_STENCILOP_KEEP) {127id->stencilTest = false;128id->stencilTestFunc = 0;129id->stencilTestRef = 0;130}131} else if (id->DepthTestFunc() == GE_COMP_ALWAYS) {132// Always treat zPass/zFail the same if there's no depth test.133id->zFail = id->zPass;134}135}136137id->alphaTestFunc = gstate.isAlphaTestEnabled() ? gstate.getAlphaTestFunction() : GE_COMP_ALWAYS;138if (id->AlphaTestFunc() != GE_COMP_ALWAYS) {139id->alphaTestRef = gstate.getAlphaTestRef() & gstate.getAlphaTestMask();140id->hasAlphaTestMask = gstate.getAlphaTestMask() != 0xFF;141// Try to pick a more optimal variant.142id->alphaTestFunc = OptimizeRefByteCompare(id->AlphaTestFunc(), id->alphaTestRef);143if (id->alphaTestFunc == GE_COMP_ALWAYS) {144id->alphaTestRef = 0;145id->hasAlphaTestMask = false;146}147}148149// If invalid (6 or 7), doesn't do any blending, so force off.150id->alphaBlend = gstate.isAlphaBlendEnabled() && gstate.getBlendEq() <= 5;151// Force it off if the factors are constant and don't blend. Some games use this...152if (id->alphaBlend && gstate.getBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {153bool srcFixedOne = gstate.getBlendFuncA() == GE_SRCBLEND_FIXA && gstate.getFixA() == 0x00FFFFFF;154bool dstFixedZero = gstate.getBlendFuncB() == GE_DSTBLEND_FIXB && gstate.getFixB() == 0x00000000;155if (srcFixedOne && dstFixedZero)156id->alphaBlend = false;157}158if (id->alphaBlend)159id->alphaBlendEq = gstate.getBlendEq();160if (id->alphaBlend && id->alphaBlendEq <= GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE) {161id->alphaBlendSrc = gstate.getBlendFuncA();162id->alphaBlendDst = gstate.getBlendFuncB();163// Special values.164if (id->alphaBlendSrc >= GE_SRCBLEND_FIXA)165id->alphaBlendSrc = (uint8_t)OptimizeAlphaFactor(gstate.getFixA());166if (id->alphaBlendDst >= GE_DSTBLEND_FIXB)167id->alphaBlendDst = (uint8_t)OptimizeAlphaFactor(gstate.getFixB());168}169170if (id->colorTest && gstate.getColorTestFunction() == GE_COMP_NOTEQUAL && gstate.getColorTestRef() == 0 && gstate.getColorTestMask() == 0xFFFFFF) {171if (!id->depthWrite && !id->stencilTest && id->alphaBlend && id->AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {172// Might be a pointless color test (seen in Ridge Racer, for example.)173if (id->AlphaBlendDst() == PixelBlendFactor::ONE)174id->colorTest = false;175}176}177178id->applyLogicOp = gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY;179id->applyFog = gstate.isFogEnabled() && !gstate.isModeThrough();180181id->earlyZChecks = id->DepthTestFunc() != GE_COMP_ALWAYS;182if (id->stencilTest && id->earlyZChecks) {183// Can't do them early if stencil might need to write.184if (id->SFail() != GE_STENCILOP_KEEP || id->ZFail() != GE_STENCILOP_KEEP)185id->earlyZChecks = false;186}187}188189if (id->useStandardStride && (id->depthTestFunc != GE_COMP_ALWAYS || id->depthWrite))190id->useStandardStride = gstate.DepthBufStride() == 512;191192// Cache some values for later convenience.193if (id->dithering) {194for (int y = 0; y < 4; ++y) {195for (int x = 0; x < 4; ++x)196id->cached.ditherMatrix[y * 4 + x] = gstate.getDitherValue(x, y);197}198}199if (id->applyColorWriteMask) {200uint32_t mask = gstate.getColorMask();201// This flag means stencil clear or stencil test, basically whether writing to stencil.202if (!id->stencilTest)203mask |= 0xFF000000;204205switch (id->fbFormat) {206case GE_FORMAT_565:207id->cached.colorWriteMask = RGBA8888ToRGB565(mask);208break;209210case GE_FORMAT_5551:211id->cached.colorWriteMask = RGBA8888ToRGBA5551(mask);212break;213214case GE_FORMAT_4444:215id->cached.colorWriteMask = RGBA8888ToRGBA4444(mask);216break;217218case GE_FORMAT_8888:219id->cached.colorWriteMask = mask;220break;221}222}223if (id->applyFog) {224id->cached.fogColor = gstate.fogcolor & 0x00FFFFFF;225}226if (id->applyLogicOp)227id->cached.logicOp = gstate.getLogicOp();228id->cached.minz = gstate.getDepthRangeMin();229id->cached.maxz = gstate.getDepthRangeMax();230id->cached.framebufStride = gstate.FrameBufStride();231id->cached.depthbufStride = gstate.DepthBufStride();232233if (id->hasStencilTestMask) {234// Without the mask applied, unlike the one in the key.235id->cached.stencilRef = gstate.getStencilTestRef();236id->cached.stencilTestMask = gstate.getStencilTestMask();237}238if (id->hasAlphaTestMask)239id->cached.alphaTestMask = gstate.getAlphaTestMask();240if (!id->clearMode && id->colorTest) {241id->cached.colorTestFunc = gstate.getColorTestFunction();242id->cached.colorTestMask = gstate.getColorTestMask();243id->cached.colorTestRef = gstate.getColorTestRef() & id->cached.colorTestMask;244}245if (id->alphaBlendSrc == GE_SRCBLEND_FIXA)246id->cached.alphaBlendSrc = gstate.getFixA();247if (id->alphaBlendDst == GE_DSTBLEND_FIXB)248id->cached.alphaBlendDst = gstate.getFixB();249}250251std::string DescribePixelFuncID(const PixelFuncID &id) {252std::string desc;253if (id.clearMode) {254desc = "Clear";255if (id.ColorClear())256desc += "C";257if (id.StencilClear())258desc += "S";259if (id.DepthClear())260desc += "D";261desc += ":";262}263if (id.applyDepthRange)264desc += "DepthR:";265if (id.useStandardStride)266desc += "Str512:";267if (id.dithering)268desc += "Dith:";269if (id.applyColorWriteMask)270desc += "Msk:";271272switch (id.fbFormat) {273case GE_FORMAT_565: desc += "5650:"; break;274case GE_FORMAT_5551: desc += "5551:"; break;275case GE_FORMAT_4444: desc += "4444:"; break;276case GE_FORMAT_8888: desc += "8888:"; break;277}278279if (id.AlphaTestFunc() != GE_COMP_ALWAYS) {280if (id.clearMode)281desc = "INVALID:" + desc;282switch (id.AlphaTestFunc()) {283case GE_COMP_NEVER: desc += "ANever"; break;284case GE_COMP_ALWAYS: break;285case GE_COMP_EQUAL: desc += "AEQ"; break;286case GE_COMP_NOTEQUAL: desc += "ANE"; break;287case GE_COMP_LESS: desc += "ALT"; break;288case GE_COMP_LEQUAL: desc += "ALE"; break;289case GE_COMP_GREATER: desc += "AGT"; break;290case GE_COMP_GEQUAL: desc += "AGE"; break;291}292if (id.hasAlphaTestMask)293desc += "Msk";294desc += StringFromFormat("%02X:", id.alphaTestRef);295} else if (id.hasAlphaTestMask || id.alphaTestRef != 0) {296desc = "INVALID:" + desc;297}298299if (id.earlyZChecks)300desc += "ZEarly:";301if (id.DepthTestFunc() != GE_COMP_ALWAYS) {302if (id.clearMode)303desc = "INVALID:" + desc;304switch (id.DepthTestFunc()) {305case GE_COMP_NEVER: desc += "ZNever:"; break;306case GE_COMP_ALWAYS: break;307case GE_COMP_EQUAL: desc += "ZEQ:"; break;308case GE_COMP_NOTEQUAL: desc += "ZNE:"; break;309case GE_COMP_LESS: desc += "ZLT:"; break;310case GE_COMP_LEQUAL: desc += "ZLE:"; break;311case GE_COMP_GREATER: desc += "ZGT:"; break;312case GE_COMP_GEQUAL: desc += "ZGE:"; break;313}314}315if (id.depthWrite && !id.clearMode)316desc += "ZWr:";317318if (id.colorTest && !id.clearMode)319desc += "CTest:";320321if (id.stencilTest && !id.clearMode) {322switch (id.StencilTestFunc()) {323case GE_COMP_NEVER: desc += "SNever"; break;324case GE_COMP_ALWAYS: desc += "SAlways"; break;325case GE_COMP_EQUAL: desc += "SEQ"; break;326case GE_COMP_NOTEQUAL: desc += "SNE"; break;327case GE_COMP_LESS: desc += "SLT"; break;328case GE_COMP_LEQUAL: desc += "SLE"; break;329case GE_COMP_GREATER: desc += "SGT"; break;330case GE_COMP_GEQUAL: desc += "SGE"; break;331}332if (id.hasStencilTestMask)333desc += "Msk";334if (id.StencilTestFunc() != GE_COMP_ALWAYS || id.DepthTestFunc() != GE_COMP_ALWAYS)335desc += StringFromFormat("%02X:", id.stencilTestRef);336} else if (id.hasStencilTestMask || id.stencilTestRef != 0 || id.stencilTestFunc != 0) {337desc = "INVALID:" + desc;338}339340switch (id.SFail()) {341case GE_STENCILOP_KEEP: break;342case GE_STENCILOP_ZERO: desc += "STstF0:"; break;343case GE_STENCILOP_REPLACE: desc += "STstFRpl:"; break;344case GE_STENCILOP_INVERT: desc += "STstFXor:"; break;345case GE_STENCILOP_INCR: desc += "STstFInc:"; break;346case GE_STENCILOP_DECR: desc += "STstFDec:"; break;347}348switch (id.ZFail()) {349case GE_STENCILOP_KEEP: break;350case GE_STENCILOP_ZERO: desc += "ZTstF0:"; break;351case GE_STENCILOP_REPLACE: desc += "ZTstFRpl:"; break;352case GE_STENCILOP_INVERT: desc += "ZTstFXor:"; break;353case GE_STENCILOP_INCR: desc += "ZTstFInc:"; break;354case GE_STENCILOP_DECR: desc += "ZTstFDec:"; break;355}356if (id.StencilTestFunc() == GE_COMP_ALWAYS && id.DepthTestFunc() == GE_COMP_ALWAYS) {357switch (id.ZPass()) {358case GE_STENCILOP_KEEP: break;359case GE_STENCILOP_ZERO: desc += "Zero:"; break;360case GE_STENCILOP_REPLACE: desc += StringFromFormat("Rpl%02X:", id.stencilTestRef); break;361case GE_STENCILOP_INVERT: desc += "Xor:"; break;362case GE_STENCILOP_INCR: desc += "Inc:"; break;363case GE_STENCILOP_DECR: desc += "Dec:"; break;364}365} else {366switch (id.ZPass()) {367case GE_STENCILOP_KEEP: break;368case GE_STENCILOP_ZERO: desc += "ZTstT0:"; break;369case GE_STENCILOP_REPLACE: desc += "ZTstTRpl:"; break;370case GE_STENCILOP_INVERT: desc += "ZTstTXor:"; break;371case GE_STENCILOP_INCR: desc += "ZTstTInc:"; break;372case GE_STENCILOP_DECR: desc += "ZTstTDec:"; break;373}374}375if (!id.stencilTest || id.clearMode) {376if (id.sFail != 0 || id.zFail != 0 || id.zPass != 0)377desc = "INVALID:" + desc;378}379380if (id.alphaBlend) {381if (id.clearMode)382desc = "INVALID:" + desc;383switch (id.AlphaBlendEq()) {384case GE_BLENDMODE_MUL_AND_ADD: desc += "BlendAdd<"; break;385case GE_BLENDMODE_MUL_AND_SUBTRACT: desc += "BlendSub<"; break;386case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: desc += "BlendRSub<"; break;387case GE_BLENDMODE_MIN: desc += "BlendMin<"; break;388case GE_BLENDMODE_MAX: desc += "BlendMax<"; break;389case GE_BLENDMODE_ABSDIFF: desc += "BlendDiff<"; break;390}391switch (id.AlphaBlendSrc()) {392case PixelBlendFactor::OTHERCOLOR: desc += "DstRGB,"; break;393case PixelBlendFactor::INVOTHERCOLOR: desc += "1-DstRGB,"; break;394case PixelBlendFactor::SRCALPHA: desc += "SrcA,"; break;395case PixelBlendFactor::INVSRCALPHA: desc += "1-SrcA,"; break;396case PixelBlendFactor::DSTALPHA: desc += "DstA,"; break;397case PixelBlendFactor::INVDSTALPHA: desc += "1-DstA,"; break;398case PixelBlendFactor::DOUBLESRCALPHA: desc += "2*SrcA,"; break;399case PixelBlendFactor::DOUBLEINVSRCALPHA: desc += "1-2*SrcA,"; break;400case PixelBlendFactor::DOUBLEDSTALPHA: desc += "2*DstA,"; break;401case PixelBlendFactor::DOUBLEINVDSTALPHA: desc += "1-2*DstA,"; break;402case PixelBlendFactor::FIX: desc += "Fix,"; break;403case PixelBlendFactor::ZERO: desc += "0,"; break;404case PixelBlendFactor::ONE: desc += "1,"; break;405}406switch (id.AlphaBlendDst()) {407case PixelBlendFactor::OTHERCOLOR: desc += "SrcRGB>:"; break;408case PixelBlendFactor::INVOTHERCOLOR: desc += "1-SrcRGB>:"; break;409case PixelBlendFactor::SRCALPHA: desc += "SrcA>:"; break;410case PixelBlendFactor::INVSRCALPHA: desc += "1-SrcA>:"; break;411case PixelBlendFactor::DSTALPHA: desc += "DstA>:"; break;412case PixelBlendFactor::INVDSTALPHA: desc += "1-DstA>:"; break;413case PixelBlendFactor::DOUBLESRCALPHA: desc += "2*SrcA>:"; break;414case PixelBlendFactor::DOUBLEINVSRCALPHA: desc += "1-2*SrcA>:"; break;415case PixelBlendFactor::DOUBLEDSTALPHA: desc += "2*DstA>:"; break;416case PixelBlendFactor::DOUBLEINVDSTALPHA: desc += "1-2*DstA>:"; break;417case PixelBlendFactor::FIX: desc += "Fix>:"; break;418case PixelBlendFactor::ZERO: desc += "0>:"; break;419case PixelBlendFactor::ONE: desc += "1>:"; break;420}421} else if (id.alphaBlendEq != 0 || id.alphaBlendSrc != 0 || id.alphaBlendDst != 0) {422desc = "INVALID:" + desc;423}424425if (id.applyLogicOp)426desc += "Logic:";427else if (id.clearMode)428desc = "INVALID:" + desc;429if (id.applyFog)430desc += "Fog:";431else if (id.clearMode)432desc = "INVALID:" + desc;433434if (desc.empty())435return "INVALID";436desc.resize(desc.size() - 1);437return desc;438}439440void ComputeSamplerID(SamplerID *id_out) {441SamplerID id{};442443id.useStandardBufw = true;444id.overReadSafe = true;445int maxLevel = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() : 0;446GETextureFormat fmt = gstate.getTextureFormat();447for (int i = 0; i <= maxLevel; ++i) {448uint32_t addr = gstate.getTextureAddress(i);449if (!Memory::IsValidAddress(addr))450id.hasInvalidPtr = true;451452int bufw = GetTextureBufw(i, addr, fmt);453int bitspp = textureBitsPerPixel[fmt];454// We use a 16 byte minimum for all small bufws, so allow those as standard.455int w = gstate.getTextureWidth(i);456if (bitspp == 0 || std::max(w, 128 / bitspp) != bufw)457id.useStandardBufw = false;458// TODO: Verify 16 bit bufw align handling in DXT.459if (fmt >= GE_TFMT_DXT1 && w != bufw)460id.useStandardBufw = false;461462int h = gstate.getTextureHeight(i);463int bytes = h * (bufw * bitspp) / 8;464if (bitspp < 32 && !Memory::IsValidAddress(addr + bytes + (32 - bitspp) / 8))465id.overReadSafe = false;466467id.cached.sizes[i].w = w;468id.cached.sizes[i].h = h;469}470// TODO: What specifically happens if these are above 11?471id.width0Shift = gstate.texsize[0] & 0xF;472id.height0Shift = (gstate.texsize[0] >> 8) & 0xF;473id.hasAnyMips = maxLevel != 0;474475id.texfmt = fmt;476id.swizzle = gstate.isTextureSwizzled();477// Only CLUT4 can use separate CLUTs per mimap.478id.useSharedClut = fmt != GE_TFMT_CLUT4 || maxLevel == 0 || !gstate.isMipmapEnabled() || gstate.isClutSharedForMipmaps();479if (gstate.isTextureFormatIndexed()) {480id.clutfmt = gstate.getClutPaletteFormat();481id.hasClutMask = gstate.getClutIndexMask() != 0xFF;482id.hasClutShift = gstate.getClutIndexShift() != 0;483id.hasClutOffset = gstate.getClutIndexStartPos() != 0;484id.cached.clutFormat = gstate.clutformat;485}486487id.clampS = gstate.isTexCoordClampedS();488id.clampT = gstate.isTexCoordClampedT();489490id.useTextureAlpha = gstate.isTextureAlphaUsed();491id.useColorDoubling = gstate.isColorDoublingEnabled();492id.texFunc = gstate.getTextureFunction();493if (id.texFunc > GE_TEXFUNC_ADD)494id.texFunc = GE_TEXFUNC_ADD;495496if (id.texFunc == GE_TEXFUNC_BLEND)497id.cached.texBlendColor = gstate.getTextureEnvColRGB();498499*id_out = id;500}501502std::string DescribeSamplerID(const SamplerID &id) {503std::string name;504switch (id.TexFmt()) {505case GE_TFMT_5650: name = "5650"; break;506case GE_TFMT_5551: name = "5551"; break;507case GE_TFMT_4444: name = "4444"; break;508case GE_TFMT_8888: name = "8888"; break;509case GE_TFMT_CLUT4: name = "CLUT4"; break;510case GE_TFMT_CLUT8: name = "CLUT8"; break;511case GE_TFMT_CLUT16: name = "CLUT16"; break;512case GE_TFMT_CLUT32: name = "CLUT32"; break;513case GE_TFMT_DXT1: name = "DXT1"; break;514case GE_TFMT_DXT3: name = "DXT3"; break;515case GE_TFMT_DXT5: name = "DXT5"; break;516default: name = "INVALID"; break;517}518switch (id.ClutFmt()) {519case GE_CMODE_16BIT_BGR5650:520switch (id.TexFmt()) {521case GE_TFMT_CLUT4:522case GE_TFMT_CLUT8:523case GE_TFMT_CLUT16:524case GE_TFMT_CLUT32:525name += ":C5650";526break;527default:528// Ignore 0 clutfmt when no clut.529break;530}531break;532case GE_CMODE_16BIT_ABGR5551: name += ":C5551"; break;533case GE_CMODE_16BIT_ABGR4444: name += ":C4444"; break;534case GE_CMODE_32BIT_ABGR8888: name += ":C8888"; break;535}536if (id.swizzle) {537name += ":SWZ";538}539if (!id.useSharedClut) {540name += ":CMIP";541}542if (id.hasInvalidPtr) {543name += ":INV";544}545if (id.hasClutMask) {546name += ":CMASK";547}548if (id.hasClutShift) {549name += ":CSHF";550}551if (id.hasClutOffset) {552name += ":COFF";553}554if (id.clampS || id.clampT) {555name += std::string(":CL") + (id.clampS ? "S" : "") + (id.clampT ? "T" : "");556}557if (!id.useStandardBufw) {558name += ":BUFW";559}560if (!id.overReadSafe) {561name += ":XRD";562}563if (id.hasAnyMips) {564name += ":MIP";565}566if (id.linear) {567name += ":LERP";568}569if (id.fetch) {570name += ":FETCH";571}572if (id.useTextureAlpha) {573name += ":A";574}575if (id.useColorDoubling) {576name += ":DBL";577}578switch (id.texFunc) {579case GE_TEXFUNC_MODULATE:580name += ":MOD";581break;582case GE_TEXFUNC_DECAL:583name += ":DECAL";584break;585case GE_TEXFUNC_BLEND:586name += ":BLEND";587break;588case GE_TEXFUNC_REPLACE:589break;590case GE_TEXFUNC_ADD:591name += ":ADD";592default:593break;594}595name += StringFromFormat(":W%dH%d", 1 << id.width0Shift, 1 << id.height0Shift);596if (id.width0Shift > 10 || id.height0Shift > 10)597name = "INVALID:" + name;598599return name;600}601602603