Skip to content

File frame.hpp

File List > data_format > frame.hpp

Go to the documentation of this file

#pragma once

#ifdef Success
#    undef Success // For 'Success' conflict
#endif

#include "illixr/data_format/poses/head_pose.hpp"
#include "illixr/data_format/stereo_presentation.hpp"
#include "illixr/switchboard.hpp"

#include 
#include 
#include 
#include 
#include 
#ifdef __ANDROID__
#    include 
#    ifndef NVDEC_DECODER
#        define NVDEC_DECODER
#    endif
#    ifdef ILLIXR_VULKAN
#        include 
#    endif // ILLIXR_VULKAN
#else
#    include 
#endif // __ANDROID__

// Define PACKET_TYPE based on available codec backends
#ifdef ILLIXR_LIBAV
extern "C" {
#    include "libavcodec_illixr/avcodec.h"
#    include "libavformat_illixr/avformat.h"
#    include "libavutil_illixr/hwcontext.h"
#    include "libavutil_illixr/opt.h"
#    include "libavutil_illixr/pixdesc.h"
}
#    define PACKET_TYPE AVPacket*
#    define PACKET_REF  AVPacket*
#elif defined(NVENC_ENCODER) || defined(NVDEC_DECODER)

#    define PACKET_TYPE std::vector
#    define PACKET_REF  std::vector&

#endif

#ifndef BUFFER_TYPE
#    ifdef USING_OPENXR
#        ifdef ENABLE_MONADO
#            define BUFFER_TYPE std::array
#        else
#            define BUFFER_TYPE std::array
#        endif
#    else
#        ifndef BUFFER_TYPE
#            define BUFFER_TYPE data_format::pose::fast_head_pose_type
#        endif
#    endif
#endif

namespace ILLIXR::data_format {

struct boba_frame_overlay {
    static constexpr std::uint32_t command_stride_floats = 14;
    static constexpr std::uint32_t max_commands_per_eye  = 256;

    std::uint32_t      source_width{0};
    std::uint32_t      source_height{0};
    std::vector<float> left_commands;
    std::vector<float> right_commands;
};

struct boba_modal_overlay {
    bool visible{false};
    bool left_valid{false};
    bool right_valid{false};

    std::uint64_t texture_id{0};
    std::uint32_t width{0};
    std::uint32_t height{0};
    std::array<float, 8> left_quad_pixels{};
    std::array<float, 8> right_quad_pixels{};
    float width_m{0.0F};
    float height_m{0.0F};
};

struct boba_modal_texture : public switchboard::event {
    static constexpr std::uint64_t wire_magic = 0x424F42414D4F444CULL; // "BOBAMODL"

    std::uint64_t             texture_id{0};
    std::uint32_t             width{0};
    std::uint32_t             height{0};
    std::vector<std::uint8_t> rgba;
    std::uint64_t             magic{wire_magic};
};

// Using arrays as a swapchain
// Array of left eyes, array of right eyes
// This more closely matches the format used by Monado
struct [[maybe_unused]] rendered_frame : public switchboard::event {
    std::array<GLuint, 2>                  swapchain_indices{}; // Index of image rendered for left and right swapchain
    [[maybe_unused]] std::array<GLuint, 2> swap_indices{};      // Which element of the swapchain
    pose::fast_head_pose_type              render_pose;         // The pose used when rendering this frame.
    time_point                             sample_time{};
    time_point                             render_time{};

    rendered_frame() = default;

    rendered_frame(std::array<GLuint, 2>&& swapchain_indices_, std::array<GLuint, 2>&& swap_indices_,
                   pose::fast_head_pose_type render_pose_, time_point sample_time_, time_point render_time_)
        : swapchain_indices{swapchain_indices_}
        , swap_indices{swap_indices_}
        , render_pose(std::move(render_pose_))
        , sample_time(sample_time_)
        , render_time(render_time_) { }
};

struct compressed_frame : public switchboard::event {
    bool    nalu_only{false};
    char*   left_color_nalu{nullptr};
    char*   right_color_nalu{nullptr};
    char*   left_depth_nalu{nullptr};
    char*   right_depth_nalu{nullptr};
    int32_t left_color_nalu_size{0};
    int32_t right_color_nalu_size{0};
    int32_t left_depth_nalu_size{0};
    int32_t right_depth_nalu_size{0};

