OnScreenUI.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. // Copyright 2023 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "VideoCommon/OnScreenUI.h"
  4. #include "Common/EnumMap.h"
  5. #include "Common/Profiler.h"
  6. #include "Common/Timer.h"
  7. #include "Core/AchievementManager.h"
  8. #include "Core/Config/MainSettings.h"
  9. #include "Core/Config/NetplaySettings.h"
  10. #include "Core/Movie.h"
  11. #include "Core/System.h"
  12. #include "VideoCommon/AbstractGfx.h"
  13. #include "VideoCommon/AbstractPipeline.h"
  14. #include "VideoCommon/AbstractShader.h"
  15. #include "VideoCommon/FramebufferShaderGen.h"
  16. #include "VideoCommon/NetPlayChatUI.h"
  17. #include "VideoCommon/NetPlayGolfUI.h"
  18. #include "VideoCommon/OnScreenDisplay.h"
  19. #include "VideoCommon/PerformanceMetrics.h"
  20. #include "VideoCommon/Present.h"
  21. #include "VideoCommon/Statistics.h"
  22. #include "VideoCommon/VertexManagerBase.h"
  23. #include "VideoCommon/VideoConfig.h"
  24. #include <inttypes.h>
  25. #include <mutex>
  26. #include <imgui.h>
  27. #include <implot.h>
  28. namespace VideoCommon
  29. {
  30. bool OnScreenUI::Initialize(u32 width, u32 height, float scale)
  31. {
  32. std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
  33. if (!IMGUI_CHECKVERSION())
  34. {
  35. PanicAlertFmt("ImGui version check failed");
  36. return false;
  37. }
  38. if (!ImGui::CreateContext())
  39. {
  40. PanicAlertFmt("Creating ImGui context failed");
  41. return false;
  42. }
  43. if (!ImPlot::CreateContext())
  44. {
  45. PanicAlertFmt("Creating ImPlot context failed");
  46. return false;
  47. }
  48. // Don't create an ini file. TODO: Do we want this in the future?
  49. ImGui::GetIO().IniFilename = nullptr;
  50. SetScale(scale);
  51. PortableVertexDeclaration vdecl = {};
  52. vdecl.position = {ComponentFormat::Float, 2, offsetof(ImDrawVert, pos), true, false};
  53. vdecl.texcoords[0] = {ComponentFormat::Float, 2, offsetof(ImDrawVert, uv), true, false};
  54. vdecl.colors[0] = {ComponentFormat::UByte, 4, offsetof(ImDrawVert, col), true, false};
  55. vdecl.stride = sizeof(ImDrawVert);
  56. m_imgui_vertex_format = g_gfx->CreateNativeVertexFormat(vdecl);
  57. if (!m_imgui_vertex_format)
  58. {
  59. PanicAlertFmt("Failed to create ImGui vertex format");
  60. return false;
  61. }
  62. // Font texture(s).
  63. {
  64. ImGuiIO& io = ImGui::GetIO();
  65. u8* font_tex_pixels;
  66. int font_tex_width, font_tex_height;
  67. io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height);
  68. TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1,
  69. AbstractTextureFormat::RGBA8, 0,
  70. AbstractTextureType::Texture_2DArray);
  71. std::unique_ptr<AbstractTexture> font_tex =
  72. g_gfx->CreateTexture(font_tex_config, "ImGui font texture");
  73. if (!font_tex)
  74. {
  75. PanicAlertFmt("Failed to create ImGui texture");
  76. return false;
  77. }
  78. font_tex->Load(0, font_tex_width, font_tex_height, font_tex_width, font_tex_pixels,
  79. sizeof(u32) * font_tex_width * font_tex_height);
  80. io.Fonts->TexID = font_tex.get();
  81. m_imgui_textures.push_back(std::move(font_tex));
  82. }
  83. if (!RecompileImGuiPipeline())
  84. return false;
  85. m_imgui_last_frame_time = Common::Timer::NowUs();
  86. m_ready = true;
  87. BeginImGuiFrameUnlocked(width, height); // lock is already held
  88. return true;
  89. }
  90. OnScreenUI::~OnScreenUI()
  91. {
  92. std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
  93. ImGui::EndFrame();
  94. ImPlot::DestroyContext();
  95. ImGui::DestroyContext();
  96. }
  97. bool OnScreenUI::RecompileImGuiPipeline()
  98. {
  99. if (g_presenter->GetBackbufferFormat() == AbstractTextureFormat::Undefined)
  100. {
  101. // No backbuffer (nogui) means no imgui rendering will happen
  102. // Some backends don't like making pipelines with no render targets
  103. return true;
  104. }
  105. const bool linear_space_output =
  106. g_presenter->GetBackbufferFormat() == AbstractTextureFormat::RGBA16F;
  107. std::unique_ptr<AbstractShader> vertex_shader = g_gfx->CreateShaderFromSource(
  108. ShaderStage::Vertex, FramebufferShaderGen::GenerateImGuiVertexShader(),
  109. "ImGui vertex shader");
  110. std::unique_ptr<AbstractShader> pixel_shader = g_gfx->CreateShaderFromSource(
  111. ShaderStage::Pixel, FramebufferShaderGen::GenerateImGuiPixelShader(linear_space_output),
  112. "ImGui pixel shader");
  113. if (!vertex_shader || !pixel_shader)
  114. {
  115. PanicAlertFmt("Failed to compile ImGui shaders");
  116. return false;
  117. }
  118. // GS is used to render the UI to both eyes in stereo modes.
  119. std::unique_ptr<AbstractShader> geometry_shader;
  120. if (g_gfx->UseGeometryShaderForUI())
  121. {
  122. geometry_shader = g_gfx->CreateShaderFromSource(
  123. ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 1),
  124. "ImGui passthrough geometry shader");
  125. if (!geometry_shader)
  126. {
  127. PanicAlertFmt("Failed to compile ImGui geometry shader");
  128. return false;
  129. }
  130. }
  131. AbstractPipelineConfig pconfig = {};
  132. pconfig.vertex_format = m_imgui_vertex_format.get();
  133. pconfig.vertex_shader = vertex_shader.get();
  134. pconfig.geometry_shader = geometry_shader.get();
  135. pconfig.pixel_shader = pixel_shader.get();
  136. pconfig.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
  137. pconfig.depth_state = RenderState::GetNoDepthTestingDepthState();
  138. pconfig.blending_state = RenderState::GetNoBlendingBlendState();
  139. pconfig.blending_state.blendenable = true;
  140. pconfig.blending_state.srcfactor = SrcBlendFactor::SrcAlpha;
  141. pconfig.blending_state.dstfactor = DstBlendFactor::InvSrcAlpha;
  142. pconfig.blending_state.srcfactoralpha = SrcBlendFactor::Zero;
  143. pconfig.blending_state.dstfactoralpha = DstBlendFactor::One;
  144. pconfig.framebuffer_state.color_texture_format = g_presenter->GetBackbufferFormat();
  145. pconfig.framebuffer_state.depth_texture_format = AbstractTextureFormat::Undefined;
  146. pconfig.framebuffer_state.samples = 1;
  147. pconfig.framebuffer_state.per_sample_shading = false;
  148. pconfig.usage = AbstractPipelineUsage::Utility;
  149. m_imgui_pipeline = g_gfx->CreatePipeline(pconfig);
  150. if (!m_imgui_pipeline)
  151. {
  152. PanicAlertFmt("Failed to create imgui pipeline");
  153. return false;
  154. }
  155. return true;
  156. }
  157. void OnScreenUI::BeginImGuiFrame(u32 width, u32 height)
  158. {
  159. std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
  160. BeginImGuiFrameUnlocked(width, height);
  161. }
  162. void OnScreenUI::BeginImGuiFrameUnlocked(u32 width, u32 height)
  163. {
  164. m_backbuffer_width = width;
  165. m_backbuffer_height = height;
  166. const u64 current_time_us = Common::Timer::NowUs();
  167. const u64 time_diff_us = current_time_us - m_imgui_last_frame_time;
  168. const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0);
  169. m_imgui_last_frame_time = current_time_us;
  170. // Update I/O with window dimensions.
  171. ImGuiIO& io = ImGui::GetIO();
  172. io.DisplaySize =
  173. ImVec2(static_cast<float>(m_backbuffer_width), static_cast<float>(m_backbuffer_height));
  174. io.DeltaTime = time_diff_secs;
  175. ImGui::NewFrame();
  176. }
  177. void OnScreenUI::DrawImGui()
  178. {
  179. ImDrawData* draw_data = ImGui::GetDrawData();
  180. if (!draw_data)
  181. return;
  182. g_gfx->SetViewport(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
  183. static_cast<float>(m_backbuffer_height), 0.0f, 1.0f);
  184. // Uniform buffer for draws.
  185. struct ImGuiUbo
  186. {
  187. float u_rcp_viewport_size_mul2[2];
  188. float padding[2];
  189. };
  190. ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}};
  191. // Set up common state for drawing.
  192. g_gfx->SetPipeline(m_imgui_pipeline.get());
  193. g_gfx->SetSamplerState(0, RenderState::GetPointSamplerState());
  194. g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
  195. for (int i = 0; i < draw_data->CmdListsCount; i++)
  196. {
  197. const ImDrawList* cmdlist = draw_data->CmdLists[i];
  198. if (cmdlist->VtxBuffer.empty() || cmdlist->IdxBuffer.empty())
  199. return;
  200. u32 base_vertex, base_index;
  201. g_vertex_manager->UploadUtilityVertices(cmdlist->VtxBuffer.Data, sizeof(ImDrawVert),
  202. cmdlist->VtxBuffer.Size, cmdlist->IdxBuffer.Data,
  203. cmdlist->IdxBuffer.Size, &base_vertex, &base_index);
  204. for (const ImDrawCmd& cmd : cmdlist->CmdBuffer)
  205. {
  206. if (cmd.UserCallback)
  207. {
  208. cmd.UserCallback(cmdlist, &cmd);
  209. continue;
  210. }
  211. g_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle(
  212. MathUtil::Rectangle<int>(
  213. static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
  214. static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)),
  215. g_gfx->GetCurrentFramebuffer()));
  216. g_gfx->SetTexture(0, reinterpret_cast<const AbstractTexture*>(cmd.TextureId));
  217. g_gfx->DrawIndexed(base_index, cmd.ElemCount, base_vertex);
  218. base_index += cmd.ElemCount;
  219. }
  220. }
  221. // Some capture software (such as OBS) hooks SwapBuffers and uses glBlitFramebuffer to copy our
  222. // back buffer just before swap. Because glBlitFramebuffer honors the scissor test, the capture
  223. // itself will be clipped to whatever bounds were last set by ImGui, resulting in a rather useless
  224. // capture whenever any ImGui windows are open. We'll reset the scissor rectangle to the entire
  225. // viewport here to avoid this problem.
  226. g_gfx->SetScissorRect(g_gfx->ConvertFramebufferRectangle(
  227. MathUtil::Rectangle<int>(0, 0, m_backbuffer_width, m_backbuffer_height),
  228. g_gfx->GetCurrentFramebuffer()));
  229. }
  230. // Create On-Screen-Messages
  231. void OnScreenUI::DrawDebugText()
  232. {
  233. const bool show_movie_window =
  234. Config::Get(Config::MAIN_SHOW_FRAME_COUNT) || Config::Get(Config::MAIN_SHOW_LAG) ||
  235. Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY) ||
  236. Config::Get(Config::MAIN_MOVIE_SHOW_RTC) || Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD);
  237. if (show_movie_window)
  238. {
  239. // Position under the FPS display.
  240. ImGui::SetNextWindowPos(
  241. ImVec2(ImGui::GetIO().DisplaySize.x - 10.f * m_backbuffer_scale, 80.f * m_backbuffer_scale),
  242. ImGuiCond_FirstUseEver, ImVec2(1.0f, 0.0f));
  243. ImGui::SetNextWindowSizeConstraints(
  244. ImVec2(150.0f * m_backbuffer_scale, 20.0f * m_backbuffer_scale),
  245. ImGui::GetIO().DisplaySize);
  246. if (ImGui::Begin("Movie", nullptr, ImGuiWindowFlags_NoFocusOnAppearing))
  247. {
  248. auto& movie = Core::System::GetInstance().GetMovie();
  249. if (movie.IsPlayingInput())
  250. {
  251. ImGui::Text("Frame: %" PRIu64 " / %" PRIu64, movie.GetCurrentFrame(),
  252. movie.GetTotalFrames());
  253. ImGui::Text("Input: %" PRIu64 " / %" PRIu64, movie.GetCurrentInputCount(),
  254. movie.GetTotalInputCount());
  255. }
  256. else if (Config::Get(Config::MAIN_SHOW_FRAME_COUNT))
  257. {
  258. ImGui::Text("Frame: %" PRIu64, movie.GetCurrentFrame());
  259. if (movie.IsRecordingInput())
  260. ImGui::Text("Input: %" PRIu64, movie.GetCurrentInputCount());
  261. }
  262. if (Config::Get(Config::MAIN_SHOW_LAG))
  263. ImGui::Text("Lag: %" PRIu64 "\n", movie.GetCurrentLagCount());
  264. if (Config::Get(Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY))
  265. ImGui::TextUnformatted(movie.GetInputDisplay().c_str());
  266. if (Config::Get(Config::MAIN_MOVIE_SHOW_RTC))
  267. ImGui::TextUnformatted(movie.GetRTCDisplay().c_str());
  268. if (Config::Get(Config::MAIN_MOVIE_SHOW_RERECORD))
  269. ImGui::TextUnformatted(movie.GetRerecords().c_str());
  270. }
  271. ImGui::End();
  272. }
  273. if (g_ActiveConfig.bOverlayStats)
  274. g_stats.Display();
  275. if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui)
  276. g_netplay_chat_ui->Display();
  277. if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui)
  278. g_netplay_golf_ui->Display();
  279. if (g_ActiveConfig.bOverlayProjStats)
  280. g_stats.DisplayProj();
  281. if (g_ActiveConfig.bOverlayScissorStats)
  282. g_stats.DisplayScissor();
  283. const std::string profile_output = Common::Profiler::ToString();
  284. if (!profile_output.empty())
  285. ImGui::TextUnformatted(profile_output.c_str());
  286. }
  287. void OnScreenUI::DrawChallengesAndLeaderboards()
  288. {
  289. if (!Config::Get(Config::MAIN_OSD_MESSAGES))
  290. return;
  291. #ifdef USE_RETRO_ACHIEVEMENTS
  292. auto& instance = AchievementManager::GetInstance();
  293. std::lock_guard lg{instance.GetLock()};
  294. if (instance.AreChallengesUpdated())
  295. {
  296. instance.ResetChallengesUpdated();
  297. const auto& challenges = instance.GetActiveChallenges();
  298. m_challenge_texture_map.clear();
  299. for (const auto& name : challenges)
  300. {
  301. const auto& icon = instance.GetAchievementBadge(name, false);
  302. const u32 width = icon.width;
  303. const u32 height = icon.height;
  304. TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
  305. AbstractTextureType::Texture_2DArray);
  306. auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config));
  307. res.first->second->Load(0, width, height, width, icon.data.data(),
  308. sizeof(u32) * width * height);
  309. }
  310. }
  311. float leaderboard_y = ImGui::GetIO().DisplaySize.y;
  312. if (!m_challenge_texture_map.empty())
  313. {
  314. float scale = ImGui::GetIO().DisplaySize.y / 1024.0;
  315. ImGui::SetNextWindowSize(ImVec2(0, 0));
  316. ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y), 0,
  317. ImVec2(1, 1));
  318. if (ImGui::Begin("Challenges", nullptr,
  319. ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
  320. ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
  321. ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
  322. ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
  323. {
  324. for (auto& [name, texture] : m_challenge_texture_map)
  325. {
  326. ImGui::Image(texture.get(), ImVec2(static_cast<float>(texture->GetWidth()) * scale,
  327. static_cast<float>(texture->GetHeight()) * scale));
  328. ImGui::SameLine();
  329. }
  330. }
  331. leaderboard_y -= ImGui::GetWindowHeight();
  332. ImGui::End();
  333. }
  334. const auto& leaderboard_progress = instance.GetActiveLeaderboards();
  335. if (!leaderboard_progress.empty())
  336. {
  337. ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x, leaderboard_y), 0,
  338. ImVec2(1.0, 1.0));
  339. ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
  340. if (ImGui::Begin("Leaderboards", nullptr,
  341. ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
  342. ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
  343. ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
  344. ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
  345. {
  346. for (const auto& value : leaderboard_progress)
  347. ImGui::TextUnformatted(value.c_str());
  348. }
  349. ImGui::End();
  350. }
  351. #endif // USE_RETRO_ACHIEVEMENTS
  352. }
  353. void OnScreenUI::Finalize()
  354. {
  355. auto lock = GetImGuiLock();
  356. g_perf_metrics.DrawImGuiStats(m_backbuffer_scale);
  357. DrawDebugText();
  358. OSD::DrawMessages();
  359. DrawChallengesAndLeaderboards();
  360. ImGui::Render();
  361. }
  362. std::unique_lock<std::mutex> OnScreenUI::GetImGuiLock()
  363. {
  364. return std::unique_lock<std::mutex>(m_imgui_mutex);
  365. }
  366. void OnScreenUI::SetScale(float backbuffer_scale)
  367. {
  368. ImGui::GetIO().DisplayFramebufferScale.x = backbuffer_scale;
  369. ImGui::GetIO().DisplayFramebufferScale.y = backbuffer_scale;
  370. ImGui::GetIO().FontGlobalScale = backbuffer_scale;
  371. // ScaleAllSizes scales in-place, so calling it twice will double-apply the scale
  372. // Reset the style first so that the scale is applied to the base style, not an already-scaled one
  373. ImGui::GetStyle() = {};
  374. ImGui::GetStyle().WindowRounding = 7.0f;
  375. ImGui::GetStyle().ScaleAllSizes(backbuffer_scale);
  376. m_backbuffer_scale = backbuffer_scale;
  377. }
  378. void OnScreenUI::SetKeyMap(const DolphinKeyMap& key_map)
  379. {
  380. static constexpr DolphinKeyMap dolphin_to_imgui_map = {
  381. ImGuiKey_Tab, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow,
  382. ImGuiKey_DownArrow, ImGuiKey_PageUp, ImGuiKey_PageDown, ImGuiKey_Home,
  383. ImGuiKey_End, ImGuiKey_Insert, ImGuiKey_Delete, ImGuiKey_Backspace,
  384. ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_KeypadEnter,
  385. ImGuiKey_A, ImGuiKey_C, ImGuiKey_V, ImGuiKey_X,
  386. ImGuiKey_Y, ImGuiKey_Z,
  387. };
  388. auto lock = GetImGuiLock();
  389. if (!ImGui::GetCurrentContext())
  390. return;
  391. m_dolphin_to_imgui_map.clear();
  392. for (int dolphin_key = 0; dolphin_key <= static_cast<int>(DolphinKey::Z); dolphin_key++)
  393. {
  394. const int imgui_key = dolphin_to_imgui_map[DolphinKey(dolphin_key)];
  395. if (imgui_key >= 0)
  396. {
  397. const int mapped_key = key_map[DolphinKey(dolphin_key)];
  398. m_dolphin_to_imgui_map[mapped_key & 0x1FF] = imgui_key;
  399. }
  400. }
  401. }
  402. void OnScreenUI::SetKey(u32 key, bool is_down, const char* chars)
  403. {
  404. auto lock = GetImGuiLock();
  405. if (auto iter = m_dolphin_to_imgui_map.find(key); iter != m_dolphin_to_imgui_map.end())
  406. ImGui::GetIO().AddKeyEvent((ImGuiKey)iter->second, is_down);
  407. if (chars)
  408. ImGui::GetIO().AddInputCharactersUTF8(chars);
  409. }
  410. void OnScreenUI::SetMousePos(float x, float y)
  411. {
  412. auto lock = GetImGuiLock();
  413. ImGui::GetIO().AddMousePosEvent(x, y);
  414. }
  415. void OnScreenUI::SetMousePress(u32 button_mask)
  416. {
  417. auto lock = GetImGuiLock();
  418. for (size_t i = 0; i < std::size(ImGui::GetIO().MouseDown); i++)
  419. {
  420. ImGui::GetIO().AddMouseButtonEvent(static_cast<int>(i), (button_mask & (1u << i)) != 0);
  421. }
  422. }
  423. } // namespace VideoCommon