Skip to content

File plugin.cpp

File List > native_renderer > plugin.cpp

Go to the documentation of this file

#include "plugin.hpp"

#include "illixr/global_module_defs.hpp"

#include 
#include 
#include 
#include 
#include 
#include 

#define TINYOBJLOADER_IMPLEMENTATION
#include "illixr/gl_util/lib/tiny_obj_loader.h"

#define NATIVE_RENDERER_BUFFER_POOL_SIZE 3

using namespace ILLIXR;
using namespace ILLIXR::data_format;

[[maybe_unused]] native_renderer::native_renderer(const std::string& name, phonebook* pb)
    : threadloop{name, pb}
    , switchboard_{phonebook_->lookup_impl<switchboard>()}
    , log_(spdlogger(switchboard_->get_env_char("NATIVE_RENDERER_LOG_LEVEL")))
    , pose_prediction_{phonebook_->lookup_impl<pose_prediction>()}
    , display_sink_{phonebook_->lookup_impl<vulkan::display_provider>()}
    , timewarp_{phonebook_->lookup_impl<vulkan::timewarp>()}
    , app_{phonebook_->lookup_impl<vulkan::app>()}
    , clock_{phonebook_->lookup_impl<relative_clock>()}
    , vsync_{switchboard_->get_reader<switchboard::event_wrapper<time_point>>("vsync_estimate")}
    , last_fps_update_{std::chrono::duration<long, std::nano>{0}} {
    if (switchboard_->get_env_char("ILLIXR_WIDTH") == nullptr || switchboard_->get_env_char("ILLIXR_HEIGHT") == nullptr) {
        log_->warn("Please define ILLIXR_WIDTH and ILLIXR_HEIGHT. Default values used.");
        width_  = display_sink_->swapchain_extent_.width;
        height_ = display_sink_->swapchain_extent_.height;
    } else {
        width_  = std::stoi(switchboard_->get_env_char("ILLIXR_WIDTH"));
        height_ = std::stoi(switchboard_->get_env_char("ILLIXR_HEIGHT"));
    }

    export_dma_ = switchboard_->get_env_bool("ILLIXR_EXPORT_DMA");
}

native_renderer::~native_renderer() {
    vkDeviceWaitIdle(display_sink_->vk_device_);

    app_->destroy();
    timewarp_->destroy();

    for (auto& framebuffer : swapchain_framebuffers_) {
        vkDestroyFramebuffer(display_sink_->vk_device_, framebuffer, nullptr);
    }

    // drain deletion_queue_
    while (!deletion_queue_.empty()) {
        deletion_queue_.top()();
        deletion_queue_.pop();
    }
}

void native_renderer::_p_thread_setup() {
    depth_images_.resize(NATIVE_RENDERER_BUFFER_POOL_SIZE);
    offscreen_images_.resize(NATIVE_RENDERER_BUFFER_POOL_SIZE);
    depth_attachment_images_.resize(NATIVE_RENDERER_BUFFER_POOL_SIZE);

    if (timewarp_->is_external() || app_->is_external()) {
        create_offscreen_pool();
    }
    for (auto i = 0; i < NATIVE_RENDERER_BUFFER_POOL_SIZE; i++) {
        for (auto eye = 0; eye < 2; eye++) {
            create_offscreen_target(offscreen_images_[i][eye]);
            create_offscreen_target(depth_images_[i][eye]);
            create_depth_image(depth_attachment_images_[i][eye]);
        }
    }
    this->buffer_pool_ = std::make_shared<vulkan::buffer_pool<pose::fast_head_pose_type>>(offscreen_images_, depth_images_);

    command_pool_ =
        vulkan::create_command_pool(display_sink_->vk_device_, display_sink_->queues_[vulkan::queue::GRAPHICS].family);
    app_command_buffer_      = vulkan::create_command_buffer(display_sink_->vk_device_, command_pool_);
    timewarp_command_buffer_ = vulkan::create_command_buffer(display_sink_->vk_device_, command_pool_);
    deletion_queue_.emplace([=]() {
        vkDestroyCommandPool(display_sink_->vk_device_, command_pool_, nullptr);
    });

    create_sync_objects();
    if (!app_->is_external()) {
        create_app_pass();
    }
    if (!timewarp_->is_external()) {
        create_timewarp_pass();
    }
    if (!app_->is_external()) {
        create_offscreen_framebuffers();
    }
    create_swapchain_framebuffers();
    app_->setup(app_pass_, 0, buffer_pool_);
    timewarp_->setup(timewarp_pass_, 0, buffer_pool_, app_->is_external());
}

