forked from intel/gvk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTileCache.cpp
More file actions
284 lines (239 loc) · 10.9 KB
/
Copy pathTileCache.cpp
File metadata and controls
284 lines (239 loc) · 10.9 KB
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
#define IMGUI_DEFINE_MATH_OPERATORS
#include "TileCache.h"
#include <imgui_internal.h>
#include <SDL.h>
#include <SDL_opengl.h>
#include <algorithm>
#include <stdio.h>
//For GetProcessMemoryInfo so we can watch our RAM usage and limit allocation if too high
#ifdef _WIN32
#include <windows.h>
#include <psapi.h>
#pragma comment(lib, "psapi.lib")
#endif
#ifndef GL_FRAMEBUFFER
#define GL_FRAMEBUFFER 0x8D40
#define GL_COLOR_ATTACHMENT0 0x8CE0
#endif
int TileCache::MAX_CACHED_TILES = 100;
int TileCache::TILE_BUDGET_CEILING = 100; // raised to the real budget at startup
TileCache::TileCache()
: safeTileLimit_(MAX_CACHED_TILES) // Default to MAX_CACHED_TILES if not set
, maxOurProcessGB_(0.0f) // Set via SetMemoryLimits()
, totalSystemRAM_GB_(0.0f)
{
}
TileCache::~TileCache() {
// Free all allocated tiles
for (auto& pair : tiles_) {
if (pair.second.isAllocated) {
FreeTile(&pair.second);
}
}
}
void TileCache::Initialize() {
// Verify OpenGL functions are loaded (should have been done via InitializeGLFunctions() in main.cpp)
if (!glGenFramebuffers || !glBindFramebuffer || !glFramebufferTexture2D || !glCheckFramebufferStatus) {
printf("ERROR: TileCache requires OpenGL FBO functions to be loaded\n");
printf(" Make sure InitializeGLFunctions() was called before TileCache::Initialize()\n");
}
}
void TileCache::AllocateTile(Tile* tile) {
if (tile->isAllocated) return;
// Generate FBO and texture
glGenFramebuffers(1, &tile->fbo);
glGenTextures(1, &tile->texture);
// Configure texture.
// 16-bit RGB565 (2 bytes/pixel) instead of the old 32-bit SRGB8_ALPHA8 (4 bytes/pixel): halves tile
// memory (256MB -> 128MB per 8192^2 tile) with no change to tiling/render/composite. Safe to drop
// alpha: tiles are cleared opaque and tile the world with no gaps, so the composite blit never uses
// the tile's own alpha. NOTE: there is no 16-bit sRGB format, so this is a LINEAR store - the GPU no
// longer does the sRGB->linear convert on sample that SRGB8_ALPHA8 gave us, so colors may look slightly
// brighter/flatter. If banding or the color shift is objectionable, switch to GL_RGBA4 (still 2 bytes).
glBindTexture(GL_TEXTURE_2D, tile->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, TILE_SIZE, TILE_SIZE, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Attach texture to FBO
glBindFramebuffer(GL_FRAMEBUFFER, tile->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tile->texture, 0);
// Check FBO status
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
printf("ERROR: Tile FBO incomplete! Status: 0x%X (likely GPU out of memory)\n", status);
// Clean up failed resources
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &tile->fbo);
glDeleteTextures(1, &tile->texture);
tile->fbo = 0;
tile->texture = 0;
return; // Don't mark as allocated!
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
tile->isAllocated = true;
tile->isDirty = true;
}
void TileCache::FreeTile(Tile* tile) {
if (!tile->isAllocated) return;
glDeleteFramebuffers(1, &tile->fbo);
glDeleteTextures(1, &tile->texture);
tile->fbo = 0;
tile->texture = 0;
tile->isAllocated = false;
}
Tile* TileCache::GetTile(int tileX, int tileY) {
auto key = std::make_pair(tileX, tileY);
// Create tile if it doesn't exist
if (tiles_.find(key) == tiles_.end()) {
Tile& newTile = tiles_[key];
newTile.worldBounds = ImRect(
ImVec2(tileX * TILE_SIZE, tileY * TILE_SIZE),
ImVec2((tileX + 1) * TILE_SIZE, (tileY + 1) * TILE_SIZE)
);
}
Tile* tile = &tiles_[key];
// Lazy allocation: allocate GL resources on first access
// Note: Memory limits enforced by periodic check in main loop (every second)
// Periodic check evicts tiles proactively to stay under budget
if (!tile->isAllocated) {
AllocateTile(tile);
}
// Update access time for LRU
tile->lastAccessTime = ImGui::GetTime();
return tile;
}
void TileCache::MarkDirty(const ImRect& worldRect) {
// Calculate which tiles intersect this world rect
int minTileX = (int)(worldRect.Min.x / TILE_SIZE);
int minTileY = (int)(worldRect.Min.y / TILE_SIZE);
int maxTileX = (int)(worldRect.Max.x / TILE_SIZE);
int maxTileY = (int)(worldRect.Max.y / TILE_SIZE);
for (int ty = minTileY; ty <= maxTileY; ty++) {
for (int tx = minTileX; tx <= maxTileX; tx++) {
auto key = std::make_pair(tx, ty);
if (tiles_.find(key) != tiles_.end()) {
tiles_[key].isDirty = true;
}
}
}
}
void TileCache::MarkAllDirty() {
for (auto& pair : tiles_) {
pair.second.isDirty = true;
}
}
std::vector<Tile*> TileCache::GetVisibleTiles(ImVec2 cameraOffset, float cameraZoom,
ImVec2 viewportSize, ImVec2 worldSize) {
std::vector<Tile*> visibleTiles;
// Mark all tiles as not visible first
for (auto& pair : tiles_) {
pair.second.isVisible_ = false;
}
// Calculate visible world rect based on camera
// At zoom=1.0, viewport shows 1:1 world pixels (1280 screen px = 1280 world px)
// At zoom=4.0, viewport shows 1:4 world pixels (1280 screen px = 320 world px, zoomed in)
ImVec2 visibleWorldSize = viewportSize / cameraZoom;
ImVec2 visibleWorldMin = cameraOffset;
ImVec2 visibleWorldMax = cameraOffset + visibleWorldSize;
// Clamp to world bounds
visibleWorldMin.x = ImMax(0.0f, visibleWorldMin.x);
visibleWorldMin.y = ImMax(0.0f, visibleWorldMin.y);
visibleWorldMax.x = ImMin(worldSize.x, visibleWorldMax.x);
visibleWorldMax.y = ImMin(worldSize.y, visibleWorldMax.y);
// Calculate tile range
int minTileX = (int)(visibleWorldMin.x / TILE_SIZE);
int minTileY = (int)(visibleWorldMin.y / TILE_SIZE);
int maxTileX = (int)(visibleWorldMax.x / TILE_SIZE);
int maxTileY = (int)(visibleWorldMax.y / TILE_SIZE);
// Get all visible tiles (create metadata but DON'T allocate GL resources)
// Allocation happens on-demand during rendering (when nodesDirty=true)
for (int ty = minTileY; ty <= maxTileY; ty++) {
for (int tx = minTileX; tx <= maxTileX; tx++) {
auto key = std::make_pair(tx, ty);
// Create tile metadata if it doesn't exist (but don't allocate GL resources)
if (tiles_.find(key) == tiles_.end()) {
Tile& newTile = tiles_[key];
newTile.worldBounds = ImRect(
ImVec2(tx * TILE_SIZE, ty * TILE_SIZE),
ImVec2((tx + 1) * TILE_SIZE, (ty + 1) * TILE_SIZE)
);
// Note: isAllocated=false by default, so no GL allocation yet
}
Tile* tile = &tiles_[key];
tile->isVisible_ = true; // Protect from eviction (if allocated)
// Stamp the access time here, not just in GetTile(): the render loop reaches tiles through
// this function and AllocateTile(), so GetTile() never runs and lastAccessTime stayed at its
// constructor 0.0 for every tile. EvictLRU() then compared equal timestamps and fell back to
// map order, evicting by tile coordinate instead of by age.
tile->lastAccessTime = ImGui::GetTime();
visibleTiles.push_back(tile);
}
}
return visibleTiles;
}
// evictVisible: allow visible tiles to be freed once no hidden ones are left. Normally false - visible
// tiles are the whole point of the cache. Set true ONLY by the critical-memory shed in
// EnforceMemoryLimits(), where the alternative is the process being killed. Without it that shed is a
// no-op in exactly the case that needs it: zoomed out far enough to see everything, EVERY allocated tile
// is visible, so the protection below skips all of them and nothing is ever returned.
void TileCache::EvictLRU(bool evictVisible) {
// Count allocated tiles
int allocatedCount = GetAllocatedTileCount();
if (allocatedCount <= MAX_CACHED_TILES) return;
// Evict multiple tiles until we're under the limit
int tilesToEvict = allocatedCount - MAX_CACHED_TILES;
for (int i = 0; i < tilesToEvict; ++i) {
// Two passes: always prefer non-visible tiles, and only fall back to visible ones when the
// caller has allowed it and there is nothing else left to give.
auto lruIt = tiles_.end();
float oldestTime = FLT_MAX;
bool evictingVisible = false;
for (auto it = tiles_.begin(); it != tiles_.end(); ++it) {
// Never evict visible tiles!
if (it->second.isAllocated && !it->second.isVisible_ && it->second.lastAccessTime < oldestTime) {
oldestTime = it->second.lastAccessTime;
lruIt = it;
}
}
if (lruIt == tiles_.end() && evictVisible) {
for (auto it = tiles_.begin(); it != tiles_.end(); ++it) {
if (it->second.isAllocated && it->second.lastAccessTime < oldestTime) {
oldestTime = it->second.lastAccessTime;
lruIt = it;
evictingVisible = true;
}
}
}
if (lruIt != tiles_.end()) {
printf(" Evicting tile (%d, %d) - LRU%s (last access: %.2f)\n",
lruIt->first.first, lruIt->first.second,
evictingVisible ? " [VISIBLE - critical memory]" : "", oldestTime);
// Leave it dirty so it re-bakes if memory recovers; FreeTile only drops the GL resources.
FreeTile(&lruIt->second);
lruIt->second.isDirty = true;
} else {
break; // No more tiles to evict
}
}
}
int TileCache::GetAllocatedTileCount() const {
int count = 0;
for (const auto& pair : tiles_) {
if (pair.second.isAllocated) {
count++;
}
}
return count;
}
void TileCache::SetMemoryLimits(float maxOurProcessGB, float totalSystemRAM_GB) {
maxOurProcessGB_ = maxOurProcessGB;
totalSystemRAM_GB_ = totalSystemRAM_GB;
// Fallback tile limit (non-Windows, or if the runtime RAM checks can't run). Derived from the
// actual tile format rather than a magic number so it tracks TILE_SIZE / bytes-per-pixel.
// Note: the authoritative limit is MAX_CACHED_TILES, set by the caller from free RAM.
safeTileLimit_ = (int)(maxOurProcessGB / TileSizeGB());
printf("TileCache: Memory limits configured\n");
printf(" Fallback tile limit: ~%d tiles (active limit: %d)\n", safeTileLimit_, MAX_CACHED_TILES);
}