Implement ImguiBackend

This commit is contained in:
Attila Uygun 2023-10-14 13:21:28 +02:00
parent eed6d4751c
commit 7b8d2db565
8 changed files with 243 additions and 2 deletions

View File

@ -1,8 +1,10 @@
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",
]

View File

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

View File

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

View File

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

View File

@ -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<InputEvent> 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) {

View File

@ -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<ImageQuad> stats_;
ImguiBackend imgui_backend_;
float fps_seconds_ = 0;
int fps_ = 0;

149
src/engine/imgui_backend.cc Normal file
View File

@ -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<Shader>(nullptr)} {}
ImguiBackend::~ImguiBackend() = default;
void ImguiBackend::Initialize() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
Engine::Get().SetImageSource(
"imgui_atlas",
[]() -> std::unique_ptr<Image> {
// 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>();
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<ShaderSource>();
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<InputEvent> ImguiBackend::OnInputEvent(
std::unique_ptr<InputEvent> 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>();
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<Texture*>(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

View File

@ -0,0 +1,37 @@
#ifndef ENGINE_IMGUI_BACKEND_H
#define ENGINE_IMGUI_BACKEND_H
#include <memory>
#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<InputEvent> OnInputEvent(std::unique_ptr<InputEvent> event);
void NewFrame();
void Render();
private:
std::unique_ptr<Shader> shader_;
Renderer* renderer_ = nullptr;
base::ElapsedTimer timer_;
};
} // namespace eng
#endif // ENGINE_IMGUI_BACKEND_H