mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
The pixel and sampler ID hashes and equality, the functions describing the IDs for the JIT's code pointers, and SamplerID's linear and fetch bits, which only told the JIT cache's entries apart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
334 lines
13 KiB
C++
334 lines
13 KiB
C++
// Copyright (c) 2021- 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 "Common/Data/Convert/ColorConv.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/MemMap.h"
|
|
#include "GPU/Common/TextureDecoder.h"
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/Software/FuncId.h"
|
|
#include "GPU/Software/SoftGpu.h"
|
|
|
|
static_assert(sizeof(SamplerID) == sizeof(SamplerID::fullKey) + sizeof(SamplerID::cached) + sizeof(SamplerID::pad), "Bad sampler ID size");
|
|
static_assert(sizeof(PixelFuncID) == sizeof(PixelFuncID::fullKey) + sizeof(PixelFuncID::cached), "Bad pixel func ID size");
|
|
|
|
static inline GEComparison OptimizeRefByteCompare(GEComparison func, u8 ref) {
|
|
// Not equal tests are easier.
|
|
if (ref == 0 && func == GE_COMP_GREATER)
|
|
return GE_COMP_NOTEQUAL;
|
|
if (ref == 0xFF && func == GE_COMP_LESS)
|
|
return GE_COMP_NOTEQUAL;
|
|
|
|
// Sometimes games pointlessly use tests like these.
|
|
if (ref == 0 && func == GE_COMP_GEQUAL)
|
|
return GE_COMP_ALWAYS;
|
|
if (ref == 0xFF && func == GE_COMP_LEQUAL)
|
|
return GE_COMP_ALWAYS;
|
|
return func;
|
|
}
|
|
|
|
// The stencil test compares the other way around, ref against the buffer's value.
|
|
static inline GEComparison OptimizeStencilRefCompare(GEComparison func, u8 ref) {
|
|
if (ref == 0 && func == GE_COMP_GREATER)
|
|
return GE_COMP_NEVER;
|
|
if (ref == 0xFF && func == GE_COMP_LESS)
|
|
return GE_COMP_NEVER;
|
|
if (ref == 0 && func == GE_COMP_LEQUAL)
|
|
return GE_COMP_ALWAYS;
|
|
if (ref == 0xFF && func == GE_COMP_GEQUAL)
|
|
return GE_COMP_ALWAYS;
|
|
return func;
|
|
}
|
|
|
|
static inline PixelBlendFactor OptimizeAlphaFactor(uint32_t color) {
|
|
if (color == 0x00000000)
|
|
return PixelBlendFactor::ZERO;
|
|
if (color == 0x00FFFFFF)
|
|
return PixelBlendFactor::ONE;
|
|
return PixelBlendFactor::FIX;
|
|
}
|
|
|
|
void ComputePixelFuncID(PixelFuncID *id) {
|
|
id->fullKey = 0;
|
|
|
|
// TODO: Could this be minz > 0x0000 || maxz < 0xFFFF? Maybe unsafe, depending on verts...
|
|
id->applyDepthRange = !gstate.isModeThrough();
|
|
// Dither happens even in clear mode.
|
|
id->dithering = gstate.isDitherEnabled() && !g_Config.bSoftwareDisableDithering;
|
|
id->fbFormat = gstate.FrameBufFormat();
|
|
id->useStandardStride = gstate.FrameBufStride() == 512;
|
|
id->applyColorWriteMask = gstate.getColorMask() != 0;
|
|
|
|
id->clearMode = gstate.isModeClear();
|
|
if (id->clearMode) {
|
|
id->colorTest = gstate.isClearModeColorMask();
|
|
id->stencilTest = gstate.isClearModeAlphaMask() && gstate.FrameBufFormat() != GE_FORMAT_565;
|
|
id->depthWrite = gstate.isClearModeDepthMask();
|
|
id->depthTestFunc = GE_COMP_ALWAYS;
|
|
id->alphaTestFunc = GE_COMP_ALWAYS;
|
|
} else {
|
|
id->colorTest = gstate.isColorTestEnabled() && gstate.getColorTestFunction() != GE_COMP_ALWAYS;
|
|
if (gstate.isStencilTestEnabled() && gstate.getStencilTestFunction() == GE_COMP_ALWAYS) {
|
|
// If stencil always passes, force off when we won't write any stencil bits.
|
|
bool stencilWrite = (gstate.pmska & 0xFF) != 0xFF && gstate.FrameBufFormat() != GE_FORMAT_565;
|
|
if (gstate.isDepthTestEnabled() && gstate.getDepthTestFunction() != GE_COMP_ALWAYS)
|
|
id->stencilTest = stencilWrite && (gstate.getStencilOpZPass() != GE_STENCILOP_KEEP || gstate.getStencilOpZFail() != GE_STENCILOP_KEEP);
|
|
else
|
|
id->stencilTest = stencilWrite && gstate.getStencilOpZPass() != GE_STENCILOP_KEEP;
|
|
} else {
|
|
id->stencilTest = gstate.isStencilTestEnabled();
|
|
}
|
|
id->depthWrite = gstate.isDepthTestEnabled() && gstate.isDepthWriteEnabled();
|
|
id->depthTestFunc = gstate.isDepthTestEnabled() ? gstate.getDepthTestFunction() : GE_COMP_ALWAYS;
|
|
|
|
if (id->stencilTest) {
|
|
id->stencilTestRef = gstate.getStencilTestRef() & gstate.getStencilTestMask();
|
|
id->stencilTestFunc = OptimizeStencilRefCompare(gstate.getStencilTestFunction(), id->stencilTestRef);
|
|
id->hasStencilTestMask = gstate.getStencilTestMask() != 0xFF && gstate.FrameBufFormat() != GE_FORMAT_565;
|
|
|
|
// Stencil can't be written on 565, and any invalid op acts like KEEP, which is 0.
|
|
if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpSFail() <= GE_STENCILOP_DECR)
|
|
id->sFail = gstate.getStencilOpSFail();
|
|
if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpZFail() <= GE_STENCILOP_DECR)
|
|
id->zFail = gstate.isDepthTestEnabled() ? gstate.getStencilOpZFail() : GE_STENCILOP_KEEP;
|
|
if (gstate.FrameBufFormat() != GE_FORMAT_565 && gstate.getStencilOpZPass() <= GE_STENCILOP_DECR)
|
|
id->zPass = gstate.getStencilOpZPass();
|
|
|
|
// Normalize REPLACE 00 to ZERO, especially if using a mask.
|
|
if (gstate.getStencilTestRef() == 0) {
|
|
if (id->SFail() == GE_STENCILOP_REPLACE)
|
|
id->sFail = GE_STENCILOP_ZERO;
|
|
if (id->ZFail() == GE_STENCILOP_REPLACE)
|
|
id->zFail = GE_STENCILOP_ZERO;
|
|
if (id->ZPass() == GE_STENCILOP_REPLACE)
|
|
id->zPass = GE_STENCILOP_ZERO;
|
|
}
|
|
|
|
// For 5551, DECR is also the same as ZERO.
|
|
if (id->FBFormat() == GE_FORMAT_5551) {
|
|
if (id->SFail() == GE_STENCILOP_DECR)
|
|
id->sFail = GE_STENCILOP_ZERO;
|
|
if (id->ZFail() == GE_STENCILOP_DECR)
|
|
id->zFail = GE_STENCILOP_ZERO;
|
|
if (id->ZPass() == GE_STENCILOP_DECR)
|
|
id->zPass = GE_STENCILOP_ZERO;
|
|
}
|
|
|
|
// And same for sFail if there's no stencil test. Prefer KEEP, though.
|
|
if (id->StencilTestFunc() == GE_COMP_ALWAYS) {
|
|
if (id->DepthTestFunc() == GE_COMP_ALWAYS)
|
|
id->zFail = GE_STENCILOP_KEEP;
|
|
id->sFail = GE_STENCILOP_KEEP;
|
|
// Always doesn't need a mask.
|
|
id->stencilTestRef = gstate.getStencilTestRef();
|
|
id->hasStencilTestMask = false;
|
|
|
|
// Turn off stencil testing if it's doing nothing.
|
|
if (id->SFail() == GE_STENCILOP_KEEP && id->ZFail() == GE_STENCILOP_KEEP && id->ZPass() == GE_STENCILOP_KEEP) {
|
|
id->stencilTest = false;
|
|
id->stencilTestFunc = 0;
|
|
id->stencilTestRef = 0;
|
|
}
|
|
} else if (id->DepthTestFunc() == GE_COMP_ALWAYS) {
|
|
// Always treat zPass/zFail the same if there's no depth test.
|
|
id->zFail = id->zPass;
|
|
}
|
|
}
|
|
|
|
id->alphaTestFunc = gstate.isAlphaTestEnabled() ? gstate.getAlphaTestFunction() : GE_COMP_ALWAYS;
|
|
if (id->AlphaTestFunc() != GE_COMP_ALWAYS) {
|
|
id->alphaTestRef = gstate.getAlphaTestRef() & gstate.getAlphaTestMask();
|
|
id->hasAlphaTestMask = gstate.getAlphaTestMask() != 0xFF;
|
|
// Try to pick a more optimal variant.
|
|
id->alphaTestFunc = OptimizeRefByteCompare(id->AlphaTestFunc(), id->alphaTestRef);
|
|
if (id->alphaTestFunc == GE_COMP_ALWAYS) {
|
|
id->alphaTestRef = 0;
|
|
id->hasAlphaTestMask = false;
|
|
}
|
|
}
|
|
|
|
// If invalid (6 or 7), doesn't do any blending, so force off.
|
|
id->alphaBlend = gstate.isAlphaBlendEnabled() && gstate.getBlendEq() <= 5;
|
|
// Force it off if the factors are constant and don't blend. Some games use this...
|
|
if (id->alphaBlend && gstate.getBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {
|
|
bool srcFixedOne = gstate.getBlendFuncA() == GE_SRCBLEND_FIXA && gstate.getFixA() == 0x00FFFFFF;
|
|
bool dstFixedZero = gstate.getBlendFuncB() == GE_DSTBLEND_FIXB && gstate.getFixB() == 0x00000000;
|
|
if (srcFixedOne && dstFixedZero)
|
|
id->alphaBlend = false;
|
|
}
|
|
if (id->alphaBlend)
|
|
id->alphaBlendEq = gstate.getBlendEq();
|
|
if (id->alphaBlend && id->alphaBlendEq <= GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE) {
|
|
id->alphaBlendSrc = gstate.getBlendFuncA();
|
|
id->alphaBlendDst = gstate.getBlendFuncB();
|
|
// Special values.
|
|
if (id->alphaBlendSrc >= GE_SRCBLEND_FIXA)
|
|
id->alphaBlendSrc = (uint8_t)OptimizeAlphaFactor(gstate.getFixA());
|
|
if (id->alphaBlendDst >= GE_DSTBLEND_FIXB)
|
|
id->alphaBlendDst = (uint8_t)OptimizeAlphaFactor(gstate.getFixB());
|
|
}
|
|
|
|
id->applyLogicOp = gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY;
|
|
id->applyFog = gstate.isFogEnabled() && !gstate.isModeThrough();
|
|
|
|
if (id->colorTest && gstate.getColorTestFunction() == GE_COMP_NOTEQUAL && gstate.getColorTestRef() == 0 && gstate.getColorTestMask() == 0xFFFFFF) {
|
|
if (!id->depthWrite && !id->stencilTest && id->alphaBlend && id->AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {
|
|
// Rejecting black then only skips adding nothing to the destination, unless dithering, fog
|
|
// or a logic op would still change it: Ridge Racer 2 (UCES00422) adds a glow with dithering,
|
|
// and its black pixels must not get the dither.
|
|
if (id->AlphaBlendDst() == PixelBlendFactor::ONE && !id->dithering && !id->applyFog && !id->applyLogicOp)
|
|
id->colorTest = false;
|
|
}
|
|
}
|
|
|
|
id->earlyZChecks = id->DepthTestFunc() != GE_COMP_ALWAYS;
|
|
if (id->stencilTest && id->earlyZChecks) {
|
|
// Can't do them early if stencil might need to write.
|
|
if (id->SFail() != GE_STENCILOP_KEEP || id->ZFail() != GE_STENCILOP_KEEP)
|
|
id->earlyZChecks = false;
|
|
}
|
|
}
|
|
|
|
if (id->depthTestFunc != GE_COMP_ALWAYS || id->depthWrite)
|
|
id->depthLayout = DepthTranslationIndex(depthbuf.translation);
|
|
if (id->useStandardStride && (id->depthTestFunc != GE_COMP_ALWAYS || id->depthWrite))
|
|
id->useStandardStride = gstate.DepthBufStride() == 512;
|
|
|
|
// Cache some values for later convenience.
|
|
if (id->dithering) {
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x)
|
|
id->cached.ditherMatrix[y * 4 + x] = gstate.getDitherValue(x, y);
|
|
}
|
|
}
|
|
if (id->applyColorWriteMask) {
|
|
uint32_t mask = gstate.getColorMask();
|
|
// This flag means stencil clear or stencil test, basically whether writing to stencil.
|
|
if (!id->stencilTest)
|
|
mask |= 0xFF000000;
|
|
|
|
switch (id->fbFormat) {
|
|
case GE_FORMAT_565:
|
|
id->cached.colorWriteMask = RGBA8888ToRGB565(mask);
|
|
break;
|
|
|
|
case GE_FORMAT_5551:
|
|
id->cached.colorWriteMask = RGBA8888ToRGBA5551(mask);
|
|
break;
|
|
|
|
case GE_FORMAT_4444:
|
|
id->cached.colorWriteMask = RGBA8888ToRGBA4444(mask);
|
|
break;
|
|
|
|
case GE_FORMAT_8888:
|
|
id->cached.colorWriteMask = mask;
|
|
break;
|
|
}
|
|
}
|
|
if (id->applyFog) {
|
|
id->cached.fogColor = gstate.fogcolor & 0x00FFFFFF;
|
|
}
|
|
if (id->applyLogicOp)
|
|
id->cached.logicOp = gstate.getLogicOp();
|
|
id->cached.minz = gstate.getDepthRangeMin();
|
|
id->cached.maxz = gstate.getDepthRangeMax();
|
|
id->cached.framebufStride = gstate.FrameBufStride();
|
|
id->cached.depthbufStride = gstate.DepthBufStride();
|
|
|
|
if (id->hasStencilTestMask) {
|
|
// Without the mask applied, unlike the one in the key.
|
|
id->cached.stencilRef = gstate.getStencilTestRef();
|
|
id->cached.stencilTestMask = gstate.getStencilTestMask();
|
|
}
|
|
if (id->hasAlphaTestMask)
|
|
id->cached.alphaTestMask = gstate.getAlphaTestMask();
|
|
if (!id->clearMode && id->colorTest) {
|
|
id->cached.colorTestFunc = gstate.getColorTestFunction();
|
|
id->cached.colorTestMask = gstate.getColorTestMask();
|
|
id->cached.colorTestRef = gstate.getColorTestRef() & id->cached.colorTestMask;
|
|
}
|
|
if (id->alphaBlendSrc == GE_SRCBLEND_FIXA)
|
|
id->cached.alphaBlendSrc = gstate.getFixA();
|
|
if (id->alphaBlendDst == GE_DSTBLEND_FIXB)
|
|
id->cached.alphaBlendDst = gstate.getFixB();
|
|
}
|
|
|
|
void ComputeSamplerID(SamplerID *id_out) {
|
|
SamplerID id{};
|
|
|
|
id.useStandardBufw = true;
|
|
id.overReadSafe = true;
|
|
int maxLevel = gstate.isMipmapEnabled() ? gstate.getTextureMaxLevel() : 0;
|
|
GETextureFormat fmt = gstate.getTextureFormat();
|
|
for (int i = 0; i <= maxLevel; ++i) {
|
|
uint32_t addr = gstate.getTextureAddress(i);
|
|
if (!Memory::IsValidAddress(addr))
|
|
id.hasInvalidPtr = true;
|
|
|
|
int bufw = GetTextureBufw(i, addr, fmt);
|
|
int bitspp = textureBitsPerPixel[fmt];
|
|
// We use a 16 byte minimum for all small bufws, so allow those as standard.
|
|
int w = gstate.getTextureWidth(i);
|
|
if (bitspp == 0 || std::max(w, 128 / bitspp) != bufw)
|
|
id.useStandardBufw = false;
|
|
// TODO: Verify 16 bit bufw align handling in DXT.
|
|
if (fmt >= GE_TFMT_DXT1 && w != bufw)
|
|
id.useStandardBufw = false;
|
|
|
|
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.cached.sizes[i].w = w;
|
|
id.cached.sizes[i].h = h;
|
|
}
|
|
// TODO: What specifically happens if these are above 11?
|
|
id.width0Shift = gstate.texsize[0] & 0xF;
|
|
id.height0Shift = (gstate.texsize[0] >> 8) & 0xF;
|
|
id.hasAnyMips = maxLevel != 0;
|
|
|
|
id.texfmt = fmt;
|
|
id.swizzle = gstate.isTextureSwizzled();
|
|
// With separate CLUTs, level n puts n above the index bits, wrapped to the CLUT: CLUT4 adds n * 16,
|
|
// CLUT8 with a 16-bit palette (512 entries) adds (n & 1) * 256, and the rest wrap back to 0 (gpu/probe exp90).
|
|
const bool clutPerLevel = fmt == GE_TFMT_CLUT4 || (fmt == GE_TFMT_CLUT8 && gstate.getClutPaletteFormat() != GE_CMODE_32BIT_ABGR8888);
|
|
id.useSharedClut = !clutPerLevel || maxLevel == 0 || !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.cached.clutFormat = gstate.clutformat;
|
|
}
|
|
|
|
id.clampS = gstate.isTexCoordClampedS();
|
|
id.clampT = gstate.isTexCoordClampedT();
|
|
|
|
id.useTextureAlpha = gstate.isTextureAlphaUsed();
|
|
id.useColorDoubling = gstate.isColorDoublingEnabled();
|
|
id.texFunc = gstate.getTextureFunction();
|
|
if (id.texFunc > GE_TEXFUNC_ADD)
|
|
id.texFunc = GE_TEXFUNC_ADD;
|
|
|
|
if (id.texFunc == GE_TEXFUNC_BLEND)
|
|
id.cached.texBlendColor = gstate.getTextureEnvColRGB();
|
|
|
|
*id_out = id;
|
|
}
|