Unravel Engine C++ Reference
Loading...
Searching...
No Matches
batch_collector.cpp
Go to the documentation of this file.
1#include "batch_collector.h"
2
3#include <algorithm>
4#include <chrono>
5#include <sstream>
6#include <iomanip>
7
8namespace unravel
9{
10
11// batch_stats implementation
12
26
28{
29 if (total_batches > 0)
30 {
31 average_batch_size = static_cast<float>(total_instances) / static_cast<float>(total_batches);
33 }
34 else
35 {
36 average_batch_size = 0.0f;
38 }
39
41}
42
43auto batch_stats::to_string() const -> std::string
44{
45 std::ostringstream oss;
46 oss << std::fixed << std::setprecision(2);
47 oss << "batch_stats{";
48 oss << "batches=" << total_batches;
49 oss << ", instances=" << total_instances;
50 oss << ", saved_calls=" << draw_calls_saved;
51 oss << ", efficiency=" << batching_efficiency;
52 oss << ", avg_size=" << average_batch_size;
53 oss << ", collection_time=" << collection_time_ms << "ms";
54 oss << ", prep_time=" << preparation_time_ms << "ms";
55 oss << ", memory=" << (instance_buffer_memory_used / 1024) << "KB";
56 if (split_batches > 0)
57 {
58 oss << ", splits=" << split_batches;
59 }
60 oss << "}";
61 return oss.str();
62}
63
64// batch_group implementation
65
67 : key(key)
68{
69}
70
72{
73 instances.add_instance(instance);
74}
75
76void batch_group::calculate_camera_distance(const math::vec3& camera_pos)
77{
78 if (instances.empty())
79 {
80 camera_distance = 0.0f;
81 return;
82 }
83
84 // Calculate average position of all instances
85 math::vec3 average_position(0.0f);
86 for (const auto& instance : instances)
87 {
88 if (!instance.world_transform_ptr)
89 {
90 continue; // Skip invalid instances
91 }
92
93 // Extract position from world transform (translation part)
94 const auto& transform = *instance.world_transform_ptr;
95 math::vec3 position(transform[3][0], transform[3][1], transform[3][2]);
96 average_position += position;
97 }
98
99 average_position /= static_cast<float>(instances.size());
100
101 // Calculate distance from camera
102 math::vec3 distance_vec = average_position - camera_pos;
103 camera_distance = math::length(distance_vec);
104}
105
106auto batch_group::is_valid() const -> bool
107{
108 return key.is_valid() && !instances.empty();
109}
110
112{
114}
115
116// batch_collector implementation
117
119{
120 stats_.reset();
121}
122
124{
125 if (!key.is_valid() || !instance.is_valid())
126 {
127 return;
128 }
129
130 auto start_time = std::chrono::high_resolution_clock::now();
131
132 auto& batch_group = get_or_create_batch_group(key);
133 batch_group.add_instance(instance);
134
135 if (profiling_enabled_)
136 {
137 auto end_time = std::chrono::high_resolution_clock::now();
138 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
139 stats_.collection_time_ms += static_cast<float>(duration.count()) / 1000.0f;
140 }
141}
142
143void batch_collector::collect_renderable(const batch_key& key, const math::mat4& world_transform)
144{
145 batch_instance instance(&world_transform);
146 collect_renderable(key, instance);
147}
148
150{
151 if (batch_groups_.empty())
152 {
153 return;
154 }
155
156 auto start_time = std::chrono::high_resolution_clock::now();
157
158 // Clear previous preparation
159 prepared_batches_.clear();
160 prepared_batches_.reserve(batch_groups_.size());
161
162 // Collect all batch groups
163 for (auto& [key, group] : batch_groups_)
164 {
165 if (group.is_valid())
166 {
167 prepared_batches_.push_back(&group);
168 }
169 }
170
171 // Split large batches if needed
172 if (context.max_instances_per_batch > 0)
173 {
174 split_large_batches(context);
175 }
176
177 // Calculate camera distances if needed
178 if (context.enable_distance_sorting)
179 {
180 calculate_camera_distances(context.camera_position);
181 }
182
183 // Sort batches for optimal rendering
184 sort_batches(context);
185
186 // Update statistics
187 update_statistics();
188
189 if (profiling_enabled_ && context.enable_profiling)
190 {
191 auto end_time = std::chrono::high_resolution_clock::now();
192 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
193 stats_.preparation_time_ms = static_cast<float>(duration.count()) / 1000.0f;
194 }
195}
196
198{
199 return prepared_batches_;
200}
201
203{
204 batch_groups_.clear();
205 prepared_batches_.clear();
206 stats_.reset();
207}
208
210{
211 return stats_;
212}
213
215{
216 return batch_groups_.size();
217}
218
220{
221 size_t total = 0;
222 for (const auto& [key, group] : batch_groups_)
223 {
224 total += group.instances.size();
225 }
226 return total;
227}
228
230{
231 return !batch_groups_.empty();
232}
233
235{
236 max_instances_per_batch_ = max_instances;
237}
238
240{
241 profiling_enabled_ = enabled;
242}
243
244void batch_collector::sort_batches(const submit_context& context)
245{
246 std::sort(prepared_batches_.begin(), prepared_batches_.end(),
247 [&context](const batch_group* a, const batch_group* b) -> bool
248 {
249 // Primary sort: by material (for state changes)
250 if (a->key.material_ptr.get() != b->key.material_ptr.get())
251 {
252 return a->key.material_ptr.get() < b->key.material_ptr.get();
253 }
254
255 // Secondary sort: by mesh (for vertex buffer changes)
256 if (a->key.mesh_ptr.get() != b->key.mesh_ptr.get())
257 {
258 return a->key.mesh_ptr.get() < b->key.mesh_ptr.get();
259 }
260
261 // Tertiary sort: by LOD (render higher detail first)
262 if (a->key.lod_index != b->key.lod_index)
263 {
264 return a->key.lod_index < b->key.lod_index;
265 }
266
267 // Quaternary sort: by submesh index
268 if (a->key.submesh_index != b->key.submesh_index)
269 {
270 return a->key.submesh_index < b->key.submesh_index;
271 }
272
273 // Final sort: by distance if enabled (far to near for opaque objects)
274 if (context.enable_distance_sorting)
275 {
276 return a->camera_distance > b->camera_distance;
277 }
278
279 return false; // Equal
280 });
281}
282
283void batch_collector::split_large_batches(const submit_context& context)
284{
285 if (context.max_instances_per_batch == 0)
286 {
287 return;
288 }
289
290 batch_list_t new_batches;
291
292 for (auto* batch : prepared_batches_)
293 {
294 if (batch->instances.size() <= context.max_instances_per_batch)
295 {
296 new_batches.push_back(batch);
297 continue;
298 }
299
300 // Split this batch
301 stats_.split_batches++;
302
303 size_t instances_processed = 0;
304 const size_t total_instances = batch->instances.size();
305
306 while (instances_processed < total_instances)
307 {
308 size_t instances_in_split = std::min(
309 static_cast<size_t>(context.max_instances_per_batch),
310 total_instances - instances_processed
311 );
312
313 // Create a new batch group for this split
314 auto split_batch = std::make_unique<batch_group>(batch->key);
315 split_batch->is_split_batch = true;
316 split_batch->camera_distance = batch->camera_distance;
317
318 // Copy instances to the split batch
319 for (size_t i = 0; i < instances_in_split; ++i)
320 {
321 split_batch->add_instance(batch->instances[instances_processed + i]);
322 }
323
324 instances_processed += instances_in_split;
325
326 // Store the split batch (we'll need to manage memory for these)
327 // For now, we'll just add to the original batch - this is a simplification
328 // In a full implementation, we'd need proper memory management for split batches
329 new_batches.push_back(batch);
330 break; // Simplified: just use the original batch for now
331 }
332 }
333
334 prepared_batches_ = std::move(new_batches);
335}
336
337void batch_collector::calculate_camera_distances(const math::vec3& camera_pos)
338{
339 for (auto* batch : prepared_batches_)
340 {
341 batch->calculate_camera_distance(camera_pos);
342 }
343}
344
345void batch_collector::update_statistics()
346{
347 stats_.total_batches = static_cast<uint32_t>(prepared_batches_.size());
348 stats_.total_instances = static_cast<uint32_t>(get_instance_count());
349
350 // Calculate memory usage
351 stats_.instance_buffer_memory_used = 0;
352 for (const auto* batch : prepared_batches_)
353 {
354 stats_.instance_buffer_memory_used += batch->get_gpu_memory_size();
355 }
356
357 // Calculate derived statistics
358 stats_.calculate_derived_stats();
359}
360
361auto batch_collector::get_or_create_batch_group(const batch_key& key) -> batch_group&
362{
363 auto it = batch_groups_.find(key);
364 if (it != batch_groups_.end())
365 {
366 return it->second;
367 }
368
369 // Create new batch group
370 auto [inserted_it, success] = batch_groups_.emplace(key, batch_group(key));
371 return inserted_it->second;
372}
373
374// Static member definition
375bool batch_collector::enable_static_mesh_batching_ = true;
376
377auto batch_collector::is_static_mesh_batching_enabled() -> bool
378{
379 return enable_static_mesh_batching_;
380}
381
382void batch_collector::set_static_mesh_batching_enabled(bool enabled)
383{
384 enable_static_mesh_batching_ = enabled;
385}
386
387} // namespace unravel
entt::handle b
entt::handle a
void clear()
Clear all collected data and reset for next frame.
std::vector< batch_group * > batch_list_t
auto get_stats() const -> const batch_stats &
Get current statistics.
batch_collector()
Default constructor.
void set_max_instances_per_batch(uint32_t max_instances)
Set maximum instances per batch.
auto get_batch_count() const -> size_t
Get number of collected batches.
void prepare_batches(const submit_context &context)
Prepare batches for rendering (sort, split, optimize)
void set_profiling_enabled(bool enabled)
Enable or disable performance profiling.
auto has_batches() const -> bool
Check if any batches have been collected.
void collect_renderable(const batch_key &key, const batch_instance &instance)
Collect a renderable object for batching.
auto get_prepared_batches() const -> const batch_list_t &
Get prepared batches for rendering.
auto get_instance_count() const -> size_t
Get total number of instances collected.
auto size() const -> size_t
Get number of instances.
auto empty() const -> bool
Check if collection is empty.
auto get_gpu_memory_size() const -> size_t
Get memory size required for GPU data.
void add_instance(const batch_instance &instance)
Add an instance to the collection.
Hash specialization for batch_key to enable use in std::unordered_map.
A group of instances that can be rendered together.
auto is_valid() const -> bool
Check if this batch is valid for rendering.
auto get_gpu_memory_size() const -> size_t
Get memory size required for GPU data.
void add_instance(const batch_instance &instance)
Add an instance to this batch.
batch_instance_collection instances
Collection of instances in this batch.
void calculate_camera_distance(const math::vec3 &camera_pos)
Calculate distance from camera for sorting.
batch_group()=default
Default constructor.
batch_key key
Key identifying this batch (mesh, material, LOD, submesh)
float camera_distance
Distance from camera (for sorting)
Instance data for a single object in a batch.
auto is_valid() const -> bool
Check if this instance has valid data.
Batch key structure for grouping compatible draw calls.
Definition batch_key.h:22
auto is_valid() const -> bool
Check if this batch key is valid.
Definition batch_key.cpp:44
Statistics for batch collection and rendering performance.
uint32_t total_instances
Total number of instances across all batches.
uint32_t split_batches
Number of batches that were split due to size limits.
float average_batch_size
Average number of instances per batch.
float batching_efficiency
Batching efficiency (instances / batches)
size_t instance_buffer_memory_used
Memory used for instance buffers (bytes)
float preparation_time_ms
Time spent preparing batches (milliseconds)
auto to_string() const -> std::string
Get string representation for debugging.
void calculate_derived_stats()
Calculate derived statistics (efficiency, averages)
uint32_t draw_calls_saved
Number of draw calls saved by batching (instances - batches)
void reset()
Reset all statistics to zero.
float submission_time_ms
Time spent submitting batches (milliseconds)
uint32_t total_batches
Total number of batches created.
float collection_time_ms
Time spent collecting instances (milliseconds)
Context information for batch submission.
uint32_t max_instances_per_batch
Maximum instances per batch (0 = no limit)
math::vec3 camera_position
Camera position for distance-based sorting.
bool enable_distance_sorting
Enable distance-based sorting for transparency.
bool enable_profiling
Enable performance profiling.