From df2a5a5e67539d0e84c47df5549e33110214ae2c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 19 Apr 2025 15:59:18 +1000 Subject: [PATCH] ImGuiManager: Move drawing out of GPUDevice --- src/core/gpu_presenter.cpp | 8 +- src/core/gpu_thread.cpp | 2 +- src/util/gpu_device.cpp | 284 ++++++---------------------------- src/util/gpu_device.h | 13 +- src/util/imgui_fullscreen.cpp | 6 +- src/util/imgui_manager.cpp | 252 ++++++++++++++++++++++++++---- src/util/imgui_manager.h | 14 +- 7 files changed, 295 insertions(+), 284 deletions(-) diff --git a/src/core/gpu_presenter.cpp b/src/core/gpu_presenter.cpp index 35c2b6b0b..5bf830f22 100644 --- a/src/core/gpu_presenter.cpp +++ b/src/core/gpu_presenter.cpp @@ -1032,6 +1032,8 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo // acquire for IO.MousePos and system state. std::atomic_thread_fence(std::memory_order_acquire); + ImGuiManager::RenderDebugWindows(); + FullscreenUI::Render(); if (backend) @@ -1046,7 +1048,7 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo if (backend && !GPUThread::IsSystemPaused()) ImGuiManager::RenderSoftwareCursors(); - ImGuiManager::RenderDebugWindows(); + ImGuiManager::CreateDrawLists(); // render offscreen for transitions if (FullscreenUI::IsTransitionActive()) @@ -1060,7 +1062,7 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo g_gpu_device->ClearRenderTarget(rtex, GPUDevice::DEFAULT_CLEAR_COLOR); g_gpu_device->SetRenderTarget(rtex); - g_gpu_device->RenderImGui(rtex); + ImGuiManager::RenderDrawLists(rtex); } } } @@ -1079,7 +1081,7 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo if (FullscreenUI::IsTransitionActive()) FullscreenUI::RenderTransitionBlend(swap_chain); else - g_gpu_device->RenderImGui(swap_chain); + ImGuiManager::RenderDrawLists(swap_chain); const GPUDevice::Features features = g_gpu_device->GetFeatures(); const bool scheduled_present = (present_time != 0); diff --git a/src/core/gpu_thread.cpp b/src/core/gpu_thread.cpp index 0902c516d..2896b8a8c 100644 --- a/src/core/gpu_thread.cpp +++ b/src/core/gpu_thread.cpp @@ -1273,7 +1273,7 @@ void GPUThread::DisplayWindowResizedOnThread() // our imgui stuff can't cope with 0x0/hidden windows const float f_width = static_cast(std::max(swap_chain->GetWidth(), 1u)); const float f_height = static_cast(std::max(swap_chain->GetHeight(), 1u)); - ImGuiManager::WindowResized(f_width, f_height); + ImGuiManager::WindowResized(swap_chain->GetFormat(), f_width, f_height); InputManager::SetDisplayWindowSize(f_width, f_height); if (s_state.gpu_backend) diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 7686a96ac..a5dd5211c 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -19,7 +19,6 @@ #include "common/timer.h" #include "fmt/format.h" -#include "imgui.h" #include "shaderc/shaderc.h" #include "spirv_cross_c.h" #include "xxhash.h" @@ -669,57 +668,6 @@ bool GPUDevice::CreateResources(Error* error) } GL_OBJECT_NAME(m_nearest_sampler, "Nearest Sampler"); GL_OBJECT_NAME(m_linear_sampler, "Nearest Sampler"); - - const RenderAPI render_api = GetRenderAPI(); - ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), m_features.dual_source_blend, - m_features.framebuffer_fetch); - - std::unique_ptr imgui_vs = - CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GenerateImGuiVertexShader(), error); - std::unique_ptr imgui_fs = - CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateImGuiFragmentShader(), error); - if (!imgui_vs || !imgui_fs) - { - Error::AddPrefix(error, "Failed to compile ImGui shaders: "); - return false; - } - GL_OBJECT_NAME(imgui_vs, "ImGui Vertex Shader"); - GL_OBJECT_NAME(imgui_fs, "ImGui Fragment Shader"); - - static constexpr GPUPipeline::VertexAttribute imgui_attributes[] = { - GPUPipeline::VertexAttribute::Make(0, GPUPipeline::VertexAttribute::Semantic::Position, 0, - GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ImDrawVert, pos)), - GPUPipeline::VertexAttribute::Make(1, GPUPipeline::VertexAttribute::Semantic::TexCoord, 0, - GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ImDrawVert, uv)), - GPUPipeline::VertexAttribute::Make(2, GPUPipeline::VertexAttribute::Semantic::Color, 0, - GPUPipeline::VertexAttribute::Type::UNorm8, 4, OFFSETOF(ImDrawVert, col)), - }; - - GPUPipeline::GraphicsConfig plconfig; - plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants; - plconfig.input_layout.vertex_attributes = imgui_attributes; - plconfig.input_layout.vertex_stride = sizeof(ImDrawVert); - plconfig.primitive = GPUPipeline::Primitive::Triangles; - plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState(); - plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); - plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); - plconfig.blend.write_mask = 0x7; - plconfig.SetTargetFormats(m_main_swap_chain ? m_main_swap_chain->GetFormat() : GPUTexture::Format::RGBA8); - plconfig.samples = 1; - plconfig.per_sample_shading = false; - plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; - plconfig.vertex_shader = imgui_vs.get(); - plconfig.geometry_shader = nullptr; - plconfig.fragment_shader = imgui_fs.get(); - - m_imgui_pipeline = CreatePipeline(plconfig, error); - if (!m_imgui_pipeline) - { - Error::AddPrefix(error, "Failed to compile ImGui pipeline: "); - return false; - } - GL_OBJECT_NAME(m_imgui_pipeline, "ImGui Pipeline"); - return true; } @@ -727,11 +675,6 @@ void GPUDevice::DestroyResources() { m_empty_texture.reset(); - m_imgui_font_texture.reset(); - m_imgui_pipeline.reset(); - - m_imgui_pipeline.reset(); - m_linear_sampler = nullptr; m_nearest_sampler = nullptr; m_sampler_map.clear(); @@ -739,131 +682,6 @@ void GPUDevice::DestroyResources() m_shader_cache.Close(); } -void GPUDevice::RenderImGui(GPUSwapChain* swap_chain) -{ - GL_SCOPE("RenderImGui"); - - ImGui::Render(); - - const ImDrawData* draw_data = ImGui::GetDrawData(); - if (draw_data->CmdListsCount == 0 || !swap_chain) - return; - - const s32 post_rotated_height = swap_chain->GetPostRotatedHeight(); - SetPipeline(m_imgui_pipeline.get()); - SetViewport(0, 0, swap_chain->GetPostRotatedWidth(), post_rotated_height); - - const bool prerotated = (swap_chain->GetPreRotation() != WindowInfo::PreRotation::Identity); - GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection( - 0.0f, 0.0f, static_cast(swap_chain->GetWidth()), static_cast(swap_chain->GetHeight()), 0.0f, 1.0f); - if (prerotated) - mproj = GSMatrix4x4::RotationZ(WindowInfo::GetZRotationForPreRotation(swap_chain->GetPreRotation())) * mproj; - PushUniformBuffer(&mproj, sizeof(mproj)); - - // Render command lists - const bool flip = UsesLowerLeftOrigin(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - static_assert(sizeof(ImDrawIdx) == sizeof(DrawIndex)); - - u32 base_vertex, base_index; - UploadVertexBuffer(cmd_list->VtxBuffer.Data, sizeof(ImDrawVert), cmd_list->VtxBuffer.Size, &base_vertex); - UploadIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size, &base_index); - - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - - if ((pcmd->ElemCount == 0 && !pcmd->UserCallback) || pcmd->ClipRect.z <= pcmd->ClipRect.x || - pcmd->ClipRect.w <= pcmd->ClipRect.y) - { - continue; - } - - GSVector4i clip = GSVector4i(GSVector4::load(&pcmd->ClipRect.x)); - - if (prerotated) - clip = GPUSwapChain::PreRotateClipRect(swap_chain->GetPreRotation(), swap_chain->GetSizeVec(), clip); - if (flip) - clip = FlipToLowerLeft(clip, post_rotated_height); - - SetScissor(clip); - SetTextureSampler(0, reinterpret_cast(pcmd->TextureId), m_linear_sampler); - - if (pcmd->UserCallback) [[unlikely]] - { - pcmd->UserCallback(cmd_list, pcmd); - PushUniformBuffer(&mproj, sizeof(mproj)); - SetPipeline(m_imgui_pipeline.get()); - } - else - { - DrawIndexed(pcmd->ElemCount, base_index + pcmd->IdxOffset, base_vertex + pcmd->VtxOffset); - } - } - } -} - -void GPUDevice::RenderImGui(GPUTexture* texture) -{ - GL_SCOPE("RenderImGui"); - - ImGui::Render(); - - const ImDrawData* draw_data = ImGui::GetDrawData(); - if (draw_data->CmdListsCount == 0) - return; - - SetPipeline(m_imgui_pipeline.get()); - SetViewport(0, 0, texture->GetWidth(), texture->GetHeight()); - - const GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection( - 0.0f, 0.0f, static_cast(texture->GetWidth()), static_cast(texture->GetHeight()), 0.0f, 1.0f); - PushUniformBuffer(&mproj, sizeof(mproj)); - - // Render command lists - const bool flip = UsesLowerLeftOrigin(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - static_assert(sizeof(ImDrawIdx) == sizeof(DrawIndex)); - - u32 base_vertex, base_index; - UploadVertexBuffer(cmd_list->VtxBuffer.Data, sizeof(ImDrawVert), cmd_list->VtxBuffer.Size, &base_vertex); - UploadIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size, &base_index); - - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - - if ((pcmd->ElemCount == 0 && !pcmd->UserCallback) || pcmd->ClipRect.z <= pcmd->ClipRect.x || - pcmd->ClipRect.w <= pcmd->ClipRect.y) - { - continue; - } - - GSVector4i clip = GSVector4i(GSVector4::load(&pcmd->ClipRect.x)); - if (flip) - clip = FlipToLowerLeft(clip, texture->GetHeight()); - - SetScissor(clip); - SetTextureSampler(0, reinterpret_cast(pcmd->TextureId), m_linear_sampler); - - if (pcmd->UserCallback) [[unlikely]] - { - pcmd->UserCallback(cmd_list, pcmd); - PushUniformBuffer(&mproj, sizeof(mproj)); - SetPipeline(m_imgui_pipeline.get()); - } - else - { - DrawIndexed(pcmd->ElemCount, base_index + pcmd->IdxOffset, base_vertex + pcmd->VtxOffset); - } - } - } -} - void GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex) { void* map; @@ -1041,40 +859,6 @@ std::array GPUDevice::RGBA8ToFloat(u32 rgba) static_cast(rgba >> 24) * (1.0f / 255.0f)}; } -bool GPUDevice::UpdateImGuiFontTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - const u32 pitch = sizeof(u32) * width; - - if (m_imgui_font_texture && m_imgui_font_texture->GetWidth() == static_cast(width) && - m_imgui_font_texture->GetHeight() == static_cast(height) && - m_imgui_font_texture->Update(0, 0, static_cast(width), static_cast(height), pixels, pitch)) - { - io.Fonts->SetTexID(m_imgui_font_texture.get()); - return true; - } - - Error error; - std::unique_ptr new_font = - FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, GPUTexture::Flags::None, - pixels, pitch, &error); - if (!new_font) [[unlikely]] - { - ERROR_LOG("Failed to create new ImGui font texture: {}", error.GetDescription()); - return false; - } - - RecycleTexture(std::move(m_imgui_font_texture)); - m_imgui_font_texture = std::move(new_font); - io.Fonts->SetTexID(m_imgui_font_texture.get()); - return true; -} - bool GPUDevice::UsesLowerLeftOrigin() const { const RenderAPI api = GetRenderAPI(); @@ -1327,7 +1111,8 @@ void GPUDevice::TrimTexturePool() } bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, - GPUTexture::Format format, GPUTexture::Flags flags, bool preserve /* = true */) + GPUTexture::Format format, GPUTexture::Flags flags, bool preserve /* = true */, + Error* error /* = nullptr */) { GPUTexture* old_tex = tex->get(); if (old_tex && old_tex->GetWidth() == new_width && old_tex->GetHeight() == new_height && old_tex->GetType() == type && @@ -1337,38 +1122,67 @@ bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u } DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1)); - std::unique_ptr new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags); + std::unique_ptr new_tex = + FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags, nullptr, 0, error); if (!new_tex) [[unlikely]] - { - ERROR_LOG("Failed to create new {}x{} texture", new_width, new_height); return false; - } - if (old_tex) + if (preserve) { - if (old_tex->GetState() == GPUTexture::State::Cleared) + if (old_tex) { - if (type == GPUTexture::Type::RenderTarget) - ClearRenderTarget(new_tex.get(), old_tex->GetClearColor()); + if (old_tex->GetState() == GPUTexture::State::Cleared) + { + if (type == GPUTexture::Type::RenderTarget) + ClearRenderTarget(new_tex.get(), old_tex->GetClearColor()); + } + else if (old_tex->GetState() == GPUTexture::State::Dirty) + { + const u32 copy_width = std::min(new_width, old_tex->GetWidth()); + const u32 copy_height = std::min(new_height, old_tex->GetHeight()); + if (type == GPUTexture::Type::RenderTarget) + ClearRenderTarget(new_tex.get(), 0); + + if (old_tex->GetFormat() == new_tex->GetFormat()) + CopyTextureRegion(new_tex.get(), 0, 0, 0, 0, old_tex, 0, 0, 0, 0, copy_width, copy_height); + } } - else if (old_tex->GetState() == GPUTexture::State::Dirty) + else { - const u32 copy_width = std::min(new_width, old_tex->GetWidth()); - const u32 copy_height = std::min(new_height, old_tex->GetHeight()); + // If we're expecting data to be there, make sure to clear it. if (type == GPUTexture::Type::RenderTarget) ClearRenderTarget(new_tex.get(), 0); - - if (old_tex->GetFormat() == new_tex->GetFormat()) - CopyTextureRegion(new_tex.get(), 0, 0, 0, 0, old_tex, 0, 0, 0, 0, copy_width, copy_height); } } - else if (preserve) + + RecycleTexture(std::move(*tex)); + *tex = std::move(new_tex); + return true; +} + +bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, + GPUTexture::Format format, GPUTexture::Flags flags, const void* replace_data, + u32 replace_data_pitch, Error* error /* = nullptr */) +{ + GPUTexture* old_tex = tex->get(); + if (old_tex && old_tex->GetWidth() == new_width && old_tex->GetHeight() == new_height && old_tex->GetType() == type && + old_tex->GetFormat() == format && old_tex->GetFlags() == flags) { - // If we're expecting data to be there, make sure to clear it. - if (type == GPUTexture::Type::RenderTarget) - ClearRenderTarget(new_tex.get(), 0); + if (replace_data && !old_tex->Update(0, 0, new_width, new_height, replace_data, replace_data_pitch)) + { + Error::SetStringView(error, "Texture update failed."); + return false; + } + + return true; } + DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1)); + std::unique_ptr new_tex = + FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags, replace_data, replace_data_pitch, error); + if (!new_tex) [[unlikely]] + return false; + RecycleTexture(std::move(*tex)); *tex = std::move(new_tex); return true; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 176c9be75..c06d4489a 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -870,18 +870,16 @@ public: virtual void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 submit_time = 0) = 0; virtual void SubmitPresent(GPUSwapChain* swap_chain) = 0; - /// Renders ImGui screen elements. Call before EndPresent(). - void RenderImGui(GPUSwapChain* swap_chain); - void RenderImGui(GPUTexture* texture); - ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } - bool UpdateImGuiFontTexture(); bool UsesLowerLeftOrigin() const; static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height); bool ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, - GPUTexture::Format format, GPUTexture::Flags flags, bool preserve = true); + GPUTexture::Format format, GPUTexture::Flags flags, bool preserve = true, Error* error = nullptr); + bool ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, + GPUTexture::Format format, GPUTexture::Flags flags, const void* replace_data, + u32 replace_data_pitch, Error* error = nullptr); virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0; @@ -991,9 +989,6 @@ private: static size_t s_total_vram_usage; - std::unique_ptr m_imgui_pipeline; - std::unique_ptr m_imgui_font_texture; - SamplerMap m_sampler_map; TexturePool m_texture_pool; diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 70edcdf2e..452d08b92 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -3329,16 +3329,16 @@ void ImGuiFullscreen::RenderLoadingScreen(std::string_view image, std::string_vi DrawLoadingScreen(image, message, progress_min, progress_max, progress_value, false); - ImGui::EndFrame(); + ImGuiManager::CreateDrawLists(); GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain(); if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK) { - g_gpu_device->RenderImGui(swap_chain); + ImGuiManager::RenderDrawLists(swap_chain); g_gpu_device->EndPresent(swap_chain, false); } - ImGui::NewFrame(); + ImGuiManager::NewFrame(); } void ImGuiFullscreen::OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message, diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index cde7f3856..43092dd1f 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -8,6 +8,7 @@ #include "imgui_fullscreen.h" #include "imgui_glyph_ranges.inl" #include "input_manager.h" +#include "shadergen.h" // TODO: Remove me when GPUDevice config is also cleaned up. #include "core/fullscreen_ui.h" @@ -79,6 +80,9 @@ static bool AddImGuiFonts(bool debug_font, bool fullscreen_fonts); static ImFont* AddTextFont(float size, const ImWchar* glyph_range); static ImFont* AddFixedFont(float size); static bool AddIconFonts(float size, const ImWchar* emoji_range); +static bool CompilePipelines(Error* error); +static void RenderDrawLists(u32 window_width, u32 window_height, WindowInfo::PreRotation prerotation); +static bool UpdateImGuiFontTexture(); static void SetCommonIOOptions(ImGuiIO& io); static void SetImKeyState(ImGuiIO& io, ImGuiKey imkey, bool pressed); static const char* GetClipboardTextImpl(void* userdata); @@ -122,11 +126,15 @@ struct ALIGN_TO_CACHE_LINE State float window_width = 0.0f; float window_height = 0.0f; + GPUTexture::Format window_format = GPUTexture::Format::Unknown; bool scale_changed = false; // we maintain a second copy of the stick state here so we can map it to the dpad std::array left_stick_axis_state = {}; + std::unique_ptr imgui_pipeline; + std::unique_ptr imgui_font_texture; + ImFont* debug_font = nullptr; ImFont* osd_font = nullptr; ImFont* fixed_font = nullptr; @@ -228,9 +236,10 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er return false; } + GPUSwapChain* const main_swap_chain = g_gpu_device->GetMainSwapChain(); + s_state.global_prescale = global_scale; - s_state.global_scale = std::max( - (g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f) * global_scale, 1.0f); + s_state.global_scale = std::max((main_swap_chain ? main_swap_chain->GetScale() : 1.0f) * global_scale, 1.0f); s_state.screen_margin = std::max(screen_margin, 0.0f); s_state.scale_changed = false; @@ -249,10 +258,9 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er SetCommonIOOptions(io); s_state.last_render_time = Timer::GetCurrentValue(); - s_state.window_width = - g_gpu_device->HasMainSwapChain() ? static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f; - s_state.window_height = - g_gpu_device->HasMainSwapChain() ? static_cast(g_gpu_device->GetMainSwapChain()->GetHeight()) : 0.0f; + s_state.window_format = main_swap_chain ? main_swap_chain->GetFormat() : GPUTexture::Format::RGBA8; + s_state.window_width = main_swap_chain ? static_cast(main_swap_chain->GetWidth()) : 0.0f; + s_state.window_height = main_swap_chain ? static_cast(main_swap_chain->GetHeight()) : 0.0f; io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling io.DisplaySize = ImVec2(s_state.window_width, s_state.window_height); @@ -260,10 +268,12 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er SetStyle(s_state.imgui_context->Style, s_state.global_scale); FullscreenUI::SetTheme(); - if (!AddImGuiFonts(false, false) || !g_gpu_device->UpdateImGuiFontTexture()) + if (!CompilePipelines(error)) + return false; + + if (!AddImGuiFonts(false, false) || !UpdateImGuiFontTexture()) { Error::SetString(error, "Failed to create ImGui font text"); - ImGui::DestroyContext(); return false; } @@ -280,17 +290,20 @@ void ImGuiManager::Shutdown() { DestroySoftwareCursorTextures(); - if (s_state.imgui_context) - { - ImGui::DestroyContext(s_state.imgui_context); - s_state.imgui_context = nullptr; - } - s_state.debug_font = nullptr; s_state.fixed_font = nullptr; s_state.medium_font = nullptr; s_state.large_font = nullptr; ImGuiFullscreen::SetFonts(nullptr, nullptr); + + s_state.imgui_pipeline.reset(); + g_gpu_device->RecycleTexture(std::move(s_state.imgui_font_texture)); + + if (s_state.imgui_context) + { + ImGui::DestroyContext(s_state.imgui_context); + s_state.imgui_context = nullptr; + } } ImGuiContext* ImGuiManager::GetMainContext() @@ -313,8 +326,20 @@ float ImGuiManager::GetWindowHeight() return s_state.window_height; } -void ImGuiManager::WindowResized(float width, float height) +void ImGuiManager::WindowResized(GPUTexture::Format format, float width, float height) { + if (s_state.window_format != format) [[unlikely]] + { + Error error; + s_state.window_format = format; + if (!CompilePipelines(&error)) + { + error.AddPrefix("Failed to compile pipelines after window format change:\n"); + GPUThread::ReportFatalErrorAndShutdown(error.GetDescription()); + return; + } + } + s_state.window_width = width; s_state.window_height = height; ImGui::GetMainViewport()->Size = ImGui::GetIO().DisplaySize = ImVec2(width, height); @@ -342,10 +367,12 @@ void ImGuiManager::UpdateScale() SetStyle(s_state.imgui_context->Style, s_state.global_scale); if (!AddImGuiFonts(HasDebugFont(), HasFullscreenFonts())) - Panic("Failed to create ImGui font text"); + { + GPUThread::ReportFatalErrorAndShutdown("Failed to create ImGui font text"); + return; + } - if (!g_gpu_device->UpdateImGuiFontTexture()) - Panic("Failed to recreate font texture after scale+resize"); + UpdateImGuiFontTexture(); } void ImGuiManager::NewFrame() @@ -381,6 +408,167 @@ void ImGuiManager::NewFrame() } } +bool ImGuiManager::CompilePipelines(Error* error) +{ + const RenderAPI render_api = g_gpu_device->GetRenderAPI(); + const GPUDevice::Features features = g_gpu_device->GetFeatures(); + const ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), features.dual_source_blend, + features.framebuffer_fetch); + + std::unique_ptr imgui_vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GenerateImGuiVertexShader(), error); + std::unique_ptr imgui_fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateImGuiFragmentShader(), error); + if (!imgui_vs || !imgui_fs) + { + Error::AddPrefix(error, "Failed to compile ImGui shaders: "); + return false; + } + GL_OBJECT_NAME(imgui_vs, "ImGui Vertex Shader"); + GL_OBJECT_NAME(imgui_fs, "ImGui Fragment Shader"); + + static constexpr GPUPipeline::VertexAttribute imgui_attributes[] = { + GPUPipeline::VertexAttribute::Make(0, GPUPipeline::VertexAttribute::Semantic::Position, 0, + GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ImDrawVert, pos)), + GPUPipeline::VertexAttribute::Make(1, GPUPipeline::VertexAttribute::Semantic::TexCoord, 0, + GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ImDrawVert, uv)), + GPUPipeline::VertexAttribute::Make(2, GPUPipeline::VertexAttribute::Semantic::Color, 0, + GPUPipeline::VertexAttribute::Type::UNorm8, 4, OFFSETOF(ImDrawVert, col)), + }; + + GPUPipeline::GraphicsConfig plconfig; + plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants; + plconfig.input_layout.vertex_attributes = imgui_attributes; + plconfig.input_layout.vertex_stride = sizeof(ImDrawVert); + plconfig.primitive = GPUPipeline::Primitive::Triangles; + plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState(); + plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); + plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); + plconfig.blend.write_mask = 0x7; + plconfig.SetTargetFormats(s_state.window_format); + plconfig.samples = 1; + plconfig.per_sample_shading = false; + plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; + plconfig.vertex_shader = imgui_vs.get(); + plconfig.geometry_shader = nullptr; + plconfig.fragment_shader = imgui_fs.get(); + + s_state.imgui_pipeline = g_gpu_device->CreatePipeline(plconfig, error); + if (!s_state.imgui_pipeline) + { + Error::AddPrefix(error, "Failed to compile ImGui pipeline: "); + return false; + } + + GL_OBJECT_NAME(s_state.imgui_pipeline, "ImGui Pipeline"); + return true; +} + +bool ImGuiManager::UpdateImGuiFontTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + Error error; + const bool result = g_gpu_device->ResizeTexture( + &s_state.imgui_font_texture, static_cast(width), static_cast(height), GPUTexture::Type::Texture, + GPUTexture::Format::RGBA8, GPUTexture::Flags::None, pixels, sizeof(u32) * width, &error); + if (!result) [[unlikely]] + ERROR_LOG("Failed to resize ImGui font texture: {}", error.GetDescription()); + + // always update pointer, it could change + io.Fonts->SetTexID(s_state.imgui_font_texture.get()); + return result; +} + +void ImGuiManager::CreateDrawLists() +{ + ImGui::EndFrame(); + ImGui::Render(); +} + +void ImGuiManager::RenderDrawLists(u32 window_width, u32 window_height, WindowInfo::PreRotation prerotation) +{ + const ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount == 0) + return; + + const GSVector2i window_size = GSVector2i(static_cast(window_width), static_cast(window_height)); + const u32 post_rotated_width = + WindowInfo::ShouldSwapDimensionsForPreRotation(prerotation) ? window_height : window_width; + const u32 post_rotated_height = + WindowInfo::ShouldSwapDimensionsForPreRotation(prerotation) ? window_width : window_height; + + g_gpu_device->SetViewport(0, 0, static_cast(post_rotated_width), static_cast(post_rotated_height)); + g_gpu_device->SetPipeline(s_state.imgui_pipeline.get()); + + const bool prerotated = (prerotation != WindowInfo::PreRotation::Identity); + GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection(0.0f, 0.0f, static_cast(window_width), + static_cast(window_height), 0.0f, 1.0f); + if (prerotated) + mproj = GSMatrix4x4::RotationZ(WindowInfo::GetZRotationForPreRotation(prerotation)) * mproj; + g_gpu_device->PushUniformBuffer(&mproj, sizeof(mproj)); + + // Render command lists + const bool flip = g_gpu_device->UsesLowerLeftOrigin(); + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + static_assert(sizeof(ImDrawIdx) == sizeof(GPUDevice::DrawIndex)); + + u32 base_vertex, base_index; + g_gpu_device->UploadVertexBuffer(cmd_list->VtxBuffer.Data, sizeof(ImDrawVert), cmd_list->VtxBuffer.Size, + &base_vertex); + g_gpu_device->UploadIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size, &base_index); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if ((pcmd->ElemCount == 0 && !pcmd->UserCallback) || pcmd->ClipRect.z <= pcmd->ClipRect.x || + pcmd->ClipRect.w <= pcmd->ClipRect.y) + { + continue; + } + + GSVector4i clip = GSVector4i(GSVector4::load(&pcmd->ClipRect.x)); + + if (prerotated) + clip = GPUSwapChain::PreRotateClipRect(prerotation, window_size, clip); + if (flip) + clip = g_gpu_device->FlipToLowerLeft(clip, post_rotated_height); + + g_gpu_device->SetScissor(clip); + g_gpu_device->SetTextureSampler(0, reinterpret_cast(pcmd->TextureId), + g_gpu_device->GetLinearSampler()); + + if (pcmd->UserCallback) [[unlikely]] + { + pcmd->UserCallback(cmd_list, pcmd); + g_gpu_device->PushUniformBuffer(&mproj, sizeof(mproj)); + g_gpu_device->SetPipeline(s_state.imgui_pipeline.get()); + } + else + { + g_gpu_device->DrawIndexed(pcmd->ElemCount, base_index + pcmd->IdxOffset, base_vertex + pcmd->VtxOffset); + } + } + } +} + +void ImGuiManager::RenderDrawLists(GPUSwapChain* swap_chain) +{ + RenderDrawLists(swap_chain->GetWidth(), swap_chain->GetHeight(), swap_chain->GetPreRotation()); +} + +void ImGuiManager::RenderDrawLists(GPUTexture* texture) +{ + RenderDrawLists(texture->GetWidth(), texture->GetHeight(), WindowInfo::PreRotation::Identity); +} + void ImGuiManager::SetStyle(ImGuiStyle& style, float scale) { style = ImGuiStyle(); @@ -785,14 +973,18 @@ void ImGuiManager::ReloadFontDataIfActive() ImGui::EndFrame(); if (!LoadFontData(nullptr)) - Panic("Failed to load font data"); + { + GPUThread::ReportFatalErrorAndShutdown("Failed to load font data"); + return; + } if (!AddImGuiFonts(HasDebugFont(), HasFullscreenFonts())) - Panic("Failed to create ImGui font text"); - - if (!g_gpu_device->UpdateImGuiFontTexture()) - Panic("Failed to recreate font texture after scale+resize"); + { + GPUThread::ReportFatalErrorAndShutdown("Failed to create ImGui font text"); + return; + } + UpdateImGuiFontTexture(); NewFrame(); } @@ -807,11 +999,11 @@ bool ImGuiManager::AddFullscreenFontsIfMissing() const bool debug_font = HasDebugFont(); if (!AddImGuiFonts(debug_font, true)) { - ERROR_LOG("Failed to lazily allocate fullscreen fonts."); + GPUThread::ReportFatalErrorAndShutdown("Failed to lazily allocate fullscreen fonts."); AddImGuiFonts(debug_font, false); } - g_gpu_device->UpdateImGuiFontTexture(); + UpdateImGuiFontTexture(); NewFrame(); return HasFullscreenFonts(); @@ -837,7 +1029,7 @@ bool ImGuiManager::AddDebugFontIfMissing() AddImGuiFonts(true, fullscreen_font); } - g_gpu_device->UpdateImGuiFontTexture(); + UpdateImGuiFontTexture(); NewFrame(); return HasDebugFont(); @@ -1592,16 +1784,14 @@ bool ImGuiManager::RenderAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state ImGui::End(); ImGui::PopFont(); + CreateDrawLists(); + const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(state->swap_chain.get()); if (pres == GPUDevice::PresentResult::OK) { - g_gpu_device->RenderImGui(state->swap_chain.get()); + RenderDrawLists(state->swap_chain.get()); g_gpu_device->EndPresent(state->swap_chain.get(), false); } - else - { - ImGui::EndFrame(); - } ImGui::SetCurrentContext(GetMainContext()); return true; diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index b3d3d18b2..d0bbff764 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -1,8 +1,10 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once +#include "util/gpu_texture.h" + #include "common/types.h" #include @@ -14,6 +16,7 @@ class Error; struct WindowInfo; class GPUSwapChain; +class GPUTexture; struct ImGuiContext; struct ImFont; @@ -91,7 +94,7 @@ float GetWindowWidth(); float GetWindowHeight(); /// Updates internal state when the window is size. -void WindowResized(float width, float height); +void WindowResized(GPUTexture::Format format, float width, float height); /// Updates scaling of the on-screen elements. void RequestScaleUpdate(); @@ -99,6 +102,13 @@ void RequestScaleUpdate(); /// Call at the beginning of the frame to set up ImGui state. void NewFrame(); +/// Creates the draw list for the frame, akin to ImGui::Render(). +void CreateDrawLists(); + +/// Renders ImGui screen elements. Call before EndPresent(). +void RenderDrawLists(GPUSwapChain* swap_chain); +void RenderDrawLists(GPUTexture* texture); + /// Renders any on-screen display elements. void RenderOSDMessages();