diff options
| author | Scott Mansell <phiren@gmail.com> | 2023-01-27 17:03:15 +1300 |
|---|---|---|
| committer | Scott Mansell <phiren@gmail.com> | 2023-01-31 18:45:21 +1300 |
| commit | 0d4537d60f695e50379c1542d6c4b1e643e8a5a0 (patch) | |
| tree | 7c0dd9c2963b7eead104df5077444e3179030b4f /Source/Core/VideoCommon/OnScreenUI.cpp | |
| parent | c38c76abad039c3c0ccb3f7ebb891dc62560f8a3 (diff) | |
Move Presenting, Dumping and ImGui out of Renderer
Diffstat (limited to 'Source/Core/VideoCommon/OnScreenUI.cpp')
| -rw-r--r-- | Source/Core/VideoCommon/OnScreenUI.cpp | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp new file mode 100644 index 0000000000..72f0c8f08a --- /dev/null +++ b/Source/Core/VideoCommon/OnScreenUI.cpp @@ -0,0 +1,386 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/OnScreenUI.h" + +#include "Common/Profiler.h" +#include "Common/Timer.h" + +#include "Core/Config/MainSettings.h" +#include "Core/Config/NetplaySettings.h" +#include "Core/Movie.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/NetPlayGolfUI.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/PerformanceMetrics.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoConfig.h" + +#include <inttypes.h> +#include <mutex> + +#include <imgui.h> +#include <implot.h> + +namespace VideoCommon +{ +bool OnScreenUI::Initialize(u32 width, u32 height, float scale) +{ + std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex); + + if (!IMGUI_CHECKVERSION()) + { + PanicAlertFmt("ImGui version check failed"); + return false; + } + if (!ImGui::CreateContext()) + { + PanicAlertFmt("Creating ImGui context failed"); + return false; + } + if (!ImPlot::CreateContext()) + { + PanicAlertFmt("Creating ImPlot context failed"); + return false; + } + + // Don't create an ini file. TODO: Do we want this in the future? + ImGui::GetIO().IniFilename = nullptr; + SetScale(scale); + ImGui::GetStyle().WindowRounding = 7.0f; + + PortableVertexDeclaration vdecl = {}; + vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false}; + vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false}; + vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false}; + vdecl.stride = sizeof(ImDrawVert); + m_imgui_vertex_format = g_renderer->CreateNativeVertexFormat(vdecl); + if (!m_imgui_vertex_format) + { + PanicAlertFmt("Failed to create ImGui vertex format"); + return false; + } + + // Font texture(s). + { + ImGuiIO& io = ImGui::GetIO(); + u8* font_tex_pixels; + int font_tex_width, font_tex_height; + io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); + + TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, + AbstractTextureFormat::RGBA8, 0); + std::unique_ptr<AbstractTexture> font_tex = + g_renderer->CreateTexture(font_tex_config, "ImGui font texture"); + if (!font_tex) + { + PanicAlertFmt("Failed to create ImGui texture"); + return false; + } + font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels, + sizeof(u32) * font_tex_width * font_tex_height); + + io.Fonts->TexID = font_tex.get(); + + m_imgui_textures.push_back(std::move(font_tex)); + } + + if (!RecompileImGuiPipeline()) + return false; + + m_imgui_last_frame_time = Common::Timer::NowUs(); + m_ready = true; + BeginImGuiFrameUnlocked(width, height); // lock is already held + + return true; +} + +OnScreenUI::~OnScreenUI() +{ + std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex); + + ImGui::EndFrame(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); +} + +bool OnScreenUI::RecompileImGuiPipeline() +{ + if (g_presenter->GetBackbufferFormat() == AbstractTextureFormat::Undefined) + { + // No backbuffer (nogui) means no imgui rendering will happen + // Some backends don't like making pipelines with no render targets + return true; + } + + std::unique_ptr<AbstractShader> vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(), + "ImGui vertex shader"); + std::unique_ptr<AbstractShader> pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(), "ImGui pixel shader"); + if (!vertex_shader || !pixel_shader) + { + PanicAlertFmt("Failed to compile ImGui shaders"); + return false; + } + + // GS is used to render the UI to both eyes in stereo modes. + std::unique_ptr<AbstractShader> geometry_shader; + if (g_renderer->UseGeometryShaderForUI()) + { + geometry_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1), + "ImGui passthrough geometry shader"); + if (!geometry_shader) + { + PanicAlertFmt("Failed to compile ImGui geometry shader"); + return false; + } + } + + AbstractPipelineConfig pconfig = {}; + pconfig.vertex_format = m_imgui_vertex_format.get(); + pconfig.vertex_shader = vertex_shader.get(); + pconfig.geometry_shader = geometry_shader.get(); + pconfig.pixel_shader = pixel_shader.get(); + pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + pconfig.depth_state = RenderState::GetNoDepthTestingDepthState(); + pconfig.blending_state = RenderState::GetNoBlendingBlendState(); + pconfig.blending_state.blendenable = true; + pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha; + pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha; + pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero; + pconfig.blending_state.dstfactoralpha = DstBlendFactor::One; + pconfig.framebuffer_state.color_texture_format = g_presenter->GetBackbufferFormat(); + pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined; + pconfig.framebuffer_state.samples = 1; + pconfig.framebuffer_state.per_sample_shading = false; + pconfig.usage = AbstractPipelineUsage::Utility; + m_imgui_pipeline = g_renderer->CreatePipeline(pconfig); + if (!m_imgui_pipeline) + { + PanicAlertFmt("Failed to create imgui pipeline"); + return false; + } + + return true; +} + +void OnScreenUI::BeginImGuiFrame(u32 width, u32 height) +{ + std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex); + BeginImGuiFrameUnlocked(width, height); +} + +void OnScreenUI::BeginImGuiFrameUnlocked(u32 width, u32 height) +{ + m_backbuffer_width = width; + m_backbuffer_height = height; + + const u64 current_time_us = Common::Timer::NowUs(); + const u64 time_diff_us = current_time_us - m_imgui_last_frame_time; + const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0); + m_imgui_last_frame_time = current_time_us; + + // Update I/O with window dimensions. + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = + ImVec2(static_cast<float>(m_backbuffer_width), static_cast<float>(m_backbuffer_height)); + io.DeltaTime = time_diff_secs; + + ImGui::NewFrame(); +} + +void OnScreenUI::DrawImGui() +{ + ImDrawData* draw_data = ImGui::GetDrawData(); + if (!draw_data) + return; + + g_renderer->SetViewport(0.0f, 0.0f, static_cast<float>(m_backbuffer_width), + static_cast<float>(m_backbuffer_height), 0.0f, 1.0f); + + // Uniform buffer for draws. + struct ImGuiUbo + { + float u_rcp_viewport_size_mul2[2]; + float padding[2]; + }; + ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}}; + + // Set up common state for drawing. + g_renderer->SetPipeline(m_imgui_pipeline.get()); + g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState()); + g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo)); + + for (int i = 0; i < draw_data->CmdListsCount; i++) + { + const ImDrawList* cmdlist = draw_data->CmdLists[i]; + if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty()) + return; + + u32 base_vertex, base_index; + g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert), + cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data, + cmdlist->IdxBuffer.Size, &base_vertex, &base_index); + + for (const ImDrawCmd& cmd : cmdlist->CmdBuffer) + { + if (cmd.UserCallback) + { + cmd.UserCallback(cmdlist, &cmd); + continue; + } + + g_renderer->SetScissorRect(g_renderer->ConvertFramebufferRectangle( + MathUtil::Rectangle<int>( + static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y), + static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)), + g_renderer->GetCurrentFramebuffer())); + g_renderer->SetTexture(0, reinterpret_cast<const AbstractTexture*>(cmd.TextureId)); + g_renderer->DrawIndexed(base_index, cmd.ElemCount, base_vertex); + base_index += cmd.ElemCount; + } + } + + // Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our + // back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture + // itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless + // capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire + // viewport here to avoid this problem. + g_renderer->SetScissorRect(g_renderer->ConvertFramebufferRectangle( + MathUtil::Rectangle<int>(0, 0, m_backbuffer_width, m_backbuffer_height), + g_renderer->GetCurrentFramebuffer())); +} + +// Create On-Screen-Messages +void OnScreenUI::DrawDebugText() +{ + const bool show_movie_window = + Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) || + Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) || + Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD); + if (show_movie_window) + { + // Position under the FPS display. + ImGui::SetNextWindowPos( + ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale), + ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f)); + ImGui::SetNextWindowSizeConstraints( + ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale), + ImGui::GetIO().DisplaySize); + if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (Movie::IsPlayingInput()) + { + ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, Movie::GetCurrentFrame(), + Movie::GetTotalFrames()); + ImGui::Text("Input: %" PRIu64 " / %" PRIu64, Movie::GetCurrentInputCount(), + Movie::GetTotalInputCount()); + } + else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT)) + { + ImGui::Text("Frame: %" PRIu64, Movie::GetCurrentFrame()); + ImGui::Text("Input: %" PRIu64, Movie::GetCurrentInputCount()); + } + if (Config::Get(Config::MAIN_SHOW_LAG)) + ImGui::Text("Lag: %" PRIu64 "\n", Movie::GetCurrentLagCount()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY)) + ImGui::TextUnformatted(Movie::GetInputDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC)) + ImGui::TextUnformatted(Movie::GetRTCDisplay().c_str()); + if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD)) + ImGui::TextUnformatted(Movie::GetRerecords().c_str()); + } + ImGui::End(); + } + + if (g_ActiveConfig.bOverlayStats) + g_stats.Display(); + + if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) + g_netplay_chat_ui->Display(); + + if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) + g_netplay_golf_ui->Display(); + + if (g_ActiveConfig.bOverlayProjStats) + g_stats.DisplayProj(); + + if (g_ActiveConfig.bOverlayScissorStats) + g_stats.DisplayScissor(); + + const std::string profile_output = Common::Profiler::ToString(); + if (!profile_output.empty()) + ImGui::TextUnformatted(profile_output.c_str()); +} + +void OnScreenUI::Finalize() +{ + auto lock = GetImGuiLock(); + + g_perf_metrics.DrawImGuiStats(m_backbuffer_scale); + DrawDebugText(); + OSD::DrawMessages(); + ImGui::Render(); +} + +std::unique_lock<std::mutex> OnScreenUI::GetImGuiLock() +{ + return std::unique_lock<std::mutex>(m_imgui_mutex); +} + +void OnScreenUI::SetScale(float backbuffer_scale) +{ + ImGui::GetIO().DisplayFramebufferScale.x = backbuffer_scale; + ImGui::GetIO().DisplayFramebufferScale.y = backbuffer_scale; + ImGui::GetIO().FontGlobalScale = backbuffer_scale; + ImGui::GetStyle().ScaleAllSizes(backbuffer_scale); + + m_backbuffer_scale = backbuffer_scale; +} +void OnScreenUI::SetKeyMap(std::span<const std::array<int, 2>> key_map) +{ + auto lock = GetImGuiLock(); + + if (!ImGui::GetCurrentContext()) + return; + + for (auto [imgui_key, qt_key] : key_map) + ImGui::GetIO().KeyMap[imgui_key] = (qt_key & 0x1FF); +} + +void OnScreenUI::SetKey(u32 key, bool is_down, const char* chars) +{ + auto lock = GetImGuiLock(); + if (key < std::size(ImGui::GetIO().KeysDown)) + ImGui::GetIO().KeysDown[key] = is_down; + + if (chars) + ImGui::GetIO().AddInputCharactersUTF8(chars); +} + +void OnScreenUI::SetMousePos(float x, float y) +{ + auto lock = GetImGuiLock(); + + ImGui::GetIO().MousePos.x = x; + ImGui::GetIO().MousePos.y = y; +} + +void OnScreenUI::SetMousePress(u32 button_mask) +{ + auto lock = GetImGuiLock(); + + for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++) + ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0; +} + +} // namespace VideoCommon |
