diff --git a/backends/imgui_impl_cpu.cpp b/backends/imgui_impl_cpu.cpp new file mode 100644 index 00000000..9fed5d3c --- /dev/null +++ b/backends/imgui_impl_cpu.cpp @@ -0,0 +1,1819 @@ +// dear imgui: CPU framebuffer renderer backend +// (supports caller-owned framebuffers/textures in generic packed pixel formats) +// +// Implemented features: +// [X] Renderer: User texture binding. Use 'ImGui_ImplCPU_Texture*' as texture identifier. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// +// CHANGELOG +// 2026-03-16: Added generic packed-pixel format support, builtin formats, Alpha8 textures, and parity-oriented generic paths. +// 2026-03-15: Initial version, extracted from the SDL surface software renderer. + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_cpu.h" + +#include +#include +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define IMGUI_IMPL_CPU_BIG_ENDIAN 1 +#else +#define IMGUI_IMPL_CPU_BIG_ENDIAN 0 +#endif + +#if IMGUI_IMPL_CPU_BIG_ENDIAN +#define IMGUI_IMPL_CPU_RGBA8888_RMASK 0xFF000000u +#define IMGUI_IMPL_CPU_RGBA8888_GMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_RGBA8888_BMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_RGBA8888_AMASK 0x000000FFu +#define IMGUI_IMPL_CPU_BGRA8888_RMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_BGRA8888_GMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_BGRA8888_BMASK 0xFF000000u +#define IMGUI_IMPL_CPU_BGRA8888_AMASK 0x000000FFu +#define IMGUI_IMPL_CPU_ARGB8888_RMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_ARGB8888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_ARGB8888_BMASK 0x000000FFu +#define IMGUI_IMPL_CPU_ARGB8888_AMASK 0xFF000000u +#define IMGUI_IMPL_CPU_ABGR8888_RMASK 0x000000FFu +#define IMGUI_IMPL_CPU_ABGR8888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_ABGR8888_BMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_ABGR8888_AMASK 0xFF000000u +#define IMGUI_IMPL_CPU_RGB888_RMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_RGB888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_RGB888_BMASK 0x000000FFu +#define IMGUI_IMPL_CPU_BGR888_RMASK 0x000000FFu +#define IMGUI_IMPL_CPU_BGR888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_BGR888_BMASK 0x00FF0000u +#else +#define IMGUI_IMPL_CPU_RGBA8888_RMASK 0x000000FFu +#define IMGUI_IMPL_CPU_RGBA8888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_RGBA8888_BMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_RGBA8888_AMASK 0xFF000000u +#define IMGUI_IMPL_CPU_BGRA8888_RMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_BGRA8888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_BGRA8888_BMASK 0x000000FFu +#define IMGUI_IMPL_CPU_BGRA8888_AMASK 0xFF000000u +#define IMGUI_IMPL_CPU_ARGB8888_RMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_ARGB8888_GMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_ARGB8888_BMASK 0xFF000000u +#define IMGUI_IMPL_CPU_ARGB8888_AMASK 0x000000FFu +#define IMGUI_IMPL_CPU_ABGR8888_RMASK 0xFF000000u +#define IMGUI_IMPL_CPU_ABGR8888_GMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_ABGR8888_BMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_ABGR8888_AMASK 0x000000FFu +#define IMGUI_IMPL_CPU_RGB888_RMASK 0x000000FFu +#define IMGUI_IMPL_CPU_RGB888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_RGB888_BMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_BGR888_RMASK 0x00FF0000u +#define IMGUI_IMPL_CPU_BGR888_GMASK 0x0000FF00u +#define IMGUI_IMPL_CPU_BGR888_BMASK 0x000000FFu +#endif + +static const ImGui_ImplCPU_PixelFormat GImGui_ImplCPU_BuiltinFormats[ImGui_ImplCPU_BuiltinFormat_COUNT] = +{ + { 32, 4, IMGUI_IMPL_CPU_RGBA8888_RMASK, IMGUI_IMPL_CPU_RGBA8888_GMASK, IMGUI_IMPL_CPU_RGBA8888_BMASK, IMGUI_IMPL_CPU_RGBA8888_AMASK, 0, 8, 16, 24, 0, 0, 0, 0, nullptr, 0, nullptr }, + { 32, 4, IMGUI_IMPL_CPU_BGRA8888_RMASK, IMGUI_IMPL_CPU_BGRA8888_GMASK, IMGUI_IMPL_CPU_BGRA8888_BMASK, IMGUI_IMPL_CPU_BGRA8888_AMASK, 16, 8, 0, 24, 0, 0, 0, 0, nullptr, 0, nullptr }, + { 32, 4, IMGUI_IMPL_CPU_ARGB8888_RMASK, IMGUI_IMPL_CPU_ARGB8888_GMASK, IMGUI_IMPL_CPU_ARGB8888_BMASK, IMGUI_IMPL_CPU_ARGB8888_AMASK, 8, 16, 24, 0, 0, 0, 0, 0, nullptr, 0, nullptr }, + { 32, 4, IMGUI_IMPL_CPU_ABGR8888_RMASK, IMGUI_IMPL_CPU_ABGR8888_GMASK, IMGUI_IMPL_CPU_ABGR8888_BMASK, IMGUI_IMPL_CPU_ABGR8888_AMASK, 24, 16, 8, 0, 0, 0, 0, 0, nullptr, 0, nullptr }, + { 16, 2, 0xF800u, 0x07E0u, 0x001Fu, 0x0000u, 11, 5, 0, 0, 3, 2, 3, 8, nullptr, 0, nullptr }, + { 16, 2, 0x7C00u, 0x03E0u, 0x001Fu, 0x8000u, 10, 5, 0, 15, 3, 3, 3, 7, nullptr, 0, nullptr }, + { 16, 2, 0xF000u, 0x0F00u, 0x00F0u, 0x000Fu, 12, 8, 4, 0, 4, 4, 4, 4, nullptr, 0, nullptr }, + { 24, 3, IMGUI_IMPL_CPU_RGB888_RMASK, IMGUI_IMPL_CPU_RGB888_GMASK, IMGUI_IMPL_CPU_RGB888_BMASK, 0x00000000u, 0, 8, 16, 0, 0, 0, 0, 8, nullptr, 0, nullptr }, + { 24, 3, IMGUI_IMPL_CPU_BGR888_RMASK, IMGUI_IMPL_CPU_BGR888_GMASK, IMGUI_IMPL_CPU_BGR888_BMASK, 0x00000000u, 16, 8, 0, 0, 0, 0, 0, 8, nullptr, 0, nullptr }, + { 8, 1, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000FFu, 0, 0, 0, 0, 8, 8, 8, 0, nullptr, 0, nullptr } +}; + +const ImGui_ImplCPU_PixelFormat* ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat format) +{ + if (format < 0 || format >= ImGui_ImplCPU_BuiltinFormat_COUNT) + return nullptr; + return &GImGui_ImplCPU_BuiltinFormats[format]; +} + +enum ImGui_ImplCPU_FormatClass +{ + ImGui_ImplCPU_FormatClass_FastRGBA32, + ImGui_ImplCPU_FormatClass_FastBGRA32, + ImGui_ImplCPU_FormatClass_FastARGB32, + ImGui_ImplCPU_FormatClass_FastABGR32, + ImGui_ImplCPU_FormatClass_RGB565, + ImGui_ImplCPU_FormatClass_ARGB1555, + ImGui_ImplCPU_FormatClass_RGBA4444, + ImGui_ImplCPU_FormatClass_RGB888, + ImGui_ImplCPU_FormatClass_BGR888, + ImGui_ImplCPU_FormatClass_Alpha8, + ImGui_ImplCPU_FormatClass_Indexed, + ImGui_ImplCPU_FormatClass_Generic +}; + +struct ImGui_ImplCPU_FastFormat32 +{ + ImU32 Rmask; + ImU32 Gmask; + ImU32 Bmask; + ImU32 Amask; + ImU8 Rshift; + ImU8 Gshift; + ImU8 Bshift; + ImU8 Ashift; + bool HasAlpha; + bool Valid; + + ImGui_ImplCPU_FastFormat32() { memset((void*)this, 0, sizeof(*this)); } +}; + +struct ImGui_ImplCPU_SurfaceInfo; +typedef void (*ImGui_ImplCPU_ReadRGBAFn)(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a); +typedef unsigned char (*ImGui_ImplCPU_ReadAlphaFn)(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y); +typedef ImU32 (*ImGui_ImplCPU_PackRGBAFn)(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +struct ImGui_ImplCPU_SurfaceInfo +{ + unsigned char* Pixels; + int Pitch; + int Width; + int Height; + const ImGui_ImplCPU_PixelFormat* Format; + ImGui_ImplCPU_FormatClass Class; + ImGui_ImplCPU_FastFormat32 FastFormat; + ImGui_ImplCPU_ReadRGBAFn ReadRGBAFn; + ImGui_ImplCPU_ReadAlphaFn ReadAlphaFn; + ImGui_ImplCPU_PackRGBAFn PackRGBAFn; + bool IsFast; + bool HasAlpha; + bool IsAlphaOnly; + bool IsIndexed; + + ImGui_ImplCPU_SurfaceInfo() { memset((void*)this, 0, sizeof(*this)); } +}; + +struct ImGui_ImplCPU_RasterVertex +{ + float X; + float Y; + float U; + float V; + float R; + float G; + float B; + float A; +}; + +struct ImGui_ImplCPU_Quad +{ + const ImGui_ImplCPU_RasterVertex* TL; + const ImGui_ImplCPU_RasterVertex* TR; + const ImGui_ImplCPU_RasterVertex* BR; + const ImGui_ImplCPU_RasterVertex* BL; +}; + +struct ImGui_ImplCPU_Data +{ + ImVector VertexCache; +}; + +static ImGui_ImplCPU_Data* ImGui_ImplCPU_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplCPU_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +static inline const ImGui_ImplCPU_PixelFormat* ImGui_ImplCPU_GetDefaultPixelFormat() +{ + return ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888); +} + +static inline float ImGui_ImplCPU_Edge(float ax, float ay, float bx, float by, float px, float py) +{ + return (bx - ax) * (py - ay) - (by - ay) * (px - ax); +} + +static inline bool ImGui_ImplCPU_NearEqual(float a, float b) +{ + return fabsf(a - b) <= 0.0001f; +} + +static inline int ImGui_ImplCPU_ClampInt(int v, int lo, int hi) +{ + return (v < lo) ? lo : (v > hi ? hi : v); +} + +static inline unsigned char ImGui_ImplCPU_ClampByte(float v) +{ + if (v <= 0.0f) + return 0; + if (v >= 255.0f) + return 255; + return (unsigned char)(v + 0.5f); +} + +static inline unsigned char ImGui_ImplCPU_Mul255(unsigned int a, unsigned int b) +{ + unsigned int t = a * b + 128; + return (unsigned char)((t + (t >> 8)) >> 8); +} + +static inline unsigned char ImGui_ImplCPU_ExpandBits(ImU32 value, int bits) +{ + if (bits <= 0) + return 0; + if (bits >= 8) + return (unsigned char)value; + ImU32 max_value = (1u << bits) - 1u; + return (unsigned char)((value * 255u + (max_value >> 1)) / max_value); +} + +static inline ImU32 ImGui_ImplCPU_CompressBits(unsigned char value, int bits) +{ + if (bits <= 0) + return 0; + if (bits >= 8) + return value; + ImU32 max_value = (1u << bits) - 1u; + return (ImU32)((value * max_value + 127u) / 255u); +} + +static inline void ImGui_ImplCPU_UnpackCanonicalRGBA(ImU32 pixel, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + *r = (unsigned char)((pixel >> IM_COL32_R_SHIFT) & 0xFF); + *g = (unsigned char)((pixel >> IM_COL32_G_SHIFT) & 0xFF); + *b = (unsigned char)((pixel >> IM_COL32_B_SHIFT) & 0xFF); + *a = (unsigned char)((pixel >> IM_COL32_A_SHIFT) & 0xFF); +} + +static inline bool ImGui_ImplCPU_CanUseFastPath(const void* pixels, int pitch) +{ +#ifdef IMGUI_IMPL_CPU_DISABLE_FAST_PATH + IM_UNUSED(pixels); + IM_UNUSED(pitch); + return false; +#else + if (pixels == nullptr || pitch <= 0) + return false; + if ((pitch & 3) != 0) + return false; + if ((((uintptr_t)pixels) & 3) != 0) + return false; + return true; +#endif +} + +static inline bool ImGui_ImplCPU_MatchesFormat(const ImGui_ImplCPU_PixelFormat* format, const ImGui_ImplCPU_PixelFormat* builtin) +{ + return format->BitsPerPixel == builtin->BitsPerPixel + && format->BytesPerPixel == builtin->BytesPerPixel + && format->Rmask == builtin->Rmask + && format->Gmask == builtin->Gmask + && format->Bmask == builtin->Bmask + && format->Amask == builtin->Amask + && format->Rshift == builtin->Rshift + && format->Gshift == builtin->Gshift + && format->Bshift == builtin->Bshift + && format->Ashift == builtin->Ashift + && format->Rloss == builtin->Rloss + && format->Gloss == builtin->Gloss + && format->Bloss == builtin->Bloss + && format->Aloss == builtin->Aloss; +} + +static inline ImGui_ImplCPU_FormatClass ImGui_ImplCPU_ClassifyFormat(const ImGui_ImplCPU_PixelFormat* format) +{ + if (format == nullptr) + format = ImGui_ImplCPU_GetDefaultPixelFormat(); + + if (format->Palette != nullptr && format->PaletteSize > 0) + return ImGui_ImplCPU_FormatClass_Indexed; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888))) + return ImGui_ImplCPU_FormatClass_FastRGBA32; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGRA8888))) + return ImGui_ImplCPU_FormatClass_FastBGRA32; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_ARGB8888))) + return ImGui_ImplCPU_FormatClass_FastARGB32; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_ABGR8888))) + return ImGui_ImplCPU_FormatClass_FastABGR32; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGB565))) + return ImGui_ImplCPU_FormatClass_RGB565; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_ARGB1555))) + return ImGui_ImplCPU_FormatClass_ARGB1555; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA4444))) + return ImGui_ImplCPU_FormatClass_RGBA4444; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGB888))) + return ImGui_ImplCPU_FormatClass_RGB888; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGR888))) + return ImGui_ImplCPU_FormatClass_BGR888; + if (ImGui_ImplCPU_MatchesFormat(format, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_Alpha8))) + return ImGui_ImplCPU_FormatClass_Alpha8; + return ImGui_ImplCPU_FormatClass_Generic; +} + +static inline ImGui_ImplCPU_FastFormat32 ImGui_ImplCPU_BuildFastFormat(const ImGui_ImplCPU_PixelFormat* format) +{ + ImGui_ImplCPU_FastFormat32 out; + if (format == nullptr) + return out; + if (format->BitsPerPixel != 32 || format->BytesPerPixel != 4) + return out; + if (format->Rloss != 0 || format->Gloss != 0 || format->Bloss != 0) + return out; + if (format->Amask != 0 && format->Aloss != 0) + return out; + out.Rmask = format->Rmask; + out.Gmask = format->Gmask; + out.Bmask = format->Bmask; + out.Amask = format->Amask; + out.Rshift = format->Rshift; + out.Gshift = format->Gshift; + out.Bshift = format->Bshift; + out.Ashift = format->Ashift; + out.HasAlpha = format->Amask != 0; + out.Valid = true; + return out; +} + +static inline ImU32 ImGui_ImplCPU_ReadPixelRaw(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + const unsigned char* p = surface.Pixels + (size_t)y * (size_t)surface.Pitch + (size_t)x * (size_t)surface.Format->BytesPerPixel; + switch (surface.Format->BytesPerPixel) + { + case 1: + return *p; + case 2: + { + ImU16 pixel = 0; + memcpy(&pixel, p, sizeof(pixel)); + return pixel; + } + case 3: +#if IMGUI_IMPL_CPU_BIG_ENDIAN + return (ImU32)((p[0] << 16) | (p[1] << 8) | p[2]); +#else + return (ImU32)(p[0] | (p[1] << 8) | (p[2] << 16)); +#endif + case 4: + { + ImU32 pixel = 0; + memcpy(&pixel, p, sizeof(pixel)); + return pixel; + } + default: + return 0; + } +} + +static inline void ImGui_ImplCPU_WritePixelRaw(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, ImU32 pixel) +{ + unsigned char* p = surface.Pixels + (size_t)y * (size_t)surface.Pitch + (size_t)x * (size_t)surface.Format->BytesPerPixel; + switch (surface.Format->BytesPerPixel) + { + case 1: + *p = (unsigned char)pixel; + return; + case 2: + { + ImU16 v = (ImU16)pixel; + memcpy(p, &v, sizeof(v)); + return; + } + case 3: +#if IMGUI_IMPL_CPU_BIG_ENDIAN + p[0] = (unsigned char)((pixel >> 16) & 0xFF); + p[1] = (unsigned char)((pixel >> 8) & 0xFF); + p[2] = (unsigned char)(pixel & 0xFF); +#else + p[0] = (unsigned char)(pixel & 0xFF); + p[1] = (unsigned char)((pixel >> 8) & 0xFF); + p[2] = (unsigned char)((pixel >> 16) & 0xFF); +#endif + return; + case 4: + memcpy(p, &pixel, sizeof(pixel)); + return; + default: + return; + } +} + +static inline void ImGui_ImplCPU_UnpackFastRGBA(const ImGui_ImplCPU_FastFormat32& format, ImU32 pixel, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + *r = (unsigned char)((pixel & format.Rmask) >> format.Rshift); + *g = (unsigned char)((pixel & format.Gmask) >> format.Gshift); + *b = (unsigned char)((pixel & format.Bmask) >> format.Bshift); + *a = format.HasAlpha ? (unsigned char)((pixel & format.Amask) >> format.Ashift) : 255; +} + +static inline ImU32 ImGui_ImplCPU_PackFastRGBA(const ImGui_ImplCPU_FastFormat32& format, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + ImU32 pixel = (((ImU32)r << format.Rshift) & format.Rmask) + | (((ImU32)g << format.Gshift) & format.Gmask) + | (((ImU32)b << format.Bshift) & format.Bmask); + if (format.HasAlpha) + pixel |= (((ImU32)a << format.Ashift) & format.Amask); + return pixel; +} + +static inline void ImGui_ImplCPU_UnpackGenericRGBA(const ImGui_ImplCPU_PixelFormat* format, ImU32 pixel, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + *r = (format->Rmask != 0) ? ImGui_ImplCPU_ExpandBits((pixel & format->Rmask) >> format->Rshift, 8 - format->Rloss) : 0; + *g = (format->Gmask != 0) ? ImGui_ImplCPU_ExpandBits((pixel & format->Gmask) >> format->Gshift, 8 - format->Gloss) : 0; + *b = (format->Bmask != 0) ? ImGui_ImplCPU_ExpandBits((pixel & format->Bmask) >> format->Bshift, 8 - format->Bloss) : 0; + *a = (format->Amask != 0) ? ImGui_ImplCPU_ExpandBits((pixel & format->Amask) >> format->Ashift, 8 - format->Aloss) : 255; +} + +static inline ImU32 ImGui_ImplCPU_PackGenericRGBA(const ImGui_ImplCPU_PixelFormat* format, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + ImU32 pixel = 0; + if (format->Rmask != 0) + pixel |= (ImGui_ImplCPU_CompressBits(r, 8 - format->Rloss) << format->Rshift) & format->Rmask; + if (format->Gmask != 0) + pixel |= (ImGui_ImplCPU_CompressBits(g, 8 - format->Gloss) << format->Gshift) & format->Gmask; + if (format->Bmask != 0) + pixel |= (ImGui_ImplCPU_CompressBits(b, 8 - format->Bloss) << format->Bshift) & format->Bmask; + if (format->Amask != 0) + pixel |= (ImGui_ImplCPU_CompressBits(a, 8 - format->Aloss) << format->Ashift) & format->Amask; + return pixel; +} + +static inline ImU32 ImGui_ImplCPU_MapNearestPaletteColor(const ImGui_ImplCPU_PixelFormat* format, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if (format->Palette == nullptr || format->PaletteSize <= 0) + return 0; + + unsigned int best_distance = 0xFFFFFFFFu; + int best_index = 0; + for (int i = 0; i < format->PaletteSize; i++) + { + unsigned char pr, pg, pb, pa; + ImGui_ImplCPU_UnpackCanonicalRGBA(format->Palette[i], &pr, &pg, &pb, &pa); + int dr = (int)pr - (int)r; + int dg = (int)pg - (int)g; + int db = (int)pb - (int)b; + int da = (int)pa - (int)a; + unsigned int distance = (unsigned int)(dr * dr + dg * dg + db * db + da * da); + if (distance < best_distance) + { + best_distance = distance; + best_index = i; + if (distance == 0) + break; + } + } + return (ImU32)best_index; +} + +static inline void ImGui_ImplCPU_ReadRGBAFast32(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImGui_ImplCPU_UnpackFastRGBA(surface.FastFormat, ImGui_ImplCPU_ReadPixelRaw(surface, x, y), r, g, b, a); +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaFast32(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + if (!surface.FastFormat.HasAlpha) + return 255; + ImU32 pixel = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + return (unsigned char)((pixel & surface.FastFormat.Amask) >> surface.FastFormat.Ashift); +} + +static inline ImU32 ImGui_ImplCPU_PackFast32(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return ImGui_ImplCPU_PackFastRGBA(surface.FastFormat, r, g, b, a); +} + +static inline void ImGui_ImplCPU_ReadRGBAGenericSurface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImGui_ImplCPU_UnpackGenericRGBA(surface.Format, ImGui_ImplCPU_ReadPixelRaw(surface, x, y), r, g, b, a); +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaGenericSurface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + if (surface.Format->Amask == 0) + return 255; + ImU32 pixel = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + return ImGui_ImplCPU_ExpandBits((pixel & surface.Format->Amask) >> surface.Format->Ashift, 8 - surface.Format->Aloss); +} + +static inline ImU32 ImGui_ImplCPU_PackGenericSurface(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return ImGui_ImplCPU_PackGenericRGBA(surface.Format, r, g, b, a); +} + +static inline void ImGui_ImplCPU_ReadRGBAAlpha8Surface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + IM_UNUSED(surface); + IM_UNUSED(x); + IM_UNUSED(y); + *r = 255; + *g = 255; + *b = 255; + *a = surface.Pixels[(size_t)y * (size_t)surface.Pitch + (size_t)x]; +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaAlpha8Surface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + return surface.Pixels[(size_t)y * (size_t)surface.Pitch + (size_t)x]; +} + +static inline ImU32 ImGui_ImplCPU_PackAlpha8Surface(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + IM_UNUSED(r); + IM_UNUSED(g); + IM_UNUSED(b); + return a; +} + +static inline void ImGui_ImplCPU_ReadRGBAIndexedSurface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImU32 index = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + if (surface.Format->Palette == nullptr || index >= (ImU32)surface.Format->PaletteSize) + { + *r = *g = *b = *a = 0; + return; + } + ImGui_ImplCPU_UnpackCanonicalRGBA(surface.Format->Palette[index], r, g, b, a); +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaIndexedSurface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + ImU32 index = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + if (surface.Format->Palette == nullptr || index >= (ImU32)surface.Format->PaletteSize) + return 0; + return (unsigned char)((surface.Format->Palette[index] >> IM_COL32_A_SHIFT) & 0xFF); +} + +static inline ImU32 ImGui_ImplCPU_PackIndexedSurface(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if (surface.Format->MapColorFn != nullptr) + return surface.Format->MapColorFn(surface.Format, r, g, b, a); + return ImGui_ImplCPU_MapNearestPaletteColor(surface.Format, r, g, b, a); +} + +static inline void ImGui_ImplCPU_ReadRGBARGB565(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImU32 pixel = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + *r = ImGui_ImplCPU_ExpandBits((pixel >> 11) & 0x1Fu, 5); + *g = ImGui_ImplCPU_ExpandBits((pixel >> 5) & 0x3Fu, 6); + *b = ImGui_ImplCPU_ExpandBits(pixel & 0x1Fu, 5); + *a = 255; +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaOpaqueSurface(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + IM_UNUSED(surface); + IM_UNUSED(x); + IM_UNUSED(y); + return 255; +} + +static inline ImU32 ImGui_ImplCPU_PackRGB565(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + IM_UNUSED(a); + return (ImU32)((ImGui_ImplCPU_CompressBits(r, 5) << 11) | (ImGui_ImplCPU_CompressBits(g, 6) << 5) | ImGui_ImplCPU_CompressBits(b, 5)); +} + +static inline void ImGui_ImplCPU_ReadRGBAARGB1555(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImU32 pixel = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + *r = ImGui_ImplCPU_ExpandBits((pixel >> 10) & 0x1Fu, 5); + *g = ImGui_ImplCPU_ExpandBits((pixel >> 5) & 0x1Fu, 5); + *b = ImGui_ImplCPU_ExpandBits(pixel & 0x1Fu, 5); + *a = (pixel & 0x8000u) ? 255 : 0; +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaARGB1555(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + return (ImGui_ImplCPU_ReadPixelRaw(surface, x, y) & 0x8000u) ? 255 : 0; +} + +static inline ImU32 ImGui_ImplCPU_PackARGB1555(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + return (ImU32)(((a >= 128 ? 1u : 0u) << 15) | (ImGui_ImplCPU_CompressBits(r, 5) << 10) | (ImGui_ImplCPU_CompressBits(g, 5) << 5) | ImGui_ImplCPU_CompressBits(b, 5)); +} + +static inline void ImGui_ImplCPU_ReadRGBARGBA4444(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImU32 pixel = ImGui_ImplCPU_ReadPixelRaw(surface, x, y); + *r = ImGui_ImplCPU_ExpandBits((pixel >> 12) & 0x0Fu, 4); + *g = ImGui_ImplCPU_ExpandBits((pixel >> 8) & 0x0Fu, 4); + *b = ImGui_ImplCPU_ExpandBits((pixel >> 4) & 0x0Fu, 4); + *a = ImGui_ImplCPU_ExpandBits(pixel & 0x0Fu, 4); +} + +static inline unsigned char ImGui_ImplCPU_ReadAlphaRGBA4444(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + return ImGui_ImplCPU_ExpandBits(ImGui_ImplCPU_ReadPixelRaw(surface, x, y) & 0x0Fu, 4); +} + +static inline ImU32 ImGui_ImplCPU_PackRGBA4444(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + return (ImU32)((ImGui_ImplCPU_CompressBits(r, 4) << 12) | (ImGui_ImplCPU_CompressBits(g, 4) << 8) | (ImGui_ImplCPU_CompressBits(b, 4) << 4) | ImGui_ImplCPU_CompressBits(a, 4)); +} + +static inline void ImGui_ImplCPU_ReadRGBARGB888(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + const unsigned char* p = surface.Pixels + (size_t)y * (size_t)surface.Pitch + (size_t)x * 3u; +#if IMGUI_IMPL_CPU_BIG_ENDIAN + *r = p[0]; + *g = p[1]; + *b = p[2]; +#else + *r = p[0]; + *g = p[1]; + *b = p[2]; +#endif + *a = 255; +} + +static inline ImU32 ImGui_ImplCPU_PackRGB888(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + IM_UNUSED(a); +#if IMGUI_IMPL_CPU_BIG_ENDIAN + return (ImU32)((r << 16) | (g << 8) | b); +#else + return (ImU32)(r | (g << 8) | (b << 16)); +#endif +} + +static inline void ImGui_ImplCPU_ReadRGBABGR888(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + const unsigned char* p = surface.Pixels + (size_t)y * (size_t)surface.Pitch + (size_t)x * 3u; +#if IMGUI_IMPL_CPU_BIG_ENDIAN + *b = p[0]; + *g = p[1]; + *r = p[2]; +#else + *b = p[0]; + *g = p[1]; + *r = p[2]; +#endif + *a = 255; +} + +static inline ImU32 ImGui_ImplCPU_PackBGR888(const ImGui_ImplCPU_SurfaceInfo& surface, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + IM_UNUSED(surface); + IM_UNUSED(a); +#if IMGUI_IMPL_CPU_BIG_ENDIAN + return (ImU32)((b << 16) | (g << 8) | r); +#else + return (ImU32)(b | (g << 8) | (r << 16)); +#endif +} + +static void ImGui_ImplCPU_InitSurfaceInfo(ImGui_ImplCPU_SurfaceInfo* out, const unsigned char* pixels, int pitch, int width, int height, const ImGui_ImplCPU_PixelFormat* format) +{ + out->Pixels = (unsigned char*)pixels; + out->Pitch = pitch; + out->Width = width; + out->Height = height; + out->Format = format ? format : ImGui_ImplCPU_GetDefaultPixelFormat(); + out->Class = ImGui_ImplCPU_ClassifyFormat(out->Format); + out->FastFormat = ImGui_ImplCPU_BuildFastFormat(out->Format); + out->HasAlpha = out->Format->Amask != 0 || out->Class == ImGui_ImplCPU_FormatClass_Alpha8; + out->IsAlphaOnly = out->Class == ImGui_ImplCPU_FormatClass_Alpha8; + out->IsIndexed = out->Class == ImGui_ImplCPU_FormatClass_Indexed; + out->IsFast = (out->Class == ImGui_ImplCPU_FormatClass_FastRGBA32 + || out->Class == ImGui_ImplCPU_FormatClass_FastBGRA32 + || out->Class == ImGui_ImplCPU_FormatClass_FastARGB32 + || out->Class == ImGui_ImplCPU_FormatClass_FastABGR32) + && out->FastFormat.Valid + && ImGui_ImplCPU_CanUseFastPath(out->Pixels, out->Pitch); + + switch (out->Class) + { + case ImGui_ImplCPU_FormatClass_FastRGBA32: + case ImGui_ImplCPU_FormatClass_FastBGRA32: + case ImGui_ImplCPU_FormatClass_FastARGB32: + case ImGui_ImplCPU_FormatClass_FastABGR32: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBAFast32; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaFast32; + out->PackRGBAFn = ImGui_ImplCPU_PackFast32; + break; + case ImGui_ImplCPU_FormatClass_RGB565: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBARGB565; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaOpaqueSurface; + out->PackRGBAFn = ImGui_ImplCPU_PackRGB565; + break; + case ImGui_ImplCPU_FormatClass_ARGB1555: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBAARGB1555; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaARGB1555; + out->PackRGBAFn = ImGui_ImplCPU_PackARGB1555; + break; + case ImGui_ImplCPU_FormatClass_RGBA4444: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBARGBA4444; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaRGBA4444; + out->PackRGBAFn = ImGui_ImplCPU_PackRGBA4444; + break; + case ImGui_ImplCPU_FormatClass_RGB888: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBARGB888; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaOpaqueSurface; + out->PackRGBAFn = ImGui_ImplCPU_PackRGB888; + break; + case ImGui_ImplCPU_FormatClass_BGR888: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBABGR888; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaOpaqueSurface; + out->PackRGBAFn = ImGui_ImplCPU_PackBGR888; + break; + case ImGui_ImplCPU_FormatClass_Alpha8: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBAAlpha8Surface; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaAlpha8Surface; + out->PackRGBAFn = ImGui_ImplCPU_PackAlpha8Surface; + break; + case ImGui_ImplCPU_FormatClass_Indexed: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBAIndexedSurface; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaIndexedSurface; + out->PackRGBAFn = ImGui_ImplCPU_PackIndexedSurface; + break; + case ImGui_ImplCPU_FormatClass_Generic: + default: + out->ReadRGBAFn = ImGui_ImplCPU_ReadRGBAGenericSurface; + out->ReadAlphaFn = ImGui_ImplCPU_ReadAlphaGenericSurface; + out->PackRGBAFn = ImGui_ImplCPU_PackGenericSurface; + break; + } +} + +static inline void ImGui_ImplCPU_InitTextureInfo(ImGui_ImplCPU_SurfaceInfo* out, const ImGui_ImplCPU_Texture* texture) +{ + ImGui_ImplCPU_InitSurfaceInfo(out, + texture ? texture->Pixels : nullptr, + texture ? texture->Pitch : 0, + texture ? texture->Width : 0, + texture ? texture->Height : 0, + texture ? texture->Format : nullptr); +} + +static inline void ImGui_ImplCPU_InitBufferInfo(ImGui_ImplCPU_SurfaceInfo* out, ImGui_ImplCPU_Framebuffer* framebuffer) +{ + ImGui_ImplCPU_InitSurfaceInfo(out, + framebuffer ? framebuffer->Pixels : nullptr, + framebuffer ? framebuffer->Pitch : 0, + framebuffer ? framebuffer->Width : 0, + framebuffer ? framebuffer->Height : 0, + framebuffer ? framebuffer->Format : nullptr); +} + +static inline void ImGui_ImplCPU_ReadPixelRGBA(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + surface.ReadRGBAFn(surface, x, y, r, g, b, a); +} + +static inline unsigned char ImGui_ImplCPU_ReadPixelAlpha(const ImGui_ImplCPU_SurfaceInfo& surface, int x, int y) +{ + return surface.ReadAlphaFn(surface, x, y); +} + +static inline void ImGui_ImplCPU_BlendPixelFast(const ImGui_ImplCPU_SurfaceInfo& target, ImU32* dst, unsigned char sr, unsigned char sg, unsigned char sb, unsigned char sa) +{ + if (sa == 0) + return; + if (sa == 255) + { + *dst = ImGui_ImplCPU_PackFastRGBA(target.FastFormat, sr, sg, sb, target.FastFormat.HasAlpha ? 255 : 255); + return; + } + + unsigned char dr, dg, db, da; + ImGui_ImplCPU_UnpackFastRGBA(target.FastFormat, *dst, &dr, &dg, &db, &da); + unsigned char inv_a = (unsigned char)(255 - sa); + unsigned char out_r = (unsigned char)(ImGui_ImplCPU_Mul255(sr, sa) + ImGui_ImplCPU_Mul255(dr, inv_a)); + unsigned char out_g = (unsigned char)(ImGui_ImplCPU_Mul255(sg, sa) + ImGui_ImplCPU_Mul255(dg, inv_a)); + unsigned char out_b = (unsigned char)(ImGui_ImplCPU_Mul255(sb, sa) + ImGui_ImplCPU_Mul255(db, inv_a)); + unsigned char out_a = target.FastFormat.HasAlpha ? (unsigned char)(sa + ImGui_ImplCPU_Mul255(da, inv_a)) : 255; + *dst = ImGui_ImplCPU_PackFastRGBA(target.FastFormat, out_r, out_g, out_b, out_a); +} + +static inline void ImGui_ImplCPU_BlendPixelGeneric(const ImGui_ImplCPU_SurfaceInfo& target, int x, int y, unsigned char sr, unsigned char sg, unsigned char sb, unsigned char sa) +{ + if (sa == 0) + return; + if (sa == 255) + { + ImU32 pixel = target.PackRGBAFn(target, sr, sg, sb, target.HasAlpha ? 255 : 255); + ImGui_ImplCPU_WritePixelRaw(target, x, y, pixel); + return; + } + + unsigned char dr, dg, db, da; + ImGui_ImplCPU_ReadPixelRGBA(target, x, y, &dr, &dg, &db, &da); + unsigned char inv_a = (unsigned char)(255 - sa); + unsigned char out_r = (unsigned char)(ImGui_ImplCPU_Mul255(sr, sa) + ImGui_ImplCPU_Mul255(dr, inv_a)); + unsigned char out_g = (unsigned char)(ImGui_ImplCPU_Mul255(sg, sa) + ImGui_ImplCPU_Mul255(dg, inv_a)); + unsigned char out_b = (unsigned char)(ImGui_ImplCPU_Mul255(sb, sa) + ImGui_ImplCPU_Mul255(db, inv_a)); + unsigned char out_a = target.HasAlpha ? (unsigned char)(sa + ImGui_ImplCPU_Mul255(da, inv_a)) : 255; + ImU32 pixel = target.PackRGBAFn(target, out_r, out_g, out_b, out_a); + ImGui_ImplCPU_WritePixelRaw(target, x, y, pixel); +} + +static inline bool ImGui_ImplCPU_SameColor(const ImGui_ImplCPU_RasterVertex& a, const ImGui_ImplCPU_RasterVertex& b) +{ + return a.R == b.R && a.G == b.G && a.B == b.B && a.A == b.A; +} + +static inline void ImGui_ImplCPU_SetupRasterVertex(ImGui_ImplCPU_RasterVertex* out, const ImDrawVert& v, const ImVec2& display_pos, const ImVec2& framebuffer_scale) +{ + out->X = (v.pos.x - display_pos.x) * framebuffer_scale.x; + out->Y = (v.pos.y - display_pos.y) * framebuffer_scale.y; + out->U = v.uv.x; + out->V = v.uv.y; + out->R = (float)((v.col >> IM_COL32_R_SHIFT) & 0xFF); + out->G = (float)((v.col >> IM_COL32_G_SHIFT) & 0xFF); + out->B = (float)((v.col >> IM_COL32_B_SHIFT) & 0xFF); + out->A = (float)((v.col >> IM_COL32_A_SHIFT) & 0xFF); +} + +static void ImGui_ImplCPU_BuildVertexCache(ImVector* out, const ImDrawList* draw_list, const ImVec2& display_pos, const ImVec2& framebuffer_scale) +{ + out->resize(draw_list->VtxBuffer.Size); + for (int vtx_i = 0; vtx_i < draw_list->VtxBuffer.Size; vtx_i++) + ImGui_ImplCPU_SetupRasterVertex(&out->Data[vtx_i], draw_list->VtxBuffer[vtx_i], display_pos, framebuffer_scale); +} + +static void ImGui_ImplCPU_RenderTriangleFast(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo* texture, bool texture_is_alpha, const ImGui_ImplCPU_RasterVertex& v0, const ImGui_ImplCPU_RasterVertex& v1, const ImGui_ImplCPU_RasterVertex& v2, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + int min_x = std::max((int)std::floor(std::min(v0.X, std::min(v1.X, v2.X))), clip_min_x); + int min_y = std::max((int)std::floor(std::min(v0.Y, std::min(v1.Y, v2.Y))), clip_min_y); + int max_x = std::min((int)std::ceil(std::max(v0.X, std::max(v1.X, v2.X))) - 1, clip_max_x - 1); + int max_y = std::min((int)std::ceil(std::max(v0.Y, std::max(v1.Y, v2.Y))) - 1, clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + float area = ImGui_ImplCPU_Edge(v0.X, v0.Y, v1.X, v1.Y, v2.X, v2.Y); + if (area == 0.0f) + return; + + float sign = (area < 0.0f) ? -1.0f : 1.0f; + area *= sign; + + float e0_dx = (v1.Y - v2.Y) * sign; + float e0_dy = (v2.X - v1.X) * sign; + float e1_dx = (v2.Y - v0.Y) * sign; + float e1_dy = (v0.X - v2.X) * sign; + float e2_dx = (v0.Y - v1.Y) * sign; + float e2_dy = (v1.X - v0.X) * sign; + + float inv_area = 1.0f / area; + float dw0_dx = e0_dx * inv_area; + float dw0_dy = e0_dy * inv_area; + float dw1_dx = e1_dx * inv_area; + float dw1_dy = e1_dy * inv_area; + float dw2_dx = e2_dx * inv_area; + float dw2_dy = e2_dy * inv_area; + + float dr_dx = dw0_dx * v0.R + dw1_dx * v1.R + dw2_dx * v2.R; + float dg_dx = dw0_dx * v0.G + dw1_dx * v1.G + dw2_dx * v2.G; + float db_dx = dw0_dx * v0.B + dw1_dx * v1.B + dw2_dx * v2.B; + float da_dx = dw0_dx * v0.A + dw1_dx * v1.A + dw2_dx * v2.A; + float du_dx = dw0_dx * v0.U + dw1_dx * v1.U + dw2_dx * v2.U; + float dv_dx = dw0_dx * v0.V + dw1_dx * v1.V + dw2_dx * v2.V; + + float dr_dy = dw0_dy * v0.R + dw1_dy * v1.R + dw2_dy * v2.R; + float dg_dy = dw0_dy * v0.G + dw1_dy * v1.G + dw2_dy * v2.G; + float db_dy = dw0_dy * v0.B + dw1_dy * v1.B + dw2_dy * v2.B; + float da_dy = dw0_dy * v0.A + dw1_dy * v1.A + dw2_dy * v2.A; + float du_dy = dw0_dy * v0.U + dw1_dy * v1.U + dw2_dy * v2.U; + float dv_dy = dw0_dy * v0.V + dw1_dy * v1.V + dw2_dy * v2.V; + + float start_x = (float)min_x + 0.5f; + float start_y = (float)min_y + 0.5f; + + float e0_row = ImGui_ImplCPU_Edge(v1.X, v1.Y, v2.X, v2.Y, start_x, start_y) * sign; + float e1_row = ImGui_ImplCPU_Edge(v2.X, v2.Y, v0.X, v0.Y, start_x, start_y) * sign; + float e2_row = ImGui_ImplCPU_Edge(v0.X, v0.Y, v1.X, v1.Y, start_x, start_y) * sign; + + float w0_row = e0_row * inv_area; + float w1_row = e1_row * inv_area; + float w2_row = e2_row * inv_area; + + float r_row = w0_row * v0.R + w1_row * v1.R + w2_row * v2.R; + float g_row = w0_row * v0.G + w1_row * v1.G + w2_row * v2.G; + float b_row = w0_row * v0.B + w1_row * v1.B + w2_row * v2.B; + float a_row = w0_row * v0.A + w1_row * v1.A + w2_row * v2.A; + float u_row = w0_row * v0.U + w1_row * v1.U + w2_row * v2.U; + float v_row = w0_row * v0.V + w1_row * v1.V + w2_row * v2.V; + + int texture_max_x = texture ? (texture->Width - 1) : 0; + int texture_max_y = texture ? (texture->Height - 1) : 0; + + if (texture == nullptr) + { + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + ImGui_ImplCPU_BlendPixelFast(target, dst, ImGui_ImplCPU_ClampByte(r), ImGui_ImplCPU_ClampByte(g), ImGui_ImplCPU_ClampByte(b), ImGui_ImplCPU_ClampByte(a)); + + dst++; + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + } + return; + } + + if (texture_is_alpha) + { + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + float u = u_row; + float v = v_row; + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + ImGui_ImplCPU_BlendPixelFast(target, dst, + ImGui_ImplCPU_ClampByte(r), + ImGui_ImplCPU_ClampByte(g), + ImGui_ImplCPU_ClampByte(b), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(a), ImGui_ImplCPU_ReadPixelAlpha(*texture, tx, ty))); + } + + dst++; + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + u += du_dx; + v += dv_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + u_row += du_dy; + v_row += dv_dy; + } + return; + } + + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + float u = u_row; + float v = v_row; + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(*texture, tx, ty, &tr, &tg, &tb, &ta); + ImGui_ImplCPU_BlendPixelFast(target, dst, + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(r), tr), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(g), tg), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(b), tb), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(a), ta)); + } + + dst++; + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + u += du_dx; + v += dv_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + u_row += du_dy; + v_row += dv_dy; + } +} + +static void ImGui_ImplCPU_RenderTriangleGeneric(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo* texture, bool texture_is_alpha, const ImGui_ImplCPU_RasterVertex& v0, const ImGui_ImplCPU_RasterVertex& v1, const ImGui_ImplCPU_RasterVertex& v2, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + int min_x = std::max((int)std::floor(std::min(v0.X, std::min(v1.X, v2.X))), clip_min_x); + int min_y = std::max((int)std::floor(std::min(v0.Y, std::min(v1.Y, v2.Y))), clip_min_y); + int max_x = std::min((int)std::ceil(std::max(v0.X, std::max(v1.X, v2.X))) - 1, clip_max_x - 1); + int max_y = std::min((int)std::ceil(std::max(v0.Y, std::max(v1.Y, v2.Y))) - 1, clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + float area = ImGui_ImplCPU_Edge(v0.X, v0.Y, v1.X, v1.Y, v2.X, v2.Y); + if (area == 0.0f) + return; + + float sign = (area < 0.0f) ? -1.0f : 1.0f; + area *= sign; + + float e0_dx = (v1.Y - v2.Y) * sign; + float e0_dy = (v2.X - v1.X) * sign; + float e1_dx = (v2.Y - v0.Y) * sign; + float e1_dy = (v0.X - v2.X) * sign; + float e2_dx = (v0.Y - v1.Y) * sign; + float e2_dy = (v1.X - v0.X) * sign; + + float inv_area = 1.0f / area; + float dw0_dx = e0_dx * inv_area; + float dw0_dy = e0_dy * inv_area; + float dw1_dx = e1_dx * inv_area; + float dw1_dy = e1_dy * inv_area; + float dw2_dx = e2_dx * inv_area; + float dw2_dy = e2_dy * inv_area; + + float dr_dx = dw0_dx * v0.R + dw1_dx * v1.R + dw2_dx * v2.R; + float dg_dx = dw0_dx * v0.G + dw1_dx * v1.G + dw2_dx * v2.G; + float db_dx = dw0_dx * v0.B + dw1_dx * v1.B + dw2_dx * v2.B; + float da_dx = dw0_dx * v0.A + dw1_dx * v1.A + dw2_dx * v2.A; + float du_dx = dw0_dx * v0.U + dw1_dx * v1.U + dw2_dx * v2.U; + float dv_dx = dw0_dx * v0.V + dw1_dx * v1.V + dw2_dx * v2.V; + + float dr_dy = dw0_dy * v0.R + dw1_dy * v1.R + dw2_dy * v2.R; + float dg_dy = dw0_dy * v0.G + dw1_dy * v1.G + dw2_dy * v2.G; + float db_dy = dw0_dy * v0.B + dw1_dy * v1.B + dw2_dy * v2.B; + float da_dy = dw0_dy * v0.A + dw1_dy * v1.A + dw2_dy * v2.A; + float du_dy = dw0_dy * v0.U + dw1_dy * v1.U + dw2_dy * v2.U; + float dv_dy = dw0_dy * v0.V + dw1_dy * v1.V + dw2_dy * v2.V; + + float start_x = (float)min_x + 0.5f; + float start_y = (float)min_y + 0.5f; + + float e0_row = ImGui_ImplCPU_Edge(v1.X, v1.Y, v2.X, v2.Y, start_x, start_y) * sign; + float e1_row = ImGui_ImplCPU_Edge(v2.X, v2.Y, v0.X, v0.Y, start_x, start_y) * sign; + float e2_row = ImGui_ImplCPU_Edge(v0.X, v0.Y, v1.X, v1.Y, start_x, start_y) * sign; + + float w0_row = e0_row * inv_area; + float w1_row = e1_row * inv_area; + float w2_row = e2_row * inv_area; + + float r_row = w0_row * v0.R + w1_row * v1.R + w2_row * v2.R; + float g_row = w0_row * v0.G + w1_row * v1.G + w2_row * v2.G; + float b_row = w0_row * v0.B + w1_row * v1.B + w2_row * v2.B; + float a_row = w0_row * v0.A + w1_row * v1.A + w2_row * v2.A; + float u_row = w0_row * v0.U + w1_row * v1.U + w2_row * v2.U; + float v_row = w0_row * v0.V + w1_row * v1.V + w2_row * v2.V; + + int texture_max_x = texture ? (texture->Width - 1) : 0; + int texture_max_y = texture ? (texture->Height - 1) : 0; + + if (texture == nullptr) + { + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, ImGui_ImplCPU_ClampByte(r), ImGui_ImplCPU_ClampByte(g), ImGui_ImplCPU_ClampByte(b), ImGui_ImplCPU_ClampByte(a)); + + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + } + return; + } + + if (texture_is_alpha) + { + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + float u = u_row; + float v = v_row; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, + ImGui_ImplCPU_ClampByte(r), + ImGui_ImplCPU_ClampByte(g), + ImGui_ImplCPU_ClampByte(b), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(a), ImGui_ImplCPU_ReadPixelAlpha(*texture, tx, ty))); + } + + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + u += du_dx; + v += dv_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + u_row += du_dy; + v_row += dv_dy; + } + return; + } + + for (int y = min_y; y <= max_y; y++) + { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + float r = r_row; + float g = g_row; + float b = b_row; + float a = a_row; + float u = u_row; + float v = v_row; + + for (int x = min_x; x <= max_x; x++) + { + if (e0 >= 0.0f && e1 >= 0.0f && e2 >= 0.0f) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(*texture, tx, ty, &tr, &tg, &tb, &ta); + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(r), tr), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(g), tg), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(b), tb), + ImGui_ImplCPU_Mul255(ImGui_ImplCPU_ClampByte(a), ta)); + } + + e0 += e0_dx; + e1 += e1_dx; + e2 += e2_dx; + r += dr_dx; + g += dg_dx; + b += db_dx; + a += da_dx; + u += du_dx; + v += dv_dx; + } + + e0_row += e0_dy; + e1_row += e1_dy; + e2_row += e2_dy; + r_row += dr_dy; + g_row += dg_dy; + b_row += db_dy; + a_row += da_dy; + u_row += du_dy; + v_row += dv_dy; + } +} + +static bool ImGui_ImplCPU_DetectQuad(const ImGui_ImplCPU_RasterVertex* vertices, const ImDrawIdx* idx_buffer, int vtx_offset, unsigned int elem_offset, ImGui_ImplCPU_Quad* out) +{ + int unique_indices[4]; + int unique_count = 0; + for (int i = 0; i < 6; i++) + { + int idx = vtx_offset + idx_buffer[elem_offset + i]; + bool found = false; + for (int j = 0; j < unique_count; j++) + if (unique_indices[j] == idx) + found = true; + if (!found) + { + if (unique_count >= 4) + return false; + unique_indices[unique_count++] = idx; + } + } + if (unique_count != 4) + return false; + + const ImGui_ImplCPU_RasterVertex* candidates[4]; + for (int i = 0; i < 4; i++) + candidates[i] = &vertices[unique_indices[i]]; + + float min_x = candidates[0]->X; + float max_x = candidates[0]->X; + float min_y = candidates[0]->Y; + float max_y = candidates[0]->Y; + for (int i = 1; i < 4; i++) + { + min_x = std::min(min_x, candidates[i]->X); + max_x = std::max(max_x, candidates[i]->X); + min_y = std::min(min_y, candidates[i]->Y); + max_y = std::max(max_y, candidates[i]->Y); + } + if (!(max_x > min_x) || !(max_y > min_y)) + return false; + + out->TL = nullptr; + out->TR = nullptr; + out->BR = nullptr; + out->BL = nullptr; + for (int i = 0; i < 4; i++) + { + const ImGui_ImplCPU_RasterVertex* v = candidates[i]; + if (ImGui_ImplCPU_NearEqual(v->X, min_x) && ImGui_ImplCPU_NearEqual(v->Y, min_y)) + out->TL = v; + else if (ImGui_ImplCPU_NearEqual(v->X, max_x) && ImGui_ImplCPU_NearEqual(v->Y, min_y)) + out->TR = v; + else if (ImGui_ImplCPU_NearEqual(v->X, max_x) && ImGui_ImplCPU_NearEqual(v->Y, max_y)) + out->BR = v; + else if (ImGui_ImplCPU_NearEqual(v->X, min_x) && ImGui_ImplCPU_NearEqual(v->Y, max_y)) + out->BL = v; + else + return false; + } + return out->TL != nullptr && out->TR != nullptr && out->BR != nullptr && out->BL != nullptr; +} + +static bool ImGui_ImplCPU_QuadHasConstantColor(const ImGui_ImplCPU_Quad& quad) +{ + return ImGui_ImplCPU_SameColor(*quad.TL, *quad.TR) && ImGui_ImplCPU_SameColor(*quad.TL, *quad.BR) && ImGui_ImplCPU_SameColor(*quad.TL, *quad.BL); +} + +static bool ImGui_ImplCPU_QuadHasLinearUV(const ImGui_ImplCPU_Quad& quad) +{ + if (!ImGui_ImplCPU_NearEqual(quad.TL->Y, quad.TR->Y) || !ImGui_ImplCPU_NearEqual(quad.BL->Y, quad.BR->Y) || !ImGui_ImplCPU_NearEqual(quad.TL->X, quad.BL->X) || !ImGui_ImplCPU_NearEqual(quad.TR->X, quad.BR->X)) + return false; + float u = quad.TL->U + (quad.TR->U - quad.TL->U) + (quad.BL->U - quad.TL->U); + float v = quad.TL->V + (quad.TR->V - quad.TL->V) + (quad.BL->V - quad.TL->V); + return ImGui_ImplCPU_NearEqual(u, quad.BR->U) && ImGui_ImplCPU_NearEqual(v, quad.BR->V); +} + +static bool ImGui_ImplCPU_QuadGetConstantTexel(const ImGui_ImplCPU_SurfaceInfo& texture, const ImGui_ImplCPU_Quad& quad, int* out_x, int* out_y) +{ + int max_x = texture.Width - 1; + int max_y = texture.Height - 1; + int tx = ImGui_ImplCPU_ClampInt((int)(quad.TL->U * (float)max_x + 0.5f), 0, max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(quad.TL->V * (float)max_y + 0.5f), 0, max_y); + int corner_tx[3] = + { + ImGui_ImplCPU_ClampInt((int)(quad.TR->U * (float)max_x + 0.5f), 0, max_x), + ImGui_ImplCPU_ClampInt((int)(quad.BR->U * (float)max_x + 0.5f), 0, max_x), + ImGui_ImplCPU_ClampInt((int)(quad.BL->U * (float)max_x + 0.5f), 0, max_x) + }; + int corner_ty[3] = + { + ImGui_ImplCPU_ClampInt((int)(quad.TR->V * (float)max_y + 0.5f), 0, max_y), + ImGui_ImplCPU_ClampInt((int)(quad.BR->V * (float)max_y + 0.5f), 0, max_y), + ImGui_ImplCPU_ClampInt((int)(quad.BL->V * (float)max_y + 0.5f), 0, max_y) + }; + for (int i = 0; i < 3; i++) + if (corner_tx[i] != tx || corner_ty[i] != ty) + return false; + *out_x = tx; + *out_y = ty; + return true; +} + +static void ImGui_ImplCPU_RenderSolidRectFast(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int min_x = std::max((int)ceilf(quad.TL->X - 0.5f), clip_min_x); + int max_x = std::min((int)floorf(quad.TR->X - 0.5f), clip_max_x - 1); + int min_y = std::max((int)ceilf(quad.TL->Y - 0.5f), clip_min_y); + int max_y = std::min((int)floorf(quad.BL->Y - 0.5f), clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + int count = max_x - min_x + 1; + if (a == 255) + { + ImU32 pixel = ImGui_ImplCPU_PackFastRGBA(target.FastFormat, r, g, b, target.FastFormat.HasAlpha ? 255 : 255); + for (int y = min_y; y <= max_y; y++) + { + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + std::fill_n(dst, count, pixel); + } + return; + } + + for (int y = min_y; y <= max_y; y++) + { + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + for (int x = 0; x < count; x++) + ImGui_ImplCPU_BlendPixelFast(target, dst + x, r, g, b, a); + } +} + +static void ImGui_ImplCPU_RenderSolidRectGeneric(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int min_x = std::max((int)ceilf(quad.TL->X - 0.5f), clip_min_x); + int max_x = std::min((int)floorf(quad.TR->X - 0.5f), clip_max_x - 1); + int min_y = std::max((int)ceilf(quad.TL->Y - 0.5f), clip_min_y); + int max_y = std::min((int)floorf(quad.BL->Y - 0.5f), clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + ImU32 opaque_pixel = 0; + if (a == 255) + opaque_pixel = target.PackRGBAFn(target, r, g, b, target.HasAlpha ? 255 : 255); + + for (int y = min_y; y <= max_y; y++) + for (int x = min_x; x <= max_x; x++) + if (a == 255) + ImGui_ImplCPU_WritePixelRaw(target, x, y, opaque_pixel); + else + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, r, g, b, a); +} + +static void ImGui_ImplCPU_RenderTexturedRectFast(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo& texture, bool texture_is_alpha, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + int min_x = std::max((int)ceilf(quad.TL->X - 0.5f), clip_min_x); + int max_x = std::min((int)floorf(quad.TR->X - 0.5f), clip_max_x - 1); + int min_y = std::max((int)ceilf(quad.TL->Y - 0.5f), clip_min_y); + int max_y = std::min((int)floorf(quad.BL->Y - 0.5f), clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + unsigned char color_r = (unsigned char)quad.TL->R; + unsigned char color_g = (unsigned char)quad.TL->G; + unsigned char color_b = (unsigned char)quad.TL->B; + unsigned char color_a = (unsigned char)quad.TL->A; + float du_dx = (quad.TR->U - quad.TL->U) / (quad.TR->X - quad.TL->X); + float dv_dx = (quad.TR->V - quad.TL->V) / (quad.TR->X - quad.TL->X); + float du_dy = (quad.BL->U - quad.TL->U) / (quad.BL->Y - quad.TL->Y); + float dv_dy = (quad.BL->V - quad.TL->V) / (quad.BL->Y - quad.TL->Y); + float u_const = quad.TL->U - quad.TL->X * du_dx - quad.TL->Y * du_dy; + float v_const = quad.TL->V - quad.TL->X * dv_dx - quad.TL->Y * dv_dy; + int texture_max_x = texture.Width - 1; + int texture_max_y = texture.Height - 1; + + for (int y = min_y; y <= max_y; y++) + { + float py = (float)y + 0.5f; + float px = (float)min_x + 0.5f; + float u = u_const + px * du_dx + py * du_dy; + float v = v_const + px * dv_dx + py * dv_dy; + ImU32* dst = (ImU32*)(void*)(target.Pixels + (size_t)y * (size_t)target.Pitch) + min_x; + + if (texture_is_alpha) + { + for (int x = min_x; x <= max_x; x++) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + ImGui_ImplCPU_BlendPixelFast(target, dst, color_r, color_g, color_b, ImGui_ImplCPU_Mul255(color_a, ImGui_ImplCPU_ReadPixelAlpha(texture, tx, ty))); + dst++; + u += du_dx; + v += dv_dx; + } + } + else + { + for (int x = min_x; x <= max_x; x++) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(texture, tx, ty, &tr, &tg, &tb, &ta); + ImGui_ImplCPU_BlendPixelFast(target, dst, + ImGui_ImplCPU_Mul255(color_r, tr), + ImGui_ImplCPU_Mul255(color_g, tg), + ImGui_ImplCPU_Mul255(color_b, tb), + ImGui_ImplCPU_Mul255(color_a, ta)); + dst++; + u += du_dx; + v += dv_dx; + } + } + } +} + +static void ImGui_ImplCPU_RenderTexturedRectGeneric(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo& texture, bool texture_is_alpha, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + int min_x = std::max((int)ceilf(quad.TL->X - 0.5f), clip_min_x); + int max_x = std::min((int)floorf(quad.TR->X - 0.5f), clip_max_x - 1); + int min_y = std::max((int)ceilf(quad.TL->Y - 0.5f), clip_min_y); + int max_y = std::min((int)floorf(quad.BL->Y - 0.5f), clip_max_y - 1); + if (min_x > max_x || min_y > max_y) + return; + + unsigned char color_r = (unsigned char)quad.TL->R; + unsigned char color_g = (unsigned char)quad.TL->G; + unsigned char color_b = (unsigned char)quad.TL->B; + unsigned char color_a = (unsigned char)quad.TL->A; + float du_dx = (quad.TR->U - quad.TL->U) / (quad.TR->X - quad.TL->X); + float dv_dx = (quad.TR->V - quad.TL->V) / (quad.TR->X - quad.TL->X); + float du_dy = (quad.BL->U - quad.TL->U) / (quad.BL->Y - quad.TL->Y); + float dv_dy = (quad.BL->V - quad.TL->V) / (quad.BL->Y - quad.TL->Y); + float u_const = quad.TL->U - quad.TL->X * du_dx - quad.TL->Y * du_dy; + float v_const = quad.TL->V - quad.TL->X * dv_dx - quad.TL->Y * dv_dy; + int texture_max_x = texture.Width - 1; + int texture_max_y = texture.Height - 1; + + for (int y = min_y; y <= max_y; y++) + { + float py = (float)y + 0.5f; + float px = (float)min_x + 0.5f; + float u = u_const + px * du_dx + py * du_dy; + float v = v_const + px * dv_dx + py * dv_dy; + + if (texture_is_alpha) + { + for (int x = min_x; x <= max_x; x++) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, color_r, color_g, color_b, ImGui_ImplCPU_Mul255(color_a, ImGui_ImplCPU_ReadPixelAlpha(texture, tx, ty))); + u += du_dx; + v += dv_dx; + } + } + else + { + for (int x = min_x; x <= max_x; x++) + { + int tx = ImGui_ImplCPU_ClampInt((int)(u * (float)texture_max_x + 0.5f), 0, texture_max_x); + int ty = ImGui_ImplCPU_ClampInt((int)(v * (float)texture_max_y + 0.5f), 0, texture_max_y); + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(texture, tx, ty, &tr, &tg, &tb, &ta); + ImGui_ImplCPU_BlendPixelGeneric(target, x, y, + ImGui_ImplCPU_Mul255(color_r, tr), + ImGui_ImplCPU_Mul255(color_g, tg), + ImGui_ImplCPU_Mul255(color_b, tb), + ImGui_ImplCPU_Mul255(color_a, ta)); + u += du_dx; + v += dv_dx; + } + } + } +} + +static bool ImGui_ImplCPU_TryRenderQuadFast(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo* texture, bool texture_is_alpha, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + if (!ImGui_ImplCPU_QuadHasConstantColor(quad)) + return false; + + unsigned char r = (unsigned char)quad.TL->R; + unsigned char g = (unsigned char)quad.TL->G; + unsigned char b = (unsigned char)quad.TL->B; + unsigned char a = (unsigned char)quad.TL->A; + if (texture == nullptr) + { + ImGui_ImplCPU_RenderSolidRectFast(target, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y, r, g, b, a); + return true; + } + + int tx = 0; + int ty = 0; + if (ImGui_ImplCPU_QuadGetConstantTexel(*texture, quad, &tx, &ty)) + { + if (texture_is_alpha) + a = ImGui_ImplCPU_Mul255(a, ImGui_ImplCPU_ReadPixelAlpha(*texture, tx, ty)); + else + { + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(*texture, tx, ty, &tr, &tg, &tb, &ta); + r = ImGui_ImplCPU_Mul255(r, tr); + g = ImGui_ImplCPU_Mul255(g, tg); + b = ImGui_ImplCPU_Mul255(b, tb); + a = ImGui_ImplCPU_Mul255(a, ta); + } + ImGui_ImplCPU_RenderSolidRectFast(target, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y, r, g, b, a); + return true; + } + + if (!ImGui_ImplCPU_QuadHasLinearUV(quad)) + return false; + ImGui_ImplCPU_RenderTexturedRectFast(target, *texture, texture_is_alpha, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y); + return true; +} + +static bool ImGui_ImplCPU_TryRenderQuadGeneric(const ImGui_ImplCPU_SurfaceInfo& target, const ImGui_ImplCPU_SurfaceInfo* texture, bool texture_is_alpha, const ImGui_ImplCPU_Quad& quad, int clip_min_x, int clip_min_y, int clip_max_x, int clip_max_y) +{ + if (!ImGui_ImplCPU_QuadHasConstantColor(quad)) + return false; + + unsigned char r = (unsigned char)quad.TL->R; + unsigned char g = (unsigned char)quad.TL->G; + unsigned char b = (unsigned char)quad.TL->B; + unsigned char a = (unsigned char)quad.TL->A; + if (texture == nullptr) + { + ImGui_ImplCPU_RenderSolidRectGeneric(target, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y, r, g, b, a); + return true; + } + + int tx = 0; + int ty = 0; + if (ImGui_ImplCPU_QuadGetConstantTexel(*texture, quad, &tx, &ty)) + { + if (texture_is_alpha) + a = ImGui_ImplCPU_Mul255(a, ImGui_ImplCPU_ReadPixelAlpha(*texture, tx, ty)); + else + { + unsigned char tr, tg, tb, ta; + ImGui_ImplCPU_ReadPixelRGBA(*texture, tx, ty, &tr, &tg, &tb, &ta); + r = ImGui_ImplCPU_Mul255(r, tr); + g = ImGui_ImplCPU_Mul255(g, tg); + b = ImGui_ImplCPU_Mul255(b, tb); + a = ImGui_ImplCPU_Mul255(a, ta); + } + ImGui_ImplCPU_RenderSolidRectGeneric(target, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y, r, g, b, a); + return true; + } + + if (!ImGui_ImplCPU_QuadHasLinearUV(quad)) + return false; + ImGui_ImplCPU_RenderTexturedRectGeneric(target, *texture, texture_is_alpha, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y); + return true; +} + +bool ImGui_ImplCPU_Init() +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + ImGui_ImplCPU_Data* bd = IM_NEW(ImGui_ImplCPU_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_cpu"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; + return true; +} + +void ImGui_ImplCPU_UpdateTexture(ImTextureData* tex) +{ + const ImGui_ImplCPU_PixelFormat* format = nullptr; + if (tex->Format == ImTextureFormat_RGBA32) + format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888); + else if (tex->Format == ImTextureFormat_Alpha8) + format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_Alpha8); + else + IM_ASSERT(false && "Unsupported ImTextureData format for imgui_impl_cpu"); + + if (tex->Status == ImTextureStatus_WantCreate) + { + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + + ImGui_ImplCPU_Texture* cpu_texture = IM_NEW(ImGui_ImplCPU_Texture)(); + cpu_texture->Pixels = (const unsigned char*)tex->GetPixels(); + cpu_texture->Width = tex->Width; + cpu_texture->Height = tex->Height; + cpu_texture->Pitch = tex->GetPitch(); + cpu_texture->Format = format; + tex->BackendUserData = cpu_texture; + tex->SetTexID((ImTextureID)(intptr_t)cpu_texture); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + ImGui_ImplCPU_Texture* cpu_texture = (ImGui_ImplCPU_Texture*)tex->BackendUserData; + IM_ASSERT(cpu_texture != nullptr); + cpu_texture->Pixels = (const unsigned char*)tex->GetPixels(); + cpu_texture->Width = tex->Width; + cpu_texture->Height = tex->Height; + cpu_texture->Pitch = tex->GetPitch(); + cpu_texture->Format = format; + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + ImGui_ImplCPU_Texture* cpu_texture = (ImGui_ImplCPU_Texture*)tex->BackendUserData; + if (cpu_texture != nullptr) + IM_DELETE(cpu_texture); + tex->BackendUserData = nullptr; + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + } +} + +static void ImGui_ImplCPU_DestroyDeviceObjects() +{ + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplCPU_UpdateTexture(tex); + } +} + +void ImGui_ImplCPU_Shutdown() +{ + ImGui_ImplCPU_Data* bd = ImGui_ImplCPU_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplCPU_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + +void ImGui_ImplCPU_NewFrame() +{ + ImGui_ImplCPU_Data* bd = ImGui_ImplCPU_GetBackendData(); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplCPU_Init()?"); + IM_UNUSED(bd); +} + +void ImGui_ImplCPU_RenderDrawData(ImDrawData* draw_data, ImGui_ImplCPU_Framebuffer* framebuffer) +{ + ImGui_ImplCPU_Data* bd = ImGui_ImplCPU_GetBackendData(); + if (bd == nullptr || draw_data == nullptr) + return; + + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplCPU_UpdateTexture(tex); + + if (framebuffer == nullptr || framebuffer->Pixels == nullptr || framebuffer->Width <= 0 || framebuffer->Height <= 0 || framebuffer->Pitch <= 0) + return; + + ImGui_ImplCPU_SurfaceInfo target; + ImGui_ImplCPU_InitBufferInfo(&target, framebuffer); + if (target.Format == nullptr || target.Format->BytesPerPixel <= 0) + return; + + const ImVec2 clip_off = draw_data->DisplayPos; + const ImVec2 clip_scale = draw_data->FramebufferScale; + + const ImGui_ImplCPU_Texture* active_texture = nullptr; + ImGui_ImplCPU_SurfaceInfo active_texture_info; + bool active_texture_valid = false; + bool active_texture_is_alpha = false; + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* draw_list = draw_data->CmdLists[n]; + const ImDrawIdx* idx_buffer = draw_list->IdxBuffer.Data; + ImGui_ImplCPU_BuildVertexCache(&bd->VertexCache, draw_list, clip_off, clip_scale); + const ImGui_ImplCPU_RasterVertex* vertex_cache = bd->VertexCache.Data; + + for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) + { + if (pcmd->UserCallback != ImDrawCallback_ResetRenderState) + pcmd->UserCallback(draw_list, pcmd); + continue; + } + if (pcmd->ElemCount == 0) + continue; + + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_min.x < 0.0f) clip_min.x = 0.0f; + if (clip_min.y < 0.0f) clip_min.y = 0.0f; + if (clip_max.x > (float)target.Width) clip_max.x = (float)target.Width; + if (clip_max.y > (float)target.Height) clip_max.y = (float)target.Height; + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + int clip_min_x = (int)clip_min.x; + int clip_min_y = (int)clip_min.y; + int clip_max_x = (int)clip_max.x; + int clip_max_y = (int)clip_max.y; + + const ImGui_ImplCPU_Texture* texture = (const ImGui_ImplCPU_Texture*)(intptr_t)pcmd->GetTexID(); + if (texture != active_texture) + { + active_texture = texture; + active_texture_valid = false; + active_texture_is_alpha = false; + if (texture != nullptr) + { + ImGui_ImplCPU_InitTextureInfo(&active_texture_info, texture); + active_texture_valid = active_texture_info.Pixels != nullptr && active_texture_info.Width > 0 && active_texture_info.Height > 0 && active_texture_info.Pitch > 0 && active_texture_info.Format != nullptr; + active_texture_is_alpha = active_texture_valid && active_texture_info.IsAlphaOnly; + } + } + + const ImGui_ImplCPU_SurfaceInfo* texture_ptr = active_texture_valid ? &active_texture_info : nullptr; + for (unsigned int idx = 0; idx + 2 < (unsigned int)pcmd->ElemCount;) + { + if (idx + 5 < (unsigned int)pcmd->ElemCount) + { + ImGui_ImplCPU_Quad quad; + if (ImGui_ImplCPU_DetectQuad(vertex_cache, idx_buffer, pcmd->VtxOffset, pcmd->IdxOffset + idx, &quad)) + { + bool rendered = target.IsFast + ? ImGui_ImplCPU_TryRenderQuadFast(target, texture_ptr, active_texture_is_alpha, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y) + : ImGui_ImplCPU_TryRenderQuadGeneric(target, texture_ptr, active_texture_is_alpha, quad, clip_min_x, clip_min_y, clip_max_x, clip_max_y); + if (rendered) + { + idx += 6; + continue; + } + } + } + + ImDrawIdx idx0 = idx_buffer[pcmd->IdxOffset + idx + 0]; + ImDrawIdx idx1 = idx_buffer[pcmd->IdxOffset + idx + 1]; + ImDrawIdx idx2 = idx_buffer[pcmd->IdxOffset + idx + 2]; + const ImGui_ImplCPU_RasterVertex& v0 = vertex_cache[pcmd->VtxOffset + idx0]; + const ImGui_ImplCPU_RasterVertex& v1 = vertex_cache[pcmd->VtxOffset + idx1]; + const ImGui_ImplCPU_RasterVertex& v2 = vertex_cache[pcmd->VtxOffset + idx2]; + if (target.IsFast) + ImGui_ImplCPU_RenderTriangleFast(target, texture_ptr, active_texture_is_alpha, v0, v1, v2, clip_min_x, clip_min_y, clip_max_x, clip_max_y); + else + ImGui_ImplCPU_RenderTriangleGeneric(target, texture_ptr, active_texture_is_alpha, v0, v1, v2, clip_min_x, clip_min_y, clip_max_x, clip_max_y); + idx += 3; + } + } + } +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_cpu.h b/backends/imgui_impl_cpu.h new file mode 100644 index 00000000..0acd0527 --- /dev/null +++ b/backends/imgui_impl_cpu.h @@ -0,0 +1,81 @@ +// dear imgui: CPU framebuffer renderer backend +// (supports caller-owned framebuffers/textures in generic packed pixel formats) +// +// Implemented features: +// [X] Renderer: User texture binding. Use 'ImGui_ImplCPU_Texture*' as texture identifier. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices. +// [X] Renderer: Texture updates support for dynamic font atlas. + +#pragma once +#ifndef IMGUI_DISABLE +#include "imgui.h" + +struct ImGui_ImplCPU_PixelFormat; +typedef ImU32 (*ImGui_ImplCPU_MapColorFn)(const ImGui_ImplCPU_PixelFormat* format, unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +struct ImGui_ImplCPU_PixelFormat +{ + int BitsPerPixel; + int BytesPerPixel; + ImU32 Rmask; + ImU32 Gmask; + ImU32 Bmask; + ImU32 Amask; + ImU8 Rshift; + ImU8 Gshift; + ImU8 Bshift; + ImU8 Ashift; + ImU8 Rloss; + ImU8 Gloss; + ImU8 Bloss; + ImU8 Aloss; + const ImU32* Palette; // Optional palette entries stored as IM_COL32(r, g, b, a). + int PaletteSize; // Number of palette entries, or 0 when Palette is unused. + ImGui_ImplCPU_MapColorFn MapColorFn; // Optional indexed/paletted mapping override. Return a raw packed pixel value. +}; + +enum ImGui_ImplCPU_BuiltinFormat +{ + ImGui_ImplCPU_BuiltinFormat_RGBA8888 = 0, + ImGui_ImplCPU_BuiltinFormat_BGRA8888, + ImGui_ImplCPU_BuiltinFormat_ARGB8888, + ImGui_ImplCPU_BuiltinFormat_ABGR8888, + ImGui_ImplCPU_BuiltinFormat_RGB565, + ImGui_ImplCPU_BuiltinFormat_ARGB1555, + ImGui_ImplCPU_BuiltinFormat_RGBA4444, + ImGui_ImplCPU_BuiltinFormat_RGB888, + ImGui_ImplCPU_BuiltinFormat_BGR888, + ImGui_ImplCPU_BuiltinFormat_Alpha8, + ImGui_ImplCPU_BuiltinFormat_COUNT +}; + +struct ImGui_ImplCPU_Framebuffer +{ + unsigned char* Pixels; + int Width; + int Height; + int Pitch; + const ImGui_ImplCPU_PixelFormat* Format; // Optional. Defaults to ImGui_ImplCPU_BuiltinFormat_RGBA8888. +}; + +struct ImGui_ImplCPU_Texture +{ + const unsigned char* Pixels; + int Width; + int Height; + int Pitch; + const ImGui_ImplCPU_PixelFormat* Format; // Optional. Defaults to ImGui_ImplCPU_BuiltinFormat_RGBA8888. +}; + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API const ImGui_ImplCPU_PixelFormat* ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat format); +IMGUI_IMPL_API bool ImGui_ImplCPU_Init(); +IMGUI_IMPL_API void ImGui_ImplCPU_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplCPU_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplCPU_RenderDrawData(ImDrawData* draw_data, ImGui_ImplCPU_Framebuffer* framebuffer); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates +// by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplCPU_UpdateTexture(ImTextureData* tex); + +#endif // #ifndef IMGUI_DISABLE diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index afb908d5..13d49f2d 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -84,6 +84,7 @@ List of Platforms Backends: List of Renderer Backends: + imgui_impl_cpu.cpp ; Generic packed-pixel CPU framebuffer renderer (RGBA/BGRA/RGB565/indexed, etc. see example_sdl2_cpu, example_apple_cpu) imgui_impl_dx9.cpp ; DirectX9 imgui_impl_dx10.cpp ; DirectX10 imgui_impl_dx11.cpp ; DirectX11 @@ -105,7 +106,7 @@ List of high-level Frameworks Backends (combining Platform + Renderer): imgui_impl_null.cpp Emscripten is also supported! -The SDL2+GL, SDL3+GL, SDL2+Surface, SDL3+Surface, GLFW+GL and GLFW+WebGPU examples are all ready to build and run with Emscripten. +The SDL2+CPU, SDL2+GL, SDL3+GL, SDL2+Surface, SDL3+Surface, GLFW+GL and GLFW+WebGPU examples are all ready to build and run with Emscripten. ### Recommended Backends diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 9ccaa7d6..9702a2e2 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -71,6 +71,11 @@ OSX + OpenGL2 example.
(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) +[example_apple_cpu/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_cpu/)
+OSX + CPU framebuffer example.
+= main.mm + imgui_impl_osx.mm + imgui_impl_cpu.cpp
+This is a native Cocoa example, renders to a native BGRA framebuffer, and does not use SDL.
+ [example_glfw_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_wgpu/)
GLFW + WebGPU example. Supports Emscripten (web), Dawn (native), WGPU (native).
= main.cpp + imgui_impl_glfw.cpp + imgui_impl_wgpu.cpp @@ -150,6 +155,12 @@ SDL2 (Win32, Mac, Linux, etc.) + SDL_Surface CPU renderer example.
= main.cpp + imgui_impl_sdl2.cpp + imgui_impl_sdlsurface2.cpp
This supports building with Emscripten.
+[example_sdl2_cpu/](https://github.com/ocornut/imgui/blob/master/examples/example_sdl2_cpu/)
+SDL2 (Win32, Mac, Linux, etc.) + caller-owned packed-pixel CPU framebuffer renderer example.
+= main.cpp + imgui_impl_sdl2.cpp + imgui_impl_cpu.cpp
+This derives the CPU framebuffer format from the current SDL window surface.
+This supports building with Emscripten.
+ [example_sdl2_vulkan/](https://github.com/ocornut/imgui/blob/master/examples/example_sdl2_vulkan/)
SDL2 (Win32, Mac, Linux, etc.) + Vulkan example.
= main.cpp + imgui_impl_sdl2.cpp + imgui_impl_vulkan.cpp
diff --git a/examples/example_apple_cpu/Makefile b/examples/example_apple_cpu/Makefile new file mode 100644 index 00000000..c5e9c977 --- /dev/null +++ b/examples/example_apple_cpu/Makefile @@ -0,0 +1,23 @@ +# Makefile for example_apple_cpu, for macOS only +CXX = clang++ +EXE = example_apple_cpu +IMGUI_DIR = ../.. +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_osx.mm $(IMGUI_DIR)/backends/imgui_impl_cpu.cpp + +OPTFLAGS ?= -O2 +CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -g $(OPTFLAGS) -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +FRAMEWORKS = -framework Cocoa -framework GameController + +all: $(EXE) + @echo Build complete for Mac OS X + +$(EXE): $(SOURCES) + $(CXX) $(CXXFLAGS) $(SOURCES) -o $(EXE) $(FRAMEWORKS) + +run: all + ./$(EXE) + +clean: + rm -f $(EXE) *.o diff --git a/examples/example_apple_cpu/README.md b/examples/example_apple_cpu/README.md new file mode 100644 index 00000000..4dc34e6f --- /dev/null +++ b/examples/example_apple_cpu/README.md @@ -0,0 +1,14 @@ +# How to Build + +## macOS + +Use the provided `Makefile` or directly: +```sh +clang++ -std=c++11 -ObjC++ -fobjc-arc -I ../.. -I ../../backends \ + main.mm ../../backends/imgui_impl_osx.mm ../../backends/imgui_impl_cpu.cpp ../../imgui*.cpp \ + -framework Cocoa -framework GameController \ + -o example_apple_cpu +``` + +This example uses Cocoa + `imgui_impl_osx` for windowing/input and `imgui_impl_cpu` for rendering. +It renders to a native BGRA CPU framebuffer and does not use SDL. diff --git a/examples/example_apple_cpu/example_apple_cpu b/examples/example_apple_cpu/example_apple_cpu new file mode 100755 index 00000000..4e6ce95c Binary files /dev/null and b/examples/example_apple_cpu/example_apple_cpu differ diff --git a/examples/example_apple_cpu/main.mm b/examples/example_apple_cpu/main.mm new file mode 100644 index 00000000..edc7b3c0 --- /dev/null +++ b/examples/example_apple_cpu/main.mm @@ -0,0 +1,424 @@ +// Dear ImGui: standalone macOS example using Cocoa + CPU framebuffer rendering. + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#import + +#include "imgui.h" +#include "imgui_impl_cpu.h" +#include "imgui_impl_osx.h" +#include +#include +#include + +struct ExampleTimingHistory +{ + double Samples[120]; + int Count; + int Offset; + + ExampleTimingHistory() : Count(0), Offset(0) { memset(Samples, 0, sizeof(Samples)); } + + void AddSample(double value_ms) + { + Samples[Offset] = value_ms; + Offset = (Offset + 1) % IM_ARRAYSIZE(Samples); + if (Count < IM_ARRAYSIZE(Samples)) + Count++; + } + + double GetAverageMs() const + { + if (Count == 0) + return 0.0; + double total = 0.0; + for (int i = 0; i < Count; i++) + total += Samples[i]; + return total / (double)Count; + } +}; + +struct ExampleFramebuffer +{ + ImVector Storage; + ImGui_ImplCPU_Framebuffer CPU; + CGColorSpaceRef ColorSpace; + CGDataProviderRef DataProvider; + CGImageRef Image; + + ExampleFramebuffer() : ColorSpace(nullptr), DataProvider(nullptr), Image(nullptr) + { + CPU.Pixels = nullptr; + CPU.Width = 0; + CPU.Height = 0; + CPU.Pitch = 0; + CPU.Format = nullptr; + } +}; + +static double ExampleGetTimeMs() +{ + typedef std::chrono::steady_clock Clock; + return std::chrono::duration(Clock::now().time_since_epoch()).count(); +} + +static void DestroyFramebuffer(ExampleFramebuffer* framebuffer) +{ + if (framebuffer->Image != nullptr) + { + CGImageRelease(framebuffer->Image); + framebuffer->Image = nullptr; + } + if (framebuffer->DataProvider != nullptr) + { + CGDataProviderRelease(framebuffer->DataProvider); + framebuffer->DataProvider = nullptr; + } + if (framebuffer->ColorSpace != nullptr) + { + CGColorSpaceRelease(framebuffer->ColorSpace); + framebuffer->ColorSpace = nullptr; + } + + framebuffer->CPU.Pixels = nullptr; + framebuffer->CPU.Width = 0; + framebuffer->CPU.Height = 0; + framebuffer->CPU.Pitch = 0; + framebuffer->Storage.clear(); +} + +static ImU32 PackBGRA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return ((ImU32)b << 24) | ((ImU32)g << 16) | ((ImU32)r << 8) | (ImU32)a; +#else + return (ImU32)b | ((ImU32)g << 8) | ((ImU32)r << 16) | ((ImU32)a << 24); +#endif +} + +static bool RecreateFramebuffer(ExampleFramebuffer* framebuffer, int width, int height) +{ + DestroyFramebuffer(framebuffer); + + framebuffer->Storage.resize(width * height * 4); + framebuffer->CPU.Pixels = framebuffer->Storage.Data; + framebuffer->CPU.Width = width; + framebuffer->CPU.Height = height; + framebuffer->CPU.Pitch = width * 4; + framebuffer->CPU.Format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGRA8888); + if (framebuffer->CPU.Pixels == nullptr) + return false; + + framebuffer->ColorSpace = CGColorSpaceCreateDeviceRGB(); + if (framebuffer->ColorSpace == nullptr) + { + DestroyFramebuffer(framebuffer); + return false; + } + + framebuffer->DataProvider = CGDataProviderCreateWithData(nullptr, framebuffer->CPU.Pixels, (size_t)framebuffer->CPU.Pitch * (size_t)framebuffer->CPU.Height, nullptr); + if (framebuffer->DataProvider == nullptr) + { + DestroyFramebuffer(framebuffer); + return false; + } + + framebuffer->Image = CGImageCreate((size_t)framebuffer->CPU.Width, (size_t)framebuffer->CPU.Height, 8, 32, (size_t)framebuffer->CPU.Pitch, framebuffer->ColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, framebuffer->DataProvider, nullptr, false, kCGRenderingIntentDefault); + if (framebuffer->Image == nullptr) + { + DestroyFramebuffer(framebuffer); + return false; + } + + return true; +} + +static void ClearFramebuffer(ImGui_ImplCPU_Framebuffer* framebuffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + const ImU32 pixel = PackBGRA(r, g, b, a); + for (int y = 0; y < framebuffer->Height; y++) + { + ImU32* dst = (ImU32*)(void*)(framebuffer->Pixels + (size_t)y * (size_t)framebuffer->Pitch); + for (int x = 0; x < framebuffer->Width; x++) + dst[x] = pixel; + } +} + +//----------------------------------------------------------------------------------- +// AppView +//----------------------------------------------------------------------------------- + +@interface AppView : NSView +- (void)shutdownImGui; +@end + +@implementation AppView +{ + ExampleFramebuffer _framebuffer; + ExampleTimingHistory _rasterTimes; + ExampleTimingHistory _presentTimes; + BOOL _imguiInitialized; + BOOL _frameScheduled; +} + +- (instancetype)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + return self; +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (void)viewDidMoveToWindow +{ + [super viewDidMoveToWindow]; + if (self.window != nil && !_imguiInitialized) + [self initializeImGui]; +} + +- (void)dealloc +{ + [self shutdownImGui]; +} + +- (void)initializeImGui +{ + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + ImGui::StyleColorsDark(); + + ImGui_ImplOSX_Init(self); + ImGui_ImplCPU_Init(); + + _imguiInitialized = YES; + [self scheduleNextFrameAfter:0.0]; +} + +- (void)shutdownImGui +{ + if (!_imguiInitialized) + return; + + DestroyFramebuffer(&_framebuffer); + ImGui_ImplCPU_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); + _imguiInitialized = NO; +} + +// Queue the next draw on the main loop instead of pacing to display refresh. +- (void)scheduleNextFrameAfter:(double)delay_seconds +{ + if (_frameScheduled) + return; + _frameScheduled = YES; + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay_seconds * (double)NSEC_PER_SEC)); + dispatch_after(when, dispatch_get_main_queue(), ^{ + self->_frameScheduled = NO; + [self setNeedsDisplay:YES]; + }); +} + +- (void)renderFrame +{ + if (!_imguiInitialized) + return; + + if (self.window == nil || [self.window isMiniaturized]) + { + [self scheduleNextFrameAfter:0.01]; + return; + } + + const CGFloat scale = self.window.backingScaleFactor > 0.0 ? self.window.backingScaleFactor : 1.0; + const int logical_w = (int)llround(self.bounds.size.width); + const int logical_h = (int)llround(self.bounds.size.height); + const int framebuffer_w = (int)llround(self.bounds.size.width * scale); + const int framebuffer_h = (int)llround(self.bounds.size.height * scale); + if (framebuffer_w <= 0 || framebuffer_h <= 0) + { + [self scheduleNextFrameAfter:0.01]; + return; + } + + if (_framebuffer.CPU.Width != framebuffer_w || _framebuffer.CPU.Height != framebuffer_h) + if (!RecreateFramebuffer(&_framebuffer, framebuffer_w, framebuffer_h)) + return; + + // Start the Dear ImGui frame + ImGui_ImplCPU_NewFrame(); + ImGui_ImplOSX_NewFrame(self); + ImGui::NewFrame(); + + // Our state + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // 1. Show the big demo window + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. + { + static float f = 0.0f; + static int counter = 0; + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Begin("Hello, world!"); + + ImGui::Text("This is some useful text."); + ImGui::Checkbox("Demo Window", &show_demo_window); + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); + ImGui::ColorEdit3("clear color", (float*)&clear_color); + + if (ImGui::Button("Button")) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Raster %.3f ms | Present %.3f ms", _rasterTimes.GetAverageMs(), _presentTimes.GetAverageMs()); + ImGui::Text("Window %d x %d | Framebuffer %d x %d (scale %.2f)", logical_w, logical_h, framebuffer_w, framebuffer_h, scale); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + const double raster_start_ms = ExampleGetTimeMs(); + ClearFramebuffer(&_framebuffer.CPU, + (unsigned char)(clear_color.x * 255), + (unsigned char)(clear_color.y * 255), + (unsigned char)(clear_color.z * 255), + (unsigned char)(clear_color.w * 255)); + ImGui_ImplCPU_RenderDrawData(ImGui::GetDrawData(), &_framebuffer.CPU); + _rasterTimes.AddSample(ExampleGetTimeMs() - raster_start_ms); +} + +- (void)drawRect:(NSRect)dirtyRect +{ + IM_UNUSED(dirtyRect); + + [self renderFrame]; + + if (_framebuffer.Image != nullptr) + { + CGContextRef context = NSGraphicsContext.currentContext.CGContext; + // CGImage draws with a bottom-up raster convention here, while the CPU framebuffer is top-down. + const double present_start_ms = ExampleGetTimeMs(); + CGContextSaveGState(context); + CGContextTranslateCTM(context, 0.0, self.bounds.size.height); + CGContextScaleCTM(context, 1.0, -1.0); + CGContextDrawImage(context, CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height), _framebuffer.Image); + CGContextRestoreGState(context); + _presentTimes.AddSample(ExampleGetTimeMs() - present_start_ms); + } + + [self scheduleNextFrameAfter:0.0]; +} + +@end + +//----------------------------------------------------------------------------------- +// AppDelegate +//----------------------------------------------------------------------------------- + +@interface AppDelegate : NSObject +@property (nonatomic, readonly) NSWindow* window; +@property (nonatomic, readonly) AppView* view; +@end + +@implementation AppDelegate +{ + NSWindow* _window; + AppView* _view; +} + +@synthesize window = _window; +@synthesize view = _view; + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)application +{ + IM_UNUSED(application); + return YES; +} + +- (void)setupMenu +{ + NSMenu* main_menu_bar = [[NSMenu alloc] init]; + NSMenu* app_menu = [[NSMenu alloc] initWithTitle:@"Dear ImGui macOS+CPU Example"]; + NSMenuItem* menu_item = [app_menu addItemWithTitle:@"Quit Dear ImGui macOS+CPU Example" action:@selector(terminate:) keyEquivalent:@"q"]; + [menu_item setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + + menu_item = [[NSMenuItem alloc] init]; + [menu_item setSubmenu:app_menu]; + [main_menu_bar addItem:menu_item]; + [NSApp setMainMenu:main_menu_bar]; +} + +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + IM_UNUSED(notification); + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [self setupMenu]; + + NSRect view_rect = NSMakeRect(100.0, 100.0, 1280.0, 720.0); + _window = [[NSWindow alloc] initWithContentRect:view_rect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO]; + [_window setTitle:@"Dear ImGui macOS+CPU Example"]; + [_window setAcceptsMouseMovedEvents:YES]; + [_window setOpaque:YES]; + + _view = [[AppView alloc] initWithFrame:_window.contentView.bounds]; + [_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [_window setContentView:_view]; + [_window makeKeyAndOrderFront:NSApp]; + [NSApp activateIgnoringOtherApps:YES]; +} + +- (void)applicationWillTerminate:(NSNotification*)notification +{ + IM_UNUSED(notification); + [_view shutdownImGui]; +} + +@end + +int main(int argc, char** argv) +{ + IM_UNUSED(argc); + IM_UNUSED(argv); + + @autoreleasepool + { + NSApplication* application = [NSApplication sharedApplication]; + AppDelegate* delegate = [AppDelegate new]; + [application setDelegate:delegate]; + [application run]; + } + + return 0; +} diff --git a/examples/example_sdl2_cpu/Makefile b/examples/example_sdl2_cpu/Makefile new file mode 100644 index 00000000..e665183e --- /dev/null +++ b/examples/example_sdl2_cpu/Makefile @@ -0,0 +1,73 @@ +# +# Cross Platform Makefile (example_sdl2_cpu) +# Compatible with MSYS2/MINGW, Linux and macOS +# +# You will need SDL2 (http://www.libsdl.org): +# Linux: apt-get install libsdl2-dev +# macOS: brew install sdl2 +# MSYS2: pacman -S mingw-w64-i686-SDL2 +# + +EXE = example_sdl2_cpu +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl2.cpp $(IMGUI_DIR)/backends/imgui_impl_cpu.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +SDL2_CONFIG ?= $(shell if command -v sdl2-config >/dev/null 2>&1; then command -v sdl2-config; elif [ -x /opt/homebrew/bin/sdl2-config ]; then echo /opt/homebrew/bin/sdl2-config; elif [ -x /usr/local/bin/sdl2-config ]; then echo /usr/local/bin/sdl2-config; else echo sdl2-config; fi) + +CXXFLAGS = -std=c++11 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +OPTFLAGS ?= -O2 +CXXFLAGS += -g $(OPTFLAGS) -Wall -Wformat +LIBS = + +##--------------------------------------------------------------------- +## BUILD FLAGS PER PLATFORM +##--------------------------------------------------------------------- + +ifeq ($(UNAME_S), Linux) #LINUX + ECHO_MESSAGE = "Linux" + LIBS += `$(SDL2_CONFIG) --libs` + + CXXFLAGS += `$(SDL2_CONFIG) --cflags` + CFLAGS = $(CXXFLAGS) +endif + +ifeq ($(UNAME_S), Darwin) #APPLE + ECHO_MESSAGE = "Mac OS X" + LIBS += `$(SDL2_CONFIG) --libs` + CXXFLAGS += `$(SDL2_CONFIG) --cflags` + CXXFLAGS += -I/usr/local/include -I/opt/local/include + CFLAGS = $(CXXFLAGS) +endif + +ifeq ($(OS), Windows_NT) + ECHO_MESSAGE = "MinGW" + LIBS += -lgdi32 `pkg-config --static --libs sdl2` + + CXXFLAGS += `pkg-config --cflags sdl2` + CFLAGS = $(CXXFLAGS) +endif + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(ECHO_MESSAGE) + +$(EXE): $(OBJS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS) + +clean: + rm -f $(EXE) $(OBJS) diff --git a/examples/example_sdl2_cpu/Makefile.emscripten b/examples/example_sdl2_cpu/Makefile.emscripten new file mode 100644 index 00000000..cc0a5e0f --- /dev/null +++ b/examples/example_sdl2_cpu/Makefile.emscripten @@ -0,0 +1,54 @@ +CC = emcc +CXX = em++ +WEB_DIR = web +EXE = $(WEB_DIR)/index.html +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl2.cpp $(IMGUI_DIR)/backends/imgui_impl_cpu.cpp +OBJS = $(addprefix em_,$(addsuffix .o, $(basename $(notdir $(SOURCES))))) +CPPFLAGS = +LDFLAGS = +EMS = + +EMS += -s USE_SDL=2 +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 + +USE_FILE_SYSTEM ?= 0 +ifeq ($(USE_FILE_SYSTEM), 0) +LDFLAGS += -s NO_FILESYSTEM=1 +CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS +endif +ifeq ($(USE_FILE_SYSTEM), 1) +LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts +endif + +CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +CPPFLAGS += -Wall -Wformat -O2 $(EMS) +LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html +LDFLAGS += $(EMS) + +em_%.o:%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +em_%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +em_%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(EXE) + +$(WEB_DIR): + mkdir $@ + +serve: all + python3 -m http.server -d $(WEB_DIR) + +$(EXE): $(OBJS) $(WEB_DIR) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -rf $(OBJS) $(WEB_DIR) diff --git a/examples/example_sdl2_cpu/README.md b/examples/example_sdl2_cpu/README.md new file mode 100644 index 00000000..d5acbb9d --- /dev/null +++ b/examples/example_sdl2_cpu/README.md @@ -0,0 +1,47 @@ +# How to Build + +## Windows with Visual Studio's IDE + +Use the provided project file (`example_sdl2_cpu.vcxproj`) or open `imgui_examples.sln`. + +## Windows with Visual Studio's CLI + +Use `build_win32.bat` or directly: +``` +set SDL2_DIR=path_to_your_sdl2_folder +cl /Zi /MD /utf-8 /I.. /I..\.. /I%SDL2_DIR%\include main.cpp ..\..\backends\imgui_impl_sdl2.cpp ..\..\backends\imgui_impl_cpu.cpp ..\..\imgui*.cpp /FeDebug/example_sdl2_cpu.exe /FoDebug/ /link /libpath:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib /subsystem:console +# ^^ include paths ^^ source files ^^ output exe ^^ output dir ^^ libraries +# or for 64-bit: +cl /Zi /MD /utf-8 /I.. /I..\.. /I%SDL2_DIR%\include main.cpp ..\..\backends\imgui_impl_sdl2.cpp ..\..\backends\imgui_impl_cpu.cpp ..\..\imgui*.cpp /FeDebug/example_sdl2_cpu.exe /FoDebug/ /link /libpath:%SDL2_DIR%\lib\x64 SDL2.lib SDL2main.lib /subsystem:console +``` + +## Linux and similar Unixes + +Use the provided `Makefile` or directly: +``` +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends \ + main.cpp ../../backends/imgui_impl_sdl2.cpp ../../backends/imgui_impl_cpu.cpp ../../imgui*.cpp \ + `sdl2-config --libs` +``` + +## macOS + +Use the provided `Makefile` or directly: +``` +brew install sdl2 +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends \ + main.cpp ../../backends/imgui_impl_sdl2.cpp ../../backends/imgui_impl_cpu.cpp ../../imgui*.cpp \ + `sdl2-config --libs` +``` + +## Emscripten + +Use `Makefile.emscripten`: +```sh +source /path/to/emsdk/emsdk_env.sh +make -f Makefile.emscripten +``` + +This makes `web/index.html`, `web/index.js`, and `web/index.wasm`. + +This example keeps Dear ImGui on the CPU and allocates its framebuffer in the active SDL window surface format, rather than forcing a fixed RGBA32 output format. diff --git a/examples/example_sdl2_cpu/build_win32.bat b/examples/example_sdl2_cpu/build_win32.bat new file mode 100644 index 00000000..977f547e --- /dev/null +++ b/examples/example_sdl2_cpu/build_win32.bat @@ -0,0 +1,8 @@ +@REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler. +@set OUT_DIR=Debug +@set OUT_EXE=example_sdl2_cpu +@set INCLUDES=/I..\.. /I..\..\backends /I%SDL2_DIR%\include +@set SOURCES=main.cpp ..\..\backends\imgui_impl_sdl2.cpp ..\..\backends\imgui_impl_cpu.cpp ..\..\imgui*.cpp +@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib +mkdir %OUT_DIR% +cl /nologo /Zi /O2 /MD /utf-8 %INCLUDES% %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% /subsystem:console diff --git a/examples/example_sdl2_cpu/example_sdl2_cpu b/examples/example_sdl2_cpu/example_sdl2_cpu new file mode 100755 index 00000000..fd8fa889 Binary files /dev/null and b/examples/example_sdl2_cpu/example_sdl2_cpu differ diff --git a/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj b/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj new file mode 100644 index 00000000..e48281c0 --- /dev/null +++ b/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj @@ -0,0 +1,187 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458} + example_sdl2_cpu + 8.1 + example_sdl2_cpu + + + + Application + true + MultiByte + v140 + + + Application + true + MultiByte + v140 + + + Application + false + true + MultiByte + v140 + + + Application + false + true + MultiByte + v140 + + + + + + + + + + + + + + + + + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + $(IncludePath) + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + $(IncludePath) + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + $(IncludePath) + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + $(IncludePath) + + + + Level4 + Disabled + ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + true + %SDL2_DIR%\lib\x86;%(AdditionalLibraryDirectories) + SDL2.lib;SDL2main.lib;%(AdditionalDependencies) + Console + msvcrt.lib + + + + + Level4 + Disabled + ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + true + %SDL2_DIR%\lib\x64;%(AdditionalLibraryDirectories) + SDL2.lib;SDL2main.lib;%(AdditionalDependencies) + Console + msvcrt.lib + + + + + Level4 + MaxSpeed + true + true + ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + false + /utf-8 %(AdditionalOptions) + + + true + true + true + %SDL2_DIR%\lib\x86;%(AdditionalLibraryDirectories) + SDL2.lib;SDL2main.lib;%(AdditionalDependencies) + Console + + + + + + + Level4 + MaxSpeed + true + true + ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + false + /utf-8 %(AdditionalOptions) + + + true + true + true + %SDL2_DIR%\lib\x64;%(AdditionalLibraryDirectories) + SDL2.lib;SDL2main.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj.filters b/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj.filters new file mode 100644 index 00000000..8ab75a88 --- /dev/null +++ b/examples/example_sdl2_cpu/example_sdl2_cpu.vcxproj.filters @@ -0,0 +1,64 @@ + + + + + {20b90ce4-7fcb-4731-b9a0-075f875de82d} + + + {f18ab499-84e1-499f-8eff-9754361e0e52} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + imgui + + + imgui + + + imgui + + + sources + + + imgui + + + imgui + + + sources + + + sources + + + + + imgui + + + imgui + + + imgui + + + sources + + + sources + + + + + + imgui + + + imgui + + + diff --git a/examples/example_sdl2_cpu/main.cpp b/examples/example_sdl2_cpu/main.cpp new file mode 100644 index 00000000..0f94dac4 --- /dev/null +++ b/examples/example_sdl2_cpu/main.cpp @@ -0,0 +1,321 @@ +// main.cpp +// Example program using imgui_impl_cpu backend + +#include "imgui.h" +#include "imgui_impl_cpu.h" +#include "imgui_impl_sdl2.h" +#include +#include +#include +#include +#ifdef __EMSCRIPTEN__ +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + +struct ExampleTimingHistory +{ + double Samples[120]; + int Count; + int Offset; + + ExampleTimingHistory() : Count(0), Offset(0) { memset(Samples, 0, sizeof(Samples)); } + + void AddSample(double value_ms) + { + Samples[Offset] = value_ms; + Offset = (Offset + 1) % IM_ARRAYSIZE(Samples); + if (Count < IM_ARRAYSIZE(Samples)) + Count++; + } + + double GetAverageMs() const + { + if (Count == 0) + return 0.0; + double total = 0.0; + for (int i = 0; i < Count; i++) + total += Samples[i]; + return total / (double)Count; + } +}; + +struct ExampleFramebuffer +{ + ImVector Storage; + ImVector Palette; + ImGui_ImplCPU_PixelFormat Format; + ImGui_ImplCPU_Framebuffer CPU; + SDL_Surface* Surface; + Uint32 SDLFormat; + + ExampleFramebuffer() { memset((void*)this, 0, sizeof(*this)); } +}; + +static double ExampleGetTimeMs() +{ + typedef std::chrono::steady_clock Clock; + return std::chrono::duration(Clock::now().time_since_epoch()).count(); +} + +static void InitCPUFormatFromSDL(ImGui_ImplCPU_PixelFormat* out, ImVector* out_palette, const SDL_PixelFormat* format) +{ + memset(out, 0, sizeof(*out)); + out->BitsPerPixel = format->BitsPerPixel; + out->BytesPerPixel = format->BytesPerPixel; + out->Rmask = format->Rmask; + out->Gmask = format->Gmask; + out->Bmask = format->Bmask; + out->Amask = format->Amask; + out->Rshift = (ImU8)format->Rshift; + out->Gshift = (ImU8)format->Gshift; + out->Bshift = (ImU8)format->Bshift; + out->Ashift = (ImU8)format->Ashift; + out->Rloss = (ImU8)format->Rloss; + out->Gloss = (ImU8)format->Gloss; + out->Bloss = (ImU8)format->Bloss; + out->Aloss = (ImU8)format->Aloss; + out->Palette = nullptr; + out->PaletteSize = 0; + out->MapColorFn = nullptr; + + out_palette->clear(); + if (format->palette != nullptr && format->palette->colors != nullptr && format->palette->ncolors > 0) + { + out_palette->resize(format->palette->ncolors); + for (int i = 0; i < format->palette->ncolors; i++) + { + const SDL_Color& c = format->palette->colors[i]; + (*out_palette)[i] = IM_COL32(c.r, c.g, c.b, c.a); + } + out->Palette = out_palette->Data; + out->PaletteSize = out_palette->Size; + } +} + +static bool RecreateFramebuffer(ExampleFramebuffer* framebuffer, SDL_Surface* window_surface) +{ + if (framebuffer->Surface != nullptr) + { + SDL_FreeSurface(framebuffer->Surface); + framebuffer->Surface = nullptr; + } + + const int width = window_surface->w; + const int height = window_surface->h; + const int bytes_per_pixel = window_surface->format->BytesPerPixel; + if (width <= 0 || height <= 0 || bytes_per_pixel <= 0) + return false; + + InitCPUFormatFromSDL(&framebuffer->Format, &framebuffer->Palette, window_surface->format); + framebuffer->Storage.resize(width * height * bytes_per_pixel); + framebuffer->CPU.Pixels = framebuffer->Storage.Data; + framebuffer->CPU.Width = width; + framebuffer->CPU.Height = height; + framebuffer->CPU.Pitch = width * bytes_per_pixel; + framebuffer->CPU.Format = &framebuffer->Format; + framebuffer->SDLFormat = window_surface->format->format; + framebuffer->Surface = SDL_CreateRGBSurfaceWithFormatFrom(framebuffer->CPU.Pixels, width, height, window_surface->format->BitsPerPixel, framebuffer->CPU.Pitch, window_surface->format->format); + return framebuffer->Surface != nullptr; +} + +int main(int, char**) +{ + // Setup SDL + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) + { + printf("Error: %s\n", SDL_GetError()); + return -1; + } + + // From 2.0.18: Enable native IME. +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + // Create window + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_SHOWN); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+CPU Example", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); + if (!window) + { + printf("Error creating window: %s\n", SDL_GetError()); + return -1; + } + + SDL_Surface* window_surface = SDL_GetWindowSurface(window); + if (!window_surface) + { + printf("Error getting window surface: %s\n", SDL_GetError()); + return -1; + } + + int win_w = window_surface->w; + int win_h = window_surface->h; + ExampleFramebuffer framebuffer; + if (!RecreateFramebuffer(&framebuffer, window_surface)) + { + printf("Error creating framebuffer: %s\n", SDL_GetError()); + return -1; + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls +#ifdef __EMSCRIPTEN__ + io.IniFilename = nullptr; +#endif + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. + style.FontScaleDpi = main_scale; + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOther(window); + ImGui_ImplSDL2_SetGamepadMode(ImGui_ImplSDL2_GamepadMode_AutoFirst, nullptr, 0); + ImGui_ImplCPU_Init(); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + ExampleTimingHistory raster_times; + ExampleTimingHistory present_times; + + // Main loop + bool done = false; +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_BEGIN +#else + while (!done) +#endif + { + // Poll and handle events (inputs, window resize, etc.) + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) + done = true; + + if (event.type == SDL_WINDOWEVENT) + { + if (event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + done = true; + + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || event.window.event == SDL_WINDOWEVENT_RESIZED) + { + // Recreate framebuffer at new size + window_surface = SDL_GetWindowSurface(window); + int new_w = window_surface->w; + int new_h = window_surface->h; + if (new_w != win_w || new_h != win_h || window_surface->format->format != framebuffer.SDLFormat) + { + if (!RecreateFramebuffer(&framebuffer, window_surface)) + { + printf("Error creating framebuffer after resize: %s\n", SDL_GetError()); + return -1; + } + win_w = new_w; + win_h = new_h; + } + } + } + } + + // Skip rendering when minimized to avoid busy-looping + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + { + SDL_Delay(10); + continue; + } + + // Start the Dear ImGui frame + ImGui_ImplCPU_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + int logical_w = 0; + int logical_h = 0; + SDL_GetWindowSize(window, &logical_w, &logical_h); + + // 1. Show the big demo window + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. Mirror the SDL_Renderer example. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Raster %.3f ms | Present %.3f ms", raster_times.GetAverageMs(), present_times.GetAverageMs()); + ImGui::Text("Window %d x %d | Framebuffer %d x %d", logical_w, logical_h, framebuffer.CPU.Width, framebuffer.CPU.Height); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + + const double raster_start_ms = ExampleGetTimeMs(); + SDL_FillRect(framebuffer.Surface, nullptr, SDL_MapRGBA(framebuffer.Surface->format, + (Uint8)(clear_color.x * 255), + (Uint8)(clear_color.y * 255), + (Uint8)(clear_color.z * 255), + (Uint8)(clear_color.w * 255))); + + ImGui_ImplCPU_RenderDrawData(ImGui::GetDrawData(), &framebuffer.CPU); + raster_times.AddSample(ExampleGetTimeMs() - raster_start_ms); + + const double present_start_ms = ExampleGetTimeMs(); + SDL_BlitSurface(framebuffer.Surface, nullptr, window_surface, nullptr); + SDL_UpdateWindowSurface(window); + present_times.AddSample(ExampleGetTimeMs() - present_start_ms); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + ImGui_ImplCPU_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + if (framebuffer.Surface != nullptr) + SDL_FreeSurface(framebuffer.Surface); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} diff --git a/examples/example_sdl2_surface/main.cpp b/examples/example_sdl2_surface/main.cpp index eeb58de1..7063a429 100644 --- a/examples/example_sdl2_surface/main.cpp +++ b/examples/example_sdl2_surface/main.cpp @@ -5,11 +5,46 @@ #include "imgui_impl_sdl2.h" #include "imgui_impl_sdlsurface2.h" #include +#include #include +#include #ifdef __EMSCRIPTEN__ #include "../libs/emscripten/emscripten_mainloop_stub.h" #endif +struct ExampleTimingHistory +{ + double Samples[120]; + int Count; + int Offset; + + ExampleTimingHistory() : Count(0), Offset(0) { memset(Samples, 0, sizeof(Samples)); } + + void AddSample(double value_ms) + { + Samples[Offset] = value_ms; + Offset = (Offset + 1) % IM_ARRAYSIZE(Samples); + if (Count < IM_ARRAYSIZE(Samples)) + Count++; + } + + double GetAverageMs() const + { + if (Count == 0) + return 0.0; + double total = 0.0; + for (int i = 0; i < Count; i++) + total += Samples[i]; + return total / (double)Count; + } +}; + +static double ExampleGetTimeMs() +{ + typedef std::chrono::steady_clock Clock; + return std::chrono::duration(Clock::now().time_since_epoch()).count(); +} + int main(int, char**) { // Setup SDL @@ -79,6 +114,8 @@ int main(int, char**) bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + ExampleTimingHistory raster_times; + ExampleTimingHistory present_times; // Main loop bool done = false; @@ -133,6 +170,10 @@ int main(int, char**) ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); + int logical_w = 0; + int logical_h = 0; + SDL_GetWindowSize(window, &logical_w, &logical_h); + // 1. Show the big demo window if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); @@ -156,6 +197,8 @@ int main(int, char**) ImGui::SameLine(); ImGui::Text("counter = %d", counter); + ImGui::Text("Raster %.3f ms | Present %.3f ms", raster_times.GetAverageMs(), present_times.GetAverageMs()); + ImGui::Text("Window %d x %d | Framebuffer %d x %d", logical_w, logical_h, framebuffer->w, framebuffer->h); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); ImGui::End(); } @@ -173,6 +216,7 @@ int main(int, char**) // Rendering ImGui::Render(); + const double raster_start_ms = ExampleGetTimeMs(); SDL_FillRect(framebuffer, nullptr, SDL_MapRGBA(framebuffer->format, (Uint8)(clear_color.x * 255), (Uint8)(clear_color.y * 255), @@ -180,9 +224,12 @@ int main(int, char**) (Uint8)(clear_color.w * 255))); ImGui_ImplSDLSurface2_RenderDrawData(ImGui::GetDrawData()); + raster_times.AddSample(ExampleGetTimeMs() - raster_start_ms); + const double present_start_ms = ExampleGetTimeMs(); SDL_BlitSurface(framebuffer, nullptr, window_surface, nullptr); SDL_UpdateWindowSurface(window); + present_times.AddSample(ExampleGetTimeMs() - present_start_ms); } #ifdef __EMSCRIPTEN__ EMSCRIPTEN_MAINLOOP_END; diff --git a/examples/imgui_examples.sln b/examples/imgui_examples.sln index 3def734f..e7a7178c 100644 --- a/examples/imgui_examples.sln +++ b/examples/imgui_examples.sln @@ -31,6 +31,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_sdl2_sdlrenderer2", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_sdl2_surface", "example_sdl2_surface\example_sdl2_surface.vcxproj", "{47525E56-7D05-474E-A455-64C5BBFFC029}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_sdl2_cpu", "example_sdl2_cpu\example_sdl2_cpu.vcxproj", "{1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_sdl3_opengl3", "example_sdl3_opengl3\example_sdl3_opengl3.vcxproj", "{84AAA301-84FE-428B-9E3E-817BC8123C0C}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_sdl3_sdlrenderer3", "example_sdl3_sdlrenderer3\example_sdl3_sdlrenderer3.vcxproj", "{C0290D21-3AD2-4A35-ABBC-A2F5F48326DA}" @@ -167,6 +169,14 @@ Global {47525E56-7D05-474E-A455-64C5BBFFC029}.Release|Win32.Build.0 = Release|Win32 {47525E56-7D05-474E-A455-64C5BBFFC029}.Release|x64.ActiveCfg = Release|x64 {47525E56-7D05-474E-A455-64C5BBFFC029}.Release|x64.Build.0 = Release|x64 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Debug|Win32.ActiveCfg = Debug|Win32 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Debug|Win32.Build.0 = Debug|Win32 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Debug|x64.ActiveCfg = Debug|x64 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Debug|x64.Build.0 = Debug|x64 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Release|Win32.ActiveCfg = Release|Win32 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Release|Win32.Build.0 = Release|Win32 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Release|x64.ActiveCfg = Release|x64 + {1E9DB182-9BF1-4C7D-8930-85ACAEAC2458}.Release|x64.Build.0 = Release|x64 {84AAA301-84FE-428B-9E3E-817BC8123C0C}.Debug|Win32.ActiveCfg = Debug|Win32 {84AAA301-84FE-428B-9E3E-817BC8123C0C}.Debug|Win32.Build.0 = Debug|Win32 {84AAA301-84FE-428B-9E3E-817BC8123C0C}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/misc/README.txt b/misc/README.txt index 58c00ee8..3baa341f 100644 --- a/misc/README.txt +++ b/misc/README.txt @@ -2,6 +2,7 @@ misc/cpp/ InputText() wrappers for C++ standard library (STL) type: std::string. This is also an example of how you may wrap your own similar types. + Includes imgui_cpu_validate.cpp, an offscreen validation utility for the imgui_impl_cpu backend. misc/debuggers/ Helper files for popular debuggers (Visual Studio, GDB, LLDB). diff --git a/misc/cpp/imgui_cpu_validate b/misc/cpp/imgui_cpu_validate new file mode 100755 index 00000000..9d1548b0 Binary files /dev/null and b/misc/cpp/imgui_cpu_validate differ diff --git a/misc/cpp/imgui_cpu_validate.cpp b/misc/cpp/imgui_cpu_validate.cpp new file mode 100644 index 00000000..788968dd --- /dev/null +++ b/misc/cpp/imgui_cpu_validate.cpp @@ -0,0 +1,385 @@ +// dear imgui: imgui_impl_cpu format validation utility +// Build manually with imgui core sources + backends/imgui_impl_cpu.cpp. + +#include "imgui.h" +#include "imgui_impl_cpu.h" +#include +#include +#include + +struct ValidationBuffer +{ + ImVector Storage; + ImVector Palette; + ImGui_ImplCPU_PixelFormat Format; + ImGui_ImplCPU_Framebuffer Framebuffer; + + ValidationBuffer() { memset((void*)this, 0, sizeof(*this)); } +}; + +static inline unsigned char Validation_ExpandBits(ImU32 value, int bits) +{ + if (bits <= 0) + return 0; + if (bits >= 8) + return (unsigned char)value; + ImU32 max_value = (1u << bits) - 1u; + return (unsigned char)((value * 255u + (max_value >> 1)) / max_value); +} + +static inline ImU32 Validation_CompressBits(unsigned char value, int bits) +{ + if (bits <= 0) + return 0; + if (bits >= 8) + return value; + ImU32 max_value = (1u << bits) - 1u; + return (ImU32)((value * max_value + 127u) / 255u); +} + +static inline void Validation_UnpackCanonicalRGBA(ImU32 pixel, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + *r = (unsigned char)((pixel >> IM_COL32_R_SHIFT) & 0xFF); + *g = (unsigned char)((pixel >> IM_COL32_G_SHIFT) & 0xFF); + *b = (unsigned char)((pixel >> IM_COL32_B_SHIFT) & 0xFF); + *a = (unsigned char)((pixel >> IM_COL32_A_SHIFT) & 0xFF); +} + +static inline ImU32 Validation_ReadRawPixel(const ValidationBuffer& buffer, int x, int y) +{ + const unsigned char* p = buffer.Framebuffer.Pixels + (size_t)y * (size_t)buffer.Framebuffer.Pitch + (size_t)x * (size_t)buffer.Format.BytesPerPixel; + switch (buffer.Format.BytesPerPixel) + { + case 1: + return *p; + case 2: + { + ImU16 pixel = 0; + memcpy(&pixel, p, sizeof(pixel)); + return pixel; + } + case 3: +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return (ImU32)((p[0] << 16) | (p[1] << 8) | p[2]); +#else + return (ImU32)(p[0] | (p[1] << 8) | (p[2] << 16)); +#endif + case 4: + { + ImU32 pixel = 0; + memcpy(&pixel, p, sizeof(pixel)); + return pixel; + } + default: + return 0; + } +} + +static inline void Validation_WriteRawPixel(ValidationBuffer* buffer, int x, int y, ImU32 pixel) +{ + unsigned char* p = buffer->Framebuffer.Pixels + (size_t)y * (size_t)buffer->Framebuffer.Pitch + (size_t)x * (size_t)buffer->Format.BytesPerPixel; + switch (buffer->Format.BytesPerPixel) + { + case 1: + *p = (unsigned char)pixel; + return; + case 2: + { + ImU16 v = (ImU16)pixel; + memcpy(p, &v, sizeof(v)); + return; + } + case 3: +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + p[0] = (unsigned char)((pixel >> 16) & 0xFF); + p[1] = (unsigned char)((pixel >> 8) & 0xFF); + p[2] = (unsigned char)(pixel & 0xFF); +#else + p[0] = (unsigned char)(pixel & 0xFF); + p[1] = (unsigned char)((pixel >> 8) & 0xFF); + p[2] = (unsigned char)((pixel >> 16) & 0xFF); +#endif + return; + case 4: + memcpy(p, &pixel, sizeof(pixel)); + return; + default: + return; + } +} + +static ImU32 Validation_MapNearestPaletteColor(const ImGui_ImplCPU_PixelFormat* format, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned int best_distance = 0xFFFFFFFFu; + int best_index = 0; + for (int i = 0; i < format->PaletteSize; i++) + { + unsigned char pr, pg, pb, pa; + Validation_UnpackCanonicalRGBA(format->Palette[i], &pr, &pg, &pb, &pa); + int dr = (int)pr - (int)r; + int dg = (int)pg - (int)g; + int db = (int)pb - (int)b; + int da = (int)pa - (int)a; + unsigned int distance = (unsigned int)(dr * dr + dg * dg + db * db + da * da); + if (distance < best_distance) + { + best_distance = distance; + best_index = i; + if (distance == 0) + break; + } + } + return (ImU32)best_index; +} + +static ImU32 Validation_PackPixel(const ValidationBuffer& buffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if (buffer.Format.Palette != nullptr && buffer.Format.PaletteSize > 0) + { + if (buffer.Format.MapColorFn != nullptr) + return buffer.Format.MapColorFn(&buffer.Format, r, g, b, a); + return Validation_MapNearestPaletteColor(&buffer.Format, r, g, b, a); + } + + ImU32 pixel = 0; + if (buffer.Format.Rmask != 0) + pixel |= (Validation_CompressBits(r, 8 - buffer.Format.Rloss) << buffer.Format.Rshift) & buffer.Format.Rmask; + if (buffer.Format.Gmask != 0) + pixel |= (Validation_CompressBits(g, 8 - buffer.Format.Gloss) << buffer.Format.Gshift) & buffer.Format.Gmask; + if (buffer.Format.Bmask != 0) + pixel |= (Validation_CompressBits(b, 8 - buffer.Format.Bloss) << buffer.Format.Bshift) & buffer.Format.Bmask; + if (buffer.Format.Amask != 0) + pixel |= (Validation_CompressBits(a, 8 - buffer.Format.Aloss) << buffer.Format.Ashift) & buffer.Format.Amask; + return pixel; +} + +static void Validation_DecodePixel(const ValidationBuffer& buffer, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) +{ + ImU32 pixel = Validation_ReadRawPixel(buffer, x, y); + if (buffer.Format.Palette != nullptr && buffer.Format.PaletteSize > 0) + { + if (pixel >= (ImU32)buffer.Format.PaletteSize) + { + *r = *g = *b = *a = 0; + return; + } + Validation_UnpackCanonicalRGBA(buffer.Format.Palette[pixel], r, g, b, a); + return; + } + + *r = (buffer.Format.Rmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Rmask) >> buffer.Format.Rshift, 8 - buffer.Format.Rloss) : 0; + *g = (buffer.Format.Gmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Gmask) >> buffer.Format.Gshift, 8 - buffer.Format.Gloss) : 0; + *b = (buffer.Format.Bmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Bmask) >> buffer.Format.Bshift, 8 - buffer.Format.Bloss) : 0; + *a = (buffer.Format.Amask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Amask) >> buffer.Format.Ashift, 8 - buffer.Format.Aloss) : 255; +} + +static bool Validation_InitBuffer(ValidationBuffer* buffer, int width, int height, const ImGui_ImplCPU_PixelFormat* format) +{ + buffer->Format = *format; + buffer->Framebuffer.Width = width; + buffer->Framebuffer.Height = height; + buffer->Framebuffer.Pitch = width * buffer->Format.BytesPerPixel; + buffer->Storage.resize(height * buffer->Framebuffer.Pitch); + buffer->Framebuffer.Pixels = buffer->Storage.Data; + buffer->Framebuffer.Format = &buffer->Format; + return buffer->Framebuffer.Pixels != nullptr; +} + +static bool Validation_InitIndexedBuffer(ValidationBuffer* buffer, int width, int height) +{ + buffer->Palette.clear(); + buffer->Palette.push_back(IM_COL32(114, 140, 153, 255)); + buffer->Palette.push_back(IM_COL32(255, 0, 0, 255)); + buffer->Palette.push_back(IM_COL32(0, 255, 0, 255)); + buffer->Palette.push_back(IM_COL32(0, 0, 255, 255)); + buffer->Palette.push_back(IM_COL32(255, 255, 255, 255)); + buffer->Palette.push_back(IM_COL32(255, 255, 0, 255)); + + memset(&buffer->Format, 0, sizeof(buffer->Format)); + buffer->Format.BitsPerPixel = 8; + buffer->Format.BytesPerPixel = 1; + buffer->Format.Palette = buffer->Palette.Data; + buffer->Format.PaletteSize = buffer->Palette.Size; + buffer->Format.MapColorFn = Validation_MapNearestPaletteColor; + return Validation_InitBuffer(buffer, width, height, &buffer->Format); +} + +static void Validation_ClearBuffer(ValidationBuffer* buffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + ImU32 pixel = Validation_PackPixel(*buffer, r, g, b, a); + for (int y = 0; y < buffer->Framebuffer.Height; y++) + for (int x = 0; x < buffer->Framebuffer.Width; x++) + Validation_WriteRawPixel(buffer, x, y, pixel); +} + +static bool Validation_CompareBuffers(const char* name, const ValidationBuffer& reference, const ValidationBuffer& candidate, int tolerance_rgb, int tolerance_alpha, bool ignore_alpha) +{ + for (int y = 0; y < reference.Framebuffer.Height; y++) + for (int x = 0; x < reference.Framebuffer.Width; x++) + { + unsigned char rr, rg, rb, ra; + unsigned char cr, cg, cb, ca; + Validation_DecodePixel(reference, x, y, &rr, &rg, &rb, &ra); + Validation_DecodePixel(candidate, x, y, &cr, &cg, &cb, &ca); + + if (abs((int)rr - (int)cr) > tolerance_rgb + || abs((int)rg - (int)cg) > tolerance_rgb + || abs((int)rb - (int)cb) > tolerance_rgb + || (!ignore_alpha && abs((int)ra - (int)ca) > tolerance_alpha)) + { + printf("Validation failed for %s at (%d, %d): ref=(%u,%u,%u,%u) got=(%u,%u,%u,%u)\n", + name, x, y, rr, rg, rb, ra, cr, cg, cb, ca); + return false; + } + } + return true; +} + +static void Validation_FillBGRAImage(ImVector* storage, ImGui_ImplCPU_Texture* texture) +{ + const int width = 48; + const int height = 32; + storage->resize(width * height * 4); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + unsigned char r = (unsigned char)(32 + x * 4); + unsigned char g = (unsigned char)(32 + y * 6); + unsigned char b = (unsigned char)(255 - x * 4); + int offset = (y * width + x) * 4; + (*storage)[offset + 0] = b; + (*storage)[offset + 1] = g; + (*storage)[offset + 2] = r; + (*storage)[offset + 3] = 255; + } + + texture->Pixels = storage->Data; + texture->Width = width; + texture->Height = height; + texture->Pitch = width * 4; + texture->Format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGRA8888); +} + +static void Validation_BuildMainScene(const ImGui_ImplCPU_Texture* image_texture) +{ + ImDrawList* bg = ImGui::GetBackgroundDrawList(); + bg->AddRectFilled(ImVec2(8.0f, 8.0f), ImVec2(40.0f, 40.0f), IM_COL32(255, 0, 0, 255)); + bg->AddRectFilled(ImVec2(472.0f, 280.0f), ImVec2(504.0f, 312.0f), IM_COL32(0, 255, 0, 255)); + + ImGui::SetNextWindowPos(ImVec2(32.0f, 24.0f), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(320.0f, 180.0f), ImGuiCond_Always); + ImGui::Begin("Validation", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGui::Text("CPU format validation"); + ImGui::Text("Top-left marker is red. Bottom-right is green."); + ImGui::ColorButton("##color", ImVec4(0.2f, 0.6f, 0.9f, 1.0f), 0, ImVec2(48.0f, 24.0f)); + ImGui::SameLine(); + ImGui::Image((ImTextureID)(intptr_t)image_texture, ImVec2((float)image_texture->Width, (float)image_texture->Height)); + ImGui::ProgressBar(0.65f, ImVec2(240.0f, 0.0f)); + ImGui::End(); +} + +static void Validation_BuildPaletteScene() +{ + ImDrawList* bg = ImGui::GetBackgroundDrawList(); + bg->AddRectFilled(ImVec2(8.0f, 8.0f), ImVec2(40.0f, 40.0f), IM_COL32(255, 0, 0, 255)); + bg->AddRectFilled(ImVec2(472.0f, 280.0f), ImVec2(504.0f, 312.0f), IM_COL32(0, 255, 0, 255)); + bg->AddRectFilled(ImVec2(160.0f, 32.0f), ImVec2(320.0f, 96.0f), IM_COL32(0, 0, 255, 255)); + bg->AddRectFilled(ImVec2(56.0f, 232.0f), ImVec2(168.0f, 280.0f), IM_COL32(255, 255, 0, 255)); + bg->AddRectFilled(ImVec2(360.0f, 64.0f), ImVec2(456.0f, 112.0f), IM_COL32(255, 255, 255, 255)); +} + +static bool Validation_RenderInto(ValidationBuffer* buffer, ImDrawData* draw_data) +{ + Validation_ClearBuffer(buffer, 114, 140, 153, 255); + ImGui_ImplCPU_RenderDrawData(draw_data, &buffer->Framebuffer); + return true; +} + +int main(int, char**) +{ + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(512.0f, 320.0f); + io.DeltaTime = 1.0f / 60.0f; + io.Fonts->TexDesiredFormat = ImTextureFormat_Alpha8; + + ImGui::StyleColorsDark(); + ImGui_ImplCPU_Init(); + + ImVector image_storage; + ImGui_ImplCPU_Texture image_texture; + memset(&image_texture, 0, sizeof(image_texture)); + Validation_FillBGRAImage(&image_storage, &image_texture); + + ValidationBuffer reference; + Validation_InitBuffer(&reference, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888)); + + const ImGui_ImplCPU_BuiltinFormat builtin_formats[] = + { + ImGui_ImplCPU_BuiltinFormat_RGBA8888, + ImGui_ImplCPU_BuiltinFormat_BGRA8888, + ImGui_ImplCPU_BuiltinFormat_ARGB8888, + ImGui_ImplCPU_BuiltinFormat_ABGR8888, + ImGui_ImplCPU_BuiltinFormat_RGB565, + ImGui_ImplCPU_BuiltinFormat_ARGB1555, + ImGui_ImplCPU_BuiltinFormat_RGBA4444, + ImGui_ImplCPU_BuiltinFormat_RGB888, + ImGui_ImplCPU_BuiltinFormat_BGR888 + }; + const char* builtin_names[] = + { + "RGBA8888", + "BGRA8888", + "ARGB8888", + "ABGR8888", + "RGB565", + "ARGB1555", + "RGBA4444", + "RGB888", + "BGR888" + }; + const int builtin_tolerance[] = + { + 0, 0, 0, 0, 8, 8, 17, 0, 0 + }; + const bool builtin_ignore_alpha[] = + { + false, false, false, false, true, false, false, true, true + }; + + ImGui_ImplCPU_NewFrame(); + ImGui::NewFrame(); + Validation_BuildMainScene(&image_texture); + ImGui::Render(); + if (!Validation_RenderInto(&reference, ImGui::GetDrawData())) + return 1; + + for (int i = 0; i < IM_ARRAYSIZE(builtin_formats); i++) + { + ValidationBuffer candidate; + Validation_InitBuffer(&candidate, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(builtin_formats[i])); + Validation_RenderInto(&candidate, ImGui::GetDrawData()); + if (!Validation_CompareBuffers(builtin_names[i], reference, candidate, builtin_tolerance[i], builtin_tolerance[i], builtin_ignore_alpha[i])) + return 1; + } + + ValidationBuffer palette_reference; + Validation_InitBuffer(&palette_reference, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888)); + ValidationBuffer palette_candidate; + Validation_InitIndexedBuffer(&palette_candidate, 512, 320); + + ImGui_ImplCPU_NewFrame(); + ImGui::NewFrame(); + Validation_BuildPaletteScene(); + ImGui::Render(); + Validation_RenderInto(&palette_reference, ImGui::GetDrawData()); + Validation_RenderInto(&palette_candidate, ImGui::GetDrawData()); + if (!Validation_CompareBuffers("Indexed8", palette_reference, palette_candidate, 0, 0, false)) + return 1; + + ImGui_ImplCPU_Shutdown(); + ImGui::DestroyContext(); + printf("imgui_impl_cpu validation passed.\n"); + return 0; +}