Add the necessary logic to use in-shader CLUT lookups for regular textures

This commit is contained in:
Henrik Rydgård
2026-06-11 12:42:16 +02:00
parent 4f925fa5f2
commit 87cc608df1
7 changed files with 52 additions and 15 deletions
+2 -1
View File
@@ -685,7 +685,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
WRITE(p, " vec2 tsize = vec2(textureSize(tex, 0).xy);\n");
WRITE(p, " vec2 fraction;\n");
WRITE(p, " bool bilinear = (u_depal_mask_shift_off_fmt >> 0x2Fu) != 0x0u;\n");
WRITE(p, " bool bilinear = (u_depal_mask_shift_off_fmt >> 0x1Fu) != 0x0u;\n");
WRITE(p, " if (bilinear) {\n");
WRITE(p, " uv_round = uv * tsize - vec2(0.5, 0.5);\n");
WRITE(p, " fraction = fract(uv_round);\n");
@@ -694,6 +694,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " uv_round = uv;\n");
WRITE(p, " }\n");
// TODO: Use fetch here when possible.
p.C(" highp vec4 t = ").SampleTexture2D("tex", "uv_round").C(";\n");
p.C(" highp vec4 t1 = ").SampleTexture2DOffset("tex", "uv_round", 1, 0).C(";\n");
p.C(" highp vec4 t2 = ").SampleTexture2DOffset("tex", "uv_round", 0, 1).C(";\n");
+1 -1
View File
@@ -944,7 +944,7 @@ Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat
Draw2DPipeline *pipeline = reinterpretFromTo_[(int)from][(int)to];
if (!pipeline) {
pipeline = draw2D_.Create2DPipeline([=](ShaderWriter &shaderWriter) -> Draw2DPipelineInfo {
pipeline = draw2D_.Create2DPipeline([from, to](ShaderWriter &shaderWriter) -> Draw2DPipelineInfo {
return GenerateReinterpretFragmentShader(shaderWriter, from, to);
});
reinterpretFromTo_[(int)from][(int)to] = pipeline;
+17 -2
View File
@@ -193,9 +193,24 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBuffe
int indexOffset = gstate.getClutIndexStartPos() >> 4;
int format = gstate_c.depalTextureFormat;
uint32_t val = BytesToUint32(indexMask, indexShift, indexOffset, format);
// Poke in a bilinear filter flag in the top bit.
if (gstate.isMagnifyFilteringEnabled())
// NOTE: This must follow similar logic to TextureCacheCommon::GetSamplingParams -
// maybe we can share it somehow.
// TOOD: Handle replaced textures.
bool bilinear = gstate.isMagnifyFilteringEnabled() && !gstate_c.pixelMapped;
switch (g_Config.iTexFiltering) {
case TEX_FILTER_FORCE_NEAREST:
bilinear = false;
break;
case TEX_FILTER_FORCE_LINEAR:
bilinear = true;
break;
default:
break;
}
if (bilinear) {
// Poke in a bilinear filter flag in the top bit.
val |= 0x80000000;
}
ub->depal_mask_shift_off_fmt = val;
}
}
+29 -9
View File
@@ -669,7 +669,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
if (hasClutGPU) {
WARN_LOG_N_TIMES(clutUseRender, 5, Log::G3D, "Using texture with dynamic CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
entry->status |= TexStatus::CLUT_GPU;
entry->status |= TexStatus::CLUT_GPU | TexStatus::CLUT8_INDEXED;
}
if (hasClut && clutRenderAddress_ == 0xFFFFFFFF) {
@@ -691,6 +691,8 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
}
nextNeedsChange_ = false;
} else {
// We had an entry already, modify it.
}
// We have to decode it, let's setup the cache entry first.
@@ -704,6 +706,14 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
entry->cluthash = cluthash;
/*
if (entry->maxLevel == 0 && (entry->format == GE_TFMT_CLUT8 || entry->format == GE_TFMT_CLUT4) &&
!(entry->status & TexStatus::TO_REPLACE) && !(entry->status & TexStatus::TO_SCALE) && !(entry->status & TexStatus::RELIABLE)) {
// Experiment with CLUT8 decoding for regular textures.
entry->status |= TexStatus::CLUT8_INDEXED;
}
*/
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
gstate_c.SetTextureIsVideo((entry->status & TexStatus::VIDEO) != 0);
@@ -720,7 +730,6 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
nextTexture_ = entry;
nextFramebufferTexture_ = nullptr;
nextNeedsRehash_ = true;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_ = true;
return entry;
}
@@ -2216,16 +2225,16 @@ void TextureCacheCommon::ApplyTexture(bool doBind, bool flatZ) {
}
gstate_c.SetTextureIsVideo((entry->status & TexStatus::VIDEO) != 0);
entry->lastFrame = gpuStats.totals.numFlips;
if (entry->status & TexStatus::CLUT_GPU) {
_dbg_assert_(entry->status & TexStatus::CLUT8_INDEXED);
// Special process.
ApplyTextureDepal(entry);
entry->lastFrame = gpuStats.totals.numFlips;
ApplyTextureDepalFramebufferCLUT(entry);
gstate_c.SetTextureSolidAlpha(false);
gstate_c.SetTextureIs3D(false);
gstate_c.SetTextureIsArray(false);
gstate_c.SetTextureIsBGRA(false);
} else {
entry->lastFrame = gpuStats.totals.numFlips;
if (doBind) {
BindTexture(entry, flatZ);
}
@@ -2233,7 +2242,15 @@ void TextureCacheCommon::ApplyTexture(bool doBind, bool flatZ) {
gstate_c.SetTextureIs3D((entry->status & TexStatus::IS_3D) != 0);
gstate_c.SetTextureIsArray(false);
gstate_c.SetTextureIsBGRA((entry->status & TexStatus::BGRA) != 0);
gstate_c.SetShaderDepal(ShaderDepalMode::OFF);
if (entry->status & TexStatus::CLUT8_INDEXED) {
bool smoothedDepal = false;
u32 depthUpperBits = 0;
ClutTexture clutTexture = clutTextureCache_.GetClutTexture(gstate.getClutPaletteFormat(), clutHash_, clutBuf_);
BindAsClutTexture(clutTexture.texture, false);
gstate_c.SetShaderDepal(ShaderDepalMode::NORMAL, GE_FORMAT_CLUT8);
} else {
gstate_c.SetShaderDepal(ShaderDepalMode::OFF);
}
}
}
@@ -2474,7 +2491,7 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer
}
// Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format.
void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) {
void TextureCacheCommon::ApplyTextureDepalFramebufferCLUT(TexCacheEntry *entry) {
uint32_t clutMode = gstate.clutformat & 0xFFFFFF;
switch (entry->format) {
@@ -2881,7 +2898,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
}
bool canReplace = !isPPGETexture;
if (entry->status & TexStatus::CLUT_GPU) {
if (entry->status & TexStatus::CLUT8_INDEXED) {
_dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8);
plan.decodeToClut8 = true;
// We only support 1 mip level when doing CLUT on GPU for now.
@@ -3001,7 +3018,7 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, s
if (!gstate_c.Use(GPU_USE_16BIT_FORMATS) || dstFmt == Draw::DataFormat::R8G8B8A8_UNORM) {
texDecFlags |= TexDecodeFlags::EXPAND32;
}
if (entry.status & (TexStatus::CLUT_GPU | TexStatus::CLUT8_INDEXED)) {
if (entry.status & TexStatus::CLUT8_INDEXED) {
_dbg_assert_(entry.format == GE_TFMT_CLUT4 || entry.format == GE_TFMT_CLUT8);
texDecFlags |= TexDecodeFlags::TO_CLUT8;
}
@@ -3080,6 +3097,9 @@ std::string TexStatusToString(TexStatus status) {
if (status & TexStatus::CLUT_GPU) {
result += "CLUT_GPU ";
}
if (status & TexStatus::CLUT8_INDEXED) {
result += "CLUT8_INDEXED ";
}
if (status & TexStatus::FORCE_REBUILD) {
result += "FORCE_REBUILD ";
}
+1 -1
View File
@@ -404,7 +404,7 @@ protected:
void Decimate(TexCacheEntry *exceptThisOne, bool forcePressure); // forcePressure defaults to false.
void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel);
void ApplyTextureDepal(TexCacheEntry *entry);
void ApplyTextureDepalFramebufferCLUT(TexCacheEntry *entry);
void HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
virtual void BuildTexture(TexCacheEntry *const entry) = 0;
+1
View File
@@ -287,6 +287,7 @@ const char *GeBufferFormatToString(GEBufferFormat fmt) {
case GE_FORMAT_565: return "565";
case GE_FORMAT_8888: return "8888";
case GE_FORMAT_DEPTH16: return "DEPTH16";
case GE_FORMAT_CLUT8: return "CLUT8";
default: return "N/A";
}
}
+1 -1
View File
@@ -958,7 +958,7 @@ void TextureCacheVulkan::LoadVulkanTextureLevel(TexCacheEntry &entry, uint8_t *w
if (!gstate_c.Use(GPU_USE_16BIT_FORMATS) || scaleFactor > 1 || dstFmt == VULKAN_8888_FORMAT) {
texDecFlags |= TexDecodeFlags::EXPAND32;
}
if (entry.status & TexStatus::CLUT_GPU) {
if (entry.status & TexStatus::CLUT8_INDEXED) {
texDecFlags |= TexDecodeFlags::TO_CLUT8;
}