diff --git a/CMakeLists.txt b/CMakeLists.txt index 46659d3d0..86cb693c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -613,6 +613,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp set(IMGUI src/imgui/imgui_config.h src/imgui/imgui_layer.h src/imgui/imgui_std.h + src/imgui/imgui_texture.h src/imgui/layer/video_info.cpp src/imgui/layer/video_info.h src/imgui/renderer/imgui_core.cpp @@ -621,6 +622,8 @@ set(IMGUI src/imgui/imgui_config.h src/imgui/renderer/imgui_impl_sdl3.h src/imgui/renderer/imgui_impl_vulkan.cpp src/imgui/renderer/imgui_impl_vulkan.h + src/imgui/renderer/texture_manager.cpp + src/imgui/renderer/texture_manager.h ) set(INPUT src/input/controller.cpp diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 4602382ed..2094d56bc 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -26,4 +26,7 @@ extern void assert_fail_debug_msg(const char* msg); #define IMGUI_DEFINE_MATH_OPERATORS #define IM_VEC2_CLASS_EXTRA \ - constexpr ImVec2(float _v) : x(_v), y(_v) {} \ No newline at end of file + constexpr ImVec2(float _v) : x(_v), y(_v) {} + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} \ No newline at end of file diff --git a/src/imgui/imgui_texture.h b/src/imgui/imgui_texture.h new file mode 100644 index 000000000..1a38066d0 --- /dev/null +++ b/src/imgui/imgui_texture.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner; +} // namespace Core::TextureManager + +class RefCountedTexture { + Core::TextureManager::Inner* inner; + + explicit RefCountedTexture(Core::TextureManager::Inner* inner); + +public: + struct Image { + ImTextureID im_id; + u32 width; + u32 height; + }; + + static RefCountedTexture DecodePngTexture(std::vector data); + + static RefCountedTexture DecodePngFile(std::filesystem::path path); + + RefCountedTexture(); + + RefCountedTexture(const RefCountedTexture& other); + RefCountedTexture(RefCountedTexture&& other) noexcept; + RefCountedTexture& operator=(const RefCountedTexture& other); + RefCountedTexture& operator=(RefCountedTexture&& other) noexcept; + + virtual ~RefCountedTexture(); + + [[nodiscard]] Image GetTexture() const; + + explicit(false) operator bool() const; +}; + +}; // namespace ImGui \ No newline at end of file diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 1c6313972..3cbd5faa0 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -10,6 +10,7 @@ #include "imgui_impl_sdl3.h" #include "imgui_impl_vulkan.h" #include "sdl_window.h" +#include "texture_manager.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" static void CheckVkResult(const vk::Result err) { @@ -68,6 +69,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w .check_vk_result_fn = &CheckVkResult, }; Vulkan::Init(vk_info); + + TextureManager::StartWorker(); } void OnResize() { @@ -77,6 +80,8 @@ void OnResize() { void Shutdown(const vk::Device& device) { device.waitIdle(); + TextureManager::StopWorker(); + const ImGuiIO& io = GetIO(); const auto ini_filename = (void*)io.IniFilename; const auto log_filename = (void*)io.LogFilename; @@ -130,7 +135,6 @@ void NewFrame() { } } - Vulkan::NewFrame(); Sdl::NewFrame(); ImGui::NewFrame(); diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp index 2c1c135f7..cf8c5ea4e 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.cpp +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -4,6 +4,8 @@ // Based on imgui_impl_vulkan.cpp from Dear ImGui repository #include +#include + #include #include "imgui_impl_vulkan.h" @@ -47,13 +49,15 @@ struct VkData { vk::ShaderModule shader_module_vert{}; vk::ShaderModule shader_module_frag{}; + std::mutex command_pool_mutex; + vk::CommandPool command_pool{}; + vk::Sampler simple_sampler{}; + // Font data - vk::Sampler font_sampler{}; vk::DeviceMemory font_memory{}; vk::Image font_image{}; vk::ImageView font_view{}; vk::DescriptorSet font_descriptor_set{}; - vk::CommandPool font_command_pool{}; vk::CommandBuffer font_command_buffer{}; // Render buffers @@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize return (size + alignment - 1) & ~(alignment - 1); } -// Register a texture -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout) { +void UploadTextureData::Upload() { VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; + vk::SubmitInfo submit_info{ + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + }; + CheckVkErr(v.queue.submit({submit_info})); + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + { + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {command_buffer}); + } + upload_buffer = VK_NULL_HANDLE; + upload_buffer_memory = VK_NULL_HANDLE; +} + +void UploadTextureData::Destroy() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CheckVkErr(v.device.waitIdle()); + RemoveTexture(descriptor_set); + descriptor_set = VK_NULL_HANDLE; + + v.device.destroyImageView(image_view, v.allocator); + image_view = VK_NULL_HANDLE; + v.device.destroyImage(image, v.allocator); + image = VK_NULL_HANDLE; + v.device.freeMemory(image_memory, v.allocator); + image_memory = VK_NULL_HANDLE; +} + +// Register a texture +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (sampler == VK_NULL_HANDLE) { + sampler = bd->simple_sampler; + } + // Create Descriptor Set: vk::DescriptorSet descriptor_set; { @@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, } return descriptor_set; } +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size) { + ImGuiIO& io = GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + UploadTextureData info{}; + { + std::unique_lock lk(bd->command_pool_mutex); + info.command_buffer = + CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{ + .commandPool = bd->command_pool, + .commandBufferCount = 1, + })) + .front(); + CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + })); + } + + // Create Image + { + vk::ImageCreateInfo image_info{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent{ + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + info.image = CheckVkResult(v.device.createImage(image_info, v.allocator)); + auto req = v.device.getImageMemoryRequirements(info.image); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0)); + } + + // Create Image View + { + vk::ImageViewCreateInfo view_info{ + .image = info.image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator)); + } + + // Create descriptor set (ImTextureID) + info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create Upload Buffer + { + vk::BufferCreateInfo buffer_info{ + .size = size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + auto req = v.device.getBufferMemoryRequirements(info.upload_buffer); + auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0)); + } + + // Upload to Buffer + { + char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size)); + memcpy(map, data, size); + vk::MappedMemoryRange range[1]{ + { + .memory = info.upload_buffer_memory, + .size = size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges(range)); + v.device.unmapMemory(info.upload_buffer_memory); + } + + // Copy to Image + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = width, + .height = height, + .depth = 1, + }, + }; + info.command_buffer.copyBufferToImage(info.upload_buffer, info.image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {use_barrier}); + } + + CheckVkErr(info.command_buffer.end()); + + return info; +} void RemoveTexture(vk::DescriptorSet descriptor_set) { VkData* bd = GetBackendData(); @@ -517,27 +722,20 @@ static bool CreateFontsTexture() { DestroyFontsTexture(); } - // Create command pool/buffer - if (bd->font_command_pool == VK_NULL_HANDLE) { - vk::CommandPoolCreateInfo info{ - .sType = vk::StructureType::eCommandPoolCreateInfo, - .flags = vk::CommandPoolCreateFlags{}, - .queueFamilyIndex = v.queue_family, - }; - bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); - } + // Create command buffer if (bd->font_command_buffer == VK_NULL_HANDLE) { vk::CommandBufferAllocateInfo info{ .sType = vk::StructureType::eCommandBufferAllocateInfo, - .commandPool = bd->font_command_pool, + .commandPool = bd->command_pool, .commandBufferCount = 1, }; + std::unique_lock lk(bd->command_pool_mutex); bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); } // Start command buffer { - CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{})); + CheckVkErr(bd->font_command_buffer.reset()); vk::CommandBufferBeginInfo begin_info{}; begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; @@ -597,8 +795,7 @@ static bool CreateFontsTexture() { } // Create the Descriptor Set: - bd->font_descriptor_set = - AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); // Create the Upload Buffer: vk::DeviceMemory upload_buffer_memory{}; @@ -956,25 +1153,6 @@ bool CreateDeviceObjects() { bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info)); } - if (!bd->font_sampler) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= - // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow - // point/nearest sampling. - vk::SamplerCreateInfo info{ - .sType = vk::StructureType::eSamplerCreateInfo, - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .maxAnisotropy = 1.0f, - .minLod = -1000, - .maxLod = 1000, - }; - bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); - } - if (!bd->descriptor_set_layout) { vk::DescriptorSetLayoutBinding binding[1]{ { @@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() { CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + if (bd->command_pool == VK_NULL_HANDLE) { + vk::CommandPoolCreateInfo info{ + .sType = vk::StructureType::eCommandPoolCreateInfo, + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = v.queue_family, + }; + std::unique_lock lk(bd->command_pool_mutex); + bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); + } + + if (!bd->simple_sampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow + // point/nearest sampling. + vk::SamplerCreateInfo info{ + .sType = vk::StructureType::eSamplerCreateInfo, + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .maxAnisotropy = 1.0f, + .minLod = -1000, + .maxLod = 1000, + }; + bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); + } + return true; } @@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() { DestroyFontsTexture(); if (bd->font_command_buffer) { - v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer}); + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer}); bd->font_command_buffer = VK_NULL_HANDLE; } - if (bd->font_command_pool) { - v.device.destroyCommandPool(bd->font_command_pool, v.allocator); - bd->font_command_pool = VK_NULL_HANDLE; + if (bd->command_pool) { + std::unique_lock lk(bd->command_pool_mutex); + v.device.destroyCommandPool(bd->command_pool, v.allocator); + bd->command_pool = VK_NULL_HANDLE; } if (bd->shader_module_vert) { v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); @@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() { v.device.destroyShaderModule(bd->shader_module_frag, v.allocator); bd->shader_module_frag = VK_NULL_HANDLE; } - if (bd->font_sampler) { - v.device.destroySampler(bd->font_sampler, v.allocator); - bd->font_sampler = VK_NULL_HANDLE; + if (bd->simple_sampler) { + v.device.destroySampler(bd->simple_sampler, v.allocator); + bd->simple_sampler = VK_NULL_HANDLE; } if (bd->descriptor_set_layout) { v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); @@ -1095,13 +1304,4 @@ void Shutdown() { IM_DELETE(bd); } -void NewFrame() { - VkData* bd = GetBackendData(); - IM_ASSERT(bd != nullptr && - "Context or backend not initialized! Did you call ImGuiImplVulkanInit()?"); - - if (!bd->font_descriptor_set) - CreateFontsTexture(); -} - } // namespace ImGui::Vulkan diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h index e68b8723f..ca76fda6d 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.h +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -6,6 +6,7 @@ #pragma once #define VULKAN_HPP_NO_EXCEPTIONS +#include "common/types.h" #include "video_core/renderer_vulkan/vk_common.h" struct ImDrawData; @@ -29,14 +30,33 @@ struct InitInfo { void (*check_vk_result_fn)(vk::Result err); }; -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout); +// Prepare all resources needed for uploading textures +// Caller should clean up the returned data. +struct UploadTextureData { + vk::Image image; + vk::ImageView image_view; + vk::DescriptorSet descriptor_set; + vk::DeviceMemory image_memory; + + vk::CommandBuffer command_buffer; // Submit to the queue + vk::Buffer upload_buffer; + vk::DeviceMemory upload_buffer_memory; + + void Upload(); + + void Destroy(); +}; + +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler = VK_NULL_HANDLE); + +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size); void RemoveTexture(vk::DescriptorSet descriptor_set); bool Init(InitInfo info); void Shutdown(); -void NewFrame(); void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, vk::Pipeline pipeline = VK_NULL_HANDLE); diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp new file mode 100644 index 000000000..ba4a05d01 --- /dev/null +++ b/src/imgui/renderer/texture_manager.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "common/assert.h" +#include "common/io_file.h" +#include "common/polyfill_thread.h" +#include "imgui_impl_vulkan.h" +#include "texture_manager.h" + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner { + std::atomic_int count = 0; + ImTextureID texture_id = nullptr; + u32 width = 0; + u32 height = 0; + + Vulkan::UploadTextureData upload_data; + + ~Inner(); +}; +} // namespace Core::TextureManager + +using namespace Core::TextureManager; + +RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) { + ++inner->count; +} + +RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector data) { + const auto core = new Inner; + Core::TextureManager::DecodePngTexture(std::move(data), core); + return RefCountedTexture(core); +} + +RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) { + const auto core = new Inner; + Core::TextureManager::DecodePngFile(std::move(path), core); + return RefCountedTexture(core); +} + +RefCountedTexture::RefCountedTexture() : inner(nullptr) {} + +RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) { + if (inner != nullptr) { + ++inner->count; + } +} + +RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) { + other.inner = nullptr; +} + +RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) { + if (this == &other) + return *this; + inner = other.inner; + if (inner != nullptr) { + ++inner->count; + } + return *this; +} + +RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept { + if (this == &other) + return *this; + std::swap(inner, other.inner); + return *this; +} + +RefCountedTexture::~RefCountedTexture() { + if (inner != nullptr) { + if (inner->count.fetch_sub(1) == 1) { + delete inner; + } + } +} +RefCountedTexture::Image RefCountedTexture::GetTexture() const { + if (inner == nullptr) { + return {}; + } + return Image{ + .im_id = inner->texture_id, + .width = inner->width, + .height = inner->height, + }; +} +RefCountedTexture::operator bool() const { + return inner != nullptr && inner->texture_id != nullptr; +} + +struct Job { + Inner* core; + std::vector data; + std::filesystem::path path; +}; + +struct UploadJob { + Inner* core = nullptr; + Vulkan::UploadTextureData data; + int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw +}; + +static bool g_is_worker_running = false; +static std::jthread g_worker_thread; +static std::condition_variable g_worker_cv; + +static std::mutex g_job_list_mtx; +static std::deque g_job_list; + +static std::mutex g_upload_mtx; +static std::deque g_upload_list; + +namespace Core::TextureManager { + +Inner::~Inner() { + if (upload_data.descriptor_set != nullptr) { + std::unique_lock lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .data = this->upload_data, + .tick = 2, + }); + } +} + +void WorkerLoop() { + std::mutex mtx; + while (g_is_worker_running) { + std::unique_lock lk{mtx}; + g_worker_cv.wait(lk); + if (!g_is_worker_running) { + break; + } + while (true) { + g_job_list_mtx.lock(); + if (g_job_list.empty()) { + g_job_list_mtx.unlock(); + break; + } + auto [core, png_raw, path] = std::move(g_job_list.front()); + g_job_list.pop_front(); + g_job_list_mtx.unlock(); + + if (!path.empty()) { // Decode PNG from file + Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string()); + continue; + } + png_raw.resize(file.GetSize()); + file.Seek(0); + file.ReadRaw(png_raw.data(), png_raw.size()); + file.Close(); + } + + int width, height; + const stbi_uc* pixels = + stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4); + + auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height, + width * height * 4 * sizeof(stbi_uc)); + + core->upload_data = texture; + core->width = width; + core->height = height; + + std::unique_lock upload_lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .core = core, + }); + } + } +} + +void StartWorker() { + ASSERT(!g_is_worker_running); + g_worker_thread = std::jthread(WorkerLoop); + g_is_worker_running = true; +} + +void StopWorker() { + ASSERT(g_is_worker_running); + g_is_worker_running = false; + g_worker_cv.notify_one(); +} + +void DecodePngTexture(std::vector data, Inner* core) { + ++core->count; + Job job{ + .core = core, + .data = std::move(data), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void DecodePngFile(std::filesystem::path path, Inner* core) { + ++core->count; + Job job{ + .core = core, + .path = std::move(path), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void Submit() { + UploadJob upload; + { + std::unique_lock lk{g_upload_mtx}; + if (g_upload_list.empty()) { + return; + } + // Upload one texture at a time to avoid slow down + upload = g_upload_list.front(); + g_upload_list.pop_front(); + if (upload.tick > 0) { + --upload.tick; + g_upload_list.emplace_back(upload); + return; + } + } + if (upload.core != nullptr) { + upload.core->upload_data.Upload(); + upload.core->texture_id = upload.core->upload_data.descriptor_set; + if (upload.core->count.fetch_sub(1) == 1) { + delete upload.core; + } + } else { + upload.data.Destroy(); + } +} +} // namespace Core::TextureManager + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.h b/src/imgui/renderer/texture_manager.h new file mode 100644 index 000000000..4fa7b9924 --- /dev/null +++ b/src/imgui/renderer/texture_manager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" +#include "imgui/imgui_texture.h" + +namespace vk { +class CommandBuffer; +} + +namespace ImGui::Core::TextureManager { + +struct Inner; + +void StartWorker(); + +void StopWorker(); + +void DecodePngTexture(std::vector data, Inner* core); + +void DecodePngFile(std::filesystem::path path, Inner* core); + +void Submit(); + +}; // namespace ImGui::Core::TextureManager \ No newline at end of file diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 9ff332aef..b99dfdbb4 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" #include "common/debug.h" +#include "imgui/renderer/texture_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -190,6 +191,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { }; try { + ImGui::Core::TextureManager::Submit(); instance.GetGraphicsQueue().submit(submit_info, info.fence); } catch (vk::DeviceLostError& err) { UNREACHABLE_MSG("Device lost during submit: {}", err.what());