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