void native_renderer::_p_one_iteration() {
    if (!timewarp_->is_external()) {
        display_sink_->poll_window_events();

        if (swapchain_image_index_ == UINT32_MAX) {
            // Acquire the next image from the swapchain
            auto ret = (vkAcquireNextImageKHR(display_sink_->vk_device_, display_sink_->vk_swapchain_, UINT64_MAX,
                                              image_available_semaphore_, VK_NULL_HANDLE, &swapchain_image_index_));

            // Check if the swapchain is out of date or suboptimal
            if (ret == VK_ERROR_OUT_OF_DATE_KHR || ret == VK_SUBOPTIMAL_KHR) {
                display_sink_->recreate_swapchain();
                return;
            } else {
                VK_ASSERT_SUCCESS(ret)
            }
        }
        VK_ASSERT_SUCCESS(vkResetFences(display_sink_->vk_device_, 1, &frame_fence_))
    }

    auto fast_pose = pose_prediction_->get_fast_pose();
    if (!app_->is_external()) {
        // Get the current fast pose and update the uniforms
        // log_->debug("Updating uniforms");
        app_->update_uniforms(fast_pose);

        VK_ASSERT_SUCCESS(vkResetCommandBuffer(app_command_buffer_, 0))

        vulkan::image_index_t buffer_index = buffer_pool_->src_acquire_image();

        // Record the command buffer
        record_src_command_buffer(buffer_index);

        // Submit the command buffer to the graphics queue
        const uint64_t ignored     = 0;
        const uint64_t fired_value = timeline_semaphore_value_ + 1;

        timeline_semaphore_value_ += 1;
        VkTimelineSemaphoreSubmitInfo timeline_submit_info{
            VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, // sType
            nullptr,                                          // pNext
            1,                                                // waitSemaphoreValueCount
            &ignored,                                         // pWaitSemaphoreValues
            1,                                                // signalSemaphoreValueCount
            &fired_value                                      // pSignalSemaphoreValues
        };

        VkSubmitInfo submit_info{
            VK_STRUCTURE_TYPE_SUBMIT_INFO,  // sType
            &timeline_submit_info,          // pNext
            0,                              // waitSemaphoreCount
            nullptr,                        // pWaitSemaphores
            nullptr,                        // pWaitDstStageMask
            1,                              // commandBufferCount
            &app_command_buffer_,           // pCommandBuffers
            1,                              // signalSemaphoreCount
            &app_render_finished_semaphore_ // pSignalSemaphores
        };
        if (timewarp_->is_external()) {
            submit_info.waitSemaphoreCount = 0;
            submit_info.pWaitSemaphores    = nullptr;
            submit_info.pWaitDstStageMask  = nullptr;
        }

        VK_ASSERT_SUCCESS(
            vulkan::locked_queue_submit(display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS], 1, &submit_info, nullptr))

        // Wait for the application to finish rendering
        VkSemaphoreWaitInfo wait_info{
            VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, // sType
            nullptr,                               // pNext
            0,                                     // flags
            1,                                     // semaphoreCount
            &app_render_finished_semaphore_,       // pSemaphores
            &fired_value                           // pValues
        };
        VK_ASSERT_SUCCESS(vkWaitSemaphores(display_sink_->vk_device_, &wait_info, UINT64_MAX))
        auto pt = fast_pose;
        buffer_pool_->src_release_image(buffer_index, std::move(pt));
    }

    // TODO: for DRM, get vsync estimate
    auto next_swap = vsync_.get_ro_nullable();
    if (next_swap == nullptr) {
        std::this_thread::sleep_for(display_params::period / 6.0 * 5);
        log_->debug("No vsync estimate!");
    } else {
        ILLIXR::time_point warp_start = **next_swap;
        warp_start -= display_params::period / 6; // leave 1/6 of the frame for warping

        const ILLIXR::duration remaining = warp_start - clock_->now();
        if (remaining > ILLIXR::duration::zero()) {
            std::this_thread::sleep_for(remaining);
        }
    }

    if (!timewarp_->is_external()) {
        // Update the timewarp uniforms and submit the timewarp command buffer to the graphics queue
        auto res          = buffer_pool_->post_processing_acquire_image();
        auto buffer_index = res.first;
        auto pose         = res.second;
        timewarp_->update_uniforms(pose);

        if (buffer_index == -1) {
            return;
        }

        record_post_processing_command_buffer(buffer_index, swapchain_image_index_);

        VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
        VkSubmitInfo         timewarp_submit_info{
            VK_STRUCTURE_TYPE_SUBMIT_INFO,       // sType
            nullptr,                             // pNext
            1,                                   // waitSemaphoreCount
            &image_available_semaphore_,         // pWaitSemaphores
            wait_stages,                         // pWaitDstStageMask
            1,                                   // commandBufferCount
            &timewarp_command_buffer_,           // pCommandBuffers
            1,                                   // signalSemaphoreCount
            &timewarp_render_finished_semaphore_ // pSignalSemaphores
        };

        VK_ASSERT_SUCCESS(vulkan::locked_queue_submit(display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS], 1,
                                                      &timewarp_submit_info, frame_fence_))

        // Present the rendered image
        VkPresentInfoKHR present_info{
            VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,   // sType
            nullptr,                              // pNext
            1,                                    // waitSemaphoreCount
            &timewarp_render_finished_semaphore_, // pWaitSemaphores
            1,                                    // swapchainCount
            &display_sink_->vk_swapchain_,        // pSwapchains
            &swapchain_image_index_,              // pImageIndices
            nullptr                               // pResults
        };

        VkResult ret = VK_SUCCESS;
        {
            std::lock_guard<std::mutex> lock{*display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS].mutex};
            ret = (vkQueuePresentKHR(display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS].vk_queue, &present_info));
        }

        // Wait for the previous frame to finish rendering
        VK_ASSERT_SUCCESS(vkWaitForFences(display_sink_->vk_device_, 1, &frame_fence_, VK_TRUE, UINT64_MAX))

        swapchain_image_index_ = UINT32_MAX;

        buffer_pool_->post_processing_release_image(buffer_index);

        if (ret == VK_ERROR_OUT_OF_DATE_KHR || ret == VK_SUBOPTIMAL_KHR) {
            display_sink_->recreate_swapchain();
        } else {
            VK_ASSERT_SUCCESS(ret)
        }
    }

    // #ifndef NDEBUG
    // Print the FPS
    if (clock_->now() - last_fps_update_ > std::chrono::milliseconds(1000)) {
        log_->info("FPS: {}", fps_);
        fps_             = 0;
        last_fps_update_ = clock_->now();
    } else {
        fps_++;
    }
    // #endif
}

