From 6aacf0019a749ae29f276a077aaffa66b186812c Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 19 Jun 2020 00:18:19 +1000 Subject: [PATCH] FrontendCommon: Add a Vulkan host display interface --- src/core/host_display.h | 1 + src/frontend-common/CMakeLists.txt | 6 +- src/frontend-common/frontend-common.vcxproj | 21 +- .../frontend-common.vcxproj.filters | 2 + src/frontend-common/vulkan_host_display.cpp | 492 ++++++++++++++++++ src/frontend-common/vulkan_host_display.h | 87 ++++ 6 files changed, 599 insertions(+), 10 deletions(-) create mode 100644 src/frontend-common/vulkan_host_display.cpp create mode 100644 src/frontend-common/vulkan_host_display.h diff --git a/src/core/host_display.h b/src/core/host_display.h index e23d0b9b9..dd8141d33 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -139,6 +139,7 @@ public: protected: ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast(m_cursor_texture); } + ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture_handle != nullptr); } void CalculateDrawRect(s32 window_width, s32 window_height, s32* out_left, s32* out_top, s32* out_width, s32* out_height, s32* out_left_padding, s32* out_top_padding, float* out_scale, diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index ecfa220bd..c70ce8ae3 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -11,9 +11,11 @@ add_library(frontend-common ini_settings_interface.h save_state_selector_ui.cpp save_state_selector_ui.h + vulkan_host_display.cpp + vulkan_host_display.h ) -target_link_libraries(frontend-common PUBLIC core common imgui simpleini scmversion) +target_link_libraries(frontend-common PUBLIC core common imgui simpleini scmversion vulkan-loader) if(SDL2_FOUND) target_sources(frontend-common PRIVATE @@ -43,4 +45,4 @@ endif() # Copy the provided data directory to the output directory. add_custom_command(TARGET frontend-common POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/data" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" -) \ No newline at end of file +) diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj index 31b051895..983918f48 100644 --- a/src/frontend-common/frontend-common.vcxproj +++ b/src/frontend-common/frontend-common.vcxproj @@ -50,6 +50,9 @@ {3773f4cc-614e-4028-8595-22e08ca649e3} + + {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} + {ee054e08-3799-4a59-a422-18259c105ffd} @@ -67,6 +70,7 @@ + @@ -78,6 +82,7 @@ + @@ -227,7 +232,7 @@ WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -254,7 +259,7 @@ WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -284,7 +289,7 @@ WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -311,7 +316,7 @@ WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -342,7 +347,7 @@ true WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -371,7 +376,7 @@ true WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -402,7 +407,7 @@ true WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -431,7 +436,7 @@ true WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters index bbe227b9f..93791c9ee 100644 --- a/src/frontend-common/frontend-common.vcxproj.filters +++ b/src/frontend-common/frontend-common.vcxproj.filters @@ -10,6 +10,7 @@ + @@ -21,6 +22,7 @@ + diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp new file mode 100644 index 000000000..36ad9406c --- /dev/null +++ b/src/frontend-common/vulkan_host_display.cpp @@ -0,0 +1,492 @@ +#include "vulkan_host_display.h" +#include "common/assert.h" +#include "common/log.h" +#include "common/vulkan/builders.h" +#include "common/vulkan/context.h" +#include "common/vulkan/shader_cache.h" +#include "common/vulkan/staging_texture.h" +#include "common/vulkan/stream_buffer.h" +#include "common/vulkan/swap_chain.h" +#include "common/vulkan/util.h" +#include "imgui.h" +#include "imgui_impl_vulkan.h" +#include +Log_SetChannel(VulkanHostDisplay); + +namespace FrontendCommon { + +class VulkanHostDisplayTexture : public HostDisplayTexture +{ +public: + VulkanHostDisplayTexture(Vulkan::Texture texture, Vulkan::StagingTexture staging_texture) + : m_texture(std::move(texture)), m_staging_texture(std::move(staging_texture)) + { + } + ~VulkanHostDisplayTexture() override = default; + + void* GetHandle() const override { return const_cast(&m_texture); } + u32 GetWidth() const override { return m_texture.GetWidth(); } + u32 GetHeight() const override { return m_texture.GetHeight(); } + + const Vulkan::Texture& GetTexture() const { return m_texture; } + Vulkan::Texture& GetTexture() { return m_texture; } + Vulkan::StagingTexture& GetStagingTexture() { return m_staging_texture; } + + static std::unique_ptr Create(u32 width, u32 height, const void* data, u32 data_stride, + bool dynamic) + { + static constexpr VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; + static constexpr VkImageUsageFlags usage = + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + + Vulkan::Texture texture; + if (!texture.Create(width, height, 1, 1, format, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, usage)) + { + return {}; + } + + Vulkan::StagingTexture staging_texture; + if (data || dynamic) + { + if (!staging_texture.Create(dynamic ? Vulkan::StagingBuffer::Type::Mutable : Vulkan::StagingBuffer::Type::Upload, + format, width, height)) + { + return {}; + } + } + + texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + if (data) + { + staging_texture.WriteTexels(0, 0, width, height, data, data_stride); + staging_texture.CopyToTexture(g_vulkan_context->GetCurrentCommandBuffer(), 0, 0, texture, 0, 0, 0, 0, width, + height); + } + else + { + // clear it instead so we don't read uninitialized data (and keep the validation layer happy!) + static constexpr VkClearColorValue ccv = {}; + static constexpr VkImageSubresourceRange isr = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}; + vkCmdClearColorImage(g_vulkan_context->GetCurrentCommandBuffer(), texture.GetImage(), texture.GetLayout(), &ccv, + 1u, &isr); + } + + texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // don't need to keep the staging texture around if we're not dynamic + if (!dynamic) + staging_texture.Destroy(true); + + return std::make_unique(std::move(texture), std::move(staging_texture)); + } + +private: + Vulkan::Texture m_texture; + Vulkan::StagingTexture m_staging_texture; +}; + +VulkanHostDisplay::VulkanHostDisplay() = default; + +VulkanHostDisplay::~VulkanHostDisplay() +{ + AssertMsg(!g_vulkan_context, "Context should have been destroyed by now"); + AssertMsg(!m_swap_chain, "Swap chain should have been destroyed by now"); +} + +bool VulkanHostDisplay::RecreateSwapChain(const WindowInfo& new_wi) +{ + Assert(!m_swap_chain); + + VkSurfaceKHR surface = Vulkan::SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), new_wi); + if (surface == VK_NULL_HANDLE) + { + Log_ErrorPrintf("Failed to create new surface for swap chain"); + return false; + } + + m_swap_chain = Vulkan::SwapChain::Create(new_wi, surface, false); + if (!m_swap_chain) + { + Log_ErrorPrintf("Failed to create swap chain"); + return false; + } + + return true; +} + +void VulkanHostDisplay::ResizeSwapChain(u32 new_width, u32 new_height) +{ + g_vulkan_context->WaitForGPUIdle(); + + if (!m_swap_chain->ResizeSwapChain(new_width, new_height)) + Panic("Failed to resize swap chain"); + + ImGui::GetIO().DisplaySize.x = static_cast(m_swap_chain->GetWidth()); + ImGui::GetIO().DisplaySize.y = static_cast(m_swap_chain->GetHeight()); +} + +void VulkanHostDisplay::DestroySwapChain() +{ + m_swap_chain.reset(); +} + +std::unique_ptr VulkanHostDisplay::CreateTexture(u32 width, u32 height, const void* data, + u32 data_stride, bool dynamic) +{ + return VulkanHostDisplayTexture::Create(width, height, data, data_stride, dynamic); +} + +void VulkanHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* data, u32 data_stride) +{ + VulkanHostDisplayTexture* vk_texture = static_cast(texture); + + Vulkan::StagingTexture* staging_texture; + if (vk_texture->GetStagingTexture().IsValid()) + { + staging_texture = &vk_texture->GetStagingTexture(); + } + else + { + // TODO: This should use a stream buffer instead for speed. + if (m_upload_staging_texture.IsValid()) + m_upload_staging_texture.Flush(); + + if ((m_upload_staging_texture.GetWidth() < width || m_upload_staging_texture.GetHeight() < height) && + !m_upload_staging_texture.Create(Vulkan::StagingBuffer::Type::Upload, VK_FORMAT_R8G8B8A8_UNORM, width, height)) + { + Panic("Failed to create upload staging texture"); + } + + staging_texture = &m_upload_staging_texture; + } + + staging_texture->WriteTexels(0, 0, width, height, data, data_stride); + staging_texture->CopyToTexture(0, 0, vk_texture->GetTexture(), x, y, 0, 0, width, height); +} + +bool VulkanHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride) +{ + Vulkan::Texture* texture = static_cast(const_cast(texture_handle)); + + if ((m_readback_staging_texture.GetWidth() < width || m_readback_staging_texture.GetHeight() < height) && + !m_readback_staging_texture.Create(Vulkan::StagingBuffer::Type::Readback, VK_FORMAT_R8G8B8A8_UNORM, width, + height)) + { + return false; + } + + m_readback_staging_texture.CopyFromTexture(*texture, x, y, 0, 0, 0, 0, width, height); + m_readback_staging_texture.ReadTexels(0, 0, width, height, out_data, out_data_stride); + return true; +} + +void VulkanHostDisplay::SetVSync(bool enabled) +{ + // This swap chain should not be used by the current buffer, thus safe to destroy. + g_vulkan_context->WaitForGPUIdle(); + m_swap_chain->SetVSync(enabled); +} + +bool VulkanHostDisplay::CreateContextAndSwapChain(const WindowInfo& wi, bool debug_device) +{ + if (!Vulkan::Context::Create(0u, &wi, &m_swap_chain, debug_device, false)) + { + Log_ErrorPrintf("Failed to create Vulkan context"); + return false; + } + + return true; +} + +void VulkanHostDisplay::CreateShaderCache(std::string_view shader_cache_directory, bool debug_shaders) +{ + Vulkan::ShaderCache::Create(shader_cache_directory, debug_shaders); +} + +bool VulkanHostDisplay::HasContext() const +{ + return static_cast(g_vulkan_context); +} + +bool VulkanHostDisplay::CreateResources() +{ + static constexpr char fullscreen_quad_vertex_shader[] = R"( +#version 450 core + +layout(push_constant) uniform PushConstants { + uniform vec4 u_src_rect; +}; + +layout(location = 0) out vec2 v_tex0; + +void main() +{ + vec2 pos = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); + v_tex0 = u_src_rect.xy + pos * u_src_rect.zw; + gl_Position = vec4(pos * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f); + gl_Position.y = -gl_Position.y; +} +)"; + + static constexpr char display_fragment_shader[] = R"( +#version 450 core + +layout(set = 0, binding = 0) uniform sampler2D samp0; + +layout(location = 0) in vec2 v_tex0; +layout(location = 0) out vec4 o_col0; + +void main() +{ + o_col0 = texture(samp0, v_tex0); +} +)"; + + VkDevice device = g_vulkan_context->GetDevice(); + VkPipelineCache pipeline_cache = g_vulkan_shader_cache->GetPipelineCache(); + + Vulkan::DescriptorSetLayoutBuilder dslbuilder; + dslbuilder.AddBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + m_descriptor_set_layout = dslbuilder.Create(device); + if (m_descriptor_set_layout == VK_NULL_HANDLE) + return false; + + Vulkan::PipelineLayoutBuilder plbuilder; + plbuilder.AddDescriptorSet(m_descriptor_set_layout); + plbuilder.AddPushConstants(VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstants)); + m_pipeline_layout = plbuilder.Create(device); + if (m_pipeline_layout == VK_NULL_HANDLE) + return false; + + VkShaderModule vertex_shader = g_vulkan_shader_cache->GetVertexShader(fullscreen_quad_vertex_shader); + if (vertex_shader == VK_NULL_HANDLE) + return false; + + VkShaderModule fragment_shader = g_vulkan_shader_cache->GetFragmentShader(display_fragment_shader); + if (fragment_shader == VK_NULL_HANDLE) + return false; + + Vulkan::GraphicsPipelineBuilder gpbuilder; + gpbuilder.SetVertexShader(vertex_shader); + gpbuilder.SetFragmentShader(fragment_shader); + gpbuilder.SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + gpbuilder.SetNoCullRasterizationState(); + gpbuilder.SetNoDepthTestState(); + gpbuilder.SetNoBlendingState(); + gpbuilder.SetDynamicViewportAndScissorState(); + gpbuilder.SetPipelineLayout(m_pipeline_layout); + gpbuilder.SetRenderPass(m_swap_chain->GetClearRenderPass(), 0); + + m_display_pipeline = gpbuilder.Create(device, pipeline_cache, false); + if (m_display_pipeline == VK_NULL_HANDLE) + return false; + + gpbuilder.SetBlendAttachment(0, true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD); + m_software_cursor_pipeline = gpbuilder.Create(device, pipeline_cache, false); + if (m_software_cursor_pipeline == VK_NULL_HANDLE) + return false; + + // don't need these anymore + vkDestroyShaderModule(device, vertex_shader, nullptr); + vkDestroyShaderModule(device, fragment_shader, nullptr); + + Vulkan::SamplerBuilder sbuilder; + sbuilder.SetPointSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + m_point_sampler = sbuilder.Create(device, true); + if (m_point_sampler == VK_NULL_HANDLE) + return false; + + sbuilder.SetLinearSampler(false, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + m_linear_sampler = sbuilder.Create(device); + if (m_linear_sampler == VK_NULL_HANDLE) + return false; + + return true; +} + +void VulkanHostDisplay::DestroyResources() +{ + m_readback_staging_texture.Destroy(false); + m_upload_staging_texture.Destroy(false); + + Vulkan::Util::SafeDestroyPipeline(m_display_pipeline); + Vulkan::Util::SafeDestroyPipeline(m_software_cursor_pipeline); + Vulkan::Util::SafeDestroyPipelineLayout(m_pipeline_layout); + Vulkan::Util::SafeDestroyDescriptorSetLayout(m_descriptor_set_layout); + Vulkan::Util::SafeDestroySampler(m_point_sampler); + Vulkan::Util::SafeDestroySampler(m_linear_sampler); +} + +void VulkanHostDisplay::DestroyImGuiContext() +{ + ImGui_ImplVulkan_Shutdown(); +} + +void VulkanHostDisplay::DestroyContext() +{ + if (!g_vulkan_context) + return; + + g_vulkan_context->WaitForGPUIdle(); + Vulkan::Context::Destroy(); +} + +void VulkanHostDisplay::DestroyShaderCache() +{ + Vulkan::ShaderCache::Destroy(); +} + +bool VulkanHostDisplay::CreateImGuiContext() +{ + ImGui::GetIO().DisplaySize.x = static_cast(m_swap_chain->GetWidth()); + ImGui::GetIO().DisplaySize.y = static_cast(m_swap_chain->GetHeight()); + + ImGui_ImplVulkan_InitInfo vii = {}; + vii.Instance = g_vulkan_context->GetVulkanInstance(); + vii.PhysicalDevice = g_vulkan_context->GetPhysicalDevice(); + vii.Device = g_vulkan_context->GetDevice(); + vii.QueueFamily = g_vulkan_context->GetGraphicsQueueFamilyIndex(); + vii.Queue = g_vulkan_context->GetGraphicsQueue(); + vii.PipelineCache = g_vulkan_shader_cache->GetPipelineCache(); + vii.DescriptorPool = g_vulkan_context->GetGlobalDescriptorPool(); + vii.MinImageCount = m_swap_chain->GetImageCount(); + vii.ImageCount = m_swap_chain->GetImageCount(); + vii.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + + if (!ImGui_ImplVulkan_Init(&vii, m_swap_chain->GetClearRenderPass()) || + !ImGui_ImplVulkan_CreateFontsTexture(g_vulkan_context->GetCurrentCommandBuffer())) + { + return false; + } + + ImGui_ImplVulkan_NewFrame(); + return true; +} + +bool VulkanHostDisplay::BeginRender() +{ + VkResult res = m_swap_chain->AcquireNextImage(); + if (res != VK_SUCCESS) + { + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + { + ResizeSwapChain(0, 0); + res = m_swap_chain->AcquireNextImage(); + } + + if (res != VK_SUCCESS) + { + Panic("Failed to acquire swap chain image"); + return false; + } + } + + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture(); + + // Swap chain images start in undefined + swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); + swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + const VkClearValue clear_value = {}; + + const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + m_swap_chain->GetClearRenderPass(), + m_swap_chain->GetCurrentFramebuffer(), + {{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}, + 1u, + &clear_value}; + vkCmdBeginRenderPass(cmdbuffer, &rp, VK_SUBPASS_CONTENTS_INLINE); + return true; +} + +void VulkanHostDisplay::EndRenderAndPresent() +{ + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture(); + + vkCmdEndRenderPass(cmdbuffer); + + swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + g_vulkan_context->SubmitCommandBuffer(m_swap_chain->GetImageAvailableSemaphore(), + m_swap_chain->GetRenderingFinishedSemaphore(), m_swap_chain->GetSwapChain(), + m_swap_chain->GetCurrentImageIndex()); + g_vulkan_context->MoveToNextCommandBuffer(); + + ImGui::NewFrame(); + ImGui_ImplVulkan_NewFrame(); +} + +void VulkanHostDisplay::RenderDisplay(s32 left, s32 top, s32 width, s32 height, void* texture_handle, u32 texture_width, + u32 texture_height, u32 texture_view_x, u32 texture_view_y, + u32 texture_view_width, u32 texture_view_height, bool linear_filter) +{ + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + + VkDescriptorSet ds = g_vulkan_context->AllocateDescriptorSet(m_descriptor_set_layout); + if (ds == VK_NULL_HANDLE) + { + Log_ErrorPrintf("Skipping rendering display because of no descriptor set"); + return; + } + + { + const Vulkan::Texture* vktex = static_cast(texture_handle); + Vulkan::DescriptorSetUpdateBuilder dsupdate; + dsupdate.AddCombinedImageSamplerDescriptorWrite( + ds, 0, vktex->GetView(), linear_filter ? m_linear_sampler : m_point_sampler, vktex->GetLayout()); + dsupdate.Update(g_vulkan_context->GetDevice()); + } + + const PushConstants pc{static_cast(texture_view_x) / static_cast(texture_width), + static_cast(texture_view_y) / static_cast(texture_height), + (static_cast(texture_view_width) - 0.5f) / static_cast(texture_width), + (static_cast(texture_view_height) - 0.5f) / static_cast(texture_height)}; + + vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_display_pipeline); + vkCmdPushConstants(cmdbuffer, m_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc); + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &ds, 0, nullptr); + Vulkan::Util::SetViewportAndScissor(cmdbuffer, left, top, width, height); + vkCmdDraw(cmdbuffer, 3, 1, 0, 0); +} + +void VulkanHostDisplay::RenderImGui() +{ + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), g_vulkan_context->GetCurrentCommandBuffer()); +} + +void VulkanHostDisplay::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, HostDisplayTexture* texture) +{ + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + + VkDescriptorSet ds = g_vulkan_context->AllocateDescriptorSet(m_descriptor_set_layout); + if (ds == VK_NULL_HANDLE) + { + Log_ErrorPrintf("Skipping rendering software cursor because of no descriptor set"); + return; + } + + { + Vulkan::DescriptorSetUpdateBuilder dsupdate; + dsupdate.AddImageDescriptorWrite(ds, 0, static_cast(texture)->GetTexture().GetView()); + dsupdate.AddSamplerDescriptorWrite(ds, 0, m_linear_sampler); + dsupdate.Update(g_vulkan_context->GetDevice()); + } + + const PushConstants pc{0.0f, 0.0f, 1.0f, 1.0f}; + vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_display_pipeline); + vkCmdPushConstants(cmdbuffer, m_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc); + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &ds, 0, nullptr); + Vulkan::Util::SetViewportAndScissor(cmdbuffer, left, top, width, height); + vkCmdDraw(cmdbuffer, 3, 1, 0, 0); +} + +} // namespace FrontendCommon \ No newline at end of file diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h new file mode 100644 index 000000000..774c74f79 --- /dev/null +++ b/src/frontend-common/vulkan_host_display.h @@ -0,0 +1,87 @@ +#pragma once +#include "common/vulkan/staging_texture.h" +#include "common/vulkan/swap_chain.h" +#include "common/window_info.h" +#include "core/host_display.h" +#include "vulkan_loader.h" +#include +#include + +namespace Vulkan { +class StreamBuffer; +class SwapChain; +} // namespace Vulkan + +namespace FrontendCommon { + +class VulkanHostDisplay +{ +public: + VulkanHostDisplay(); + ~VulkanHostDisplay(); + + ALWAYS_INLINE HostDisplay::RenderAPI GetRenderAPI() const { return HostDisplay::RenderAPI::Vulkan; } + ALWAYS_INLINE void* GetRenderDevice() const { return nullptr; } + ALWAYS_INLINE void* GetRenderContext() const { return nullptr; } + + std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, + bool dynamic); + void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, + u32 data_stride); + bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride); + + void SetVSync(bool enabled); + + bool BeginRender(); + void RenderDisplay(s32 left, s32 top, s32 width, s32 height, void* texture_handle, u32 texture_width, + u32 texture_height, u32 texture_view_x, u32 texture_view_y, u32 texture_view_width, + u32 texture_view_height, bool linear_filter); + void RenderImGui(); + void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, HostDisplayTexture* texture_handle); + void EndRenderAndPresent(); + + bool CreateContextAndSwapChain(const WindowInfo& wi, bool debug_device); + bool HasContext() const; + void DestroyContext(); + + void CreateShaderCache(std::string_view shader_cache_directory, bool debug_shaders); + void DestroyShaderCache(); + + bool CreateResources(); + void DestroyResources(); + + bool CreateImGuiContext(); + void DestroyImGuiContext(); + + ALWAYS_INLINE u32 GetSwapChainWidth() const { return m_swap_chain->GetWidth(); } + ALWAYS_INLINE u32 GetSwapChainHeight() const { return m_swap_chain->GetHeight(); } + ALWAYS_INLINE bool HasSwapChain() const { return static_cast(m_swap_chain); } + + bool RecreateSwapChain(const WindowInfo& new_wi); + void ResizeSwapChain(u32 new_width, u32 new_height); + void DestroySwapChain(); + +private: + struct PushConstants + { + float src_rect_left; + float src_rect_top; + float src_rect_width; + float src_rect_height; + }; + + std::unique_ptr m_swap_chain; + + VkDescriptorSetLayout m_descriptor_set_layout = VK_NULL_HANDLE; + VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE; + VkPipeline m_software_cursor_pipeline = VK_NULL_HANDLE; + VkPipeline m_display_pipeline = VK_NULL_HANDLE; + VkSampler m_point_sampler = VK_NULL_HANDLE; + VkSampler m_linear_sampler = VK_NULL_HANDLE; + + Vulkan::StagingTexture m_upload_staging_texture; + Vulkan::StagingTexture m_readback_staging_texture; +}; + +} // namespace FrontendCommon \ No newline at end of file