Newer
Older
ForwardPlusRenderer / src / Context.cpp
#include <stdafx.h>
#include "Context.h"
#include "IO.h"
#include "VulkanWindow.h"
#include "SwapChain.h"
#include <algorithm>
#include <numeric>
namespace fpr
{
Version::Version(uint32_t variant, uint32_t major, uint32_t minor, uint32_t patch):
    m_variant(variant), m_major(major), m_minor(minor), m_patch(patch)
{
}

uint32_t Version::MakeVersion() const FPR_NOEXCEPT
{
  return VK_MAKE_API_VERSION(m_variant, m_major, m_minor, m_patch);
}


VulkanWindow*       Context::s_window;
std::string         Context::s_app_name;
fpr::Version        Context::s_app_version;
std::string         Context::s_engine_name;
fpr::Version        Context::s_engine_version;
fpr::Version        Context::s_vulkan_version;
vk::ApplicationInfo Context::ApplicationInfo(
    const char*    application_name,
    const Version& app_version,
    const char*    engine_name,
    const Version& engine_version,
    const Version& api_version) FPR_NOEXCEPT
{
  vk::ApplicationInfo app_info{};
  app_info.pApplicationName   = application_name;
  app_info.applicationVersion = app_version.MakeVersion();
  app_info.pEngineName        = engine_name;
  app_info.engineVersion      = engine_version.MakeVersion();
  app_info.apiVersion         = api_version.MakeVersion();
  return app_info;
}

std::vector<const char*> Context::GetRequiredExtensions(const std::vector<const char*>& validation_layers) FPR_NOEXCEPT
{
  auto [enum_result, available_layers] = vk::enumerateInstanceLayerProperties();
  if(validation_layers_enabled)
  {
    [[maybe_unused]] bool validation_supported = IsValidationLayersValid(validation_layers, available_layers);
    assert(("Validation layers not supported but requested", validation_supported));
  }

  uint32_t                 extension_count = 0;
  const char**             glfw_extensions = glfwGetRequiredInstanceExtensions(&extension_count);
  std::vector<const char*> result(glfw_extensions, glfw_extensions + extension_count);
  if(validation_layers_enabled)
  {
    result.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
  }
  return result;
}

inline bool Context::IsValidationLayersValid(
    const std::vector<const char*>&         validation_layers,
    const std::vector<vk::LayerProperties>& available_layers) FPR_NOEXCEPT
{
  // Iterate over requested validation layers, ensure they are supported.
  for(auto& validation_layer : validation_layers)
  {
    auto layer_name_matches = [&](const auto& layer) { return strcmp(layer.layerName, validation_layer) == 0; };

    if(std::none_of(available_layers.begin(), available_layers.end(), layer_name_matches))
      return false;
  }
  return true;
}

bool Context::IsVulkanSupported() const FPR_NOEXCEPT
{
  auto [enum_result, ext_props]                   = vk::enumerateInstanceExtensionProperties();
  std::vector<vk::ExtensionProperties> extensions = ext_props;

  // Check each extension and if the current system can support Vulkan.
  for(const auto extensionToCheck : m_extensions)
  {
    bool hasExtensions = false;
    for(const auto& ext : extensions)
    {
      if(strcmp(extensionToCheck, ext.extensionName) == 0)
      {
        hasExtensions = true;
        break;
      }
    }

    if(!hasExtensions)
      return false;
  }
  return true;
}

const vk::Instance& fpr::Context::GetVulkanInstance() const FPR_NOEXCEPT
{
  return m_instance.get();
}

std::function<Context::PipelineCreationData(void)> Context::DEFAULT_PIPELINE_CREATION_CALLBACK = []()
{
  fpr::Context::PipelineCreationData pipelines;

  pipelines.emplace_back(
      EPipelineType::EPT_Graphics, "Graphics", fpr::Pipeline::GetDefaultGraphicsPipeline(), tl::nullopt);

  pipelines.emplace_back(EPipelineType::EPT_Graphics, "Depth", fpr::Pipeline::GetDepthPassPipeline(), "Graphics");

  pipelines.emplace_back(
      EPipelineType::EPT_Compute, "LightCulling", fpr::Pipeline::GetLightCullingPipeline(), tl::nullopt);
  return pipelines;
};

void Context::RecreateSwapchain()
{
  m_old_swapchain = std::move(m_swapchain->GetUniqueHandle());
  m_swapchain.reset();
  m_swapchain =
      std::make_unique<fpr::SwapChain>(m_device.get(), *m_surface, std::make_optional(std::move(m_old_swapchain)));
  m_swapchain->CreateCommandBuffers();
}

void Context::CreatePipelines(std::function<PipelineCreationData(void)> fn)
{
  // Takes a callback that returns configuration for pipeline creation.
  auto pipeline_configs = std::invoke(std::bind(fn));
  for(auto& pipeline_config : pipeline_configs)
  {
    auto& [pipeline_type, name, settings, parent_name] = pipeline_config;
    switch(pipeline_type)
    {
      case EPipelineType::EPT_Graphics:
      {
        fpr::Pipeline* parent_pipeline = nullptr;
        if(parent_name != tl::nullopt)
          parent_pipeline = m_render_graph->GraphicsPipelines.Get(*parent_name);

        auto pipeline = std::make_unique<fpr::Pipeline>(settings, parent_pipeline);
        m_render_graph->GraphicsPipelines.Add(std::string(name), pipeline);
        break;
      }
      case EPipelineType::EPT_Compute:
      {
        fpr::Pipeline* parent_pipeline = nullptr;
        if(parent_name != tl::nullopt)
          parent_pipeline = m_render_graph->ComputePipelines.Get(*parent_name);

        auto pipeline = std::make_unique<fpr::Pipeline>(settings, parent_pipeline, EPipelineType::EPT_Compute);
        m_render_graph->ComputePipelines.Add(std::string(name), pipeline);
        break;
      }
    }
  }
}

void Context::CreateSingleTimeCommandBuffer()
{
  // Command buffers used for transitioning image layouts and so on can be 'discarded' once they have been used.
  vk::CommandBufferAllocateInfo cmd_buffer_alloc_info{};
  cmd_buffer_alloc_info.level              = vk::CommandBufferLevel::ePrimary;
  cmd_buffer_alloc_info.commandPool        = GetGraphicsCmdPool();
  cmd_buffer_alloc_info.commandBufferCount = 1;

  auto [cmd_buff_alloc_result, cmd_buff] =
      m_device->GetLogicalDeviceHandle().allocateCommandBuffersUnique(cmd_buffer_alloc_info);

  m_single_time_cmd_buffer = std::move(cmd_buff[0]);
}
Context& Context::GetImpl(
    VulkanWindow*      Window,
    const std::string& app_name,
    fpr::Version       app_version,
    const std::string& engine_name,
    fpr::Version       engine_version,
    fpr::Version       vulkan_version)
{
  // Myers' singleton design pattern. Public function invokes private constructor.
  static Context context = fpr::Context(Window, app_name, app_version, engine_name, engine_version, vulkan_version);
  return context;
}

void fpr::Context::CreateModelDescriptorSet()
{
  auto& device = GetDevice()->GetLogicalDeviceHandle();


  // Allow model data to be accessed in shader.
  for(size_t i = 0; i < GetSwapChain()->GetMaxFrameIdx(); ++i)
  {
    auto                          layout = m_render_graph->DescriptorSetLayouts.Get("Model");
    vk::DescriptorSetAllocateInfo desc_set_alloc_info;
    desc_set_alloc_info.descriptorPool = GetDescriptorPool();
    desc_set_alloc_info.setSetLayouts(layout);

    auto [result_signal, descriptor_sets] = device.allocateDescriptorSetsUnique(desc_set_alloc_info);
    assert(("Failed to allocate descriptor sets!", result_signal == vk::Result::eSuccess));

    m_render_graph->DescriptorSets.Add("Model" + std::to_string(i), descriptor_sets[0]);

    vk::DescriptorBufferInfo uniform_buffer_info;
    uniform_buffer_info.buffer = m_render_graph->Buffers.Get("Model" + std::to_string(i))->GetBuffer();
    uniform_buffer_info.offset = 0;
    uniform_buffer_info.range  = GetRequiredDynamicAlignment(); // DYNAMIC uniform buffer. requires specific alignment.

    std::vector<vk::WriteDescriptorSet> desc_writes = {};

    vk::WriteDescriptorSet model_write_desc_set;
    model_write_desc_set.dstSet          = m_render_graph->DescriptorSets.Get("Model" + std::to_string(i));
    model_write_desc_set.dstBinding      = 0;
    model_write_desc_set.dstArrayElement = 0;
    model_write_desc_set.descriptorCount = 1;
    model_write_desc_set.descriptorType  = vk::DescriptorType::eUniformBufferDynamic;
    model_write_desc_set.pBufferInfo     = &uniform_buffer_info;

    desc_writes.emplace_back(model_write_desc_set);
    device.updateDescriptorSets(desc_writes, nullptr);
  }
}

void fpr::Context::CreateCameraDescriptorSet()
{
  auto& device = GetDevice()->GetLogicalDeviceHandle();
  // Allow camera data to be accessed in shader. Simple uniform buffer for each frame in flighht.
  for(size_t i = 0; i < GetSwapChain()->GetMaxFrameIdx(); ++i)
  {
    auto                          layout = m_render_graph->DescriptorSetLayouts.Get("Camera");
    vk::DescriptorSetAllocateInfo desc_set_alloc_info;
    desc_set_alloc_info.descriptorPool = GetDescriptorPool();
    desc_set_alloc_info.setSetLayouts(layout);

    auto [result_signal, descriptor_sets] = device.allocateDescriptorSetsUnique(desc_set_alloc_info);
    assert(("Failed to allocate descriptor sets!", result_signal == vk::Result::eSuccess));

    m_render_graph->DescriptorSets.Add("Camera" + std::to_string(i), descriptor_sets[0]);

    vk::DescriptorBufferInfo uniform_buffer_info;
    uniform_buffer_info.buffer = m_render_graph->Buffers.Get("Camera" + std::to_string(i))->GetBuffer();
    uniform_buffer_info.offset = 0;
    uniform_buffer_info.range  = sizeof(UBOPerFrame);

    std::vector<vk::WriteDescriptorSet> desc_writes = {};

    vk::WriteDescriptorSet camera_write_desc_set;
    camera_write_desc_set.dstSet          = m_render_graph->DescriptorSets.Get("Camera" + std::to_string(i));
    camera_write_desc_set.dstBinding      = 0;
    camera_write_desc_set.dstArrayElement = 0;
    camera_write_desc_set.descriptorCount = 1;
    camera_write_desc_set.descriptorType  = vk::DescriptorType::eUniformBuffer;
    camera_write_desc_set.pBufferInfo     = &uniform_buffer_info;

    desc_writes.emplace_back(camera_write_desc_set);
    device.updateDescriptorSets(desc_writes, nullptr);
  }
}

void fpr::Context::CreateCameraDescriptorSetLayout()
{
  auto& device = GetDevice()->GetLogicalDeviceHandle();

  const vk::ShaderStageFlags camera_stage_flags = vk::ShaderStageFlagBits::eVertex |
      vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eCompute; // Camera data accessed in all shaders.

  vk::DescriptorSetLayoutBinding camera_desc_set_layout_bind;
  camera_desc_set_layout_bind.binding         = 0;
  camera_desc_set_layout_bind.descriptorType  = vk::DescriptorType::eUniformBuffer;
  camera_desc_set_layout_bind.descriptorCount = 1;
  camera_desc_set_layout_bind.stageFlags      = camera_stage_flags;

  vk::DescriptorSetLayoutCreateInfo create_info;
  create_info.setBindings(camera_desc_set_layout_bind);

  auto [layout_result, layout] = device.createDescriptorSetLayoutUnique(create_info);
  assert(layout_result == vk::Result::eSuccess);
  m_render_graph->DescriptorSetLayouts.Add("Camera", std::move(layout));
}

void fpr::Context::CreateModelDescriptorSetLayout()
{
  auto& device = GetDevice()->GetLogicalDeviceHandle();

  const vk::ShaderStageFlags model_stage_flags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
      vk::ShaderStageFlagBits::eCompute; // Model data accessed in all shaders.


  vk::DescriptorSetLayoutBinding model_desc_set_layout_bind;
  model_desc_set_layout_bind.binding         = 0;
  model_desc_set_layout_bind.descriptorType  = vk::DescriptorType::eUniformBufferDynamic;
  model_desc_set_layout_bind.descriptorCount = 1;

  model_desc_set_layout_bind.stageFlags = model_stage_flags;

  vk::DescriptorSetLayoutCreateInfo create_info;
  create_info.setBindings(model_desc_set_layout_bind);

  auto [layout_result, layout] = device.createDescriptorSetLayoutUnique(create_info);
  assert(layout_result == vk::Result::eSuccess);
  m_render_graph->DescriptorSetLayouts.Add("Model", std::move(layout));
}

void fpr::Context::CreateDepthSamplerLayout()
{
  auto& device = GetDevice()->GetLogicalDeviceHandle();

  const vk::ShaderStageFlags depth_sampler_flags = vk::ShaderStageFlagBits::eCompute |
      vk::ShaderStageFlagBits::eFragment; // Depth buffer is sampled in compute for light culling.
  // Can be sampled in fragment shader for further optimisations.


  vk::DescriptorSetLayoutBinding model_desc_set_layout_bind;
  model_desc_set_layout_bind.binding         = 0;
  model_desc_set_layout_bind.descriptorType  = vk::DescriptorType::eCombinedImageSampler;
  model_desc_set_layout_bind.descriptorCount = 1;

  model_desc_set_layout_bind.stageFlags = depth_sampler_flags;

  vk::DescriptorSetLayoutCreateInfo create_info;
  create_info.setBindings(model_desc_set_layout_bind);

  auto [layout_result, layout] = device.createDescriptorSetLayoutUnique(create_info);
  assert(layout_result == vk::Result::eSuccess);
  m_render_graph->DescriptorSetLayouts.Add("DepthSampler", std::move(layout));
}

void fpr::Context::CreateDepthSamplerDescriptorSet()
{
  auto&                         device = GetDevice()->GetLogicalDeviceHandle();
  auto                          layout = m_render_graph->DescriptorSetLayouts.Get("DepthSampler");
  vk::DescriptorSetAllocateInfo desc_set_alloc_info;

  desc_set_alloc_info.descriptorPool = GetDescriptorPool();
  desc_set_alloc_info.setSetLayouts(layout);

  auto [result_signal, descriptor_sets] = device.allocateDescriptorSetsUnique(desc_set_alloc_info);
  assert(("Failed to allocate descriptor sets!", result_signal == vk::Result::eSuccess));

  m_render_graph->DescriptorSets.Add("DepthSampler", descriptor_sets[0]);

  vk::DescriptorImageInfo depth_sampler_info;
  depth_sampler_info.setSampler(m_render_graph->Samplers.Get(
      "Sampler")); // Use already created texture sampler, no need to create a different one.
  depth_sampler_info.setImageLayout(vk::ImageLayout::eDepthStencilReadOnlyOptimal);
  depth_sampler_info.setImageView(GetSwapChain()->GetDepthBuffer()->GetDepthBufferView());

  std::vector<vk::WriteDescriptorSet> desc_writes = {};

  vk::WriteDescriptorSet depth_sampler_write;
  depth_sampler_write.dstSet          = m_render_graph->DescriptorSets.Get("DepthSampler");
  depth_sampler_write.dstBinding      = 0;
  depth_sampler_write.dstArrayElement = 0;
  depth_sampler_write.descriptorCount = 1;
  depth_sampler_write.descriptorType  = vk::DescriptorType::eCombinedImageSampler;
  depth_sampler_write.setImageInfo(depth_sampler_info);

  desc_writes.emplace_back(depth_sampler_write);
  device.updateDescriptorSets(desc_writes, nullptr);
}

void fpr::Context::CreateDescriptorSets()
{
  CreateModelDescriptorSetLayout();
  CreateCameraDescriptorSetLayout();
  CreateModelDescriptorSet();
  CreateCameraDescriptorSet();
}

Context& Context::Get()
{
  return GetImpl(s_window, s_app_name, s_app_version, s_engine_name, s_engine_version, s_vulkan_version);
}

void Context::CreateSceneBuffers()
{
  // Ensure that model data alignment meets the device requirements,
  // otherwise buffer access can cause crashes or severere performance loss.
  vk::DeviceSize min_alignment =
      m_device->GetPhysicalDeviceHandle().getProperties().limits.minUniformBufferOffsetAlignment;
  uint32_t dynamic_alignment = (uint32_t)(m_dynamic_alignment + min_alignment - 1) & ~(min_alignment - 1);
  m_dynamic_alignment        = dynamic_alignment;
  size_t buffer_size         = dynamic_alignment * MAX_MESHES;


  for(size_t i = 0; i < m_swapchain->GetMaxFrameIdx(); ++i)
  {
    // Aligned malloc for dynamic uniform buffer. Allows for a massive buffer but needs specially aligned allocation.
    PER_MODEL_UBO.model_matrix = (glm::mat4*)_aligned_malloc(buffer_size, dynamic_alignment);

    m_render_graph->Buffers.Add(
        "Model" + std::to_string(i),
        std::make_unique<fpr::Buffer>(
            buffer_size, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible));
    // Model and camera buffer are HostVisible. This is due to the fact that they are updated every frame and thus
    // cannot "live on the GPU" alone.

    m_render_graph->Buffers.Add(
        "Camera" + std::to_string(i),
        std::make_unique<fpr::Buffer>(
            sizeof(UBOPerFrame), vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible));
  }
}

void Context::Init(
    VulkanWindow*      Window,
    const std::string& app_name,
    fpr::Version       app_version,
    const std::string& engine_name,
    fpr::Version       engine_version,
    fpr::Version       vulkan_version)
{
  s_window         = Window;
  s_app_name       = app_name;
  s_app_version    = app_version;
  s_engine_name    = engine_name;
  s_engine_version = engine_version;
  s_vulkan_version = vulkan_version;
  auto& context    = GetImpl(Window, app_name, app_version, engine_name, engine_version, vulkan_version);

  // The context handles the bulk of the renderer, can be initialised once and referred back to from child objects.
  // This allows for central resource management whilst avoiding globals, amongst other benefits.s
  context.CreateGraphicsCmdPool();
  context.CreateComputeCmdPool();
  context.CreateSwapChain();
  context.GetSwapChain()->CreateCommandBuffers();
  context.CreateDescriptorPools();
  context.CreateTextureSampler();
  context.CreateSceneBuffers();
  context.CreateDescriptorSets();
  fpr::PointLight::CreateDescriptorSet();
  context.CreateDepthSamplerLayout();
  context.CreateDepthSamplerDescriptorSet();
  context.CreatePipelines(DEFAULT_PIPELINE_CREATION_CALLBACK);
}

VulkanWindow* Context::GetWindow()
{
  return m_window;
}

SwapChain* Context::GetSwapChain()
{
  return m_swapchain.get();
}

void Context::CreateGraphicsCmdPool()
{
  vk::CommandPoolCreateInfo command_pool_create_info;

  command_pool_create_info.flags            = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
  command_pool_create_info.queueFamilyIndex = m_device->GetGraphicsQueueIndex();

  auto [cmd_pool_result, cmd_pool] =
      m_device->GetLogicalDeviceHandle().createCommandPoolUnique(command_pool_create_info);
  assert(("Failed to create command pool!", cmd_pool_result == vk::Result::eSuccess));

  m_command_pool = std::move(cmd_pool);

  CreateSingleTimeCommandBuffer();
}

void Context::CreateSwapChain()
{
  m_swapchain = std::make_unique<SwapChain>(m_device.get(), *m_surface);
}

void Context::CreateComputeCmdPool()
{
  vk::CommandPoolCreateInfo command_pool_create_info;
  command_pool_create_info.queueFamilyIndex = m_device->GetGraphicsQueueIndex();
  command_pool_create_info.flags            = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;

  auto [cmd_pool_result, cmd_pool] =
      m_device->GetLogicalDeviceHandle().createCommandPoolUnique(command_pool_create_info);
  assert(("Failed to create command pool!", cmd_pool_result == vk::Result::eSuccess));
  m_compute_comand_pool = std::move(cmd_pool);
}

void Context::CreateDescriptorPools()
{
  // Camera
  std::array<vk::DescriptorPoolSize, 4> pool_sizes;
  pool_sizes[0].type            = vk::DescriptorType::eUniformBuffer;
  pool_sizes[0].descriptorCount = 2;

  // Model data.
  pool_sizes[1].type            = vk::DescriptorType::eUniformBufferDynamic;
  pool_sizes[1].descriptorCount = 2;


  // Depth + texture sampler + the textures.
  pool_sizes[2].type            = vk::DescriptorType::eCombinedImageSampler;
  pool_sizes[2].descriptorCount = MAX_TEXTURES + 1;

  // Light culling buffer. PointLight buffer.
  pool_sizes[3].type            = vk::DescriptorType::eStorageBuffer;
  pool_sizes[3].descriptorCount = 4;


  vk::DescriptorPoolCreateInfo pool_info = {};

  pool_info.poolSizeCount = (uint32_t)pool_sizes.size();
  pool_info.pPoolSizes    = pool_sizes.data();
  pool_info.maxSets       = std::accumulate(
      pool_sizes.begin(),
      pool_sizes.end(),
      0,
      [&](uint32_t i, const vk::DescriptorPoolSize& dps) -> uint32_t { return dps.descriptorCount + i; });
  pool_info.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;

  auto [pool_result, pool] = m_device->GetLogicalDeviceHandle().createDescriptorPoolUnique(pool_info);

  assert(pool_result == vk::Result::eSuccess);
  m_descriptor_pool = std::move(pool);
}

void Context::CreateTextureSampler()
{
  // Anisotropy x16 assumed to be supported. May need to check for older machines.
  // Other options mostly set to default but specified for clarity.
  vk::SamplerCreateInfo create_info;
  create_info.setMagFilter(vk::Filter::eLinear);
  create_info.setMinFilter(vk::Filter::eLinear);
  create_info.setAddressModeU(vk::SamplerAddressMode::eRepeat);
  create_info.setAddressModeV(vk::SamplerAddressMode::eRepeat);
  create_info.setAddressModeW(vk::SamplerAddressMode::eRepeat);
  create_info.setUnnormalizedCoordinates(false);
  create_info.setMipmapMode(vk::SamplerMipmapMode::eLinear);
  create_info.borderColor = vk::BorderColor::eIntOpaqueBlack;
  create_info.setAnisotropyEnable(true);
  create_info.setMinLod(0.0f);
  create_info.setMaxLod(VK_LOD_CLAMP_NONE);
  create_info.setMaxAnisotropy(16.0f);
  create_info.compareOp = vk::CompareOp::eAlways;


  auto [sampler_result, sampler] = m_device->GetLogicalDeviceHandle().createSamplerUnique(create_info);
  assert(sampler_result == vk::Result::eSuccess);
  m_render_graph->Samplers.Add("Sampler", std::move(sampler));

  vk::DescriptorSetLayoutBinding sampler_binding;
  sampler_binding.setBinding(0);
  sampler_binding.setDescriptorType(vk::DescriptorType::eCombinedImageSampler);
  sampler_binding.setDescriptorCount(1);
  sampler_binding.setStageFlags(
      vk::ShaderStageFlagBits::eFragment |
      vk::ShaderStageFlagBits::eCompute); // Used for model texturees AND depth sampling in compute shader.
  vk::DescriptorSetLayoutCreateInfo sampler_layout_info;
  sampler_layout_info.setBindings(sampler_binding);

  auto [layout_result, sampler_set_layout] =
      m_device->GetLogicalDeviceHandle().createDescriptorSetLayoutUnique(sampler_layout_info);

  assert(layout_result == vk::Result::eSuccess);
  m_render_graph->DescriptorSetLayouts.Add("Sampler", std::move(sampler_set_layout));
}

vk::CommandPool& Context::GetGraphicsCmdPool() FPR_NOEXCEPT
{
  return *m_command_pool;
}

vk::CommandPool& Context::GetComputeCmdPool()
{
  return *m_compute_comand_pool;
}

vk::DescriptorPool& Context::GetDescriptorPool()
{
  return m_descriptor_pool.get();
}

vk::SurfaceKHR& Context::GetSurface() FPR_NOEXCEPT
{
  return m_surface.get();
}

Device* Context::GetDevice() const FPR_NOEXCEPT
{
  return m_device.get();
}

uint32_t Context::GetRequiredDynamicAlignment() const
{
  return m_dynamic_alignment;
}

void Context::SetDynamicAlignment(uint32_t alignment)
{
  m_dynamic_alignment = alignment;
}

vk::Instance& Context::GetInstance()
{
  return m_instance.get();
}

vk::CommandBuffer& Context::BeginSingleTimeCommands()
{
  vk::CommandBufferBeginInfo cmd_buffer_begin_info{};
  cmd_buffer_begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;

  [[maybe_unused]] auto result = m_single_time_cmd_buffer->begin(cmd_buffer_begin_info);
  assert(("Failed to begin single time submission command buffer.", result == vk::Result::eSuccess));

  return *m_single_time_cmd_buffer;
}

void Context::EndSingleTimeCommands()
{
  [[maybe_unused]] auto result = m_single_time_cmd_buffer->end();
  assert(("Failed to end single time submission command buffer.", result == vk::Result::eSuccess));


  vk::SubmitInfo submit_info{};
  submit_info.pNext = nullptr;

  submit_info.commandBufferCount = 1;
  submit_info.pCommandBuffers    = &m_single_time_cmd_buffer.get();

  submit_info.pSignalSemaphores    = nullptr;
  submit_info.signalSemaphoreCount = 0;

  submit_info.pWaitSemaphores    = nullptr;
  submit_info.waitSemaphoreCount = 0;

  vk::FenceCreateInfo submit_fence_info;
  auto [fence_result, update_done_fence] = m_device->GetLogicalDeviceHandle().createFenceUnique(submit_fence_info);

  result = m_device->GetGraphicsQueue().submit(submit_info, *update_done_fence);


  [[maybe_unused]] vk::Result wait_result =
      m_device->GetLogicalDeviceHandle().waitForFences(*update_done_fence, true, std::numeric_limits<uint32_t>::max());
  assert(wait_result == vk::Result::eSuccess);
}

InputHandler& Context::GetCameraInputHandler()
{
  return m_camera_input_handler;
}


Context::Context(
    VulkanWindow*      Window,
    const std::string& app_name,
    fpr::Version       app_version,
    const std::string& engine_name,
    fpr::Version       engine_version,
    fpr::Version       vulkan_version):
    m_camera_input_handler(Window->GetWindowHandle())
{
  m_window     = Window;
  m_extensions = GetRequiredExtensions(m_validation_layers);
  assert(IsVulkanSupported());

  vk::ApplicationInfo app_info =
      ApplicationInfo(app_name.data(), app_version, engine_name.data(), engine_version, vulkan_version);

  vk::InstanceCreateInfo instance_info{};
  instance_info.pApplicationInfo        = &app_info;
  instance_info.enabledExtensionCount   = (uint32_t)m_extensions.size();
  instance_info.ppEnabledExtensionNames = m_extensions.data();

  if(validation_layers_enabled)
  {
    instance_info.enabledLayerCount   = (uint32_t)m_validation_layers.size();
    instance_info.ppEnabledLayerNames = m_validation_layers.data();
  }
  else
  {
    instance_info.enabledLayerCount = 0;
  }
  instance_info.flags            = {};
  auto [result_signal, instance] = vk::createInstanceUnique(instance_info);
  assert(("Failed to create instance!", result_signal == vk::Result::eSuccess));
  m_instance = std::move(instance);
  VkSurfaceKHR tmp_surface;

  [[maybe_unused]] VkResult result =
      glfwCreateWindowSurface(*m_instance, Window->GetWindowHandle(), nullptr, &tmp_surface);

  assert(("Surface creation failed!", result == VK_SUCCESS));
  m_surface = vk::UniqueSurfaceKHR(vk::SurfaceKHR(tmp_surface), *m_instance);
  m_device  = std::make_unique<Device>(m_surface.get(), *m_instance);


  m_render_graph = std::make_unique<RenderGraph>();
}
} // namespace fpr