MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
BackendWindowHandler.cpp
Go to the documentation of this file.
2
4#include "VKFramebuffer.hpp"
5#include "VKRenderPass.hpp"
6#include "VKSwapchain.hpp"
7
11
12namespace MayaFlux::Core {
13
15{
16 vk::Device device = context.get_device();
17
18 for (auto& framebuffer : framebuffers) {
19 if (framebuffer) {
20 framebuffer->cleanup(device);
21 }
22 }
23 framebuffers.clear();
24
25 if (render_pass) {
26 render_pass->cleanup(device);
27 render_pass.reset();
28 }
29
30 for (auto& img : image_available) {
31 if (img) {
32 device.destroySemaphore(img);
33 }
34 }
35 image_available.clear();
36
37 for (auto& render : render_finished) {
38 if (render) {
39 device.destroySemaphore(render);
40 }
41 }
42 render_finished.clear();
43
44 for (auto& fence : in_flight) {
45 if (fence) {
46 device.destroyFence(fence);
47 }
48 }
49 in_flight.clear();
50
51 if (swapchain) {
52 swapchain->cleanup();
53 swapchain.reset();
54 }
55
56 if (surface) {
57 context.destroy_surface(surface);
58 surface = nullptr;
59 }
60
61 window->set_graphics_registered(false);
62}
63
65 : m_context(context)
66 , m_command_manager(command_manager)
67{
68}
69
70void BackendWindowHandler::setup_backend_service(const std::shared_ptr<Registry::Service::DisplayService>& display_service)
71{
72 display_service->present_frame = [this](const std::shared_ptr<void>& window_ptr, uint64_t command_buffer_bits) {
73 auto window = std::static_pointer_cast<Window>(window_ptr);
74 auto ctx = find_window_context(window);
75 if (!ctx) {
77 "Window '{}' not registered for presentation", window->get_create_info().title);
78 return;
79 }
80 ctx->command_buffer = *reinterpret_cast<vk::CommandBuffer*>(&command_buffer_bits);
81 this->render_window(window);
82 };
83
84 display_service->wait_idle = [this]() {
85 m_context.get_device().waitIdle();
86 };
87
88 display_service->resize_surface = [this](const std::shared_ptr<void>& window_ptr, uint32_t width, uint32_t height) {
89 auto window = std::static_pointer_cast<Window>(window_ptr);
90 window->set_size(width, height);
91 for (auto& ctx : m_window_contexts) {
92 if (ctx.window == window) {
93 ctx.needs_recreation = true;
94 break;
95 }
96 }
97 };
98
99 display_service->get_swapchain_image_count = [this](const std::shared_ptr<void>& window_ptr) -> uint32_t {
100 auto window = std::static_pointer_cast<Window>(window_ptr);
101 for (const auto& ctx : m_window_contexts) {
102 if (ctx.window == window) {
103 return static_cast<uint32_t>(ctx.swapchain->get_image_count());
104 }
105 }
106 return 0;
107 };
108
109 display_service->get_swapchain_format = [this](const std::shared_ptr<void>& window_ptr) -> uint32_t {
110 auto window = std::static_pointer_cast<Window>(window_ptr);
111 for (const auto& ctx : m_window_contexts) {
112 if (ctx.window == window) {
113 return static_cast<int>(ctx.swapchain->get_image_format());
114 }
115 }
116 return 0;
117 };
118
119 display_service->get_current_framebuffer = [this](const std::shared_ptr<void>& window_ptr) -> void* {
120 auto window = std::static_pointer_cast<Window>(window_ptr);
121 auto* context = find_window_context(window);
122
123 if (!context || context->framebuffers.empty()) {
124 return nullptr;
125 }
126
127 size_t frame_index = context->current_frame % context->framebuffers.size();
128 auto& handle = *context->framebuffers[frame_index];
129 return static_cast<void*>(&handle);
130 };
131
132 display_service->get_swapchain_extent = [this](
133 const std::shared_ptr<void>& window_ptr,
134 uint32_t& out_width,
135 uint32_t& out_height) {
136 auto window = std::static_pointer_cast<Window>(window_ptr);
137 auto* context = find_window_context(window);
138
139 if (context && context->swapchain) {
140 auto extent = context->swapchain->get_extent();
141 out_width = extent.width;
142 out_height = extent.height;
143 } else {
144 out_width = 0;
145 out_height = 0;
146 }
147 };
148
149 display_service->get_window_render_pass = [this](const std::shared_ptr<void>& window_ptr) -> void* {
150 auto window = std::static_pointer_cast<Window>(window_ptr);
151 auto* context = find_window_context(window);
152
153 if (!context || !context->render_pass) {
154 return nullptr;
155 }
156
157 vk::RenderPass rp = context->render_pass->get();
158 return static_cast<void*>(rp);
159 };
160
161 display_service->attach_render_pass = [this](
162 const std::shared_ptr<void>& window_ptr,
163 const std::shared_ptr<void>& render_pass_handle) -> bool {
164 auto window = std::static_pointer_cast<Window>(window_ptr);
165 auto render_pass = std::static_pointer_cast<Core::VKRenderPass>(render_pass_handle);
166 return this->attach_render_pass(window, render_pass);
167 };
168}
169
171{
172 auto it = std::ranges::find_if(m_window_contexts,
173 [window](const auto& config) { return config.window == window; });
174 return it != m_window_contexts.end() ? &(*it) : nullptr;
175}
176
177const WindowRenderContext* BackendWindowHandler::find_window_context(const std::shared_ptr<Window>& window) const
178{
179 auto it = std::ranges::find_if(m_window_contexts,
180 [window](const auto& config) { return config.window == window; });
181 return it != m_window_contexts.end() ? &(*it) : nullptr;
182}
183
184bool BackendWindowHandler::register_window(const std::shared_ptr<Window>& window)
185{
186 if (window->is_graphics_registered() || find_window_context(window)) {
187 return false;
188 }
189
190 vk::SurfaceKHR surface = m_context.create_surface(window);
191 if (!surface) {
193 "Failed to create Vulkan surface for window '{}'",
194 window->get_create_info().title);
195 return false;
196 }
197
198 if (!m_context.update_present_family(surface)) {
200 "No presentation support for window '{}'", window->get_create_info().title);
201 m_context.destroy_surface(surface);
202 return false;
203 }
204
205 WindowRenderContext config;
206 config.window = window;
207 config.surface = surface;
208 config.swapchain = std::make_unique<VKSwapchain>();
209
210 if (!config.swapchain->create(m_context, surface, window->get_create_info())) {
212 "Failed to create swapchain for window '{}'", window->get_create_info().title);
213 return false;
214 }
215
216 if (!create_sync_objects(config)) {
218 "Failed to create sync objects for window '{}'",
219 window->get_create_info().title);
220 config.swapchain->cleanup();
221 m_context.destroy_surface(surface);
222 return false;
223 }
224
225 m_window_contexts.emplace_back(std::move(config));
226 window->set_graphics_registered(true);
227
228 window->set_event_callback([this, window_ptr = window](const WindowEvent& event) {
230 auto* config = find_window_context(window_ptr);
231 if (config) {
232 config->needs_recreation = true;
233 }
234 }
235 });
236
238 "Registered window '{}' for graphics processing", window->get_create_info().title);
239
240 return true;
241}
242
243bool BackendWindowHandler::create_sync_objects(WindowRenderContext& config)
244{
245 auto device = m_context.get_device();
246 uint32_t image_count = config.swapchain->get_image_count();
247
248 try {
249 config.image_available.resize(image_count);
250 config.render_finished.resize(image_count);
251 config.in_flight.resize(image_count);
252
253 vk::SemaphoreCreateInfo semaphore_info {};
254 vk::FenceCreateInfo fence_info {};
255 fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
256
257 for (uint32_t i = 0; i < image_count; ++i) {
258 config.image_available[i] = device.createSemaphore(semaphore_info);
259 config.render_finished[i] = device.createSemaphore(semaphore_info);
260 }
261
262 for (auto& i : config.in_flight) {
263 i = device.createFence(fence_info);
264 }
265
266 config.current_frame = 0;
267
268 config.render_pass = std::make_unique<VKRenderPass>();
269 if (!config.render_pass->create(device, config.swapchain->get_image_format())) {
271 "Failed to create render pass");
272 return false;
273 }
274
275 config.framebuffers.resize(image_count);
276 auto extent = config.swapchain->get_extent();
277 const auto& image_views = config.swapchain->get_image_views();
278
279 for (uint32_t i = 0; i < image_count; ++i) {
280 config.framebuffers[i] = std::make_unique<VKFramebuffer>();
281
282 std::vector<vk::ImageView> attachments = { image_views[i] };
283
284 if (!config.framebuffers[i]->create(
285 device,
286 config.render_pass->get(),
287 attachments,
288 extent.width,
289 extent.height)) {
291 "Failed to create framebuffer {}", i);
292 return false;
293 }
294 }
295
296 return true;
297 } catch (const vk::SystemError& e) {
299 "Failed to create sync objects: {}", e.what());
300 return false;
301 }
302}
303
304void BackendWindowHandler::recreate_swapchain_for_context(WindowRenderContext& context)
305{
306 m_context.wait_idle();
307
308 if (recreate_swapchain_internal(context)) {
309 context.needs_recreation = false;
311 "Recreated swapchain for window '{}' ({}x{})",
312 context.window->get_create_info().title,
313 context.window->get_state().current_width,
314 context.window->get_state().current_height);
315 }
316}
317
318bool BackendWindowHandler::recreate_swapchain_internal(WindowRenderContext& context)
319{
320 const auto& state = context.window->get_state();
321
322 for (auto& framebuffer : context.framebuffers) {
323 if (framebuffer) {
324 framebuffer->cleanup(m_context.get_device());
325 }
326 }
327 context.framebuffers.clear();
328
329 if (context.render_pass && !context.user_render_pass_attached) {
330 context.render_pass->cleanup(m_context.get_device());
331 context.render_pass.reset();
332 }
333
334 if (!context.swapchain->recreate(state.current_width, state.current_height)) {
336 "Failed to recreate swapchain for window '{}'",
337 context.window->get_create_info().title);
338 return false;
339 }
340
341 if (!context.user_render_pass_attached) {
342 context.render_pass = std::make_unique<VKRenderPass>();
343 if (!context.render_pass->create(
344 m_context.get_device(),
345 context.swapchain->get_image_format())) {
347 "Failed to recreate render pass for window '{}'",
348 context.window->get_create_info().title);
349 return false;
350 }
351 }
352
353 uint32_t image_count = context.swapchain->get_image_count();
354 context.framebuffers.resize(image_count);
355 auto extent = context.swapchain->get_extent();
356 const auto& image_views = context.swapchain->get_image_views();
357
358 for (uint32_t i = 0; i < image_count; ++i) {
359 context.framebuffers[i] = std::make_unique<VKFramebuffer>();
360 std::vector<vk::ImageView> attachments = { image_views[i] };
361
362 if (!context.framebuffers[i]->create(
363 m_context.get_device(),
364 context.render_pass->get(),
365 attachments,
366 extent.width,
367 extent.height)) {
369 "Failed to recreate framebuffer {} for window '{}'",
370 i, context.window->get_create_info().title);
371 return false;
372 }
373 }
374
375 return true;
376}
377
378void BackendWindowHandler::render_window_internal(WindowRenderContext& context)
379{
380 auto device = m_context.get_device();
381 auto graphics_queue = m_context.get_graphics_queue();
382
383 size_t frame_index = context.current_frame;
384 auto& in_flight = context.in_flight[frame_index];
385
386 if (device.waitForFences(1, &in_flight, VK_TRUE, UINT64_MAX) == vk::Result::eTimeout) {
388 "Skipping frame rendering for window '{}' due to in-flight fence timeout",
389 context.window->get_create_info().title);
390 return;
391 }
392 if (device.resetFences(1, &in_flight) != vk::Result::eSuccess) {
394 "Failed to reset in-flight fence for window '{}'",
395 context.window->get_create_info().title);
396 return;
397 }
398
399 auto image_index_opt = context.swapchain->acquire_next_image(context.image_available[frame_index]);
400 if (!image_index_opt.has_value()) {
401 context.needs_recreation = true;
402 return;
403 }
404 uint32_t image_index = image_index_opt.value();
405
406 auto& image_available = context.image_available[frame_index];
407 auto& render_finished = context.render_finished[frame_index];
408
409 vk::CommandBuffer cmd = m_command_manager.allocate_command_buffer();
410 vk::CommandBufferBeginInfo begin_info {};
411 begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
412 cmd.begin(begin_info);
413
414 vk::RenderPassBeginInfo render_pass_info {};
415 render_pass_info.renderPass = context.render_pass->get();
416 render_pass_info.framebuffer = context.framebuffers[image_index]->get();
417 render_pass_info.renderArea.offset = vk::Offset2D { 0, 0 };
418 render_pass_info.renderArea.extent = context.swapchain->get_extent();
419
420 vk::ClearValue clear_color {};
421 clear_color.color = vk::ClearColorValue(std::array<float, 4> { 0.0F, 0.1F, 0.2F, 1.0F });
422 render_pass_info.clearValueCount = 1;
423 render_pass_info.pClearValues = &clear_color;
424
425 cmd.beginRenderPass(render_pass_info, vk::SubpassContents::eInline);
426 cmd.endRenderPass();
427
428 cmd.end();
429
430 vk::SubmitInfo submit_info {};
431 vk::PipelineStageFlags wait_stages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
432 submit_info.waitSemaphoreCount = 1;
433 submit_info.pWaitSemaphores = &image_available;
434 submit_info.pWaitDstStageMask = wait_stages;
435 submit_info.commandBufferCount = 1;
436 submit_info.pCommandBuffers = &cmd;
437 submit_info.signalSemaphoreCount = 1;
438 submit_info.pSignalSemaphores = &render_finished;
439
440 try {
441 auto result = graphics_queue.submit(1, &submit_info, in_flight);
442 } catch (const vk::SystemError& e) {
444 "Failed to submit draw command buffer: {}", e.what());
445 m_command_manager.free_command_buffer(cmd);
446 return;
447 }
448
449 bool present_success = context.swapchain->present(image_index, render_finished, graphics_queue);
450 if (!present_success) {
451 context.needs_recreation = true;
452 }
453
454 context.current_frame = (frame_index + 1) % context.in_flight.size();
455}
456
457void BackendWindowHandler::render_window(const std::shared_ptr<Window>& window)
458{
459 auto* context = find_window_context(window);
460 if (!context) {
462 "No context found for window '{}'",
463 window->get_create_info().title);
464 return;
465 }
466
467 auto device = m_context.get_device();
468 auto graphics_queue = m_context.get_graphics_queue();
469
470 size_t frame_index = context->current_frame;
471 auto& in_flight = context->in_flight[frame_index];
472 auto& image_available = context->image_available[frame_index];
473 auto& render_finished = context->render_finished[frame_index];
474
475 if (device.waitForFences(1, &in_flight, VK_TRUE, UINT64_MAX) == vk::Result::eTimeout) {
477 "Skipping frame rendering for window '{}' due to in-flight fence timeout",
478 context->window->get_create_info().title);
479 return;
480 }
481
482 auto image_index_opt = context->swapchain->acquire_next_image(image_available);
483 if (!image_index_opt.has_value()) {
484 context->needs_recreation = true;
485 return;
486 }
487 uint32_t image_index = image_index_opt.value();
488
489 if (device.resetFences(1, &in_flight) != vk::Result::eSuccess) {
491 "Failed to reset in-flight fence for window '{}'",
492 context->window->get_create_info().title);
493 return;
494 }
495
496 vk::SubmitInfo submit_info {};
497 vk::PipelineStageFlags wait_stages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
498 submit_info.waitSemaphoreCount = 1;
499 submit_info.pWaitSemaphores = &image_available;
500 submit_info.pWaitDstStageMask = wait_stages;
501 submit_info.commandBufferCount = 1;
502 submit_info.pCommandBuffers = &(context->command_buffer);
503 submit_info.signalSemaphoreCount = 1;
504 submit_info.pSignalSemaphores = &render_finished;
505
506 try {
507 auto result = graphics_queue.submit(1, &submit_info, in_flight);
508 } catch (const std::exception& e) {
509 error_rethrow(
512 std::source_location::current(),
513 "Unexpected error during command buffer submission: {}",
514 e.what());
515 return;
516 }
517
518 bool present_success = context->swapchain->present(image_index, render_finished, graphics_queue);
519 if (!present_success) {
520 context->needs_recreation = true;
521 }
522
523 context->current_frame = (frame_index + 1) % context->in_flight.size();
524}
525
526void BackendWindowHandler::render_all_windows()
527{
528 for (auto& context : m_window_contexts) {
529 render_window_internal(context);
530 }
531}
532
533bool BackendWindowHandler::is_window_registered(const std::shared_ptr<Window>& window) const
534{
535 return find_window_context(window) != nullptr;
536}
537
538void BackendWindowHandler::unregister_window(const std::shared_ptr<Window>& window)
539{
540 auto it = m_window_contexts.begin();
541 while (it != m_window_contexts.end()) {
542 if (it->window == window) {
543 it->cleanup(m_context);
545 "Unregistered window '{}'", it->window->get_create_info().title);
546 it = m_window_contexts.erase(it);
547 return;
548 }
549 ++it;
550 }
551}
552
553void BackendWindowHandler::handle_window_resize()
554{
555 for (auto& context : m_window_contexts) {
556 if (context.needs_recreation) {
557 recreate_swapchain_for_context(context);
558 }
559 }
560}
561
562bool BackendWindowHandler::attach_render_pass(
563 const std::shared_ptr<Window>& window,
564 const std::shared_ptr<Core::VKRenderPass>& render_pass)
565{
566 auto* context = find_window_context(window);
567 if (!context) {
569 "Window not registered");
570 return false;
571 }
572
573 if (!render_pass) {
575 "Cannot attach null render pass");
576 return false;
577 }
578
579 m_context.wait_idle();
580
581 for (auto& fb : context->framebuffers) {
582 if (fb)
583 fb->cleanup(m_context.get_device());
584 }
585 context->framebuffers.clear();
586
587 if (context->render_pass) {
588 context->render_pass->cleanup(m_context.get_device());
589 }
590 context->render_pass = render_pass;
591 context->user_render_pass_attached = true;
592
593 uint32_t image_count = context->swapchain->get_image_count();
594 context->framebuffers.resize(image_count);
595 auto extent = context->swapchain->get_extent();
596 const auto& image_views = context->swapchain->get_image_views();
597
598 for (uint32_t i = 0; i < image_count; ++i) {
599 context->framebuffers[i] = std::make_unique<VKFramebuffer>();
600 std::vector<vk::ImageView> attachments = { image_views[i] };
601
602 if (!context->framebuffers[i]->create(
603 m_context.get_device(),
604 render_pass->get(),
605 attachments,
606 extent.width,
607 extent.height)) {
609 "Failed to create framebuffer {} with user render pass", i);
610 return false;
611 }
612 }
613
615 "Attached user render pass to window '{}' - recreated {} framebuffers",
616 window->get_create_info().title, image_count);
617
618 return true;
619}
620
621void BackendWindowHandler::cleanup()
622{
623 for (auto& config : m_window_contexts) {
624 config.cleanup(m_context);
625 }
626 m_window_contexts.clear();
627}
628
629}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_WARN(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
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)
bool create_sync_objects(WindowRenderContext &config)
Create synchronization objects for a window's swapchain.
WindowRenderContext * find_window_context(const std::shared_ptr< Window > &window)
bool attach_render_pass(const std::shared_ptr< Window > &window, const std::shared_ptr< Core::VKRenderPass > &render_pass)
Attach a user render pass and recreate sync objects with it Used by Portal when registering a window ...
void render_window(const std::shared_ptr< Window > &window)
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:56
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
@ 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.
Event data for window and input events.
std::vector< vk::Semaphore > image_available
std::unique_ptr< VKSwapchain > swapchain
std::vector< std::unique_ptr< VKFramebuffer > > framebuffers
std::vector< vk::Semaphore > render_finished
std::shared_ptr< VKRenderPass > render_pass