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.
1061{
1062 state.readback_running.store(true, std::memory_order_release);
1063
1064 state.readback_thread = std::thread([&state, dev]() {
1065 while (state.readback_running.load(std::memory_order_acquire)) {
1066 bool any = false;
1067
1068 for (auto& slot_ptr : state.slots) {
1069 auto& slot = *slot_ptr;
1070
1071 if (!slot.pending.load(std::memory_order_acquire))
1072 continue;
1073
1074 if (dev.getFenceStatus(slot.fence) != vk::Result::eSuccess)
1075 continue;
1076
1077 const size_t nb = static_cast<size_t>(slot.extent.width)
1078 * slot.extent.height * state.bpp;
1079
1080 vk::SubresourceLayout layout = dev.getImageSubresourceLayout(
1081 slot.image, { vk::ImageAspectFlagBits::eColor, 0, 0 });
1082
1083 const auto* mapped = static_cast<const uint8_t*>(
1084 dev.mapMemory(slot.mem, 0, VK_WHOLE_SIZE));
1085 mapped += layout.offset;
1086
1087 auto buf = std::make_shared<std::vector<uint8_t>>(nb);
1088 const uint32_t row_bytes = slot.extent.width * state.bpp;
1089 for (uint32_t y = 0; y < slot.extent.height; ++y) {
1090 std::memcpy(buf->data() + static_cast<size_t>(y * row_bytes),
1091 mapped + y * layout.rowPitch,
1092 row_bytes);
1093 }
1094
1095 dev.unmapMemory(slot.mem);
1096
1097
1098
1099
1100#ifdef MAYAFLUX_PLATFORM_MACOS
1101 auto* raw_frame = new std::vector<uint8_t>(*buf);
1102 auto* old = state.last_frame.exchange(raw_frame, std::memory_order_acq_rel);
1103 if (old)
1104 state.retire_last_frame(old);
1105#else
1106 state.last_frame.store(buf, std::memory_order_release);
1107#endif
1108
1109
1110
1111
1112#ifdef MAYAFLUX_PLATFORM_MACOS
1113 size_t obs_slot = CaptureState::OBSERVERS_MAX_READERS;
1114 for (size_t i = 0; i < CaptureState::OBSERVERS_MAX_READERS; ++i) {
1115 bool expected = false;
1116 if (state.observers_slot_active[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
1117 obs_slot = i;
1118 break;
1119 }
1120 }
1121
1122 if (obs_slot != CaptureState::OBSERVERS_MAX_READERS) {
1123 const CaptureState::ObserverMap* obs_current;
1124 do {
1125 obs_current = state.observers.load(std::memory_order_acquire);
1126 state.observers_hazard_ptrs[obs_slot].store(obs_current, std::memory_order_release);
1127 } while (obs_current != state.observers.load(std::memory_order_acquire));
1128
1129 if (obs_current) {
1130 for (const auto& [id, cb] : *obs_current) {
1131 cb(buf, slot.extent.width, slot.extent.height,
1132 static_cast<uint32_t>(state.format));
1133 }
1134 }
1135
1136 state.observers_hazard_ptrs[obs_slot].store(nullptr, std::memory_order_release);
1137 state.observers_slot_active[obs_slot].store(false, std::memory_order_release);
1138 }
1139#else
1140 auto obs = state.observers.load(std::memory_order_acquire);
1141 if (obs) {
1142 for (const auto& [id, cb] : *obs) {
1143 cb(buf, slot.extent.width, slot.extent.height,
1144 static_cast<uint32_t>(state.format));
1145 }
1146 }
1147#endif
1148
1149 slot.pending.store(false, std::memory_order_release);
1150 any = true;
1151 }
1152
1153 if (!any)
1154 std::this_thread::sleep_for(std::chrono::microseconds(200));
1155 }
1156 });
1157}