diff --git a/.agents/docs/2026-06-03-imgui-backend-abstraction-design.md b/.agents/docs/2026-06-03-imgui-backend-abstraction-design.md new file mode 100644 index 0000000..704e144 --- /dev/null +++ b/.agents/docs/2026-06-03-imgui-backend-abstraction-design.md @@ -0,0 +1,307 @@ +# imgui-m: Backend Abstraction & Cross-Platform Design + +> 状态: design (approved) → implementing +> 分支: `codex/fix-imgui-window-clear` (PR #3) +> Last updated: 2026-06-03 +> 目标: 把 imgui-m 拆成 `imgui.core` + 通用 backend 抽象层 + 可替换的后端实现, +> 让"换后端只改 import + 一行 alias",并把工程从 Linux-only 推进到 Linux/macOS/Windows。 + +## 1. 背景与问题 + +当前结构(`0.0.1`)有三个限制: + +1. **后端职责越界 + 组合模块大量手工转发。** `imgui.backend.glfw_opengl3` + 把整个子模块 API 逐函数手抄转发一遍(~每个函数一处),N 处同步漂移风险。 +2. **没有显式的"后端抽象层"。** 不同后端之间没有一个被编译期约束的统一表面, + 消费者无法做到"换后端 = 换 import"。 +3. **工程只验证过 Linux/x86_64。** `mcpp.toml` 只声明 `linux` toolchain,CI 只有 + `ubuntu-latest`;示例硬编码 `ConfigureOpenGL(3,3,true,false)`,在 macOS 上会因 + 缺少 forward-compat + core profile 直接创建上下文失败。 + +## 2. 设计目标 + +- `imgui.core` 与 backend 彻底分层;backend 实现模块**不 re-export `imgui.core`**。 +- 提供**通用 backend 抽象层(契约)**:用 C++20 `concept` 在编译期约束每个后端的 + 统一 API 形状。 +- 每个后端对外暴露**单一 `Backend` 类型**(全 static 方法),职责包含:窗口生命周期、 + GL/帧缓冲操作、ImGui platform/renderer 绑定、错误诊断。 +- **用法一致**:`import imgui.core; import imgui.backend.;` 后,业务代码在不同 + 后端间逐字复用,换后端只改 import 行 + 一行 `using Backend = ...`。 +- 跨平台:Linux / macOS / Windows 都能 `mcpp build`;`RecommendedGlConfig()` 自动给出 + 各平台正确的 GL/GLSL 配置。 + +### 非目标 (Non-Goals) + +- 不打包 `libGL`/GLX/EGL/Mesa/驱动(沿用既有 GL runtime boundary 约定)。 +- 本次不实现 SDL/Vulkan/Metal/DX 后端,只**预留**扩展位。 +- 不追求把 Dear ImGui 全量 API 手抄进 `imgui.core`(见 §7)。 +- 不在 headless CI 里依赖真实显示;真实窗口运行是独立的、需显示环境的步骤。 + +## 3. 模块分层 + +``` +imgui.core # 纯 Dear ImGui:context / frame / widgets / draw-data +imgui.backend # ★契约层:共享值类型 + BackendApi concept(无实现、不依赖平台库) +│ +├─ imgui.backend.platform.glfw # 平台片:窗口/事件 + ImGui_ImplGlfw_*(可复用拼装单元) +├─ imgui.backend.renderer.opengl3 # 渲染片:GL 操作 + ImGui_ImplOpenGL3_*(可复用拼装单元) +│ +└─ imgui.backend.glfw_opengl3 # ★组合实现:装配 platform+renderer 为满足契约的单一 Backend 类型 + (预留) imgui.backend.sdl3_opengl3, imgui.backend.glfw_vulkan, ... +``` + +设计理由: + +- **契约层 (`imgui.backend`)** 是"通用 backend 抽象层"的载体:只放跨后端共享的*值类型* + 与*概念*,不放任何实现,不 `import` 平台库。它给出统一表面的"形状定义"。 +- **platform / renderer 片**对应 Dear ImGui 自身的 platform/renderer 拆分,是可复用的 + 拼装单元,避免 `GLFW×OpenGL3`、`GLFW×Vulkan`、`SDL×OpenGL3` 的组合爆炸。 +- **组合实现**把一个 platform 片和一个 renderer 片装配成对外的单一 `Backend` 类型, + 并用 `static_assert(BackendApi<...>)` 在编译期保证它符合契约。 + +### import 规则(硬约束) + +- 所有 backend 模块**只 `import imgui.core`(私有,仅为签名用到 `ImDrawData` 等)**, + **绝不 `export import imgui.core`**。消费者必须自己 `import imgui.core`。 +- 但组合实现模块**应 `export import imgui.backend`**:`GlConfig`/`Error`/`FbSize` + 这些共享值类型是后端公开表面的一部分,消费者需要直接命名它们(例如接住 + `Backend::LastError()` 的返回值)。"不 re-export" 的约束只针对 `imgui.core`, + 不针对抽象层本身。 +- 消费者两种合法用法: + - 纯逻辑 / headless:`import imgui.core;` + - 带窗口:`import imgui.core; import imgui.backend.;`(两个 import 都要显式写) + +## 4. 契约层 `imgui.backend` + +只含跨后端共享的值类型、跨平台默认配置、以及统一 API 的 `concept`。 + +```cpp +export module imgui.backend; + +import imgui.core; // 私有:仅 RenderDrawData(ImDrawData*) 等签名需要,不 re-export + +export namespace ImGui::Backend { + // ---- 共享值类型 ---- + struct GlConfig { + int major = 3; + int minor = 3; + bool coreProfile = true; + bool forwardCompat = false; + const char* glsl = nullptr; // nullptr => 让 renderer 选默认 + }; + struct Error { int code = 0; const char* description = nullptr; }; + struct FbSize { int width = 0; int height = 0; }; + struct Rgba { float r = 0, g = 0, b = 0, a = 1; }; + + // ---- 跨平台正确默认值 ---- + // macOS: 强制 GL>=3.2 core + forwardCompat,glsl="#version 150" + // Linux/Windows: GL 3.3 core,glsl="#version 130"/nullptr + GlConfig RecommendedGlConfig(); + + // ---- 编译期契约:每个后端必须满足的统一 API 形状 ---- + template + concept BackendApi = requires ( + typename T::Window* w, const char** desc, ImDrawData* dd, + int i, float f, GlConfig cfg + ) { + typename T::Window; + { T::InitGlfw() } -> std::same_as; + { T::TerminateGlfw() } -> std::same_as; + { T::CreateWindow(i, i, "") } -> std::same_as; + { T::DestroyWindow(w) } -> std::same_as; + { T::MakeContextCurrent(w) } -> std::same_as; + { T::Init(w, cfg) } -> std::same_as; + { T::NewFrame() } -> std::same_as; + { T::Viewport(i, i, i, i) } -> std::same_as; + { T::ClearColor(f, f, f, f) } -> std::same_as; + { T::ClearColorBuffer() } -> std::same_as; + { T::RenderDrawData(dd) } -> std::same_as; + { T::SwapBuffers(w) } -> std::same_as; + { T::PollEvents() } -> std::same_as; + { T::WindowShouldClose(w) } -> std::same_as; + { T::FramebufferSize(w) } -> std::same_as; + { T::Shutdown() } -> std::same_as; + { T::LastError() } -> std::same_as; + }; +} +``` + +> 说明:`concept` 约束的是"类型 T 是否具备这组 static 成员",因此每个后端用一个 +> `struct` 承载 API。这既给出编译期保证,又让所有后端的调用写法完全一致。 + +## 5. 后端实现:单一 `Backend` 类型 + +### 5.1 平台片 `imgui.backend.platform.glfw` + +封装 GLFW 窗口/事件 + `ImGui_ImplGlfw_*`,导出一个 `struct GlfwPlatform`(全 static)。 +内部使用 `#define GLFW_INCLUDE_NONE` + `` + ``。 +负责:`InitGlfw/Terminate/CreateWindow/.../FramebufferSize/LastError` 与 ImGui 平台帧。 +GL 上下文创建按传入 `GlConfig` 设置 window hints(含 macOS forward-compat)。 + +### 5.2 渲染片 `imgui.backend.renderer.opengl3` + +封装 `` + loader,导出 `struct OpenGL3Renderer`(全 static): +`Init(glsl)/Shutdown/NewFrame/Viewport/ClearColor/ClearColorBuffer/RenderDrawData`。 + +### 5.3 组合实现 `imgui.backend.glfw_opengl3` + +```cpp +export module imgui.backend.glfw_opengl3; + +import imgui.core; // 私有,不 re-export +export import imgui.backend; // 契约层类型(GlConfig/Error/FbSize)随后端公开 +import imgui.backend.platform.glfw; // 私有装配单元 +import imgui.backend.renderer.opengl3; // 私有装配单元 + +export namespace ImGui::Backend { + struct GlfwOpenGL3 { + using Window = GlfwPlatform::Window; + using Monitor = GlfwPlatform::Monitor; + + static bool InitGlfw() { return GlfwPlatform::InitGlfw(); } + static void TerminateGlfw() { GlfwPlatform::TerminateGlfw(); } + static Window* CreateWindow(int w, int h, const char* t, + Monitor* m = nullptr, Window* s = nullptr); + static bool Init(Window* w, GlConfig cfg = RecommendedGlConfig()); + static void NewFrame(); // renderer + platform 帧 + static void Viewport(int, int, int, int); + static void ClearColor(float, float, float, float); + static void ClearColorBuffer(); + static void RenderDrawData(ImDrawData*); + static void SwapBuffers(Window*); + static void PollEvents(); + static bool WindowShouldClose(Window*); + static FbSize FramebufferSize(Window*); + static void Shutdown(); // renderer + platform 关闭 + static Error LastError(); + // ... 其余诊断/回调按需 + }; + + static_assert(BackendApi); // 编译期保证用法一致 +} +``` + +要点: +- **取消逐函数手工转发的"组合模块"形态** —— 现在是单一类型、单处定义,内部委托 + platform/renderer 片。 +- `Init` 内部:`GlfwPlatform::InitForOpenGL(...)` 失败即返回;再 `OpenGL3Renderer::Init(cfg.glsl)`, + 失败则回滚 platform 关闭(保留既有错误回滚语义)。 +- **不隐式清屏**:`RenderDrawData` 只画 ImGui,清屏交给应用(`ClearColor`/`ClearColorBuffer`), + 以支持"先渲染自己的场景、ImGui 作 overlay"——这正是 PR #3 的设计取向。 + +## 6. 用法一致(消费者视角) + +```cpp +import imgui.core; +import imgui.backend.glfw_opengl3; +using Backend = ImGui::Backend::GlfwOpenGL3; // ← 换后端只改这两行(import + alias) + +int main() { + if (!Backend::InitGlfw()) { /* Backend::LastError() */ return 1; } + auto* window = Backend::CreateWindow(960, 540, "demo"); + Backend::MakeContextCurrent(window); + + auto* ctx = ImGui::CreateContext(); + ImGui::SetCurrentContext(ctx); + Backend::Init(window); // 默认 RecommendedGlConfig(),跨平台正确 + + while (!Backend::WindowShouldClose(window)) { + Backend::PollEvents(); + Backend::NewFrame(); + ImGui::NewFrame(); + ImGui::Begin("hello"); ImGui::TextUnformatted("..."); ImGui::End(); + ImGui::Render(); + + auto fb = Backend::FramebufferSize(window); + Backend::Viewport(0, 0, fb.width, fb.height); + Backend::ClearColor(0.08f, 0.09f, 0.10f, 1.0f); + Backend::ClearColorBuffer(); + Backend::RenderDrawData(ImGui::GetDrawData()); + Backend::SwapBuffers(window); + } + Backend::Shutdown(); + ImGui::DestroyContext(ctx); + Backend::DestroyWindow(window); + Backend::TerminateGlfw(); +} +``` + +换成将来的 `imgui.backend.sdl3_opengl3` 时,业务循环逐字不变;`concept` + +`static_assert` 保证两个后端表面完全一致。 + +## 7. `imgui.core` 导出策略(可扩展性) + +当前 `core.cppm` 是手维护的 `using` 白名单,每加一个 ImGui 函数都要补一行,不可持续。 +本设计确立约定: + +- `imgui.core` 导出**常用核心子集**(context 生命周期、frame、常用 widgets、draw-data、 + 基础类型 `ImVec2/ImVec4/ImGuiContext/ImGuiIO/ImFontAtlas/ImDrawData`)。 +- 对暂未导出的冷门 API,提供**显式逃生舱口**:文档约定消费者可在自己的 TU 里 + `#include `(global module fragment)直接调用,与模块用法并存。 +- 不在本次扩成全量;后续按需增量补充导出子集。 + +> 本次实现范围:在 §3 分层与 §4–§6 落地的前提下,`imgui.core` 至少补齐 `ImVec4` 与 +> 示例所需 widgets;全量导出策略留作后续。 + +## 8. 跨平台工程化 + +### 8.1 toolchain + +`mcpp.toml` 增加 macOS / Windows toolchain 条目(与 mcpp 支持的工具链命名对齐), +例如: + +```toml +[toolchain] +linux = "llvm@20.1.7" +macos = "llvm@20.1.7" # 或系统 clang;最终以 mcpp-index 可用项为准 +windows = "llvm@20.1.7" +``` + +### 8.2 GL/GLSL 配置 + +`RecommendedGlConfig()` 按平台返回: + +| 平台 | major.minor | coreProfile | forwardCompat | glsl | +|---|---|---|---|---| +| Linux | 3.3 | true | false | `#version 130` | +| Windows | 3.3 | true | false | `#version 130` | +| macOS | 3.2 | true | **true** | `#version 150` | + +示例改用 `Backend::Init(window)`(默认即 `RecommendedGlConfig()`),**移除硬编码** +`ConfigureOpenGL(3,3,true,false)`。 + +### 8.3 CI 矩阵 + +`.github/workflows/ci.yml` 由单 `ubuntu-latest` 扩为 `ubuntu-latest` / +`macos-latest` / `windows-latest` 的 build-check 矩阵: + +- 每个 runner:`mcpp build` + `mcpp test` + build 三个 examples。 +- headless 例子 `examples/basic` 可在所有平台 `mcpp run`。 +- **真实窗口运行**保持为需显示环境的条件步骤(不进 headless CI 必经路径)。 + +## 9. 影响面 / 文件清单 + +新增: +- `src/backends/backend.cppm` (契约层) +- `src/backends/platform_glfw.cppm` (+ 既有 `glfw_impl.cpp`) +- `src/backends/renderer_opengl3.cppm` (+ 既有 `opengl3_impl.cpp`) + +改造: +- `src/backends/glfw_opengl3.cppm` → 单一 `GlfwOpenGL3` 类型 +- `src/core.cppm` → 补 `ImVec4` 等(§7) +- `tests/backend_test.cpp` → 针对新表面 + `static_assert(BackendApi<...>)` +- `examples/minimal_window`、`examples/glfw_opengl3` → 统一 `Backend::` 用法 + `RecommendedGlConfig()` +- `mcpp.toml`(toolchain + sources)、`.github/workflows/ci.yml`(矩阵) +- `docs/architecture.md`、`README.md` 同步 + +> 旧的 `imgui.backend.glfw` / `imgui.backend.opengl3` 自由函数命名空间被 +> platform/renderer 片取代。如需保留兼容名,可在过渡期保留薄别名;本次按"干净切换"处理。 + +## 10. 验证 + +- `mcpp build` / `mcpp test`(三平台 CI) +- `cd examples/basic && mcpp run`(headless,跨平台) +- `cd examples/minimal_window && mcpp build` +- `cd examples/glfw_opengl3 && mcpp build` +- **真实窗口运行**(本机 DISPLAY + OpenGL runtime 可用时): + `cd examples/minimal_window && mcpp run` 观察窗口出现、拖动无残影。 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af60393..682d775 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,44 +16,77 @@ concurrency: jobs: mcpp: - runs-on: ubuntu-latest - timeout-minutes: 30 + name: mcpp (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + experimental: false + - os: macos-latest + experimental: true + - os: windows-latest + experimental: true + runs-on: ${{ matrix.os }} + # Linux is the required, must-pass platform. macOS/Windows are best-effort + # while mcpp-index runtime/toolchain support for them settles, so they do + # not block the overall CI result. + continue-on-error: ${{ matrix.experimental }} + timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@v4 - - name: Install Xlings + - name: Install Xlings (Linux/macOS) + if: runner.os != 'Windows' env: XLINGS_NON_INTERACTIVE: 1 + shell: bash run: | curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - name: Install Xlings (Windows) + if: runner.os == 'Windows' + env: + XLINGS_NON_INTERACTIVE: 1 + shell: pwsh + run: | + irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex + "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install project tools + shell: bash run: | xlings update xlings install - name: Show mcpp version + shell: bash run: mcpp --version - name: Build library + shell: bash run: mcpp build - name: Run tests + shell: bash run: mcpp test - name: Run headless example + shell: bash run: | cd examples/basic mcpp run - name: Build minimal window example + shell: bash run: | cd examples/minimal_window mcpp build - name: Build GLFW/OpenGL3 example + shell: bash run: | cd examples/glfw_opengl3 mcpp build diff --git a/README.md b/README.md index 46f8e1c..2111c8b 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,24 @@ module interfaces for the core API plus GLFW/OpenGL3 backend entry points. - `imgui.core` - Core Dear ImGui context, frame, widget, and draw-data APIs. -- `imgui.backend.glfw` - - GLFW platform backend plus a small GLFW window/context helper surface. -- `imgui.backend.opengl3` - - OpenGL3 renderer backend. +- `imgui.backend` + - Generic backend abstraction layer: shared value types (`GlConfig`, `Error`, + `FbSize`), `RecommendedGlConfig()` cross-platform defaults, and the + compile-time `BackendApi` contract every backend satisfies. No platform code. +- `imgui.backend.platform.glfw` + - GLFW platform piece: window/event management + `ImGui_ImplGlfw_*` binding. +- `imgui.backend.renderer.opengl3` + - OpenGL3 renderer piece: GL framebuffer ops + `ImGui_ImplOpenGL3_*` binding. - `imgui.backend.glfw_opengl3` - - Convenience module that re-exports core, GLFW, and OpenGL3 backend modules - and provides combined lifecycle helpers. + - Concrete backend: assembles the GLFW platform and OpenGL3 renderer pieces + into a single `ImGui::Backend::GlfwOpenGL3` type satisfying `BackendApi`. + Re-exports `imgui.backend` (shared types) but **not** `imgui.core`. + +Backends expose one `Backend` type with a uniform static API, so swapping +backend is just a different `import` plus a one-line `using Backend = ...;` +alias — the rest of the consumer code is identical. Cross-platform GL/GLSL +configuration (incl. macOS forward-compat) is handled by `RecommendedGlConfig()`, +which `CreateWindow`/`Init` use by default. ## Dependencies @@ -80,11 +91,21 @@ Then import the modules you need: ```cpp import imgui.core; import imgui.backend.glfw_opengl3; +using Backend = ImGui::Backend::GlfwOpenGL3; // swap import + alias to switch backends int main() { + Backend::InitGlfw(); + auto* window = Backend::CreateWindow(960, 540, "demo"); // cross-platform GL hints + Backend::MakeContextCurrent(window); + ImGuiContext* context = ImGui::CreateContext(); ImGui::SetCurrentContext(context); + Backend::Init(window); // RecommendedGlConfig() default + // ... frame loop: Backend::NewFrame / ImGui::* / Backend::RenderDrawData ... + Backend::Shutdown(); ImGui::DestroyContext(context); + Backend::DestroyWindow(window); + Backend::TerminateGlfw(); } ``` @@ -93,13 +114,19 @@ int main() { `src/core.cppm` wraps `imgui.h` through a global module fragment and exports a tested core API surface: -- Types: `ImGuiContext`, `ImFontAtlas`, `ImGuiIO`, `ImDrawData`, `ImVec2`. +- Types: `ImGuiContext`, `ImFontAtlas`, `ImGuiIO`, `ImDrawData`, `ImVec2`, + `ImVec4`. - Functions: context lifecycle, `GetIO`, `NewFrame`, `Begin`, `Button`, `TextUnformatted`, `End`, `Render`, and `GetDrawData`. +`imgui.core` exports a curated core subset; for rarely used APIs not yet +exported, a consumer translation unit can still `#include ` directly +alongside the module imports. + Backend modules adapt the official Dear ImGui backend headers internally and -export explicit wrapper functions. Consumer code should only need module -imports for the surfaces exposed by this package. +expose a single `Backend` type per backend (uniform static API constrained by +`ImGui::Backend::BackendApi`). Consumer code only needs module imports for the +surfaces exposed by this package. ## Verification diff --git a/docs/architecture.md b/docs/architecture.md index 93e7f43..06336fc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -7,9 +7,13 @@ upstream source ownership in compat packages. - Let consumers use explicit module imports: - `import imgui.core;` - - `import imgui.backend.glfw;` - - `import imgui.backend.opengl3;` + - `import imgui.backend;` (generic abstraction layer: shared types + contract) + - `import imgui.backend.platform.glfw;` + - `import imgui.backend.renderer.opengl3;` - `import imgui.backend.glfw_opengl3;` +- Provide a uniform, compile-time-constrained backend surface so swapping a + backend is just a different import plus a one-line alias, with cross-platform + GL/GLSL defaults handled by the abstraction layer. - Keep the package source small: module wrappers and backend implementation translation units only. - Use `compat.imgui`, `compat.glfw`, and `compat.opengl` as the source and @@ -24,9 +28,10 @@ upstream source ownership in compat packages. |-- src/ | |-- core.cppm | `-- backends/ -| |-- glfw.cppm -| |-- opengl3.cppm -| |-- glfw_opengl3.cppm +| |-- backend.cppm (abstraction layer: types + BackendApi) +| |-- platform_glfw.cppm (GLFW platform piece) +| |-- renderer_opengl3.cppm (OpenGL3 renderer piece) +| |-- glfw_opengl3.cppm (concrete Backend assembly) | |-- glfw_impl.cpp | `-- opengl3_impl.cpp |-- tests/ @@ -47,8 +52,9 @@ backend implementation translation units, and depends on compat packages: [build] sources = [ "src/core.cppm", - "src/backends/glfw.cppm", - "src/backends/opengl3.cppm", + "src/backends/backend.cppm", + "src/backends/platform_glfw.cppm", + "src/backends/renderer_opengl3.cppm", "src/backends/glfw_opengl3.cppm", "src/backends/glfw_impl.cpp", "src/backends/opengl3_impl.cpp", diff --git a/examples/basic/mcpp.toml b/examples/basic/mcpp.toml index 267ec64..acf4edf 100644 --- a/examples/basic/mcpp.toml +++ b/examples/basic/mcpp.toml @@ -4,8 +4,5 @@ version = "0.1.0" description = "Headless consumer for the imgui.core module" license = "MIT" -[toolchain] -linux = "llvm@20.1.7" - [dependencies] imgui = { path = "../.." } diff --git a/examples/glfw_opengl3/mcpp.toml b/examples/glfw_opengl3/mcpp.toml index 1c7d3c3..2350a3b 100644 --- a/examples/glfw_opengl3/mcpp.toml +++ b/examples/glfw_opengl3/mcpp.toml @@ -4,8 +4,5 @@ version = "0.1.0" description = "GLFW and OpenGL3 consumer for imgui backend modules" license = "MIT" -[toolchain] -linux = "llvm@20.1.7" - [dependencies] imgui = { path = "../.." } diff --git a/examples/glfw_opengl3/src/main.cpp b/examples/glfw_opengl3/src/main.cpp index 16dcf1e..9a662dd 100644 --- a/examples/glfw_opengl3/src/main.cpp +++ b/examples/glfw_opengl3/src/main.cpp @@ -2,65 +2,65 @@ import std; import imgui.core; import imgui.backend.glfw_opengl3; +// Swap the import above and this alias to switch backends; the rest is identical. +using Backend = ImGui::Backend::GlfwOpenGL3; + int main() { - if (!ImGui::Backend::GlfwOpenGL3::InitGlfw()) { + if (!Backend::InitGlfw()) { std::println("failed to initialize GLFW"); return 1; } - ImGui::Backend::GlfwOpenGL3::DefaultWindowHints(); - ImGui::Backend::GlfwOpenGL3::ConfigureOpenGL(3, 3, true, false); - - auto* window = ImGui::Backend::GlfwOpenGL3::CreateWindow( - 960, - 540, - "mcpp imgui glfw/opengl3 demo" - ); + auto* window = Backend::CreateWindow(960, 540, "mcpp imgui glfw/opengl3 demo"); if (window == nullptr) { std::println("failed to create GLFW window"); - ImGui::Backend::GlfwOpenGL3::TerminateGlfw(); + Backend::TerminateGlfw(); return 1; } - ImGui::Backend::GlfwOpenGL3::MakeContextCurrent(window); - ImGui::Backend::GlfwOpenGL3::SwapInterval(1); + Backend::MakeContextCurrent(window); + Backend::SwapInterval(1); ImGuiContext* context = ImGui::CreateContext(); ImGui::SetCurrentContext(context); - if (!ImGui::Backend::GlfwOpenGL3::Init(window)) { + if (!Backend::Init(window)) { std::println("failed to initialize ImGui GLFW/OpenGL3 backends"); ImGui::DestroyContext(context); - ImGui::Backend::GlfwOpenGL3::DestroyWindow(window); - ImGui::Backend::GlfwOpenGL3::TerminateGlfw(); + Backend::DestroyWindow(window); + Backend::TerminateGlfw(); return 1; } - while (!ImGui::Backend::GlfwOpenGL3::WindowShouldClose(window)) { - ImGui::Backend::GlfwOpenGL3::PollEvents(); - ImGui::Backend::GlfwOpenGL3::NewFrame(); + while (!Backend::WindowShouldClose(window)) { + Backend::PollEvents(); + Backend::NewFrame(); ImGui::NewFrame(); bool open = true; ImGui::Begin("mcpp imgui", &open); ImGui::TextUnformatted("Hello from import imgui.backend.glfw_opengl3"); if (ImGui::Button("Close")) { - ImGui::Backend::GlfwOpenGL3::SetWindowShouldClose(window, true); + Backend::SetWindowShouldClose(window, true); } ImGui::End(); ImGui::Render(); - ImGui::Backend::GlfwOpenGL3::RenderDrawData(ImGui::GetDrawData()); - ImGui::Backend::GlfwOpenGL3::SwapBuffers(window); + const auto framebuffer = Backend::FramebufferSize(window); + Backend::Viewport(0, 0, framebuffer.width, framebuffer.height); + Backend::ClearColor(0.08f, 0.09f, 0.10f, 1.0f); + Backend::ClearColorBuffer(); + Backend::RenderDrawData(ImGui::GetDrawData()); + Backend::SwapBuffers(window); if (!open) { - ImGui::Backend::GlfwOpenGL3::SetWindowShouldClose(window, true); + Backend::SetWindowShouldClose(window, true); } } - ImGui::Backend::GlfwOpenGL3::Shutdown(); + Backend::Shutdown(); ImGui::DestroyContext(context); - ImGui::Backend::GlfwOpenGL3::DestroyWindow(window); - ImGui::Backend::GlfwOpenGL3::TerminateGlfw(); + Backend::DestroyWindow(window); + Backend::TerminateGlfw(); return 0; } diff --git a/examples/minimal_window/mcpp.toml b/examples/minimal_window/mcpp.toml index 41e80a2..3924708 100644 --- a/examples/minimal_window/mcpp.toml +++ b/examples/minimal_window/mcpp.toml @@ -4,8 +4,5 @@ version = "0.1.0" description = "Minimal GLFW/OpenGL3 window using imgui modules" license = "MIT" -[toolchain] -linux = "llvm@20.1.7" - [dependencies] imgui = { path = "../.." } diff --git a/examples/minimal_window/src/main.cpp b/examples/minimal_window/src/main.cpp index 24b048e..32fe91c 100644 --- a/examples/minimal_window/src/main.cpp +++ b/examples/minimal_window/src/main.cpp @@ -1,19 +1,12 @@ import std; +import imgui.core; import imgui.backend.glfw_opengl3; -namespace { - struct GlfwError { - int code = 0; - const char* description = nullptr; - }; - - GlfwError captureError() { - const char* description = nullptr; - const int errorCode = ImGui::Backend::GlfwOpenGL3::GetError(&description); - return GlfwError{errorCode, description}; - } +// Swap the import above and this alias to switch backends; the rest is identical. +using Backend = ImGui::Backend::GlfwOpenGL3; - int fail(const char* step, int exitCode, GlfwError error) { +namespace { + int fail(const char* step, int exitCode, ImGui::Backend::Error error) { std::println( "failed at {}: GLFW error {} ({})", step, @@ -25,18 +18,13 @@ namespace { } int main() { - namespace Backend = ImGui::Backend::GlfwOpenGL3; - if (!Backend::InitGlfw()) { - return fail("glfwInit", 1, captureError()); + return fail("glfwInit", 1, Backend::LastError()); } - Backend::DefaultWindowHints(); - Backend::ConfigureOpenGL(3, 3, true, false); - auto* window = Backend::CreateWindow(640, 360, "mcpp imgui minimal window"); if (window == nullptr) { - const auto error = captureError(); + const auto error = Backend::LastError(); Backend::TerminateGlfw(); return fail("glfwCreateWindow", 2, error); } @@ -48,7 +36,7 @@ int main() { ImGui::SetCurrentContext(context); if (!Backend::Init(window)) { - const auto error = captureError(); + const auto error = Backend::LastError(); ImGui::DestroyContext(context); Backend::DestroyWindow(window); Backend::TerminateGlfw(); @@ -65,6 +53,10 @@ int main() { ImGui::End(); ImGui::Render(); + const auto framebuffer = Backend::FramebufferSize(window); + Backend::Viewport(0, 0, framebuffer.width, framebuffer.height); + Backend::ClearColor(0.08f, 0.09f, 0.10f, 1.0f); + Backend::ClearColorBuffer(); Backend::RenderDrawData(ImGui::GetDrawData()); Backend::SwapBuffers(window); } diff --git a/mcpp.toml b/mcpp.toml index 13088f0..56b3cb1 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -9,14 +9,12 @@ repo = "https://github.com/mcpplibs/imgui-m" [lib] path = "src/core.cppm" -[toolchain] -linux = "llvm@20.1.7" - [build] sources = [ "src/core.cppm", - "src/backends/glfw.cppm", - "src/backends/opengl3.cppm", + "src/backends/backend.cppm", + "src/backends/platform_glfw.cppm", + "src/backends/renderer_opengl3.cppm", "src/backends/glfw_opengl3.cppm", "src/backends/glfw_impl.cpp", "src/backends/opengl3_impl.cpp", diff --git a/src/backends/backend.cppm b/src/backends/backend.cppm new file mode 100644 index 0000000..b043758 --- /dev/null +++ b/src/backends/backend.cppm @@ -0,0 +1,69 @@ +export module imgui.backend; + +import imgui.core; + +// Generic backend abstraction layer. +// +// This module owns the shared value types, the cross-platform default GL +// configuration, and the compile-time API contract every concrete backend +// must satisfy. It contains no platform code and does not re-export +// imgui.core: consumers import imgui.core themselves. +export namespace ImGui::Backend { + // Requested OpenGL context configuration. Defaults are filled by + // RecommendedGlConfig() so examples never hard-code platform values. + struct GlConfig { + int major = 3; + int minor = 3; + bool coreProfile = true; + bool forwardCompat = false; + const char* glsl = nullptr; // nullptr => let the renderer pick a default + }; + + // Last platform error, surfaced for diagnostics. + struct Error { + int code = 0; + const char* description = nullptr; + }; + + // Framebuffer size in pixels. + struct FbSize { + int width = 0; + int height = 0; + }; + + // Platform-correct context/shader defaults. + // macOS : GL 3.2 core + forward-compat, "#version 150" + // others : GL 3.3 core, "#version 130" + GlConfig RecommendedGlConfig() { +#if defined(__APPLE__) + return GlConfig{3, 2, true, true, "#version 150"}; +#else + return GlConfig{3, 3, true, false, "#version 130"}; +#endif + } + + // Compile-time contract. Every backend exposes one type T whose static + // surface satisfies this concept, so consumer code is identical across + // backends and a backend swap is just a different import + alias. + template + concept BackendApi = requires (typename T::Window* window, ImDrawData* drawData, GlConfig config) { + typename T::Window; + T::InitGlfw(); + T::TerminateGlfw(); + T::CreateWindow(0, 0, ""); + T::DestroyWindow(window); + T::MakeContextCurrent(window); + T::Init(window, config); + T::NewFrame(); + T::Viewport(0, 0, 0, 0); + T::ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + T::ClearColorBuffer(); + T::RenderDrawData(drawData); + T::SwapBuffers(window); + T::PollEvents(); + T::WindowShouldClose(window); + T::FramebufferSize(window); + T::Shutdown(); + T::LastError(); + }; +} diff --git a/src/backends/glfw.cppm b/src/backends/glfw.cppm deleted file mode 100644 index 4c461ce..0000000 --- a/src/backends/glfw.cppm +++ /dev/null @@ -1,153 +0,0 @@ -module; - -#define GLFW_INCLUDE_NONE -#include -#include - -export module imgui.backend.glfw; - -import imgui.core; - -export namespace ImGui::Backend::Glfw { - using Monitor = ::GLFWmonitor; - using Window = ::GLFWwindow; - - bool InitGlfw() { - return glfwInit() == GLFW_TRUE; - } - - void TerminateGlfw() { - glfwTerminate(); - } - - const char* GlfwVersionString() { - return glfwGetVersionString(); - } - - int GetError(const char** description) { - return glfwGetError(description); - } - - void DefaultWindowHints() { - glfwDefaultWindowHints(); - } - - void ConfigureOpenGL(int major, int minor, bool coreProfile = true, bool forwardCompat = false) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor); - if (coreProfile) { - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - } - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, forwardCompat ? GLFW_TRUE : GLFW_FALSE); - } - - void SetNextWindowVisible(bool visible) { - glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE); - } - - Window* CreateWindow( - int width, - int height, - const char* title, - Monitor* monitor = nullptr, - Window* share = nullptr - ) { - return glfwCreateWindow(width, height, title, monitor, share); - } - - void DestroyWindow(Window* window) { - glfwDestroyWindow(window); - } - - void MakeContextCurrent(Window* window) { - glfwMakeContextCurrent(window); - } - - void SwapInterval(int interval) { - glfwSwapInterval(interval); - } - - bool WindowShouldClose(Window* window) { - return glfwWindowShouldClose(window) == GLFW_TRUE; - } - - void SetWindowShouldClose(Window* window, bool value) { - glfwSetWindowShouldClose(window, value ? GLFW_TRUE : GLFW_FALSE); - } - - void PollEvents() { - glfwPollEvents(); - } - - void SwapBuffers(Window* window) { - glfwSwapBuffers(window); - } - - bool InitForOpenGL(Window* window, bool installCallbacks = true) { - return ImGui_ImplGlfw_InitForOpenGL(window, installCallbacks); - } - - void Shutdown() { - ImGui_ImplGlfw_Shutdown(); - } - - void NewFrame() { - ImGui_ImplGlfw_NewFrame(); - } - - void InstallCallbacks(Window* window) { - ImGui_ImplGlfw_InstallCallbacks(window); - } - - void RestoreCallbacks(Window* window) { - ImGui_ImplGlfw_RestoreCallbacks(window); - } - - void SetCallbacksChainForAllWindows(bool chainForAllWindows) { - ImGui_ImplGlfw_SetCallbacksChainForAllWindows(chainForAllWindows); - } - - void WindowFocusCallback(Window* window, int focused) { - ImGui_ImplGlfw_WindowFocusCallback(window, focused); - } - - void CursorEnterCallback(Window* window, int entered) { - ImGui_ImplGlfw_CursorEnterCallback(window, entered); - } - - void CursorPosCallback(Window* window, double x, double y) { - ImGui_ImplGlfw_CursorPosCallback(window, x, y); - } - - void MouseButtonCallback(Window* window, int button, int action, int mods) { - ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods); - } - - void ScrollCallback(Window* window, double xoffset, double yoffset) { - ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset); - } - - void KeyCallback(Window* window, int key, int scancode, int action, int mods) { - ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods); - } - - void CharCallback(Window* window, unsigned int c) { - ImGui_ImplGlfw_CharCallback(window, c); - } - - void MonitorCallback(Monitor* monitor, int event) { - ImGui_ImplGlfw_MonitorCallback(monitor, event); - } - - void Sleep(int milliseconds) { - ImGui_ImplGlfw_Sleep(milliseconds); - } - - float GetContentScaleForWindow(Window* window) { - return ImGui_ImplGlfw_GetContentScaleForWindow(window); - } - - float GetContentScaleForMonitor(Monitor* monitor) { - return ImGui_ImplGlfw_GetContentScaleForMonitor(monitor); - } -} diff --git a/src/backends/glfw_opengl3.cppm b/src/backends/glfw_opengl3.cppm index aacd780..302c61c 100644 --- a/src/backends/glfw_opengl3.cppm +++ b/src/backends/glfw_opengl3.cppm @@ -1,129 +1,144 @@ export module imgui.backend.glfw_opengl3; -export import imgui.core; -export import imgui.backend.glfw; -export import imgui.backend.opengl3; - -export namespace ImGui::Backend::GlfwOpenGL3 { - using Monitor = Glfw::Monitor; - using Window = Glfw::Window; - - bool InitGlfw() { - return Glfw::InitGlfw(); - } - - void TerminateGlfw() { - Glfw::TerminateGlfw(); - } - - const char* GlfwVersionString() { - return Glfw::GlfwVersionString(); - } - - int GetError(const char** description) { - return Glfw::GetError(description); - } - - void DefaultWindowHints() { - Glfw::DefaultWindowHints(); - } - - void ConfigureOpenGL(int major, int minor, bool coreProfile = true, bool forwardCompat = false) { - Glfw::ConfigureOpenGL(major, minor, coreProfile, forwardCompat); - } - - void SetNextWindowVisible(bool visible) { - Glfw::SetNextWindowVisible(visible); - } - - Window* CreateWindow( - int width, - int height, - const char* title, - Monitor* monitor = nullptr, - Window* share = nullptr - ) { - return Glfw::CreateWindow(width, height, title, monitor, share); - } - - void DestroyWindow(Window* window) { - Glfw::DestroyWindow(window); - } - - void MakeContextCurrent(Window* window) { - Glfw::MakeContextCurrent(window); - } - - void SwapInterval(int interval) { - Glfw::SwapInterval(interval); - } - - bool WindowShouldClose(Window* window) { - return Glfw::WindowShouldClose(window); - } - - void SetWindowShouldClose(Window* window, bool value) { - Glfw::SetWindowShouldClose(window, value); - } - - void PollEvents() { - Glfw::PollEvents(); - } - - void SwapBuffers(Window* window) { - Glfw::SwapBuffers(window); - } - - bool InitForOpenGL(Window* window, bool installCallbacks = true) { - return Glfw::InitForOpenGL(window, installCallbacks); - } - - bool InitRenderer(const char* glslVersion = nullptr) { - return OpenGL3::Init(glslVersion); - } - - bool Init(Window* window, const char* glslVersion = nullptr, bool installCallbacks = true) { - if (!InitForOpenGL(window, installCallbacks)) { - return false; - } - if (!InitRenderer(glslVersion)) { - Glfw::Shutdown(); - return false; - } - return true; - } - - void ShutdownRenderer() { - OpenGL3::Shutdown(); - } - - void ShutdownPlatform() { - Glfw::Shutdown(); - } - - void Shutdown() { - ShutdownRenderer(); - ShutdownPlatform(); - } - - void NewFrame() { - OpenGL3::NewFrame(); - Glfw::NewFrame(); - } - - void RenderDrawData(ImDrawData* drawData) { - OpenGL3::RenderDrawData(drawData); - } - - void InstallCallbacks(Window* window) { - Glfw::InstallCallbacks(window); - } - - void RestoreCallbacks(Window* window) { - Glfw::RestoreCallbacks(window); - } - - void SetCallbacksChainForAllWindows(bool chainForAllWindows) { - Glfw::SetCallbacksChainForAllWindows(chainForAllWindows); - } +import imgui.core; +export import imgui.backend; // shared types (GlConfig/Error/FbSize) are part of the backend surface +import imgui.backend.platform.glfw; +import imgui.backend.renderer.opengl3; + +// Concrete backend: assembles the GLFW platform piece and the OpenGL3 renderer +// piece into a single Backend type that satisfies BackendApi. Consumer code is +// identical across backends; swapping backend is a different import + alias. +// +// This module imports imgui.core for signatures but does NOT re-export it. +export namespace ImGui::Backend { + struct GlfwOpenGL3 { + using Window = GlfwPlatform::Window; + using Monitor = GlfwPlatform::Monitor; + + static bool InitGlfw() { + return GlfwPlatform::InitGlfw(); + } + + static void TerminateGlfw() { + GlfwPlatform::TerminateGlfw(); + } + + static const char* VersionString() { + return GlfwPlatform::VersionString(); + } + + static Error LastError() { + return GlfwPlatform::LastError(); + } + + // Cross-platform window creation: applies default hints + GlConfig + // (incl. macOS forward-compat) before creating the window. + static Window* CreateWindow( + int width, + int height, + const char* title, + GlConfig config = RecommendedGlConfig() + ) { + GlfwPlatform::DefaultWindowHints(); + GlfwPlatform::ApplyGlConfig(config); + return GlfwPlatform::CreateWindow(width, height, title); + } + + static void DestroyWindow(Window* window) { + GlfwPlatform::DestroyWindow(window); + } + + static void MakeContextCurrent(Window* window) { + GlfwPlatform::MakeContextCurrent(window); + } + + static void SwapInterval(int interval) { + GlfwPlatform::SwapInterval(interval); + } + + static FbSize FramebufferSize(Window* window) { + return GlfwPlatform::FramebufferSize(window); + } + + static bool WindowShouldClose(Window* window) { + return GlfwPlatform::WindowShouldClose(window); + } + + static void SetWindowShouldClose(Window* window, bool value) { + GlfwPlatform::SetWindowShouldClose(window, value); + } + + static void PollEvents() { + GlfwPlatform::PollEvents(); + } + + static void SwapBuffers(Window* window) { + GlfwPlatform::SwapBuffers(window); + } + + // Initialize ImGui platform + renderer bindings. glsl is taken from the + // same config used for window creation (default RecommendedGlConfig()). + static bool Init( + Window* window, + GlConfig config = RecommendedGlConfig(), + bool installCallbacks = true + ) { + if (!GlfwPlatform::InitForOpenGL(window, installCallbacks)) { + return false; + } + if (!OpenGL3Renderer::Init(config.glsl)) { + GlfwPlatform::Shutdown(); + return false; + } + return true; + } + + static void NewFrame() { + OpenGL3Renderer::NewFrame(); + GlfwPlatform::NewFrame(); + } + + static void Viewport(int x, int y, int width, int height) { + OpenGL3Renderer::Viewport(x, y, width, height); + } + + static void ClearColor(float red, float green, float blue, float alpha) { + OpenGL3Renderer::ClearColor(red, green, blue, alpha); + } + + static void ClearColorBuffer() { + OpenGL3Renderer::ClearColorBuffer(); + } + + static void RenderDrawData(ImDrawData* drawData) { + OpenGL3Renderer::RenderDrawData(drawData); + } + + static void ShutdownRenderer() { + OpenGL3Renderer::Shutdown(); + } + + static void ShutdownPlatform() { + GlfwPlatform::Shutdown(); + } + + static void Shutdown() { + ShutdownRenderer(); + ShutdownPlatform(); + } + + static void InstallCallbacks(Window* window) { + GlfwPlatform::InstallCallbacks(window); + } + + static void RestoreCallbacks(Window* window) { + GlfwPlatform::RestoreCallbacks(window); + } + + static void SetCallbacksChainForAllWindows(bool chainForAllWindows) { + GlfwPlatform::SetCallbacksChainForAllWindows(chainForAllWindows); + } + }; + + static_assert(BackendApi, "GlfwOpenGL3 must satisfy the backend contract"); } diff --git a/src/backends/opengl3.cppm b/src/backends/opengl3.cppm deleted file mode 100644 index f24e58a..0000000 --- a/src/backends/opengl3.cppm +++ /dev/null @@ -1,25 +0,0 @@ -module; - -#include - -export module imgui.backend.opengl3; - -import imgui.core; - -export namespace ImGui::Backend::OpenGL3 { - bool Init(const char* glslVersion = nullptr) { - return ImGui_ImplOpenGL3_Init(glslVersion); - } - - void Shutdown() { - ImGui_ImplOpenGL3_Shutdown(); - } - - void NewFrame() { - ImGui_ImplOpenGL3_NewFrame(); - } - - void RenderDrawData(ImDrawData* drawData) { - ImGui_ImplOpenGL3_RenderDrawData(drawData); - } -} diff --git a/src/backends/platform_glfw.cppm b/src/backends/platform_glfw.cppm new file mode 100644 index 0000000..5318b92 --- /dev/null +++ b/src/backends/platform_glfw.cppm @@ -0,0 +1,138 @@ +module; + +#define GLFW_INCLUDE_NONE +#include +#include + +export module imgui.backend.platform.glfw; + +import imgui.core; +import imgui.backend; + +// GLFW platform piece: window/event management plus the ImGui_ImplGlfw_* +// platform binding. Reusable assembly unit; composed into a concrete backend +// by imgui.backend.glfw_opengl3. Does not re-export imgui.core. +export namespace ImGui::Backend { + struct GlfwPlatform { + using Window = ::GLFWwindow; + using Monitor = ::GLFWmonitor; + + static bool InitGlfw() { + return glfwInit() == GLFW_TRUE; + } + + static void TerminateGlfw() { + glfwTerminate(); + } + + static const char* VersionString() { + return glfwGetVersionString(); + } + + static Error LastError() { + const char* description = nullptr; + const int code = glfwGetError(&description); + return Error{code, description}; + } + + static void DefaultWindowHints() { + glfwDefaultWindowHints(); + } + + // Apply context/profile hints. Must be called before CreateWindow. + static void ApplyGlConfig(const GlConfig& config) { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, config.major); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, config.minor); + if (config.coreProfile) { + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + } + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, config.forwardCompat ? GLFW_TRUE : GLFW_FALSE); + } + + static void SetNextWindowVisible(bool visible) { + glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE); + } + + static Window* CreateWindow( + int width, + int height, + const char* title, + Monitor* monitor = nullptr, + Window* share = nullptr + ) { + return glfwCreateWindow(width, height, title, monitor, share); + } + + static void DestroyWindow(Window* window) { + glfwDestroyWindow(window); + } + + static void MakeContextCurrent(Window* window) { + glfwMakeContextCurrent(window); + } + + static FbSize FramebufferSize(Window* window) { + int width = 0; + int height = 0; + glfwGetFramebufferSize(window, &width, &height); + return FbSize{width, height}; + } + + static void SwapInterval(int interval) { + glfwSwapInterval(interval); + } + + static bool WindowShouldClose(Window* window) { + return glfwWindowShouldClose(window) == GLFW_TRUE; + } + + static void SetWindowShouldClose(Window* window, bool value) { + glfwSetWindowShouldClose(window, value ? GLFW_TRUE : GLFW_FALSE); + } + + static void PollEvents() { + glfwPollEvents(); + } + + static void SwapBuffers(Window* window) { + glfwSwapBuffers(window); + } + + // ImGui platform binding. + static bool InitForOpenGL(Window* window, bool installCallbacks = true) { + return ImGui_ImplGlfw_InitForOpenGL(window, installCallbacks); + } + + static void Shutdown() { + ImGui_ImplGlfw_Shutdown(); + } + + static void NewFrame() { + ImGui_ImplGlfw_NewFrame(); + } + + static void InstallCallbacks(Window* window) { + ImGui_ImplGlfw_InstallCallbacks(window); + } + + static void RestoreCallbacks(Window* window) { + ImGui_ImplGlfw_RestoreCallbacks(window); + } + + static void SetCallbacksChainForAllWindows(bool chainForAllWindows) { + ImGui_ImplGlfw_SetCallbacksChainForAllWindows(chainForAllWindows); + } + + static float GetContentScaleForWindow(Window* window) { + return ImGui_ImplGlfw_GetContentScaleForWindow(window); + } + + static float GetContentScaleForMonitor(Monitor* monitor) { + return ImGui_ImplGlfw_GetContentScaleForMonitor(monitor); + } + + static void Sleep(int milliseconds) { + ImGui_ImplGlfw_Sleep(milliseconds); + } + }; +} diff --git a/src/backends/renderer_opengl3.cppm b/src/backends/renderer_opengl3.cppm new file mode 100644 index 0000000..ac9517b --- /dev/null +++ b/src/backends/renderer_opengl3.cppm @@ -0,0 +1,47 @@ +module; + +#include +#include + +export module imgui.backend.renderer.opengl3; + +import imgui.core; +import imgui.backend; + +// OpenGL3 renderer piece: GL framebuffer ops plus the ImGui_ImplOpenGL3_* +// renderer binding. Reusable assembly unit; composed into a concrete backend +// by imgui.backend.glfw_opengl3. Does not re-export imgui.core. +// +// The renderer deliberately does not clear implicitly in RenderDrawData so an +// application can render its own scene first and draw ImGui as an overlay. +export namespace ImGui::Backend { + struct OpenGL3Renderer { + static bool Init(const char* glslVersion = nullptr) { + return ImGui_ImplOpenGL3_Init(glslVersion); + } + + static void Shutdown() { + ImGui_ImplOpenGL3_Shutdown(); + } + + static void NewFrame() { + ImGui_ImplOpenGL3_NewFrame(); + } + + static void Viewport(int x, int y, int width, int height) { + glViewport(x, y, width, height); + } + + static void ClearColor(float red, float green, float blue, float alpha) { + glClearColor(red, green, blue, alpha); + } + + static void ClearColorBuffer() { + glClear(GL_COLOR_BUFFER_BIT); + } + + static void RenderDrawData(ImDrawData* drawData) { + ImGui_ImplOpenGL3_RenderDrawData(drawData); + } + }; +} diff --git a/src/core.cppm b/src/core.cppm index 6975170..5a443ed 100644 --- a/src/core.cppm +++ b/src/core.cppm @@ -9,6 +9,7 @@ export using ImGuiContext = ::ImGuiContext; export using ImGuiIO = ::ImGuiIO; export using ImFontAtlas = ::ImFontAtlas; export using ImVec2 = ::ImVec2; +export using ImVec4 = ::ImVec4; export namespace ImGui { using ::ImGui::Begin; diff --git a/tests/backend_test.cpp b/tests/backend_test.cpp index c3f4a4c..a452660 100644 --- a/tests/backend_test.cpp +++ b/tests/backend_test.cpp @@ -1,71 +1,97 @@ #include import imgui.core; -import imgui.backend.glfw; -import imgui.backend.opengl3; +import imgui.backend; +import imgui.backend.platform.glfw; +import imgui.backend.renderer.opengl3; import imgui.backend.glfw_opengl3; -TEST(ImGuiGlfwBackendModuleTest, ExposesPlatformApi) { - ImGui::Backend::Glfw::Window* window = nullptr; - ImGui::Backend::Glfw::Monitor* monitor = nullptr; +namespace Backend = ImGui::Backend; - auto initForOpenGL = &ImGui::Backend::Glfw::InitForOpenGL; - auto shutdown = &ImGui::Backend::Glfw::Shutdown; - auto newFrame = &ImGui::Backend::Glfw::NewFrame; - auto installCallbacks = &ImGui::Backend::Glfw::InstallCallbacks; - auto restoreCallbacks = &ImGui::Backend::Glfw::RestoreCallbacks; - auto getContentScaleForWindow = &ImGui::Backend::Glfw::GetContentScaleForWindow; - auto getContentScaleForMonitor = &ImGui::Backend::Glfw::GetContentScaleForMonitor; - auto getError = &ImGui::Backend::Glfw::GetError; +// The contract is satisfied at compile time. This also documents that any new +// backend must pass the same assertion. +static_assert(Backend::BackendApi); + +TEST(ImGuiBackendContractTest, ProvidesCrossPlatformDefaults) { + Backend::GlConfig config = Backend::RecommendedGlConfig(); + EXPECT_GE(config.major, 3); + EXPECT_TRUE(config.coreProfile); + EXPECT_NE(config.glsl, nullptr); +#if defined(__APPLE__) + EXPECT_TRUE(config.forwardCompat); + EXPECT_GE(config.minor, 2); +#endif +} + +TEST(ImGuiGlfwPlatformModuleTest, ExposesPlatformApi) { + Backend::GlfwPlatform::Window* window = nullptr; + Backend::GlfwPlatform::Monitor* monitor = nullptr; + + auto initForOpenGL = &Backend::GlfwPlatform::InitForOpenGL; + auto shutdown = &Backend::GlfwPlatform::Shutdown; + auto newFrame = &Backend::GlfwPlatform::NewFrame; + auto framebufferSize = &Backend::GlfwPlatform::FramebufferSize; + auto applyGlConfig = &Backend::GlfwPlatform::ApplyGlConfig; + auto lastError = &Backend::GlfwPlatform::LastError; EXPECT_EQ(window, nullptr); EXPECT_EQ(monitor, nullptr); EXPECT_NE(initForOpenGL, nullptr); EXPECT_NE(shutdown, nullptr); EXPECT_NE(newFrame, nullptr); - EXPECT_NE(installCallbacks, nullptr); - EXPECT_NE(restoreCallbacks, nullptr); - EXPECT_NE(getContentScaleForWindow, nullptr); - EXPECT_NE(getContentScaleForMonitor, nullptr); - EXPECT_NE(getError, nullptr); + EXPECT_NE(framebufferSize, nullptr); + EXPECT_NE(applyGlConfig, nullptr); + EXPECT_NE(lastError, nullptr); } -TEST(ImGuiOpenGL3BackendModuleTest, ExposesRendererApi) { - auto init = &ImGui::Backend::OpenGL3::Init; - auto shutdown = &ImGui::Backend::OpenGL3::Shutdown; - auto newFrame = &ImGui::Backend::OpenGL3::NewFrame; - auto renderDrawData = &ImGui::Backend::OpenGL3::RenderDrawData; +TEST(ImGuiOpenGL3RendererModuleTest, ExposesRendererApi) { + auto init = &Backend::OpenGL3Renderer::Init; + auto shutdown = &Backend::OpenGL3Renderer::Shutdown; + auto newFrame = &Backend::OpenGL3Renderer::NewFrame; + auto viewport = &Backend::OpenGL3Renderer::Viewport; + auto clearColor = &Backend::OpenGL3Renderer::ClearColor; + auto clearColorBuffer = &Backend::OpenGL3Renderer::ClearColorBuffer; + auto renderDrawData = &Backend::OpenGL3Renderer::RenderDrawData; EXPECT_NE(init, nullptr); EXPECT_NE(shutdown, nullptr); EXPECT_NE(newFrame, nullptr); + EXPECT_NE(viewport, nullptr); + EXPECT_NE(clearColor, nullptr); + EXPECT_NE(clearColorBuffer, nullptr); EXPECT_NE(renderDrawData, nullptr); } -TEST(ImGuiGlfwOpenGL3BackendModuleTest, ExposesCombinedApi) { - auto initGlfw = &ImGui::Backend::GlfwOpenGL3::InitGlfw; - auto terminateGlfw = &ImGui::Backend::GlfwOpenGL3::TerminateGlfw; - auto configureOpenGL = &ImGui::Backend::GlfwOpenGL3::ConfigureOpenGL; - auto setNextWindowVisible = &ImGui::Backend::GlfwOpenGL3::SetNextWindowVisible; - auto createWindow = &ImGui::Backend::GlfwOpenGL3::CreateWindow; - auto destroyWindow = &ImGui::Backend::GlfwOpenGL3::DestroyWindow; - auto makeContextCurrent = &ImGui::Backend::GlfwOpenGL3::MakeContextCurrent; - auto init = &ImGui::Backend::GlfwOpenGL3::Init; - auto shutdown = &ImGui::Backend::GlfwOpenGL3::Shutdown; - auto newFrame = &ImGui::Backend::GlfwOpenGL3::NewFrame; - auto renderDrawData = &ImGui::Backend::GlfwOpenGL3::RenderDrawData; - auto getError = &ImGui::Backend::GlfwOpenGL3::GetError; +TEST(ImGuiGlfwOpenGL3BackendModuleTest, ExposesUnifiedApi) { + auto initGlfw = &Backend::GlfwOpenGL3::InitGlfw; + auto terminateGlfw = &Backend::GlfwOpenGL3::TerminateGlfw; + auto createWindow = &Backend::GlfwOpenGL3::CreateWindow; + auto destroyWindow = &Backend::GlfwOpenGL3::DestroyWindow; + auto makeContextCurrent = &Backend::GlfwOpenGL3::MakeContextCurrent; + auto framebufferSize = &Backend::GlfwOpenGL3::FramebufferSize; + auto init = &Backend::GlfwOpenGL3::Init; + auto shutdown = &Backend::GlfwOpenGL3::Shutdown; + auto newFrame = &Backend::GlfwOpenGL3::NewFrame; + auto viewport = &Backend::GlfwOpenGL3::Viewport; + auto clearColor = &Backend::GlfwOpenGL3::ClearColor; + auto clearColorBuffer = &Backend::GlfwOpenGL3::ClearColorBuffer; + auto renderDrawData = &Backend::GlfwOpenGL3::RenderDrawData; + auto swapBuffers = &Backend::GlfwOpenGL3::SwapBuffers; + auto lastError = &Backend::GlfwOpenGL3::LastError; EXPECT_NE(initGlfw, nullptr); EXPECT_NE(terminateGlfw, nullptr); - EXPECT_NE(configureOpenGL, nullptr); - EXPECT_NE(setNextWindowVisible, nullptr); EXPECT_NE(createWindow, nullptr); EXPECT_NE(destroyWindow, nullptr); EXPECT_NE(makeContextCurrent, nullptr); + EXPECT_NE(framebufferSize, nullptr); EXPECT_NE(init, nullptr); EXPECT_NE(shutdown, nullptr); EXPECT_NE(newFrame, nullptr); + EXPECT_NE(viewport, nullptr); + EXPECT_NE(clearColor, nullptr); + EXPECT_NE(clearColorBuffer, nullptr); EXPECT_NE(renderDrawData, nullptr); - EXPECT_NE(getError, nullptr); + EXPECT_NE(swapBuffers, nullptr); + EXPECT_NE(lastError, nullptr); }