Skip to content

File offload_rendering_server.hpp

File List > offload_rendering_server > offload_rendering_server.hpp

Go to the documentation of this file

#pragma once
#define MONADO_IS_SOURCE
#define DOUBLE_INCLUDE

#ifdef _WIN32
#    ifndef XRT_OS_WINDOWS
#        define XRT_OS_WINDOWS
#    endif
#endif

#include "drivers/illixr/illixr_framebuffer.h"
#ifdef USING_OPENXR
#    include "illixr/data_format/poses/combined_pose.hpp"
#endif
#include "illixr/data_format/frame.hpp"
#include "illixr/data_format/hmd_config.hpp"
#include "illixr/data_format/pose_id.hpp"
#include "illixr/data_format/pose_prediction.hpp"
#include "illixr/data_format/serialization/frame.hpp"
#include "illixr/data_format/serialization/head_pose.hpp"
#include "illixr/switchboard.hpp"
#include "illixr/threadloop.hpp"
#include "illixr/vk/display_provider.hpp"
#include "illixr/vk/render_pass.hpp"
#include "illixr/vk/vulkan_utils.hpp"
#include "pose_relay.hpp"

#ifdef NVENC_ENCODER
#    include "nvenc/nvenc_encoder.hpp"
#    define OFFLOAD_RENDERING_BITRATE 100000000
#else
#    include "illixr/vk/ffmpeg_utils.hpp"
#endif

#undef DOUBLE_INCLUDE

#include 
#include 
#include 
#include 

namespace ILLIXR {

class MY_EXPORT_API offload_rendering_server
    : public threadloop
    , public vulkan::timewarp
    , public data_format::pose_prediction {
public:
    offload_rendering_server(const std::string& name, phonebook* pb);
    void start() override;
    void stop() override;
    void _p_thread_setup() override;

    void setup(VkRenderPass render_pass, uint32_t subpass, std::shared_ptr<vulkan::buffer_pool<BUFFER_TYPE>> buffer_pool,
               bool input_texture_vulkan_coordinates, struct illixr_framebuffer* framebuffer_array, VkExtent2D extent) override;

    bool is_external() override {
        return true;
    }

    void destroy() override;

    [[nodiscard]] POSE_TYPE get_fast_pose() const override {
        return pose_relay_->get_pose();
    }

#ifndef USING_OPENXR
    ILLIXR::data_format::pose::head_pose_type get_true_pose() const override {
        return get_fast_pose().pose;
    }
#endif

    [[nodiscard]] POSE_TYPE get_fast_pose(POSE_TIME_TYPE future_time) const override {
        return pose_relay_->get_pose(future_time);
    }

    [[nodiscard]] bool fast_pose_reliable() const override {
        return pose_relay_->fast_pose_reliable();
    }

    [[nodiscard]] bool true_pose_reliable() const override {
        return false;
    }

    void set_offset(const Eigen::Quaternionf& orientation) override {
        (void) orientation;
    }

    Eigen::Quaternionf get_offset() override {
        return {};
    }

    [[nodiscard]] data_format::pose::head_pose_type correct_pose(const data_format::pose::head_pose_type& pose) const override {
        (void) pose;
        return {};
    }

    void record_command_buffer(VkCommandBuffer commandBuffer, VkFramebuffer framebuffer, int buffer_ind, bool left) override {
        (void) commandBuffer;
        (void) framebuffer;
        (void) buffer_ind;
        (void) left;
    }

protected:
    threadloop::skip_option _p_should_skip() override {
        return threadloop::_p_should_skip();
    }

    void _p_one_iteration() override;

private:
    void enqueue_for_network_send(BUFFER_TYPE& pose
#ifdef USING_OPENXR
                                  ,
                                  uint64_t pose_id
#endif
    );

#ifdef NVENC_ENCODER
    // ========================================================================
    // NVENC-specific methods (no FFmpeg dependency)
    // ========================================================================

    void nvenc_init_vulkan_context();

    void nvenc_init_encoders();

    void nvenc_import_buffer_pool_images();

    void nvenc_encode_frames(int ind);

#else
    // ========================================================================
    // FFmpeg-specific methods
    // ========================================================================

    void ffmpeg_init_device();

    void ffmpeg_init_cuda_device();

    void ffmpeg_init_frame_ctx();

    void ffmpeg_init_cuda_frame_ctx();

    // Linux depth is a separate RGBA grayscale stream, not the color context.
    void ffmpeg_init_depth_frame_ctx();
    void ffmpeg_transfer_depth(size_t buffer_index, size_t eye);

    void ffmpeg_init_buffer_pool();

    void ffmpeg_init_encoder();

    void ffmpeg_populate_buffer_pool_from_framebuffers();
#endif
    void sender_loop();

    std::shared_ptr<spdlog::logger>                            log_;
    std::shared_ptr<vulkan::display_provider>                  display_provider_;
    std::shared_ptr<switchboard>                               switchboard_;
    switchboard::network_writer<data_format::compressed_frame> frames_topic_;

    mutable data_format::pose::fast_head_pose_type cached_head_pose_;

    mutable time_point last_processed_time_{};

    std::shared_ptr<vulkan::buffer_pool<BUFFER_TYPE>> buffer_pool_;

#ifdef OPENXR_CLIENT
    int framerate_ = 90;
#else
    int framerate_ = 144;
#endif
    long bitrate_ = OFFLOAD_RENDERING_BITRATE;

    bool use_pass_depth_ = false;
#ifdef _WIN32
    // These are currently only supported with Unity, which only supports OpenXR on Windows
    bool use_pass_motion_vectors_ = false;
#endif
    bool nalu_only_ = false;

    std::atomic<bool> framebuffers_imported_{false};

    // Set after each color encode call and copied into compressed_frame::is_keyframe.
    bool color_frame_is_keyframe_ = false;

#ifdef NVENC_ENCODER
    // ========================================================================
    // NVENC-specific members
    // ========================================================================

    // Vulkan context for CUDA interop
    vulkan_context vk_ctx_;

    // NVENC encoders (one per eye for color, optionally for depth and motion vectors)
    std::array<std::unique_ptr<nvenc_encoder>, 2> color_encoder_;
    std::array<std::unique_ptr<nvenc_encoder>, 2> depth_encoder_;
    std::array<std::unique_ptr<nvenc_encoder>, 2> motion_vec_encoder_;

    // Imported image indices: [buffer_index][eye] for color, depth, and motion vectors
    std::vector<std::array<int, 2>> color_imported_indices_;
    std::vector<std::array<int, 2>> depth_imported_indices_;
    std::vector<std::array<int, 2>> motion_vec_imported_indices_;

    // Set by nvenc_encode_frames() immediately after each color encode call.
    // Read by enqueue_for_network_send() to populate compressed_frame::is_keyframe.
    // Using last_frame_was_keyframe() from the encoder is authoritative for both
    // HEVC (IDR) and AV1 (KEY_FRAME / auto-GOP I-frame), avoiding any need for
    // bitstream parsing on the client side.
    // bool color_frame_is_keyframe_ = false;

#    ifdef COMBINED_ENCODING
    // Under COMBINED_ENCODING a single encoder handles both eyes at double width.
    // color_encoder_[0] is used; color_encoder_[1] is unused.
    // encode_out_combined_color_packet_ carries the single combined bitstream;
    // encode_out_color_packets_ is not used for color in this mode.
    PACKET_TYPE encode_out_combined_color_packet_{};
#    endif // COMBINED_ENCODING

#else
    // ========================================================================
    // FFmpeg-specific members
    // ========================================================================

    std::vector<std::array<vulkan::ffmpeg_utils::ffmpeg_vk_frame, 2>> avvk_color_frames_;
    std::vector<std::array<vulkan::ffmpeg_utils::ffmpeg_vk_frame, 2>> avvk_depth_frames_;

    AVBufferRef*         device_ctx_            = nullptr;
    AVBufferRef*         cuda_device_ctx_       = nullptr;
    AVBufferRef*         frame_ctx_             = nullptr;
    AVBufferRef*         cuda_frame_ctx_        = nullptr;
    AVBufferRef*         depth_frame_ctx_       = nullptr;
    AVBufferRef*         cuda_depth_frame_ctx_  = nullptr;
    VkCommandPool        depth_transfer_pool_   = VK_NULL_HANDLE;
    VkCommandBuffer      depth_transfer_cmd_    = VK_NULL_HANDLE;
    VkFence              depth_transfer_fence_  = VK_NULL_HANDLE;
    PFN_vkWaitSemaphores depth_wait_semaphores_ = nullptr;

    AVCodecContext*         codec_color_ctx_ = nullptr;
    std::array<AVFrame*, 2> encode_src_color_frames_{};
#endif
    std::array<PACKET_TYPE, 2> encode_out_color_packets_{};
#ifndef NVENC_ENCODER
    AVCodecContext*         codec_depth_ctx_ = nullptr;
    std::array<AVFrame*, 2> encode_src_depth_frames_{};
#endif
    std::array<PACKET_TYPE, 2> encode_out_depth_packets_{};
    std::array<PACKET_TYPE, 2> encode_out_motion_vec_packets_{};

    uint64_t frame_count_ = 0;

    float near_z_{0.};
    float far_z_{0.};
    // Boxcar FPS: timestamps of frames encoded within the last 1 second.
    // Updated every frame in _p_one_iteration(); get_fps() returns the count.
    std::deque<std::chrono::high_resolution_clock::time_point> fps_window_;
    std::map<std::string, long long>                           metrics_;

    int32_t last_frame_ind_ = -1;
#ifdef OPENXR_CLIENT
    xrt_pose last_sent_pose_{};
#else
    data_format::pose::fast_head_pose_type last_sent_pose_{};
#endif

    struct illixr_framebuffer* framebuffer_array_ = nullptr;
    VkExtent2D                 extent_            = {0, 0};

    std::shared_ptr<pose_relay> pose_relay_;
#ifdef OPENXR_CLIENT
    hmd_config hmd_setup_;
#endif

    std::atomic<bool> ready_{false};
    uint64_t          frame_number_{0};
    double            current_encode_time_{0.};

    std::deque<std::shared_ptr<data_format::compressed_frame>> send_queue_;

    std::mutex                  send_queue_mutex_;
    std::condition_variable     send_queue_cv_;
    std::thread                 sender_thread_;
    static constexpr size_t     MAX_QUEUE_DEPTH = 6;
    std::atomic<bool>           sender_running_{false};
    std::map<uint64_t, uint8_t> pose_usage_{};
#ifdef _WIN32
    std::map<uint64_t, std::chrono::steady_clock::time_point> frame_timing_{};
#else
    std::map<uint64_t, std::chrono::time_point<std::chrono::high_resolution_clock>> frame_timing_{};
#endif
#ifdef OPENXR_CLIENT
    float overscan_ = 1.f;
#endif
};
} // namespace ILLIXR