fangorn/sunshine-qemu
public
ref:main
/**
* @file src/platform/linux/qemu/frame_store.h
* @brief Declarations for the shared-memory frame store of the QEMU capture backend.
*/
#pragma once
// standard includes
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <optional>
#include <span>
#include <vector>
// local includes
#include "session.h"
namespace qemu {
/**
* @brief Result of polling the frame store for a new frame.
*/
enum class frame_status_e {
new_frame, ///< A frame newer than the caller's sequence number was copied.
unchanged, ///< Nothing changed since the caller's sequence number.
size_changed, ///< The frame size differs from the caller's size.
disconnected, ///< QEMU closed the listener connection.
};
/**
* @brief Reconstructs the guest framebuffer from QEMU listener calls.
* @details Listener calls arrive on the session thread; the capture thread copies frames out. The
* stored frame is always 4 bytes per pixel in B, G, R, X order with a stride of `4 * width`,
* which is the layout Sunshine's system-memory images use.
*/
class frame_store_t: public display_listener_t {
public:
frame_store_t() = default;
~frame_store_t() override;
frame_store_t(const frame_store_t &) = delete;
frame_store_t &operator=(const frame_store_t &) = delete;
void scanout(std::uint32_t width, std::uint32_t height, std::uint32_t stride, std::uint32_t format, std::span<const std::uint8_t> data) override;
void update(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint32_t stride, std::uint32_t format, std::span<const std::uint8_t> data) override;
void scanout_map(fd_t fd, std::uint32_t offset, std::uint32_t width, std::uint32_t height, std::uint32_t stride, std::uint32_t format) override;
void update_map(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) override;
void disable() override;
void disconnected() override;
/**
* @brief Wait until the first scanout arrived.
*
* @param timeout Maximum time to wait.
* @return True when a frame is available.
*/
bool wait_for_frame(std::chrono::milliseconds timeout);
/**
* @brief Current frame width.
*
* @return Width in pixels, 0 before the first scanout.
*/
[[nodiscard]] int width() const;
/**
* @brief Current frame height.
*
* @return Height in pixels, 0 before the first scanout.
*/
[[nodiscard]] int height() const;
/**
* @brief Report whether QEMU still has the listener connection open.
*
* @return False once QEMU closed the connection.
*/
[[nodiscard]] bool connected() const;
/**
* @brief Sequence number of the latest change.
*
* @return Monotonic counter, 0 before the first scanout.
*/
[[nodiscard]] std::uint64_t sequence() const;
/**
* @brief Copy the frame if it changed since the caller last copied it.
*
* @param last_sequence Sequence of the caller's last copy; updated on `new_frame`.
* @param width Width of the destination buffer.
* @param height Height of the destination buffer.
* @param dst Destination with `4 * width` bytes per row.
* @param timestamp Receipt time of the call that produced the copied frame; set on `new_frame`.
* @return Poll result.
*/
frame_status_e copy_if_newer(std::uint64_t &last_sequence, int width, int height, std::uint8_t *dst, std::chrono::steady_clock::time_point ×tamp);
private:
/**
* @brief Resize the frame, logging size or transport changes.
*
* @param new_width New width in pixels.
* @param new_height New height in pixels.
*/
void resize_locked(int new_width, int new_height);
/**
* @brief Convert and copy a rectangle of source pixels into the frame.
*
* @param x Destination left edge.
* @param y Destination top edge.
* @param w Rectangle width.
* @param h Rectangle height.
* @param src Pointer to the top-left source pixel of the rectangle.
* @param src_stride Source bytes per row.
* @param format Source pixman format; must be supported.
*/
void blit_locked(int x, int y, int w, int h, const std::uint8_t *src, std::uint32_t src_stride, std::uint32_t format);
/**
* @brief Mark the frame as changed now.
*/
void touch_locked();
/**
* @brief Unmap the current shared memory scanout.
*/
void unmap_locked();
mutable std::mutex mutex; ///< Guards all state below.
std::condition_variable frame_ready; ///< Signaled when a frame or disconnect arrives.
std::vector<std::uint8_t> pixels; ///< BGRX frame, `4 * frame_width` bytes per row.
int frame_width {0}; ///< Frame width in pixels.
int frame_height {0}; ///< Frame height in pixels.
std::uint64_t change_sequence {0}; ///< Incremented on every change.
std::chrono::steady_clock::time_point change_time; ///< Receipt time of the latest change.
bool is_disconnected {false}; ///< Whether QEMU closed the listener connection.
bool logged_mapped {false}; ///< Whether the last logged scanout used the shared memory map.
fd_t map_fd; ///< Shared memory descriptor of the current map scanout.
const std::uint8_t *map_addr {nullptr}; ///< Mapping of `map_fd`.
std::size_t map_size {0}; ///< Length of the mapping.
std::uint32_t map_offset {0}; ///< Offset of the first pixel in the mapping.
std::uint32_t map_stride {0}; ///< Bytes per row in the mapping.
std::uint32_t map_format {0}; ///< Pixman format of the mapping.
};
} // namespace qemu