Skip to content

Commit 899c7be

Browse files
committed
refactor(gpu_mem): replace time-based resource reuse with frame-delay buckets
- Replace unreliable timestamp-based resource reclamation with a 3-frame ring buffer mechanism (Frame Buckets). - Remove dependency on GPU fence signaling to avoid issues with incomplete RHI implementations. - Implement begin_frame() to manage resource transitions from Pending (cooling down) to Ready (available for reuse) stages. - Retain DECAY_TIME as a long-term garbage collection policy for idle resources. - Update print_info to provide detailed breakdown of allocated, committed, and ready/pending free resources.
1 parent a71c5d5 commit 899c7be

4 files changed

Lines changed: 121 additions & 141 deletions

File tree

pathfinder/core/d3d11/renderer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ void RendererD3D11::set_up_pipelines() {
329329
void RendererD3D11::draw(const std::shared_ptr<SceneBuilder> &_scene_builder, bool _clear_dst_texture) {
330330
clear_dest_texture = _clear_dst_texture;
331331

332+
allocator->begin_frame();
333+
332334
auto *scene_builder = static_cast<SceneBuilderD3D11 *>(_scene_builder.get());
333335

334336
if (scene_builder->built_segments.draw_segments.points.empty()) {

pathfinder/core/d3d9/renderer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ void RendererD3D9::draw(const std::shared_ptr<SceneBuilder> &_scene_builder, boo
392392

393393
reallocate_alpha_tile_pages_if_necessary();
394394

395+
allocator->begin_frame();
396+
395397
auto encoder = device->create_command_encoder("upload & draw fills, tiles");
396398

397399
uint64_t *fill_vertex_buffer_id = nullptr;

pathfinder/gpu_mem/allocator.cpp

Lines changed: 89 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,30 @@ uint64_t GpuMemoryAllocator::allocate_buffer(size_t byte_size, BufferType type,
99

1010
auto descriptor = BufferDescriptor{type, byte_size, MemoryProperty::HostVisibleAndCoherent};
1111

12-
auto now = std::chrono::steady_clock::now();
13-
14-
// Try to find a free object.
15-
for (int free_object_index = 0; free_object_index < free_objects.size(); free_object_index++) {
16-
auto free_obj = free_objects[free_object_index];
17-
18-
bool found_free_obj = false;
12+
// Try to find a free object in the idle pool.
13+
// Anything in idle_pool is confirmed safe by frame-delay.
14+
for (int i = 0; i < (int)idle_pool.size(); i++) {
15+
auto& free_obj = idle_pool[i];
1916

2017
if (free_obj.kind == FreeObjectKind::Buffer && free_obj.buffer_allocation.descriptor == descriptor) {
21-
std::chrono::duration<double> duration = now - free_obj.timestamp;
22-
23-
// Check if this free buffer can be reused.
24-
if (duration.count() >= REUSE_TIME) {
25-
found_free_obj = true;
26-
}
27-
}
28-
29-
if (!found_free_obj) {
30-
continue;
31-
}
18+
uint64_t id = free_obj.id;
19+
BufferAllocation allocation = free_obj.buffer_allocation;
3220

33-
// Reuse this free object.
34-
free_objects.erase(free_objects.begin() + free_object_index);
21+
idle_pool.erase(idle_pool.begin() + i);
3522

36-
uint64_t id = free_obj.id;
37-
BufferAllocation allocation = free_obj.buffer_allocation;
23+
allocation.tag = tag;
24+
allocation.buffer->set_label(tag);
3825

39-
// Update tag. Also update GPU debug marker.
40-
allocation.tag = tag;
41-
allocation.buffer->set_label(tag);
26+
bytes_committed += byte_size;
27+
active_buffers[id] = allocation;
4228

43-
bytes_committed += byte_size;
44-
buffers_in_use[id] = allocation;
45-
46-
return id;
29+
return id;
30+
}
4731
}
4832

49-
// Create a new buffer.
50-
5133
auto buffer = device->create_buffer(descriptor, tag);
52-
53-
auto id = next_buffer_id;
54-
next_buffer_id += 1;
55-
56-
buffers_in_use[id] = BufferAllocation{buffer, descriptor, tag};
34+
auto id = next_buffer_id++;
35+
active_buffers[id] = BufferAllocation{buffer, descriptor, tag};
5736

5837
bytes_allocated += byte_size;
5938
bytes_committed += byte_size;
@@ -65,40 +44,31 @@ uint64_t GpuMemoryAllocator::allocate_texture(Vec2I size, TextureFormat format,
6544
assert(!size.is_any_zero());
6645

6746
auto descriptor = TextureDescriptor{size, format};
68-
6947
auto byte_size = descriptor.byte_size();
7048

71-
// Try to find a free object.
72-
for (int free_object_index = 0; free_object_index < free_objects.size(); free_object_index++) {
73-
auto free_obj = free_objects[free_object_index];
74-
75-
if (!(free_obj.kind == FreeObjectKind::Texture && free_obj.texture_allocation.descriptor == descriptor)) {
76-
continue;
77-
}
49+
// Try to find a free object in the idle pool.
50+
for (int i = 0; i < (int)idle_pool.size(); i++) {
51+
auto& free_obj = idle_pool[i];
7852

79-
// Reuse this free object.
80-
free_objects.erase(free_objects.begin() + free_object_index);
53+
if (free_obj.kind == FreeObjectKind::Texture && free_obj.texture_allocation.descriptor == descriptor) {
54+
uint64_t id = free_obj.id;
55+
auto allocation = free_obj.texture_allocation;
8156

82-
uint64_t id = free_obj.id;
83-
auto allocation = free_obj.texture_allocation;
57+
idle_pool.erase(idle_pool.begin() + i);
8458

85-
// Update tag. Also update GPU debug marker.
86-
allocation.tag = tag;
87-
allocation.texture->set_label(tag);
59+
allocation.tag = tag;
60+
allocation.texture->set_label(tag);
8861

89-
bytes_committed += byte_size;
90-
textures_in_use[id] = allocation;
62+
bytes_committed += byte_size;
63+
active_textures[id] = allocation;
9164

92-
return id;
65+
return id;
66+
}
9367
}
9468

95-
// Create a new texture.
96-
9769
auto texture = device->create_texture(descriptor, tag);
98-
auto id = next_texture_id;
99-
next_texture_id += 1;
100-
101-
textures_in_use[id] = TextureAllocation{texture, descriptor, tag};
70+
auto id = next_texture_id++;
71+
active_textures[id] = TextureAllocation{texture, descriptor, tag};
10272

10373
bytes_allocated += byte_size;
10474
bytes_committed += byte_size;
@@ -107,131 +77,124 @@ uint64_t GpuMemoryAllocator::allocate_texture(Vec2I size, TextureFormat format,
10777
}
10878

10979
void GpuMemoryAllocator::free_buffer(uint64_t id) {
110-
if (buffers_in_use.find(id) == buffers_in_use.end()) {
80+
auto it = active_buffers.find(id);
81+
if (it == active_buffers.end()) {
11182
Logger::error("Attempted to free unallocated buffer!");
11283
return;
11384
}
11485

115-
auto allocation = buffers_in_use[id];
116-
117-
buffers_in_use.erase(id);
86+
auto allocation = it->second;
87+
active_buffers.erase(it);
11888

11989
bytes_committed -= allocation.buffer->get_size();
12090

121-
// Put the buffer back to the free objects.
122-
12391
FreeObject free_obj;
12492
free_obj.timestamp = std::chrono::steady_clock::now();
12593
free_obj.kind = FreeObjectKind::Buffer;
12694
free_obj.id = id;
12795
free_obj.buffer_allocation = allocation;
12896

129-
free_objects.push_back(free_obj);
97+
// Put into current frame bucket.
98+
pending_buckets[current_frame_index % MAX_FRAMES_IN_FLIGHT].objects.push_back(free_obj);
13099
}
131100

132101
void GpuMemoryAllocator::free_texture(uint64_t id) {
133-
if (textures_in_use.find(id) == textures_in_use.end()) {
102+
auto it = active_textures.find(id);
103+
if (it == active_textures.end()) {
134104
Logger::error("Attempted to free unallocated texture!");
135105
return;
136106
}
137107

138-
auto allocation = textures_in_use[id];
139-
140-
textures_in_use.erase(id);
108+
auto allocation = it->second;
109+
active_textures.erase(it);
141110

142-
auto byte_size = allocation.descriptor.byte_size();
143-
bytes_committed -= byte_size;
111+
bytes_committed -= allocation.descriptor.byte_size();
144112

145113
FreeObject free_obj;
146114
free_obj.timestamp = std::chrono::steady_clock::now();
147115
free_obj.kind = FreeObjectKind::Texture;
148116
free_obj.id = id;
149117
free_obj.texture_allocation = allocation;
150118

151-
free_objects.push_back(free_obj);
119+
// Put into current frame bucket.
120+
pending_buckets[current_frame_index % MAX_FRAMES_IN_FLIGHT].objects.push_back(free_obj);
152121
}
153122

154123
std::shared_ptr<Buffer> GpuMemoryAllocator::get_buffer(uint64_t id) {
155-
if (buffers_in_use.find(id) == buffers_in_use.end()) {
124+
auto it = active_buffers.find(id);
125+
if (it == active_buffers.end()) {
156126
Logger::error("Attempted to get unallocated buffer!");
157127
return nullptr;
158128
}
159-
160-
return buffers_in_use[id].buffer;
129+
return it->second.buffer;
161130
}
162131

163132
std::shared_ptr<Texture> GpuMemoryAllocator::get_texture(uint64_t id) {
164-
if (textures_in_use.find(id) == textures_in_use.end()) {
133+
auto it = active_textures.find(id);
134+
if (it == active_textures.end()) {
165135
Logger::error("Attempted to get unallocated texture!");
166136
return nullptr;
167137
}
138+
return it->second.texture;
139+
}
140+
141+
void GpuMemoryAllocator::begin_frame() {
142+
// Move to next frame.
143+
current_frame_index++;
168144

169-
return textures_in_use[id].texture;
145+
// Reclaim the bucket we are about to overwrite.
146+
// This bucket contains objects that have been "cooling down" for MAX_FRAMES_IN_FLIGHT frames.
147+
auto& bucket = pending_buckets[current_frame_index % MAX_FRAMES_IN_FLIGHT];
148+
if (!bucket.objects.empty()) {
149+
idle_pool.insert(idle_pool.end(), bucket.objects.begin(), bucket.objects.end());
150+
bucket.objects.clear();
151+
}
170152
}
171153

172154
void GpuMemoryAllocator::purge_if_needed() {
173155
auto now = std::chrono::steady_clock::now();
174-
175156
bool purge_happened = false;
176157

177-
while (true) {
178-
if (free_objects.empty()) {
179-
break;
180-
}
181-
182-
// Has to be copied by value.
183-
auto oldest_free_obj = free_objects.front();
184-
185-
std::chrono::duration<double> duration = now - oldest_free_obj.timestamp;
186-
// If the first free object has not decayed, so do the rest free objects.
187-
if (duration.count() < DECAY_TIME) {
188-
break;
189-
}
190-
191-
switch (free_objects.front().kind) {
192-
case FreeObjectKind::Buffer: {
193-
Logger::debug("Purging buffer: " + oldest_free_obj.buffer_allocation.tag);
194-
195-
free_objects.erase(free_objects.begin());
196-
bytes_allocated -= oldest_free_obj.buffer_allocation.buffer->get_size();
197-
198-
purge_happened = true;
199-
} break;
200-
case FreeObjectKind::Texture: {
201-
Logger::debug("Purging texture: " + oldest_free_obj.texture_allocation.tag);
202-
203-
free_objects.erase(free_objects.begin());
204-
bytes_allocated -= oldest_free_obj.texture_allocation.descriptor.byte_size();
205-
206-
purge_happened = true;
207-
} break;
208-
default:
209-
break;
158+
for (auto it = idle_pool.begin(); it != idle_pool.end();) {
159+
std::chrono::duration<double> duration = now - it->timestamp;
160+
if (duration.count() >= DECAY_TIME) {
161+
if (it->kind == FreeObjectKind::Buffer) {
162+
bytes_allocated -= it->buffer_allocation.buffer->get_size();
163+
} else {
164+
bytes_allocated -= it->texture_allocation.descriptor.byte_size();
165+
}
166+
it = idle_pool.erase(it);
167+
purge_happened = true;
168+
} else {
169+
++it;
210170
}
211171
}
212172

213-
if (purge_happened) {
214-
print_info();
215-
}
173+
if (purge_happened) print_info();
216174
}
217175

218176
void GpuMemoryAllocator::print_info() {
219-
size_t texture_count = textures_in_use.size();
220-
size_t buffer_count = buffers_in_use.size();
221-
size_t free_object_count = free_objects.size();
177+
size_t texture_count = active_textures.size();
178+
size_t buffer_count = active_buffers.size();
179+
180+
size_t idle_count = idle_pool.size();
181+
size_t pending_count = 0;
182+
for (const auto& bucket : pending_buckets) {
183+
pending_count += bucket.objects.size();
184+
}
222185

223186
Logger::debug("Current status: ALLOCATED " + std::to_string(int(bytes_allocated / 1024.f)) + " KB | COMMITTED " +
224-
std::to_string(int(bytes_committed / 1024.f)) + " KB | Textures " + std::to_string(texture_count) +
225-
" | Buffers " + std::to_string(buffer_count) + " | Free objects " +
226-
std::to_string(free_object_count));
187+
std::to_string(int(bytes_committed / 1024.f)) + " KB | Active textures " + std::to_string(texture_count) +
188+
" | Active buffers " + std::to_string(buffer_count) + " | Free (Idle/Pending) " +
189+
std::to_string(idle_count) + "/" + std::to_string(pending_count));
227190

228-
for (auto& allocation : textures_in_use) {
229-
Logger::debug("Texture " + std::to_string(allocation.first) + ": " + allocation.second.tag + " - " +
191+
for (auto& allocation : active_textures) {
192+
Logger::debug("Active texture " + std::to_string(allocation.first) + ": " + allocation.second.tag + " - " +
230193
std::to_string(int(allocation.second.descriptor.byte_size() / 1024.f)) + " KB");
231194
}
232195

233-
for (auto& allocation : buffers_in_use) {
234-
Logger::debug("Buffer " + std::to_string(allocation.first) + ": " + allocation.second.tag + " - " +
196+
for (auto& allocation : active_buffers) {
197+
Logger::debug("Active Buffer " + std::to_string(allocation.first) + ": " + allocation.second.tag + " - " +
235198
std::to_string(int(allocation.second.descriptor.size / 1024.f)) + " KB");
236199
}
237200
}

0 commit comments

Comments
 (0)