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