    bool use_depth{false};
    bool use_motion_vectors{false};
#ifdef ILLIXR_ENABLE_BOBA
    stereo_presentation_mode presentation_mode{stereo_presentation_mode::stereo_fullscreen};
    float                    content_aspect_ratio{0.0F};
    boba_frame_overlay       boba_overlay{};
    boba_modal_overlay       boba_modal{};
#endif

    struct [[maybe_unused]] has_motion_vectors_tag { };
#if defined(ILLIXR_LIBAV) || defined(NVENC_ENCODER) || defined(NVDEC_DECODER)
    PACKET_TYPE left_color;
    PACKET_TYPE right_color;

    PACKET_TYPE left_depth;
    PACKET_TYPE right_depth;

    PACKET_TYPE left_motion_vec;
    PACKET_TYPE right_motion_vec;

#endif

    BUFFER_TYPE pose{};

    // Projection clip planes forwarded from XrCompositionLayerDepthInfoKHR.
    // The decoder uses these to linearise the encoded RG depth values back into
    // view-space metres: depth_m = near_z * far_z / (far_z - ndc * (far_z - near_z))
    // where ndc is the reconstructed [0,1] normalised depth.
    // Both are 0 when no depth layer was submitted this frame.
    float near_z = 0.0f;
    float far_z  = 0.0f;

    // Render FOV angles (radians) used by the server when encoding this frame.
    // Forwarded to the headset so xrEndFrame can use the correct render FOV
    // for timewarp, enabling overdraw margins to be used correctly.
#ifdef USING_OPENXR
    std::array<float, 2> fov_left  = {0.0f, 0.0f};
    std::array<float, 2> fov_right = {0.0f, 0.0f};
    std::array<float, 2> fov_up    = {0.0f, 0.0f};
    std::array<float, 2> fov_down  = {0.0f, 0.0f};
#endif

    uint64_t sent_time{0};
    uint64_t frame_number{0};
    uint64_t pose_id{0}; 
    double encode_time = 0.;
    // True if the color bitstream for this frame is a keyframe (IDR / AV1 KEY_FRAME).
    // Set by the server from nvenc_encoder::last_frame_was_keyframe() immediately
    // after encoding, so the client never needs to parse OBU or NAL headers.
    bool    is_keyframe = false;
    int64_t magic       = 0; // Changed from long to int64_t for cross-platform compatibility

    compressed_frame() = default;

#if defined(ILLIXR_LIBAV) || defined(NVENC_ENCODER) || defined(NVDEC_DECODER)
    compressed_frame(PACKET_REF left_color, PACKET_REF right_color, const BUFFER_TYPE& pose, uint64_t sent_time,
                     uint64_t frame_no, bool nalu_only = false)
        : nalu_only(nalu_only)
        , use_depth(false)
        , use_motion_vectors(false)
        , left_color(left_color)
        , right_color(right_color)
        , left_depth{}
        , right_depth{}
        , left_motion_vec{}
        , right_motion_vec{}
        , pose(pose)
        , near_z(0.0f)
        , far_z(0.0f)
        , sent_time(sent_time)
        , frame_number(frame_no)
        , pose_id(0)
        , magic(0xdeadbeef) { }

    compressed_frame(PACKET_REF left_color, PACKET_REF right_color, PACKET_REF left_depth, PACKET_REF right_depth,
                     const BUFFER_TYPE pose, uint64_t sent_time, uint64_t frame_no, float near_z, float far_z,
                     bool nalu_only = false)
        : nalu_only(nalu_only)
        , use_depth(true)
        , use_motion_vectors(false)
        , left_color(left_color)
        , right_color(right_color)
        , left_depth(left_depth)
        , right_depth(right_depth)
        , left_motion_vec{}
        , right_motion_vec{}
        , pose(pose)
        , near_z(near_z)
        , far_z(far_z)
        , sent_time(sent_time)
        , frame_number(frame_no)
        , pose_id(0)
        , magic(0xdeadbeef) { }

