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:
Vinicius Rangel 2024-09-20 06:34:19 -03:00 committed by GitHub
parent 077f8981a7
commit 0f4bcd8c83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 4919 additions and 1134 deletions

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