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/GLES/TextureCacheGLES.cpp
Views: 1401
// Copyright (c) 2012- 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 <algorithm>18#include <cstring>1920#include "ext/xxhash.h"21#include "Common/Common.h"22#include "Common/Data/Convert/ColorConv.h"23#include "Common/Data/Text/I18n.h"24#include "Common/Math/math_util.h"25#include "Common/Profiler/Profiler.h"26#include "Common/System/OSD.h"27#include "Common/GPU/OpenGL/GLRenderManager.h"28#include "Common/TimeUtil.h"2930#include "Core/Config.h"31#include "Core/MemMap.h"32#include "GPU/ge_constants.h"33#include "GPU/GPUState.h"34#include "GPU/GLES/TextureCacheGLES.h"35#include "GPU/GLES/FramebufferManagerGLES.h"36#include "GPU/Common/FragmentShaderGenerator.h"37#include "GPU/Common/TextureShaderCommon.h"38#include "GPU/GLES/ShaderManagerGLES.h"39#include "GPU/GLES/DrawEngineGLES.h"40#include "GPU/Common/TextureDecoder.h"4142#ifdef _M_SSE43#include <emmintrin.h>44#endif4546TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw, Draw2D *draw2D)47: TextureCacheCommon(draw, draw2D) {48render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);4950nextTexture_ = nullptr;51}5253TextureCacheGLES::~TextureCacheGLES() {54Clear(true);55}5657void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager) {58framebufferManagerGL_ = fbManager;59framebufferManager_ = fbManager;60}6162void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry, bool delete_them) {63if (delete_them) {64if (entry->textureName) {65render_->DeleteTexture(entry->textureName);66}67}68entry->textureName = nullptr;69}7071void TextureCacheGLES::Clear(bool delete_them) {72TextureCacheCommon::Clear(delete_them);73}7475Draw::DataFormat getClutDestFormat(GEPaletteFormat format) {76switch (format) {77case GE_CMODE_16BIT_ABGR4444:78return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;79case GE_CMODE_16BIT_ABGR5551:80return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;81case GE_CMODE_16BIT_BGR5650:82return Draw::DataFormat::R5G6B5_UNORM_PACK16;83case GE_CMODE_32BIT_ABGR8888:84return Draw::DataFormat::R8G8B8A8_UNORM;85}86return Draw::DataFormat::UNDEFINED;87}8889static const GLuint MinFiltGL[8] = {90GL_NEAREST,91GL_LINEAR,92GL_NEAREST,93GL_LINEAR,94GL_NEAREST_MIPMAP_NEAREST,95GL_LINEAR_MIPMAP_NEAREST,96GL_NEAREST_MIPMAP_LINEAR,97GL_LINEAR_MIPMAP_LINEAR,98};99100static const GLuint MagFiltGL[2] = {101GL_NEAREST,102GL_LINEAR103};104105void TextureCacheGLES::ApplySamplingParams(const SamplerCacheKey &key) {106if (gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {107float minLod = (float)key.minLevel / 256.0f;108float maxLod = (float)key.maxLevel / 256.0f;109float lodBias = (float)key.lodBias / 256.0f;110render_->SetTextureLod(0, minLod, maxLod, lodBias);111}112113float aniso = 0.0f;114int minKey = ((int)key.mipEnable << 2) | ((int)key.mipFilt << 1) | ((int)key.minFilt);115render_->SetTextureSampler(0,116key.sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, key.tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT,117key.magFilt ? GL_LINEAR : GL_NEAREST, MinFiltGL[minKey], aniso);118}119120static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dstFmt, int numPixels) {121const u32 *src = (const u32 *)srcBuf;122u32 *dst = (u32 *)dstBuf;123switch (dstFmt) {124case Draw::DataFormat::R4G4B4A4_UNORM_PACK16:125ConvertRGBA4444ToABGR4444((u16 *)dst, (const u16 *)src, numPixels);126break;127// Final Fantasy 2 uses this heavily in animated textures.128case Draw::DataFormat::R5G5B5A1_UNORM_PACK16:129ConvertRGBA5551ToABGR1555((u16 *)dst, (const u16 *)src, numPixels);130break;131case Draw::DataFormat::R5G6B5_UNORM_PACK16:132ConvertRGB565ToBGR565((u16 *)dst, (const u16 *)src, numPixels);133break;134default:135// No need to convert RGBA8888, right order already136if (dst != src)137memcpy(dst, src, numPixels * sizeof(u32));138break;139}140}141142void TextureCacheGLES::StartFrame() {143TextureCacheCommon::StartFrame();144145GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);146if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) {147lowMemoryMode_ = true;148decimationCounter_ = 0;149150auto err = GetI18NCategory(I18NCat::ERRORS);151if (standardScaleFactor_ > 1) {152g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f);153} else {154g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f);155}156}157}158159void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {160const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16));161// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.162// If not, we're going to hash random data, which hopefully doesn't cause a performance issue.163//164// TODO: Actually, this seems like a hack. The game can upload part of a CLUT and reference other data.165// clutTotalBytes_ is the last amount uploaded. We should hash clutMaxBytes_, but this will often hash166// unrelated old entries for small palettes.167// Adding clutBaseBytes may just be mitigating this for some usage patterns.168const u32 clutExtendedBytes = std::min(clutTotalBytes_ + clutBaseBytes, clutMaxBytes_);169170if (replacer_.Enabled())171clutHash_ = XXH32((const char *)clutBufRaw_, clutExtendedBytes, 0xC0108888);172else173clutHash_ = XXH3_64bits((const char *)clutBufRaw_, clutExtendedBytes) & 0xFFFFFFFF;174175// Avoid a copy when we don't need to convert colors.176if (clutFormat != GE_CMODE_32BIT_ABGR8888) {177const int numColors = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutMaxBytes_ / sizeof(u32)) : (clutMaxBytes_ / sizeof(u16));178ConvertColors(clutBufConverted_, clutBufRaw_, getClutDestFormat(clutFormat), numColors);179clutBuf_ = clutBufConverted_;180} else {181clutBuf_ = clutBufRaw_;182}183184// Special optimization: fonts typically draw clut4 with just alpha values in a single color.185clutAlphaLinear_ = false;186clutAlphaLinearColor_ = 0;187if (clutFormat == GE_CMODE_16BIT_ABGR4444 && clutIndexIsSimple) {188const u16_le *clut = GetCurrentClut<u16_le>();189clutAlphaLinear_ = true;190clutAlphaLinearColor_ = clut[15] & 0xFFF0;191for (int i = 0; i < 16; ++i) {192u16 step = clutAlphaLinearColor_ | i;193if (clut[i] != step) {194clutAlphaLinear_ = false;195break;196}197}198}199200clutLastFormat_ = gstate.clutformat;201}202203void TextureCacheGLES::BindTexture(TexCacheEntry *entry) {204if (!entry) {205render_->BindTexture(0, nullptr);206lastBoundTexture = nullptr;207return;208}209if (entry->textureName != lastBoundTexture) {210render_->BindTexture(0, entry->textureName);211lastBoundTexture = entry->textureName;212}213int maxLevel = (entry->status & TexCacheEntry::STATUS_NO_MIPS) ? 0 : entry->maxLevel;214SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry);215ApplySamplingParams(samplerKey);216gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF);217}218219void TextureCacheGLES::Unbind() {220render_->BindTexture(TEX_SLOT_PSP_TEXTURE, nullptr);221ForgetLastTexture();222}223224void TextureCacheGLES::BindAsClutTexture(Draw::Texture *tex, bool smooth) {225GLRTexture *glrTex = (GLRTexture *)draw_->GetNativeObject(Draw::NativeObject::TEXTURE_VIEW, tex);226render_->BindTexture(TEX_SLOT_CLUT, glrTex);227render_->SetTextureSampler(TEX_SLOT_CLUT, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, smooth ? GL_LINEAR : GL_NEAREST, smooth ? GL_LINEAR : GL_NEAREST, 0.0f);228}229230void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {231BuildTexturePlan plan;232if (!PrepareBuildTexture(plan, entry)) {233// We're screwed?234return;235}236237_assert_(!entry->textureName);238239// GLES2 doesn't have support for a "Max lod" which is critical as PSP games often240// don't specify mips all the way down. As a result, we either need to manually generate241// the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not242// be as good quality as the game's own (might even be better in some cases though).243244int tw = plan.createW;245int th = plan.createH;246247Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());248if (plan.doReplace) {249plan.replaced->GetSize(plan.baseLevelSrc, &tw, &th);250dstFmt = plan.replaced->Format();251} else if (plan.scaleFactor > 1 || plan.saveTexture) {252dstFmt = Draw::DataFormat::R8G8B8A8_UNORM;253} else if (plan.decodeToClut8) {254dstFmt = Draw::DataFormat::R8_UNORM;255}256257if (plan.depth == 1) {258entry->textureName = render_->CreateTexture(GL_TEXTURE_2D, tw, th, 1, plan.levelsToCreate);259} else {260entry->textureName = render_->CreateTexture(GL_TEXTURE_3D, tw, th, plan.depth, 1);261}262263// Apply some additional compatibility checks.264if (plan.levelsToLoad > 1) {265// Avoid PowerVR driver bug266if (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 > width267// It's ok to generate mipmaps beyond the loaded levels.268} else {269plan.levelsToCreate = plan.levelsToLoad;270}271}272273if (!gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {274// If the mip chain is not full..275if (plan.levelsToCreate != plan.maxPossibleLevels) {276// We need to avoid creating mips at all, or generate them all - can't be incomplete277// on this hardware (strict OpenGL rules).278plan.levelsToCreate = 1;279plan.levelsToLoad = 1;280entry->status |= TexCacheEntry::STATUS_NO_MIPS;281}282}283284if (plan.depth == 1) {285for (int i = 0; i < plan.levelsToLoad; i++) {286int srcLevel = i == 0 ? plan.baseLevelSrc : i;287288int mipWidth;289int mipHeight;290plan.GetMipSize(i, &mipWidth, &mipHeight);291292u8 *data = nullptr;293int stride = 0;294int dataSize;295296bool bc = false;297298if (plan.doReplace) {299int blockSize = 0;300if (Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), &blockSize)) {301stride = mipWidth * 4;302dataSize = plan.replaced->GetLevelDataSizeAfterCopy(i);303bc = true;304} else {305int bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format());306stride = mipWidth * bpp;307dataSize = stride * mipHeight;308}309} else {310int bpp = 0;311if (plan.scaleFactor > 1) {312bpp = 4;313} else {314bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);315}316stride = mipWidth * bpp;317dataSize = stride * mipHeight;318}319320data = (u8 *)AllocateAlignedMemory(dataSize, 16);321322if (!data) {323ERROR_LOG(Log::G3D, "Ran out of RAM trying to allocate a temporary texture upload buffer (%dx%d)", mipWidth, mipHeight);324return;325}326327LoadTextureLevel(*entry, data, dataSize, stride, plan, srcLevel, dstFmt, TexDecodeFlags::REVERSE_COLORS);328329// NOTE: TextureImage takes ownership of data, so we don't free it afterwards.330render_->TextureImage(entry->textureName, i, mipWidth, mipHeight, 1, dstFmt, data, GLRAllocType::ALIGNED);331}332333bool genMips = plan.levelsToCreate > plan.levelsToLoad;334335render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips);336} else {337int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);338int stride = bpp * (plan.w * plan.scaleFactor);339int levelStride = stride * (plan.h * plan.scaleFactor);340341size_t dataSize = levelStride * plan.depth;342u8 *data = (u8 *)AllocateAlignedMemory(dataSize, 16);343memset(data, 0, levelStride * plan.depth);344u8 *p = data;345346for (int i = 0; i < plan.depth; i++) {347LoadTextureLevel(*entry, p, dataSize, stride, plan, i, dstFmt, TexDecodeFlags::REVERSE_COLORS);348p += levelStride;349}350351render_->TextureImage(entry->textureName, 0, plan.w * plan.scaleFactor, plan.h * plan.scaleFactor, plan.depth, dstFmt, data, GLRAllocType::ALIGNED);352353// Signal that we support depth textures so use it as one.354entry->status |= TexCacheEntry::STATUS_3D;355356render_->FinalizeTexture(entry->textureName, 1, false);357}358359if (plan.doReplace) {360entry->SetAlphaStatus(TexCacheEntry::TexStatus(plan.replaced->AlphaStatus()));361}362}363364Draw::DataFormat TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) {365switch (format) {366case GE_TFMT_CLUT4:367case GE_TFMT_CLUT8:368case GE_TFMT_CLUT16:369case GE_TFMT_CLUT32:370return getClutDestFormat(clutFormat);371case GE_TFMT_4444:372return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;373case GE_TFMT_5551:374return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;375case GE_TFMT_5650:376return Draw::DataFormat::R5G6B5_UNORM_PACK16;377case GE_TFMT_8888:378case GE_TFMT_DXT1:379case GE_TFMT_DXT3:380case GE_TFMT_DXT5:381default:382return Draw::DataFormat::R8G8B8A8_UNORM;383}384}385386bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) {387ForgetLastTexture();388SetTexture();389if (!nextTexture_) {390return GetCurrentFramebufferTextureDebug(buffer, isFramebuffer);391}392393// Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer.394TexCacheEntry *entry = nextTexture_;395// We might need a render pass to set the sampling params, unfortunately. Otherwise BuildTexture may crash.396framebufferManagerGL_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");397ApplyTexture();398399GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);400401// Not a framebuffer, so let's assume these are right.402// TODO: But they may definitely not be, if the texture was scaled.403int w = gstate.getTextureWidth(level);404int h = gstate.getTextureHeight(level);405406bool result = entry->textureName != nullptr;407if (result) {408buffer.Allocate(w, h, GE_FORMAT_8888, false);409renderManager->CopyImageToMemorySync(entry->textureName, level, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, (uint8_t *)buffer.GetData(), w, "GetCurrentTextureDebug");410} else {411ERROR_LOG(Log::G3D, "Failed to get debug texture: texture is null");412}413gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);414framebufferManager_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");415416*isFramebuffer = false;417return result;418}419420void TextureCacheGLES::DeviceLost() {421textureShaderCache_->DeviceLost();422Clear(false);423draw_ = nullptr;424render_ = nullptr;425}426427void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) {428draw_ = draw;429render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);430textureShaderCache_->DeviceRestore(draw);431}432433void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry) {434GLRTexture *tex = entry->textureName;435return (void *)tex;436}437438439