    compressed_frame(PACKET_REF left_color, PACKET_REF right_color, PACKET_REF left_depth, PACKET_REF right_depth,
                     PACKET_REF left_motion_vec, PACKET_REF right_motion_vec, const BUFFER_TYPE pose, uint64_t sent_time,
                     uint64_t frame_no, float near_z, float far_z, bool nalu_only = false)
        : nalu_only(nalu_only)
        , use_depth(true)
        , use_motion_vectors(true)
        , left_color(left_color)
        , right_color(right_color)
        , left_depth(left_depth)
        , right_depth(right_depth)
        , left_motion_vec(left_motion_vec)
        , right_motion_vec(right_motion_vec)
        , pose(pose)
        , near_z(near_z)
        , far_z(far_z)
        , sent_time(sent_time)
        , frame_number(frame_no)
        , pose_id(0)
        , magic(0xdeadbeef) { }

#endif
    ~compressed_frame() override {
        if (nalu_only && left_color_nalu != nullptr && right_color_nalu != nullptr) {
            free(left_color_nalu);
            free(right_color_nalu);
            if (use_depth) {
                free(left_depth_nalu);
                free(right_depth_nalu);
            }
        }
    }
};

enum class frame_format : uint8_t {
    nv12,                   
    rgba8 [[maybe_unused]], 
    external_oes,           
#ifdef __ANDROID__
    hardware_buffer 
#endif
};

struct eye_frame {
    std::vector<uint8_t> data{};

    GLuint texture_id{0};

    std::array<float, 16> texture_transform{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
                                            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f};
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    AHardwareBuffer* hw_buffer{nullptr};
#endif

    eye_frame() = default;

    explicit eye_frame(std::vector<uint8_t> raw_data)
        : data{std::move(raw_data)}
        , texture_id{0} { }

    eye_frame(GLuint tex_id, const float* transform)
        : texture_id{tex_id} {
        if (transform) {
            std::copy(transform, transform + 16, texture_transform.begin());
        }
    }
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    explicit eye_frame(AHardwareBuffer* buf)
        : texture_id{0}
        , hw_buffer{buf} { }
#endif

    [[nodiscard]] [[maybe_unused]] bool has_data() const {
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
        return !data.empty() || texture_id != 0 || hw_buffer != nullptr;
#else
        return !data.empty() || texture_id != 0;
#endif
    }

    [[nodiscard]] [[maybe_unused]] const uint8_t* get_y_plane() const {
        return data.data();
    }

    [[nodiscard]] [[maybe_unused]] const uint8_t* get_uv_plane(int width, int height) const {
        return data.data() + (width * height);
    }
};

struct [[maybe_unused]] dual_frames : public switchboard::event {
    eye_frame left_eye{};
    eye_frame right_eye{};

    eye_frame left_depth{};
    eye_frame right_depth{};

#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    eye_frame left_motion_vec{};
    eye_frame right_motion_vec{};
#endif // defined(__ANDROID__) && defined(ILLIXR_VULKAN)

    int width{0};
    int height{0};

    // Format of the frame data
    frame_format format{frame_format::nv12};

    // Presentation timestamp
    time_point render_time{};

    BUFFER_TYPE pose{};

    // Frame sequence number (for debugging/sync)
    uint64_t frame_number{0};

    // ID of the combined_pose that was used to render this frame on the server.
    // Used by oxr_interface to look up the original headset pose measurement
    // for latency and accuracy logging.
    uint64_t pose_id{0};
    double   encode_time{0.};
#ifdef ILLIXR_ENABLE_BOBA
    // Presentation metadata matched to this exact decoded frame.
    stereo_presentation_mode presentation_mode{stereo_presentation_mode::stereo_fullscreen};
    float                    content_aspect_ratio{0.0F};
    boba_frame_overlay       boba_overlay{};
    boba_modal_overlay       boba_modal{};
    // Optional cached modal pixels resolved from boba_modal.texture_id on Quest.
    std::shared_ptr<const std::vector<std::uint8_t>> boba_modal_rgba{};
#endif
    // Projection clip planes forwarded from the server's compressed_frame.
    // Required by XrCompositionLayerDepthInfoKHR and
    // XrCompositionLayerSpaceWarpInfoFB (nearZ / farZ fields).
    // Zero-initialised; only meaningful when has_valid_depth() is true.
    float near_z{0.0f};
    float far_z{0.0f};

#ifdef USING_OPENXR
    std::array<float, 2> fov_left  = {0.0f, 0.0f};
    std::array<float, 2> fov_right = {0.0f, 0.0f};
    std::array<float, 2> fov_up    = {0.0f, 0.0f};
    std::array<float, 2> fov_down  = {0.0f, 0.0f};
#endif

    bool has_depth{false};
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    bool has_motion_vectors{false};
#endif // defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    dual_frames() = default;

