Files
ppsspp/GPU/GLES/TextureCacheGLES.cpp

438 lines
15 KiB
C++
Raw Normal View History

2012-11-01 16:19:01 +01:00
// Copyright (c) 2012- 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
2012-11-04 23:58:25 +01:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 16:19:01 +01:00
// 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 <algorithm>
2013-12-29 15:55:09 -08:00
#include <cstring>
2012-11-01 16:19:01 +01:00
#include "ext/xxhash.h"
#include "Common/Common.h"
#include "Common/Data/Convert/ColorConv.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Math/math_util.h"
2020-10-04 10:04:01 +02:00
#include "Common/Profiler/Profiler.h"
#include "Common/System/OSD.h"
#include "Common/GPU/OpenGL/GLRenderManager.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "GPU/ge_constants.h"
#include "GPU/GPUState.h"
#include "GPU/GLES/TextureCacheGLES.h"
#include "GPU/GLES/FramebufferManagerGLES.h"
#include "GPU/Common/FragmentShaderGenerator.h"
#include "GPU/Common/TextureShaderCommon.h"
#include "GPU/GLES/ShaderManagerGLES.h"
#include "GPU/GLES/DrawEngineGLES.h"
#include "GPU/Common/TextureDecoder.h"
2012-11-01 16:19:01 +01:00
2013-05-05 19:58:59 -07:00
#ifdef _M_SSE
#include <emmintrin.h>
2013-05-05 19:58:59 -07:00
#endif
TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw, Draw2D *draw2D)
: TextureCacheCommon(draw, draw2D) {
2017-11-19 17:37:56 +01:00
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
nextTexture_ = nullptr;
}
TextureCacheGLES::~TextureCacheGLES() {
2015-01-24 20:36:01 -08:00
Clear(true);
}
2017-02-08 15:58:46 +01:00
void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager) {
framebufferManagerGL_ = fbManager;
framebufferManager_ = fbManager;
}
void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry, bool delete_them) {
if (delete_them) {
2017-11-19 17:37:56 +01:00
if (entry->textureName) {
render_->DeleteTexture(entry->textureName);
}
}
2017-11-19 17:37:56 +01:00
entry->textureName = nullptr;
2017-02-19 22:31:07 +01:00
}
void TextureCacheGLES::Clear(bool delete_them) {
2017-02-19 22:31:07 +01:00
TextureCacheCommon::Clear(delete_them);
2012-11-01 16:19:01 +01:00
}
Draw::DataFormat getClutDestFormat(GEPaletteFormat format) {
switch (format) {
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_ABGR4444:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_ABGR5551:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_BGR5650:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R5G6B5_UNORM_PACK16;
2012-11-08 23:26:30 +01:00
case GE_CMODE_32BIT_ABGR8888:
return Draw::DataFormat::R8G8B8A8_UNORM;
2012-11-08 23:26:30 +01:00
}
2022-08-05 10:00:27 +02:00
return Draw::DataFormat::UNDEFINED;
2012-11-08 23:26:30 +01:00
}
2013-02-08 00:04:34 +01:00
static const GLuint MinFiltGL[8] = {
GL_NEAREST,
GL_LINEAR,
GL_NEAREST,
GL_LINEAR,
GL_NEAREST_MIPMAP_NEAREST,
GL_LINEAR_MIPMAP_NEAREST,
GL_NEAREST_MIPMAP_LINEAR,
GL_LINEAR_MIPMAP_LINEAR,
};
2013-02-08 00:04:34 +01:00
static const GLuint MagFiltGL[2] = {
GL_NEAREST,
GL_LINEAR
};
void TextureCacheGLES::ApplySamplingParams(const SamplerCacheKey &key) {
2022-10-17 08:30:27 +02:00
if (gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {
float minLod = (float)key.minLevel / 256.0f;
float maxLod = (float)key.maxLevel / 256.0f;
float lodBias = (float)key.lodBias / 256.0f;
render_->SetTextureLod(0, minLod, maxLod, lodBias);
}
float aniso = 0.0f;
2020-09-17 20:47:22 +02:00
int minKey = ((int)key.mipEnable << 2) | ((int)key.mipFilt << 1) | ((int)key.minFilt);
render_->SetTextureSampler(0,
key.sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, key.tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT,
2020-09-17 20:47:22 +02:00
key.magFilt ? GL_LINEAR : GL_NEAREST, MinFiltGL[minKey], aniso);
2012-11-01 16:19:01 +01:00
}
static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dstFmt, int numPixels) {
const u32 *src = (const u32 *)srcBuf;
u32 *dst = (u32 *)dstBuf;
switch (dstFmt) {
2019-10-24 23:01:45 +02:00
case Draw::DataFormat::R4G4B4A4_UNORM_PACK16:
ConvertRGBA4444ToABGR4444((u16 *)dst, (const u16 *)src, numPixels);
break;
// Final Fantasy 2 uses this heavily in animated textures.
2019-10-24 23:01:45 +02:00
case Draw::DataFormat::R5G5B5A1_UNORM_PACK16:
ConvertRGBA5551ToABGR1555((u16 *)dst, (const u16 *)src, numPixels);
break;
2019-10-24 23:01:45 +02:00
case Draw::DataFormat::R5G6B5_UNORM_PACK16:
ConvertRGB565ToBGR565((u16 *)dst, (const u16 *)src, numPixels);
break;
default:
// No need to convert RGBA8888, right order already
if (dst != src)
memcpy(dst, src, numPixels * sizeof(u32));
break;
}
}
void TextureCacheGLES::StartFrame() {
TextureCacheCommon::StartFrame();
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) {
lowMemoryMode_ = true;
decimationCounter_ = 0;
auto err = GetI18NCategory(I18NCat::ERRORS);
if (standardScaleFactor_ > 1) {
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f);
} else {
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f);
}
}
}
void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16));
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
// If not, we're going to hash random data, which hopefully doesn't cause a performance issue.
//
// TODO: Actually, this seems like a hack. The game can upload part of a CLUT and reference other data.
// clutTotalBytes_ is the last amount uploaded. We should hash clutMaxBytes_, but this will often hash
// unrelated old entries for small palettes.
// Adding clutBaseBytes may just be mitigating this for some usage patterns.
const u32 clutExtendedBytes = std::min(clutTotalBytes_ + clutBaseBytes, clutMaxBytes_);
if (replacer_.Enabled())
clutHash_ = XXH32((const char *)clutBufRaw_, clutExtendedBytes, 0xC0108888);
else
clutHash_ = XXH3_64bits((const char *)clutBufRaw_, clutExtendedBytes) & 0xFFFFFFFF;
// Avoid a copy when we don't need to convert colors.
if (clutFormat != GE_CMODE_32BIT_ABGR8888) {
const int numColors = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutMaxBytes_ / sizeof(u32)) : (clutMaxBytes_ / sizeof(u16));
ConvertColors(clutBufConverted_, clutBufRaw_, getClutDestFormat(clutFormat), numColors);
clutBuf_ = clutBufConverted_;
} else {
clutBuf_ = clutBufRaw_;
}
// Special optimization: fonts typically draw clut4 with just alpha values in a single color.
clutAlphaLinear_ = false;
clutAlphaLinearColor_ = 0;
if (clutFormat == GE_CMODE_16BIT_ABGR4444 && clutIndexIsSimple) {
const u16_le *clut = GetCurrentClut<u16_le>();
clutAlphaLinear_ = true;
clutAlphaLinearColor_ = clut[15] & 0xFFF0;
for (int i = 0; i < 16; ++i) {
2016-01-01 09:02:16 -08:00
u16 step = clutAlphaLinearColor_ | i;
if (clut[i] != step) {
clutAlphaLinear_ = false;
break;
}
}
}
clutLastFormat_ = gstate.clutformat;
}
2017-02-19 23:07:00 +01:00
void TextureCacheGLES::BindTexture(TexCacheEntry *entry) {
if (!entry) {
render_->BindTexture(0, nullptr);
lastBoundTexture = nullptr;
return;
}
2017-02-19 23:07:00 +01:00
if (entry->textureName != lastBoundTexture) {
2017-11-19 17:37:56 +01:00
render_->BindTexture(0, entry->textureName);
2017-02-19 23:07:00 +01:00
lastBoundTexture = entry->textureName;
}
int maxLevel = (entry->status & TexCacheEntry::STATUS_NO_MIPS) ? 0 : entry->maxLevel;
SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry);
ApplySamplingParams(samplerKey);
gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF);
2017-02-19 23:07:00 +01:00
}
2017-02-08 15:43:53 +01:00
void TextureCacheGLES::Unbind() {
render_->BindTexture(TEX_SLOT_PSP_TEXTURE, nullptr);
2023-02-24 21:53:12 +01:00
ForgetLastTexture();
2013-09-01 11:40:35 -07:00
}
void TextureCacheGLES::BindAsClutTexture(Draw::Texture *tex, bool smooth) {
2022-08-05 11:44:09 +02:00
GLRTexture *glrTex = (GLRTexture *)draw_->GetNativeObject(Draw::NativeObject::TEXTURE_VIEW, tex);
2022-08-05 10:00:27 +02:00
render_->BindTexture(TEX_SLOT_CLUT, glrTex);
render_->SetTextureSampler(TEX_SLOT_CLUT, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, smooth ? GL_LINEAR : GL_NEAREST, smooth ? GL_LINEAR : GL_NEAREST, 0.0f);
2022-08-05 10:00:27 +02:00
}
void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
BuildTexturePlan plan;
if (!PrepareBuildTexture(plan, entry)) {
// We're screwed?
return;
}
_assert_(!entry->textureName);
// GLES2 doesn't have support for a "Max lod" which is critical as PSP games often
// don't specify mips all the way down. As a result, we either need to manually generate
// the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not
// be as good quality as the game's own (might even be better in some cases though).
int tw = plan.createW;
int th = plan.createH;
Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());
2023-03-18 11:45:26 +01:00
if (plan.doReplace) {
2023-03-10 14:16:14 +01:00
plan.replaced->GetSize(plan.baseLevelSrc, &tw, &th);
dstFmt = plan.replaced->Format();
} else if (plan.scaleFactor > 1 || plan.saveTexture) {
dstFmt = Draw::DataFormat::R8G8B8A8_UNORM;
2022-09-14 23:35:15 +02:00
} else if (plan.decodeToClut8) {
dstFmt = Draw::DataFormat::R8_UNORM;
}
if (plan.depth == 1) {
entry->textureName = render_->CreateTexture(GL_TEXTURE_2D, tw, th, 1, plan.levelsToCreate);
} else {
entry->textureName = render_->CreateTexture(GL_TEXTURE_3D, tw, th, plan.depth, 1);
}
// Apply some additional compatibility checks.
if (plan.levelsToLoad > 1) {
// Avoid PowerVR driver bug
if (plan.w > 1 && plan.h > 1 && !(plan.h > plan.w && draw_->GetBugs().Has(Draw::Bugs::PVR_GENMIPMAP_HEIGHT_GREATER))) { // Really! only seems to fail if height > width
// It's ok to generate mipmaps beyond the loaded levels.
} else {
plan.levelsToCreate = plan.levelsToLoad;
}
}
2022-10-17 08:30:27 +02:00
if (!gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {
// If the mip chain is not full..
if (plan.levelsToCreate != plan.maxPossibleLevels) {
// We need to avoid creating mips at all, or generate them all - can't be incomplete
// on this hardware (strict OpenGL rules).
plan.levelsToCreate = 1;
plan.levelsToLoad = 1;
entry->status |= TexCacheEntry::STATUS_NO_MIPS;
}
}
if (plan.depth == 1) {
for (int i = 0; i < plan.levelsToLoad; i++) {
int srcLevel = i == 0 ? plan.baseLevelSrc : i;
int mipWidth;
int mipHeight;
plan.GetMipSize(i, &mipWidth, &mipHeight);
u8 *data = nullptr;
int stride = 0;
int dataSize;
bool bc = false;
2023-03-18 11:45:26 +01:00
if (plan.doReplace) {
int blockSize = 0;
if (Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), &blockSize)) {
stride = mipWidth * 4;
dataSize = plan.replaced->GetLevelDataSizeAfterCopy(i);
bc = true;
} else {
int bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format());
stride = mipWidth * bpp;
dataSize = stride * mipHeight;
}
} else {
int bpp = 0;
if (plan.scaleFactor > 1) {
bpp = 4;
} else {
2022-09-14 23:35:15 +02:00
bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
}
stride = mipWidth * bpp;
dataSize = stride * mipHeight;
}
data = (u8 *)AllocateAlignedMemory(dataSize, 16);
if (!data) {
ERROR_LOG(G3D, "Ran out of RAM trying to allocate a temporary texture upload buffer (%dx%d)", mipWidth, mipHeight);
return;
}
LoadTextureLevel(*entry, data, dataSize, stride, plan, srcLevel, dstFmt, TexDecodeFlags::REVERSE_COLORS);
// NOTE: TextureImage takes ownership of data, so we don't free it afterwards.
render_->TextureImage(entry->textureName, i, mipWidth, mipHeight, 1, dstFmt, data, GLRAllocType::ALIGNED);
}
bool genMips = plan.levelsToCreate > plan.levelsToLoad;
render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips);
} else {
2022-09-14 23:35:15 +02:00
int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
int stride = bpp * (plan.w * plan.scaleFactor);
int levelStride = stride * (plan.h * plan.scaleFactor);
size_t dataSize = levelStride * plan.depth;
u8 *data = (u8 *)AllocateAlignedMemory(dataSize, 16);
memset(data, 0, levelStride * plan.depth);
u8 *p = data;
for (int i = 0; i < plan.depth; i++) {
LoadTextureLevel(*entry, p, dataSize, stride, plan, i, dstFmt, TexDecodeFlags::REVERSE_COLORS);
p += levelStride;
}
render_->TextureImage(entry->textureName, 0, plan.w * plan.scaleFactor, plan.h * plan.scaleFactor, plan.depth, dstFmt, data, GLRAllocType::ALIGNED);
// Signal that we support depth textures so use it as one.
entry->status |= TexCacheEntry::STATUS_3D;
render_->FinalizeTexture(entry->textureName, 1, false);
}
2020-12-10 10:28:48 +01:00
2023-03-18 11:45:26 +01:00
if (plan.doReplace) {
entry->SetAlphaStatus(TexCacheEntry::TexStatus(plan.replaced->AlphaStatus()));
}
2013-02-08 00:04:34 +01:00
}
Draw::DataFormat TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const {
switch (format) {
case GE_TFMT_CLUT4:
case GE_TFMT_CLUT8:
case GE_TFMT_CLUT16:
case GE_TFMT_CLUT32:
return getClutDestFormat(clutFormat);
case GE_TFMT_4444:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
case GE_TFMT_5551:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;
case GE_TFMT_5650:
2019-10-24 23:01:45 +02:00
return Draw::DataFormat::R5G6B5_UNORM_PACK16;
case GE_TFMT_8888:
case GE_TFMT_DXT1:
case GE_TFMT_DXT3:
case GE_TFMT_DXT5:
default:
return Draw::DataFormat::R8G8B8A8_UNORM;
}
}
bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) {
2023-02-24 21:53:12 +01:00
ForgetLastTexture();
SetTexture();
if (!nextTexture_) {
return GetCurrentFramebufferTextureDebug(buffer, isFramebuffer);
}
// Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer.
TexCacheEntry *entry = nextTexture_;
// We might need a render pass to set the sampling params, unfortunately. Otherwise BuildTexture may crash.
framebufferManagerGL_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");
ApplyTexture();
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
// Not a framebuffer, so let's assume these are right.
// TODO: But they may definitely not be, if the texture was scaled.
int w = gstate.getTextureWidth(level);
int h = gstate.getTextureHeight(level);
bool result = entry->textureName != nullptr;
if (result) {
buffer.Allocate(w, h, GE_FORMAT_8888, false);
renderManager->CopyImageToMemorySync(entry->textureName, level, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, (uint8_t *)buffer.GetData(), w, "GetCurrentTextureDebug");
} else {
ERROR_LOG(G3D, "Failed to get debug texture: texture is null");
}
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
framebufferManager_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");
*isFramebuffer = false;
return result;
}
void TextureCacheGLES::DeviceLost() {
textureShaderCache_->DeviceLost();
Clear(false);
draw_ = nullptr;
render_ = nullptr;
}
void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) {
draw_ = draw;
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
textureShaderCache_->DeviceRestore(draw);
}
void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry) {
GLRTexture *tex = entry->textureName;
return (void *)tex;
}