/
TextureCacheBase.cpp
2070 lines (1792 loc) · 75.5 KB
/
TextureCacheBase.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cmath>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#if defined(_M_X86) || defined(_M_X86_64)
#include <pmmintrin.h>
#endif
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/Hash.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MemoryUtil.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/FifoPlayer/FifoRecorder.h"
#include "Core/HW/Memmap.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/Debugger.h"
#include "VideoCommon/FramebufferManagerBase.h"
#include "VideoCommon/HiresTextures.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/SamplerCommon.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/TextureDecoder.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
static const u64 TEXHASH_INVALID = 0;
// Sonic the Fighters (inside Sonic Gems Collection) loops a 64 frames animation
static const int TEXTURE_KILL_THRESHOLD = 64;
static const int TEXTURE_POOL_KILL_THRESHOLD = 3;
std::unique_ptr<TextureCacheBase> g_texture_cache;
std::bitset<8> TextureCacheBase::valid_bind_points;
TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex)
: texture(std::move(tex))
{
}
TextureCacheBase::TCacheEntry::~TCacheEntry()
{
for (auto& reference : references)
reference->references.erase(this);
}
void TextureCacheBase::CheckTempSize(size_t required_size)
{
if (required_size <= temp_size)
return;
temp_size = required_size;
Common::FreeAlignedMemory(temp);
temp = static_cast<u8*>(Common::AllocateAlignedMemory(temp_size, 16));
}
TextureCacheBase::TextureCacheBase()
{
SetBackupConfig(g_ActiveConfig);
temp_size = 2048 * 2048 * 4;
temp = static_cast<u8*>(Common::AllocateAlignedMemory(temp_size, 16));
TexDecoder_SetTexFmtOverlayOptions(backup_config.texfmt_overlay,
backup_config.texfmt_overlay_center);
HiresTexture::Init();
SetHash64Function();
InvalidateAllBindPoints();
}
void TextureCacheBase::Invalidate()
{
InvalidateAllBindPoints();
for (size_t i = 0; i < bound_textures.size(); ++i)
{
bound_textures[i] = nullptr;
}
for (auto& tex : textures_by_address)
{
delete tex.second;
}
textures_by_address.clear();
textures_by_hash.clear();
texture_pool.clear();
}
TextureCacheBase::~TextureCacheBase()
{
HiresTexture::Shutdown();
Invalidate();
Common::FreeAlignedMemory(temp);
temp = nullptr;
}
void TextureCacheBase::OnConfigChanged(VideoConfig& config)
{
if (config.bHiresTextures != backup_config.hires_textures ||
config.bCacheHiresTextures != backup_config.cache_hires_textures)
{
HiresTexture::Update();
}
// TODO: Invalidating texcache is really stupid in some of these cases
if (config.iSafeTextureCache_ColorSamples != backup_config.color_samples ||
config.bTexFmtOverlayEnable != backup_config.texfmt_overlay ||
config.bTexFmtOverlayCenter != backup_config.texfmt_overlay_center ||
config.bHiresTextures != backup_config.hires_textures ||
config.bEnableGPUTextureDecoding != backup_config.gpu_texture_decoding ||
config.bDisableCopyToVRAM != backup_config.disable_vram_copies)
{
Invalidate();
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable,
g_ActiveConfig.bTexFmtOverlayCenter);
}
if ((config.stereo_mode != StereoMode::Off) != backup_config.stereo_3d ||
config.bStereoEFBMonoDepth != backup_config.efb_mono_depth)
{
g_texture_cache->DeleteShaders();
if (!g_texture_cache->CompileShaders())
PanicAlert("Failed to recompile one or more texture conversion shaders.");
}
SetBackupConfig(config);
}
void TextureCacheBase::Cleanup(int _frameCount)
{
TexAddrCache::iterator iter = textures_by_address.begin();
TexAddrCache::iterator tcend = textures_by_address.end();
while (iter != tcend)
{
if (iter->second->tmem_only)
{
iter = InvalidateTexture(iter);
}
else if (iter->second->frameCount == FRAMECOUNT_INVALID)
{
iter->second->frameCount = _frameCount;
++iter;
}
else if (_frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount)
{
if (iter->second->IsCopy())
{
// Only remove EFB copies when they wouldn't be used anymore(changed hash), because EFB
// copies living on the
// host GPU are unrecoverable. Perform this check only every TEXTURE_KILL_THRESHOLD for
// performance reasons
if ((_frameCount - iter->second->frameCount) % TEXTURE_KILL_THRESHOLD == 1 &&
iter->second->hash != iter->second->CalculateHash())
{
iter = InvalidateTexture(iter);
}
else
{
++iter;
}
}
else
{
iter = InvalidateTexture(iter);
}
}
else
{
++iter;
}
}
TexPool::iterator iter2 = texture_pool.begin();
TexPool::iterator tcend2 = texture_pool.end();
while (iter2 != tcend2)
{
if (iter2->second.frameCount == FRAMECOUNT_INVALID)
{
iter2->second.frameCount = _frameCount;
}
if (_frameCount > TEXTURE_POOL_KILL_THRESHOLD + iter2->second.frameCount)
{
iter2 = texture_pool.erase(iter2);
}
else
{
++iter2;
}
}
}
bool TextureCacheBase::TCacheEntry::OverlapsMemoryRange(u32 range_address, u32 range_size) const
{
if (addr + size_in_bytes <= range_address)
return false;
if (addr >= range_address + range_size)
return false;
return true;
}
void TextureCacheBase::SetBackupConfig(const VideoConfig& config)
{
backup_config.color_samples = config.iSafeTextureCache_ColorSamples;
backup_config.texfmt_overlay = config.bTexFmtOverlayEnable;
backup_config.texfmt_overlay_center = config.bTexFmtOverlayCenter;
backup_config.hires_textures = config.bHiresTextures;
backup_config.cache_hires_textures = config.bCacheHiresTextures;
backup_config.stereo_3d = config.stereo_mode != StereoMode::Off;
backup_config.efb_mono_depth = config.bStereoEFBMonoDepth;
backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding;
backup_config.disable_vram_copies = config.bDisableCopyToVRAM;
}
TextureCacheBase::TCacheEntry*
TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTFormat tlutfmt)
{
TextureConfig new_config = entry->texture->GetConfig();
new_config.levels = 1;
new_config.rendertarget = true;
TCacheEntry* decoded_entry = AllocateCacheEntry(new_config);
if (!decoded_entry)
return nullptr;
decoded_entry->SetGeneralParameters(entry->addr, entry->size_in_bytes, entry->format,
entry->should_force_safe_hashing);
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
decoded_entry->SetHashes(entry->base_hash, entry->hash);
decoded_entry->frameCount = FRAMECOUNT_INVALID;
decoded_entry->should_force_safe_hashing = false;
decoded_entry->SetNotCopy();
decoded_entry->may_have_overlapping_textures = entry->may_have_overlapping_textures;
ConvertTexture(decoded_entry, entry, palette, tlutfmt);
textures_by_address.emplace(entry->addr, decoded_entry);
return decoded_entry;
}
void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* entry, u32 new_width,
u32 new_height)
{
if (entry->GetWidth() == new_width && entry->GetHeight() == new_height)
{
return;
}
const u32 max = g_ActiveConfig.backend_info.MaxTextureSize;
if (max < new_width || max < new_height)
{
ERROR_LOG(VIDEO, "Texture too big, width = %d, height = %d", new_width, new_height);
return;
}
TextureConfig newconfig;
newconfig.width = new_width;
newconfig.height = new_height;
newconfig.layers = entry->GetNumLayers();
newconfig.rendertarget = true;
std::unique_ptr<AbstractTexture> new_texture = AllocateTexture(newconfig);
if (new_texture)
{
new_texture->ScaleRectangleFromTexture(entry->texture.get(),
entry->texture->GetConfig().GetRect(),
new_texture->GetConfig().GetRect());
entry->texture.swap(new_texture);
auto config = new_texture->GetConfig();
// At this point new_texture has the old texture in it,
// we can potentially reuse this, so let's move it back to the pool
texture_pool.emplace(config, TexPoolEntry(std::move(new_texture)));
}
else
{
ERROR_LOG(VIDEO, "Scaling failed");
}
}
TextureCacheBase::TCacheEntry*
TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* palette,
TLUTFormat tlutfmt)
{
// If the flag may_have_overlapping_textures is cleared, there are no overlapping EFB copies,
// which aren't applied already. It is set for new textures, and for the affected range
// on each EFB copy.
if (!entry_to_update->may_have_overlapping_textures)
return entry_to_update;
entry_to_update->may_have_overlapping_textures = false;
const bool isPaletteTexture = IsColorIndexed(entry_to_update->format.texfmt);
// EFB copies are excluded from these updates, until there's an example where a game would
// benefit from updating. This would require more work to be done.
if (entry_to_update->IsCopy())
return entry_to_update;
u32 block_width = TexDecoder_GetBlockWidthInTexels(entry_to_update->format.texfmt);
u32 block_height = TexDecoder_GetBlockHeightInTexels(entry_to_update->format.texfmt);
u32 block_size = block_width * block_height *
TexDecoder_GetTexelSizeInNibbles(entry_to_update->format.texfmt) / 2;
u32 numBlocksX = (entry_to_update->native_width + block_width - 1) / block_width;
auto iter = FindOverlappingTextures(entry_to_update->addr, entry_to_update->size_in_bytes);
while (iter.first != iter.second)
{
TCacheEntry* entry = iter.first->second;
if (entry != entry_to_update && entry->IsCopy() && !entry->tmem_only &&
entry->references.count(entry_to_update) == 0 &&
entry->OverlapsMemoryRange(entry_to_update->addr, entry_to_update->size_in_bytes) &&
entry->memory_stride == numBlocksX * block_size)
{
if (entry->hash == entry->CalculateHash())
{
if (isPaletteTexture)
{
TCacheEntry* decoded_entry = ApplyPaletteToEntry(entry, palette, tlutfmt);
if (decoded_entry)
{
// Link the efb copy with the partially updated texture, so we won't apply this partial
// update again
entry->CreateReference(entry_to_update);
// Mark the texture update as used, as if it was loaded directly
entry->frameCount = FRAMECOUNT_INVALID;
entry = decoded_entry;
}
else
{
++iter.first;
continue;
}
}
u32 src_x, src_y, dst_x, dst_y;
// Note for understanding the math:
// Normal textures can't be strided, so the 2 missing cases with src_x > 0 don't exist
if (entry->addr >= entry_to_update->addr)
{
u32 block_offset = (entry->addr - entry_to_update->addr) / block_size;
u32 block_x = block_offset % numBlocksX;
u32 block_y = block_offset / numBlocksX;
src_x = 0;
src_y = 0;
dst_x = block_x * block_width;
dst_y = block_y * block_height;
}
else
{
u32 block_offset = (entry_to_update->addr - entry->addr) / block_size;
u32 block_x = (~block_offset + 1) % numBlocksX;
u32 block_y = (block_offset + block_x) / numBlocksX;
src_x = 0;
src_y = block_y * block_height;
dst_x = block_x * block_width;
dst_y = 0;
}
u32 copy_width =
std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x);
u32 copy_height =
std::min(entry->native_height - src_y, entry_to_update->native_height - dst_y);
// If one of the textures is scaled, scale both with the current efb scaling factor
if (entry_to_update->native_width != entry_to_update->GetWidth() ||
entry_to_update->native_height != entry_to_update->GetHeight() ||
entry->native_width != entry->GetWidth() || entry->native_height != entry->GetHeight())
{
ScaleTextureCacheEntryTo(entry_to_update,
g_renderer->EFBToScaledX(entry_to_update->native_width),
g_renderer->EFBToScaledY(entry_to_update->native_height));
ScaleTextureCacheEntryTo(entry, g_renderer->EFBToScaledX(entry->native_width),
g_renderer->EFBToScaledY(entry->native_height));
src_x = g_renderer->EFBToScaledX(src_x);
src_y = g_renderer->EFBToScaledY(src_y);
dst_x = g_renderer->EFBToScaledX(dst_x);
dst_y = g_renderer->EFBToScaledY(dst_y);
copy_width = g_renderer->EFBToScaledX(copy_width);
copy_height = g_renderer->EFBToScaledY(copy_height);
}
MathUtil::Rectangle<int> srcrect, dstrect;
srcrect.left = src_x;
srcrect.top = src_y;
srcrect.right = (src_x + copy_width);
srcrect.bottom = (src_y + copy_height);
dstrect.left = dst_x;
dstrect.top = dst_y;
dstrect.right = (dst_x + copy_width);
dstrect.bottom = (dst_y + copy_height);
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
{
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
0, dstrect, layer, 0);
}
if (isPaletteTexture)
{
// Remove the temporary converted texture, it won't be used anywhere else
// TODO: It would be nice to convert and copy in one step, but this code path isn't common
InvalidateTexture(GetTexCacheIter(entry));
}
else
{
// Link the two textures together, so we won't apply this partial update again
entry->CreateReference(entry_to_update);
// Mark the texture update as used, as if it was loaded directly
entry->frameCount = FRAMECOUNT_INVALID;
}
}
else
{
// If the hash does not match, this EFB copy will not be used for anything, so remove it
iter.first = InvalidateTexture(iter.first);
continue;
}
}
++iter.first;
}
return entry_to_update;
}
void TextureCacheBase::DumpTexture(TCacheEntry* entry, std::string basename, unsigned int level,
bool is_arbitrary)
{
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().GetGameID();
// make sure that the directory exists
if (!File::IsDirectory(szDir))
File::CreateDir(szDir);
if (is_arbitrary)
{
basename += "_arb";
}
if (level > 0)
{
basename += StringFromFormat("_mip%i", level);
}
std::string filename = szDir + "/" + basename + ".png";
if (!File::Exists(filename))
entry->texture->Save(filename, level);
}
static u32 CalculateLevelSize(u32 level_0_size, u32 level)
{
return std::max(level_0_size >> level, 1u);
}
void TextureCacheBase::BindTextures()
{
for (u32 i = 0; i < bound_textures.size(); i++)
{
if (IsValidBindPoint(i) && bound_textures[i])
g_renderer->SetTexture(i, bound_textures[i]->texture.get());
}
}
class ArbitraryMipmapDetector
{
private:
using PixelRGBAf = std::array<float, 4>;
public:
explicit ArbitraryMipmapDetector() = default;
void AddLevel(u32 width, u32 height, u32 row_length, const u8* buffer)
{
levels.push_back({{width, height, row_length}, buffer});
}
bool HasArbitraryMipmaps(u8* downsample_buffer) const
{
if (levels.size() < 2)
return false;
// This is the average per-pixel, per-channel difference in percent between what we
// expect a normal blurred mipmap to look like and what we actually received
// 4.5% was chosen because it's just below the lowest clearly-arbitrary texture
// I found in my tests, the background clouds in Mario Galaxy's Observatory lobby.
constexpr auto THRESHOLD_PERCENT = 4.5f;
auto* src = downsample_buffer;
auto* dst = downsample_buffer + levels[1].shape.row_length * levels[1].shape.height * 4;
float total_diff = 0.f;
for (std::size_t i = 0; i < levels.size() - 1; ++i)
{
const auto& level = levels[i];
const auto& mip = levels[i + 1];
// Manually downsample the past downsample with a simple box blur
// This is not necessarily close to whatever the original artists used, however
// It should still be closer than a thing that's not a downscale at all
Level::Downsample(i ? src : level.pixels, level.shape, dst, mip.shape);
// Find the average difference between pixels in this level but downsampled
// and the next level
auto diff = mip.AverageDiff(dst);
total_diff += diff;
std::swap(src, dst);
}
auto all_levels = total_diff / (levels.size() - 1);
return all_levels > THRESHOLD_PERCENT;
}
private:
static float SRGBToLinear(u8 srgb_byte)
{
auto srgb_float = static_cast<float>(srgb_byte) / 256.f;
// approximations found on
// http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
return srgb_float * (srgb_float * (srgb_float * 0.305306011f + 0.682171111f) + 0.012522878f);
}
static u8 LinearToSRGB(float linear)
{
return static_cast<u8>(std::max(1.055f * std::pow(linear, 0.416666667f) - 0.055f, 0.f) * 256.f);
}
struct Shape
{
u32 width;
u32 height;
u32 row_length;
};
struct Level
{
Shape shape;
const u8* pixels;
static PixelRGBAf Sample(const u8* src, const Shape& src_shape, u32 x, u32 y)
{
const auto* p = src + (x + y * src_shape.row_length) * 4;
return {{SRGBToLinear(p[0]), SRGBToLinear(p[1]), SRGBToLinear(p[2]), SRGBToLinear(p[3])}};
}
// Puts a downsampled image in dst. dst must be at least width*height*4
static void Downsample(const u8* src, const Shape& src_shape, u8* dst, const Shape& dst_shape)
{
for (u32 i = 0; i < dst_shape.height; ++i)
{
for (u32 j = 0; j < dst_shape.width; ++j)
{
auto x = j * 2;
auto y = i * 2;
const std::array<PixelRGBAf, 4> samples{{
Sample(src, src_shape, x, y),
Sample(src, src_shape, x + 1, y),
Sample(src, src_shape, x, y + 1),
Sample(src, src_shape, x + 1, y + 1),
}};
auto* dst_pixel = dst + (j + i * dst_shape.row_length) * 4;
dst_pixel[0] =
LinearToSRGB((samples[0][0] + samples[1][0] + samples[2][0] + samples[3][0]) * 0.25f);
dst_pixel[1] =
LinearToSRGB((samples[0][1] + samples[1][1] + samples[2][1] + samples[3][1]) * 0.25f);
dst_pixel[2] =
LinearToSRGB((samples[0][2] + samples[1][2] + samples[2][2] + samples[3][2]) * 0.25f);
dst_pixel[3] =
LinearToSRGB((samples[0][3] + samples[1][3] + samples[2][3] + samples[3][3]) * 0.25f);
}
}
}
float AverageDiff(const u8* other) const
{
float average_diff = 0.f;
const auto* ptr1 = pixels;
const auto* ptr2 = other;
for (u32 i = 0; i < shape.height; ++i)
{
const auto* row1 = ptr1;
const auto* row2 = ptr2;
for (u32 j = 0; j < shape.width; ++j, row1 += 4, row2 += 4)
{
average_diff += std::abs(static_cast<float>(row1[0]) - static_cast<float>(row2[0]));
average_diff += std::abs(static_cast<float>(row1[1]) - static_cast<float>(row2[1]));
average_diff += std::abs(static_cast<float>(row1[2]) - static_cast<float>(row2[2]));
average_diff += std::abs(static_cast<float>(row1[3]) - static_cast<float>(row2[3]));
}
ptr1 += shape.row_length;
ptr2 += shape.row_length;
}
return average_diff / (shape.width * shape.height * 4) / 2.56f;
}
};
std::vector<Level> levels;
};
TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
{
// if this stage was not invalidated by changes to texture registers, keep the current texture
if (IsValidBindPoint(stage) && bound_textures[stage])
{
return bound_textures[stage];
}
const FourTexUnits& tex = bpmem.tex[stage >> 2];
const u32 id = stage & 3;
const u32 address = (tex.texImage3[id].image_base /* & 0x1FFFFF*/) << 5;
u32 width = tex.texImage0[id].width + 1;
u32 height = tex.texImage0[id].height + 1;
const TextureFormat texformat = static_cast<TextureFormat>(tex.texImage0[id].format);
const u32 tlutaddr = tex.texTlut[id].tmem_offset << 9;
const TLUTFormat tlutfmt = static_cast<TLUTFormat>(tex.texTlut[id].tlut_format);
const bool use_mipmaps = SamplerCommon::AreBpTexMode0MipmapsEnabled(tex.texMode0[id]);
u32 tex_levels = use_mipmaps ? ((tex.texMode1[id].max_lod + 0xf) / 0x10 + 1) : 1;
const bool from_tmem = tex.texImage1[id].image_type != 0;
const u32 tmem_address_even = from_tmem ? tex.texImage1[id].tmem_even * TMEM_LINE_SIZE : 0;
const u32 tmem_address_odd = from_tmem ? tex.texImage2[id].tmem_odd * TMEM_LINE_SIZE : 0;
auto entry = GetTexture(address, width, height, texformat,
g_ActiveConfig.iSafeTextureCache_ColorSamples, tlutaddr, tlutfmt,
use_mipmaps, tex_levels, from_tmem, tmem_address_even, tmem_address_odd);
if (!entry)
return nullptr;
entry->frameCount = FRAMECOUNT_INVALID;
bound_textures[stage] = entry;
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
// We need to keep track of invalided textures until they have actually been replaced or
// re-loaded
valid_bind_points.set(stage);
return entry;
}
TextureCacheBase::TCacheEntry*
TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFormat texformat,
const int textureCacheSafetyColorSampleSize, u32 tlutaddr,
TLUTFormat tlutfmt, bool use_mipmaps, u32 tex_levels, bool from_tmem,
u32 tmem_address_even, u32 tmem_address_odd)
{
// TexelSizeInNibbles(format) * width * height / 16;
const unsigned int bsw = TexDecoder_GetBlockWidthInTexels(texformat);
const unsigned int bsh = TexDecoder_GetBlockHeightInTexels(texformat);
unsigned int expandedWidth = Common::AlignUp(width, bsw);
unsigned int expandedHeight = Common::AlignUp(height, bsh);
const unsigned int nativeW = width;
const unsigned int nativeH = height;
// Hash assigned to texcache entry (also used to generate filenames used for texture dumping and
// custom texture lookup)
u64 base_hash = TEXHASH_INVALID;
u64 full_hash = TEXHASH_INVALID;
TextureAndTLUTFormat full_format(texformat, tlutfmt);
const bool isPaletteTexture = IsColorIndexed(texformat);
// Reject invalid tlut format.
if (isPaletteTexture && !IsValidTLUTFormat(tlutfmt))
return nullptr;
const u32 texture_size =
TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, texformat);
u32 bytes_per_block = (bsw * bsh * TexDecoder_GetTexelSizeInNibbles(texformat)) / 2;
u32 additional_mips_size = 0; // not including level 0, which is texture_size
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in
// the mipmap chain
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we
// limit the mipmap count to 6 there
tex_levels = std::min<u32>(IntLog2(std::max(width, height)) + 1, tex_levels);
for (u32 level = 1; level != tex_levels; ++level)
{
// We still need to calculate the original size of the mips
const u32 expanded_mip_width = Common::AlignUp(CalculateLevelSize(width, level), bsw);
const u32 expanded_mip_height = Common::AlignUp(CalculateLevelSize(height, level), bsh);
additional_mips_size +=
TexDecoder_GetTextureSizeInBytes(expanded_mip_width, expanded_mip_height, texformat);
}
// TODO: the texture cache lookup is based on address, but a texture from tmem has no reason
// to have a unique and valid address. This could result in a regular texture and a tmem
// texture aliasing onto the same texture cache entry.
const u8* src_data;
if (from_tmem)
src_data = &texMem[tmem_address_even];
else
src_data = Memory::GetPointer(address);
if (!src_data)
{
ERROR_LOG(VIDEO, "Trying to use an invalid texture address 0x%8x", address);
return nullptr;
}
// If we are recording a FifoLog, keep track of what memory we read. FifoRecorder does
// its own memory modification tracking independent of the texture hashing below.
if (g_bRecordFifoData && !from_tmem)
FifoRecorder::GetInstance().UseMemory(address, texture_size + additional_mips_size,
MemoryUpdate::TEXTURE_MAP);
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data
// from the low tmem bank than it should)
base_hash = GetHash64(src_data, texture_size, textureCacheSafetyColorSampleSize);
u32 palette_size = 0;
if (isPaletteTexture)
{
palette_size = TexDecoder_GetPaletteSize(texformat);
full_hash =
base_hash ^ GetHash64(&texMem[tlutaddr], palette_size, textureCacheSafetyColorSampleSize);
}
else
{
full_hash = base_hash;
}
// Search the texture cache for textures by address
//
// Find all texture cache entries for the current texture address, and decide whether to use one
// of
// them, or to create a new one
//
// In most cases, the fastest way is to use only one texture cache entry for the same address.
// Usually,
// when a texture changes, the old version of the texture is unlikely to be used again. If there
// were
// new cache entries created for normal texture updates, there would be a slowdown due to a huge
// amount
// of unused cache entries. Also thanks to texture pooling, overwriting an existing cache entry is
// faster than creating a new one from scratch.
//
// Some games use the same address for different textures though. If the same cache entry was used
// in
// this case, it would be constantly overwritten, and effectively there wouldn't be any caching
// for
// those textures. Examples for this are Metroid Prime and Castlevania 3. Metroid Prime has
// multiple
// sets of fonts on each other stored in a single texture and uses the palette to make different
// characters visible or invisible. In Castlevania 3 some textures are used for 2 different things
// or
// at least in 2 different ways(size 1024x1024 vs 1024x256).
//
// To determine whether to use multiple cache entries or a single entry, use the following
// heuristic:
// If the same texture address is used several times during the same frame, assume the address is
// used
// for different purposes and allow creating an additional cache entry. If there's at least one
// entry
// that hasn't been used for the same frame, then overwrite it, in order to keep the cache as
// small as
// possible. If the current texture is found in the cache, use that entry.
//
// For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else
// it was
// done in vain.
auto iter_range = textures_by_address.equal_range(address);
TexAddrCache::iterator iter = iter_range.first;
TexAddrCache::iterator oldest_entry = iter;
int temp_frameCount = 0x7fffffff;
TexAddrCache::iterator unconverted_copy = textures_by_address.end();
while (iter != iter_range.second)
{
TCacheEntry* entry = iter->second;
// Skip entries that are only left in our texture cache for the tmem cache emulation
if (entry->tmem_only)
{
++iter;
continue;
}
// Do not load strided EFB copies, they are not meant to be used directly.
// Also do not directly load EFB copies, which were partly overwritten.
if (entry->IsEfbCopy() && entry->native_width == nativeW && entry->native_height == nativeH &&
entry->memory_stride == entry->BytesPerRow() && !entry->may_have_overlapping_textures)
{
// EFB copies have slightly different rules as EFB copy formats have different
// meanings from texture formats.
if ((base_hash == entry->hash &&
(!isPaletteTexture || g_Config.backend_info.bSupportsPaletteConversion)) ||
IsPlayingBackFifologWithBrokenEFBCopies)
{
// TODO: We should check format/width/height/levels for EFB copies. Checking
// format is complicated because EFB copy formats don't exactly match
// texture formats. I'm not sure what effect checking width/height/levels
// would have.
if (!isPaletteTexture || !g_Config.backend_info.bSupportsPaletteConversion)
return entry;
// Note that we found an unconverted EFB copy, then continue. We'll
// perform the conversion later. Currently, we only convert EFB copies to
// palette textures; we could do other conversions if it proved to be
// beneficial.
unconverted_copy = iter;
}
else
{
// Aggressively prune EFB copies: if it isn't useful here, it will probably
// never be useful again. It's theoretically possible for a game to do
// something weird where the copy could become useful in the future, but in
// practice it doesn't happen.
iter = InvalidateTexture(iter);
continue;
}
}
else
{
// For normal textures, all texture parameters need to match
if (!entry->IsEfbCopy() && entry->hash == full_hash && entry->format == full_format &&
entry->native_levels >= tex_levels && entry->native_width == nativeW &&
entry->native_height == nativeH)
{
entry = DoPartialTextureUpdates(iter->second, &texMem[tlutaddr], tlutfmt);
return entry;
}
}
// Find the texture which hasn't been used for the longest time. Count paletted
// textures as the same texture here, when the texture itself is the same. This
// improves the performance a lot in some games that use paletted textures.
// Example: Sonic the Fighters (inside Sonic Gems Collection)
// Skip EFB copies here, so they can be used for partial texture updates
if (entry->frameCount != FRAMECOUNT_INVALID && entry->frameCount < temp_frameCount &&
!entry->IsEfbCopy() && !(isPaletteTexture && entry->base_hash == base_hash))
{
temp_frameCount = entry->frameCount;
oldest_entry = iter;
}
++iter;
}
if (unconverted_copy != textures_by_address.end())
{
TCacheEntry* decoded_entry =
ApplyPaletteToEntry(unconverted_copy->second, &texMem[tlutaddr], tlutfmt);
if (decoded_entry)
{
return decoded_entry;
}
}
// Search the texture cache for normal textures by hash
//
// If the texture was fully hashed, the address does not need to match. Identical duplicate
// textures cause unnecessary slowdowns
// Example: Tales of Symphonia (GC) uses over 500 small textures in menus, but only around 70
// different ones
if (textureCacheSafetyColorSampleSize == 0 ||
std::max(texture_size, palette_size) <= (u32)textureCacheSafetyColorSampleSize * 8)
{
auto hash_range = textures_by_hash.equal_range(full_hash);
TexHashCache::iterator hash_iter = hash_range.first;
while (hash_iter != hash_range.second)
{
TCacheEntry* entry = hash_iter->second;
// All parameters, except the address, need to match here
if (entry->format == full_format && entry->native_levels >= tex_levels &&
entry->native_width == nativeW && entry->native_height == nativeH)
{
entry = DoPartialTextureUpdates(hash_iter->second, &texMem[tlutaddr], tlutfmt);
return entry;
}
++hash_iter;
}
}
// If at least one entry was not used for the same frame, overwrite the oldest one
if (temp_frameCount != 0x7fffffff)
{
// pool this texture and make a new one later
InvalidateTexture(oldest_entry);
}
std::shared_ptr<HiresTexture> hires_tex;
if (g_ActiveConfig.bHiresTextures)
{
hires_tex = HiresTexture::Search(src_data, texture_size, &texMem[tlutaddr], palette_size, width,
height, texformat, use_mipmaps);
if (hires_tex)
{
const auto& level = hires_tex->m_levels[0];
if (level.width != width || level.height != height)
{
width = level.width;
height = level.height;
}
expandedWidth = level.width;
expandedHeight = level.height;
}
}
// how many levels the allocated texture shall have
const u32 texLevels = hires_tex ? (u32)hires_tex->m_levels.size() : tex_levels;
// We can decode on the GPU if it is a supported format and the flag is enabled.
// Currently we don't decode RGBA8 textures from Tmem, as that would require copying from both
// banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since
// there's no conversion between formats. In the future this could be extended with a separate
// shader, however.
bool decode_on_gpu = !hires_tex && g_ActiveConfig.UseGPUTextureDecoding() &&
g_texture_cache->SupportsGPUTextureDecode(texformat, tlutfmt) &&
!(from_tmem && texformat == TextureFormat::RGBA8);
// create the entry/texture
TextureConfig config;
config.width = width;
config.height = height;
config.levels = texLevels;
config.format = hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8;
ArbitraryMipmapDetector arbitrary_mip_detector;
TCacheEntry* entry = AllocateCacheEntry(config);
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
if (!entry)
return nullptr;
const u8* tlut = &texMem[tlutaddr];
if (hires_tex)
{
const auto& level = hires_tex->m_levels[0];
entry->texture->Load(0, level.width, level.height, level.row_length, level.data.get(),
level.data_size);
}
// Initialized to null because only software loading uses this buffer
u8* dst_buffer = nullptr;
if (!hires_tex)
{
if (decode_on_gpu)
{
u32 row_stride = bytes_per_block * (expandedWidth / bsw);
g_texture_cache->DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width,
height, expandedWidth, expandedHeight, row_stride, tlut,
tlutfmt);
}
else
{
size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight;
// Allocate memory for all levels at once
size_t total_texture_size = decoded_texture_size;
// For the downsample, we need 2 buffers; 1 is 1/4 of the original texture, the other 1/16
size_t mip_downsample_buffer_size = decoded_texture_size * 5 / 16;
size_t prev_level_size = decoded_texture_size;
for (u32 i = 1; i < tex_levels; ++i)
{
prev_level_size /= 4;
total_texture_size += prev_level_size;
}
// Add space for the downsampling at the end
total_texture_size += mip_downsample_buffer_size;
CheckTempSize(total_texture_size);
dst_buffer = temp;
if (!(texformat == TextureFormat::RGBA8 && from_tmem))
{
TexDecoder_Decode(dst_buffer, src_data, expandedWidth, expandedHeight, texformat, tlut,