[[maybe_unused]] void native_renderer::recreate_swapchain() {
    vkDeviceWaitIdle(display_sink_->vk_device_);
    for (auto& framebuffer : swapchain_framebuffers_) {
        vkDestroyFramebuffer(display_sink_->vk_device_, framebuffer, nullptr);
    }
    display_sink_->recreate_swapchain();
    create_swapchain_framebuffers();
}

void native_renderer::create_swapchain_framebuffers() {
    swapchain_framebuffers_.resize(display_sink_->swapchain_image_views_.size());

    for (auto i = 0; i < display_sink_->swapchain_image_views_.size(); i++) {
        std::array<VkImageView, 1> attachments = {display_sink_->swapchain_image_views_[i]};

        assert(timewarp_pass_ != VK_NULL_HANDLE);
        VkFramebufferCreateInfo framebuffer_info{
            VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, // sType
            nullptr,                                   // pNext
            0,                                         // flags
            timewarp_pass_,                            // renderPass
            attachments.size(),                        // attachmentCount
            attachments.data(),                        // pAttachments
            display_sink_->swapchain_extent_.width,    // width
            display_sink_->swapchain_extent_.height,   // height
            1                                          // layers
        };

        VK_ASSERT_SUCCESS(
            vkCreateFramebuffer(display_sink_->vk_device_, &framebuffer_info, nullptr, &swapchain_framebuffers_[i]))
    }
}

