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
146 };
147
148 display_service->wait_idle = [this]() {
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);
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);
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);
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);
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);
203 if (!ctx) {
205 "Window '{}' not registered for swapchain acquisition",
206 window->get_create_info().title);
207 return 0;
208 }
209
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);
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);
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 ,
282 uint32_t ,
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);
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
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);
338 if (!ctx) {
340 "ensure_depth_attachment: window '{}' not registered",
341 window->get_create_info().title);
342 return;
343 }
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);
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);
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
369
370 display_service->get_last_frame = [this](const std::shared_ptr<void>& window_ptr)
371 -> std::shared_ptr<std::vector<uint8_t>> {
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
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 {
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
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
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(
477 std::memory_order_release, std::memory_order_acquire));
478#endif
479 return id;
480 };
481
482
483
484
485 display_service->unregister_frame_observer = [this](
486 const std::shared_ptr<void>& window_ptr, uint32_t id) {
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
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
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(
544 std::memory_order_release, std::memory_order_acquire));
545#endif
546 };
547}
#define MF_RT_WARN(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
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.
std::vector< WindowRenderContext > m_window_contexts
BackendResourceManager * m_resource_manager
void submit_and_present(const std::shared_ptr< Window > &window, const vk::CommandBuffer &command_buffer)
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.
vk::Device get_device() const
Get logical device.
@ TEXTURE_2D
Sampled texture (shader read)
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ GraphicsCallback
Graphics/visual rendering callback - frame-rate real-time.
@ Core
Core engine, backend, subsystems.
@ IMAGE_COLOR
2D RGB/RGBA image
std::unordered_map< uint32_t, FrameObserver > ObserverMap