diff --git a/impeller/playground/BUILD.gn b/impeller/playground/BUILD.gn index e1b38dfde4caa..b01803ed86281 100644 --- a/impeller/playground/BUILD.gn +++ b/impeller/playground/BUILD.gn @@ -17,8 +17,10 @@ impeller_component("playground") { "../entity:entity_shaders", "../fixtures:shader_fixtures", "../renderer", + "imgui:imgui_impeller_backend", "//flutter/testing", "//third_party/glfw", + "//third_party/imgui:imgui_glfw", ] public_configs = [ ":playground_config" ] diff --git a/impeller/playground/imgui/BUILD.gn b/impeller/playground/imgui/BUILD.gn new file mode 100644 index 0000000000000..9f1e0d3b89d0f --- /dev/null +++ b/impeller/playground/imgui/BUILD.gn @@ -0,0 +1,25 @@ +import("//flutter/impeller/tools/impeller.gni") + +impeller_shaders("imgui_shaders") { + name = "imgui_shaders" + shaders = [ + "imgui_raster.vert", + "imgui_raster.frag", + ] +} + +source_set("imgui_impeller_backend") { + testonly = true + + public_deps = [ + ":imgui_shaders", + "//third_party/imgui", + ] + + deps = [ "//flutter/impeller/renderer" ] + + sources = [ + "imgui_impl_impeller.cc", + "imgui_impl_impeller.h", + ] +} diff --git a/impeller/playground/imgui/imgui_impl_impeller.cc b/impeller/playground/imgui/imgui_impl_impeller.cc new file mode 100644 index 0000000000000..bda00edc5c22e --- /dev/null +++ b/impeller/playground/imgui/imgui_impl_impeller.cc @@ -0,0 +1,225 @@ +#include "imgui_impl_impeller.h" + +#include +#include +#include +#include + +#include "imgui_raster.frag.h" +#include "imgui_raster.vert.h" +#include "third_party/imgui/imgui.h" + +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/command.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/pipeline_builder.h" +#include "impeller/renderer/pipeline_library.h" +#include "impeller/renderer/range.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/renderer/texture.h" +#include "impeller/renderer/texture_descriptor.h" +#include "impeller/renderer/vertex_buffer.h" + +struct ImGui_ImplImpeller_Data { + std::shared_ptr context; + std::shared_ptr font_texture; + std::shared_ptr pipeline; + std::shared_ptr sampler; +}; + +static ImGui_ImplImpeller_Data* ImGui_ImplImpeller_GetBackendData() { + return ImGui::GetCurrentContext() + ? static_cast( + ImGui::GetIO().BackendRendererUserData) + : nullptr; +} + +bool ImGui_ImplImpeller_Init(std::shared_ptr context) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == nullptr && + "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + auto* bd = new ImGui_ImplImpeller_Data(); + io.BackendRendererUserData = reinterpret_cast(bd); + io.BackendRendererName = "imgui_impl_impeller"; + io.BackendFlags |= + ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the + // ImDrawCmd::VtxOffset field, + // allowing for large meshes. + + bd->context = context; + + // Generate/upload the font atlas. + { + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + auto texture_descriptor = impeller::TextureDescriptor{}; + texture_descriptor.format = impeller::PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = {width, height}; + texture_descriptor.mip_count = 1u; + + bd->font_texture = context->GetPermanentsAllocator()->CreateTexture( + impeller::StorageMode::kHostVisible, texture_descriptor); + IM_ASSERT(bd->font_texture != nullptr && + "Could not allocate ImGui font texture."); + + bool uploaded = bd->font_texture->SetContents(pixels, width * height * 4); + IM_ASSERT(uploaded && + "Could not upload ImGui font texture to device memory."); + } + + // Build the raster pipeline. + { + auto desc = impeller::PipelineBuilder:: + MakeDefaultPipelineDescriptor(*context); + desc->SetSampleCount(impeller::SampleCount::kCount4); + bd->pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + IM_ASSERT(bd->pipeline != nullptr && "Could not create ImGui pipeline."); + + bd->sampler = context->GetSamplerLibrary()->GetSampler({}); + IM_ASSERT(bd->pipeline != nullptr && "Could not create ImGui sampler."); + } + + return true; +} + +void ImGui_ImplImpeller_Shutdown() { + auto* bd = ImGui_ImplImpeller_GetBackendData(); + IM_ASSERT(bd != nullptr && + "No renderer backend to shutdown, or already shutdown?"); + delete bd; +} + +void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, + impeller::RenderPass& render_pass) { + if (draw_data->CmdListsCount == 0) { + return; // Nothing to render. + } + + using VS = impeller::ImguiRasterVertexShader; + using FS = impeller::ImguiRasterFragmentShader; + + auto* bd = ImGui_ImplImpeller_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplImpeller_Init()?"); + + size_t total_vtx_bytes = draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t total_idx_bytes = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + if (!total_vtx_bytes || !total_idx_bytes) { + return; // Nothing to render. + } + + // Allocate buffer for vertices + indices. + auto buffer = bd->context->GetTransientsAllocator()->CreateBuffer( + impeller::StorageMode::kHostVisible, total_vtx_bytes + total_idx_bytes); + buffer->SetLabel(impeller::SPrintF("ImGui vertex+index buffer")); + + VS::UniformBuffer uniforms; + uniforms.mvp = impeller::Matrix::MakeOrthographic( + impeller::Size(draw_data->DisplaySize.x, draw_data->DisplaySize.y)); + uniforms.mvp = uniforms.mvp.Translate( + -impeller::Vector3(draw_data->DisplayPos.x, draw_data->DisplayPos.y)); + + size_t vertex_buffer_offset = 0; + size_t index_buffer_offset = total_vtx_bytes; + + for (int draw_list_i = 0; draw_list_i < draw_data->CmdListsCount; + draw_list_i++) { + const ImDrawList* cmd_list = draw_data->CmdLists[draw_list_i]; + + auto draw_list_vtx_bytes = + static_cast(cmd_list->VtxBuffer.size_in_bytes()); + auto draw_list_idx_bytes = + static_cast(cmd_list->IdxBuffer.size_in_bytes()); + + if (!buffer->CopyHostBuffer( + reinterpret_cast(cmd_list->VtxBuffer.Data), + impeller::Range{0, draw_list_vtx_bytes}, vertex_buffer_offset)) { + IM_ASSERT(false && "Could not copy vertices to buffer."); + } + if (!buffer->CopyHostBuffer( + reinterpret_cast(cmd_list->IdxBuffer.Data), + impeller::Range{0, draw_list_idx_bytes}, index_buffer_offset)) { + IM_ASSERT(false && "Could not copy indices to buffer."); + } + + auto viewport = impeller::Viewport{ + .rect = + impeller::Rect(draw_data->DisplayPos.x, draw_data->DisplayPos.y, + draw_data->DisplaySize.x, draw_data->DisplaySize.y)}; + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if (pcmd->UserCallback) { + pcmd->UserCallback(cmd_list, pcmd); + } else { + // Project scissor/clipping rectangles into framebuffer space. + impeller::IPoint clip_min(pcmd->ClipRect.x - draw_data->DisplayPos.x, + pcmd->ClipRect.y - draw_data->DisplayPos.y); + impeller::IPoint clip_max(pcmd->ClipRect.z - draw_data->DisplayPos.x, + pcmd->ClipRect.w - draw_data->DisplayPos.y); + // Ensure the scissor never goes out of bounds. + clip_min.x = std::clamp( + clip_min.x, 0ll, + static_cast(draw_data->DisplaySize.x)); + clip_min.y = std::clamp( + clip_min.y, 0ll, + static_cast(draw_data->DisplaySize.y)); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) { + continue; // Nothing to render. + } + + impeller::Command cmd; + cmd.label = impeller::SPrintF("ImGui draw list %d (command %d)", + draw_list_i, cmd_i); + + cmd.viewport = viewport; + cmd.scissor = impeller::IRect::MakeLTRB( + std::max(0ll, clip_min.x), std::max(0ll, clip_min.y), + std::min(render_pass.GetRenderTargetSize().width, clip_max.x), + std::min(render_pass.GetRenderTargetSize().height, clip_max.y)); + + cmd.winding = impeller::WindingOrder::kClockwise; + cmd.pipeline = bd->pipeline; + VS::BindUniformBuffer( + cmd, render_pass.GetTransientsBuffer().EmplaceUniform(uniforms)); + FS::BindTex(cmd, bd->font_texture, bd->sampler); + + size_t vb_start = + vertex_buffer_offset + pcmd->VtxOffset * sizeof(ImDrawVert); + + impeller::VertexBuffer vertex_buffer; + vertex_buffer.vertex_buffer = { + .buffer = buffer, + .range = impeller::Range(vb_start, draw_list_vtx_bytes - vb_start)}; + vertex_buffer.index_buffer = { + .buffer = buffer, + .range = impeller::Range( + index_buffer_offset + pcmd->IdxOffset * sizeof(ImDrawIdx), + pcmd->ElemCount * sizeof(ImDrawIdx))}; + vertex_buffer.index_count = pcmd->ElemCount; + vertex_buffer.index_type = impeller::IndexType::k16bit; + cmd.BindVertices(vertex_buffer); + cmd.base_vertex = pcmd->VtxOffset; + cmd.primitive_type = impeller::PrimitiveType::kTriangle; + + render_pass.AddCommand(std::move(cmd)); + } + } + + vertex_buffer_offset += draw_list_vtx_bytes; + index_buffer_offset += draw_list_idx_bytes; + } +} diff --git a/impeller/playground/imgui/imgui_impl_impeller.h b/impeller/playground/imgui/imgui_impl_impeller.h new file mode 100644 index 0000000000000..ffdaaa072d5a2 --- /dev/null +++ b/impeller/playground/imgui/imgui_impl_impeller.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "third_party/imgui/imgui.h" + +namespace impeller { +class Context; +class RenderPass; +} // namespace impeller + +IMGUI_IMPL_API bool ImGui_ImplImpeller_Init( + std::shared_ptr context); + +IMGUI_IMPL_API void ImGui_ImplImpeller_Shutdown(); + +IMGUI_IMPL_API void ImGui_ImplImpeller_RenderDrawData( + ImDrawData* draw_data, + impeller::RenderPass& renderpass); diff --git a/impeller/playground/imgui/imgui_raster.frag b/impeller/playground/imgui/imgui_raster.frag new file mode 100644 index 0000000000000..890a784245885 --- /dev/null +++ b/impeller/playground/imgui/imgui_raster.frag @@ -0,0 +1,10 @@ +in vec2 frag_texture_coordinates; +in vec4 frag_vertex_color; + +out vec4 frag_color; + +uniform sampler2D tex; + +void main() { + frag_color = frag_vertex_color * texture(tex, frag_texture_coordinates.st); +} diff --git a/impeller/playground/imgui/imgui_raster.vert b/impeller/playground/imgui/imgui_raster.vert new file mode 100644 index 0000000000000..391c14eb3d005 --- /dev/null +++ b/impeller/playground/imgui/imgui_raster.vert @@ -0,0 +1,17 @@ +uniform UniformBuffer { + mat4 mvp; +} +uniforms; + +in vec2 vertex_position; +in vec2 texture_coordinates; +in uint vertex_color; + +out vec2 frag_texture_coordinates; +out vec4 frag_vertex_color; + +void main() { + gl_Position = uniforms.mvp * vec4(vertex_position.xy, 0.0, 1.0); + frag_texture_coordinates = texture_coordinates; + frag_vertex_color = unpackUnorm4x8(vertex_color); +} diff --git a/impeller/playground/playground.mm b/impeller/playground/playground.mm index df4867ecaa423..183f91aaa48e9 100644 --- a/impeller/playground/playground.mm +++ b/impeller/playground/playground.mm @@ -10,6 +10,8 @@ #include "flutter/testing/testing.h" #include "impeller/base/validation.h" #include "impeller/image/compressed_image.h" +#include "impeller/playground/imgui/imgui_impl_impeller.h" +#include "impeller/playground/imgui/imgui_shaders.h" #include "impeller/playground/playground.h" #include "impeller/renderer/allocator.h" #include "impeller/renderer/backend/metal/context_mtl.h" @@ -20,6 +22,8 @@ #include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/renderer.h" +#include "third_party/imgui/backends/imgui_impl_glfw.h" +#include "third_party/imgui/imgui.h" #define GLFW_INCLUDE_NONE #import "third_party/glfw/include/GLFW/glfw3.h" @@ -38,6 +42,8 @@ impeller_entity_shaders_length), std::make_shared(impeller_shader_fixtures_data, impeller_shader_fixtures_length), + std::make_shared(impeller_imgui_shaders_data, + impeller_imgui_shaders_length), }; } @@ -93,6 +99,13 @@ static void PlaygroundKeyCallback(GLFWwindow* window, return false; } + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + fml::ScopedCleanupClosure destroy_imgui_context( + []() { ImGui::DestroyContext(); }); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + if (::glfwInit() != GLFW_TRUE) { return false; } @@ -129,6 +142,13 @@ static void PlaygroundKeyCallback(GLFWwindow* window, fml::ScopedCleanupClosure close_window( [window]() { ::glfwDestroyWindow(window); }); + ImGui_ImplGlfw_InitForOther(window, true); + fml::ScopedCleanupClosure shutdown_imgui([]() { ImGui_ImplGlfw_Shutdown(); }); + + ImGui_ImplImpeller_Init(renderer_.GetContext()); + fml::ScopedCleanupClosure shutdown_imgui_impeller( + []() { ImGui_ImplImpeller_Shutdown(); }); + NSWindow* cocoa_window = ::glfwGetCocoaWindow(window); CAMetalLayer* layer = [CAMetalLayer layer]; layer.device = ContextMTL::Cast(*renderer_.GetContext()).GetMTLDevice(); @@ -144,6 +164,8 @@ static void PlaygroundKeyCallback(GLFWwindow* window, return true; } + ImGui_ImplGlfw_NewFrame(); + const auto layer_size = layer.bounds.size; const auto layer_scale = layer.contentsScale; layer.drawableSize = CGSizeMake(layer_size.width * layer_scale, @@ -151,7 +173,12 @@ static void PlaygroundKeyCallback(GLFWwindow* window, Renderer::RenderCallback wrapped_callback = [render_callback](auto& pass) { pass.SetLabel("Playground Main Render Pass"); - return render_callback(pass); + + ImGui::NewFrame(); + bool result = render_callback(pass); + ImGui::Render(); + ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), pass); + return result; }; if (!renderer_.Render(SurfaceMTL::WrapCurrentMetalLayerDrawable(