MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
BackendWindowHandler.cpp
Go to the documentation of this file.
2
5#include "VKEnumUtils.hpp"
6#include "VKImage.hpp"
7#include "VKSwapchain.hpp"
8
12
13namespace MayaFlux::Core {
14
15// ---------------------------------------------------------------------------
16// CaptureState retirement implementations (macOS only)
17// ---------------------------------------------------------------------------
18#ifdef MAYAFLUX_PLATFORM_MACOS
19
20void CaptureState::retire_last_frame(const std::vector<uint8_t>* ptr)
21{
22 for (;;) {
23 bool in_use = false;
24 for (size_t i = 0; i < LAST_FRAME_MAX_READERS; ++i) {
25 if (last_frame_hazard_ptrs[i].load(std::memory_order_acquire) == ptr) {
26 in_use = true;
27 break;
28 }
29 }
30 if (!in_use)
31 break;
32 std::this_thread::yield();
33 }
34 delete ptr;
35}
36
37void CaptureState::retire_observers(const ObserverMap* ptr)
38{
39 for (;;) {
40 bool in_use = false;
41 for (size_t i = 0; i < OBSERVERS_MAX_READERS; ++i) {
42 if (observers_hazard_ptrs[i].load(std::memory_order_acquire) == ptr) {
43 in_use = true;
44 break;
45 }
46 }
47 if (!in_use)
48 break;
49 std::this_thread::yield();
50 }
51 delete ptr;
52}
53#endif
54
56{
57 vk::Device device = context.get_device();
58
59 if (capture) {
60 capture->readback_running.store(false, std::memory_order_release);
61 device.waitIdle();
62
63 if (capture->readback_thread.joinable())
64 capture->readback_thread.join();
65
66 for (auto& slot : capture->slots) {
67 if (slot->fence) {
68 (void)device.waitForFences(1, &slot->fence, VK_TRUE, UINT64_MAX);
69 device.destroyFence(slot->fence);
70 }
71 if (slot->image)
72 device.destroyImage(slot->image);
73 if (slot->mem)
74 device.freeMemory(slot->mem);
75 }
76 capture.reset();
77 }
78
79 device.waitIdle();
80
81 for (auto& img : image_available) {
82 if (img) {
83 device.destroySemaphore(img);
84 }
85 }
86 image_available.clear();
87
88 for (auto& render : render_finished) {
89 if (render) {
90 device.destroySemaphore(render);
91 }
92 }
93 render_finished.clear();
94
96
97 for (auto& fence : in_flight) {
98 if (fence) {
99 device.destroyFence(fence);
100 }
101 }
102 in_flight.clear();
103
104 if (depth_image && depth_image->is_initialized()) {
105 const auto& res = depth_image->get_image_resources();
106 if (res.image_view) {
107 device.destroyImageView(res.image_view);
108 }
109 if (res.memory) {
110 device.freeMemory(res.memory);
111 }
112 if (res.image) {
113 device.destroyImage(res.image);
114 }
115 depth_image.reset();
116 }
117
118 if (swapchain) {
119 swapchain->cleanup();
120 swapchain.reset();
121 }
122
123 if (surface) {
124 context.destroy_surface(surface);
125 surface = nullptr;
126 }
127
128 window->set_graphics_registered(false);
129}
130
132 : m_context(context)
133 , m_command_manager(command_manager)
134{
135}
136
137void BackendWindowHandler::setup_backend_service(const std::shared_ptr<Registry::Service::DisplayService>& display_service)
138{
139 display_service->submit_and_present = [this](
140 const std::shared_ptr<void>& window_ptr,
141 uint64_t primary_cmd_bits) {
142 auto window = std::static_pointer_cast<Window>(window_ptr);
143 vk::CommandBuffer primary_cmd = *reinterpret_cast<vk::CommandBuffer*>(&primary_cmd_bits);
144
145 this->submit_and_present(window, primary_cmd);
146 };
147
148 display_service->wait_idle = [this]() {
149 m_context.get_device().waitIdle();
150 };
151
152 display_service->resize_surface = [this](const std::shared_ptr<void>& window_ptr, uint32_t width, uint32_t height) {
153 auto window = std::static_pointer_cast<Window>(window_ptr);
154 window->set_size(width, height);
155 for (auto& ctx : m_window_contexts) {
156 if (ctx.window == window) {
157 ctx.needs_recreation = true;
158 break;
159 }
160 }
161 };
162
163 display_service->get_swapchain_image_count = [this](const std::shared_ptr<void>& window_ptr) -> uint32_t {
164 auto window = std::static_pointer_cast<Window>(window_ptr);
165 for (const auto& ctx : m_window_contexts) {
166 if (ctx.window == window) {
167 return static_cast<uint32_t>(ctx.swapchain->get_image_count());
168 }
169 }
170 return 0;
171 };
172
173 display_service->get_swapchain_format = [this](const std::shared_ptr<void>& window_ptr) -> uint32_t {
174 auto window = std::static_pointer_cast<Window>(window_ptr);
175 for (const auto& ctx : m_window_contexts) {
176 if (ctx.window == window) {
177 return static_cast<int>(ctx.swapchain->get_image_format());
178 }
179 }
180 return 0;
181 };
182
183 display_service->get_swapchain_extent = [this](
184 const std::shared_ptr<void>& window_ptr,
185 uint32_t& out_width,
186 uint32_t& out_height) {
187 auto window = std::static_pointer_cast<Window>(window_ptr);
188 auto* context = find_window_context(window);
189
190 if (context && context->swapchain) {
191 auto extent = context->swapchain->get_extent();
192 out_width = extent.width;
193 out_height = extent.height;
194 } else {
195 out_width = 0;
196 out_height = 0;
197 }
198 };
199
200 display_service->acquire_next_swapchain_image = [this](const std::shared_ptr<void>& window_ptr) -> uint64_t {
201 auto window = std::static_pointer_cast<Window>(window_ptr);
202 auto* ctx = find_window_context(window);
203 if (!ctx) {
205 "Window '{}' not registered for swapchain acquisition",
206 window->get_create_info().title);
207 return 0;
208 }
209
210 auto device = m_context.get_device();
211 size_t frame_index = ctx->current_frame;
212 auto& in_flight = ctx->in_flight[frame_index];
213 auto& image_available = ctx->image_available[frame_index];
214
215 if (device.waitForFences(1, &in_flight, VK_TRUE, UINT64_MAX) == vk::Result::eTimeout) {
217 "Fence timeout during swapchain acquisition for window '{}'",
218 window->get_create_info().title);
219 return 0;
220 }
221
222 auto image_index_opt = ctx->swapchain->acquire_next_image(image_available);
223 if (!image_index_opt.has_value()) {
224 ctx->needs_recreation = true;
225 return 0;
226 }
227
228 ctx->current_image_index = image_index_opt.value();
229
230 if (device.resetFences(1, &in_flight) != vk::Result::eSuccess) {
232 "Failed to reset fence for window '{}'",
233 window->get_create_info().title);
234 return 0;
235 }
236
237 const auto& images = ctx->swapchain->get_images();
238 VkImage raw = images[ctx->current_image_index];
239 return reinterpret_cast<uint64_t>(raw);
240 };
241
242 display_service->get_current_image_view = [this](const std::shared_ptr<void>& window_ptr) -> void* {
243 auto window = std::static_pointer_cast<Window>(window_ptr);
244 auto* context = find_window_context(window);
245
246 if (!context || !context->swapchain) {
247 return nullptr;
248 }
249
250 const auto& image_views = context->swapchain->get_image_views();
251 if (context->current_image_index >= image_views.size()) {
253 "Invalid current_image_index {} for window '{}' (swapchain has {} images)",
254 context->current_image_index,
255 window->get_create_info().title,
256 image_views.size());
257 return nullptr;
258 }
259
260 static thread_local vk::ImageView view;
261 view = image_views[context->current_image_index];
262 return static_cast<void*>(&view);
263 };
264
265 display_service->get_current_swapchain_image = [this](const std::shared_ptr<void>& window_ptr) -> uint64_t {
266 auto window = std::static_pointer_cast<Window>(window_ptr);
267 auto* ctx = find_window_context(window);
268 if (!ctx || !ctx->swapchain)
269 return 0;
270
271 const auto& images = ctx->swapchain->get_images();
272 if (ctx->current_image_index >= images.size())
273 return 0;
274
275 return reinterpret_cast<uint64_t>(static_cast<VkImage>(images[ctx->current_image_index]));
276 };
277
278 display_service->readback_swapchain_region = [this](
279 const std::shared_ptr<void>& window_ptr,
280 void* dst,
281 uint32_t /*x_offset*/,
282 uint32_t /*y_offset*/,
283 uint32_t pixel_width,
284 uint32_t pixel_height,
285 size_t byte_count) -> bool {
286 auto window = std::static_pointer_cast<Window>(window_ptr);
287 auto* ctx = find_window_context(window);
288
289 if (!ctx || !ctx->swapchain) {
291 "readback_swapchain_region: window '{}' has no swapchain",
292 window->get_create_info().title);
293 return false;
294 }
295
296 if (!m_resource_manager) {
298 "readback_swapchain_region: resource manager not set");
299 return false;
300 }
301
302 const auto& images = ctx->swapchain->get_images();
303 if (ctx->current_image_index >= images.size()) {
305 "readback_swapchain_region: invalid image index {} for '{}'",
306 ctx->current_image_index, window->get_create_info().title);
307 return false;
308 }
309
310 auto proxy = std::make_shared<VKImage>(
311 pixel_width, pixel_height, 1U,
312 ctx->swapchain->get_image_format(),
315 1U, 1U,
317
318 VKImageResources res {};
319 res.image = images[ctx->current_image_index];
320 proxy->set_image_resources(res);
321 proxy->set_current_layout(vk::ImageLayout::ePresentSrcKHR);
322
324 proxy,
325 dst,
326 byte_count,
327 nullptr,
328 false,
329 vk::ImageLayout::ePresentSrcKHR,
330 vk::PipelineStageFlagBits::eBottomOfPipe);
331
332 return true;
333 };
334
335 display_service->ensure_depth_attachment = [this](const std::shared_ptr<void>& window_ptr) {
336 auto window = std::static_pointer_cast<Window>(window_ptr);
337 auto* ctx = find_window_context(window);
338 if (!ctx) {
340 "ensure_depth_attachment: window '{}' not registered",
341 window->get_create_info().title);
342 return;
343 }
344 ensure_depth_image(*ctx);
345 };
346
347 display_service->get_depth_image_view = [this](const std::shared_ptr<void>& window_ptr) -> void* {
348 auto window = std::static_pointer_cast<Window>(window_ptr);
349 auto* ctx = find_window_context(window);
350 if (!ctx || !ctx->depth_image || !ctx->depth_image->is_initialized()) {
351 return nullptr;
352 }
353 static thread_local vk::ImageView view;
354 view = ctx->depth_image->get_image_view();
355 return static_cast<void*>(&view);
356 };
357
358 display_service->get_depth_format = [this](const std::shared_ptr<void>& window_ptr) -> uint32_t {
359 auto window = std::static_pointer_cast<Window>(window_ptr);
360 auto* ctx = find_window_context(window);
361 if (!ctx || !ctx->depth_image || !ctx->depth_image->is_initialized()) {
362 return static_cast<uint32_t>(vk::Format::eUndefined);
363 }
364 return static_cast<uint32_t>(ctx->depth_image->get_format());
365 };
366
367 // ======================================================================
368 // get_last_frame – hazard‑pointer protected read + copy
369 // ======================================================================
370 display_service->get_last_frame = [this](const std::shared_ptr<void>& window_ptr)
371 -> std::shared_ptr<std::vector<uint8_t>> {
372 auto* ctx = find_window_context(std::static_pointer_cast<Window>(window_ptr));
373 if (!ctx || !ctx->capture)
374 return nullptr;
375
376#ifdef MAYAFLUX_PLATFORM_MACOS
377 auto& state = *ctx->capture;
378
379 size_t slot = CaptureState::LAST_FRAME_MAX_READERS;
380 for (size_t i = 0; i < CaptureState::LAST_FRAME_MAX_READERS; ++i) {
381 bool expected = false;
382 if (state.last_frame_slot_active[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
383 slot = i;
384 break;
385 }
386 }
387 if (slot == CaptureState::LAST_FRAME_MAX_READERS)
388 return nullptr;
389
390 const std::vector<uint8_t>* raw;
391 do {
392 raw = state.last_frame.load(std::memory_order_acquire);
393 state.last_frame_hazard_ptrs[slot].store(raw, std::memory_order_release);
394 } while (raw != state.last_frame.load(std::memory_order_acquire));
395
396 if (!raw) {
397 state.last_frame_hazard_ptrs[slot].store(nullptr, std::memory_order_release);
398 state.last_frame_slot_active[slot].store(false, std::memory_order_release);
399 return nullptr;
400 }
401
402 return std::shared_ptr<std::vector<uint8_t>>(
403 const_cast<std::vector<uint8_t>*>(raw),
404 [&state, slot](std::vector<uint8_t>*) {
405 state.last_frame_hazard_ptrs[slot].store(nullptr, std::memory_order_release);
406 state.last_frame_slot_active[slot].store(false, std::memory_order_release);
407 });
408#else
409 return ctx->capture->last_frame.load(std::memory_order_acquire);
410#endif
411 };
412
413 // ======================================================================
414 // register_frame_observer – hazard‑pointer update of observer map
415 // ======================================================================
416 display_service->register_frame_observer = [this](
417 const std::shared_ptr<void>& window_ptr,
418 const std::function<void(
419 const std::shared_ptr<std::vector<uint8_t>>&,
420 uint32_t, uint32_t, uint32_t)>&
421 cb) -> uint32_t {
422 auto* ctx = find_window_context(std::static_pointer_cast<Window>(window_ptr));
423 if (!ctx) {
425 "register_frame_observer: window not registered with graphics backend");
426 return 0;
427 }
428 if (!ctx->capture) {
430 }
431
432 auto& state = *ctx->capture;
433 uint32_t id = state.next_observer_id.fetch_add(1, std::memory_order_relaxed);
434
435#ifdef MAYAFLUX_PLATFORM_MACOS
436 size_t obs_slot = CaptureState::OBSERVERS_MAX_READERS;
437 for (size_t i = 0; i < CaptureState::OBSERVERS_MAX_READERS; ++i) {
438 bool expected = false;
439 if (state.observers_slot_active[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
440 obs_slot = i;
441 break;
442 }
443 }
444 if (obs_slot == CaptureState::OBSERVERS_MAX_READERS)
445 return 0;
446
448 CaptureState::ObserverMap* new_map = nullptr;
449
450 do {
451 if (new_map)
452 delete new_map;
453
454 do {
455 current = state.observers.load(std::memory_order_acquire);
456 state.observers_hazard_ptrs[obs_slot].store(current, std::memory_order_release);
457 } while (current != state.observers.load(std::memory_order_acquire));
458
460 (*new_map)[id] = cb;
461
462 } while (!state.observers.compare_exchange_weak(current, new_map, std::memory_order_acq_rel, std::memory_order_acquire));
463
464 state.observers_hazard_ptrs[obs_slot].store(nullptr, std::memory_order_release);
465 state.observers_slot_active[obs_slot].store(false, std::memory_order_release);
466
467 if (current)
468 state.retire_observers(current);
469#else
470 auto current = state.observers.load(std::memory_order_acquire);
471 std::shared_ptr<CaptureState::ObserverMap> next;
472 do {
473 next = std::make_shared<CaptureState::ObserverMap>(*current);
474 (*next)[id] = cb;
475 } while (!state.observers.compare_exchange_weak(
476 current, next,
477 std::memory_order_release, std::memory_order_acquire));
478#endif
479 return id;
480 };
481
482 // ======================================================================
483 // unregister_frame_observer – hazard‑pointer update
484 // ======================================================================
485 display_service->unregister_frame_observer = [this](
486 const std::shared_ptr<void>& window_ptr, uint32_t id) {
487 auto* ctx = find_window_context(std::static_pointer_cast<Window>(window_ptr));
488 if (!ctx) {
490 "unregister_frame_observer: window not registered with graphics backend");
491 return;
492 }
493 if (!ctx->capture) {
495 "unregister_frame_observer: capture not active for '{}'",
496 ctx->window->get_create_info().title);
497 return;
498 }
499
500 auto& state = *ctx->capture;
501
502#ifdef MAYAFLUX_PLATFORM_MACOS
503 size_t obs_slot = CaptureState::OBSERVERS_MAX_READERS;
504 for (size_t i = 0; i < CaptureState::OBSERVERS_MAX_READERS; ++i) {
505 bool expected = false;
506 if (state.observers_slot_active[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
507 obs_slot = i;
508 break;
509 }
510 }
511 if (obs_slot == CaptureState::OBSERVERS_MAX_READERS)
512 return;
513
515 CaptureState::ObserverMap* new_map = nullptr;
516
517 do {
518 if (new_map)
519 delete new_map;
520
521 do {
522 current = state.observers.load(std::memory_order_acquire);
523 state.observers_hazard_ptrs[obs_slot].store(current, std::memory_order_release);
524 } while (current != state.observers.load(std::memory_order_acquire));
525
527 new_map->erase(id);
528
529 } while (!state.observers.compare_exchange_weak(current, new_map, std::memory_order_acq_rel, std::memory_order_acquire));
530
531 state.observers_hazard_ptrs[obs_slot].store(nullptr, std::memory_order_release);
532 state.observers_slot_active[obs_slot].store(false, std::memory_order_release);
533
534 if (current)
535 state.retire_observers(current);
536#else
537 auto current = state.observers.load(std::memory_order_acquire);
538 std::shared_ptr<CaptureState::ObserverMap> next;
539 do {
540 next = std::make_shared<CaptureState::ObserverMap>(*current);
541 next->erase(id);
542 } while (!state.observers.compare_exchange_weak(
543 current, next,
544 std::memory_order_release, std::memory_order_acquire));
545#endif
546 };
547}
548
550{
551 auto it = std::ranges::find_if(m_window_contexts,
552 [window](const auto& config) { return config.window == window; });
553 return it != m_window_contexts.end() ? &(*it) : nullptr;
554}
555
556const WindowRenderContext* BackendWindowHandler::find_window_context(const std::shared_ptr<Window>& window) const
557{
558 auto it = std::ranges::find_if(m_window_contexts,
559 [window](const auto& config) { return config.window == window; });
560 return it != m_window_contexts.end() ? &(*it) : nullptr;
561}
562
563bool BackendWindowHandler::register_window(const std::shared_ptr<Window>& window)
564{
565 if (window->is_graphics_registered() || find_window_context(window)) {
566 return false;
567 }
568
569 vk::SurfaceKHR surface = m_context.create_surface(window);
570 if (!surface) {
572 "Failed to create Vulkan surface for window '{}'",
573 window->get_create_info().title);
574 return false;
575 }
576
577 if (!m_context.update_present_family(surface)) {
579 "No presentation support for window '{}'", window->get_create_info().title);
580 m_context.destroy_surface(surface);
581 return false;
582 }
583
584 WindowRenderContext config;
585 config.window = window;
586 config.surface = surface;
587 config.swapchain = std::make_unique<VKSwapchain>();
588
589 if (!config.swapchain->create(m_context, surface, window->get_create_info())) {
591 "Failed to create swapchain for window '{}'", window->get_create_info().title);
592 return false;
593 }
594
595 if (!create_sync_objects(config)) {
597 "Failed to create sync objects for window '{}'",
598 window->get_create_info().title);
599 config.swapchain->cleanup();
600 m_context.destroy_surface(surface);
601 return false;
602 }
603
604 m_window_contexts.emplace_back(std::move(config));
605 window->set_graphics_registered(true);
606
607 window->set_event_callback([this, window_ptr = window](const WindowEvent& event) {
609 auto* config = find_window_context(window_ptr);
610 if (config) {
611 config->needs_recreation = true;
612 }
613 }
614 });
615
617 "Registered window '{}' for graphics processing", window->get_create_info().title);
618
619 return true;
620}
621
622bool BackendWindowHandler::create_sync_objects(WindowRenderContext& config)
623{
624 auto device = m_context.get_device();
625 uint32_t image_count = config.swapchain->get_image_count();
626
627 try {
628 config.image_available.resize(image_count);
629 config.render_finished.resize(image_count);
630 config.in_flight.resize(image_count);
631 config.clear_command_buffers.resize(image_count);
632
633 vk::SemaphoreCreateInfo semaphore_info {};
634 vk::FenceCreateInfo fence_info {};
635 fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
636
637 for (uint32_t i = 0; i < image_count; ++i) {
638 config.image_available[i] = device.createSemaphore(semaphore_info);
639 config.render_finished[i] = device.createSemaphore(semaphore_info);
640
641 config.clear_command_buffers[i] = m_command_manager.allocate_command_buffer(
642 vk::CommandBufferLevel::ePrimary);
643 }
644
645 for (auto& i : config.in_flight) {
646 i = device.createFence(fence_info);
647 }
648
649 config.current_frame = 0;
650
651 return true;
652 } catch (const vk::SystemError& e) {
654 "Failed to create sync objects: {}", e.what());
655 return false;
656 }
657}
658
659void BackendWindowHandler::recreate_swapchain_for_context(WindowRenderContext& context)
660{
661 m_context.wait_idle();
662
663 if (recreate_swapchain_internal(context)) {
664 context.needs_recreation = false;
666 "Recreated swapchain for window '{}' ({}x{})",
667 context.window->get_create_info().title,
668 context.window->get_state().current_width,
669 context.window->get_state().current_height);
670 }
671}
672
673bool BackendWindowHandler::recreate_swapchain_internal(WindowRenderContext& context)
674{
675 const auto& state = context.window->get_state();
676
677 if (!context.swapchain->recreate(state.current_width, state.current_height)) {
679 "Failed to recreate swapchain for window '{}'",
680 context.window->get_create_info().title);
681 return false;
682 }
683
684 if (context.depth_image) {
685 auto device = m_context.get_device();
686 const auto& res = context.depth_image->get_image_resources();
687 if (res.image_view) {
688 device.destroyImageView(res.image_view);
689 }
690 if (res.memory) {
691 device.freeMemory(res.memory);
692 }
693 if (res.image) {
694 device.destroyImage(res.image);
695 }
696 context.depth_image.reset();
697 }
698
699 return true;
700}
701
702void BackendWindowHandler::ensure_depth_image(WindowRenderContext& ctx)
703{
704 if (!ctx.swapchain) {
705 return;
706 }
707
708 auto extent = ctx.swapchain->get_extent();
709
710 if (ctx.depth_image && ctx.depth_image->is_initialized()
711 && ctx.depth_image->get_width() == extent.width
712 && ctx.depth_image->get_height() == extent.height) {
713 return;
714 }
715
716 auto device = m_context.get_device();
717
718 if (ctx.depth_image && ctx.depth_image->is_initialized()) {
719 device.waitIdle();
720 const auto& res = ctx.depth_image->get_image_resources();
721 if (res.image_view) {
722 device.destroyImageView(res.image_view);
723 }
724 if (res.memory) {
725 device.freeMemory(res.memory);
726 }
727 if (res.image) {
728 device.destroyImage(res.image);
729 }
730 ctx.depth_image.reset();
731 }
732
733 ctx.depth_image = std::make_shared<VKImage>(
734 extent.width, extent.height, 1,
735 vk::Format::eD32Sfloat,
736 VKImage::Usage::DEPTH_STENCIL,
737 VKImage::Type::TYPE_2D,
738 1, 1,
740
741 m_resource_manager->initialize_image(ctx.depth_image);
742
743 if (!ctx.depth_image->is_initialized()) {
745 "Failed to create depth image for window '{}'",
746 ctx.window->get_create_info().title);
747 ctx.depth_image.reset();
748 return;
749 }
750
751 m_resource_manager->transition_image_layout(
752 ctx.depth_image->get_image(),
753 vk::ImageLayout::eUndefined,
754 vk::ImageLayout::eDepthStencilAttachmentOptimal,
755 1, 1,
756 vk::ImageAspectFlagBits::eDepth);
757 ctx.depth_image->set_current_layout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
758
760 "Created depth image for window '{}': {}x{}, D32_SFLOAT",
761 ctx.window->get_create_info().title,
762 extent.width, extent.height);
763}
764
765void BackendWindowHandler::render_window(const std::shared_ptr<Window>& window)
766{
767 auto ctx = find_window_context(window);
768 if (!ctx) {
770 "Window '{}' not registered for rendering",
771 window->get_create_info().title);
772 return;
773 }
774
775 if (ctx->window && ctx->window->get_rendering_buffers().empty()) {
776 render_empty_window(*ctx);
777 }
778}
779
780void BackendWindowHandler::render_all_windows()
781{
783 "render_all_windows() is not implemented in BackendWindowHandler. Dynamic rendering is expected to be handled externally.");
784}
785
786void BackendWindowHandler::render_empty_window(WindowRenderContext& ctx)
787{
788 auto window = ctx.window;
789
790 if (!window || !window->get_state().is_visible || !window->get_rendering_buffers().empty()) {
791 return;
792 }
793
794 try {
795 auto device = m_context.get_device();
796
797 size_t frame_index = ctx.current_frame;
798 auto& in_flight = ctx.in_flight[frame_index];
799 auto& image_available = ctx.image_available[frame_index];
800 auto& render_finished = ctx.render_finished[frame_index];
801
802 auto cmd_buffer = ctx.clear_command_buffers[frame_index];
803 if (!cmd_buffer) {
805 "Clear command buffer not allocated for window '{}'",
806 window->get_create_info().title);
807 return;
808 }
809 cmd_buffer.reset({});
810
811 if (device.waitForFences(1, &in_flight, VK_TRUE, UINT64_MAX) == vk::Result::eTimeout) {
812 return;
813 }
814
815 auto image_index_opt = ctx.swapchain->acquire_next_image(image_available);
816 if (!image_index_opt.has_value()) {
817 ctx.needs_recreation = true;
818 return;
819 }
820 ctx.current_image_index = image_index_opt.value();
821
822 if (device.resetFences(1, &in_flight) != vk::Result::eSuccess) {
823 return;
824 }
825
826 vk::CommandBufferBeginInfo begin_info {};
827 begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
828 cmd_buffer.begin(begin_info);
829
830 const auto& image_views = ctx.swapchain->get_image_views();
831 auto extent = ctx.swapchain->get_extent();
832
833 vk::RenderingAttachmentInfo color_attachment {};
834 color_attachment.imageView = image_views[ctx.current_image_index];
835 color_attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
836 color_attachment.loadOp = vk::AttachmentLoadOp::eClear;
837 color_attachment.storeOp = vk::AttachmentStoreOp::eStore;
838 color_attachment.clearValue.color = vk::ClearColorValue(
839 window->get_create_info().clear_color);
840
841 vk::RenderingInfo rendering_info {};
842 rendering_info.renderArea = vk::Rect2D { { 0, 0 }, extent };
843 rendering_info.layerCount = 1;
844 rendering_info.colorAttachmentCount = 1;
845 rendering_info.pColorAttachments = &color_attachment;
846
847 vk::ImageMemoryBarrier barrier {};
848 barrier.oldLayout = vk::ImageLayout::eUndefined;
849 barrier.newLayout = vk::ImageLayout::eColorAttachmentOptimal;
850 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
851 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
852 barrier.image = ctx.swapchain->get_images()[ctx.current_image_index];
853 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
854 barrier.subresourceRange.levelCount = 1;
855 barrier.subresourceRange.layerCount = 1;
856 barrier.srcAccessMask = {};
857 barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
858
859 cmd_buffer.pipelineBarrier(
860 vk::PipelineStageFlagBits::eTopOfPipe,
861 vk::PipelineStageFlagBits::eColorAttachmentOutput,
862 {}, {}, {}, barrier);
863
864 cmd_buffer.beginRendering(rendering_info);
865 cmd_buffer.endRendering();
866
867 barrier.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
868 barrier.newLayout = vk::ImageLayout::ePresentSrcKHR;
869 barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
870 barrier.dstAccessMask = {};
871
872 cmd_buffer.pipelineBarrier(
873 vk::PipelineStageFlagBits::eColorAttachmentOutput,
874 vk::PipelineStageFlagBits::eBottomOfPipe,
875 {}, {}, {}, barrier);
876
877 cmd_buffer.end();
878
879 submit_and_present(window, cmd_buffer);
880
881 ctx.current_frame = (ctx.current_frame + 1) % ctx.in_flight.size();
882
883 } catch (const std::exception& e) {
885 "Failed to render empty window '{}': {}",
886 window->get_create_info().title, e.what());
887 }
888}
889
890void BackendWindowHandler::ensure_capture_state(WindowRenderContext& ctx)
891{
892 if (ctx.capture || !ctx.swapchain || !ctx.window->is_capture_enabled())
893 return;
894
895 auto dev = m_context.get_device();
896 const vk::Format fmt = ctx.swapchain->get_image_format();
897 const uint32_t bpp = Core::vk_format_bytes_per_pixel(fmt);
898
899 ctx.capture = std::make_unique<CaptureState>();
900 ctx.capture->format = fmt;
901 ctx.capture->bpp = bpp;
902
903 const uint32_t count = ctx.swapchain->get_image_count();
904 ctx.capture->slots.reserve(count);
905 for (uint32_t i = 0; i < count; ++i) {
906 auto slot = std::make_unique<CaptureSlot>();
907 slot->cmd = m_command_manager.allocate_command_buffer(
908 vk::CommandBufferLevel::ePrimary);
909 slot->fence = dev.createFence({});
910 ctx.capture->slots.push_back(std::move(slot));
911 }
912
913 start_readback_thread(*ctx.capture, dev);
914}
915
916void BackendWindowHandler::capture_frame(WindowRenderContext& ctx)
917{
918 if (!ctx.swapchain || ctx.window->get_rendering_buffers().empty()
919 || !ctx.window->is_capture_enabled())
920 return;
921
922 auto dev = m_context.get_device();
923 const auto ext = ctx.swapchain->get_extent();
924 const vk::Format fmt = ctx.swapchain->get_image_format();
925 const uint32_t bpp = Core::vk_format_bytes_per_pixel(fmt);
926 const vk::Image src = ctx.swapchain->get_images()[ctx.current_image_index];
927
928 if (!ctx.capture) {
929 ensure_capture_state(ctx);
930 }
931
932 auto& slot = *ctx.capture->slots[ctx.capture->slot_index];
933
934 if (slot.pending.load(std::memory_order_acquire))
935 return;
936
937 if (slot.image && slot.extent != ext) {
938 dev.destroyImage(slot.image);
939 dev.freeMemory(slot.mem);
940 slot.image = vk::Image {};
941 slot.mem = vk::DeviceMemory {};
942 }
943
944 if (!slot.image) {
945 vk::ImageCreateInfo ici {};
946 ici.imageType = vk::ImageType::e2D;
947 ici.format = fmt;
948 ici.extent = vk::Extent3D { ext.width, ext.height, 1 };
949 ici.arrayLayers = 1;
950 ici.mipLevels = 1;
951 ici.samples = vk::SampleCountFlagBits::e1;
952 ici.tiling = vk::ImageTiling::eLinear;
953 ici.usage = vk::ImageUsageFlagBits::eTransferDst;
954
955 slot.image = dev.createImage(ici);
956
957 auto req = dev.getImageMemoryRequirements(slot.image);
958 vk::MemoryAllocateInfo mai {};
959 mai.allocationSize = req.size;
960 mai.memoryTypeIndex = m_resource_manager->find_memory_type(
961 req.memoryTypeBits,
962 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
963
964 slot.mem = dev.allocateMemory(mai);
965 dev.bindImageMemory(slot.image, slot.mem, 0);
966 slot.extent = ext;
967 }
968
969 slot.cmd.reset({});
970 vk::CommandBufferBeginInfo bi {};
971 bi.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
972 slot.cmd.begin(bi);
973
974 vk::ImageMemoryBarrier b {};
975 b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
976 b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
977 b.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 };
978
979 b.image = slot.image;
980 b.oldLayout = vk::ImageLayout::eUndefined;
981 b.newLayout = vk::ImageLayout::eTransferDstOptimal;
982 b.srcAccessMask = {};
983 b.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
984 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
985 vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, b);
986
987 b.image = src;
988 b.oldLayout = vk::ImageLayout::ePresentSrcKHR;
989 b.newLayout = vk::ImageLayout::eTransferSrcOptimal;
990 b.srcAccessMask = vk::AccessFlagBits::eMemoryRead;
991 b.dstAccessMask = vk::AccessFlagBits::eTransferRead;
992 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
993 vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, b);
994
995 vk::ImageCopy copy {};
996 copy.srcSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 };
997 copy.dstSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 };
998 copy.extent = vk::Extent3D { ext.width, ext.height, 1 };
999 slot.cmd.copyImage(src, vk::ImageLayout::eTransferSrcOptimal,
1000 slot.image, vk::ImageLayout::eTransferDstOptimal, 1, &copy);
1001
1002 b.image = slot.image;
1003 b.oldLayout = vk::ImageLayout::eTransferDstOptimal;
1004 b.newLayout = vk::ImageLayout::eGeneral;
1005 b.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
1006 b.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
1007 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1008 vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, b);
1009
1010 b.image = src;
1011 b.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
1012 b.newLayout = vk::ImageLayout::ePresentSrcKHR;
1013 b.srcAccessMask = vk::AccessFlagBits::eTransferRead;
1014 b.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
1015 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1016 vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, b);
1017
1018 slot.cmd.end();
1019
1020 (void)dev.resetFences(1, &slot.fence);
1021
1022 vk::SubmitInfo si {};
1023 si.commandBufferCount = 1;
1024 si.pCommandBuffers = &slot.cmd;
1025
1026 slot.pending.store(true, std::memory_order_release);
1027
1028 if (auto result = m_context.get_graphics_queue().submit(1, &si, slot.fence); result != vk::Result::eSuccess) {
1030 "Failed to submit capture command buffer for window '{}': {}",
1031 ctx.window->get_create_info().title, vk::to_string(result));
1032 slot.pending.store(false, std::memory_order_release);
1033 return;
1034 }
1035
1036 ctx.capture->slot_index = (ctx.capture->slot_index + 1) % ctx.capture->slots.size();
1037}
1038
1039void BackendWindowHandler::start_readback_thread(CaptureState& state, vk::Device dev)
1040{
1041 state.readback_running.store(true, std::memory_order_release);
1042
1043 state.readback_thread = std::thread([&state, dev]() {
1044 while (state.readback_running.load(std::memory_order_acquire)) {
1045 bool any = false;
1046
1047 for (auto& slot_ptr : state.slots) {
1048 auto& slot = *slot_ptr;
1049
1050 if (!slot.pending.load(std::memory_order_acquire))
1051 continue;
1052
1053 if (dev.getFenceStatus(slot.fence) != vk::Result::eSuccess)
1054 continue;
1055
1056 const size_t nb = static_cast<size_t>(slot.extent.width)
1057 * slot.extent.height * state.bpp;
1058
1059 vk::SubresourceLayout layout = dev.getImageSubresourceLayout(
1060 slot.image, { vk::ImageAspectFlagBits::eColor, 0, 0 });
1061
1062 const auto* mapped = static_cast<const uint8_t*>(
1063 dev.mapMemory(slot.mem, 0, VK_WHOLE_SIZE));
1064 mapped += layout.offset;
1065
1066 auto buf = std::make_shared<std::vector<uint8_t>>(nb);
1067 const uint32_t row_bytes = slot.extent.width * state.bpp;
1068 for (uint32_t y = 0; y < slot.extent.height; ++y) {
1069 std::memcpy(buf->data() + static_cast<size_t>(y * row_bytes),
1070 mapped + y * layout.rowPitch,
1071 row_bytes);
1072 }
1073
1074 dev.unmapMemory(slot.mem);
1075
1076 // ---------------------------------------------------------------
1077 // Store new frame into last_frame (same pattern as InputManager)
1078 // ---------------------------------------------------------------
1079#ifdef MAYAFLUX_PLATFORM_MACOS
1080 auto* raw_frame = new std::vector<uint8_t>(std::move(*buf));
1081 auto* old = state.last_frame.exchange(raw_frame, std::memory_order_acq_rel);
1082 if (old)
1083 state.retire_last_frame(old);
1084#else
1085 state.last_frame.store(buf, std::memory_order_release);
1086#endif
1087
1088// ---------------------------------------------------------------
1089// Read observers and invoke callbacks
1090// ---------------------------------------------------------------
1091#ifdef MAYAFLUX_PLATFORM_MACOS
1092 size_t obs_slot = CaptureState::OBSERVERS_MAX_READERS;
1093 for (size_t i = 0; i < CaptureState::OBSERVERS_MAX_READERS; ++i) {
1094 bool expected = false;
1095 if (state.observers_slot_active[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
1096 obs_slot = i;
1097 break;
1098 }
1099 }
1100
1101 if (obs_slot != CaptureState::OBSERVERS_MAX_READERS) {
1102 const CaptureState::ObserverMap* obs_current;
1103 do {
1104 obs_current = state.observers.load(std::memory_order_acquire);
1105 state.observers_hazard_ptrs[obs_slot].store(obs_current, std::memory_order_release);
1106 } while (obs_current != state.observers.load(std::memory_order_acquire));
1107
1108 if (obs_current) {
1109 for (const auto& [id, cb] : *obs_current) {
1110 cb(buf, slot.extent.width, slot.extent.height,
1111 static_cast<uint32_t>(state.format));
1112 }
1113 }
1114
1115 state.observers_hazard_ptrs[obs_slot].store(nullptr, std::memory_order_release);
1116 state.observers_slot_active[obs_slot].store(false, std::memory_order_release);
1117 }
1118#else
1119 auto obs = state.observers.load(std::memory_order_acquire);
1120 if (obs) {
1121 for (const auto& [id, cb] : *obs) {
1122 cb(buf, slot.extent.width, slot.extent.height,
1123 static_cast<uint32_t>(state.format));
1124 }
1125 }
1126#endif
1127
1128 slot.pending.store(false, std::memory_order_release);
1129 any = true;
1130 }
1131
1132 if (!any)
1133 std::this_thread::sleep_for(std::chrono::microseconds(200));
1134 }
1135 });
1136}
1137
1138void BackendWindowHandler::submit_and_present(
1139 const std::shared_ptr<Window>& window,
1140 const vk::CommandBuffer& command_buffer)
1141{
1142 auto* ctx = find_window_context(window);
1143 if (!ctx) {
1145 "Window not registered for submit_and_present");
1146 return;
1147 }
1148
1149 auto graphics_queue = m_context.get_graphics_queue();
1150
1151 size_t frame_index = ctx->current_frame;
1152 auto& in_flight = ctx->in_flight[frame_index];
1153 auto& image_available = ctx->image_available[frame_index];
1154 auto& render_finished = ctx->render_finished[frame_index];
1155
1156 vk::Semaphore deferred_sem = m_resource_manager
1157 ? m_resource_manager->flush_deferred_commands()
1158 : nullptr;
1159
1160 std::vector<vk::Semaphore> wait_sems { image_available };
1161 std::vector<vk::PipelineStageFlags> wait_stages {
1162 vk::PipelineStageFlagBits::eColorAttachmentOutput
1163 };
1164
1165 if (deferred_sem) {
1166 wait_sems.push_back(deferred_sem);
1167 wait_stages.emplace_back(vk::PipelineStageFlagBits::eFragmentShader);
1168 }
1169
1170 vk::SubmitInfo submit_info {};
1171 submit_info.waitSemaphoreCount = static_cast<uint32_t>(wait_sems.size());
1172 submit_info.pWaitSemaphores = wait_sems.data();
1173 submit_info.pWaitDstStageMask = wait_stages.data();
1174 submit_info.commandBufferCount = 1;
1175 submit_info.pCommandBuffers = &command_buffer;
1176 submit_info.signalSemaphoreCount = 1;
1177 submit_info.pSignalSemaphores = &render_finished;
1178
1179 try {
1180 auto result = graphics_queue.submit(1, &submit_info, in_flight);
1181 } catch (const vk::SystemError& e) {
1183 "Failed to submit primary command buffer: {}", e.what());
1184 return;
1185 }
1186
1187 bool present_success = ctx->swapchain->present(
1188 ctx->current_image_index, render_finished, graphics_queue);
1189
1190 if (!present_success) {
1191 ctx->needs_recreation = true;
1192 }
1193
1194 if (present_success)
1195 capture_frame(*ctx);
1196
1197 ctx->current_frame = (frame_index + 1) % ctx->in_flight.size();
1198
1200 "Window '{}': frame submitted and presented",
1201 window->get_create_info().title);
1202}
1203
1204bool BackendWindowHandler::is_window_registered(const std::shared_ptr<Window>& window) const
1205{
1206 return find_window_context(window) != nullptr;
1207}
1208
1209void BackendWindowHandler::unregister_window(const std::shared_ptr<Window>& window)
1210{
1211 auto it = m_window_contexts.begin();
1212 while (it != m_window_contexts.end()) {
1213 if (it->window == window) {
1214 it->cleanup(m_context);
1216 "Unregistered window '{}'", it->window->get_create_info().title);
1217 it = m_window_contexts.erase(it);
1218 return;
1219 }
1220 ++it;
1221 }
1222}
1223
1224void BackendWindowHandler::handle_window_resize()
1225{
1226 for (auto& context : m_window_contexts) {
1227 if (context.needs_recreation) {
1228 recreate_swapchain_for_context(context);
1229 }
1230 }
1231}
1232
1233void BackendWindowHandler::cleanup()
1234{
1235 for (auto& config : m_window_contexts) {
1236 config.cleanup(m_context);
1237 }
1238 m_window_contexts.clear();
1239}
1240
1241} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_WARN(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_RT_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
vk::Fence fence
uint32_t width
Definition Decoder.cpp:66
glm::vec2 current
size_t b
size_t count
const uint8_t * ptr
uint32_t height
void download_image_data(std::shared_ptr< VKImage > image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download image data to a host pointer.
void ensure_depth_image(WindowRenderContext &ctx)
Ensure depth image exists at current swapchain extent.
bool register_window(const std::shared_ptr< Window > &window)
std::vector< WindowRenderContext > m_window_contexts
void setup_backend_service(const std::shared_ptr< Registry::Service::DisplayService > &display_service)
BackendWindowHandler(VKContext &context, VKCommandManager &command_manager)
void submit_and_present(const std::shared_ptr< Window > &window, const vk::CommandBuffer &command_buffer)
bool create_sync_objects(WindowRenderContext &config)
Create synchronization objects for a window's swapchain.
WindowRenderContext * find_window_context(const std::shared_ptr< Window > &window)
void ensure_capture_state(WindowRenderContext &ctx)
Ensure capture state is initialized for a window context.
Manages Vulkan command pools and command buffers.
bool update_present_family(vk::SurfaceKHR surface)
Update presentation support for a surface.
vk::Device get_device() const
Get logical device.
Definition VKContext.hpp:49
vk::SurfaceKHR create_surface(std::shared_ptr< Window > window)
Create surface from window's native handles.
Definition VKContext.cpp:73
void destroy_surface(vk::SurfaceKHR surface)
Destroy a specific surface Called when window is unregistered.
High-level wrapper for Vulkan instance and device.
Definition VKContext.hpp:16
@ TEXTURE_2D
Sampled texture (shader read)
uint32_t vk_format_bytes_per_pixel(vk::Format fmt)
Byte width of a single pixel for a given Vulkan format.
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ GraphicsSubsystem
Graphics subsystem operations (Vulkan, rendering pipeline)
@ GraphicsCallback
Graphics/visual rendering callback - frame-rate real-time.
@ Core
Core engine, backend, subsystems.
@ IMAGE_COLOR
2D RGB/RGBA image
@ IMAGE_2D
2D image (grayscale or single channel)
std::unordered_map< uint32_t, FrameObserver > ObserverMap
Vulkan image resource handles.
Definition VKImage.hpp:15
Event data for window and input events.
std::vector< vk::Semaphore > image_available
std::unique_ptr< CaptureState > capture
std::unique_ptr< VKSwapchain > swapchain
std::vector< vk::Semaphore > render_finished
std::vector< vk::CommandBuffer > clear_command_buffers