From 047a115b3ea5c989407050465264050b94043a50 Mon Sep 17 00:00:00 2001 From: psucien Date: Sun, 8 Sep 2024 11:12:25 +0200 Subject: [PATCH 1/7] hot-fix: exclude tiling condition from promotion of textures to depth --- src/video_core/texture_cache/image_info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 7d87fb6665..0b0f4278d2 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -204,7 +204,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) n tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); // Override format if image is forced to be a depth target - if (force_depth || tiling_mode == AmdGpu::TilingMode::Depth_MacroTiled) { + if (force_depth) { if (pixel_format == vk::Format::eR32Sfloat || pixel_format == vk::Format::eR8Unorm) { pixel_format = vk::Format::eD32SfloatS8Uint; } else if (pixel_format == vk::Format::eR16Unorm) { From f1becb2507cb056e222cdbedef63a0418b108ceb Mon Sep 17 00:00:00 2001 From: psucien Date: Sun, 8 Sep 2024 14:18:48 +0200 Subject: [PATCH 2/7] hot-fix: linear cubemaps check assert removed (verified) --- src/video_core/texture_cache/image_info.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 0b0f4278d2..011e19db8f 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -260,7 +260,6 @@ void ImageInfo::UpdateSize() { switch (tiling_mode) { case AmdGpu::TilingMode::Display_Linear: { - ASSERT(!props.is_cube); std::tie(mip_info.pitch, mip_info.size) = ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); mip_info.height = mip_h; From 035cb3eeaa1ca69dcb71fe8bfd594fbdc54454ca Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 8 Sep 2024 16:50:32 -0300 Subject: [PATCH 3/7] Dear ImGui Implementation (#598) * added imgui as dependency * imgui renderer/basic input implementation * imgui: add layers system Add video info layer to show fps. Press F10 to toggle it. * imgui: add custom imgui config * imgui: gamepad capture, stopping propagation * imgui: changed config & log file path to use portable dir * videoout: render blank frame when video output is closed required to render imgui even when game has no video output - fixed merge compile-error --- .gitmodules | 5 + CMakeLists.txt | 19 +- externals/CMakeLists.txt | 11 + externals/dear_imgui | 1 + src/common/assert.cpp | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/videoout/driver.cpp | 18 +- src/core/libraries/videoout/driver.h | 1 + src/imgui/imgui_config.h | 29 + src/imgui/imgui_layer.h | 21 + src/imgui/layer/video_info.cpp | 16 + src/imgui/layer/video_info.h | 23 + src/imgui/renderer/imgui_core.cpp | 213 ++++ src/imgui/renderer/imgui_core.h | 31 + src/imgui/renderer/imgui_impl_sdl3.cpp | 789 ++++++++++++ src/imgui/renderer/imgui_impl_sdl3.h | 31 + src/imgui/renderer/imgui_impl_vulkan.cpp | 1107 +++++++++++++++++ src/imgui/renderer/imgui_impl_vulkan.h | 43 + src/sdl_window.cpp | 6 + src/sdl_window.h | 4 + .../renderer_vulkan/renderer_vulkan.cpp | 15 +- .../renderer_vulkan/renderer_vulkan.h | 4 + 23 files changed, 2386 insertions(+), 8 deletions(-) create mode 160000 externals/dear_imgui create mode 100644 src/imgui/imgui_config.h create mode 100644 src/imgui/imgui_layer.h create mode 100644 src/imgui/layer/video_info.cpp create mode 100644 src/imgui/layer/video_info.h create mode 100644 src/imgui/renderer/imgui_core.cpp create mode 100644 src/imgui/renderer/imgui_core.h create mode 100644 src/imgui/renderer/imgui_impl_sdl3.cpp create mode 100644 src/imgui/renderer/imgui_impl_sdl3.h create mode 100644 src/imgui/renderer/imgui_impl_vulkan.cpp create mode 100644 src/imgui/renderer/imgui_impl_vulkan.h diff --git a/.gitmodules b/.gitmodules index 95b0fc0bb4..be4c1851a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -85,3 +85,8 @@ [submodule "externals/half"] path = externals/half url = https://github.com/ROCm/half.git +[submodule "externals/dear_imgui"] + path = externals/dear_imgui + url = https://github.com/shadps4-emu/ext-imgui.git + shallow = true + branch = docking diff --git a/CMakeLists.txt b/CMakeLists.txt index 2da9465cc9..4987b96e16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -561,6 +561,18 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderdoc.h ) +set(IMGUI src/imgui/imgui_config.h + src/imgui/imgui_layer.h + src/imgui/layer/video_info.cpp + src/imgui/layer/video_info.h + src/imgui/renderer/imgui_core.cpp + src/imgui/renderer/imgui_core.h + src/imgui/renderer/imgui_impl_sdl3.cpp + src/imgui/renderer/imgui_impl_sdl3.h + src/imgui/renderer/imgui_impl_vulkan.cpp + src/imgui/renderer/imgui_impl_vulkan.h +) + set(INPUT src/input/controller.cpp src/input/controller.h ) @@ -617,6 +629,7 @@ endif() if (ENABLE_QT_GUI) qt_add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${QT_GUI} ${COMMON} @@ -629,6 +642,7 @@ if (ENABLE_QT_GUI) else() add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${COMMON} ${CORE} @@ -645,9 +659,12 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) +target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") +target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") + if (APPLE) option(USE_SYSTEM_VULKAN_LOADER "Enables using the system Vulkan loader instead of directly linking with MoltenVK. Useful for loading validation layers." OFF) if (USE_SYSTEM_VULKAN_LOADER) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index de0317ff90..b3ba2134a3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -155,6 +155,17 @@ if (APPLE) endif() endif() +# Dear ImGui +add_library(Dear_ImGui + dear_imgui/imgui.cpp + dear_imgui/imgui_demo.cpp + dear_imgui/imgui_draw.cpp + dear_imgui/imgui_internal.h + dear_imgui/imgui_tables.cpp + dear_imgui/imgui_widgets.cpp +) +target_include_directories(Dear_ImGui INTERFACE dear_imgui/) + # Tracy option(TRACY_ENABLE "" ON) option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash diff --git a/externals/dear_imgui b/externals/dear_imgui new file mode 160000 index 0000000000..636cd4a7d6 --- /dev/null +++ b/externals/dear_imgui @@ -0,0 +1 @@ +Subproject commit 636cd4a7d623a2bc9bf59bb3acbb4ca075befba3 diff --git a/src/common/assert.cpp b/src/common/assert.cpp index 3a49c9396b..78c6ec0754 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -18,3 +18,8 @@ void assert_fail_impl() { Crash(); throw std::runtime_error("Unreachable code"); } + +void assert_fail_debug_msg(const char* msg) { + LOG_CRITICAL(Debug, "Assertion Failed!\n{}", msg); + assert_fail_impl(); +} diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index ab3468ca07..3257a6019e 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -117,6 +117,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { CLS(Render) \ SUB(Render, Vulkan) \ SUB(Render, Recompiler) \ + CLS(ImGui) \ CLS(Input) \ CLS(Tty) \ CLS(Loader) diff --git a/src/common/logging/types.h b/src/common/logging/types.h index dd2376ea67..dbae836c4f 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -84,6 +84,7 @@ enum class Class : u8 { Render, ///< Video Core Render_Vulkan, ///< Vulkan backend Render_Recompiler, ///< Shader recompiler + ImGui, ///< ImGui Loader, ///< ROM loader Input, ///< Input emulation Tty, ///< Debug output from emu diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 91694cfafc..27fe773b60 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -161,10 +161,6 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { } std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { - if (!req) { - return std::chrono::microseconds{0}; - } - const auto start = std::chrono::high_resolution_clock::now(); // Whatever the game is rendering show splash if it is active @@ -207,6 +203,11 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { return std::chrono::duration_cast(end - start); } +void VideoOutDriver::DrawBlankFrame() { + const auto empty_frame = renderer->PrepareBlankFrame(false); + renderer->Present(empty_frame); +} + bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { { @@ -283,7 +284,14 @@ void VideoOutDriver::PresentThread(std::stop_token token) { auto& vblank_status = main_port.vblank_status; if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); - delay = Flip(request); + if (!request) { + delay = std::chrono::microseconds{0}; + if (!main_port.is_open) { + DrawBlankFrame(); + } + } else { + delay = Flip(request); + } FRAME_END; } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 6fc74e012d..141294bfd5 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -102,6 +102,7 @@ class VideoOutDriver { }; std::chrono::microseconds Flip(const Request& req); + void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h new file mode 100644 index 0000000000..4602382edb --- /dev/null +++ b/src/imgui/imgui_config.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// WARNING: All includes from this file must be relative to allow Dear_ImGui project to compile +// without having this project include paths. + +#include + +extern void assert_fail_debug_msg(const char* msg); + +#define ImDrawIdx std::uint32_t + +#define IM_STRINGIZE(x) IM_STRINGIZE2(x) +#define IM_STRINGIZE2(x) #x +#define IM_ASSERT(_EXPR) \ + ([&]() { \ + if (!(_EXPR)) [[unlikely]] { \ + assert_fail_debug_msg(#_EXPR " at " __FILE__ ":" IM_STRINGIZE(__LINE__)); \ + } \ + }()) + +#define IMGUI_USE_WCHAR32 +#define IMGUI_ENABLE_STB_TRUETYPE +#define IMGUI_DEFINE_MATH_OPERATORS + +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(float _v) : x(_v), y(_v) {} \ No newline at end of file diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h new file mode 100644 index 0000000000..a2ec7fd24d --- /dev/null +++ b/src/imgui/imgui_layer.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace ImGui { + +class Layer { +public: + virtual ~Layer() = default; + static void AddLayer(Layer* layer); + static void RemoveLayer(Layer* layer); + + virtual void Draw() = 0; + + virtual bool ShouldGrabGamepad() { + return false; + } +}; + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp new file mode 100644 index 0000000000..15226cd8c5 --- /dev/null +++ b/src/imgui/layer/video_info.cpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "video_info.h" + +void ImGui::Layers::VideoInfo::Draw() { + const ImGuiIO& io = GetIO(); + + m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show; + + if (m_show && Begin("Video Info")) { + Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + End(); + } +} diff --git a/src/imgui/layer/video_info.h b/src/imgui/layer/video_info.h new file mode 100644 index 0000000000..8eec972a82 --- /dev/null +++ b/src/imgui/layer/video_info.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "imgui/imgui_layer.h" + +namespace Vulkan { +class RendererVulkan; +} +namespace ImGui::Layers { + +class VideoInfo : public Layer { + bool m_show = false; + ::Vulkan::RendererVulkan* renderer{}; + +public: + explicit VideoInfo(::Vulkan::RendererVulkan* renderer) : renderer(renderer) {} + + void Draw() override; +}; + +} // namespace ImGui::Layers diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp new file mode 100644 index 0000000000..26b732c13f --- /dev/null +++ b/src/imgui/renderer/imgui_core.cpp @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/config.h" +#include "common/path_util.h" +#include "imgui/imgui_layer.h" +#include "imgui_core.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_vulkan.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +static void CheckVkResult(const vk::Result err) { + LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err)); +} + +static std::vector layers; + +// Update layers before rendering to allow layer changes to be applied during rendering. +// Using deque to keep the order of changes in case a Layer is removed then added again between +// frames. +static std::deque> change_layers; +static std::mutex change_layers_mutex{}; + +namespace ImGui { + +namespace Core { + +void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& window, + const u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator) { + + const auto config_path = GetUserPath(Common::FS::PathType::UserDir) / "imgui.ini"; + const auto log_path = GetUserPath(Common::FS::PathType::LogDir) / "imgui_log.txt"; + + CreateContext(); + ImGuiIO& io = GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight()); + io.IniFilename = SDL_strdup(config_path.string().c_str()); + io.LogFilename = SDL_strdup(log_path.string().c_str()); + StyleColorsDark(); + + Sdl::Init(window.GetSdlWindow()); + + const Vulkan::InitInfo vk_info{ + .instance = instance.GetInstance(), + .physical_device = instance.GetPhysicalDevice(), + .device = instance.GetDevice(), + .queue_family = instance.GetPresentQueueFamilyIndex(), + .queue = instance.GetPresentQueue(), + .image_count = image_count, + .min_allocation_size = 1024 * 1024, + .pipeline_rendering_create_info{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &surface_format, + }, + .allocator = allocator, + .check_vk_result_fn = &CheckVkResult, + }; + Vulkan::Init(vk_info); +} + +void OnResize() { + Sdl::OnResize(); +} + +void Shutdown(const vk::Device& device) { + device.waitIdle(); + + const ImGuiIO& io = GetIO(); + const auto ini_filename = (void*)io.IniFilename; + const auto log_filename = (void*)io.LogFilename; + + Vulkan::Shutdown(); + Sdl::Shutdown(); + DestroyContext(); + + SDL_free(ini_filename); + SDL_free(log_filename); +} + +bool ProcessEvent(SDL_Event* event) { + Sdl::ProcessEvent(event); + switch (event->type) { + 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; + default: + return false; + } +} + +void NewFrame() { + { + std::scoped_lock lock{change_layers_mutex}; + while (!change_layers.empty()) { + const auto [to_be_added, layer] = change_layers.front(); + if (to_be_added) { + layers.push_back(layer); + } else { + const auto [begin, end] = std::ranges::remove(layers, layer); + layers.erase(begin, end); + } + change_layers.pop_front(); + } + } + + 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; + } +} + +void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { + ImGui::Render(); + ImDrawData* draw_data = GetDrawData(); + if (draw_data->CmdListsCount == 0) { + return; + } + + if (Config::vkMarkersEnabled()) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "ImGui Render", + }); + } + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, {}, {}, + {vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = frame->image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }}); + + vk::RenderingAttachmentInfo color_attachments[1]{ + { + .imageView = frame->image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + }, + }; + vk::RenderingInfo render_info = {}; + render_info.renderArea = { + .offset = {0, 0}, + .extent = {frame->width, frame->height}, + }; + render_info.layerCount = 1; + render_info.colorAttachmentCount = 1; + render_info.pColorAttachments = color_attachments; + cmdbuf.beginRendering(render_info); + Vulkan::RenderDrawData(*draw_data, cmdbuf); + cmdbuf.endRendering(); + if (Config::vkMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } +} + +} // namespace Core + +void Layer::AddLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + change_layers.emplace_back(true, layer); +} + +void Layer::RemoveLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + change_layers.emplace_back(false, layer); +} + +} // namespace ImGui diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h new file mode 100644 index 0000000000..9ad708f818 --- /dev/null +++ b/src/imgui/renderer/imgui_core.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "vulkan/vulkan_handles.hpp" + +union SDL_Event; + +namespace Vulkan { +struct Frame; +} + +namespace ImGui::Core { + +void Initialize(const Vulkan::Instance& instance, const Frontend::WindowSDL& window, + u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator = nullptr); + +void OnResize(); + +void Shutdown(const vk::Device& device); + +bool ProcessEvent(SDL_Event* event); + +void NewFrame(); + +void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame); + +} // namespace ImGui::Core diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp new file mode 100644 index 0000000000..2a7d801e4b --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -0,0 +1,789 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.cpp from Dear ImGui repository + +#include +#include "imgui_impl_sdl3.h" + +// SDL +#include +#if defined(__APPLE__) +#include +#endif +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace ImGui::Sdl { + +// SDL Data +struct SdlData { + SDL_Window* window{}; + SDL_WindowID window_id{}; + Uint64 time{}; + const char* clipboard_text_data{}; + + // IME handling + SDL_Window* ime_window{}; + + // Mouse handling + Uint32 mouse_window_id{}; + int mouse_buttons_down{}; + SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; + SDL_Cursor* mouse_last_cursor{}; + int mouse_pending_leave_frame{}; + + // Gamepad handling + ImVector gamepads{}; + GamepadMode gamepad_mode{}; + bool want_update_gamepads_list{}; +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static SdlData* GetBackendData() { + return ImGui::GetCurrentContext() ? (SdlData*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +static const char* GetClipboardText(ImGuiContext*) { + SdlData* bd = GetBackendData(); + if (bd->clipboard_text_data) + SDL_free((void*)bd->clipboard_text_data); + const char* sdl_clipboard_text = SDL_GetClipboardText(); + bd->clipboard_text_data = sdl_clipboard_text; + return bd->clipboard_text_data; +} + +static void SetClipboardText(ImGuiContext*, const char* text) { + SDL_SetClipboardText(text); +} + +static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { + SdlData* bd = GetBackendData(); + auto window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle; + SDL_Window* window = SDL_GetWindowFromID(window_id); + if ((!data->WantVisible || bd->ime_window != window) && bd->ime_window != nullptr) { + SDL_StopTextInput(bd->ime_window); + bd->ime_window = nullptr; + } + if (data->WantVisible) { + SDL_Rect r; + r.x = (int)data->InputPos.x; + r.y = (int)data->InputPos.y; + r.w = 1; + r.h = (int)data->InputLineHeight; + SDL_SetTextInputArea(window, &r, 0); + SDL_StartTextInput(window); + bd->ime_window = window; + } +} + +static ImGuiKey KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode) { + // Keypad doesn't have individual key values in SDL3 + switch (scancode) { + case SDL_SCANCODE_KP_0: + return ImGuiKey_Keypad0; + case SDL_SCANCODE_KP_1: + return ImGuiKey_Keypad1; + case SDL_SCANCODE_KP_2: + return ImGuiKey_Keypad2; + case SDL_SCANCODE_KP_3: + return ImGuiKey_Keypad3; + case SDL_SCANCODE_KP_4: + return ImGuiKey_Keypad4; + case SDL_SCANCODE_KP_5: + return ImGuiKey_Keypad5; + case SDL_SCANCODE_KP_6: + return ImGuiKey_Keypad6; + case SDL_SCANCODE_KP_7: + return ImGuiKey_Keypad7; + case SDL_SCANCODE_KP_8: + return ImGuiKey_Keypad8; + case SDL_SCANCODE_KP_9: + return ImGuiKey_Keypad9; + case SDL_SCANCODE_KP_PERIOD: + return ImGuiKey_KeypadDecimal; + case SDL_SCANCODE_KP_DIVIDE: + return ImGuiKey_KeypadDivide; + case SDL_SCANCODE_KP_MULTIPLY: + return ImGuiKey_KeypadMultiply; + case SDL_SCANCODE_KP_MINUS: + return ImGuiKey_KeypadSubtract; + case SDL_SCANCODE_KP_PLUS: + return ImGuiKey_KeypadAdd; + case SDL_SCANCODE_KP_ENTER: + return ImGuiKey_KeypadEnter; + case SDL_SCANCODE_KP_EQUALS: + return ImGuiKey_KeypadEqual; + default: + break; + } + switch (keycode) { + case SDLK_TAB: + return ImGuiKey_Tab; + case SDLK_LEFT: + return ImGuiKey_LeftArrow; + case SDLK_RIGHT: + return ImGuiKey_RightArrow; + case SDLK_UP: + return ImGuiKey_UpArrow; + case SDLK_DOWN: + return ImGuiKey_DownArrow; + case SDLK_PAGEUP: + return ImGuiKey_PageUp; + case SDLK_PAGEDOWN: + return ImGuiKey_PageDown; + case SDLK_HOME: + return ImGuiKey_Home; + case SDLK_END: + return ImGuiKey_End; + case SDLK_INSERT: + return ImGuiKey_Insert; + case SDLK_DELETE: + return ImGuiKey_Delete; + case SDLK_BACKSPACE: + return ImGuiKey_Backspace; + case SDLK_SPACE: + return ImGuiKey_Space; + case SDLK_RETURN: + return ImGuiKey_Enter; + case SDLK_ESCAPE: + return ImGuiKey_Escape; + case SDLK_APOSTROPHE: + return ImGuiKey_Apostrophe; + case SDLK_COMMA: + return ImGuiKey_Comma; + case SDLK_MINUS: + return ImGuiKey_Minus; + case SDLK_PERIOD: + return ImGuiKey_Period; + case SDLK_SLASH: + return ImGuiKey_Slash; + case SDLK_SEMICOLON: + return ImGuiKey_Semicolon; + case SDLK_EQUALS: + return ImGuiKey_Equal; + case SDLK_LEFTBRACKET: + return ImGuiKey_LeftBracket; + case SDLK_BACKSLASH: + return ImGuiKey_Backslash; + case SDLK_RIGHTBRACKET: + return ImGuiKey_RightBracket; + case SDLK_GRAVE: + return ImGuiKey_GraveAccent; + case SDLK_CAPSLOCK: + return ImGuiKey_CapsLock; + case SDLK_SCROLLLOCK: + return ImGuiKey_ScrollLock; + case SDLK_NUMLOCKCLEAR: + return ImGuiKey_NumLock; + case SDLK_PRINTSCREEN: + return ImGuiKey_PrintScreen; + case SDLK_PAUSE: + return ImGuiKey_Pause; + case SDLK_LCTRL: + return ImGuiKey_LeftCtrl; + case SDLK_LSHIFT: + return ImGuiKey_LeftShift; + case SDLK_LALT: + return ImGuiKey_LeftAlt; + case SDLK_LGUI: + return ImGuiKey_LeftSuper; + case SDLK_RCTRL: + return ImGuiKey_RightCtrl; + case SDLK_RSHIFT: + return ImGuiKey_RightShift; + case SDLK_RALT: + return ImGuiKey_RightAlt; + case SDLK_RGUI: + return ImGuiKey_RightSuper; + case SDLK_APPLICATION: + return ImGuiKey_Menu; + case SDLK_0: + return ImGuiKey_0; + case SDLK_1: + return ImGuiKey_1; + case SDLK_2: + return ImGuiKey_2; + case SDLK_3: + return ImGuiKey_3; + case SDLK_4: + return ImGuiKey_4; + case SDLK_5: + return ImGuiKey_5; + case SDLK_6: + return ImGuiKey_6; + case SDLK_7: + return ImGuiKey_7; + case SDLK_8: + return ImGuiKey_8; + case SDLK_9: + return ImGuiKey_9; + case SDLK_A: + return ImGuiKey_A; + case SDLK_B: + return ImGuiKey_B; + case SDLK_C: + return ImGuiKey_C; + case SDLK_D: + return ImGuiKey_D; + case SDLK_E: + return ImGuiKey_E; + case SDLK_F: + return ImGuiKey_F; + case SDLK_G: + return ImGuiKey_G; + case SDLK_H: + return ImGuiKey_H; + case SDLK_I: + return ImGuiKey_I; + case SDLK_J: + return ImGuiKey_J; + case SDLK_K: + return ImGuiKey_K; + case SDLK_L: + return ImGuiKey_L; + case SDLK_M: + return ImGuiKey_M; + case SDLK_N: + return ImGuiKey_N; + case SDLK_O: + return ImGuiKey_O; + case SDLK_P: + return ImGuiKey_P; + case SDLK_Q: + return ImGuiKey_Q; + case SDLK_R: + return ImGuiKey_R; + case SDLK_S: + return ImGuiKey_S; + case SDLK_T: + return ImGuiKey_T; + case SDLK_U: + return ImGuiKey_U; + case SDLK_V: + return ImGuiKey_V; + case SDLK_W: + return ImGuiKey_W; + case SDLK_X: + return ImGuiKey_X; + case SDLK_Y: + return ImGuiKey_Y; + case SDLK_Z: + return ImGuiKey_Z; + case SDLK_F1: + return ImGuiKey_F1; + case SDLK_F2: + return ImGuiKey_F2; + case SDLK_F3: + return ImGuiKey_F3; + case SDLK_F4: + return ImGuiKey_F4; + case SDLK_F5: + return ImGuiKey_F5; + case SDLK_F6: + return ImGuiKey_F6; + case SDLK_F7: + return ImGuiKey_F7; + case SDLK_F8: + return ImGuiKey_F8; + case SDLK_F9: + return ImGuiKey_F9; + case SDLK_F10: + return ImGuiKey_F10; + case SDLK_F11: + return ImGuiKey_F11; + case SDLK_F12: + return ImGuiKey_F12; + case SDLK_F13: + return ImGuiKey_F13; + case SDLK_F14: + return ImGuiKey_F14; + case SDLK_F15: + return ImGuiKey_F15; + case SDLK_F16: + return ImGuiKey_F16; + case SDLK_F17: + return ImGuiKey_F17; + case SDLK_F18: + return ImGuiKey_F18; + case SDLK_F19: + return ImGuiKey_F19; + case SDLK_F20: + return ImGuiKey_F20; + case SDLK_F21: + return ImGuiKey_F21; + case SDLK_F22: + return ImGuiKey_F22; + case SDLK_F23: + return ImGuiKey_F23; + case SDLK_F24: + return ImGuiKey_F24; + case SDLK_AC_BACK: + return ImGuiKey_AppBack; + case SDLK_AC_FORWARD: + return ImGuiKey_AppForward; + default: + break; + } + return ImGuiKey_None; +} + +static void UpdateKeyModifiers(SDL_Keymod sdl_key_mods) { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0); +} + +static ImGuiViewport* GetViewportForWindowId(SDL_WindowID window_id) { + SdlData* bd = GetBackendData(); + return (window_id == bd->window_id) ? ImGui::GetMainViewport() : nullptr; +} + +// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to +// use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or +// clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main +// application, or clear/overwrite your copy of the keyboard data. Generally you may always pass all +// inputs to dear imgui, and hide them from your application based on those two flags. If you have +// multiple SDL events and some of them are not meant to be used by dear imgui, you may need to +// filter events based on their windowID field. +bool ProcessEvent(const SDL_Event* event) { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && + "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?"); + ImGuiIO& io = ImGui::GetIO(); + + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: { + if (GetViewportForWindowId(event->motion.windowID) == NULL) + return false; + ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); + io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + return true; + } + case SDL_EVENT_MOUSE_WHEEL: { + if (GetViewportForWindowId(event->wheel.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, + // (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY); + float wheel_x = -event->wheel.x; + float wheel_y = event->wheel.y; +#ifdef __EMSCRIPTEN__ + wheel_x /= 100.0f; +#endif + io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseWheelEvent(wheel_x, wheel_y); + return true; + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + if (GetViewportForWindowId(event->button.windowID) == NULL) + return false; + int mouse_button = -1; + if (event->button.button == SDL_BUTTON_LEFT) { + mouse_button = 0; + } + if (event->button.button == SDL_BUTTON_RIGHT) { + mouse_button = 1; + } + if (event->button.button == SDL_BUTTON_MIDDLE) { + mouse_button = 2; + } + if (event->button.button == SDL_BUTTON_X1) { + mouse_button = 3; + } + if (event->button.button == SDL_BUTTON_X2) { + mouse_button = 4; + } + if (mouse_button == -1) + break; + io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)); + bd->mouse_buttons_down = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) + ? (bd->mouse_buttons_down | (1 << mouse_button)) + : (bd->mouse_buttons_down & ~(1 << mouse_button)); + return true; + } + case SDL_EVENT_TEXT_INPUT: { + if (GetViewportForWindowId(event->text.windowID) == NULL) + return false; + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + if (GetViewportForWindowId(event->key.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event->type == + // SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event->key.key, event->key.scancode, + // event->key.mod); + UpdateKeyModifiers((SDL_Keymod)event->key.mod); + ImGuiKey key = KeyEventToImGuiKey(event->key.key, event->key.scancode); + io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN)); + io.SetKeyEventNativeData( + key, event->key.key, event->key.scancode, + event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend + // uses SDLK_*** as indices to IsKeyXXX() functions. + return true; + } + case SDL_EVENT_WINDOW_MOUSE_ENTER: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_window_id = event->window.windowID; + bd->mouse_pending_leave_frame = 0; + return true; + } + // - In some cases, when detaching a window from main viewport SDL may send + // SDL_WINDOWEVENT_ENTER one frame too late, + // causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse + // position. This is why we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See + // issue #5012 for details. + // FIXME: Unconfirmed whether this is still needed with SDL3. + case SDL_EVENT_WINDOW_MOUSE_LEAVE: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; + return true; + } + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED); + return true; + } + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: { + bd->want_update_gamepads_list = true; + return true; + } + } + return false; +} + +static void SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window) { + viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window); + viewport->PlatformHandleRaw = nullptr; +#if defined(_WIN32) && !defined(__WINRT__) + viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); +#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) + viewport->PlatformHandleRaw = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); +#endif +} + +bool Init(SDL_Window* window) { + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + + // Setup backend capabilities flags + SdlData* bd = IM_NEW(SdlData)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_sdl3_shadps4"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests + // (optional, rarely used) + + bd->window = window; + bd->window_id = SDL_GetWindowID(window); + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Platform_SetClipboardTextFn = SetClipboardText; + platform_io.Platform_GetClipboardTextFn = GetClipboardText; + platform_io.Platform_SetImeDataFn = PlatformSetImeData; + + // Gamepad handling + bd->gamepad_mode = ImGui_ImplSDL3_GamepadMode_AutoFirst; + bd->want_update_gamepads_list = true; + + // Load mouse cursors +#define CURSOR(left, right) \ + bd->mouse_cursors[ImGuiMouseCursor_##left] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_##right) + CURSOR(Arrow, DEFAULT); + CURSOR(TextInput, TEXT); + CURSOR(ResizeAll, MOVE); + CURSOR(ResizeNS, NS_RESIZE); + CURSOR(ResizeEW, EW_RESIZE); + CURSOR(ResizeNESW, NESW_RESIZE); + CURSOR(ResizeNWSE, NWSE_RESIZE); + CURSOR(Hand, POINTER); + CURSOR(NotAllowed, NOT_ALLOWED); +#undef CURSOR + + // Set platform dependent data in viewport + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + SetupPlatformHandles(main_viewport, window); + + // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't + // emit the event. Without this, when clicking to gain focus, our widgets wouldn't activate even + // though they showed as hovered. (This is unfortunately a global SDL setting, so enabling it + // might have a side-effect on your application. It is unlikely to make a difference, but if + // your app absolutely needs to ignore the initial on-focus click: you can ignore + // SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) +#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + + // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows + // (see #5710) +#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); +#endif + + return true; +} + +static void CloseGamepads(); + +void Shutdown() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + if (bd->clipboard_text_data) { + SDL_free((void*)bd->clipboard_text_data); + } + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + SDL_DestroyCursor(bd->mouse_cursors[cursor_n]); + CloseGamepads(); + + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | + ImGuiBackendFlags_HasGamepad); + IM_DELETE(bd); +} + +static void UpdateMouseData() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused + // (below) + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries + // shouldn't e.g. trigger other operations outside + SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE); + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + const bool is_app_focused = (bd->window == focused_window); + + if (is_app_focused) { + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when + // ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + SDL_WarpMouseInWindow(bd->window, io.MousePos.x, io.MousePos.y); + + // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION + // already provides this when hovered or captured) + if (bd->mouse_buttons_down == 0) { + // Single-viewport mode: mouse position in client window coordinates (io.MousePos is + // (0,0) when the mouse is on the upper-left corner of the app window) + float mouse_x_global, mouse_y_global; + int window_x, window_y; + SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); + SDL_GetWindowPosition(focused_window, &window_x, &window_y); + io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); + } + } +} + +static void UpdateMouseCursor() { + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + SdlData* bd = GetBackendData(); + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + SDL_HideCursor(); + } else { + // Show OS mouse cursor + SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] + ? bd->mouse_cursors[imgui_cursor] + : bd->mouse_cursors[ImGuiMouseCursor_Arrow]; + if (bd->mouse_last_cursor != expected_cursor) { + SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) + bd->mouse_last_cursor = expected_cursor; + } + SDL_ShowCursor(); + } +} + +static void CloseGamepads() { + SdlData* bd = GetBackendData(); + if (bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) + for (SDL_Gamepad* gamepad : bd->gamepads) + SDL_CloseGamepad(gamepad); + bd->gamepads.resize(0); +} + +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array, + int manual_gamepads_count) { + SdlData* bd = GetBackendData(); + CloseGamepads(); + if (mode == ImGui_ImplSDL3_GamepadMode_Manual) { + IM_ASSERT(manual_gamepads_array != nullptr && manual_gamepads_count > 0); + for (int n = 0; n < manual_gamepads_count; n++) + bd->gamepads.push_back(manual_gamepads_array[n]); + } else { + IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0); + bd->want_update_gamepads_list = true; + } + bd->gamepad_mode = mode; +} + +static void UpdateGamepadButton(SdlData* bd, ImGuiIO& io, ImGuiKey key, + SDL_GamepadButton button_no) { + bool merged_value = false; + for (SDL_Gamepad* gamepad : bd->gamepads) + merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0; + io.AddKeyEvent(key, merged_value); +} + +static inline float Saturate(float v) { + return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; +} +static void UpdateGamepadAnalog(SdlData* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, + float v0, float v1) { + float merged_value = 0.0f; + for (SDL_Gamepad* gamepad : bd->gamepads) { + float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0)); + if (merged_value < vn) + merged_value = vn; + } + io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value); +} + +static void UpdateGamepads() { + ImGuiIO& io = ImGui::GetIO(); + SdlData* bd = GetBackendData(); + + // Update list of gamepads to use + if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { + CloseGamepads(); + int sdl_gamepads_count = 0; + const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count); + for (int n = 0; n < sdl_gamepads_count; n++) + if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) { + bd->gamepads.push_back(gamepad); + if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst) + break; + } + bd->want_update_gamepads_list = false; + } + + // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + if (bd->gamepads.Size == 0) + return; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + // Update gamepad inputs + const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value. + UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, + SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, + SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, + SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown, + SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, + +thumb_dead_zone, +32767); +} + +void NewFrame() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens + // in VMs and Emscripten, see #6189, #6114, #3644) + static Uint64 frequency = SDL_GetPerformanceFrequency(); + Uint64 current_time = SDL_GetPerformanceCounter(); + if (current_time <= bd->time) + current_time = bd->time + 1; + io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency) + : (float)(1.0f / 60.0f); + bd->time = current_time; + + if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && + bd->mouse_buttons_down == 0) { + bd->mouse_window_id = 0; + bd->mouse_pending_leave_frame = 0; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + } + + UpdateMouseData(); + UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + UpdateGamepads(); +} + +void OnResize() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + int w, h; + int display_w, display_h; + SDL_GetWindowSize(bd->window, &w, &h); + if (SDL_GetWindowFlags(bd->window) & SDL_WINDOW_MINIMIZED) { + w = h = 0; + } + SDL_GetWindowSizeInPixels(bd->window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) { + io.DisplayFramebufferScale = {(float)display_w / (float)w, (float)display_h / (float)h}; + } +} + +} // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h new file mode 100644 index 0000000000..59b1a68567 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.h from Dear ImGui repository + +#pragma once + +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Gamepad; +typedef union SDL_Event SDL_Event; + +namespace ImGui::Sdl { + +bool Init(SDL_Window* window); +void Shutdown(); +void NewFrame(); +bool ProcessEvent(const SDL_Event* event); +void OnResize(); + +// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. +// You may override this. When using manual mode, caller is responsible for opening/closing gamepad. +enum GamepadMode { + ImGui_ImplSDL3_GamepadMode_AutoFirst, + ImGui_ImplSDL3_GamepadMode_AutoAll, + ImGui_ImplSDL3_GamepadMode_Manual +}; +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array = NULL, + int manual_gamepads_count = -1); + +}; // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp new file mode 100644 index 0000000000..2c1c135f7c --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -0,0 +1,1107 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.cpp from Dear ImGui repository + +#include +#include + +#include "imgui_impl_vulkan.h" + +#ifndef IM_MAX +#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) +#endif + +#define IDX_SIZE sizeof(ImDrawIdx) + +namespace ImGui::Vulkan { + +struct RenderBuffer { + vk::DeviceMemory buffer_memory{}; + vk::DeviceSize buffer_size{}; + vk::Buffer buffer{}; +}; + +// Reusable buffers used for rendering 1 current in-flight frame, for RenderDrawData() +struct FrameRenderBuffers { + RenderBuffer vertex; + RenderBuffer index; +}; + +// Each viewport will hold 1 WindowRenderBuffers +struct WindowRenderBuffers { + uint32_t index{}; + uint32_t count{}; + std::vector frame_render_buffers{}; +}; + +// Vulkan data +struct VkData { + const InitInfo init_info; + vk::DeviceSize buffer_memory_alignment = 256; + vk::PipelineCreateFlags pipeline_create_flags{}; + vk::DescriptorPool descriptor_pool{}; + vk::DescriptorSetLayout descriptor_set_layout{}; + vk::PipelineLayout pipeline_layout{}; + vk::Pipeline pipeline{}; + vk::ShaderModule shader_module_vert{}; + vk::ShaderModule shader_module_frag{}; + + // 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 + WindowRenderBuffers render_buffers{}; + + VkData(const InitInfo init_info) : init_info(init_info) { + render_buffers.count = init_info.image_count; + render_buffers.frame_render_buffers.resize(render_buffers.count); + } +}; + +//----------------------------------------------------------------------------- +// SHADERS +//----------------------------------------------------------------------------- + +// backends/vulkan/glsl_shader.vert, compiled with: +// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +/* +#version 450 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); +} +*/ +static uint32_t glsl_shader_vert_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000f, 0x00000015, + 0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43, + 0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f, + 0x00040005, 0x0000000f, 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005, + 0x00000019, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000019, 0x00000000, + 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, 0x00040005, 0x0000001c, + 0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074, + 0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001, + 0x61725475, 0x616c736e, 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b, + 0x0000001e, 0x00000000, 0x00040047, 0x0000000f, 0x0000001e, 0x00000002, 0x00040047, 0x00000015, + 0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, 0x00000000, 0x00030047, + 0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008, + 0x00030047, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, + 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040017, + 0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, 0x00000008, 0x00040020, + 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015, + 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020, + 0x0000000e, 0x00000001, 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020, + 0x00000011, 0x00000003, 0x00000007, 0x0004002b, 0x0000000c, 0x00000013, 0x00000001, 0x00040020, + 0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040020, + 0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a, + 0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014, + 0x0000001c, 0x00000001, 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f, + 0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, 0x00000020, 0x00000009, 0x00040020, 0x00000021, + 0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006, + 0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012, + 0x0000000b, 0x0000000d, 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016, + 0x00000015, 0x00050041, 0x00000017, 0x00000018, 0x0000000b, 0x00000013, 0x0003003e, 0x00000018, + 0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, 0x00000021, 0x00000022, + 0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008, + 0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013, + 0x0004003d, 0x00000008, 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024, + 0x00000026, 0x00050051, 0x00000006, 0x0000002a, 0x00000027, 0x00000000, 0x00050051, 0x00000006, + 0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, 0x0000002a, 0x0000002b, + 0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e, + 0x0000002d, 0x0000002c, 0x000100fd, 0x00010038}; + +// backends/vulkan/glsl_shader.frag, compiled with: +// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 450 core +layout(location = 0) out vec4 fColor; +layout(set=0, binding=0) uniform sampler2D sTexture; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +void main() +{ + fColor = In.Color * texture(sTexture, In.UV.st); +} +*/ +static uint32_t glsl_shader_frag_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000d, 0x00030010, + 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000, + 0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001, + 0x00005655, 0x00030005, 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574, + 0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d, 0x0000001e, + 0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x00000021, + 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, + 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, + 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006, + 0x00000002, 0x0004001e, 0x0000000b, 0x00000007, 0x0000000a, 0x00040020, 0x0000000c, 0x00000001, + 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, + 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, + 0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000000, 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000, + 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x0000000e, 0x00000018, + 0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, 0x00000002, 0x00000004, + 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, + 0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, + 0x00000016, 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a, + 0x0000001b, 0x0000001a, 0x00050057, 0x00000007, 0x0000001c, 0x00000017, 0x0000001b, 0x00050085, + 0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x000100fd, + 0x00010038}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static VkData* GetBackendData() { + return ImGui::GetCurrentContext() ? (VkData*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +static uint32_t FindMemoryType(vk::MemoryPropertyFlags properties, uint32_t type_bits) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + const auto prop = v.physical_device.getMemoryProperties(); + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) + return i; + return 0xFFFFFFFF; // Unable to find memoryType +} + +template +static T CheckVkResult(vk::ResultValue res) { + if (res.result == vk::Result::eSuccess) { + return res.value; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return res.value; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res.result); + } + return res.value; +} + +static void CheckVkErr(vk::Result res) { + if (res == vk::Result::eSuccess) { + return; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res); + } +} + +// Same as IM_MEMALIGN(). 'alignment' must be a power of two. +static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +// Register a texture +vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, + vk::ImageLayout image_layout) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + // Create Descriptor Set: + vk::DescriptorSet descriptor_set; + { + vk::DescriptorSetAllocateInfo alloc_info{ + .sType = vk::StructureType::eDescriptorSetAllocateInfo, + .descriptorPool = bd->descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = &bd->descriptor_set_layout, + }; + descriptor_set = CheckVkResult(v.device.allocateDescriptorSets(alloc_info)).front(); + } + + // Update the Descriptor Set: + { + vk::DescriptorImageInfo desc_image[1]{ + { + .sampler = sampler, + .imageView = image_view, + .imageLayout = image_layout, + }, + }; + vk::WriteDescriptorSet write_desc[1]{ + { + .sType = vk::StructureType::eWriteDescriptorSet, + .dstSet = descriptor_set, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = desc_image, + }, + }; + v.device.updateDescriptorSets({write_desc}, {}); + } + return descriptor_set; +} + +void RemoveTexture(vk::DescriptorSet descriptor_set) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set}); +} + +static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr); + const InitInfo& v = bd->init_info; + if (rb.buffer != VK_NULL_HANDLE) { + v.device.destroyBuffer(rb.buffer, v.allocator); + } + if (rb.buffer_memory != VK_NULL_HANDLE) { + v.device.freeMemory(rb.buffer_memory, v.allocator); + } + + const vk::DeviceSize buffer_size_aligned = + AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment); + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = buffer_size_aligned, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + rb.buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + + const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = req.size, + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + rb.buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + + CheckVkErr(v.device.bindBufferMemory(rb.buffer, rb.buffer_memory, 0)); + rb.buffer_size = buffer_size_aligned; +} + +static void SetupRenderState(ImDrawData& draw_data, vk::Pipeline pipeline, vk::CommandBuffer cmdbuf, + FrameRenderBuffers& frb, int fb_width, int fb_height) { + VkData* bd = GetBackendData(); + + // Bind pipeline: + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // Bind Vertex And Index Buffer: + if (draw_data.TotalVtxCount > 0) { + vk::Buffer vertex_buffers[1] = {frb.vertex.buffer}; + vk::DeviceSize vertex_offset[1] = {0}; + cmdbuf.bindVertexBuffers(0, {vertex_buffers}, vertex_offset); + cmdbuf.bindIndexBuffer(frb.index.buffer, 0, + IDX_SIZE == 2 ? vk::IndexType::eUint16 : vk::IndexType::eUint32); + } + + // Setup viewport: + { + vk::Viewport viewport{ + .x = 0, + .y = 0, + .width = (float)fb_width, + .height = (float)fb_height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + cmdbuf.setViewport(0, {viewport}); + } + + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single + // viewport apps. + { + float scale[2]; + scale[0] = 2.0f / draw_data.DisplaySize.x; + scale[1] = 2.0f / draw_data.DisplaySize.y; + float translate[2]; + translate[0] = -1.0f - draw_data.DisplayPos.x * scale[0]; + translate[1] = -1.0f - draw_data.DisplayPos.y * scale[1]; + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 0, sizeof(float) * 2, scale); + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 2, sizeof(float) * 2, translate); + } +} + +// Render function +void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, + vk::Pipeline pipeline) { + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != + // framebuffer coordinates) + int fb_width = (int)(draw_data.DisplaySize.x * draw_data.FramebufferScale.x); + int fb_height = (int)(draw_data.DisplaySize.y * draw_data.FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) { + return; + } + + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + if (pipeline == VK_NULL_HANDLE) { + pipeline = bd->pipeline; + } + + // Allocate array to store enough vertex/index buffers + WindowRenderBuffers& wrb = bd->render_buffers; + wrb.index = (wrb.index + 1) % wrb.count; + FrameRenderBuffers& frb = wrb.frame_render_buffers[wrb.index]; + + if (draw_data.TotalVtxCount > 0) { + // Create or resize the vertex/index buffers + size_t vertex_size = AlignBufferSize(draw_data.TotalVtxCount * sizeof(ImDrawVert), + bd->buffer_memory_alignment); + size_t index_size = + AlignBufferSize(draw_data.TotalIdxCount * IDX_SIZE, bd->buffer_memory_alignment); + if (frb.vertex.buffer == VK_NULL_HANDLE || frb.vertex.buffer_size < vertex_size) { + CreateOrResizeBuffer(frb.vertex, vertex_size, vk::BufferUsageFlagBits::eVertexBuffer); + } + if (frb.index.buffer == VK_NULL_HANDLE || frb.index.buffer_size < index_size) { + CreateOrResizeBuffer(frb.index, index_size, vk::BufferUsageFlagBits::eIndexBuffer); + } + + // Upload vertex/index data into a single contiguous GPU buffer + ImDrawVert* vtx_dst = nullptr; + ImDrawIdx* idx_dst = nullptr; + vtx_dst = (ImDrawVert*)CheckVkResult( + v.device.mapMemory(frb.vertex.buffer_memory, 0, vertex_size, vk::MemoryMapFlags{})); + idx_dst = (ImDrawIdx*)CheckVkResult( + v.device.mapMemory(frb.index.buffer_memory, 0, index_size, vk::MemoryMapFlags{})); + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, + cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * IDX_SIZE); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + vk::MappedMemoryRange range[2]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.vertex.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.index.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(frb.vertex.buffer_memory); + v.device.unmapMemory(frb.index.buffer_memory); + } + + // Setup desired Vulkan state + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + + // (0,0) unless using multi-viewports + ImVec2 clip_off = draw_data.DisplayPos; + // (1,1) unless using retina display which are often (2,2) + ImVec2 clip_scale = draw_data.FramebufferScale; + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_vtx_offset = 0; + int global_idx_offset = 0; + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to + // request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) { + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + } else { + pcmd->UserCallback(cmd_list, pcmd); + } + } else { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as vk::CmdSetScissor() won't accept values that are off bounds + if (clip_min.x < 0.0f) { + clip_min.x = 0.0f; + } + if (clip_min.y < 0.0f) { + clip_min.y = 0.0f; + } + if (clip_max.x > fb_width) { + clip_max.x = (float)fb_width; + } + if (clip_max.y > fb_height) { + clip_max.y = (float)fb_height; + } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle + vk::Rect2D scissor{ + .offset{ + .x = (int32_t)(clip_min.x), + .y = (int32_t)(clip_min.y), + }, + .extent{ + .width = (uint32_t)(clip_max.x - clip_min.x), + .height = (uint32_t)(clip_max.y - clip_min.y), + }, + }; + command_buffer.setScissor(0, 1, &scissor); + + // Bind DescriptorSet with font or user texture + vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId}; + if (sizeof(ImTextureID) < sizeof(ImU64)) { + // We don't support texture switches if ImTextureID hasn't been redefined to be + // 64-bit. Do a flaky check that other textures haven't been used. + IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set); + desc_set[0] = bd->font_descriptor_set; + } + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + bd->pipeline_layout, 0, {desc_set}, {}); + + // Draw + command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, + pcmd->VtxOffset + global_vtx_offset, 0); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + // vk::Rect2D scissor = {{0, 0}, {(uint32_t)fb_width, (uint32_t)fb_height}}; + // command_buffer.setScissor(0, 1, &scissor); +} + +static void DestroyFontsTexture(); + +static bool CreateFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + // Destroy existing texture (if any) + if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) { + CheckVkErr(v.queue.waitIdle()); + 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)); + } + if (bd->font_command_buffer == VK_NULL_HANDLE) { + vk::CommandBufferAllocateInfo info{ + .sType = vk::StructureType::eCommandBufferAllocateInfo, + .commandPool = bd->font_command_pool, + .commandBufferCount = 1, + }; + bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); + } + + // Start command buffer + { + CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{})); + vk::CommandBufferBeginInfo begin_info{}; + begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; + begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + CheckVkErr(bd->font_command_buffer.begin(&begin_info)); + } + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + size_t upload_size = width * height * 4 * sizeof(char); + + // Create the Image: + { + vk::ImageCreateInfo info{ + .sType = vk::StructureType::eImageCreateInfo, + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .extent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator)); + vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + bd->font_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(bd->font_image, bd->font_memory, 0)); + } + + // Create the Image View: + { + vk::ImageViewCreateInfo info{ + .sType = vk::StructureType::eImageViewCreateInfo, + .image = bd->font_image, + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + bd->font_view = CheckVkResult(v.device.createImageView(info, v.allocator)); + } + + // Create the Descriptor Set: + bd->font_descriptor_set = + AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create the Upload Buffer: + vk::DeviceMemory upload_buffer_memory{}; + vk::Buffer upload_buffer{}; + { + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = upload_size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(upload_buffer, upload_buffer_memory, 0)); + } + + // Upload to Buffer: + { + char* map = (char*)CheckVkResult(v.device.mapMemory(upload_buffer_memory, 0, upload_size)); + memcpy(map, pixels, upload_size); + vk::MappedMemoryRange range[1]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = upload_buffer_memory, + .size = upload_size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(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 = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + }; + bd->font_command_buffer.copyBufferToImage(upload_buffer, bd->font_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 = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, + {}, {use_barrier}); + } + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)bd->font_descriptor_set); + + // End command buffer + vk::SubmitInfo end_info = {}; + end_info.sType = vk::StructureType::eSubmitInfo; + end_info.commandBufferCount = 1; + end_info.pCommandBuffers = &bd->font_command_buffer; + CheckVkErr(bd->font_command_buffer.end()); + CheckVkErr(v.queue.submit({end_info})); + + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + + return true; +} + +// You probably never need to call this, as it is called by CreateFontsTexture() +// and Shutdown(). +static void DestroyFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (bd->font_descriptor_set) { + RemoveTexture(bd->font_descriptor_set); + bd->font_descriptor_set = VK_NULL_HANDLE; + io.Fonts->SetTexID(nullptr); + } + + if (bd->font_view) { + v.device.destroyImageView(bd->font_view, v.allocator); + bd->font_view = VK_NULL_HANDLE; + } + if (bd->font_image) { + v.device.destroyImage(bd->font_image, v.allocator); + bd->font_image = VK_NULL_HANDLE; + } + if (bd->font_memory) { + v.device.freeMemory(bd->font_memory, v.allocator); + bd->font_memory = VK_NULL_HANDLE; + } +} + +static void DestroyFrameRenderBuffers(vk::Device device, RenderBuffer& rb, + const vk::AllocationCallbacks* allocator) { + if (rb.buffer) { + device.destroyBuffer(rb.buffer, allocator); + rb.buffer = VK_NULL_HANDLE; + } + if (rb.buffer_memory) { + device.freeMemory(rb.buffer_memory, allocator); + rb.buffer_memory = VK_NULL_HANDLE; + } + rb.buffer_size = 0; +} + +static void DestroyWindowRenderBuffers(vk::Device device, WindowRenderBuffers& buffers, + const vk::AllocationCallbacks* allocator) { + for (uint32_t n = 0; n < buffers.count; n++) { + auto& frb = buffers.frame_render_buffers[n]; + DestroyFrameRenderBuffers(device, frb.index, allocator); + DestroyFrameRenderBuffers(device, frb.vertex, allocator); + } + buffers = {}; +} + +static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks* allocator) { + // Create the shader modules + VkData* bd = GetBackendData(); + if (bd->shader_module_vert == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo vert_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_vert_spv), + .pCode = (uint32_t*)glsl_shader_vert_spv, + }; + bd->shader_module_vert = CheckVkResult(device.createShaderModule(vert_info, allocator)); + } + if (bd->shader_module_frag == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo frag_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_frag_spv), + .pCode = (uint32_t*)glsl_shader_frag_spv, + }; + bd->shader_module_frag = CheckVkResult(device.createShaderModule(frag_info, allocator)); + } +} + +static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* allocator, + vk::PipelineCache pipeline_cache, vk::RenderPass render_pass, + vk::Pipeline* pipeline, uint32_t subpass) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CreateShaderModules(device, allocator); + + vk::PipelineShaderStageCreateInfo stage[2]{ + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eVertex, + .module = bd->shader_module_vert, + .pName = "main", + }, + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eFragment, + .module = bd->shader_module_frag, + .pName = "main", + }, + }; + + vk::VertexInputBindingDescription binding_desc[1]{ + { + .stride = sizeof(ImDrawVert), + .inputRate = vk::VertexInputRate::eVertex, + }, + }; + + vk::VertexInputAttributeDescription attribute_desc[3]{ + { + .location = 0, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, pos), + }, + { + .location = 1, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, uv), + }, + { + .location = 2, + .binding = binding_desc[0].binding, + .format = vk::Format::eR8G8B8A8Unorm, + .offset = offsetof(ImDrawVert, col), + }, + }; + + vk::PipelineVertexInputStateCreateInfo vertex_info{ + .sType = vk::StructureType::ePipelineVertexInputStateCreateInfo, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = binding_desc, + .vertexAttributeDescriptionCount = 3, + .pVertexAttributeDescriptions = attribute_desc, + }; + + vk::PipelineInputAssemblyStateCreateInfo ia_info{ + .sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo, + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + vk::PipelineViewportStateCreateInfo viewport_info{ + .sType = vk::StructureType::ePipelineViewportStateCreateInfo, + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo raster_info{ + .sType = vk::StructureType::ePipelineRasterizationStateCreateInfo, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eNone, + .frontFace = vk::FrontFace::eCounterClockwise, + .lineWidth = 1.0f, + }; + + vk::PipelineMultisampleStateCreateInfo ms_info{ + .sType = vk::StructureType::ePipelineMultisampleStateCreateInfo, + .rasterizationSamples = vk::SampleCountFlagBits::e1, + }; + + vk::PipelineColorBlendAttachmentState color_attachment[1]{ + { + .blendEnable = VK_TRUE, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOne, + .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }, + }; + + vk::PipelineDepthStencilStateCreateInfo depth_info{ + .sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo, + }; + + vk::PipelineColorBlendStateCreateInfo blend_info{ + .sType = vk::StructureType::ePipelineColorBlendStateCreateInfo, + .attachmentCount = 1, + .pAttachments = color_attachment, + }; + + vk::DynamicState dynamic_states[2]{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamic_state{ + .sType = vk::StructureType::ePipelineDynamicStateCreateInfo, + .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), + .pDynamicStates = dynamic_states, + }; + + vk::GraphicsPipelineCreateInfo info{ + .sType = vk::StructureType::eGraphicsPipelineCreateInfo, + .pNext = &v.pipeline_rendering_create_info, + .flags = bd->pipeline_create_flags, + .stageCount = 2, + .pStages = stage, + .pVertexInputState = &vertex_info, + .pInputAssemblyState = &ia_info, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_info, + .pMultisampleState = &ms_info, + .pDepthStencilState = &depth_info, + .pColorBlendState = &blend_info, + .pDynamicState = &dynamic_state, + .layout = bd->pipeline_layout, + .renderPass = render_pass, + .subpass = subpass, + }; + + *pipeline = + CheckVkResult(device.createGraphicsPipelines(pipeline_cache, {info}, allocator)).front(); +} + +bool CreateDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + vk::Result err; + + if (!bd->descriptor_pool) { + // large enough descriptor pool + vk::DescriptorPoolSize pool_sizes[]{ + {vk::DescriptorType::eSampler, 1000}, + {vk::DescriptorType::eCombinedImageSampler, 1000}, + {vk::DescriptorType::eSampledImage, 1000}, + {vk::DescriptorType::eStorageImage, 1000}, + {vk::DescriptorType::eUniformTexelBuffer, 1000}, + {vk::DescriptorType::eStorageTexelBuffer, 1000}, + {vk::DescriptorType::eUniformBuffer, 1000}, + {vk::DescriptorType::eStorageBuffer, 1000}, + {vk::DescriptorType::eUniformBufferDynamic, 1000}, + {vk::DescriptorType::eStorageBufferDynamic, 1000}, + {vk::DescriptorType::eInputAttachment, 1000}, + }; + + vk::DescriptorPoolCreateInfo pool_info{ + .sType = vk::StructureType::eDescriptorPoolCreateInfo, + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1000, + .poolSizeCount = std::size(pool_sizes), + .pPoolSizes = pool_sizes, + }; + + 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]{ + { + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }, + }; + vk::DescriptorSetLayoutCreateInfo info{ + .sType = vk::StructureType::eDescriptorSetLayoutCreateInfo, + .bindingCount = 1, + .pBindings = binding, + }; + bd->descriptor_set_layout = + CheckVkResult(v.device.createDescriptorSetLayout(info, v.allocator)); + } + + if (!bd->pipeline_layout) { + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection + // matrix + vk::PushConstantRange push_constants[1]{ + { + .stageFlags = vk::ShaderStageFlagBits::eVertex, + .offset = sizeof(float) * 0, + .size = sizeof(float) * 4, + }, + }; + vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout}; + vk::PipelineLayoutCreateInfo layout_info{ + .sType = vk::StructureType::ePipelineLayoutCreateInfo, + .setLayoutCount = 1, + .pSetLayouts = set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = push_constants, + }; + bd->pipeline_layout = + CheckVkResult(v.device.createPipelineLayout(layout_info, v.allocator)); + } + + CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + + return true; +} + +void ImGuiImplVulkanDestroyDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + DestroyWindowRenderBuffers(v.device, bd->render_buffers, v.allocator); + DestroyFontsTexture(); + + if (bd->font_command_buffer) { + v.device.freeCommandBuffers(bd->font_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->shader_module_vert) { + v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); + bd->shader_module_vert = VK_NULL_HANDLE; + } + if (bd->shader_module_frag) { + 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->descriptor_set_layout) { + v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); + bd->descriptor_set_layout = VK_NULL_HANDLE; + } + if (bd->pipeline_layout) { + v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator); + bd->pipeline_layout = VK_NULL_HANDLE; + } + if (bd->pipeline) { + v.device.destroyPipeline(bd->pipeline, v.allocator); + bd->pipeline = VK_NULL_HANDLE; + } +} + +bool Init(InitInfo info) { + + IM_ASSERT(info.instance != VK_NULL_HANDLE); + IM_ASSERT(info.physical_device != VK_NULL_HANDLE); + IM_ASSERT(info.device != VK_NULL_HANDLE); + IM_ASSERT(info.image_count >= 2); + + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + auto* bd = IM_NEW(VkData)(info); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_vulkan_shadps4"; + // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + + CreateDeviceObjects(); + CreateFontsTexture(); + + return true; +} + +void Shutdown() { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGuiImplVulkanDestroyDeviceObjects(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + 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 new file mode 100644 index 0000000000..e68b8723f5 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.h from Dear ImGui repository + +#pragma once + +#define VULKAN_HPP_NO_EXCEPTIONS +#include "video_core/renderer_vulkan/vk_common.h" + +struct ImDrawData; + +namespace ImGui::Vulkan { + +struct InitInfo { + vk::Instance instance; + vk::PhysicalDevice physical_device; + vk::Device device; + uint32_t queue_family; + vk::Queue queue; + uint32_t image_count; // >= 2 + vk::DeviceSize min_allocation_size; // Minimum allocation size + vk::PipelineCache pipeline_cache; + uint32_t subpass; + vk::PipelineRenderingCreateInfoKHR pipeline_rendering_create_info; + + // (Optional) Allocation, Logging + const vk::AllocationCallbacks* allocator{}; + void (*check_vk_result_fn)(vk::Result err); +}; + +vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, + vk::ImageLayout image_layout); + +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); + +} // namespace ImGui::Vulkan \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 31460d07ce..f3418c8f90 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -9,6 +9,7 @@ #include "common/config.h" #include "common/version.h" #include "core/libraries/pad/pad.h" +#include "imgui/renderer/imgui_core.h" #include "input/controller.h" #include "sdl_window.h" #include "video_core/renderdoc.h" @@ -80,6 +81,10 @@ void WindowSDL::waitEvent() { return; } + if (ImGui::Core::ProcessEvent(&event)) { + return; + } + switch (event.type) { case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_MAXIMIZED: @@ -115,6 +120,7 @@ void WindowSDL::waitEvent() { void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); + ImGui::Core::OnResize(); } void WindowSDL::onKeyPress(const SDL_Event* event) { diff --git a/src/sdl_window.h b/src/sdl_window.h index 11ee92896c..2a5aeb38c0 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -58,6 +58,10 @@ class WindowSDL { return is_open; } + [[nodiscard]] SDL_Window* GetSdlWindow() const { + return window; + } + WindowSystemInfo getWindowInfo() const { return window_info; } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index f1c81b6e26..6416acfb53 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -6,6 +6,7 @@ #include "common/singleton.h" #include "core/file_format/splash.h" #include "core/libraries/system/systemservice.h" +#include "imgui/renderer/imgui_core.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" @@ -73,7 +74,7 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, rasterizer{std::make_unique(instance, draw_scheduler, liverpool)}, - texture_cache{rasterizer->GetTextureCache()} { + texture_cache{rasterizer->GetTextureCache()}, video_info_ui{this} { const u32 num_images = swapchain.GetImageCount(); const vk::Device device = instance.GetDevice(); @@ -84,9 +85,14 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); free_queue.push(&frame); } + + // Setup ImGui + ImGui::Core::Initialize(instance, window, num_images, swapchain.GetSurfaceFormat().format); + ImGui::Layer::AddLayer(&video_info_ui); } RendererVulkan::~RendererVulkan() { + ImGui::Layer::RemoveLayer(&video_info_ui); draw_scheduler.Finish(); const vk::Device device = instance.GetDevice(); for (auto& frame : present_frames) { @@ -94,6 +100,7 @@ RendererVulkan::~RendererVulkan() { device.destroyImageView(frame.image_view); device.destroyFence(frame.present_done); } + ImGui::Core::Shutdown(device); } void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) { @@ -254,6 +261,8 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop } void RendererVulkan::Present(Frame* frame) { + ImGui::Core::NewFrame(); + swapchain.AcquireNextImage(); const vk::Image swapchain_image = swapchain.Image(); @@ -286,7 +295,7 @@ void RendererVulkan::Present(Frame* frame) { vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, - .oldLayout = vk::ImageLayout::eGeneral, + .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -317,6 +326,8 @@ void RendererVulkan::Present(Frame* frame) { }, }; + ImGui::Core::Render(cmdbuf, frame); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index eab9d527cd..c8e566418f 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -4,6 +4,8 @@ #pragma once #include + +#include "imgui/layer/video_info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -103,6 +105,8 @@ class RendererVulkan { std::condition_variable_any frame_cv; std::optional splash_img; std::vector vo_buffers_addr; + + ImGui::Layers::VideoInfo video_info_ui; }; } // namespace Vulkan From 446d8c4ff12a09b65c4689ea698ab7579c2e9e40 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 8 Sep 2024 17:27:50 -0300 Subject: [PATCH 4/7] Message Dialog library (#767) * system/MsgDialog: types & basic text display * system/MsgDialog: User message dialog * system/MsgDialog: RAII for MsgDialog ui * system/MsgDialog: Progress bar dialog * system/MsgDialog: System message texts * system/MsgDialog: copy all ui state to local memory handles when game release memory before close extracted all UI code to it's own file use single window instead of creating new one every single dialogOpen * system/MsgDialog: debug logging --- CMakeLists.txt | 3 + src/common/fixed_value.h | 35 +++ src/core/libraries/system/commondialog.cpp | 20 +- src/core/libraries/system/commondialog.h | 43 +++- src/core/libraries/system/msgdialog.cpp | 176 +++++++++---- src/core/libraries/system/msgdialog.h | 107 ++------ src/core/libraries/system/msgdialog_ui.cpp | 273 +++++++++++++++++++++ src/core/libraries/system/msgdialog_ui.h | 177 +++++++++++++ src/imgui/imgui_std.h | 27 ++ 9 files changed, 712 insertions(+), 149 deletions(-) create mode 100644 src/common/fixed_value.h create mode 100644 src/core/libraries/system/msgdialog_ui.cpp create mode 100644 src/core/libraries/system/msgdialog_ui.h create mode 100644 src/imgui/imgui_std.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4987b96e16..d1413b1569 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/commondialog.h src/core/libraries/system/msgdialog.cpp src/core/libraries/system/msgdialog.h + src/core/libraries/system/msgdialog_ui.cpp src/core/libraries/system/posix.cpp src/core/libraries/system/posix.h src/core/libraries/save_data/error_codes.h @@ -325,6 +326,7 @@ set(COMMON src/common/logging/backend.cpp src/common/error.cpp src/common/error.h src/common/scope_exit.h + src/common/fixed_value.h src/common/func_traits.h src/common/native_clock.cpp src/common/native_clock.h @@ -563,6 +565,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/layer/video_info.cpp src/imgui/layer/video_info.h src/imgui/renderer/imgui_core.cpp diff --git a/src/common/fixed_value.h b/src/common/fixed_value.h new file mode 100644 index 0000000000..e32a795f22 --- /dev/null +++ b/src/common/fixed_value.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/** + * @brief A template class that encapsulates a fixed, compile-time constant value. + * + * @tparam T The type of the value. + * @tparam Value The fixed value of type T. + * + * This class provides a way to encapsulate a value that is constant and known at compile-time. + * The value is stored as a private member and cannot be changed. Any attempt to assign a new + * value to an object of this class will reset it to the fixed value. + */ +template +class FixedValue { + T m_value{Value}; + +public: + constexpr FixedValue() = default; + + constexpr explicit(false) operator T() const { + return m_value; + } + + FixedValue& operator=(const T&) { + m_value = Value; + return *this; + } + FixedValue& operator=(T&&) noexcept { + m_value = {Value}; + return *this; + } +}; diff --git a/src/core/libraries/system/commondialog.cpp b/src/core/libraries/system/commondialog.cpp index e32e3bb3f1..cb9ce0442d 100644 --- a/src/core/libraries/system/commondialog.cpp +++ b/src/core/libraries/system/commondialog.cpp @@ -8,6 +8,9 @@ namespace Libraries::CommonDialog { +bool g_isInitialized = false; +bool g_isUsed = false; + int PS4_SYSV_ABI _ZN3sce16CommonDialogUtil12getSelfAppIdEv() { LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); return ORBIS_OK; @@ -83,14 +86,19 @@ int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE() { return ORBIS_OK; } -int PS4_SYSV_ABI sceCommonDialogInitialize() { - LOG_ERROR(Lib_CommonDlg, "(DUMMY) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceCommonDialogInitialize() { + if (g_isInitialized) { + LOG_INFO(Lib_CommonDlg, "already initialized"); + return Error::ALREADY_SYSTEM_INITIALIZED; + } + LOG_DEBUG(Lib_CommonDlg, "initialized"); + g_isInitialized = true; + return Error::OK; } -int PS4_SYSV_ABI sceCommonDialogIsUsed() { - LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); - return ORBIS_OK; +bool PS4_SYSV_ABI sceCommonDialogIsUsed() { + LOG_TRACE(Lib_CommonDlg, "called"); + return g_isUsed; } int PS4_SYSV_ABI Func_0FF577E4E8457883() { diff --git a/src/core/libraries/system/commondialog.h b/src/core/libraries/system/commondialog.h index 110e9cc99e..6b8ea3d953 100644 --- a/src/core/libraries/system/commondialog.h +++ b/src/core/libraries/system/commondialog.h @@ -11,9 +11,44 @@ class SymbolsResolver; namespace Libraries::CommonDialog { -struct OrbisCommonDialogBaseParam { +enum class Status : u32 { + NONE = 0, + INITIALIZED = 1, + RUNNING = 2, + FINISHED = 3, +}; + +enum class Result : u32 { + OK = 0, + USER_CANCELED = 1, +}; + +enum class Error : u32 { + OK = 0, + NOT_SYSTEM_INITIALIZED = 0x80B80001, + ALREADY_SYSTEM_INITIALIZED = 0x80B80002, + NOT_INITIALIZED = 0x80B80003, + ALREADY_INITIALIZED = 0x80B80004, + NOT_FINISHED = 0x80B80005, + INVALID_STATE = 0x80B80006, + RESULT_NONE = 0x80B80007, + BUSY = 0x80B80008, + OUT_OF_MEMORY = 0x80B80009, + PARAM_INVALID = 0x80B8000A, + NOT_RUNNING = 0x80B8000B, + ALREADY_CLOSE = 0x80B8000C, + ARG_NULL = 0x80B8000D, + UNEXPECTED_FATAL = 0x80B8000E, + NOT_SUPPORTED = 0x80B8000F, + INHIBIT_SHAREPLAY_CLIENT = 0x80B80010, +}; + +extern bool g_isInitialized; +extern bool g_isUsed; + +struct BaseParam { std::size_t size; - u8 reserved[36]; + std::array reserved; u32 magic; }; @@ -32,8 +67,8 @@ int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8getAppIdEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8isFinishEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client9getResultEv(); int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE(); -int PS4_SYSV_ABI sceCommonDialogInitialize(); -int PS4_SYSV_ABI sceCommonDialogIsUsed(); +Error PS4_SYSV_ABI sceCommonDialogInitialize(); +bool PS4_SYSV_ABI sceCommonDialogIsUsed(); int PS4_SYSV_ABI Func_0FF577E4E8457883(); int PS4_SYSV_ABI Func_41716C2CE379416C(); int PS4_SYSV_ABI Func_483A427D8F6E0748(); diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index 452feec934..94c122d9bb 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -1,79 +1,157 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/assert.h" #include "common/logging/log.h" -#include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/system/msgdialog.h" - -#include +#include "imgui_internal.h" +#include "msgdialog_ui.h" namespace Libraries::MsgDialog { -int PS4_SYSV_ABI sceMsgDialogClose() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; + +static auto g_status = Status::NONE; +static MsgDialogState g_state{}; +static DialogResult g_result{}; +static MsgDialogUi g_msg_dialog_ui; + +Error PS4_SYSV_ABI sceMsgDialogClose() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + g_msg_dialog_ui.Finish(ButtonId::INVALID); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogGetResult() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + for (const auto v : result->reserved) { + if (v != 0) { + return Error::PARAM_INVALID; + } + } + *result = g_result; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogGetStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Status PS4_SYSV_ABI sceMsgDialogGetStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } -int PS4_SYSV_ABI sceMsgDialogInitialize() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogInitialize() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; } -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param) { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - switch (param->mode) { - case ORBIS_MSG_DIALOG_MODE_USER_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen userMsg type = %s msg = %s", - magic_enum::enum_name(param->userMsgParam->buttonType), param->userMsgParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen progressBar type = %s msg = %s", - magic_enum::enum_name(param->progBarParam->barType), param->progBarParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen systemMsg type: %s", - magic_enum::enum_name(param->sysMsgParam->sysMsgType)); - break; - default: - break; - } - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_MsgDlg, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_MsgDlg, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_MsgDlg, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = MsgDialogState{*param}; + g_status = Status::RUNNING; + g_msg_dialog_ui = MsgDialogUi(&g_state, &g_status, &g_result); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarInc() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget target, u32 delta) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget target, + const char* msg) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().msg = msg; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget target, + u32 value) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = value; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogTerminate() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogTerminate() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status == Status::RUNNING) { + sceMsgDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogUpdateStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Status PS4_SYSV_ABI sceMsgDialogUpdateStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/system/msgdialog.h b/src/core/libraries/system/msgdialog.h index 28d379138c..b8a1f3f079 100644 --- a/src/core/libraries/system/msgdialog.h +++ b/src/core/libraries/system/msgdialog.h @@ -3,7 +3,6 @@ #pragma once -#include "common/types.h" #include "core/libraries/system/commondialog.h" namespace Core::Loader { @@ -12,95 +11,23 @@ class SymbolsResolver; namespace Libraries::MsgDialog { -using OrbisUserServiceUserId = s32; - -enum OrbisCommonDialogStatus { - ORBIS_COMMON_DIALOG_STATUS_NONE = 0, - ORBIS_COMMON_DIALOG_STATUS_INITIALIZED = 1, - ORBIS_COMMON_DIALOG_STATUS_RUNNING = 2, - ORBIS_COMMON_DIALOG_STATUS_FINISHED = 3 -}; - -enum OrbisMsgDialogMode { - ORBIS_MSG_DIALOG_MODE_USER_MSG = 1, - ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR = 2, - ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG = 3, -}; - -enum OrbisMsgDialogButtonType { - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK = 0, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO = 1, - ORBIS_MSG_DIALOG_BUTTON_TYPE_NONE = 2, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL = 3, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT = 5, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT_CANCEL = 6, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO = 7, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL_FOCUS_CANCEL = 8, - ORBIS_MSG_DIALOG_BUTTON_TYPE_2BUTTONS = 9, -}; - -enum OrbisMsgDialogProgressBarType { - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE = 0, - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE_CANCEL = 1, -}; - -enum OrbisMsgDialogSystemMessageType { - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_EMPTY_STORE = 0, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION = 1, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_UGC_RESTRICTION = 2, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_CAMERA_NOT_CONNECTED = 4, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, -}; - -struct OrbisMsgDialogButtonsParam { - const char* msg1; - const char* msg2; - char reserved[32]; -}; - -struct OrbisMsgDialogUserMessageParam { - OrbisMsgDialogButtonType buttonType; - s32 : 32; - const char* msg; - OrbisMsgDialogButtonsParam* buttonsParam; - char reserved[24]; -}; - -struct OrbisMsgDialogProgressBarParam { - OrbisMsgDialogProgressBarType barType; - int32_t : 32; - const char* msg; - char reserved[64]; -}; - -struct OrbisMsgDialogSystemMessageParam { - OrbisMsgDialogSystemMessageType sysMsgType; - char reserved[32]; -}; - -struct OrbisMsgDialogParam { - CommonDialog::OrbisCommonDialogBaseParam baseParam; - std::size_t size; - OrbisMsgDialogMode mode; - s32 : 32; - OrbisMsgDialogUserMessageParam* userMsgParam; - OrbisMsgDialogProgressBarParam* progBarParam; - OrbisMsgDialogSystemMessageParam* sysMsgParam; - OrbisUserServiceUserId userId; - char reserved[40]; - s32 : 32; -}; - -int PS4_SYSV_ABI sceMsgDialogClose(); -int PS4_SYSV_ABI sceMsgDialogGetResult(); -int PS4_SYSV_ABI sceMsgDialogGetStatus(); -int PS4_SYSV_ABI sceMsgDialogInitialize(); -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param); -int PS4_SYSV_ABI sceMsgDialogProgressBarInc(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceMsgDialogTerminate(); -int PS4_SYSV_ABI sceMsgDialogUpdateStatus(); +struct DialogResult; +struct OrbisParam; +enum class OrbisMsgDialogProgressBarTarget : u32; + +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogInitialize(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget, + u32 delta); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget, + const char* msg); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget, + u32 value); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogUpdateStatus(); void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::MsgDialog diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp new file mode 100644 index 0000000000..d69754fe2b --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "imgui/imgui_std.h" +#include "msgdialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; +using namespace Libraries::MsgDialog; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +struct { + int count = 0; + const char* text1; + const char* text2; +} static constexpr user_button_texts[] = { + {1, "OK"}, // 0 OK + {2, "Yes", "No"}, // 1 YESNO + {0}, // 2 NONE + {2, "OK", "Cancel"}, // 3 OK_CANCEL + {}, // 4 !!NOP + {1, "Wait"}, // 5 WAIT + {2, "Wait", "Cancel"}, // 6 WAIT_CANCEL + {2, "Yes", "No"}, // 7 YESNO_FOCUS_NO + {2, "OK", "Cancel"}, // 8 OK_CANCEL_FOCUS_CANCEL + {0xFF}, // 9 TWO_BUTTONS +}; +static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); + +static void DrawCenteredText(const char* text) { + const auto ws = GetWindowSize(); + const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f); + PushTextWrapPos(ws.x - 30.0f); + SetCursorPos({ + (ws.x - text_size.x) / 2.0f, + (ws.y - text_size.y) / 2.0f - 50.0f, + }); + Text("%s", text); + PopTextWrapPos(); +} + +MsgDialogState::MsgDialogState(const OrbisParam& param) { + this->mode = param.mode; + switch (mode) { + case MsgDialogMode::USER_MSG: { + ASSERT(param.userMsgParam); + const auto& v = *param.userMsgParam; + auto state = UserState{ + .type = v.buttonType, + .msg = std::string(v.msg), + }; + if (v.buttonType == ButtonType::TWO_BUTTONS) { + ASSERT(v.buttonsParam); + state.btn_param1 = std::string(v.buttonsParam->msg1); + state.btn_param2 = std::string(v.buttonsParam->msg2); + } + this->state = state; + } break; + case MsgDialogMode::PROGRESS_BAR: { + ASSERT(param.progBarParam); + const auto& v = *param.progBarParam; + this->state = ProgressState{ + .type = v.barType, + .msg = std::string(v.msg), + .progress = 0, + }; + } break; + case MsgDialogMode::SYSTEM_MSG: { + ASSERT(param.sysMsgParam); + const auto& v = *param.sysMsgParam; + this->state = SystemState{ + .type = v.sysMsgType, + }; + } break; + default: + UNREACHABLE_MSG("Unknown dialog mode"); + } +} + +void MsgDialogUi::DrawUser() { + const auto& [button_type, msg, btn_param1, btn_param2] = + state->GetState(); + const auto ws = GetWindowSize(); + DrawCenteredText(msg.c_str()); + ASSERT(button_type <= ButtonType::TWO_BUTTONS); + auto [count, text1, text2] = user_button_texts[static_cast(button_type)]; + if (count == 0xFF) { // TWO_BUTTONS -> User defined message + count = 2; + text1 = btn_param1.c_str(); + text2 = btn_param2.c_str(); + } + const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO; + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + BeginGroup(); + if (count > 0) { + // First button at the right, so we render the second button first + if (count == 2) { + PushID(2); + if (Button(text2, BUTTON_SIZE)) { + switch (button_type) { + case ButtonType::OK_CANCEL: + case ButtonType::WAIT_CANCEL: + case ButtonType::OK_CANCEL_FOCUS_CANCEL: + Finish(ButtonId::INVALID, Result::USER_CANCELED); + break; + default: + Finish(ButtonId::BUTTON2); + break; + } + } + if (first_render && !focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + PushID(1); + if (Button(text1, BUTTON_SIZE)) { + Finish(ButtonId::BUTTON1); + } + if (first_render && focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + EndGroup(); +} + +void MsgDialogUi::DrawProgressBar() { + const auto& [bar_type, msg, progress_bar_value] = + state->GetState(); + DrawCenteredText(msg.c_str()); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + const bool has_cancel = bar_type == ProgressBarType::PERCENTAGE_CANCEL; + float bar_width = PROGRESS_BAR_WIDTH * ws.x; + if (has_cancel) { + bar_width -= BUTTON_SIZE.x - 10.0f; + } + BeginGroup(); + ProgressBar(static_cast(progress_bar_value) / 100.0f, {bar_width, BUTTON_SIZE.y}); + if (has_cancel) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (first_render) { + SetItemCurrentNavFocus(); + } + } + EndGroup(); +} + +struct { + const char* text; +} static constexpr system_message_texts[] = { + "No product available in the store.", // TRC_EMPTY_STORE + "PSN chat restriction.", // TRC_PSN_CHAT_RESTRICTION + "User-generated Media restriction", // TRC_PSN_UGC_RESTRICTION + nullptr, // !!NOP + "Camera not connected.", // CAMERA_NOT_CONNECTED + "Warning: profile picture and name are not set", // WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED +}; +static_assert(std::size(system_message_texts) == + static_cast(SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED) + 1); + +void MsgDialogUi::DrawSystemMessage() { + // TODO: Implement go to settings & user profile + const auto& [msg_type] = state->GetState(); + ASSERT(msg_type <= SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED); + auto [msg] = system_message_texts[static_cast(msg_type)]; + DrawCenteredText(msg); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - 10.0f - BUTTON_SIZE.y, + }); + if (Button("OK", BUTTON_SIZE)) { + Finish(ButtonId::OK); + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +MsgDialogUi::MsgDialogUi(MsgDialogState* state, Status* status, DialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} +MsgDialogUi::~MsgDialogUi() { + Finish(ButtonId::INVALID); +} +MsgDialogUi::MsgDialogUi(MsgDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; +} +MsgDialogUi& MsgDialogUi::operator=(MsgDialogUi other) { + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void MsgDialogUi::Finish(ButtonId buttonId, Result r) { + if (result) { + result->result = r; + result->buttonId = buttonId; + } + if (status) { + *status = Status::FINISHED; + } + state = nullptr; + status = nullptr; + result = nullptr; + RemoveLayer(this); +} + +void MsgDialogUi::Draw() { + if (status == nullptr || *status != Status::RUNNING) { + return; + } + const auto& io = GetIO(); + + const ImVec2 window_size{ + std::min(io.DisplaySize.x, 500.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowFocus(); + SetNextWindowCollapsed(false); + KeepNavHighlight(); + // Hack to allow every dialog to have a unique window + if (Begin("Message Dialog##MessageDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + switch (state->GetMode()) { + case MsgDialogMode::USER_MSG: + DrawUser(); + break; + case MsgDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + case MsgDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + } + End(); + } + + first_render = false; +} \ No newline at end of file diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h new file mode 100644 index 0000000000..845abdc43f --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.h @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/fixed_value.h" +#include "common/types.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::MsgDialog { + +using OrbisUserServiceUserId = s32; + +enum class MsgDialogMode : u32 { + USER_MSG = 1, + PROGRESS_BAR = 2, + SYSTEM_MSG = 3, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, + BUTTON1 = 1, + BUTTON2 = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + OK_CANCEL = 3, + WAIT = 5, + WAIT_CANCEL = 6, + YESNO_FOCUS_NO = 7, + OK_CANCEL_FOCUS_CANCEL = 8, + TWO_BUTTONS = 9, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, + PERCENTAGE_CANCEL = 1, +}; + +enum class SystemMessageType : u32 { + TRC_EMPTY_STORE = 0, + TRC_PSN_CHAT_RESTRICTION = 1, + TRC_PSN_UGC_RESTRICTION = 2, + CAMERA_NOT_CONNECTED = 4, + WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, +}; + +enum class OrbisMsgDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct ButtonsParam { + const char* msg1{}; + const char* msg2{}; + std::array reserved{}; +}; + +struct UserMessageParam { + ButtonType buttonType{}; + s32 : 32; + const char* msg{}; + ButtonsParam* buttonsParam{}; + std::array reserved{}; +}; + +struct ProgressBarParam { + ProgressBarType barType{}; + s32 : 32; + const char* msg{}; + std::array reserved{}; +}; + +struct SystemMessageParam { + SystemMessageType sysMsgType{}; + std::array reserved{}; +}; + +struct OrbisParam { + CommonDialog::BaseParam baseParam; + std::size_t size; + MsgDialogMode mode; + s32 : 32; + UserMessageParam* userMsgParam; + ProgressBarParam* progBarParam; + SystemMessageParam* sysMsgParam; + OrbisUserServiceUserId userId; + std::array reserved; + s32 : 32; +}; + +struct DialogResult { + FixedValue mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId buttonId{ButtonId::INVALID}; + std::array reserved{}; +}; + +// State is used to copy all the data from the param struct +class MsgDialogState { +public: + struct UserState { + ButtonType type{}; + std::string msg{}; + std::string btn_param1{}; + std::string btn_param2{}; + }; + struct ProgressState { + ProgressBarType type{}; + std::string msg{}; + u32 progress{}; + }; + struct SystemState { + SystemMessageType type{}; + }; + +private: + OrbisUserServiceUserId user_id{}; + MsgDialogMode mode{}; + std::variant state{std::monostate{}}; + +public: + explicit MsgDialogState(const OrbisParam& param); + MsgDialogState() = default; + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const { + return user_id; + } + + [[nodiscard]] MsgDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class MsgDialogUi final : public ImGui::Layer { + bool first_render{false}; + MsgDialogState* state{}; + CommonDialog::Status* status{}; + DialogResult* result{}; + + void DrawUser(); + void DrawProgressBar(); + void DrawSystemMessage(); + +public: + explicit MsgDialogUi(MsgDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + DialogResult* result = nullptr); + ~MsgDialogUi() override; + MsgDialogUi(const MsgDialogUi& other) = delete; + MsgDialogUi(MsgDialogUi&& other) noexcept; + MsgDialogUi& operator=(MsgDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void SetProgressBarValue(u32 value, bool increment); + + void Draw() override; + + bool ShouldGrabGamepad() override { + return true; + } +}; + +}; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h new file mode 100644 index 0000000000..6d97cc11bc --- /dev/null +++ b/src/imgui/imgui_std.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "imgui_internal.h" + +namespace ImGui { + +inline void CentralizeWindow() { + const auto display_size = GetIO().DisplaySize; + SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); +} + +inline void KeepNavHighlight() { + GetCurrentContext()->NavDisableHighlight = false; +} + +inline void SetItemCurrentNavFocus() { + const auto ctx = GetCurrentContext(); + SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow); + ctx->NavInitResult.Clear(); +} + +} // namespace ImGui From 9101bd7ad45d6b259dfae7938d60fe7619568bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:39:48 +0200 Subject: [PATCH 5/7] Improved Dark theme, search bar and icon theme (#830) * Improved Dark theme, search bar and icon theme * Update FR translation --- src/images/themes_icon.png | Bin 3395 -> 1413 bytes src/qt_gui/main_window_themes.cpp | 33 +++++++++++++++--------------- src/qt_gui/main_window_ui.h | 2 +- src/qt_gui/translations/fr.ts | 32 ++++++++++++++--------------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/images/themes_icon.png b/src/images/themes_icon.png index 822ef3af0c61e8ec9f0e13e93634273901fa06ed..cc711011e53f54f032735b712d3485f2513cfb91 100644 GIT binary patch literal 1413 zcmai!{Xf$Q0LQ;OHf!_zkQq5NytN8Tx-o^S_&tO$m5*y zSn|9pQPk#X(@mJC45#F9UZ;oCA8@bx;r)5P|AJ3C#l>C$hJ*nCpy230cH43Acc74+ zjhe}I-_fop2cJs-09X4C2q@qn003;|NG7^p=ByMsruo^bryOfnQ+5_t!&T&T8YyN> zW?BZKN5a4>A3A_c*Y<*;(w+|Dm{61h6hS=0D=I0`W6COoKT=SvPE)ys%w_LYg#Qi| zI~E#L2)7curn`JST3jZ4kBwGpew;iywS68RSEJiZ9pUZcy=n+R-|P<5fyi!IlZIqS z=ITjUG-*ig|H5278T((*ltO6wnIfA-S=J|mRoH6w>_Q@pLp~9x^JK~?59m^SC;wA} z;E!og{Y?Md*ya_HRk6;Pw_%Y!cK62IJ--n@KekllB$%Mjmc%;our!#p z6$&rMNM&&K1?>WhEIZ<24r}P)GGm38%JY{v)8SEX0Q>c&FTimmD5Om(6HF)ZLj9GY zc5BobK7+dwN%!F`uq_lH_ZpRnv~2YZ`+)9I0BvL#;w5R~#RvqG6t&FYwz_C9Npfga zhr5Nc(1lx2Sg;|@kx^k($7Y<5tXY-hRNSFgU&^Q>2Tns@0EFRq!>*<$$p$%f1~Ta| zj619|iqU8b$}*(bdl?*YrJqKBBv?q2 z>j12QT97BG@Q2RrYS|ji3B`plXw@m2ZzgiFOnfB-4f2QI>PR669C1gxIElCIdZtxKA%qoNIIqWOqa(-i%R!T z>}OLf4}$IdFjlIFeplXeOib)jAi}Q>U|x>tU*vUKUMNP(|4Eaa0xS279KzUDR3x+( zFDqjxNRYwVMpx_j01wzR;gT8nx~5zeu2<`3n>}%`;sF&opP7{!R1T@a@wLQG7lY-i zaKl=5ULZLQSz7P8_CnY!yTF8krkv<1ENE_zpoQP>DaCxu{OfvxhOC?TL#76=Okwc> zIwZ8VK7X){zv7rO6tM8aDGdI`-MxgMVgJcB8VM5NsF6ar((D>7@)YA30R%d!G7KAO z-}Tul<=_yJrVs< zZ;1)mPiTDmTQ=eAXR?=;G|`xk71xyfCf3D{#j8bGrb|`9VOb=ePlHUUEmEXCenyUX z)07I#QsVo%#a!U!le7Q_4;r^;PiU_qYxkQRc`>I`Gr09a3vksMbmdUIGH$WyF7~yD zR;T&3@daqmWO2*OMtHqB(SnaC53|=hyKObF^x-a1+f(J}JTR+tm3?Gj$w%op`x4Md z$lj`(G}XdW2eA&z#?RhrN_&gTJuL5>%RsV!jPi385+~+z8-HH^f&g)vWVUiHEbGwk z1?}=lT2G#p=Q@qMuBCd?xGmu+fw1MZr)K2B^Q=#(sP+21SN|;Ro3pv$U2hA0Ps^xP zJ=it{VKq$Yh72G{%Q6i78MTzN<2aKT1(9j{bnd*juuC|ka(LyD-adX$ASbr!2q8E5*cb5QdGBnO#BKGRUj-a(UC57Y H=*)isKd*71 literal 3395 zcmZ{nXHXN0vVid@)ga2HOOb#`SA!710HT3F5DfhU2{nWRf^?CX018Ss)PvF?1Va(2 zA#^E*5{f_o>6dl@BOuZP=;h9tdGqd@cYo~eY?;~J*+1XLAk1LAJYqa-Y;3&7Mo^1W zn|dl(E{;=;^vBAbnlk|wu)A#K1LA9^&N+8|Q++nJO2YYLXZBN{+t&ygz{Yl=2v4f z8XKAI3l!?Ng^fA2bQgE5XG3YjgCN`BT<{@ie~;mS-qh%9Vy83J=46`~n zR97$pO`EV+VA(M={!C$61*ZHFD83!`%SO?F%weqnS0}^GOZ2T{EqF^TQaurA1y&Iy z0;CW6CrYemox-3vn){FOnO1AeRvnp6eL)xu5s~PMR;6w)-p56|!p7q_gtHYgUbP)M z2m0>Bty0ZN*?I=znS|E^8_z|>g_>_+VSy07zK@+-QLEA2A>sgz-KuP<+w%z$A}EM< z3}?~DJRFx4YtS6!us^6rJX=7AUEBT$2o=PuvqBke_75)JY8yj;D06;vm(il<|!H!|GnJ0K5OrvzFv~`+pg}B2s4qO zkO-Hs);Q`>vkgfieWxqNOgu=`R+rQYT?j+bMz>q^8r*hZe1MnWfWd4j_l0{$6~b|k z1+uj>UM;@l$`Lxz;O8u>kr4+LVVSEtl5d_T(w`9r4N$bS%GlBt9aa=4t&^9y2?by> z`po8{8_r!)|UB=V^pcvF4Gp=kxpz*oO|E{L+w6fASz^mU>jD{z_k^vFoq3g6jrV!jlf} z02ZP~o;l`PeF$1tR~Xe{U>BO+|CsK1LWKt+0@qi6Y1uPf5dGkQGUcP@6S?7V^n_74 zw(mWut)AolsMkS;*KFjE6oC&Q4VN9GizwU|< z@C$PmlZog(S$7p#z0KPAV%<_3Hc?osXj#jduJ|I>vg0A#VjlU0*Q5qXPG%?^*9Rh0teifo`KE z0KMQ*wl@-8@ED6qXgqd4CB5Nqc6{YZW$>_Y54Y;UR)q-ScI9& zQ9ZjUV(PYgX8nj%DNPYwclk&g+-^j-5IGH_d7A@Bi45xkY)gRQ)e6Yu>n2duzP9IS zqtJGa-KC@yFg@>BZwA}|Wz-#ge+W$QYCMNakUD{2b2$womzk+ah81Lq`JUknq>g2VxiONYj>CgmYvK zOu8ln&hl8;yy)v56ZjCwA(iomZ>NN#X0FL|xK4%6Hde z5x=rZG=C(Vu6^(Qb&ZKEg4vI=K(n50=w1eCX!+#__#!Sp?_^e}P>g|f9&|~;6IP{% zI)FO4n$W25sGRZXAA85EoI_S6`s?&Bw9c1_ZtK9Ib(x!#6@$7MR-_>dcz*Zus>2Xd zW?<~?RJ14J*&>f&w7arz$c;iy*ff7eo!?6UFF_}m?d%x2tnJ`FNsKZ3y_9RVKvU8Kw)gUk#2e{PWvki-{4!Hfu+02>#_(1j|p>7Mu13ZL*b(^LcEq zpdx~~Fp=s1T8@wjn>43~@#iE()q*s7^3IjzO1|egvNaDoDx~bg1`{%FMIxy_+U_%o4;{9pV}`+CcAu2xH^Sn8@A*Ry3Mj6PM*O)DvKo}=WC?&9 zwk4XiA&pMvpKnw}Zt1D#_&TT68G3j1C-jg>h6x03d4&Tak7001ry7Hb?Mw>zsg0&JRdvM54d%kTU z3FVhTYUCp9QpeCC(fT8yFxrx*dr2fHZDO}#HJnR0b`LyKk4;-_?$vQ|AM42zgXL$` z(?gut<#DNra{hW+y7*7zC5XChQ7ll7;2bC!oHGV&^UVgpvzwKGyY8iTR+oyBu(gz} zw=nPSRwaUZ8PrQxzXZHw-@I3CpA{Zb1h6A`0-kHOVz6P z=RA_ms5ZuXI$Vg~L3r|aH074d>+$m^Sv+c8;GySqrgw{cSyGb)-xX;01=cEf`A21p zZ?juzUU9(i*#a9ok}P|Mpd zCo1rvnqT(vSIT~Cw7^{u!?(^c2FfP>fLDFsw;q{9X#PruH-`tDB*m%^$0|)ZNIuED6(%eZm@|%}2{Lcrl=j8V zU7vD7uuK(K9B3BfS1QU_!Iui($+s`vOg{?TG*TwiWy6}S;P=fbZF#DKYcCp=BN0z~ zg`aP}eup~B;e+}&FqYJxyB90|cyHKMbUF1Oi&HaJ@c>@F`X`!@@A#x-;CK(9aIIv* z=wUX-quQP@tJSc^LF#GLoB$*@8sm)=g11zM zj#Et4xOq~O(MHpkYrjVAMuJVB95$$GEh}PFq*$Cr!p@Lbxj?C{**>mgOOX1>qt>hV z&UFU*%t0BgE4_T8RdY?F1CxIp>JuCIraIV8;8`;^^F+Zei1J*{0SCrAYxI0en`2N$(W0G^XNQTq&$$JKMaa@RvJ7eAfF`?V{h6n|GHlz;K6ssBw^Dg;L& z!Cf&ayd}nK*}`FuLR=v^amP*F0goY>p=q{A(Es8-xH(Hq!1m6ZX>n_DU(p~{`$?)W zCjee$Bh1b1%duB)8o9DE;)k0GuvzD#)iJ3*I5oQJbmj^RA^Rae>$iSuszy0qfh-Nn zV`gY=;Aj}qGT50n0^tFZI@{(v(60(cFqn6a5wZnzDoF7V`IxKla=+6Ja>zTgFfjF# z?Vj_jpL;mG3vB?P0W*Ji-Y-23)Y$=AHtjm|6w~nk4ZSBN|6KU1n4?xlnUwKrwD;2H zZ|_9FtxcX`YNfW6MfIFu4;{xs0TDB%VG#Y{mv=(DJ)ZFa8BonWL@% diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index c89fa5a009..35e64ef741 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -8,13 +8,13 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { switch (theme) { case Theme::Dark: - mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); - themePalette.setColor(QPalette::Window, QColor(53, 53, 53)); + themePalette.setColor(QPalette::Window, QColor(50, 50, 50)); themePalette.setColor(QPalette::WindowText, Qt::white); - themePalette.setColor(QPalette::Base, QColor(25, 25, 25)); + themePalette.setColor(QPalette::Base, QColor(20, 20, 20)); themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25)); themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); themePalette.setColor(QPalette::ToolTipBase, Qt::white); @@ -30,8 +30,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Light: - mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */" - "color: #000000; /* Black text */" + mw_searchbar->setStyleSheet("background-color: #ffffff;" // Light gray background + "color: #000000;" // Black text + "border: 2px solid #000000;" // Black border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray themePalette.setColor(QPalette::WindowText, Qt::black); // Black @@ -49,9 +50,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Green: - mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -72,9 +73,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Blue: - mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -95,9 +96,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Violet: - mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background themePalette.setColor(QPalette::WindowText, Qt::white); // White text diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 8ae5965f8a..6ddc4155e9 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -267,8 +267,8 @@ class Ui_MainWindow { menuView->addAction(menuGame_List_Mode->menuAction()); menuView->addAction(menuGame_List_Icons->menuAction()); menuView->addAction(menuThemes->menuAction()); - menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeDark); + menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeGreen); menuThemes->addAction(setThemeBlue); menuThemes->addAction(setThemeViolet); diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 33e2990c09..5ba5e7e2e1 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -21,7 +21,7 @@ This software should not be used to play games you have not legally obtained. - Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. + Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. @@ -60,7 +60,7 @@ Directory to install games - Répertoire d'installation des jeux + Répertoire d'installation des jeux @@ -75,7 +75,7 @@ The value for location to install games is not valid. - Le répertoire d'installation des jeux n'est pas valide. + Le répertoire d'installation des jeux n'est pas valide. @@ -118,7 +118,7 @@ Copy Serial - Copier le numéro de série + Copier le N° de série @@ -201,7 +201,7 @@ Exit the application. - Fermer l'application. + Fermer l'application. @@ -291,7 +291,7 @@ Game List Mode - Mode d'affichage + Mode d'affichage @@ -301,7 +301,7 @@ Utils - Utilitaire + Utilitaires @@ -316,12 +316,12 @@ Dark - Noir + Sombre Light - Blanc + Clair @@ -341,7 +341,7 @@ toolBar - Bare d'outils + Bare d'outils @@ -385,7 +385,7 @@ Emulator Language - Langage de l'émulateur + Langage de l'émulateur @@ -400,7 +400,7 @@ Show Splash - Afficher l'image du jeu + Afficher l'image du jeu @@ -410,7 +410,7 @@ Username - Nom d'utilisateur + Nom d'utilisateur @@ -538,7 +538,7 @@ All Patches available for all games have been downloaded. - Tous les patchs disponibles pour les jeux ont été téléchargés. + Tous les patchs disponibles ont été téléchargés. @@ -906,7 +906,7 @@ Can't apply cheats before the game is started - Impossible d'appliquer les triches avant que le jeu ne commence. + Impossible d'appliquer les Cheats avant que le jeu ne commence. @@ -972,7 +972,7 @@ Path - Chemin + Répertoire \ No newline at end of file From 96f7a75f38d5febe2c3ae785cfe4014727958bd4 Mon Sep 17 00:00:00 2001 From: Zack McKevitt <49414389+zmckevitt@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:30:18 -0600 Subject: [PATCH 6/7] Redefined ffmpeg's av_err2str macro to be c++ friendly (#815) * Fixed compiler error for av_err2string by redefining in c++ friendly way * removed link from comment, putting in PR * fixed formatting * Minor fix: enable qt gui to find PKG files with lowercase extension .pkg * Added missing dependencies and instructions for enabling QT for linux builds --- documents/building-linux.md | 13 ++++++++----- src/core/libraries/avplayer/avplayer_source.cpp | 11 +++++++++++ src/qt_gui/main_window.cpp | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index 9645d8b4fc..622de543b7 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later #### Debian & Ubuntu ``` -sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev +sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev ``` #### Fedora @@ -34,9 +34,9 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git cd shadPS4 ``` -Generate the build directory in the shadPS4 directory: +Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag: ``` -cmake -S . -B build/ +cmake -S . -B build/ -DENABLE_QT_GUI=ON ``` Enter the directory: @@ -49,8 +49,11 @@ Use make to build the project: cmake --build . --parallel$(nproc) ``` -Now run the emulator: - +Now run the emulator. If QT is enabled: +``` +./shadps4 +``` +Otherwise, specify the path to your PKG's boot file: ``` ./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin ``` diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 99ba2e8b65..603d550144 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -20,6 +20,17 @@ extern "C" { #include } +// The av_err2str macro in libavutil/error.h does not play nice with C++ +#ifdef av_err2str +#undef av_err2str +#include +av_always_inline std::string av_err2string(int errnum) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum); +} +#define av_err2str(err) av_err2string(err).c_str() +#endif // av_err2str + namespace Libraries::AvPlayer { using namespace Kernel; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index bd2f097eac..e5b502c588 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -577,7 +577,7 @@ void MainWindow::SaveWindowState() const { void MainWindow::InstallPkg() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFiles); - dialog.setNameFilter(tr("PKG File (*.PKG)")); + dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)")); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); int nPkg = fileNames.size(); @@ -949,4 +949,4 @@ void MainWindow::OnLanguageChanged(const std::string& locale) { Config::setEmulatorLanguage(locale); LoadTranslation(); -} \ No newline at end of file +} From e3c2a914773b54b03e51f33fad33d23c753efdf0 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:48:00 +0300 Subject: [PATCH 7/7] kernel: Delete cond attr May solve memory leaks in games that create/destroy these often --- src/core/libraries/kernel/thread_management.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 8f97ed8795..80328dc116 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1182,6 +1182,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) { int result = pthread_condattr_destroy(&(*attr)->cond_attr); LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result); + delete *attr; switch (result) { case 0: