File stereo_renderer.hpp
File List > openxr_interface > stereo_renderer.hpp
Go to the documentation of this file
#pragma once
#include "illixr/data_format/frame.hpp"
#include "illixr/quest3_params.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ILLIXR {
class stereo_renderer {
public:
stereo_renderer() = default;
~stereo_renderer();
// Non-copyable
stereo_renderer(const stereo_renderer&) = delete;
stereo_renderer& operator=(const stereo_renderer&) = delete;
bool initialize(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, VkQueue queue,
uint32_t queue_family, VkFormat swapchain_format);
void receive_frame(const data_format::dual_frames& frame);
bool render_eye(int eye, VkImage swapchain_image, uint32_t swapchain_width, uint32_t swapchain_height,
VkSemaphore signal_semaphore = VK_NULL_HANDLE);
bool render_eye_depth(int eye, VkImage depth_swapchain_image, VkFormat depth_format, uint32_t swapchain_width,
uint32_t swapchain_height);
bool render_eye_motion_vec(int eye, VkImage mv_swapchain_image, uint32_t swapchain_width, uint32_t swapchain_height);
void wait_idle();
// Check if renderer is initialized
[[nodiscard]] bool is_initialized() const {
return initialized_;
}
// Check if we have a valid frame to render
[[nodiscard]] bool has_frame() const {
return has_valid_frame_;
}
void cleanup();
// Set the crop dimensions (original size before padding)
// Call this before initialize() or after receiving frame metadata
void set_crop_region(int original_width, int original_height, int padded_width, int padded_height);
#ifdef COMBINED_ENCODING
void set_combined_encoding(bool enabled) {
combined_encoding_ = enabled;
}
#endif
private:
// ── Imported decoder image (one per unique AHardwareBuffer pointer) ────────
struct imported_image {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView image_view = VK_NULL_HANDLE;
VkSamplerYcbcrConversion ycbcr_conv = VK_NULL_HANDLE;
VkSampler sampler = VK_NULL_HANDLE;
uint64_t external_fmt = 0; // from AHardwareBufferProperties
AHardwareBuffer* hw_buffer = nullptr;
};
// ── Shader compilation ────────────────────────────────────────────────────
static VkShaderModule create_shader_module(VkDevice device, const uint32_t* spv, size_t word_count);
// ── Per-eye descriptor setup ───────────────────────────────────────────────
bool update_descriptor_set(int eye, const imported_image& img);
bool update_depth_descriptor_set(int eye, const imported_image& img);
bool update_mv_descriptor_set(int eye, const imported_image& img);
// ── AHardwareBuffer → Vulkan import ────────────────────────────────────────
imported_image* import_hardware_buffer(AHardwareBuffer* hw_buffer);
void destroy_imported_image(imported_image& img);
imported_image* import_mv_hardware_buffer(AHardwareBuffer* hw_buffer);
// ── Color pipeline helpers ─────────────────────────────────────────────────
bool create_render_pass();
bool create_pipeline(const imported_image& prototype);
bool create_command_pool();
bool allocate_command_buffers();
bool create_descriptor_pool();
#ifdef ILLIXR_ENABLE_BOBA
// ── Boba vector/modal overlay helpers ────────────────────────────────────
struct overlay_vertex {
float x;
float y;
float red;
float green;
float blue;
float alpha;
};
struct modal_vertex {
float x;
float y;
float u;
float v;
};
bool create_boba_overlay_resources();
bool create_overlay_pipeline();
bool create_modal_pipeline();
bool create_host_visible_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer* buffer, VkDeviceMemory* memory,
void** mapped);
bool upload_modal_texture(std::uint64_t texture_id, std::uint32_t width, std::uint32_t height,
const std::vector<std::uint8_t>& rgba);
void destroy_modal_texture();
void update_boba_overlay_state(const data_format::dual_frames& frame);
void record_boba_overlays(VkCommandBuffer command_buffer, int eye);
std::uint32_t find_memory_type(std::uint32_t type_filter, VkMemoryPropertyFlags properties) const;
#endif
// ── Depth pipeline helpers ─────────────────────────────────────────────────
bool create_depth_render_pass(VkFormat depth_format);
bool create_depth_pipeline(const imported_image& prototype, VkFormat depth_format);
bool create_depth_descriptor_pool();
bool allocate_depth_command_buffers();
// ── Motion-vector pipeline helpers ────────────────────────────────────────
bool create_mv_render_pass();
bool create_mv_pipeline(const imported_image& prototype);
bool create_mv_descriptor_pool();
bool allocate_mv_command_buffers();
// Vulkan device objects (not owned — owned by oxr_interface)
VkInstance instance_ = VK_NULL_HANDLE;
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
VkDevice device_ = VK_NULL_HANDLE;
VkQueue queue_ = VK_NULL_HANDLE;
uint32_t queue_family_ = 0;
VkFormat swapchain_format_ = VK_FORMAT_R8G8B8A8_UNORM;
// Color rendering resources (owned)
VkRenderPass render_pass_ = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline pipeline_ = VK_NULL_HANDLE;
VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSetLayout desc_set_layout_ = VK_NULL_HANDLE;
VkCommandPool command_pool_ = VK_NULL_HANDLE;
// Per-eye color resources
std::array<VkDescriptorSet, 2> descriptor_sets_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkCommandBuffer, 2> command_buffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkFence, 2> render_fences_{VK_NULL_HANDLE, VK_NULL_HANDLE};
// Transient per-frame objects from the previous render_eye() call.
// Destroyed at the top of the next call, after the fence has signalled,
// because Vulkan requires them to outlive their command buffer submission.
std::array<VkFramebuffer, 2> prev_framebuffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkImageView, 2> prev_swapchain_views_{VK_NULL_HANDLE, VK_NULL_HANDLE};
#ifdef ILLIXR_ENABLE_BOBA
// Boba vector overlays (controller rays, placement rectangle, markers).
VkPipelineLayout overlay_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline overlay_pipeline_ = VK_NULL_HANDLE;
std::array<VkBuffer, 2> overlay_vertex_buffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkDeviceMemory, 2> overlay_vertex_memories_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<void*, 2> overlay_vertex_mapped_{nullptr, nullptr};
std::array<std::vector<overlay_vertex>, 2> overlay_vertices_{};
// Boba modal bitmap card.
VkDescriptorSetLayout modal_desc_set_layout_ = VK_NULL_HANDLE;
VkDescriptorPool modal_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSet modal_descriptor_set_ = VK_NULL_HANDLE;
VkPipelineLayout modal_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline modal_pipeline_ = VK_NULL_HANDLE;
VkSampler modal_sampler_ = VK_NULL_HANDLE;
VkImage modal_image_ = VK_NULL_HANDLE;
VkDeviceMemory modal_image_memory_ = VK_NULL_HANDLE;
VkImageView modal_image_view_ = VK_NULL_HANDLE;
std::array<VkBuffer, 2> modal_vertex_buffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkDeviceMemory, 2> modal_vertex_memories_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<void*, 2> modal_vertex_mapped_{nullptr, nullptr};
std::array<std::array<modal_vertex, 6>, 2> modal_vertices_{};
std::array<std::uint32_t, 2> modal_vertex_counts_{0, 0};
data_format::boba_modal_overlay active_modal_{};
std::uint64_t modal_texture_id_{0};
std::uint32_t overlay_source_width_{0};
std::uint32_t overlay_source_height_{0};
bool render_boba_overlays_{false};
#endif
// Depth pipeline resources (owned)
VkRenderPass depth_render_pass_ = VK_NULL_HANDLE;
VkPipelineLayout depth_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline depth_pipeline_ = VK_NULL_HANDLE;
VkDescriptorPool depth_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSetLayout depth_desc_set_layout_ = VK_NULL_HANDLE;
// Per-eye depth resources
std::array<VkDescriptorSet, 2> depth_descriptor_sets_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkCommandBuffer, 2> depth_command_buffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkFence, 2> depth_fences_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkFramebuffer, 2> prev_depth_framebuffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkImageView, 2> prev_depth_views_{VK_NULL_HANDLE, VK_NULL_HANDLE};
// Immutable sampler embedded in depth_desc_set_layout_. Must outlive the
// layout (and therefore the pipeline). Created in create_depth_pipeline(),
// destroyed in cleanup().
VkSampler depth_immutable_sampler_ = VK_NULL_HANDLE;
// Motion-vector pipeline resources (owned)
VkRenderPass mv_render_pass_ = VK_NULL_HANDLE;
VkPipelineLayout mv_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline mv_pipeline_ = VK_NULL_HANDLE;
VkDescriptorPool mv_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSetLayout mv_desc_set_layout_ = VK_NULL_HANDLE;
VkSampler mv_immutable_sampler_ = VK_NULL_HANDLE;
std::array<VkDescriptorSet, 2> mv_descriptor_sets_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkCommandBuffer, 2> mv_command_buffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkFence, 2> mv_fences_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkFramebuffer, 2> prev_mv_framebuffers_{VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkImageView, 2> prev_mv_swapchain_views_{VK_NULL_HANDLE, VK_NULL_HANDLE};
// Cached color imported images keyed by AHardwareBuffer pointer.
std::unordered_map<AHardwareBuffer*, std::unique_ptr<imported_image>> image_cache_;
// Cached depth imported images (separate cache — different AHardwareBuffers).
std::unordered_map<AHardwareBuffer*, std::unique_ptr<imported_image>> depth_image_cache_;
std::unordered_map<AHardwareBuffer*, std::unique_ptr<imported_image>> mv_image_cache_;
// Current frame's per-eye imported image pointers (stable — heap-allocated in image_cache_).
std::array<imported_image*, 2> current_images_{nullptr, nullptr};
// Current frame's per-eye depth imported image pointers (into depth_image_cache_).
std::array<imported_image*, 2> current_depth_images_{nullptr, nullptr};
std::array<imported_image*, 2> current_mv_images_{nullptr, nullptr};
// Crop scale factors
float crop_scale_x_{1.0f};
float crop_scale_y_{1.0f};
// Extension function pointers
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vk_get_ahb_properties_ = nullptr;
bool initialized_ = false;
bool has_valid_frame_ = false;
bool pipeline_created_ = false;
bool depth_pipeline_created_ = false;
bool mv_pipeline_created_ = false;
bool has_depth_frame_ = false;
bool has_mv_frame_ = false;
#ifdef COMBINED_ENCODING
bool combined_encoding_ = false;
#endif
};
} // namespace ILLIXR