imgui Ref-counted textures

- has a background thread to decode textures
This commit is contained in:
Vinicius Rangel 2024-09-18 02:14:49 -03:00
parent 0e61607f2e
commit e9218c1395
No known key found for this signature in database
GPG key ID: A5B154D904B761D9
9 changed files with 608 additions and 58 deletions

View file

@ -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

View file

@ -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) {}
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) {}

45
src/imgui/imgui_texture.h Normal file
View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <imgui.h>
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<u8> 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

View file

@ -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();

View file

@ -4,6 +4,8 @@
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
#include <cstdio>
#include <mutex>
#include <imgui.h>
#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

View file

@ -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);

View file

@ -0,0 +1,243 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <deque>
#include <utility>
#include <externals/stb_image.h>
#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<u8> 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<u8> 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<Job> g_job_list;
static std::mutex g_upload_mtx;
static std::deque<UploadJob> 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<u8>(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<u8> 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

View file

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
#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<u8> data, Inner* core);
void DecodePngFile(std::filesystem::path path, Inner* core);
void Submit();
}; // namespace ImGui::Core::TextureManager

View file

@ -4,6 +4,7 @@
#include <mutex>
#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());