Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/GPU/GLES/TextureCacheGLES.cpp
5673 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <cstring>
19
20
#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/Profiler/Profiler.h"
25
#include "Common/System/OSD.h"
26
#include "Common/GPU/OpenGL/GLRenderManager.h"
27
#include "Common/TimeUtil.h"
28
29
#include "GPU/ge_constants.h"
30
#include "GPU/GPUState.h"
31
#include "GPU/GPUDefinitions.h"
32
#include "GPU/GLES/TextureCacheGLES.h"
33
#include "GPU/GLES/FramebufferManagerGLES.h"
34
#include "GPU/Common/TextureShaderCommon.h"
35
#include "GPU/Common/DrawEngineCommon.h"
36
37
TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw, Draw2D *draw2D)
38
: TextureCacheCommon(draw, draw2D) {
39
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
40
41
nextTexture_ = nullptr;
42
}
43
44
TextureCacheGLES::~TextureCacheGLES() {
45
Clear(true);
46
}
47
48
void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager) {
49
framebufferManagerGL_ = fbManager;
50
framebufferManager_ = fbManager;
51
}
52
53
void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry, bool delete_them) {
54
if (delete_them) {
55
if (entry->textureName) {
56
render_->DeleteTexture(entry->textureName);
57
}
58
}
59
entry->textureName = nullptr;
60
}
61
62
void TextureCacheGLES::Clear(bool delete_them) {
63
TextureCacheCommon::Clear(delete_them);
64
}
65
66
static Draw::DataFormat getClutDestFormat(GEPaletteFormat format) {
67
switch (format) {
68
case GE_CMODE_16BIT_ABGR4444:
69
return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
70
case GE_CMODE_16BIT_ABGR5551:
71
return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;
72
case GE_CMODE_16BIT_BGR5650:
73
return Draw::DataFormat::R5G6B5_UNORM_PACK16;
74
case GE_CMODE_32BIT_ABGR8888:
75
return Draw::DataFormat::R8G8B8A8_UNORM;
76
}
77
return Draw::DataFormat::UNDEFINED;
78
}
79
80
static const GLuint MinFiltGL[8] = {
81
GL_NEAREST,
82
GL_LINEAR,
83
GL_NEAREST,
84
GL_LINEAR,
85
GL_NEAREST_MIPMAP_NEAREST,
86
GL_LINEAR_MIPMAP_NEAREST,
87
GL_NEAREST_MIPMAP_LINEAR,
88
GL_LINEAR_MIPMAP_LINEAR,
89
};
90
91
static const GLuint MagFiltGL[2] = {
92
GL_NEAREST,
93
GL_LINEAR
94
};
95
96
void TextureCacheGLES::ApplySamplingParams(const SamplerCacheKey &key) {
97
if (gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {
98
float minLod = (float)key.minLevel / 256.0f;
99
float maxLod = (float)key.maxLevel / 256.0f;
100
float lodBias = (float)key.lodBias / 256.0f;
101
render_->SetTextureLod(0, minLod, maxLod, lodBias);
102
}
103
104
float aniso = 0.0f;
105
int minKey = ((int)key.mipEnable << 2) | ((int)key.mipFilt << 1) | ((int)key.minFilt);
106
render_->SetTextureSampler(0,
107
key.sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, key.tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT,
108
key.magFilt ? GL_LINEAR : GL_NEAREST, MinFiltGL[minKey], aniso);
109
}
110
111
static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dstFmt, int numPixels) {
112
const u32 *src = (const u32 *)srcBuf;
113
u32 *dst = (u32 *)dstBuf;
114
switch (dstFmt) {
115
case Draw::DataFormat::R4G4B4A4_UNORM_PACK16:
116
ConvertRGBA4444ToABGR4444((u16 *)dst, (const u16 *)src, numPixels);
117
break;
118
// Final Fantasy 2 uses this heavily in animated textures.
119
case Draw::DataFormat::R5G5B5A1_UNORM_PACK16:
120
ConvertRGBA5551ToABGR1555((u16 *)dst, (const u16 *)src, numPixels);
121
break;
122
case Draw::DataFormat::R5G6B5_UNORM_PACK16:
123
ConvertRGB565ToBGR565((u16 *)dst, (const u16 *)src, numPixels);
124
break;
125
default:
126
// No need to convert RGBA8888, right order already
127
if (dst != src)
128
memcpy(dst, src, numPixels * sizeof(u32));
129
break;
130
}
131
}
132
133
void TextureCacheGLES::StartFrame() {
134
TextureCacheCommon::StartFrame();
135
136
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
137
if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) {
138
lowMemoryMode_ = true;
139
decimationCounter_ = 0;
140
141
auto err = GetI18NCategory(I18NCat::ERRORS);
142
if (standardScaleFactor_ > 1) {
143
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f);
144
} else {
145
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f);
146
}
147
}
148
}
149
150
void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
151
const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16));
152
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
153
// If not, we're going to hash random data, which hopefully doesn't cause a performance issue.
154
//
155
// TODO: Actually, this seems like a hack. The game can upload part of a CLUT and reference other data.
156
// clutTotalBytes_ is the last amount uploaded. We should hash clutMaxBytes_, but this will often hash
157
// unrelated old entries for small palettes.
158
// Adding clutBaseBytes may just be mitigating this for some usage patterns.
159
const u32 clutExtendedBytes = std::min(clutTotalBytes_ + clutBaseBytes, clutMaxBytes_);
160
161
if (replacer_.Enabled())
162
clutHash_ = XXH32((const char *)clutBufRaw_, clutExtendedBytes, 0xC0108888);
163
else
164
clutHash_ = XXH3_64bits((const char *)clutBufRaw_, clutExtendedBytes) & 0xFFFFFFFF;
165
166
// Avoid a copy when we don't need to convert colors.
167
if (clutFormat != GE_CMODE_32BIT_ABGR8888) {
168
const int numColors = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutMaxBytes_ / sizeof(u32)) : (clutMaxBytes_ / sizeof(u16));
169
ConvertColors(clutBufConverted_, clutBufRaw_, getClutDestFormat(clutFormat), numColors);
170
clutBuf_ = clutBufConverted_;
171
} else {
172
clutBuf_ = clutBufRaw_;
173
}
174
175
// Special optimization: fonts typically draw clut4 with just alpha values in a single color.
176
clutAlphaLinear_ = false;
177
clutAlphaLinearColor_ = 0;
178
if (clutFormat == GE_CMODE_16BIT_ABGR4444 && clutIndexIsSimple) {
179
const u16_le *clut = GetCurrentClut<u16_le>();
180
clutAlphaLinear_ = true;
181
clutAlphaLinearColor_ = clut[15] & 0xFFF0;
182
for (int i = 0; i < 16; ++i) {
183
u16 step = clutAlphaLinearColor_ | i;
184
if (clut[i] != step) {
185
clutAlphaLinear_ = false;
186
break;
187
}
188
}
189
}
190
191
clutLastFormat_ = gstate.clutformat;
192
}
193
194
void TextureCacheGLES::BindTexture(TexCacheEntry *entry) {
195
if (!entry) {
196
render_->BindTexture(0, nullptr);
197
lastBoundTexture = nullptr;
198
return;
199
}
200
if (entry->textureName != lastBoundTexture) {
201
render_->BindTexture(0, entry->textureName);
202
lastBoundTexture = entry->textureName;
203
}
204
int maxLevel = (entry->status & TexCacheEntry::STATUS_NO_MIPS) ? 0 : entry->maxLevel;
205
SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry);
206
ApplySamplingParams(samplerKey);
207
gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF);
208
}
209
210
void TextureCacheGLES::Unbind() {
211
render_->BindTexture(TEX_SLOT_PSP_TEXTURE, nullptr);
212
ForgetLastTexture();
213
}
214
215
void TextureCacheGLES::BindAsClutTexture(Draw::Texture *tex, bool smooth) {
216
GLRTexture *glrTex = (GLRTexture *)draw_->GetNativeObject(Draw::NativeObject::TEXTURE_VIEW, tex);
217
render_->BindTexture(TEX_SLOT_CLUT, glrTex);
218
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);
219
}
220
221
void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
222
BuildTexturePlan plan;
223
if (!PrepareBuildTexture(plan, entry)) {
224
// We're screwed?
225
return;
226
}
227
228
_assert_(!entry->textureName);
229
230
// GLES2 doesn't have support for a "Max lod" which is critical as PSP games often
231
// don't specify mips all the way down. As a result, we either need to manually generate
232
// the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not
233
// be as good quality as the game's own (might even be better in some cases though).
234
235
int tw = plan.createW;
236
int th = plan.createH;
237
238
Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());
239
if (plan.doReplace) {
240
plan.replaced->GetSize(plan.baseLevelSrc, &tw, &th);
241
dstFmt = plan.replaced->Format();
242
} else if (plan.scaleFactor > 1 || plan.saveTexture) {
243
dstFmt = Draw::DataFormat::R8G8B8A8_UNORM;
244
} else if (plan.decodeToClut8) {
245
dstFmt = Draw::DataFormat::R8_UNORM;
246
}
247
248
if (plan.depth == 1) {
249
entry->textureName = render_->CreateTexture(GL_TEXTURE_2D, tw, th, 1, plan.levelsToCreate);
250
} else {
251
_dbg_assert_(draw_->GetDeviceCaps().texture3DSupported);
252
entry->textureName = render_->CreateTexture(GL_TEXTURE_3D, tw, th, plan.depth, 1);
253
}
254
255
// Apply some additional compatibility checks.
256
if (plan.levelsToLoad > 1) {
257
// Avoid PowerVR driver bug
258
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
259
// It's ok to generate mipmaps beyond the loaded levels.
260
} else {
261
plan.levelsToCreate = plan.levelsToLoad;
262
}
263
}
264
265
if (!gstate_c.Use(GPU_USE_TEXTURE_LOD_CONTROL)) {
266
// If the mip chain is not full..
267
if (plan.levelsToCreate != plan.maxPossibleLevels) {
268
// We need to avoid creating mips at all, or generate them all - can't be incomplete
269
// on this hardware (strict OpenGL rules).
270
plan.levelsToCreate = 1;
271
plan.levelsToLoad = 1;
272
entry->status |= TexCacheEntry::STATUS_NO_MIPS;
273
}
274
}
275
276
if (plan.depth == 1) {
277
for (int i = 0; i < plan.levelsToLoad; i++) {
278
int srcLevel = i == 0 ? plan.baseLevelSrc : i;
279
280
int mipWidth;
281
int mipHeight;
282
plan.GetMipSize(i, &mipWidth, &mipHeight);
283
284
u8 *data = nullptr;
285
int stride = 0;
286
int dataSize;
287
288
bool bc = false;
289
290
if (plan.doReplace) {
291
int blockSize = 0;
292
if (Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), &blockSize)) {
293
stride = mipWidth * 4;
294
dataSize = plan.replaced->GetLevelDataSizeAfterCopy(i);
295
bc = true;
296
} else {
297
int bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format());
298
stride = mipWidth * bpp;
299
dataSize = stride * mipHeight;
300
}
301
} else {
302
int bpp = 0;
303
if (plan.scaleFactor > 1) {
304
bpp = 4;
305
} else {
306
bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
307
}
308
stride = mipWidth * bpp;
309
dataSize = stride * mipHeight;
310
}
311
312
data = (u8 *)AllocateAlignedMemory(dataSize, 16);
313
_assert_msg_(data != nullptr, "Failed to allocate aligned memory for texture level %d: %d bytes (%dx%d)", i, (int)dataSize, mipWidth, mipHeight);
314
315
if (!data) {
316
ERROR_LOG(Log::G3D, "Ran out of RAM trying to allocate a temporary texture upload buffer (%dx%d)", mipWidth, mipHeight);
317
return;
318
}
319
320
LoadTextureLevel(*entry, data, dataSize, stride, plan, srcLevel, dstFmt, TexDecodeFlags::REVERSE_COLORS);
321
322
// NOTE: TextureImage takes ownership of data, so we don't free it afterwards.
323
render_->TextureImage(entry->textureName, i, mipWidth, mipHeight, 1, dstFmt, data, GLRAllocType::ALIGNED);
324
}
325
326
bool genMips = plan.levelsToCreate > plan.levelsToLoad;
327
328
render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips);
329
} else {
330
int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
331
int stride = bpp * (plan.w * plan.scaleFactor);
332
int levelStride = stride * (plan.h * plan.scaleFactor);
333
334
size_t dataSize = levelStride * plan.depth;
335
u8 *data = (u8 *)AllocateAlignedMemory(dataSize, 16);
336
_assert_msg_(data != nullptr, "Failed to allocate aligned memory for 3d texture: %d bytes", (int)dataSize);
337
memset(data, 0, levelStride * plan.depth);
338
u8 *p = data;
339
340
for (int i = 0; i < plan.depth; i++) {
341
LoadTextureLevel(*entry, p, dataSize, stride, plan, i, dstFmt, TexDecodeFlags::REVERSE_COLORS);
342
p += levelStride;
343
}
344
345
render_->TextureImage(entry->textureName, 0, plan.w * plan.scaleFactor, plan.h * plan.scaleFactor, plan.depth, dstFmt, data, GLRAllocType::ALIGNED);
346
347
// Signal that we support depth textures so use it as one.
348
entry->status |= TexCacheEntry::STATUS_3D;
349
350
render_->FinalizeTexture(entry->textureName, 1, false);
351
}
352
353
if (plan.doReplace) {
354
entry->SetAlphaStatus(TexCacheEntry::TexStatus(plan.replaced->AlphaStatus()));
355
}
356
}
357
358
Draw::DataFormat TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) {
359
switch (format) {
360
case GE_TFMT_CLUT4:
361
case GE_TFMT_CLUT8:
362
case GE_TFMT_CLUT16:
363
case GE_TFMT_CLUT32:
364
return getClutDestFormat(clutFormat);
365
case GE_TFMT_4444:
366
return Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
367
case GE_TFMT_5551:
368
return Draw::DataFormat::R5G5B5A1_UNORM_PACK16;
369
case GE_TFMT_5650:
370
return Draw::DataFormat::R5G6B5_UNORM_PACK16;
371
case GE_TFMT_8888:
372
case GE_TFMT_DXT1:
373
case GE_TFMT_DXT3:
374
case GE_TFMT_DXT5:
375
default:
376
return Draw::DataFormat::R8G8B8A8_UNORM;
377
}
378
}
379
380
bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) {
381
ForgetLastTexture();
382
SetTexture();
383
if (!nextTexture_) {
384
return GetCurrentFramebufferTextureDebug(buffer, isFramebuffer);
385
}
386
387
// Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer.
388
TexCacheEntry *entry = nextTexture_;
389
// We might need a render pass to set the sampling params, unfortunately. Otherwise BuildTexture may crash.
390
framebufferManagerGL_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");
391
ApplyTexture(false);
392
393
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
394
395
// Not a framebuffer, so let's assume these are right.
396
// TODO: But they may definitely not be, if the texture was scaled.
397
int w = gstate.getTextureWidth(level);
398
int h = gstate.getTextureHeight(level);
399
400
bool result = entry->textureName != nullptr;
401
if (result) {
402
buffer.Allocate(w, h, GE_FORMAT_8888, false);
403
renderManager->CopyImageToMemorySync(entry->textureName, level, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, (uint8_t *)buffer.GetData(), w, "GetCurrentTextureDebug");
404
} else {
405
ERROR_LOG(Log::G3D, "Failed to get debug texture: texture is null");
406
}
407
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
408
framebufferManager_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug");
409
410
*isFramebuffer = false;
411
return result;
412
}
413
414
void TextureCacheGLES::DeviceLost() {
415
textureShaderCache_->DeviceLost();
416
Clear(false);
417
draw_ = nullptr;
418
render_ = nullptr;
419
}
420
421
void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) {
422
draw_ = draw;
423
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
424
textureShaderCache_->DeviceRestore(draw);
425
}
426
427
void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry, bool flat) const {
428
GLRTexture *tex = entry->textureName;
429
return (void *)tex;
430
}
431
432