Thread function to perform asynchronous readback of captured frames.
This thread continuously checks for pending capture slots and performs the necessary Vulkan commands to read back the image data into CPU memory. The last captured frame is stored atomically for retrieval by the display service.
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
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
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}