├── .gitignore ├── .gitmodules ├── README.md ├── build.sh ├── build_and_run.sh ├── configure.sh ├── screenshots ├── gl_reference.png └── imgui_sw.png └── src ├── imgui_sw.cpp ├── imgui_sw.hpp ├── libs.cpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | *.bin 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/emilib"] 2 | path = third_party/emilib 3 | url = git@github.com:emilk/emilib.git 4 | [submodule "third_party/imgui"] 5 | path = third_party/imgui 6 | url = git@github.com:ocornut/imgui.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dear ImGui software renderer 2 | This is a software renderer for [Dear ImGui](https://github.com/ocornut/imgui). 3 | I built it not out of a specific need, but because it was fun. 4 | The goal was to get something accurate and decently fast in not too many lines of code. 5 | It renders a complex GUI in 1-10 milliseconds on a modern laptop. 6 | 7 | ## What it is: 8 | As the name implies, this is a software renderer for ImGui. It does not handle any windows or input. In the supplied example I use [SDL2](www.libsdl.org) for that. 9 | 10 | ## How to use it 11 | Just copy `imgui_sw.hpp` and `imgui_sw.cpp`. There are no other dependencies beside Dear ImGui. Requires C++11. 12 | 13 | ## How to test it 14 | ``` 15 | git clone https://github.com/emilk/imgui_software_renderer.git 16 | cd imgui_software_renderer 17 | git submodule update --init --recursive 18 | ./build_and_run.sh 19 | ``` 20 | 21 | For the example to work you will need to have SDL2 on your system. 22 | 23 | ## Example: 24 | This renders in 7 ms on my MacBook Pro: 25 | 26 | ![Software rendered](screenshots/imgui_sw.png) 27 | 28 | ## Alternatives 29 | There is another software rasterizer for ImGui (which I did not know about when I wrote mine) at https://github.com/sronsse/imgui/tree/sw_rasterizer_example/examples/sdl_sw_example. 30 | I have not compared the two (yet). 31 | 32 | ## Future work: 33 | * We do not yet support painting with any other texture than the default font texture. 34 | * Optimize rendering of gradient rectangles (common for color pickers) 35 | * Compare my software renderer to [the one by](https://github.com/sronsse/imgui/tree/sw_rasterizer_example/examples/sdl_sw_example) @sronsse 36 | 37 | ## License: 38 | This software is dual-licensed to the public domain and under the following 39 | license: you are granted a perpetual, irrevocable license to copy, modify, 40 | publish, and distribute this file as you see fit. 41 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ ! -z ${1+x} ] && [ $1 == "clean" ]; then 5 | rm -rf build/ 6 | rm -f *.bin 7 | exit 0 8 | fi 9 | 10 | FOLDER_NAME=${PWD##*/} 11 | BINARY_NAME="$FOLDER_NAME.bin" 12 | 13 | mkdir -p build 14 | 15 | OPENGL_REFERENCE_RENDERER=false # Set to true to try the OpenGL reference renderer 16 | 17 | CXX="ccache g++" 18 | 19 | # ----------------------------------------------------------------------------- 20 | # Flags for compiler and linker: 21 | 22 | if $OPENGL_REFERENCE_RENDERER; then 23 | CPPFLAGS="--std=c++14" 24 | else 25 | CPPFLAGS="--std=c++11" 26 | fi 27 | 28 | # CPPFLAGS="$CPPFLAGS -Werror -Wall -Wpedantic -Wextra -Weverything -Wunreachable-code" # These can all be turned on, but are off just to make the example compile everywhere. 29 | 30 | CPPFLAGS="$CPPFLAGS -Wno-double-promotion" # Implicitly converting a float to a double is fine 31 | CPPFLAGS="$CPPFLAGS -Wno-float-equal" # Comparing floating point numbers is fine if you know what you're doing 32 | CPPFLAGS="$CPPFLAGS -Wno-shorten-64-to-32" 33 | CPPFLAGS="$CPPFLAGS -Wno-sign-compare" 34 | CPPFLAGS="$CPPFLAGS -Wno-sign-conversion" 35 | 36 | # Turn off some warning that -Weverything turns on: 37 | CPPFLAGS="$CPPFLAGS -Wno-c++98-compat" 38 | CPPFLAGS="$CPPFLAGS -Wno-c++98-compat-pedantic" 39 | # CPPFLAGS="$CPPFLAGS -Wno-covered-switch-default" 40 | CPPFLAGS="$CPPFLAGS -Wno-disabled-macro-expansion" 41 | CPPFLAGS="$CPPFLAGS -Wno-documentation" 42 | CPPFLAGS="$CPPFLAGS -Wno-documentation-unknown-command" 43 | CPPFLAGS="$CPPFLAGS -Wno-exit-time-destructors" 44 | # CPPFLAGS="$CPPFLAGS -Wno-global-constructors" 45 | # CPPFLAGS="$CPPFLAGS -Wno-missing-noreturn" 46 | CPPFLAGS="$CPPFLAGS -Wno-missing-prototypes" 47 | CPPFLAGS="$CPPFLAGS -Wno-padded" 48 | CPPFLAGS="$CPPFLAGS -Wno-reserved-id-macro" 49 | CPPFLAGS="$CPPFLAGS -Wno-unused-macros" 50 | 51 | # CPPFLAGS="$CPPFLAGS -Wno-unused-function" # Useful during development (TEMPORARY) 52 | # CPPFLAGS="$CPPFLAGS -Wno-unused-parameter" # Useful during development (TEMPORARY) 53 | # CPPFLAGS="$CPPFLAGS -Wno-unused-variable" # Useful during development (TEMPORARY) 54 | 55 | # Check if clang:ret=0 56 | ret=0 57 | $CXX --version 2>/dev/null | grep clang > /dev/null || ret=$? 58 | if [ $ret == 0 ]; then 59 | # Clang: 60 | CPPFLAGS="$CPPFLAGS -Wno-gnu-zero-variadic-macro-arguments" # Loguru 61 | CPPFLAGS="$CPPFLAGS -Wno-#warnings" # emilib 62 | else 63 | # GCC: 64 | CPPFLAGS="$CPPFLAGS -Wno-maybe-uninitialized" # stb 65 | fi 66 | 67 | DEBUG_SYMBOLS="-g -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-optimize-sibling-calls" 68 | 69 | # CPPFLAGS="$CPPFLAGS $DEBUG_SYMBOLS -fsanitize=address" # Debug build with fsanatize 70 | # CPPFLAGS="$CPPFLAGS $DEBUG_SYMBOLS" # Debug build 71 | # CPPFLAGS="$CPPFLAGS -O2 -DNDEBUG $DEBUG_SYMBOLS" # Release build with debug symbols 72 | CPPFLAGS="$CPPFLAGS -O2 -DNDEBUG" # Release build 73 | 74 | CPPFLAGS="$CPPFLAGS -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS" 75 | 76 | # ----------------------------------------------------------------------------- 77 | # Flags for compiler: 78 | 79 | COMPILE_FLAGS="$CPPFLAGS" 80 | COMPILE_FLAGS="$COMPILE_FLAGS -I ." 81 | COMPILE_FLAGS="$COMPILE_FLAGS -isystem third_party" 82 | COMPILE_FLAGS="$COMPILE_FLAGS -isystem third_party/emilib" 83 | 84 | # ----------------------------------------------------------------------------- 85 | # Custom compile-time flags: 86 | 87 | COMPILE_FLAGS="$COMPILE_FLAGS -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1" 88 | COMPILE_FLAGS="$COMPILE_FLAGS -DLOGURU_REDEFINE_ASSERT=1" 89 | 90 | # ----------------------------------------------------------------------------- 91 | # Libraries to link with: 92 | 93 | LDLIBS="-lstdc++ -lpthread -ldl" 94 | LDLIBS="$LDLIBS -lSDL2" 95 | 96 | if $OPENGL_REFERENCE_RENDERER; then 97 | COMPILE_FLAGS="$COMPILE_FLAGS -DOPENGL_REFERENCE_RENDERER" 98 | LDLIBS="$LDLIBS -lGLEW" 99 | if [ "$(uname)" == "Darwin" ]; then 100 | LDLIBS="$LDLIBS -framework OpenGL" 101 | else 102 | LDLIBS="$LDLIBS -lGL" 103 | fi 104 | fi 105 | 106 | # ----------------------------------------------------------------------------- 107 | 108 | echo "Compiling..." 109 | OBJECTS="" 110 | for source_path in src/*.cpp; do 111 | rel_source_path=${source_path#src/} # Remove src/ path prefix 112 | obj_path="build/${rel_source_path}.o" 113 | OBJECTS="$OBJECTS $obj_path" 114 | rm -f $obj_path 115 | $CXX $COMPILE_FLAGS -c $source_path -o $obj_path & 116 | done 117 | 118 | wait 119 | 120 | echo >&2 "Linking..." 121 | $CXX $CPPFLAGS $OBJECTS $LDLIBS -o "$BINARY_NAME" 122 | 123 | echo >&2 "Generating .dSYM..." 124 | dsymutil "$BINARY_NAME" -o "$BINARY_NAME.dSYM" 125 | 126 | echo >&2 "Build done." 127 | -------------------------------------------------------------------------------- /build_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | ./build.sh 4 | FOLDER_NAME=${PWD##*/} 5 | ./"$FOLDER_NAME.bin" $@ 6 | -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This scripts sets up a new project ready to build and run. 3 | set -eu 4 | 5 | if [ ! -d ".git" ]; then 6 | git init . 7 | echo "/build/*" >> .gitignore 8 | echo "*.bin" >> .gitignore 9 | fi 10 | 11 | if [ ! -d "third_party" ]; then 12 | mkdir -p "third_party" 13 | pushd "third_party" 14 | 15 | # git submodule add https://github.com/cbeck88/visit_struct.git 16 | # git submodule add https://github.com/emilk/emath.git 17 | git submodule add https://github.com/emilk/emilib.git 18 | # git submodule add https://github.com/nothings/stb.git 19 | git submodule add https://github.com/ocornut/imgui.git 20 | 21 | pushd imgui 22 | git checkout tags/v1.60 23 | popd 24 | 25 | popd 26 | fi 27 | 28 | git submodule update --init --recursive 29 | 30 | if [ ! -d "src" ]; then 31 | mkdir -p "src" 32 | touch "src/libs.cpp" # TODO: get from some gist 33 | touch "src/main.cpp" # TODO: get from some gist 34 | fi 35 | 36 | # TODO: get build.sh from some gist 37 | -------------------------------------------------------------------------------- /screenshots/gl_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JesusKrists/imgui_software_renderer/4f3c133df702b68c61986839870d6a07f185ee6f/screenshots/gl_reference.png -------------------------------------------------------------------------------- /screenshots/imgui_sw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JesusKrists/imgui_software_renderer/4f3c133df702b68c61986839870d6a07f185ee6f/screenshots/imgui_sw.png -------------------------------------------------------------------------------- /src/imgui_sw.cpp: -------------------------------------------------------------------------------- 1 | // By Emil Ernerfeldt 2018 2 | // LICENSE: 3 | // This software is dual-licensed to the public domain and under the following 4 | // license: you are granted a perpetual, irrevocable license to copy, modify, 5 | // publish, and distribute this file as you see fit. 6 | #include "imgui_sw.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace imgui_sw { 17 | namespace { 18 | 19 | const Texture *fontTexture = nullptr; 20 | 21 | struct Stats 22 | { 23 | int uniform_triangle_pixels = 0; 24 | int textured_triangle_pixels = 0; 25 | int gradient_triangle_pixels = 0; 26 | int font_pixels = 0; 27 | double uniform_rectangle_pixels = 0; 28 | double textured_rectangle_pixels = 0; 29 | double gradient_rectangle_pixels = 0; 30 | double gradient_textured_rectangle_pixels = 0; 31 | }; 32 | struct PaintTarget 33 | { 34 | uint32_t *pixels; 35 | int width; 36 | int height; 37 | ImVec2 scale;// Multiply ImGui (point) coordinates with this to get pixel coordinates. 38 | ImVec2 DisplayPos; 39 | }; 40 | 41 | // ---------------------------------------------------------------------------- 42 | 43 | #pragma pack(push, 1) 44 | struct ColorInt 45 | { 46 | uint8_t r, g, b, a = 0; 47 | ColorInt(const std::string &) {} 48 | 49 | 50 | ColorInt &operator*=(const ColorInt &other) 51 | { 52 | r = r * other.r / 255; 53 | g = g * other.g / 255; 54 | b = b * other.b / 255; 55 | a = a * other.a / 255; 56 | return *this; 57 | } 58 | }; 59 | #pragma pack(pop) 60 | 61 | uint32_t blend(const ColorInt &target, const ColorInt &source) 62 | { 63 | if (source.a >= 255) return *reinterpret_cast(&source); 64 | return (target.a << 24u) | (((source.b * source.a + target.b * (255 - source.a)) / 255) << 16u) 65 | | (((source.g * source.a + target.g * (255 - source.a)) / 255) << 8u) 66 | | ((source.r * source.a + target.r * (255 - source.a)) / 255); 67 | } 68 | 69 | // ---------------------------------------------------------------------------- 70 | // Used for interpolating vertex attributes (color and texture coordinates) in a triangle. 71 | 72 | struct Barycentric 73 | { 74 | float w0, w1, w2; 75 | }; 76 | 77 | Barycentric operator*(const float f, const Barycentric &va) { return { f * va.w0, f * va.w1, f * va.w2 }; } 78 | 79 | void operator+=(Barycentric &a, const Barycentric &b) 80 | { 81 | a.w0 += b.w0; 82 | a.w1 += b.w1; 83 | a.w2 += b.w2; 84 | } 85 | 86 | Barycentric operator+(const Barycentric &a, const Barycentric &b) 87 | { 88 | return Barycentric{ a.w0 + b.w0, a.w1 + b.w1, a.w2 + b.w2 }; 89 | } 90 | 91 | // ---------------------------------------------------------------------------- 92 | // Useful operators on ImGui vectors: 93 | 94 | ImVec2 operator*(const float f, const ImVec2 &v) { return ImVec2{ f * v.x, f * v.y }; } 95 | 96 | ImVec2 operator+(const ImVec2 &a, const ImVec2 &b) { return ImVec2{ a.x + b.x, a.y + b.y }; } 97 | 98 | ImVec2 operator-(const ImVec2 &a, const ImVec2 &b) { return ImVec2{ a.x - b.x, a.y - b.y }; } 99 | 100 | bool operator!=(const ImVec2 &a, const ImVec2 &b) { return a.x != b.x || a.y != b.y; } 101 | 102 | ImVec4 operator*(const float f, const ImVec4 &v) { return ImVec4{ f * v.x, f * v.y, f * v.z, f * v.w }; } 103 | 104 | ImVec4 operator+(const ImVec4 &a, const ImVec4 &b) { return ImVec4{ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; } 105 | 106 | // ---------------------------------------------------------------------------- 107 | // Copies of functions in ImGui, inlined for speed: 108 | 109 | ImVec4 color_convert_u32_to_float4(ImU32 in) 110 | { 111 | const float s = 1.0f / 255.0f; 112 | return ImVec4(((in >> IM_COL32_R_SHIFT) & 0xFF) * s, 113 | ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, 114 | ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, 115 | ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); 116 | } 117 | 118 | ImU32 color_convert_float4_to_u32(const ImVec4 &in) 119 | { 120 | ImU32 out; 121 | out = uint32_t(in.x * 255.0f + 0.5f) << IM_COL32_R_SHIFT; 122 | out |= uint32_t(in.y * 255.0f + 0.5f) << IM_COL32_G_SHIFT; 123 | out |= uint32_t(in.z * 255.0f + 0.5f) << IM_COL32_B_SHIFT; 124 | out |= uint32_t(in.w * 255.0f + 0.5f) << IM_COL32_A_SHIFT; 125 | return out; 126 | } 127 | 128 | // ---------------------------------------------------------------------------- 129 | // For fast and subpixel-perfect triangle rendering we used fixed point arithmetic. 130 | // To keep the code simple we use 64 bits to avoid overflows. 131 | 132 | using Int = int64_t; 133 | const Int kFixedBias = 256; 134 | 135 | struct Point 136 | { 137 | Int x, y; 138 | }; 139 | 140 | Int orient2d(const Point &a, const Point &b, const Point &c) 141 | { 142 | return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); 143 | } 144 | 145 | Int as_int(float v) { return static_cast(std::floor(v * kFixedBias)); } 146 | 147 | Point as_point(ImVec2 v) { return Point{ as_int(v.x), as_int(v.y) }; } 148 | 149 | // ---------------------------------------------------------------------------- 150 | 151 | float min3(float a, float b, float c) 152 | { 153 | if (a < b && a < c) { return a; } 154 | return b < c ? b : c; 155 | } 156 | 157 | float max3(float a, float b, float c) 158 | { 159 | if (a > b && a > c) { return a; } 160 | return b > c ? b : c; 161 | } 162 | 163 | float barycentric(const ImVec2 &a, const ImVec2 &b, const ImVec2 &point) 164 | { 165 | return (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x); 166 | } 167 | 168 | inline uint8_t sample_font_texture(const Texture &texture, int x, int y) 169 | { 170 | return reinterpret_cast(texture.pixels)[x + y * texture.width]; 171 | } 172 | 173 | inline uint32_t sample_texture(const Texture &texture, int x, int y) { return texture.pixels[x + y * texture.width]; } 174 | 175 | void paint_uniform_rectangle(const PaintTarget &target, 176 | const ImVec2 &min_f, 177 | const ImVec2 &max_f, 178 | const ColorInt &color, 179 | Stats *stats) 180 | { 181 | // Integer bounding box [min, max): 182 | int min_x_i = static_cast(target.scale.x * min_f.x + 0.5f); 183 | int min_y_i = static_cast(target.scale.y * min_f.y + 0.5f); 184 | int max_x_i = static_cast(target.scale.x * max_f.x + 0.5f); 185 | int max_y_i = static_cast(target.scale.y * max_f.y + 0.5f); 186 | 187 | // Clamp to render target: 188 | min_x_i = std::max(min_x_i, 0); 189 | min_y_i = std::max(min_y_i, 0); 190 | max_x_i = std::min(max_x_i, target.width); 191 | max_y_i = std::min(max_y_i, target.height); 192 | 193 | // We often blend the same colors over and over again, so optimize for this (saves 25% total cpu): 194 | uint32_t last_target_pixel = target.pixels[min_y_i * target.width + min_x_i]; 195 | const auto *lastColorRef = reinterpret_cast(&last_target_pixel); 196 | uint32_t last_output = blend(*lastColorRef, color); 197 | 198 | for (int y = min_y_i; y < max_y_i; ++y) { 199 | for (int x = min_x_i; x < max_x_i; ++x) { 200 | uint32_t &target_pixel = target.pixels[y * target.width + x]; 201 | if (target_pixel == last_target_pixel) { 202 | target_pixel = last_output; 203 | continue; 204 | } 205 | last_target_pixel = target_pixel; 206 | const auto *colorRef = reinterpret_cast(&target_pixel); 207 | target_pixel = blend(*colorRef, color); 208 | last_output = target_pixel; 209 | } 210 | } 211 | } 212 | 213 | void paint_uniform_textured_rectangle(const PaintTarget &target, 214 | const Texture &texture, 215 | const ImVec4 &clip_rect, 216 | const ImDrawVert &min_v, 217 | const ImDrawVert &max_v, 218 | Stats *stats) 219 | { 220 | const ImVec2 min_p = ImVec2(target.scale.x * min_v.pos.x, target.scale.y * min_v.pos.y); 221 | const ImVec2 max_p = ImVec2(target.scale.x * max_v.pos.x, target.scale.y * max_v.pos.y); 222 | 223 | float distanceX = max_p.x - min_p.x; 224 | float distanceY = max_p.y - min_p.y; 225 | if (distanceX == 0 || distanceY == 0) { return; } 226 | 227 | // Find bounding box: 228 | float min_x_f = min_p.x; 229 | float min_y_f = min_p.y; 230 | float max_x_f = max_p.x; 231 | float max_y_f = max_p.y; 232 | 233 | // Clip against clip_rect: 234 | min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x - target.DisplayPos.x); 235 | min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y - target.DisplayPos.y); 236 | max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f - target.DisplayPos.x); 237 | max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f - target.DisplayPos.y); 238 | 239 | // Integer bounding box [min, max): 240 | int min_x_i = static_cast(min_x_f); 241 | int min_y_i = static_cast(min_y_f); 242 | int max_x_i = static_cast(max_x_f + 1.0f); 243 | int max_y_i = static_cast(max_y_f + 1.0f); 244 | 245 | // Clip against render target: 246 | min_x_i = std::max(min_x_i, 0); 247 | min_y_i = std::max(min_y_i, 0); 248 | max_x_i = std::min(max_x_i, target.width); 249 | max_y_i = std::min(max_y_i, target.height); 250 | 251 | const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x, min_y_i + 0.5f * target.scale.y); 252 | const ImVec2 delta_uv_per_pixel = { 253 | (max_v.uv.x - min_v.uv.x) / distanceX, 254 | (max_v.uv.y - min_v.uv.y) / distanceY, 255 | }; 256 | const ImVec2 uv_topleft = { 257 | min_v.uv.x + (topleft.x - min_v.pos.x) * delta_uv_per_pixel.x, 258 | min_v.uv.y + (topleft.y - min_v.pos.y) * delta_uv_per_pixel.y, 259 | }; 260 | 261 | int startX = uv_topleft.x * (texture.width - 1.0f) + 0.5f; 262 | int startY = uv_topleft.y * (texture.height - 1.0f) + 0.5f; 263 | 264 | int currentX = startX; 265 | int currentY = startY; 266 | 267 | float deltaX = delta_uv_per_pixel.x * texture.width; 268 | float deltaY = delta_uv_per_pixel.y * texture.height; 269 | 270 | for (int y = min_y_i; y < max_y_i; ++y) { 271 | currentX = startX; 272 | for (int x = min_x_i; x < max_x_i; ++x) { 273 | uint32_t &target_pixel = target.pixels[y * target.width + x]; 274 | const auto *targetColorRef = reinterpret_cast(&target_pixel); 275 | const auto *colorRef = reinterpret_cast(&min_v.col); 276 | 277 | if (&texture == fontTexture) { 278 | uint8_t texel = sample_font_texture(texture, currentX, currentY); 279 | if (deltaX != 0 && currentX < texture.width - 1) { currentX += 1; } 280 | 281 | // The font texture is all black or all white, so optimize for this: 282 | if (texel == 0) { continue; } 283 | if (texel == 255) { 284 | target_pixel = blend(*targetColorRef, *colorRef); 285 | continue; 286 | } 287 | 288 | } else { 289 | auto texColor = sample_texture(texture, currentX, currentY); 290 | auto src_color = reinterpret_cast(&texColor); 291 | 292 | if (deltaX != 0 && currentX < texture.width - 1) { currentX += 1; } 293 | 294 | *src_color *= *colorRef; 295 | target_pixel = blend(*targetColorRef, *src_color); 296 | } 297 | } 298 | if (deltaY != 0 && currentY < texture.height - 1) { currentY += 1; } 299 | } 300 | } 301 | 302 | // When two triangles share an edge, we want to draw the pixels on that edge exactly once. 303 | // The edge will be the same, but the direction will be the opposite 304 | // (assuming the two triangles have the same winding order). 305 | // Which edge wins? This functions decides. 306 | bool is_dominant_edge(ImVec2 edge) 307 | { 308 | // return edge.x < 0 || (edge.x == 0 && edge.y > 0); 309 | return edge.y > 0 || (edge.y == 0 && edge.x < 0); 310 | } 311 | 312 | // Handles triangles in any winding order (CW/CCW) 313 | void paint_triangle(const PaintTarget &target, 314 | const Texture *texture, 315 | const ImVec4 &clip_rect, 316 | const ImDrawVert &v0, 317 | const ImDrawVert &v1, 318 | const ImDrawVert &v2, 319 | Stats *stats) 320 | { 321 | const ImVec2 p0 = ImVec2(target.scale.x * v0.pos.x, target.scale.y * v0.pos.y); 322 | const ImVec2 p1 = ImVec2(target.scale.x * v1.pos.x, target.scale.y * v1.pos.y); 323 | const ImVec2 p2 = ImVec2(target.scale.x * v2.pos.x, target.scale.y * v2.pos.y); 324 | 325 | const auto rect_area = barycentric(p0, p1, p2);// Can be positive or negative depending on winding order 326 | if (rect_area == 0.0f) { return; } 327 | // if (rect_area < 0.0f) { return paint_triangle(target, texture, clip_rect, v0, v2, v1, stats); } 328 | 329 | // Find bounding box: 330 | float min_x_f = min3(p0.x, p1.x, p2.x); 331 | float min_y_f = min3(p0.y, p1.y, p2.y); 332 | float max_x_f = max3(p0.x, p1.x, p2.x); 333 | float max_y_f = max3(p0.y, p1.y, p2.y); 334 | 335 | // Clip against clip_rect: 336 | min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x - target.DisplayPos.x); 337 | min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y - target.DisplayPos.y); 338 | max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f - target.DisplayPos.x); 339 | max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f - target.DisplayPos.y); 340 | 341 | // Integer bounding box [min, max): 342 | int min_x_i = static_cast(min_x_f); 343 | int min_y_i = static_cast(min_y_f); 344 | int max_x_i = static_cast(max_x_f + 1.0f); 345 | int max_y_i = static_cast(max_y_f + 1.0f); 346 | 347 | // Clip against render target: 348 | min_x_i = std::max(min_x_i, 0); 349 | min_y_i = std::max(min_y_i, 0); 350 | max_x_i = std::min(max_x_i, target.width); 351 | max_y_i = std::min(max_y_i, target.height); 352 | 353 | // ------------------------------------------------------------------------ 354 | // Set up interpolation of barycentric coordinates: 355 | 356 | const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x, min_y_i + 0.5f * target.scale.y); 357 | const auto dx = ImVec2(1, 0); 358 | const auto dy = ImVec2(0, 1); 359 | 360 | const auto w0_topleft = barycentric(p1, p2, topleft); 361 | const auto w1_topleft = barycentric(p2, p0, topleft); 362 | const auto w2_topleft = barycentric(p0, p1, topleft); 363 | 364 | const auto w0_dx = barycentric(p1, p2, topleft + dx) - w0_topleft; 365 | const auto w1_dx = barycentric(p2, p0, topleft + dx) - w1_topleft; 366 | const auto w2_dx = barycentric(p0, p1, topleft + dx) - w2_topleft; 367 | 368 | const auto w0_dy = barycentric(p1, p2, topleft + dy) - w0_topleft; 369 | const auto w1_dy = barycentric(p2, p0, topleft + dy) - w1_topleft; 370 | const auto w2_dy = barycentric(p0, p1, topleft + dy) - w2_topleft; 371 | 372 | const Barycentric bary_0{ 1, 0, 0 }; 373 | const Barycentric bary_1{ 0, 1, 0 }; 374 | const Barycentric bary_2{ 0, 0, 1 }; 375 | 376 | const auto inv_area = 1 / rect_area; 377 | const Barycentric bary_topleft = inv_area * (w0_topleft * bary_0 + w1_topleft * bary_1 + w2_topleft * bary_2); 378 | const Barycentric bary_dx = inv_area * (w0_dx * bary_0 + w1_dx * bary_1 + w2_dx * bary_2); 379 | const Barycentric bary_dy = inv_area * (w0_dy * bary_0 + w1_dy * bary_1 + w2_dy * bary_2); 380 | 381 | Barycentric bary_current_row = bary_topleft; 382 | 383 | // ------------------------------------------------------------------------ 384 | // For pixel-perfect inside/outside testing: 385 | 386 | const int sign = rect_area > 0 ? 1 : -1;// winding order? 387 | 388 | const int bias0i = is_dominant_edge(p2 - p1) ? 0 : -1; 389 | const int bias1i = is_dominant_edge(p0 - p2) ? 0 : -1; 390 | const int bias2i = is_dominant_edge(p1 - p0) ? 0 : -1; 391 | 392 | const auto p0i = as_point(p0); 393 | const auto p1i = as_point(p1); 394 | const auto p2i = as_point(p2); 395 | 396 | // ------------------------------------------------------------------------ 397 | 398 | const bool has_uniform_color = (v0.col == v1.col && v0.col == v2.col); 399 | 400 | const ImVec4 c0 = color_convert_u32_to_float4(v0.col); 401 | const ImVec4 c1 = color_convert_u32_to_float4(v1.col); 402 | const ImVec4 c2 = color_convert_u32_to_float4(v2.col); 403 | 404 | // We often blend the same colors over and over again, so optimize for this (saves 10% total cpu): 405 | uint32_t last_target_pixel = 0; 406 | const auto *lastColorRef = reinterpret_cast(&last_target_pixel); 407 | const auto *colorRef = reinterpret_cast(&v0.col); 408 | uint32_t last_output = blend(*lastColorRef, *colorRef); 409 | 410 | for (int y = min_y_i; y < max_y_i; ++y) { 411 | auto bary = bary_current_row; 412 | 413 | bool has_been_inside_this_row = false; 414 | 415 | for (int x = min_x_i; x < max_x_i; ++x) { 416 | const auto w0 = bary.w0; 417 | const auto w1 = bary.w1; 418 | const auto w2 = bary.w2; 419 | bary += bary_dx; 420 | 421 | { 422 | // Inside/outside test: 423 | const auto p = Point{ kFixedBias * x + kFixedBias / 2, kFixedBias * y + kFixedBias / 2 }; 424 | const auto w0i = sign * orient2d(p1i, p2i, p) + bias0i; 425 | const auto w1i = sign * orient2d(p2i, p0i, p) + bias1i; 426 | const auto w2i = sign * orient2d(p0i, p1i, p) + bias2i; 427 | if (w0i < 0 || w1i < 0 || w2i < 0) { 428 | if (has_been_inside_this_row) { 429 | break;// Gives a nice 10% speedup 430 | } else { 431 | continue; 432 | } 433 | } 434 | } 435 | has_been_inside_this_row = true; 436 | 437 | uint32_t &target_pixel = target.pixels[y * target.width + x]; 438 | 439 | if (has_uniform_color && !texture) { 440 | if (target_pixel == last_target_pixel) { 441 | target_pixel = last_output; 442 | continue; 443 | } 444 | last_target_pixel = target_pixel; 445 | target_pixel = blend(*lastColorRef, *colorRef); 446 | last_output = target_pixel; 447 | continue; 448 | } 449 | 450 | ImVec4 src_color; 451 | 452 | if (has_uniform_color) { 453 | src_color = c0; 454 | } else { 455 | src_color = w0 * c0 + w1 * c1 + w2 * c2; 456 | } 457 | 458 | if (texture) { 459 | if (texture != fontTexture) { raise(SIGTRAP); } 460 | 461 | const ImVec2 uv = w0 * v0.uv + w1 * v1.uv + w2 * v2.uv; 462 | int x = uv.x * (texture->width - 1.0f) + 0.5f; 463 | int y = uv.y * (texture->height - 1.0f) + 0.5f; 464 | src_color.w *= sample_font_texture(*texture, x, y) / 255.0f; 465 | } 466 | 467 | if (src_color.w <= 0.0f) { continue; }// Transparent. 468 | if (src_color.w >= 1.0f) { 469 | // Opaque, no blending needed: 470 | target_pixel = color_convert_float4_to_u32(src_color); 471 | continue; 472 | } 473 | 474 | ImVec4 target_color = color_convert_u32_to_float4(target_pixel); 475 | const auto blended_color = src_color.w * src_color + (1.0f - src_color.w) * target_color; 476 | target_pixel = color_convert_float4_to_u32(blended_color); 477 | } 478 | 479 | bary_current_row += bary_dy; 480 | } 481 | } 482 | 483 | void paint_draw_cmd(const PaintTarget &target, 484 | const ImDrawVert *vertices, 485 | const ImDrawIdx *idx_buffer, 486 | const ImDrawCmd &pcmd, 487 | const SwOptions &options, 488 | Stats *stats) 489 | { 490 | const auto texture = reinterpret_cast(pcmd.TextureId); 491 | assert(texture); 492 | 493 | // ImGui uses the first pixel for "white". 494 | const ImVec2 white_uv = ImVec2(0.5f / texture->width, 0.5f / texture->height); 495 | 496 | for (int i = 0; i + 3 <= pcmd.ElemCount;) { 497 | ImDrawVert v0 = vertices[idx_buffer[i + 0]]; 498 | v0.pos.x -= target.DisplayPos.x; 499 | v0.pos.y -= target.DisplayPos.y; 500 | ImDrawVert v1 = vertices[idx_buffer[i + 1]]; 501 | v1.pos.x -= target.DisplayPos.x; 502 | v1.pos.y -= target.DisplayPos.y; 503 | ImDrawVert v2 = vertices[idx_buffer[i + 2]]; 504 | v2.pos.x -= target.DisplayPos.x; 505 | v2.pos.y -= target.DisplayPos.y; 506 | 507 | // Text is common, and is made of textured rectangles. So let's optimize for it. 508 | // This assumes the ImGui way to layout text does not change. 509 | if (options.optimize_text && i + 6 <= pcmd.ElemCount && idx_buffer[i + 3] == idx_buffer[i + 0] 510 | && idx_buffer[i + 4] == idx_buffer[i + 2]) { 511 | ImDrawVert v3 = vertices[idx_buffer[i + 5]]; 512 | v3.pos.x -= target.DisplayPos.x; 513 | v3.pos.y -= target.DisplayPos.y; 514 | 515 | if (v0.pos.x == v3.pos.x && v1.pos.x == v2.pos.x && v0.pos.y == v1.pos.y && v2.pos.y == v3.pos.y 516 | && v0.uv.x == v3.uv.x && v1.uv.x == v2.uv.x && v0.uv.y == v1.uv.y && v2.uv.y == v3.uv.y) { 517 | const bool has_uniform_color = v0.col == v1.col && v0.col == v2.col && v0.col == v3.col; 518 | 519 | const bool has_texture = v0.uv != white_uv || v1.uv != white_uv || v2.uv != white_uv || v3.uv != white_uv; 520 | 521 | if (has_uniform_color && has_texture) { 522 | paint_uniform_textured_rectangle(target, *texture, pcmd.ClipRect, v0, v2, stats); 523 | i += 6; 524 | continue; 525 | } 526 | } 527 | } 528 | 529 | // A lot of the big stuff are uniformly colored rectangles, 530 | // so we can save a lot of CPU by detecting them: 531 | if (options.optimize_rectangles && i + 6 <= pcmd.ElemCount) { 532 | ImDrawVert v3 = vertices[idx_buffer[i + 3]]; 533 | v3.pos.x -= target.DisplayPos.x; 534 | v3.pos.y -= target.DisplayPos.y; 535 | ImDrawVert v4 = vertices[idx_buffer[i + 4]]; 536 | v4.pos.x -= target.DisplayPos.x; 537 | v4.pos.y -= target.DisplayPos.y; 538 | ImDrawVert v5 = vertices[idx_buffer[i + 5]]; 539 | v5.pos.x -= target.DisplayPos.x; 540 | v5.pos.y -= target.DisplayPos.y; 541 | 542 | ImVec2 min, max; 543 | min.x = min3(v0.pos.x - target.DisplayPos.x, v1.pos.x - target.DisplayPos.x, v2.pos.x - target.DisplayPos.x); 544 | min.y = min3(v0.pos.y - target.DisplayPos.y, v1.pos.y - target.DisplayPos.y, v2.pos.y - target.DisplayPos.y); 545 | max.x = max3(v0.pos.x - target.DisplayPos.x, v1.pos.x - target.DisplayPos.x, v2.pos.x - target.DisplayPos.x); 546 | max.y = max3(v0.pos.y - target.DisplayPos.y, v1.pos.y - target.DisplayPos.y, v2.pos.y - target.DisplayPos.y); 547 | 548 | // Not the prettiest way to do this, but it catches all cases 549 | // of a rectangle split into two triangles. 550 | // TODO: Stop it from also assuming duplicate triangles is one rectangle. 551 | if ((v0.pos.x == min.x || v0.pos.x == max.x) && (v0.pos.y == min.y || v0.pos.y == max.y) 552 | && (v1.pos.x == min.x || v1.pos.x == max.x) && (v1.pos.y == min.y || v1.pos.y == max.y) 553 | && (v2.pos.x == min.x || v2.pos.x == max.x) && (v2.pos.y == min.y || v2.pos.y == max.y) 554 | && (v3.pos.x == min.x || v3.pos.x == max.x) && (v3.pos.y == min.y || v3.pos.y == max.y) 555 | && (v4.pos.x == min.x || v4.pos.x == max.x) && (v4.pos.y == min.y || v4.pos.y == max.y) 556 | && (v5.pos.x == min.x || v5.pos.x == max.x) && (v5.pos.y == min.y || v5.pos.y == max.y)) { 557 | const bool has_uniform_color = 558 | v0.col == v1.col && v0.col == v2.col && v0.col == v3.col && v0.col == v4.col && v0.col == v5.col; 559 | 560 | const bool has_texture = v0.uv != white_uv || v1.uv != white_uv || v2.uv != white_uv || v3.uv != white_uv 561 | || v4.uv != white_uv || v5.uv != white_uv; 562 | 563 | min.x = std::max(min.x, pcmd.ClipRect.x - target.DisplayPos.x); 564 | min.y = std::max(min.y, pcmd.ClipRect.y - target.DisplayPos.y); 565 | max.x = std::min(max.x, pcmd.ClipRect.z - 0.5f - target.DisplayPos.x); 566 | max.y = std::min(max.y, pcmd.ClipRect.w - 0.5f - target.DisplayPos.y); 567 | 568 | if (max.x < min.x || max.y < min.y) { 569 | i += 6; 570 | continue; 571 | }// Completely clipped 572 | 573 | const auto num_pixels = (max.x - min.x) * (max.y - min.y) * target.scale.x * target.scale.y; 574 | 575 | if (has_uniform_color) { 576 | const auto *colorRef = reinterpret_cast(&v0.col); 577 | paint_uniform_rectangle(target, min, max, *colorRef, stats); 578 | i += 6; 579 | continue; 580 | } 581 | } 582 | } 583 | 584 | const bool has_texture = (v0.uv != white_uv || v1.uv != white_uv || v2.uv != white_uv); 585 | paint_triangle(target, has_texture ? texture : nullptr, pcmd.ClipRect, v0, v1, v2, stats); 586 | i += 3; 587 | } 588 | } 589 | 590 | void paint_draw_list(const PaintTarget &target, const ImDrawList *cmd_list, const SwOptions &options, Stats *stats) 591 | { 592 | const ImDrawIdx *idx_buffer = &cmd_list->IdxBuffer[0]; 593 | const ImDrawVert *vertices = cmd_list->VtxBuffer.Data; 594 | 595 | for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.size(); cmd_i++) { 596 | const ImDrawCmd &pcmd = cmd_list->CmdBuffer[cmd_i]; 597 | if (pcmd.UserCallback) { 598 | pcmd.UserCallback(cmd_list, &pcmd); 599 | } else { 600 | paint_draw_cmd(target, vertices, idx_buffer, pcmd, options, stats); 601 | } 602 | idx_buffer += pcmd.ElemCount; 603 | } 604 | } 605 | 606 | }// namespace 607 | 608 | void make_style_fast() 609 | { 610 | ImGuiStyle &style = ImGui::GetStyle(); 611 | 612 | style.AntiAliasedLines = false; 613 | style.AntiAliasedFill = false; 614 | style.WindowRounding = 0; 615 | } 616 | 617 | void restore_style() 618 | { 619 | ImGuiStyle &style = ImGui::GetStyle(); 620 | const ImGuiStyle default_style = ImGuiStyle(); 621 | style.AntiAliasedLines = default_style.AntiAliasedLines; 622 | style.AntiAliasedFill = default_style.AntiAliasedFill; 623 | style.WindowRounding = default_style.WindowRounding; 624 | } 625 | 626 | void bind_imgui_painting() 627 | { 628 | ImGuiIO &io = ImGui::GetIO(); 629 | 630 | // Load default font (embedded in code): 631 | uint8_t *tex_data; 632 | int font_width, font_height; 633 | io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height); 634 | const auto texture = new Texture{ reinterpret_cast(tex_data), font_width, font_height }; 635 | io.Fonts->TexID = texture; 636 | 637 | fontTexture = texture; 638 | } 639 | 640 | static Stats s_stats;// TODO: pass as an argument? 641 | 642 | void paint_imgui(uint32_t *pixels, ImDrawData *drawData, const SwOptions &options) 643 | { 644 | int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x); 645 | int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y); 646 | if (fb_width <= 0 || fb_height <= 0) return; 647 | 648 | PaintTarget target{ pixels, fb_width, fb_height, drawData->FramebufferScale, drawData->DisplayPos }; 649 | 650 | s_stats = Stats{}; 651 | for (int i = 0; i < drawData->CmdListsCount; ++i) { 652 | paint_draw_list(target, drawData->CmdLists[i], options, &s_stats); 653 | } 654 | } 655 | 656 | void unbind_imgui_painting() 657 | { 658 | ImGuiIO &io = ImGui::GetIO(); 659 | delete reinterpret_cast(io.Fonts->TexID); 660 | // io.Fonts = nullptr; 661 | } 662 | 663 | bool show_options(SwOptions *io_options) 664 | { 665 | assert(io_options); 666 | bool changed = false; 667 | changed |= ImGui::Checkbox("optimize_text", &io_options->optimize_text); 668 | changed |= ImGui::Checkbox("optimize_rectangles", &io_options->optimize_rectangles); 669 | return changed; 670 | } 671 | 672 | void show_stats() 673 | { 674 | ImGui::Text("uniform_triangle_pixels: %7d", s_stats.uniform_triangle_pixels); 675 | ImGui::Text("textured_triangle_pixels: %7d", s_stats.textured_triangle_pixels); 676 | ImGui::Text("gradient_triangle_pixels: %7d", s_stats.gradient_triangle_pixels); 677 | ImGui::Text("font_pixels: %7d", s_stats.font_pixels); 678 | ImGui::Text("uniform_rectangle_pixels: %7.0f", s_stats.uniform_rectangle_pixels); 679 | ImGui::Text("textured_rectangle_pixels: %7.0f", s_stats.textured_rectangle_pixels); 680 | ImGui::Text("gradient_rectangle_pixels: %7.0f", s_stats.gradient_rectangle_pixels); 681 | ImGui::Text("gradient_textured_rectangle_pixels: %7.0f", s_stats.gradient_textured_rectangle_pixels); 682 | } 683 | 684 | }// namespace imgui_sw 685 | -------------------------------------------------------------------------------- /src/imgui_sw.hpp: -------------------------------------------------------------------------------- 1 | // By Emil Ernerfeldt 2018 2 | // LICENSE: 3 | // This software is dual-licensed to the public domain and under the following 4 | // license: you are granted a perpetual, irrevocable license to copy, modify, 5 | // publish, and distribute this file as you see fit. 6 | // WHAT: 7 | // This is a software renderer for Dear ImGui. 8 | // It is decently fast, but has a lot of room for optimization. 9 | // The goal was to get something fast and decently accurate in not too many lines of code. 10 | // LIMITATIONS: 11 | // * It is not pixel-perfect, but it is good enough for must use cases. 12 | // * It does not support painting with any other texture than the default font texture. 13 | #pragma once 14 | 15 | #include 16 | 17 | struct ImDrawData; 18 | namespace imgui_sw { 19 | 20 | struct Texture 21 | { 22 | const uint32_t *pixels = nullptr; 23 | int width = 0; 24 | int height = 0; 25 | }; 26 | 27 | 28 | struct SwOptions 29 | { 30 | bool optimize_text = true;// No reason to turn this off. 31 | bool optimize_rectangles = true;// No reason to turn this off. 32 | }; 33 | 34 | /// Optional: tweak ImGui style to make it render faster. 35 | void make_style_fast(); 36 | 37 | /// Undo what make_style_fast did. 38 | void restore_style(); 39 | 40 | /// Call once a the start of your program. 41 | void bind_imgui_painting(); 42 | 43 | /// The buffer is assumed to follow how ImGui packs pixels, i.e. ABGR by default. 44 | /// Change with IMGUI_USE_BGRA_PACKED_COLOR. 45 | /// If width/height differs from ImGui::GetIO().DisplaySize then 46 | /// the function scales the UI to fit the given pixel buffer. 47 | void paint_imgui(uint32_t *pixels, ImDrawData *data, const SwOptions &options = {}); 48 | 49 | /// Free the resources allocated by bind_imgui_painting. 50 | void unbind_imgui_painting(); 51 | 52 | /// Show ImGui controls for rendering options if you want to. 53 | bool show_options(SwOptions *io_options); 54 | 55 | /// Show rendering stats in an ImGui window if you want to. 56 | void show_stats(); 57 | 58 | }// namespace imgui_sw 59 | -------------------------------------------------------------------------------- /src/libs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define LOGURU_IMPLEMENTATION 1 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef OPENGL_REFERENCE_RENDERER 13 | #include 14 | #include 15 | #include 16 | #endif 17 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef OPENGL_REFERENCE_RENDERER 9 | // For gl path: 10 | #include 11 | #include 12 | #include 13 | #endif // OPENGL_REFERENCE_RENDERER 14 | 15 | #include "imgui_sw.hpp" 16 | 17 | using namespace emilib; 18 | 19 | void customRendering(ImVec4 col) 20 | { 21 | // Taken from imgui_demo.cpp: 22 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 23 | static float sz = 36.0f; 24 | static float thickness = 4.0f; 25 | const ImVec2 p = ImGui::GetCursorScreenPos(); 26 | const ImU32 col32 = ImColor(col); 27 | float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; 28 | for (int n = 0; n < 2; n++) { 29 | float curr_thickness = (n == 0) ? 1.0f : thickness; 30 | draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, curr_thickness); x += sz+spacing; 31 | draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; 32 | draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; 33 | draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, curr_thickness); x += sz+spacing; 34 | draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, curr_thickness); x += sz+spacing; 35 | draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, curr_thickness); x += sz+spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) 36 | draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, curr_thickness); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) 37 | draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, curr_thickness); x += sz+spacing; // Diagonal line 38 | draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, curr_thickness); 39 | x = p.x + 4; 40 | y += sz+spacing; 41 | } 42 | draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; 43 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; 44 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; 45 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; 46 | draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; 47 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+thickness), col32); x += sz+spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) 48 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+thickness, y+sz), col32); x += spacing+spacing; // Vertical line (faster than AddLine, but only handle integer thickness) 49 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+1, y+1), col32); x += sz; // Pixel (faster than AddLine) 50 | draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); 51 | ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); 52 | } 53 | 54 | void showTestWindows() 55 | { 56 | static ImVec4 s_some_color{ 0.7f, 0.8f, 0.9f, 0.5f }; 57 | 58 | ImGui::SetNextWindowPos(ImVec2{700.0f, 32.0f}); 59 | ImGui::SetNextWindowSize(ImVec2{400.0f, 800.0f}); 60 | if (ImGui::Begin("Graphics")) { 61 | ImGui::ColorPicker4("some color", &s_some_color.x, ImGuiColorEditFlags_PickerHueBar); 62 | ImGui::ColorPicker4("same color", &s_some_color.x, ImGuiColorEditFlags_PickerHueWheel); 63 | customRendering(s_some_color); 64 | } 65 | ImGui::End(); 66 | 67 | ImGui::SetNextWindowPos(ImVec2{32.0f, 400.0f}); 68 | ImGui::SetNextWindowSize(ImVec2{600.0f, 600.0f}); 69 | if (ImGui::Begin("Test")) { 70 | for (int i = 100; i > 0; --i) { 71 | ImGui::Text("%d bottles of beer on the wall, %d bottles of beer. Take one down, pass it around, %d bottles of beer on the wall.", i, i, i - 1); 72 | } 73 | } 74 | ImGui::End(); 75 | } 76 | 77 | void run_software() 78 | { 79 | int width_points = 1280; 80 | int height_points = 1024; 81 | 82 | CHECK_EQ_F(SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO), 0, "SDL_Init fail: %s\n", SDL_GetError()); 83 | const auto window = SDL_CreateWindow("ImGui Software Renderer Example", SDL_WINDOWPOS_UNDEFINED, 84 | SDL_WINDOWPOS_UNDEFINED, width_points, height_points, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); 85 | CHECK_NOTNULL_F(window, "Failed to create window: %s", SDL_GetError()); 86 | 87 | SDL_GetWindowSize(window, &width_points, &height_points); 88 | 89 | int width_pixels, height_pixels; 90 | SDL_GL_GetDrawableSize(window, &width_pixels, &height_pixels); 91 | 92 | const auto pixels_per_point = static_cast(width_pixels) / static_cast(width_points); 93 | 94 | LOG_F(INFO, "%dx%d points (%dx%d pixels)", width_points, height_points, width_pixels, height_pixels); 95 | 96 | IMGUI_CHECKVERSION(); 97 | ImGui_SDL imgui_sdl(width_points, height_points, pixels_per_point); 98 | 99 | auto surface = SDL_GetWindowSurface(window); 100 | auto renderer = SDL_CreateSoftwareRenderer(surface); 101 | CHECK_NOTNULL_F(renderer, "Failed to create software renderer: %s", SDL_GetError()); 102 | 103 | SDL_Texture* texture = SDL_CreateTexture(renderer, 104 | SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, width_pixels, height_pixels); 105 | CHECK_NOTNULL_F(texture); 106 | 107 | std::vector pixel_buffer(width_pixels * height_pixels, 0); 108 | std::vector point_buffer(width_points * height_points, 0); 109 | 110 | imgui_sw::bind_imgui_painting(); 111 | 112 | imgui_sw::SwOptions sw_options; 113 | bool full_res = (width_pixels == width_points); 114 | bool fast_style = false; 115 | 116 | double paint_time = 0; 117 | double upsample_time = 0; 118 | 119 | bool quit = false; 120 | while (!quit) { 121 | SDL_Event event; 122 | while (SDL_PollEvent(&event)) { 123 | if (event.type == SDL_QUIT) { quit = true; } 124 | imgui_sdl.on_event(event); 125 | } 126 | imgui_sdl.new_frame(); 127 | 128 | // -------------------------------------------------------------------- 129 | 130 | if (ImGui::BeginMainMenuBar()) { 131 | imgui_helpers::show_im_gui_menu(); 132 | ImGui::EndMainMenuBar(); 133 | } 134 | 135 | ImGui::SetNextWindowPos(ImVec2{32.0f, 32.0f}); 136 | if (ImGui::Begin("Settings")) { 137 | if (ImGui::Checkbox("Tweak ImGui style for speed", &fast_style)) { 138 | if (fast_style) { 139 | imgui_sw::make_style_fast(); 140 | } else { 141 | imgui_sw::restore_style(); 142 | } 143 | } 144 | 145 | ImGui::Checkbox("full_res", &full_res); 146 | ImGui::Text("Paint time: %.2f ms", 1000 * paint_time); 147 | if (!full_res) { 148 | ImGui::Text("Upsample time: %.2f ms", 1000 * paint_time); 149 | } 150 | imgui_sw::show_options(&sw_options); 151 | imgui_sw::show_stats(); 152 | } 153 | ImGui::End(); 154 | 155 | showTestWindows(); 156 | 157 | // -------------------------------------------------------------------- 158 | 159 | imgui_sdl.paint(); 160 | double frame_paint_time; 161 | 162 | if (full_res) { 163 | std::fill_n(pixel_buffer.data(), pixel_buffer.size(), 0x19191919u); 164 | Timer paint_timer; 165 | paint_imgui(pixel_buffer.data(), width_pixels, height_pixels, sw_options); 166 | frame_paint_time = paint_timer.secs(); 167 | } else { 168 | // Render ImGui in low resolution: 169 | CHECK_LE_F(width_points, width_pixels); 170 | std::fill_n(point_buffer.data(), point_buffer.size(), 0x19191919u); 171 | Timer paint_timer; 172 | paint_imgui(point_buffer.data(), width_points, height_points, sw_options); 173 | frame_paint_time = paint_timer.secs(); 174 | 175 | // Now upsample it (TODO: a faster way). 176 | Timer upsample_timer; 177 | const int scale = height_pixels / height_points; 178 | for (int y_px = 0; y_px < height_pixels; ++y_px) { 179 | const auto y_pts = y_px / scale; 180 | for (int x_px = 0; x_px < width_pixels; ++x_px) { 181 | const auto x_pts = x_px / scale; 182 | pixel_buffer[y_px * width_pixels + x_px] = point_buffer[y_pts * width_points + x_pts]; 183 | } 184 | } 185 | upsample_time = upsample_timer.secs(); 186 | } 187 | 188 | paint_time = 0.95 * paint_time + 0.05 * frame_paint_time; 189 | 190 | SDL_UpdateTexture(texture, nullptr, pixel_buffer.data(), width_pixels * sizeof(Uint32)); 191 | // SDL_RenderClear(renderer); 192 | SDL_RenderCopy(renderer, texture, nullptr, nullptr); 193 | SDL_RenderPresent(renderer); 194 | SDL_UpdateWindowSurface(window); 195 | } 196 | } 197 | 198 | #ifdef OPENGL_REFERENCE_RENDERER 199 | void run_gl() 200 | { 201 | sdl::Params sdl_params; 202 | sdl_params.window_name = "ImGui GL Renderer"; 203 | sdl_params.width_points = 1280; 204 | sdl_params.height_points = 1024; 205 | auto sdl = sdl::init(sdl_params); 206 | ImGui_SDL imgui_sdl(sdl.width_points, sdl.height_points, sdl.pixels_per_point); 207 | gl::bind_imgui_painting(); 208 | 209 | double paint_time = 0; 210 | bool wireframe = false; 211 | 212 | bool quit = false; 213 | while (!quit) { 214 | SDL_Event event; 215 | while (SDL_PollEvent(&event)) { 216 | if (event.type == SDL_QUIT) { quit = true; } 217 | imgui_sdl.on_event(event); 218 | } 219 | imgui_sdl.new_frame(); 220 | 221 | if (ImGui::BeginMainMenuBar()) { 222 | imgui_helpers::show_im_gui_menu(); 223 | ImGui::EndMainMenuBar(); 224 | } 225 | ImGui::Text("Paint time: %.2f ms", 1000 * paint_time); 226 | ImGui::Checkbox("wireframe", &wireframe); 227 | 228 | showTestWindows(); 229 | 230 | imgui_sdl.paint(); 231 | 232 | glClearColor(0.1f, 0.1f, 0.1f, 0.0f); 233 | glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 234 | glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL); 235 | Timer paint_timer; 236 | gl::paint_imgui(); 237 | paint_time = paint_timer.secs(); 238 | SDL_GL_SwapWindow(sdl.window); 239 | } 240 | } 241 | #endif // OPENGL_REFERENCE_RENDERER 242 | 243 | int main(int argc, char* argv[]) 244 | { 245 | loguru::g_colorlogtostderr = false; 246 | loguru::init(argc, argv); 247 | 248 | #ifdef OPENGL_REFERENCE_RENDERER 249 | run_gl(); 250 | #else 251 | run_software(); 252 | #endif 253 | } 254 | --------------------------------------------------------------------------------