    dual_frames(time_point tp, std::vector<uint8_t> left, std::vector<uint8_t> right, int w, int h, uint64_t frame_num = 0,
                bool has_depth = false)
        : left_eye{std::move(left)}
        , right_eye{std::move(right)}
        , width{w}
        , height{h}
        , format{frame_format::nv12}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{has_depth} { }

#ifndef ILLIXR_VULKAN
    dual_frames(time_point tp, GLuint left_tex, const float* left_transform, GLuint right_tex, const float* right_transform,
                int w, int h, uint64_t frame_num = 0, bool has_depth = false)
        : left_eye{left_tex, left_transform}
        , right_eye{right_tex, right_transform}
        , width{w}
        , height{h}
        , format{frame_format::external_oes}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{has_depth} { }

    dual_frames(time_point tp, GLuint left_tex, const float* left_transform, GLuint right_tex, const float* right_transform,
                GLuint left_depth_tex, const float* left_depth_transform, GLuint right_depth_tex,
                const float* right_depth_transform, int w, int h, uint64_t frame_num = 0)
        : left_eye{left_tex, left_transform}
        , right_eye{right_tex, right_transform}
        , left_depth{left_depth_tex, left_depth_transform}
        , right_depth{right_depth_tex, right_depth_transform}
        , width{w}
        , height{h}
        , format{frame_format::external_oes}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{true} { }
#endif

#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    dual_frames(time_point tp, AHardwareBuffer* left_buf, AHardwareBuffer* right_buf, int w, int h, uint64_t frame_num = 0)
        : left_eye{left_buf}
        , right_eye{right_buf}
        , width{w}
        , height{h}
        , format{frame_format::hardware_buffer}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{false}
        , has_motion_vectors{false} { }

    dual_frames(time_point tp, AHardwareBuffer* left_color_buf, AHardwareBuffer* right_color_buf,
                AHardwareBuffer* left_depth_buf, AHardwareBuffer* right_depth_buf, int w, int h, uint64_t frame_num = 0)
        : left_eye{left_color_buf}
        , right_eye{right_color_buf}
        , left_depth{left_depth_buf}
        , right_depth{right_depth_buf}
        , width{w}
        , height{h}
        , format{frame_format::hardware_buffer}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{true}
        , has_motion_vectors{false} { }

    dual_frames(time_point tp, AHardwareBuffer* left_color_buf, AHardwareBuffer* right_color_buf,
                AHardwareBuffer* left_depth_buf, AHardwareBuffer* right_depth_buf, AHardwareBuffer* left_motion_buf,
                AHardwareBuffer* right_motion_buf, int w, int h, uint64_t frame_num = 0)
        : left_eye{left_color_buf}
        , right_eye{right_color_buf}
        , left_depth{left_depth_buf}
        , right_depth{right_depth_buf}
        , left_motion_vec{left_motion_buf}
        , right_motion_vec{right_motion_buf}
        , width{w}
        , height{h}
        , format{frame_format::hardware_buffer}
        , render_time{tp}
        , frame_number{frame_num}
        , has_depth{true}
        , has_motion_vectors{true} { }
#endif // defined(__ANDROID__) && defined(ILLIXR_VULKAN)

    [[nodiscard]] bool is_valid() const {
        if (format == frame_format::external_oes) {
            return left_eye.texture_id != 0 && right_eye.texture_id != 0;
        }
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
        if (format == frame_format::hardware_buffer) {
            return left_eye.hw_buffer != nullptr && right_eye.hw_buffer != nullptr;
        }
#endif
        return !left_eye.data.empty() && !right_eye.data.empty();
    }

    [[nodiscard]] bool has_valid_depth() const {
        if (!has_depth)
            return false;
#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
        if (format == frame_format::hardware_buffer) {
            return left_depth.hw_buffer != nullptr && right_depth.hw_buffer != nullptr;
        }
#endif
        return left_depth.texture_id != 0 && right_depth.texture_id != 0;
    }

#if defined(__ANDROID__) && defined(ILLIXR_VULKAN)
    [[nodiscard]] bool has_valid_motion_vectors() const {
        return has_motion_vectors && left_motion_vec.hw_buffer != nullptr && right_motion_vec.hw_buffer != nullptr;
    }
#endif // defined(__ANDROID__) && defined(ILLIXR_VULKAN)
};
} // namespace ILLIXR::data_format