Unravel Engine C++ Reference
Loading...
Searching...
No Matches
thumbnail_manager.cpp
Go to the documentation of this file.
1#include "thumbnail_manager.h"
2
8#include <engine/engine.h>
9#include <engine/events.h>
14
17
21#include <graphics/graphics.h>
23#include <graphics/texture.h>
24#include <seq/seq.h>
25
27#include <filesystem/watcher.h>
28
29#include <algorithm>
30
31namespace unravel
32{
33
34namespace
35{
36
37const usize32_t k_thumbnail_size{256, 256};
38
39auto capture_thumbnail_snapshot(const gfx::frame_buffer::ptr& source) -> gfx::texture::ptr
40{
41 if(!source)
42 {
43 return nullptr;
44 }
45
46 const auto& src_tex = source->get_texture(0);
47 if(!src_tex || !src_tex->is_valid())
48 {
49 return nullptr;
50 }
51
52 const auto src_size = source->get_size();
53 const auto blit_width = static_cast<uint16_t>(std::min(src_size.width, k_thumbnail_size.width));
54 const auto blit_height = static_cast<uint16_t>(std::min(src_size.height, k_thumbnail_size.height));
55 if(blit_width == 0 || blit_height == 0)
56 {
57 return nullptr;
58 }
59
60 auto snapshot = std::make_shared<gfx::texture>(k_thumbnail_size.width,
61 k_thumbnail_size.height,
62 false,
63 1,
64 gfx::texture_format::RGBA8,
65 BGFX_TEXTURE_BLIT_DST | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
66
67 if(!snapshot->is_valid())
68 {
69 return nullptr;
70 }
71
72 gfx::render_pass pass("Thumbnail/Capture Blit");
73 gfx::blit(pass.id,
74 snapshot->native_handle(),
75 0,
76 0,
77 src_tex->native_handle(),
78 0,
79 0,
80 blit_width,
81 blit_height);
82 return snapshot;
83}
84
85auto render_thumbnail_preview_scene(rendering_system& rpath, scene& scn, delta_t dt, int frames) -> gfx::frame_buffer::ptr
86{
88 scn.registry->view<camera_component>().each(
89 [&](auto e, auto&& camera_comp)
90 {
91 auto handle = scn.create_handle(e);
92 for(int i = 0; i < frames; ++i)
93 {
94 rpath.on_frame_before_render(scn, dt);
95 auto new_fbo = rpath.render_scene(handle, camera_comp, scn, dt, false);
96 if(new_fbo)
97 {
98 captured = std::move(new_fbo);
99 }
100 }
101 });
102 return captured;
103}
104
105template<typename T>
106auto make_thumbnail(thumbnail_manager::generator& gen, const asset_handle<T>& asset, int frames = 2, delta_t dt = delta_t(0.016667f)) -> gfx::texture::ptr
107{
108 auto& thumbnail = gen.thumbnails[asset.uid()];
109 auto current_fbo = thumbnail.get();
110
111 if(gen.remaining > 0 && thumbnail.needs_regeneration && asset.is_ready())
112 {
113 try
114 {
115 auto& ctx = engine::context();
116 auto& rpath = ctx.get_cached<rendering_system>();
117
118 auto& scn = gen.get_scene();
119 rpath.release_pipeline_resources(scn);
120 scn.unload();
121 auto result = defaults::create_default_3d_scene_for_asset_preview(ctx, scn, asset, k_thumbnail_size, false);
122
123 for(int i = 0; i < frames; i++)
124 {
125 rpath.on_frame_update(scn, dt);
126 rpath.on_frame_before_render(scn, dt);
127 }
128
130
131 if(auto captured = render_thumbnail_preview_scene(rpath, scn, dt, frames))
132 {
133 if(auto snapshot = capture_thumbnail_snapshot(captured))
134 {
135 thumbnail.pending_snapshot = std::move(snapshot);
136 gen.remaining--;
137 }
138 }
139
140 }
141 catch(const std::exception& e)
142 {
143 APPLOG_ERROR("{}", e.what());
144 }
145 }
146
147 return current_fbo;
148}
149
150template<typename T>
151auto get_thumbnail_impl(thumbnail_manager::generator& gen,
152 const asset_handle<T>& asset,
153 const asset_handle<gfx::texture>& transparent,
154 const asset_handle<gfx::texture>& loading,
155 int frames = 2, delta_t dt = delta_t(0.016667f)) -> gfx::texture::ptr
156{
157 if(!asset.is_valid())
158 {
159 return transparent.get();
160 }
161
162 asset.get(false);
163
164 if(!asset.is_ready())
165 {
166 return loading.get();
167 }
168
169 return make_thumbnail(gen, asset, frames, dt);
170}
171
172} // namespace
173
174
175template<>
177{
178 auto thumbnail = get_thumbnail_impl(gen_, asset, thumbnails_.transparent, thumbnails_.loading);
179
180 if(thumbnail)
181 {
182 return thumbnail;
183 }
184
185 return thumbnails_.mesh.get();
186}
187
188template<>
190{
191 auto thumbnail = get_thumbnail_impl(gen_, asset, thumbnails_.transparent, thumbnails_.loading);
192
193 if(thumbnail)
194 {
195 return thumbnail;
196 }
197
198 return thumbnails_.material.get();
199}
200
201
202template<>
204{
205 if(!asset.is_valid())
206 {
207 return thumbnails_.transparent.get();
208 }
209
210 asset.get(false);
211
212 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.ui_tree.get();
213}
214
215template<>
217{
218 if(!asset.is_valid())
219 {
220 return thumbnails_.transparent.get();
221 }
222
223 asset.get(false);
224
225 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.style_sheet.get();
226}
227
228template<>
230{
231 if(!asset.is_valid())
232 {
233 return thumbnails_.transparent.get();
234 }
235
236 asset.get(false);
237
238 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.script.get();
239}
240
241template<>
244{
245 if(!asset.is_valid())
246 {
247 return thumbnails_.transparent.get();
248 }
249
250 asset.get(false);
251
252 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.physics_material.get();
253}
254
255template<>
257{
258 if(!asset.is_valid())
259 {
260 return thumbnails_.transparent.get();
261 }
262
263 asset.get(false);
264
265 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.audio_clip.get();
266}
267
268template<>
270{
271 if(!asset.is_valid())
272 {
273 return thumbnails_.transparent.get();
274 }
275
276 asset.get(false);
277
278 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.font.get();
279}
280
281template<>
283{
284 if(!asset.is_valid())
285 {
286 return thumbnails_.transparent.get();
287 }
288
289 asset.get(false);
290
291 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.animation.get();
292}
293
294template<>
295auto thumbnail_manager::get_thumbnail<gfx::texture>(const asset_handle<gfx::texture>& asset) -> gfx::texture::ptr
296{
297 if(!asset.is_valid())
298 {
299 return thumbnails_.transparent.get();
300 }
301
302 asset.get(false);
303
304 return !asset.is_ready() ? thumbnails_.loading.get() : asset.get();
305}
306
307template<>
308auto thumbnail_manager::get_thumbnail<gfx::shader>(const asset_handle<gfx::shader>& asset) -> gfx::texture::ptr
309{
310 if(!asset.is_valid())
311 {
312 return thumbnails_.transparent.get();
313 }
314
315 asset.get(false);
316
317 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.shader.get();
318}
319
320template<>
322{
323 delta_t dt(0.016667f * 5.0f);
324 int frames = 2;
325 auto thumbnail = get_thumbnail_impl(gen_, asset, thumbnails_.transparent, thumbnails_.loading, frames, dt);
326
327 if(thumbnail)
328 {
329 return thumbnail;
330 }
331
332 return thumbnails_.prefab.get();
333}
334
335template<>
337{
338 if(!asset.is_valid())
339 {
340 return thumbnails_.transparent.get();
341 }
342
343 asset.get(false);
344
345 return !asset.is_ready() ? thumbnails_.loading.get() : thumbnails_.scene_prefab.get();
346}
347
349{
350 fs::error_code ec;
351 if(fs::is_directory(path, ec))
352 {
353 return thumbnails_.folder.get();
354 }
355
356 return thumbnails_.file.get();
357}
358
360{
361 auto& entry = gen_.thumbnails[uid];
362 entry.needs_regeneration = true;
363 entry.snapshot = nullptr;
364 entry.pending_snapshot = nullptr;
365}
366void thumbnail_manager::remove_thumbnail(const hpp::uuid& uid)
367{
368 gen_.thumbnails.erase(uid);
369}
370
372{
373 auto& ctx = engine::context();
374 auto& rpath = ctx.get_cached<rendering_system>();
375
376 seq::scope::stop_all("camera_focus");
377
378 for(auto& scn : gen_.scenes)
379 {
380 rpath.release_pipeline_resources(scn);
381 scn.unload();
382 }
383
384 gen_.thumbnails.clear();
385 gen_.remaining = static_cast<int>(gen_.scenes.size());
386 gen_.wait_frames = 0;
387 last_eviction_scan_ = clock::now();
388}
389
390void thumbnail_manager::set_cache_directory(const fs::path& cache_dir)
391{
392 cache_directory_ = cache_dir;
393 fs::error_code ec;
394 fs::create_directories(cache_directory_, ec);
395}
396
397auto thumbnail_manager::get_cache_path(const hpp::uuid& uid) const -> fs::path
398{
399 if(cache_directory_.empty())
400 {
401 return {};
402 }
403 return cache_directory_ / (hpp::to_string(uid) + ".thumb");
404}
405
407{
408 (void)uid;
409 // Stub: disk persistence not yet implemented.
410 return nullptr;
411}
412
414{
415 (void)uid;
416 (void)tex;
417 // Stub: disk persistence not yet implemented.
418}
419
421{
422 auto path = get_cache_path(uid);
423 if(!path.empty())
424 {
425 fs::error_code ec;
426 fs::remove(path, ec);
427 }
428}
429
431{
433
434 if(e.all_of<camera_component>())
435 {
436 icon = gimzmo_icons_.camera;
437 }
438
439 if(e.all_of<light_component>())
440 {
441 const auto& light_comp = e.get<light_component>();
442 const auto& light = light_comp.get_light();
443
444 auto type = [&]() -> asset_handle<gfx::texture>
445 {
446 switch(light.type)
447 {
449 {
450 if(e.all_of<skylight_component>())
451 return gimzmo_icons_.sky_light;
452
453 return gimzmo_icons_.directional_light;
454 }
456 return gimzmo_icons_.point_light;
457 case light_type::spot:
458 return gimzmo_icons_.spot_light;
459 default:
460 return gimzmo_icons_.sky_light;
461 }
462 }();
463
464 icon = type;
465 }
466
467 if(e.all_of<reflection_probe_component>())
468 {
469 icon = gimzmo_icons_.reflection_probe;
470 }
471
472 if(e.all_of<audio_source_component>())
473 {
474 icon = gimzmo_icons_.audio_source;
475 }
476
477 if(e.all_of<particle_emitter_component>())
478 {
479 icon = gimzmo_icons_.particle_emitter;
480 }
481
482 return icon.get();
483}
484
485
487{
488 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
489
490 auto& ev = ctx.get_cached<events>();
491 ev.on_frame_update.connect(sentinel_, this, &thumbnail_manager::on_frame_update);
492 ev.on_frame_end.connect(sentinel_, -10000, this, &thumbnail_manager::on_frame_end);
493
494 auto& am = ctx.get_cached<asset_manager>();
495 thumbnails_.transparent = am.get_asset<gfx::texture>("engine:/data/textures/transparent.png");
496
497 thumbnails_.file = am.get_asset<gfx::texture>("editor:/data/icons/file.png");
498 thumbnails_.folder = am.get_asset<gfx::texture>("editor:/data/icons/folder.png");
499 thumbnails_.folder_empty = am.get_asset<gfx::texture>("editor:/data/icons/folder_empty.png");
500 thumbnails_.loading = am.get_asset<gfx::texture>("editor:/data/icons/loading.png");
501 thumbnails_.font = am.get_asset<gfx::texture>("editor:/data/icons/font.png");
502 thumbnails_.shader = am.get_asset<gfx::texture>("editor:/data/icons/shader.png");
503 thumbnails_.material = am.get_asset<gfx::texture>("editor:/data/icons/material.png");
504 thumbnails_.physics_material = am.get_asset<gfx::texture>("editor:/data/icons/material.png");
505 thumbnails_.mesh = am.get_asset<gfx::texture>("editor:/data/icons/mesh.png");
506 thumbnails_.animation = am.get_asset<gfx::texture>("editor:/data/icons/animation.png");
507 thumbnails_.prefab = am.get_asset<gfx::texture>("editor:/data/icons/prefab.png");
508 thumbnails_.scene_prefab = am.get_asset<gfx::texture>("editor:/data/icons/scene.png");
509 thumbnails_.audio_clip = am.get_asset<gfx::texture>("editor:/data/icons/sound.png");
510 thumbnails_.script = am.get_asset<gfx::texture>("editor:/data/icons/script.png");
511 thumbnails_.ui_tree = am.get_asset<gfx::texture>("editor:/data/icons/rhtml.png");
512 thumbnails_.style_sheet = am.get_asset<gfx::texture>("editor:/data/icons/rcss.png");
513
514 gimzmo_icons_.camera = am.get_asset<gfx::texture>("editor:/data/icons/camera.png");
515 gimzmo_icons_.sky_light = am.get_asset<gfx::texture>("editor:/data/icons/sky_light.png");
516 gimzmo_icons_.directional_light = am.get_asset<gfx::texture>("editor:/data/icons/directional_light.png");
517 gimzmo_icons_.point_light = am.get_asset<gfx::texture>("editor:/data/icons/point_light.png");
518 gimzmo_icons_.spot_light = am.get_asset<gfx::texture>("editor:/data/icons/spot_light.png");
519 gimzmo_icons_.audio_source = am.get_asset<gfx::texture>("editor:/data/icons/audio_source.png");
520 gimzmo_icons_.reflection_probe = am.get_asset<gfx::texture>("editor:/data/icons/reflection_probe.png");
521 gimzmo_icons_.particle_emitter = am.get_asset<gfx::texture>("editor:/data/icons/particle_emitter.png");
522
523 gen_.remaining = static_cast<int>(gen_.scenes.size());
524 gen_.reset_wait();
525 return true;
526}
527
529{
530 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
531
532 return true;
533}
534
536{
537 auto& rpath = ctx.get_cached<rendering_system>();
538 gen_.reset(rpath);
539 evict_unused_thumbnails(std::chrono::seconds(30), std::chrono::seconds(60));
540}
541
543{
544 commit_pending_snapshots();
545}
546
547void thumbnail_manager::commit_pending_snapshots()
548{
549 for(auto& [uid, entry] : gen_.thumbnails)
550 {
551 (void)uid;
552 if(!entry.pending_snapshot)
553 {
554 continue;
555 }
556
557 entry.set(std::move(entry.pending_snapshot));
558 }
559}
560
561void thumbnail_manager::evict_unused_thumbnails(std::chrono::seconds scan_interval, std::chrono::seconds idle_timeout)
562{
563
564 auto now = clock::now();
565 if((now - last_eviction_scan_) < scan_interval)
566 {
567 return;
568 }
569 last_eviction_scan_ = now;
570
571 auto& thumbnails = gen_.thumbnails;
572 for(auto it = thumbnails.begin(); it != thumbnails.end();)
573 {
574 auto& entry = it->second;
575 bool is_idle = (now - entry.last_access_time) > idle_timeout;
576 if(is_idle && !entry.needs_regeneration)
577 {
578 it = thumbnails.erase(it);
579 }
580 else
581 {
582 ++it;
583 }
584 }
585}
586
588{
589 if(!snapshot || !snapshot->is_valid())
590 {
591 if(snapshot)
592 {
593 snapshot = nullptr;
594 needs_regeneration = true;
595 }
596 return nullptr;
597 }
598
599 last_access_time = clock::now();
600 return snapshot;
601}
602
604{
605 if(!tex || !tex->is_valid())
606 {
607 return;
608 }
609
610 snapshot = std::move(tex);
611 needs_regeneration = false;
612 last_access_time = clock::now();
613}
614
616{
617 reset_wait();
618 return scenes[0];
619}
620
622{
623 if(wait_frames-- <= 0)
624 {
625 for(auto& scn : scenes)
626 {
628 scn.unload();
629 }
630 remaining = static_cast<int>(scenes.size());
631
632 reset_wait();
633 }
634}
635
637{
638 wait_frames = 2;
639}
640
641} // namespace unravel
Manages assets, including loading, unloading, and storage.
auto get_asset(const std::string &key, load_flags flags=load_flags::standard, load_mode mode=load_mode::immediate) -> asset_handle< T >
Gets an asset by its key.
Class that contains core data for audio sources.
Class that contains core camera data, used for rendering and other purposes.
Class that contains core light data, used for rendering and other purposes.
Component that wraps the soa particle system emitter.
Class that contains core reflection probe data, used for rendering and other purposes.
Base class for different rendering paths in the ACE framework.
auto release_pipeline_resources(scene &scn) -> bool
Class that contains sky light data.
std::chrono::duration< float > delta_t
const char * icon
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_TRACE(...)
Definition logging.h:17
texture_job_type type
const aiScene * scene
void frames(int _count, int32_t _flags)
void blit(view_id _id, texture_handle _dst, uint16_t _dstX, uint16_t _dstY, texture_handle _src, uint16_t _srcX, uint16_t _srcY, uint16_t _width, uint16_t _height)
void stop_all(const std::string &scope)
Stops all actions within the specified scope.
Definition seq.cpp:160
Thread-safe handle to an asset.
auto is_valid() const -> bool
Checks if the handle references a task.
auto get(bool wait=true) const -> std::shared_ptr< T >
Gets the shared pointer to the asset.
auto is_ready() const -> bool
Checks if the task is ready.
auto uid() const -> hpp::uuid
Gets the unique identifier of the asset.
auto get_cached() -> T &
Definition context.hpp:49
static void focus_camera_on_3d_scene_for_asset_preview(rtti::context &ctx, const asset_preview_result &result)
static auto create_default_3d_scene_for_asset_preview(rtti::context &ctx, scene &scn, const asset_handle< T > &asset, const usize32_t &size, bool focus_camera=true) -> asset_preview_result
static auto context() -> rtti::context &
Definition engine.cpp:111
hpp::event< void(rtti::context &, delta_t)> on_frame_update
Definition events.h:16
Struct representing a light.
Definition light.h:87
light_type type
The type of the light.
Definition light.h:89
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70
gfx::texture::ptr snapshot
Owned snapshot copied from the preview OBUFFER (not a live render target).
std::map< hpp::uuid, generated_thumbnail > thumbnails
void reset(rendering_system &rpath)
void invalidate_cache_entry(const hpp::uuid &uid)
Invalidates a cached thumbnail on disk.
auto load_cached_thumbnail(const hpp::uuid &uid) -> gfx::texture::ptr
Loads a cached thumbnail from disk (stub – returns nullptr for now).
auto init(rtti::context &ctx) -> bool
auto get_thumbnail(const asset_handle< T > &asset) -> gfx::texture::ptr
auto deinit(rtti::context &ctx) -> bool
void set_cache_directory(const fs::path &cache_dir)
Sets the directory for persistent thumbnail caching.
void save_thumbnail_to_cache(const hpp::uuid &uid, gfx::texture::ptr tex)
Saves a generated thumbnail to disk (stub – no-op for now).
auto get_gizmo_icon(entt::handle e) -> gfx::texture::ptr
void on_frame_update(rtti::context &ctx, delta_t)
void on_frame_end(rtti::context &ctx, delta_t)
void remove_thumbnail(const hpp::uuid &uid)
void regenerate_thumbnail(const hpp::uuid &uid)
auto get_cache_path(const hpp::uuid &uid) const -> fs::path
Gets the disk cache path for a given asset uid.
gfx::uniform_handle handle
Definition uniform.cpp:9