void native_renderer::record_src_command_buffer(vulkan::image_index_t buffer_index) {
    if (!app_->is_external()) {
        // Begin recording app command buffer
        VkCommandBufferBeginInfo begin_info = {
            VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, // sType
            nullptr,                                     // pNext
            0,                                           // flags
            nullptr                                      // pInheritanceInfo
        };
        VK_ASSERT_SUCCESS(vkBeginCommandBuffer(app_command_buffer_, &begin_info))

        for (auto eye = 0; eye < 2; eye++) {
            assert(app_pass_ != VK_NULL_HANDLE);
            std::array<VkClearValue, 3> clear_values = {};
            clear_values[0].color                    = {{1.0f, 1.0f, 1.0f, 1.0f}};

            // Make sure the depth image is also cleared correctly
            float clear_depth                  = rendering_params::reverse_z ? 0.0f : 1.0f;
            clear_values[1].color              = {{clear_depth, clear_depth, clear_depth, 1.0f}};
            clear_values[2].depthStencil.depth = clear_depth;

            VkRenderPassBeginInfo render_pass_info{
                VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,   // sType
                nullptr,                                    // pNext
                app_pass_,                                  // renderPass
                offscreen_framebuffers_[buffer_index][eye], // framebuffer
                {
                    {0, 0},                                                                               // offset
                    {display_sink_->swapchain_extent_.width / 2, display_sink_->swapchain_extent_.height} // extent
                },                                                                                        // renderArea
                clear_values.size(),                                                                      // clearValueCount
                clear_values.data()                                                                       // pClearValues
            };

            vkCmdBeginRenderPass(app_command_buffer_, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
            // Call app service to record the command buffer
            app_->record_command_buffer(app_command_buffer_, offscreen_framebuffers_[buffer_index][eye], buffer_index,
                                        eye == 0);
            vkCmdEndRenderPass(app_command_buffer_);
        }
        VK_ASSERT_SUCCESS(vkEndCommandBuffer(app_command_buffer_))
    }
}

void native_renderer::record_post_processing_command_buffer(vulkan::image_index_t buffer_index,
                                                            uint32_t              swapchain_image_index) {
    // Begin recording timewarp command buffer
    VkCommandBufferBeginInfo timewarp_begin_info = {
        VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, // sType
        nullptr,                                     // pNext
        0,                                           // flags
        nullptr                                      // pInheritanceInfo
    };
    VK_ASSERT_SUCCESS(vkBeginCommandBuffer(timewarp_command_buffer_, &timewarp_begin_info))

    for (auto eye = 0; eye < 2; eye++) {
        timewarp_->record_command_buffer(timewarp_command_buffer_, swapchain_framebuffers_[swapchain_image_index], buffer_index,
                                         eye == 0);
    }

    VK_ASSERT_SUCCESS(vkEndCommandBuffer(timewarp_command_buffer_))
}

void native_renderer::create_sync_objects() {
    VkSemaphoreTypeCreateInfo timeline_semaphore_info{
        VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, // sType
        nullptr,                                      // pNext
        VK_SEMAPHORE_TYPE_TIMELINE,                   // semaphoreType
        0                                             // initialValue
    };

    VkSemaphoreCreateInfo create_info{
        VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // sType
        &timeline_semaphore_info,                // pNext
        0                                        // flags
    };

    VK_ASSERT_SUCCESS(vkCreateSemaphore(display_sink_->vk_device_, &create_info, nullptr, &app_render_finished_semaphore_))
    deletion_queue_.emplace([=]() {
        vkDestroySemaphore(display_sink_->vk_device_, app_render_finished_semaphore_, nullptr);
    });

    VkSemaphoreCreateInfo semaphore_info{
        VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // sType
        nullptr,                                 // pNext
        0                                        // flags
    };
    VkFenceCreateInfo fence_info{
        VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, // sType
        nullptr,                             // pNext
        VK_FENCE_CREATE_SIGNALED_BIT         // flags
    };

    VK_ASSERT_SUCCESS(vkCreateSemaphore(display_sink_->vk_device_, &semaphore_info, nullptr, &image_available_semaphore_))
    deletion_queue_.emplace([=]() {
        vkDestroySemaphore(display_sink_->vk_device_, image_available_semaphore_, nullptr);
    });

    VK_ASSERT_SUCCESS(
        vkCreateSemaphore(display_sink_->vk_device_, &semaphore_info, nullptr, &timewarp_render_finished_semaphore_))
    deletion_queue_.emplace([=]() {
        vkDestroySemaphore(display_sink_->vk_device_, timewarp_render_finished_semaphore_, nullptr);
    });

    VK_ASSERT_SUCCESS(vkCreateFence(display_sink_->vk_device_, &fence_info, nullptr, &frame_fence_))
    deletion_queue_.emplace([=]() {
        vkDestroyFence(display_sink_->vk_device_, frame_fence_, nullptr);
    });
}

void native_renderer::create_depth_image(vulkan::vk_image& depth_image) {
    VkImageCreateInfo image_info{
        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType
        nullptr,                             // pNext
        0,                                   // flags
        VK_IMAGE_TYPE_2D,                    // imageType
        VK_FORMAT_D32_SFLOAT,                // format
        {
            width_ / 2,                                                           // width
            height_,                                                              // height
            1                                                                     // depth
        },                                                                        // extent
        1,                                                                        // mipLevels
        1,                                                                        // arrayLayers
        VK_SAMPLE_COUNT_1_BIT,                                                    // samples
        VK_IMAGE_TILING_OPTIMAL,                                                  // tiling
        VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, // usage
        {},                                                                       // sharingMode
        0,                                                                        // queueFamilyIndexCount
        nullptr,                                                                  // pQueueFamilyIndices
        {}                                                                        // initialLayout
    };

    depth_image.image_info = image_info;

    VmaAllocationCreateInfo alloc_info{};
    alloc_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;

    VK_ASSERT_SUCCESS(vmaCreateImage(display_sink_->vma_allocator_, &image_info, &alloc_info, &depth_image.image,
                                     &depth_image.allocation, &depth_image.allocation_info))
    deletion_queue_.emplace([=]() {
        vmaDestroyImage(display_sink_->vma_allocator_, depth_image.image, depth_image.allocation);
    });

    VkImageViewCreateInfo view_info{
        VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // sType
        nullptr,                                  // pNext
        0,                                        // flags
        depth_image.image,                        // image
        VK_IMAGE_VIEW_TYPE_2D,                    // viewType
        VK_FORMAT_D32_SFLOAT,                     // format
        {},                                       // components
        {
            VK_IMAGE_ASPECT_DEPTH_BIT, // aspectMask
            0,                         // baseMipLevel
            1,                         // levelCount
            0,                         // baseArrayLayer
            1                          // layerCount
        } // subresourceRange
    };

    VK_ASSERT_SUCCESS(vkCreateImageView(display_sink_->vk_device_, &view_info, nullptr, &depth_image.image_view))
    deletion_queue_.emplace([=]() {
        vkDestroyImageView(display_sink_->vk_device_, depth_image.image_view, nullptr);
    });
}

void native_renderer::create_offscreen_pool() {
    VkImageCreateInfo sample_create_info{
        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType
        nullptr,                             // pNext
        0,                                   // flags
        VK_IMAGE_TYPE_2D,                    // imageType
        VK_FORMAT_B8G8R8A8_UNORM,            // format
        {
            width_ / 2,                                                 // width
            height_,                                                    // height
            1                                                           // depth
        },                                                              // extent
        1,                                                              // mipLevels
        1,                                                              // arrayLayers
        VK_SAMPLE_COUNT_1_BIT,                                          // samples
        export_dma_ ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL, // tiling
        static_cast<VkImageUsageFlags>(
            (VK_IMAGE_USAGE_TRANSFER_DST_BIT | (export_dma_ ? 0 : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) |
            (timewarp_->is_external() ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT)), // usage
        {},                                                                                             // sharingMode
        0,                                                                                              // queueFamilyIndexCount
        nullptr,                                                                                        // pQueueFamilyIndices
        {}                                                                                              // initialLayout
    };

    uint32_t                mem_type_index;
    VmaAllocationCreateInfo alloc_info{};
    alloc_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
    alloc_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;

    VK_ASSERT_SUCCESS(
        vmaFindMemoryTypeIndexForImageInfo(display_sink_->vma_allocator_, &sample_create_info, &alloc_info, &mem_type_index));

    offscreen_export_mem_alloc_info_.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
    offscreen_export_mem_alloc_info_.handleTypes =
        export_dma_ ? VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT : VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;

    VmaPoolCreateInfo pool_create_info   = {};
    pool_create_info.memoryTypeIndex     = mem_type_index;
    pool_create_info.blockSize           = 0;
    pool_create_info.maxBlockCount       = 0;
    pool_create_info.pMemoryAllocateNext = &offscreen_export_mem_alloc_info_;
    this->offscreen_pool_create_info_    = pool_create_info;

    VK_ASSERT_SUCCESS(vmaCreatePool(display_sink_->vma_allocator_, &offscreen_pool_create_info_, &offscreen_pool_));
    deletion_queue_.emplace([=]() {
        vmaDestroyPool(display_sink_->vma_allocator_, offscreen_pool_);
    });
}

void native_renderer::create_offscreen_target(vulkan::vk_image& image) {
    if (timewarp_->is_external() || app_->is_external()) {
        assert(offscreen_pool_ != VK_NULL_HANDLE);
        image.export_image_info = {
            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, nullptr,
            export_dma_ ? static_cast<VkExternalMemoryHandleTypeFlags>(VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT)
                        : static_cast<VkExternalMemoryHandleTypeFlags>(VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)};
    }

    std::vector<uint32_t> queue_family_indices;
    queue_family_indices.push_back(display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS].family);
    if (display_sink_->queues_.find(vulkan::queue::queue_type::COMPUTE) != display_sink_->queues_.end() &&
        display_sink_->queues_.find(vulkan::queue::queue_type::COMPUTE)->second.family !=
            display_sink_->queues_[vulkan::queue::queue_type::GRAPHICS].family) {
        queue_family_indices.push_back(display_sink_->queues_[vulkan::queue::queue_type::COMPUTE].family);
    }
    if (display_sink_->queues_.find(vulkan::queue::queue_type::DEDICATED_TRANSFER) != display_sink_->queues_.end()) {
        queue_family_indices.push_back(display_sink_->queues_[vulkan::queue::queue_type::DEDICATED_TRANSFER].family);
    }

    image.image_info = {
        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,                                                    // sType
        (app_->is_external() || timewarp_->is_external()) ? &image.export_image_info : nullptr, // pNext
        0,                                                                                      // flags
        VK_IMAGE_TYPE_2D,                                                                       // imageType
        VK_FORMAT_B8G8R8A8_UNORM,                                                               // format
        {
            width_ / 2,          // width
            height_,             // height
            1                    // depth
        },                       // extent
        1,                       // mipLevels
        1,                       // arrayLayers
        VK_SAMPLE_COUNT_1_BIT,   // samples
        VK_IMAGE_TILING_OPTIMAL, // tiling
        static_cast<VkImageUsageFlags>(
            (VK_IMAGE_USAGE_TRANSFER_DST_BIT | (export_dma_ ? 0 : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) |
            (timewarp_->is_external() ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT)), // usage
        VK_SHARING_MODE_CONCURRENT,                                                                     // sharingMode
        static_cast<uint32_t>(queue_family_indices.size()),                                             // queueFamilyIndexCount
        queue_family_indices.data(),                                                                    // pQueueFamilyIndices
        VK_IMAGE_LAYOUT_UNDEFINED                                                                       // initialLayout
    };

    VmaAllocationCreateInfo alloc_info{};
    alloc_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
    alloc_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;

    if (timewarp_->is_external() || app_->is_external()) {
        alloc_info.pool = offscreen_pool_;
    }

    VK_ASSERT_SUCCESS(vmaCreateImage(display_sink_->vma_allocator_, &image.image_info, &alloc_info, &image.image,
                                     &image.allocation, &image.allocation_info))
    assert(image.allocation_info.deviceMemory);
    deletion_queue_.emplace([=]() {
        vmaDestroyImage(display_sink_->vma_allocator_, image.image, image.allocation);
    });

    VkImageViewCreateInfo view_info{
        VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // sType
        nullptr,                                  // pNext
        0,                                        // flags
        image.image,                              // image
        VK_IMAGE_VIEW_TYPE_2D,                    // viewType
        VK_FORMAT_B8G8R8A8_UNORM,                 // format
        {},                                       // components
        {
            VK_IMAGE_ASPECT_COLOR_BIT, // aspectMask
            0,                         // baseMipLevel
            1,                         // levelCount
            0,                         // baseArrayLayer
            1                          // layerCount
        } // subresourceRange
    };

    VK_ASSERT_SUCCESS(vkCreateImageView(display_sink_->vk_device_, &view_info, nullptr, &image.image_view))

    if (export_dma_) {
        VkMemoryGetFdInfoKHR get_fd_info{VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, // sType
                                         nullptr,                                  // pNext
                                         image.allocation_info.deviceMemory,       // memory
                                         VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};

        auto GetMemoryFdKHR =
            reinterpret_cast<PFN_vkGetMemoryFdKHR>(vkGetDeviceProcAddr(display_sink_->vk_device_, "vkGetMemoryFdKHR"));

        VK_ASSERT_SUCCESS(GetMemoryFdKHR(display_sink_->vk_device_, &get_fd_info, &image.fd))
    }

    deletion_queue_.emplace([=]() {
        vkDestroyImageView(display_sink_->vk_device_, image.image_view, nullptr);
    });
}

void native_renderer::create_offscreen_framebuffers() {
    offscreen_framebuffers_.resize(NATIVE_RENDERER_BUFFER_POOL_SIZE);

    for (auto i = 0; i < NATIVE_RENDERER_BUFFER_POOL_SIZE; i++) {
        for (auto eye = 0; eye < 2; eye++) {
            std::array<VkImageView, 3> attachments = {offscreen_images_[i][eye].image_view, depth_images_[i][eye].image_view,
                                                      depth_attachment_images_[i][eye].image_view};

            VkFramebufferCreateInfo framebuffer_info{
                VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,  // sType
                nullptr,                                    // pNext
                0,                                          // flags
                app_pass_,                                  // renderPass
                static_cast<uint32_t>(attachments.size()),  // attachmentCount
                attachments.data(),                         // pAttachments
                display_sink_->swapchain_extent_.width / 2, // width
                display_sink_->swapchain_extent_.height,    // height
                1                                           // layers
            };

            VK_ASSERT_SUCCESS(
                vkCreateFramebuffer(display_sink_->vk_device_, &framebuffer_info, nullptr, &offscreen_framebuffers_[i][eye]))

            deletion_queue_.emplace([=]() {
                vkDestroyFramebuffer(display_sink_->vk_device_, offscreen_framebuffers_[i][eye], nullptr);
            });
        }
    }
}

void native_renderer::create_app_pass() {
    std::array<VkAttachmentDescription, 3> attachment_descriptions{
        {{
             0,                                // flags
             VK_FORMAT_B8G8R8A8_UNORM,         // format
             VK_SAMPLE_COUNT_1_BIT,            // samples
             VK_ATTACHMENT_LOAD_OP_CLEAR,      // loadOp
             VK_ATTACHMENT_STORE_OP_STORE,     // storeOp
             VK_ATTACHMENT_LOAD_OP_DONT_CARE,  // stencilLoadOp
             VK_ATTACHMENT_STORE_OP_DONT_CARE, // stencilStoreOp
             VK_IMAGE_LAYOUT_UNDEFINED,        // initialLayout
             timewarp_->is_external() ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
                                      : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL // finalLayout
         },
         {
             0,                                // flags
             VK_FORMAT_B8G8R8A8_UNORM,         // format
             VK_SAMPLE_COUNT_1_BIT,            // samples
             VK_ATTACHMENT_LOAD_OP_CLEAR,      // loadOp
             VK_ATTACHMENT_STORE_OP_STORE,     // storeOp
             VK_ATTACHMENT_LOAD_OP_DONT_CARE,  // stencilLoadOp
             VK_ATTACHMENT_STORE_OP_DONT_CARE, // stencilStoreOp
             VK_IMAGE_LAYOUT_UNDEFINED,        // initialLayout
             timewarp_->is_external() ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
                                      : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL // finalLayout
         },
         {
             0,                                               // flags
             VK_FORMAT_D32_SFLOAT,                            // format
             VK_SAMPLE_COUNT_1_BIT,                           // samples
             VK_ATTACHMENT_LOAD_OP_CLEAR,                     // loadOp
             VK_ATTACHMENT_STORE_OP_DONT_CARE,                // storeOp
             VK_ATTACHMENT_LOAD_OP_DONT_CARE,                 // stencilLoadOp
             VK_ATTACHMENT_STORE_OP_DONT_CARE,                // stencilStoreOp
             VK_IMAGE_LAYOUT_UNDEFINED,                       // initialLayout
             VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL // finalLayout
         }}};

    VkAttachmentReference color_attachment_ref{
        0,                                       // attachment
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // layout
    };

    VkAttachmentReference depth_image_attachment_ref{
        1,                                       // attachment
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // layout
    };

    VkAttachmentReference color_refs[2] = {color_attachment_ref, depth_image_attachment_ref};

    VkAttachmentReference depth_attachment_ref{
        2,                                               // attachment
        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL // layout
    };

    VkSubpassDescription subpass = {
        0,                               // flags
        VK_PIPELINE_BIND_POINT_GRAPHICS, // pipelineBindPoint
        0,                               // inputAttachmentCount
        nullptr,                         // pInputAttachments
        2,                               // colorAttachmentCount
        color_refs,                      // pColorAttachments
        nullptr,                         // pResolveAttachments
        &depth_attachment_ref,           // pDepthStencilAttachment
        0,                               // preserveAttachmentCount
        nullptr                          // pPreserveAttachments
    };

    // Subpass dependencies for layout transitions
    std::array<VkSubpassDependency, 3> dependencies{
        {{
             // After timewarp samples from the offscreen image, it needs to be transitioned to a color attachment
             VK_SUBPASS_EXTERNAL,                                                                               // srcSubpass
             0,                                                                                                 // dstSubpass
             timewarp_->is_external() ? VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // srcStageMask
             VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,                                                     // dstStageMask
             timewarp_->is_external() ? VK_ACCESS_TRANSFER_READ_BIT : VK_ACCESS_SHADER_READ_BIT,                // srcAccessMask
             VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,                                                              // dstAccessMask
             VK_DEPENDENCY_BY_REGION_BIT // dependencyFlags
         },
         {
             // After the app is done rendering to the offscreen image, it needs to be transitioned to a shader read
             0,                                             // srcSubpass
             VK_SUBPASS_EXTERNAL,                           // dstSubpass
             VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // srcStageMask
             static_cast<VkPipelineStageFlags>(
                 timewarp_->is_external()
                     ? VK_PIPELINE_STAGE_TRANSFER_BIT
                     : (VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)), // dstStageMask
             VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,                                                     // srcAccessMask
             timewarp_->is_external() ? VK_ACCESS_TRANSFER_READ_BIT : VK_ACCESS_SHADER_READ_BIT,       // dstAccessMask
             VK_DEPENDENCY_BY_REGION_BIT                                                               // dependencyFlags
         },
         {// depth buffer write-after-write hazard
          VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
          VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
          VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, 0}}};

    VkRenderPassCreateInfo render_pass_info{
        VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,             // sType
        nullptr,                                               // pNext
        0,                                                     // flags
        static_cast<uint32_t>(attachment_descriptions.size()), // attachmentCount
        attachment_descriptions.data(),                        // pAttachments
        1,                                                     // subpassCount
        &subpass,                                              // pSubpasses
        static_cast<uint32_t>(dependencies.size()),            // dependencyCount
        dependencies.data()                                    // pDependencies
    };
    VK_ASSERT_SUCCESS(vkCreateRenderPass(display_sink_->vk_device_, &render_pass_info, nullptr, &app_pass_))
    deletion_queue_.emplace([=]() {
        vkDestroyRenderPass(display_sink_->vk_device_, app_pass_, nullptr);
    });
}

void native_renderer::create_timewarp_pass() {
    std::array<VkAttachmentDescription, 1> attchmentDescriptions{{{
        0,                                             // flags
        display_sink_->swapchain_image_format_.format, // format
        VK_SAMPLE_COUNT_1_BIT,                         // samples
        VK_ATTACHMENT_LOAD_OP_DONT_CARE,               // loadOp
        VK_ATTACHMENT_STORE_OP_STORE,                  // storeOp
        VK_ATTACHMENT_LOAD_OP_DONT_CARE,               // stencilLoadOp
        VK_ATTACHMENT_STORE_OP_DONT_CARE,              // stencilStoreOp
        VK_IMAGE_LAYOUT_UNDEFINED,                     // initialLayout
        VK_IMAGE_LAYOUT_PRESENT_SRC_KHR                // finalLayout
    }}};

    VkAttachmentReference color_attachment_ref{
        0,                                       // attachment
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // layout
    };

    VkSubpassDependency dependency = {VK_SUBPASS_EXTERNAL,
                                      0,
                                      VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
                                      VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
                                      0,
                                      VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
                                      0};

    VkSubpassDescription subpass = {
        0,                               // flags
        VK_PIPELINE_BIND_POINT_GRAPHICS, // pipelineBindPoint
        0,                               // inputAttachmentCount
        nullptr,                         // pInputAttachments
        1,                               // colorAttachmentCount
        &color_attachment_ref,           // pColorAttachments
        nullptr,                         // pResolveAttachments
        nullptr,                         // pDepthStencilAttachment
        0,                               // preserveAttachmentCount
        nullptr                          // pPreserveAttachments
    };

    // assert(app_->is_external());

    VkRenderPassCreateInfo render_pass_info{
        VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,           // sType
        nullptr,                                             // pNext
        0,                                                   // flags
        static_cast<uint32_t>(attchmentDescriptions.size()), // attachmentCount
        attchmentDescriptions.data(),                        // pAttachments
        1,                                                   // subpassCount
        &subpass,                                            // pSubpasses
        1,                                                   // dependencyCount
        &dependency                                          // pDependencies
    };

    VK_ASSERT_SUCCESS(vkCreateRenderPass(display_sink_->vk_device_, &render_pass_info, nullptr, &timewarp_pass_))
    deletion_queue_.emplace([=]() {
        vkDestroyRenderPass(display_sink_->vk_device_, timewarp_pass_, nullptr);
    });
}

PLUGIN_MAIN(native_renderer)