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
|
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <array>
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/TMEM.h"
namespace TMEM
{
struct TextureUnitState
{
enum class State
{
// Cache is invalid. Configuration has changed
INVALID,
// Valid, but not cached due to either being too big, or overlapping with another texture unit
VALID,
// Texture unit has cached all of the previous draw
CACHED,
};
struct BankConfig
{
u32 width = 0;
u32 height = 0;
u32 base = 0;
u32 size = 0;
bool Overlaps(const BankConfig& other) const;
};
BankConfig even;
BankConfig odd;
State state;
bool Overlaps(const TextureUnitState& other) const;
};
static u32 CalculateUnitSize(TextureUnitState::BankConfig bank_config);
static std::array<TextureUnitState, 8> s_unit;
// On TMEM configuration changed:
// 1. invalidate stage.
void ConfigurationChanged(TexUnitAddress bp_addr, u32 config)
{
TextureUnitState& unit_state = s_unit[bp_addr.GetUnitID()];
// If anything has changed, we can't assume existing state is still valid.
unit_state.state = TextureUnitState::State::INVALID;
// Note: BPStructs has already filtered out NOP changes before calling us
switch (bp_addr.Reg)
{
case TexUnitAddress::Register::SETIMAGE1:
{
// Image Type and Even bank's Cache Height, Cache Width, TMEM Offset
TexImage1 even = {.hex = config};
unit_state.even = {even.cache_width, even.cache_height, even.tmem_even << 5, 0};
break;
}
case TexUnitAddress::Register::SETIMAGE2:
{
// Odd bank's Cache Height, Cache Width, TMEM Offset
TexImage2 odd = {.hex = config};
unit_state.odd = {odd.cache_width, odd.cache_height, odd.tmem_odd << 5, 0};
break;
}
default:
// Something else has changed
return;
}
}
void InvalidateAll()
{
for (auto& unit : s_unit)
{
unit.state = TextureUnitState::State::INVALID;
}
}
// On invalidate cache:
// 1. invalidate all texture units.
void Invalidate([[maybe_unused]] u32 param)
{
// The exact arguments of Invalidate commands is currently unknown.
// It appears to contain the TMEM address and a size.
// For simplicity, we will just invalidate everything
InvalidateAll();
}
// On bind:
// 1. use mipmapping/32bit status to calculate final sizes
// 2. if texture size is small enough to fit in region mark as cached.
// otherwise, mark as valid
void Bind(u32 unit, int width, int height, bool is_mipmapped, bool is_32_bit)
{
TextureUnitState& unit_state = s_unit[unit];
// All textures use the even bank.
// It holds the level 0 mipmap (and other even mipmap LODs, if mipmapping is enabled)
unit_state.even.size = CalculateUnitSize(unit_state.even);
bool fits = (width * height * 32U) <= unit_state.even.size;
if (is_mipmapped || is_32_bit)
{
// And the odd bank is enabled when either mipmapping is enabled or the texture is 32 bit
// It holds the Alpha and Red channels of 32 bit textures or the odd layers of a mipmapped
// texture
unit_state.odd.size = CalculateUnitSize(unit_state.odd);
fits = fits && (width * height * 32U) <= unit_state.odd.size;
}
else
{
unit_state.odd.size = 0;
}
if (is_mipmapped)
{
// TODO: This is what games appear to expect from hardware. But seems odd, as it doesn't line up
// with how much extra memory is required for mipmapping, just 33% more.
// Hardware testing is required to see exactly what gets used.
// When mipmapping is enabled, the even bank is doubled in size
// The extended region holds the remaining even mipmap layers
unit_state.even.size *= 2;
if (is_32_bit)
{
// When a 32bit texture is mipmapped, the odd bank is also doubled in size
unit_state.odd.size *= 2;
}
}
unit_state.state = fits ? TextureUnitState::State::CACHED : TextureUnitState::State::VALID;
}
static u32 CalculateUnitSize(TextureUnitState::BankConfig bank_config)
{
u32 width = bank_config.width;
u32 height = bank_config.height;
// These are the only cache sizes supported by the sdk
if (width == height)
{
switch (width)
{
case 3: // 32KB
return 32 * 1024;
case 4: // 128KB
return 128 * 1024;
case 5: // 512KB
return 512 * 1024;
default:
break;
}
}
// However, the registers allow a much larger amount of configurablity.
// Maybe other sizes are broken?
// Until hardware tests are done, this is a guess at the size algorithm
return 512 * (1 << width) * (1 << height);
}
bool TextureUnitState::BankConfig::Overlaps(const BankConfig& other) const
{
if (size == 0 || other.size == 0)
return false;
return (base <= other.base && (base + size) > other.base) ||
(other.base <= base && (other.base + other.size) > base);
}
bool TextureUnitState::Overlaps(const TextureUnitState& other) const
{
if (state == TextureUnitState::State::INVALID || other.state == TextureUnitState::State::INVALID)
return false;
return even.Overlaps(other.even) || even.Overlaps(other.odd) || odd.Overlaps(other.even) ||
odd.Overlaps(other.odd);
}
// Scans though active texture units checks for overlaps.
void FinalizeBinds(BitSet32 used_textures)
{
for (u32 i : used_textures)
{
if (s_unit[i].even.Overlaps(s_unit[i].odd))
{ // Self-overlap
s_unit[i].state = TextureUnitState::State::VALID;
}
for (size_t j = 0; j < s_unit.size(); j++)
{
if (j != i && s_unit[i].Overlaps(s_unit[j]))
{
// There is an overlap, downgrade both from CACHED
// (for there to be an overlap, both must have started as valid or cached)
s_unit[i].state = TextureUnitState::State::VALID;
s_unit[j].state = TextureUnitState::State::VALID;
}
}
}
}
bool IsCached(u32 unit)
{
return s_unit[unit].state == TextureUnitState::State::CACHED;
}
bool IsValid(u32 unit)
{
return s_unit[unit].state != TextureUnitState::State::INVALID;
}
} // namespace TMEM
|