diff --git a/DebugGUI/CMakeLists.txt b/DebugGUI/CMakeLists.txt index ef1c184..90d3df8 100644 --- a/DebugGUI/CMakeLists.txt +++ b/DebugGUI/CMakeLists.txt @@ -30,6 +30,7 @@ if(APPLE) src/implot_items.cpp # src/Sokol3DUtils.cxx src/HandMade3DImpl.cxx + src/DebugGUIHeadless.cxx src/DebugGUIMacos.mm) set(HEADERS src/imgui_extras.h @@ -52,6 +53,7 @@ elseif(GLFW_FOUND) src/Sokol3DUtils.cxx src/GL3DUtils.cxx src/HandMade3DImpl.cxx + src/DebugGUIHeadless.cxx src/DebugGUI.cxx) set(HEADERS src/imgui_extras.h diff --git a/DebugGUI/src/DebugGUI.cxx b/DebugGUI/src/DebugGUI.cxx index 8bebf5e..2bb2012 100644 --- a/DebugGUI/src/DebugGUI.cxx +++ b/DebugGUI/src/DebugGUI.cxx @@ -87,137 +87,6 @@ void* initGUI(const char* name, void(*error_callback)(int, char const*descriptio return window; } -// fills a stream with drawing data in JSON format -// the JSON is composed of a list of draw commands -/// FIXME: document the actual schema of the format. -void getFrameJSON(void *data, std::ostream& json_data) -{ - ImDrawData* draw_data = (ImDrawData*)data; - - json_data << "["; - - for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { - const auto cmd_list = draw_data->CmdLists[cmd_id]; - const auto vtx_buffer = cmd_list->VtxBuffer; - const auto idx_buffer = cmd_list->IdxBuffer; - const auto cmd_buffer = cmd_list->CmdBuffer; - - json_data << "{\"vtx\":["; - for (int i = 0; i < vtx_buffer.size(); ++i) { - auto v = vtx_buffer[i]; - json_data << "[" << v.pos.x << "," << v.pos.y << "," << v.col << ',' << v.uv.x << "," << v.uv.y << "]"; - if (i < vtx_buffer.size() - 1) json_data << ","; - } - - json_data << "],\"idx\":["; - for (int i = 0; i < idx_buffer.size(); ++i) { - auto id = idx_buffer[i]; - json_data << id; - if (i < idx_buffer.size() - 1) json_data << ","; - } - - json_data << "],\"cmd\":["; - for (int i = 0; i < cmd_buffer.size(); ++i) { - auto cmd = cmd_buffer[i]; - json_data << "{\"cnt\":" << cmd.ElemCount << ", \"clp\":[" << cmd.ClipRect.x << "," << cmd.ClipRect.y << "," << cmd.ClipRect.z << "," << cmd.ClipRect.w << "]}"; - if (i < cmd_buffer.size() - 1) json_data << ","; - } - json_data << "]}"; - if (cmd_id < draw_data->CmdListsCount - 1) json_data << ","; - } - - json_data << "]"; -} - - -struct vtxContainer { - float posX, posY, uvX, uvY; - int col; -}; - -struct cmdContainer { - int count; - float rectX, rectY, rectZ, rectW; -}; - -// given draw data, returns a mallocd buffer containing formatted frame data -// ready to be sent and its size. -// the returned buffer must be freed by the caller. -/// FIXME: document actual schema of the format -void getFrameRaw(void *data, void **raw_data, int *size) -{ - ImDrawData* draw_data = (ImDrawData*)data; - - // compute sizes - int buffer_size = sizeof(int)*3, - vtx_count = 0, - idx_count = 0, - cmd_count = 0; - for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { - const auto cmd_list = draw_data->CmdLists[cmd_id]; - - vtx_count += cmd_list->VtxBuffer.size(); - buffer_size += cmd_list->VtxBuffer.size() * (sizeof(vtxContainer)); - - idx_count += cmd_list->IdxBuffer.size(); - buffer_size += cmd_list->IdxBuffer.size() * (sizeof(short)); - - cmd_count += cmd_list->CmdBuffer.size(); - buffer_size += cmd_list->CmdBuffer.size() * (sizeof(cmdContainer)); - } - - void *local_data_base = malloc(buffer_size); - - int *data_header_ptr = (int*) local_data_base; - *data_header_ptr = vtx_count; data_header_ptr++; - *data_header_ptr = idx_count; data_header_ptr++; - *data_header_ptr = cmd_count; data_header_ptr++; - - vtxContainer* data_vtx_ptr = (vtxContainer*) data_header_ptr; - for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { - const auto cmd_list = draw_data->CmdLists[cmd_id]; - - for (auto const& vtx : cmd_list->VtxBuffer) { - data_vtx_ptr->posX = vtx.pos.x; - data_vtx_ptr->posY = vtx.pos.y; - data_vtx_ptr->uvX = vtx.uv.x; - data_vtx_ptr->uvY = vtx.uv.y; - data_vtx_ptr->col = vtx.col; - data_vtx_ptr++; - } - } - - int idx_offset = 0; - short* data_idx_ptr = (short*) data_vtx_ptr; - for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { - const auto cmd_list = draw_data->CmdLists[cmd_id]; - - for (auto const& idx : cmd_list->IdxBuffer) { - *data_idx_ptr = idx + (short)idx_offset; - data_idx_ptr++; - } - - idx_offset += cmd_list->VtxBuffer.size(); - } - - cmdContainer* data_cmd_ptr = (cmdContainer*) data_idx_ptr; - for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { - const auto cmd_list = draw_data->CmdLists[cmd_id]; - - for (auto const& cmd : cmd_list->CmdBuffer) { - data_cmd_ptr->count = cmd.ElemCount; - data_cmd_ptr->rectX = cmd.ClipRect.x; - data_cmd_ptr->rectY = cmd.ClipRect.y; - data_cmd_ptr->rectZ = cmd.ClipRect.z; - data_cmd_ptr->rectW = cmd.ClipRect.w; - data_cmd_ptr++; - } - } - - *size = buffer_size; - *raw_data = local_data_base; -} - bool pollGUIPreRender(void* context, float delta) { if (context) { diff --git a/DebugGUI/src/DebugGUIHeadless.cxx b/DebugGUI/src/DebugGUIHeadless.cxx new file mode 100644 index 0000000..82eccb5 --- /dev/null +++ b/DebugGUI/src/DebugGUIHeadless.cxx @@ -0,0 +1,142 @@ +#include + +#include "imgui.h" + +namespace o2::framework { + +// fills a stream with drawing data in JSON format +// the JSON is composed of a list of draw commands +/// FIXME: document the actual schema of the format. +void getFrameJSON(void *data, std::ostream &json_data) { + ImDrawData *draw_data = (ImDrawData *)data; + + json_data << "["; + + for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { + const auto cmd_list = draw_data->CmdLists[cmd_id]; + const auto vtx_buffer = cmd_list->VtxBuffer; + const auto idx_buffer = cmd_list->IdxBuffer; + const auto cmd_buffer = cmd_list->CmdBuffer; + + json_data << "{\"vtx\":["; + for (int i = 0; i < vtx_buffer.size(); ++i) { + auto v = vtx_buffer[i]; + json_data << "[" << v.pos.x << "," << v.pos.y << "," << v.col << ',' + << v.uv.x << "," << v.uv.y << "]"; + if (i < vtx_buffer.size() - 1) + json_data << ","; + } + + json_data << "],\"idx\":["; + for (int i = 0; i < idx_buffer.size(); ++i) { + auto id = idx_buffer[i]; + json_data << id; + if (i < idx_buffer.size() - 1) + json_data << ","; + } + + json_data << "],\"cmd\":["; + for (int i = 0; i < cmd_buffer.size(); ++i) { + auto cmd = cmd_buffer[i]; + json_data << "{\"cnt\":" << cmd.ElemCount << ", \"clp\":[" + << cmd.ClipRect.x << "," << cmd.ClipRect.y << "," + << cmd.ClipRect.z << "," << cmd.ClipRect.w << "]}"; + if (i < cmd_buffer.size() - 1) + json_data << ","; + } + json_data << "]}"; + if (cmd_id < draw_data->CmdListsCount - 1) + json_data << ","; + } + + json_data << "]"; +} + +struct vtxContainer { + float posX, posY, uvX, uvY; + int col; +}; + +struct cmdContainer { + int count; + float rectX, rectY, rectZ, rectW; +}; + +// given draw data, returns a mallocd buffer containing formatted frame data +// ready to be sent and its size. +// the returned buffer must be freed by the caller. +/// FIXME: document actual schema of the format +void getFrameRaw(void *data, void **raw_data, int *size) { + ImDrawData *draw_data = (ImDrawData *)data; + + // compute sizes + int buffer_size = sizeof(int) * 3, vtx_count = 0, idx_count = 0, + cmd_count = 0; + for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { + const auto cmd_list = draw_data->CmdLists[cmd_id]; + + vtx_count += cmd_list->VtxBuffer.size(); + buffer_size += cmd_list->VtxBuffer.size() * (sizeof(vtxContainer)); + + idx_count += cmd_list->IdxBuffer.size(); + buffer_size += cmd_list->IdxBuffer.size() * (sizeof(short)); + + cmd_count += cmd_list->CmdBuffer.size(); + buffer_size += cmd_list->CmdBuffer.size() * (sizeof(cmdContainer)); + } + + void *local_data_base = malloc(buffer_size); + + int *data_header_ptr = (int *)local_data_base; + *data_header_ptr = vtx_count; + data_header_ptr++; + *data_header_ptr = idx_count; + data_header_ptr++; + *data_header_ptr = cmd_count; + data_header_ptr++; + + vtxContainer *data_vtx_ptr = (vtxContainer *)data_header_ptr; + for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { + const auto cmd_list = draw_data->CmdLists[cmd_id]; + + for (auto const &vtx : cmd_list->VtxBuffer) { + data_vtx_ptr->posX = vtx.pos.x; + data_vtx_ptr->posY = vtx.pos.y; + data_vtx_ptr->uvX = vtx.uv.x; + data_vtx_ptr->uvY = vtx.uv.y; + data_vtx_ptr->col = vtx.col; + data_vtx_ptr++; + } + } + + int idx_offset = 0; + short *data_idx_ptr = (short *)data_vtx_ptr; + for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { + const auto cmd_list = draw_data->CmdLists[cmd_id]; + + for (auto const &idx : cmd_list->IdxBuffer) { + *data_idx_ptr = idx + (short)idx_offset; + data_idx_ptr++; + } + + idx_offset += cmd_list->VtxBuffer.size(); + } + + cmdContainer *data_cmd_ptr = (cmdContainer *)data_idx_ptr; + for (int cmd_id = 0; cmd_id < draw_data->CmdListsCount; ++cmd_id) { + const auto cmd_list = draw_data->CmdLists[cmd_id]; + + for (auto const &cmd : cmd_list->CmdBuffer) { + data_cmd_ptr->count = cmd.ElemCount; + data_cmd_ptr->rectX = cmd.ClipRect.x; + data_cmd_ptr->rectY = cmd.ClipRect.y; + data_cmd_ptr->rectZ = cmd.ClipRect.z; + data_cmd_ptr->rectW = cmd.ClipRect.w; + data_cmd_ptr++; + } + } + + *size = buffer_size; + *raw_data = local_data_base; +} +} // namespace o2::framework diff --git a/DebugGUI/src/DebugGUIMacos.mm b/DebugGUI/src/DebugGUIMacos.mm index cf20a02..3a0e871 100644 --- a/DebugGUI/src/DebugGUIMacos.mm +++ b/DebugGUI/src/DebugGUIMacos.mm @@ -1,8 +1,8 @@ +#include "icons_font_awesome.h" #include "imgui.h" -#include "implot.h" #include "imgui_impl_glfw.h" #include "imgui_impl_metal.h" -#include "icons_font_awesome.h" +#include "implot.h" // Needed by icons_font_awesome.ttf.h #include #include "icons_font_awesome.ttf.h" @@ -17,18 +17,16 @@ #include #include -static void default_error_callback(int error, const char* description) -{ +static void default_error_callback(int error, const char *description) { fprintf(stderr, "Error %d: %s\n", error, description); } -namespace o2::framework -{ +namespace o2::framework { struct DebugGUIContext { GLFWwindow *window; - id device; - id commandQueue; + id device; + id commandQueue; MTLRenderPassDescriptor* renderPassDescriptor; }; @@ -36,54 +34,90 @@ static void default_error_callback(int error, const char* description) GLFWwindow *window; // @return an object of kind GLFWwindow* as void* to avoid having a direct dependency -void* initGUI(const char* name, void(*error_callback)(int, char const*description)) -{ - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - - // Setup style - ImGui::StyleColorsDark(); - - // Setup window - if (error_callback == nullptr) { - glfwSetErrorCallback(default_error_callback); - } - if (!glfwInit()) - return nullptr; - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - DebugGUIContext *context = (DebugGUIContext*)(malloc(sizeof(DebugGUIContext))); - window = glfwCreateWindow(1280, 720, name, nullptr, nullptr); +void *initGUI(const char *name, + void (*error_callback)(int, char const *description)) { + DebugGUIContext *context = nullptr; + + if (name) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard + // Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable + // Gamepad Controls + + // Setup style + ImGui::StyleColorsDark(); + + // Setup window + if (error_callback == nullptr) { + glfwSetErrorCallback(default_error_callback); + } + if (!glfwInit()) + return nullptr; + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + context = (DebugGUIContext *)(malloc(sizeof(DebugGUIContext))); + window = glfwCreateWindow(1280, 720, name, nullptr, nullptr); - if (window == NULL) + if (window == NULL) return 0; - context->window = window; - context->device = MTLCreateSystemDefaultDevice();; - context->commandQueue = [context->device newCommandQueue]; - - // Setup Platform/Renderer backends - ImGui_ImplGlfw_InitForOpenGL(context->window, true); - ImGui_ImplMetal_Init(context->device); - - NSWindow *nswin = glfwGetCocoaWindow(context->window); - layer = [CAMetalLayer layer]; - layer.device = context->device; - layer.pixelFormat = MTLPixelFormatBGRA8Unorm; - nswin.contentView.layer = layer; - nswin.contentView.wantsLayer = YES; - - context->renderPassDescriptor = [MTLRenderPassDescriptor new]; + context->window = window; + context->device = MTLCreateSystemDefaultDevice(); + ; + context->commandQueue = [context->device newCommandQueue]; + + // Setup Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(context->window, true); + ImGui_ImplMetal_Init(context->device); + + NSWindow *nswin = glfwGetCocoaWindow(context->window); + layer = [CAMetalLayer layer]; + layer.device = context->device; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + nswin.contentView.layer = layer; + nswin.contentView.wantsLayer = YES; + + context->renderPassDescriptor = [MTLRenderPassDescriptor new]; + ImGui_ImplMetal_CreateDeviceObjects(layer.device); + } else { + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + ImGui::StyleColorsDark(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + } // Load Fonts - // (there is a default font, this is only if you want to change it. see extra_fonts/README.txt for more details) + // (there is a default font, this is only if you want to change it. see + // extra_fonts/README.txt for more details) + ImGuiIO &io = ImGui::GetIO(); io.Fonts->AddFontDefault(); - static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; - ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true; icons_config.FontDataOwnedByAtlas = false; - io.Fonts->AddFontFromMemoryTTF((void*)s_iconsFontAwesomeTtf, sizeof(s_iconsFontAwesomeTtf), 12.0f, &icons_config, icons_ranges); - ImGui_ImplMetal_CreateDeviceObjects(layer.device); + static const ImWchar icons_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; + ImFontConfig icons_config; + icons_config.MergeMode = true; + icons_config.PixelSnapH = true; + icons_config.FontDataOwnedByAtlas = false; + io.Fonts->AddFontFromMemoryTTF((void *)s_iconsFontAwesomeTtf, + sizeof(s_iconsFontAwesomeTtf), 12.0f, + &icons_config, icons_ranges); ImPlot::CreateContext(); return context; @@ -91,9 +125,15 @@ static void default_error_callback(int error, const char* description) float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f}; -/// @return true if we do not need to exit, false if we do. -bool pollGUI(void* context, std::function guiCallback) -{ +bool pollGUIPreRender(void *context, float delta) { + if (context == nullptr) { + // Just initialize new frame + ImGuiIO &io = ImGui::GetIO(); + io.DeltaTime = delta; + ImGui::NewFrame(); + return true; + } + DebugGUIContext* ctx = reinterpret_cast(context); NSWindow* nswin = glfwGetCocoaWindow(ctx->window); if (glfwWindowShouldClose(ctx->window)) { @@ -107,26 +147,37 @@ bool pollGUI(void* context, std::function guiCallback) layer.drawableSize = CGSizeMake(width, height); auto drawable = [layer nextDrawable]; - id commandBuffer = [ctx->commandQueue commandBuffer]; - ctx->renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + ctx->renderPassDescriptor.colorAttachments[0].clearColor = + MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], + clear_color[3]); ctx->renderPassDescriptor.colorAttachments[0].texture = drawable.texture; ctx->renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; ctx->renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; - id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:ctx->renderPassDescriptor]; - [renderEncoder pushDebugGroup:@"ImGui demo"]; // Start the Dear ImGui frame ImGui_ImplMetal_NewFrame(ctx->renderPassDescriptor); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - - - // This is where the magic actually happens... - if (guiCallback) { - guiCallback(); - } - ImGui::Render(); - ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder); + } + + return true; +} + +void pollGUIPostRender(void *context, void *drawData) { + if (!context) { + return; + } + DebugGUIContext *ctx = reinterpret_cast(context); + @autoreleasepool { + auto drawable = [layer nextDrawable]; + + id commandBuffer = [ctx->commandQueue commandBuffer]; + id renderEncoder = [commandBuffer + renderCommandEncoderWithDescriptor:ctx->renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"ImGui demo"]; + + ImGui_ImplMetal_RenderDrawData((ImDrawData *)drawData, commandBuffer, + renderEncoder); [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; @@ -134,11 +185,31 @@ bool pollGUI(void* context, std::function guiCallback) [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; } +} + +/// @return draw data as void* to avoid dependencies +void *pollGUIRender(std::function guiCallback) { + // This is where the magic actually happens... + if (guiCallback) { + guiCallback(); + } + ImGui::Render(); + + return ImGui::GetDrawData(); +} + +/// @return true if we do not need to exit, false if we do. +bool pollGUI(void *context, std::function guiCallback) { + if (!pollGUIPreRender(context, 1.0f / 60.0f)) { + return false; + } + // This is where the magic actually happens... + void *drawData = pollGUIRender(guiCallback); + pollGUIPostRender(context, drawData); return true; } -void disposeGUI() -{ +void disposeGUI() { ImGui_ImplMetal_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext();