From 7b8d2db565dc1002093081a8b4a172184a46d71f Mon Sep 17 00:00:00 2001 From: Attila Uygun Date: Sat, 14 Oct 2023 13:21:28 +0200 Subject: [PATCH] Implement ImguiBackend --- assets/engine/BUILD.gn | 6 +- assets/engine/imgui.glsl_fragment | 18 ++++ assets/engine/imgui.glsl_vertex | 17 ++++ src/engine/BUILD.gn | 2 + src/engine/engine.cc | 13 +++ src/engine/engine.h | 3 + src/engine/imgui_backend.cc | 149 ++++++++++++++++++++++++++++++ src/engine/imgui_backend.h | 37 ++++++++ 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 assets/engine/imgui.glsl_fragment create mode 100644 assets/engine/imgui.glsl_vertex create mode 100644 src/engine/imgui_backend.cc create mode 100644 src/engine/imgui_backend.h diff --git a/assets/engine/BUILD.gn b/assets/engine/BUILD.gn index 9d96d85..c94c609 100644 --- a/assets/engine/BUILD.gn +++ b/assets/engine/BUILD.gn @@ -1,11 +1,13 @@ copy("engine") { sources = [ + "RobotoMono-Regular.ttf", + "imgui.glsl_fragment", + "imgui.glsl_vertex", "pass_through.glsl_fragment", "pass_through.glsl_vertex", - "RobotoMono-Regular.ttf", "solid.glsl_fragment", "solid.glsl_vertex", ] - outputs = ["$root_out_dir/assets/engine/{{source_file_part}}"] + outputs = [ "$root_out_dir/assets/engine/{{source_file_part}}" ] } diff --git a/assets/engine/imgui.glsl_fragment b/assets/engine/imgui.glsl_fragment new file mode 100644 index 0000000..5d12326 --- /dev/null +++ b/assets/engine/imgui.glsl_fragment @@ -0,0 +1,18 @@ +#ifdef GL_ES +precision mediump float; +#endif + +IN(0) vec2 tex_coord_0; +IN(1) vec4 color; + +UNIFORM_BEGIN + UNIFORM_V(mat4 projection) +UNIFORM_END + +SAMPLER(0, sampler2D texture_0) + +FRAG_COLOR_OUT(frag_color) + +void main() { + FRAG_COLOR(frag_color) = TEXTURE(texture_0, tex_coord_0) * color; +} diff --git a/assets/engine/imgui.glsl_vertex b/assets/engine/imgui.glsl_vertex new file mode 100644 index 0000000..5f8f33d --- /dev/null +++ b/assets/engine/imgui.glsl_vertex @@ -0,0 +1,17 @@ +IN(0) vec2 in_position; +IN(1) vec2 in_tex_coord_0; +IN(2) vec4 in_color; + +UNIFORM_BEGIN + UNIFORM_V(mat4 projection) +UNIFORM_END + +OUT(0) vec2 tex_coord_0; +OUT(1) vec4 color; + +void main() { + tex_coord_0 = in_tex_coord_0; + color = in_color; + + gl_Position = PARAM(projection) * vec4(in_position, 0.0, 1.0); +} diff --git a/src/engine/BUILD.gn b/src/engine/BUILD.gn index f042dd9..e9727cd 100644 --- a/src/engine/BUILD.gn +++ b/src/engine/BUILD.gn @@ -22,6 +22,8 @@ source_set("engine") { "game_factory.h", "image_quad.cc", "image_quad.h", + "imgui_backend.cc", + "imgui_backend.h", "input_event.h", "persistent_data.cc", "persistent_data.h", diff --git a/src/engine/engine.cc b/src/engine/engine.cc index bfbd9c2..83ec335 100644 --- a/src/engine/engine.cc +++ b/src/engine/engine.cc @@ -53,6 +53,7 @@ Engine::~Engine() { thread_pool_.CancelTasks(); thread_pool_.Shutdown(); + imgui_backend_.Shutdown(); game_.reset(); stats_.reset(); textures_.clear(); @@ -125,6 +126,9 @@ void Engine::Initialize() { // Create resources and let the game finalize initialization. CreateRenderResources(); WaitForAsyncWork(); + + imgui_backend_.Initialize(); + CHECK(game_->Initialize()) << "Failed to initialize the game."; } @@ -161,7 +165,10 @@ void Engine::Draw(float frame_frac) { d->Draw(frame_frac); } + imgui_backend_.Render(); + renderer_->Present(); + imgui_backend_.NewFrame(); } void Engine::AddDrawable(Drawable* drawable) { @@ -516,6 +523,10 @@ void Engine::GainedFocus(bool from_interstitial_ad) { } void Engine::AddInputEvent(std::unique_ptr event) { + event = imgui_backend_.OnInputEvent(std::move(event)); + if (!event) + return; + if (replaying_) return; @@ -644,6 +655,8 @@ void Engine::CreateRenderResources() { LOG(0) << "Could not create solid shader."; } + imgui_backend_.CreateRenderResources(renderer_.get()); + for (auto& t : textures_) { t.second.texture->SetRenderer(renderer_.get()); if (t.second.persistent || t.second.use_count > 0) { diff --git a/src/engine/engine.h b/src/engine/engine.h index c330a36..05042e7 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -10,6 +10,7 @@ #include "base/random.h" #include "base/thread_pool.h" #include "base/vecmath.h" +#include "engine/imgui_backend.h" #include "engine/persistent_data.h" #include "engine/platform/platform_observer.h" @@ -196,6 +197,8 @@ class Engine : public PlatformObserver { std::unique_ptr stats_; + ImguiBackend imgui_backend_; + float fps_seconds_ = 0; int fps_ = 0; diff --git a/src/engine/imgui_backend.cc b/src/engine/imgui_backend.cc new file mode 100644 index 0000000..54edeb2 --- /dev/null +++ b/src/engine/imgui_backend.cc @@ -0,0 +1,149 @@ +#include "engine/imgui_backend.h" + +#include "base/log.h" +#include "engine/asset/image.h" +#include "engine/asset/mesh.h" +#include "engine/asset/shader_source.h" +#include "engine/engine.h" +#include "engine/input_event.h" +#include "engine/renderer/renderer.h" +#include "engine/renderer/shader.h" +#include "engine/renderer/texture.h" +#include "third_party/imgui/imgui.h" + +using namespace base; + +namespace eng { + +ImguiBackend::ImguiBackend() : shader_{std::make_unique(nullptr)} {} + +ImguiBackend::~ImguiBackend() = default; + +void ImguiBackend::Initialize() { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + Engine::Get().SetImageSource( + "imgui_atlas", + []() -> std::unique_ptr { + // Build texture atlas + unsigned char* pixels; + int width, height; + ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + LOG(0) << "imgui font width: " << width << " height: " << height; + auto image = std::make_unique(); + image->Create(width, height); + memcpy(image->GetBuffer(), pixels, width * height * 4); + return image; + }, + true); + Engine::Get().RefreshImage("imgui_atlas"); + ImGui::GetIO().Fonts->SetTexID( + (ImTextureID)(intptr_t)Engine::Get().AcquireTexture("imgui_atlas")); + + NewFrame(); +} + +void ImguiBackend::Shutdown() { + ImGui::DestroyContext(); + shader_.reset(); +} + +void ImguiBackend::CreateRenderResources(Renderer* renderer) { + renderer_ = renderer; + shader_->SetRenderer(renderer); + + auto source = std::make_unique(); + if (source->Load("engine/imgui.glsl")) { + static const char vertex_description[] = "p2f;t2f;c4b"; + VertexDescription vd; + if (!ParseVertexDescription(vertex_description, vd)) { + DLOG(0) << "Failed to parse vertex description."; + } else { + shader_->Create(std::move(source), vd, kPrimitive_Triangles, false); + } + } else { + LOG(0) << "Could not create imgui shader."; + } +} + +std::unique_ptr ImguiBackend::OnInputEvent( + std::unique_ptr event) { + ImGuiIO& io = ImGui::GetIO(); + switch (event->GetType()) { + case InputEvent::kDragStart: + io.AddMousePosEvent(event->GetVector().x, event->GetVector().y); + io.AddMouseButtonEvent(0, true); + break; + case InputEvent::kDragEnd: + io.AddMousePosEvent(event->GetVector().x, event->GetVector().y); + io.AddMouseButtonEvent(0, false); + break; + case InputEvent::kDrag: + io.AddMousePosEvent(event->GetVector().x, event->GetVector().y); + break; + default: + break; + } + // TODO: Keyboard input + + if (io.WantCaptureMouse) + event.reset(); + return event; +} + +void ImguiBackend::NewFrame() { + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2((float)renderer_->GetScreenWidth(), + (float)renderer_->GetScreenHeight()); + io.DeltaTime = timer_.Elapsed(); + ImGui::NewFrame(); +} + +void ImguiBackend::Render() { + ImGui::Render(); + + ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount < -0) + return; + + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + base::Matrix4f ortho_projection; + ortho_projection.CreateOrthoProjection(L, R, B, T); + + for (int n = 0; n < draw_data->CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + static const char vertex_description[] = "p2f;t2f;c4b"; + auto mesh = std::make_unique(); + mesh->Create(kPrimitive_Triangles, vertex_description, + cmd_list->VtxBuffer.Size, cmd_list->VtxBuffer.Data, + kDataType_UShort, cmd_list->IdxBuffer.Size, + cmd_list->IdxBuffer.Data); + auto geometry_id = renderer_->CreateGeometry(std::move(mesh)); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + reinterpret_cast(pcmd->GetTexID())->Activate(); + + // Vulkan renderer needs activating the shader again after a texture. + // TODO: Fix it in vulkan renderer and active the shader only once. + shader_->Activate(); + shader_->SetUniform("projection", ortho_projection); + shader_->UploadUniforms(); + + if (pcmd->ClipRect.z > pcmd->ClipRect.x && + pcmd->ClipRect.w > pcmd->ClipRect.y) { + renderer_->SetScissor(int(pcmd->ClipRect.x), int(pcmd->ClipRect.y), + int(pcmd->ClipRect.z - pcmd->ClipRect.x), + int(pcmd->ClipRect.w - pcmd->ClipRect.y)); + } + renderer_->Draw(geometry_id, pcmd->ElemCount, pcmd->IdxOffset); + } + renderer_->DestroyGeometry(geometry_id); + } + renderer_->ResetScissor(); +} + +} // namespace eng diff --git a/src/engine/imgui_backend.h b/src/engine/imgui_backend.h new file mode 100644 index 0000000..109a179 --- /dev/null +++ b/src/engine/imgui_backend.h @@ -0,0 +1,37 @@ +#ifndef ENGINE_IMGUI_BACKEND_H +#define ENGINE_IMGUI_BACKEND_H + +#include + +#include "base/timer.h" + +namespace eng { + +class InputEvent; +class Shader; +class Renderer; + +class ImguiBackend { + public: + ImguiBackend(); + ~ImguiBackend(); + + void Initialize(); + void Shutdown(); + + void CreateRenderResources(Renderer* renderer); + + std::unique_ptr OnInputEvent(std::unique_ptr event); + + void NewFrame(); + void Render(); + + private: + std::unique_ptr shader_; + Renderer* renderer_ = nullptr; + base::ElapsedTimer timer_; +}; + +} // namespace eng + +#endif // ENGINE_IMGUI_BACKEND_H