ImGuiManager: Move drawing out of GPUDevice

This commit is contained in:
Stenzek 2025-04-19 15:59:18 +10:00
parent 46e11d96da
commit df2a5a5e67
No known key found for this signature in database
7 changed files with 295 additions and 284 deletions

View File

@ -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);

View File

@ -1273,7 +1273,7 @@ void GPUThread::DisplayWindowResizedOnThread()
// our imgui stuff can't cope with 0x0/hidden windows
const float f_width = static_cast<float>(std::max(swap_chain->GetWidth(), 1u));
const float f_height = static_cast<float>(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)

View File

@ -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<GPUShader> imgui_vs =
CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GenerateImGuiVertexShader(), error);
std::unique_ptr<GPUShader> 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<float>(swap_chain->GetWidth()), static_cast<float>(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<false>(&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<GPUTexture*>(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<float>(texture->GetWidth()), static_cast<float>(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<false>(&pcmd->ClipRect.x));
if (flip)
clip = FlipToLowerLeft(clip, texture->GetHeight());
SetScissor(clip);
SetTextureSampler(0, reinterpret_cast<GPUTexture*>(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<float, 4> GPUDevice::RGBA8ToFloat(u32 rgba)
static_cast<float>(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<u32>(width) &&
m_imgui_font_texture->GetHeight() == static_cast<u32>(height) &&
m_imgui_font_texture->Update(0, 0, static_cast<u32>(width), static_cast<u32>(height), pixels, pitch))
{
io.Fonts->SetTexID(m_imgui_font_texture.get());
return true;
}
Error error;
std::unique_ptr<GPUTexture> 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<GPUTexture>* 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<GPUTexture>* tex, u32 new_width, u
}
DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1));
std::unique_ptr<GPUTexture> new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags);
std::unique_ptr<GPUTexture> 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<GPUTexture>* 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<GPUTexture> 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;

View File

@ -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<GPUTexture>* 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<GPUTexture>* 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<GPUPipeline> m_imgui_pipeline;
std::unique_ptr<GPUTexture> m_imgui_font_texture;
SamplerMap m_sampler_map;
TexturePool m_texture_pool;

View File

@ -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,

View File

@ -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<s8, 2> left_stick_axis_state = {};
std::unique_ptr<GPUPipeline> imgui_pipeline;
std::unique_ptr<GPUTexture> 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<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f;
s_state.window_height =
g_gpu_device->HasMainSwapChain() ? static_cast<float>(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<float>(main_swap_chain->GetWidth()) : 0.0f;
s_state.window_height = main_swap_chain ? static_cast<float>(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<GPUShader> imgui_vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
shadergen.GenerateImGuiVertexShader(), error);
std::unique_ptr<GPUShader> 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<u32>(width), static_cast<u32>(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<s32>(window_width), static_cast<s32>(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<s32>(post_rotated_width), static_cast<s32>(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<float>(window_width),
static_cast<float>(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<false>(&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<GPUTexture*>(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;

View File

@ -1,8 +1,10 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "util/gpu_texture.h"
#include "common/types.h"
#include <memory>
@ -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();