mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-10 05:38:49 +00:00
Rewrite Save Data & Impl Save Data Dialog (#824)
* core: Rewrite PSF parser & add encoder add .sfo hex pattern to /scripts * core/fs: allow to mount path as read-only * common: Add CString wrapper to handle native null-terminated strings * SaveData: rewrite to implement full functionality * mock value for SYSTEM_VER * SavaData: backup features * SavaData: SaveDataMemory features * imgui Ref-counted textures - has a background thread to decode textures * imgui: rework gamepad navigation * PSF: fixed psf not using enum class for PSFEntryFmt (was a standard old ugly enum) - Add null check to CString when itself is used in a nullable field * SaveDataDialog implementation - Fix Mounting/Unmounting check of SaveInstance
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
#include "imgui_core.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "imgui_internal.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 +70,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 +81,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;
|
||||
@@ -92,24 +98,19 @@ void Shutdown(const vk::Device& device) {
|
||||
bool ProcessEvent(SDL_Event* event) {
|
||||
Sdl::ProcessEvent(event);
|
||||
switch (event->type) {
|
||||
// Don't block release/up events
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
return GetIO().WantCaptureMouse;
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
return GetIO().WantCaptureKeyboard;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
||||
return GetIO().NavActive;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -130,21 +131,11 @@ void NewFrame() {
|
||||
}
|
||||
}
|
||||
|
||||
Vulkan::NewFrame();
|
||||
Sdl::NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
bool capture_gamepad = false;
|
||||
for (auto* layer : layers) {
|
||||
layer->Draw();
|
||||
if (layer->ShouldGrabGamepad()) {
|
||||
capture_gamepad = true;
|
||||
}
|
||||
}
|
||||
if (capture_gamepad) {
|
||||
GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
} else {
|
||||
GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
243
src/imgui/renderer/texture_manager.cpp
Normal file
243
src/imgui/renderer/texture_manager.cpp
Normal 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
|
||||
30
src/imgui/renderer/texture_manager.h
Normal file
30
src/imgui/renderer/texture_manager.h
Normal 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
|
||||
Reference in New Issue
Block a user