Skip to content

Instantly share code, notes, and snippets.

@fearofcode
Last active September 28, 2025 00:27
Show Gist options
  • Save fearofcode/b2630eebf4ede460a4ae9506697cba45 to your computer and use it in GitHub Desktop.
Save fearofcode/b2630eebf4ede460a4ae9506697cba45 to your computer and use it in GitHub Desktop.
DPI-aware Odin Dear imgui setup using odin-imgui

imgui setup:

  • Download Inconsolata from https://fonts.google.com/specimen/Inconsolata and extract its file Inconsolata-Regular.ttf to a directory called assets if you want the assets/Inconsolata-Regular.ttf font the code uses to work as is

  • Clone https://gitlab.com/L-4/odin-imgui to a submodule: git submodule add [email protected]:L-4/odin-imgui.git odin-imgui

  • Clone dear_bindings to a submodule: git submodule add [email protected]:dearimgui/dear_bindings.git dear_bindings

  • Make a python venv and install dear_bindings requirements (ply):

    • python3 -m venv venv
    • source venv/bin/activate
    • cd dear_bindings
    • pip install -r requirements.txt

Now build odin-imgui with this venv that has ply installed:

  • cd ../odin-imgui
  • python3 build.py

The venv part might not be necessary (I haven't tried it without).

This has currently only been tested on x64 Linux and Windows 11.

If you get a linking error about -lc++ missing, install libstdc++. On Arch (btw), sudo pacman -S libc++.

On Windows 11, I had to downloaded the latest SDL2 release and extract SDL2.dll in the same directory as the .exe.

package main
import "core:fmt"
import im "odin-imgui"
import "odin-imgui/imgui_impl_opengl3"
import "odin-imgui/imgui_impl_sdl2"
import SDL "vendor:sdl2"
DEBUG_UI_FONT_PATH :: "assets/Inconsolata-Regular.ttf"
DEBUG_UI_FONT_SIZE :: 20.0
init_dear_imgui :: proc(window_context : SDL_Window_Context) -> ^im.IO {
im.CHECKVERSION()
im.CreateContext()
io := im.GetIO()
io.ConfigFlags += {.NavEnableKeyboard, .NavEnableGamepad}
im.StyleColorsDark()
imgui_style := im.GetStyle()
// this uses our ad hoc import above
main_scale := get_content_scale_for_window(window_context.window)
im.Style_ScaleAllSizes(imgui_style, main_scale)
im.FontAtlas_AddFontFromFileTTF(io.Fonts, DEBUG_UI_FONT_PATH, DEBUG_UI_FONT_SIZE * main_scale)
imgui_impl_sdl2.InitForOpenGL(window_context.window, window_context.gl_context)
imgui_impl_opengl3.Init(nil)
return io
}
dear_imgui_cleanup :: proc() {
imgui_impl_opengl3.Shutdown()
imgui_impl_sdl2.Shutdown()
im.DestroyContext()
}
new_debug_ui_frame :: proc() {
imgui_impl_opengl3.NewFrame()
imgui_impl_sdl2.NewFrame()
im.NewFrame()
}
render_debug_ui :: proc(io : ^im.IO) {
if app_state.show_debug_ui {
window_flags : im.WindowFlags
window_flags = {im.WindowFlag.AlwaysAutoResize}
if im.Begin("Debug UI", &app_state.show_debug_ui, window_flags) {
im.Checkbox("Toggle show quad", &app_state.show_quad)
im.NewLine()
framerate_str := fmt.ctprintf("%f FPS", io.Framerate)
im.Text(framerate_str)
im.Text("ESC to quit")
im.Text("F1 to toggle UI")
}
im.End()
}
im.Render()
}
render_debug_ui_draw_data :: proc() {
imgui_impl_opengl3.RenderDrawData(im.GetDrawData())
}
debug_ui_process_event :: proc(event : ^SDL.Event) {
imgui_impl_sdl2.ProcessEvent(event)
}
package main
import "core:fmt"
import "core:math"
import glm "core:math/linalg/glsl"
import "core:time"
import gl "vendor:OpenGL"
import SDL "vendor:sdl2"
App_State :: struct {
show_quad : bool,
show_debug_ui : bool,
}
app_state : App_State
Vertex :: struct {
pos : glm.vec3,
col : glm.vec4,
}
main :: proc() {
window_context, window_ok := init_sdl_opengl_context()
if !window_ok {
return
}
defer sdl_opengl_cleanup(window_context)
io := init_dear_imgui(window_context)
defer dear_imgui_cleanup()
main_shader, shader_ok := shader_from_source_strings(vertex_source, fragment_source)
if !shader_ok {
fmt.eprintln("Failed to create GLSL program")
return
}
defer cleanup_shader(main_shader)
use_shader(main_shader)
vao : u32
gl.GenVertexArrays(1, &vao); defer gl.DeleteVertexArrays(1, &vao)
gl.BindVertexArray(vao)
vbo, ebo : u32
gl.GenBuffers(1, &vbo)
defer gl.DeleteBuffers(1, &vbo)
gl.GenBuffers(1, &ebo)
defer gl.DeleteBuffers(1, &ebo)
vertices := []Vertex {
// position color
{{-0.5, +0.5, 0}, {1.0, 0.0, 0.0, 0.75}},
{{-0.5, -0.5, 0}, {1.0, 1.0, 0.0, 0.75}},
{{+0.5, -0.5, 0}, {0.0, 1.0, 0.0, 0.75}},
{{+0.5, +0.5, 0}, {0.0, 0.0, 1.0, 0.75}},
}
indices := []u16{0, 1, 2, 2, 3, 0}
index_count := i32(len(indices))
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices) * size_of(vertices[0]), raw_data(vertices), gl.STATIC_DRAW)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, pos))
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(1, 4, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, col))
gl.EnableVertexAttribArray(1)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices) * size_of(indices[0]), raw_data(indices), gl.STATIC_DRAW)
start_tick := time.tick_now()
camera_position : glm.vec3 = {0, 0, -2}
world_center : glm.vec3 = {0, 0, 0}
y_up : glm.vec3 = {0, 1, 0}
camera_fov := math.to_radians_f32(45)
app_state.show_quad = true
app_state.show_debug_ui = true
loop: for {
duration := time.tick_since(start_tick)
t := f32(time.duration_seconds(duration))
event : SDL.Event
for SDL.PollEvent(&event) {
debug_ui_process_event(&event)
#partial switch event.type {
case .QUIT:
break loop
case .KEYDOWN, .KEYUP:
if event.key.keysym.sym == .F1 && event.type == .KEYDOWN {
app_state.show_debug_ui = !app_state.show_debug_ui
}
if event.key.keysym.sym == .ESCAPE {break loop}
}
}
new_debug_ui_frame()
render_debug_ui(io)
gl.Viewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
gl.ClearColor(0.0, 0.0, 0.0, 1.0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
if app_state.show_quad {
gl.Enable(gl.DEPTH_TEST)
gl.Disable(gl.SCISSOR_TEST)
gl.Disable(gl.BLEND)
gl.BindVertexArray(vao)
// rotate about Z axis
model := mat4_identity() * glm.mat4Rotate({0, 1, 0}, t)
view := glm.mat4LookAt(eye = camera_position, centre = world_center, up = y_up)
projection := glm.mat4Perspective(camera_fov, ASPECT, 0.1, 100.0)
use_shader(main_shader)
transform := projection * view * model
set_shader_uniform_mat4(main_shader, "u_transform", transform)
gl.DrawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_SHORT, nil)
}
render_debug_ui_draw_data()
SDL.GL_SwapWindow(window_context.window)
free_all(context.temp_allocator)
}
}
vertex_source := `#version 460 core
layout(location=0) in vec3 a_position;
layout(location=1) in vec4 a_color;
out vec4 v_color;
uniform mat4 u_transform;
void main() {
gl_Position = u_transform * vec4(a_position, 1.0);
v_color = a_color;
}
`
fragment_source := `#version 460 core
in vec4 v_color;
out vec4 o_color;
void main() {
o_color = v_color;
}
`
package main
import "core:fmt"
import glm "core:math/linalg/glsl"
import gl "vendor:OpenGL"
Shader :: struct {
program_id : u32,
uniforms : gl.Uniforms,
}
shader_from_source_strings :: proc(vertex_source, fragment_source : string) -> (Shader, bool) {
shader : Shader
program_id, program_ok := gl.load_shaders_source(vertex_source, fragment_source)
if !program_ok {
fmt.eprintln("Failed to create GLSL program!")
}
shader.program_id = program_id
shader.uniforms = gl.get_uniforms_from_program(program_id)
return shader, program_ok
}
cleanup_shader :: proc(shader : Shader) {
gl.DeleteProgram(shader.program_id)
delete(shader.uniforms)
}
use_shader :: proc(shader : Shader) {
gl.UseProgram(shader.program_id)
}
set_shader_uniform_mat4 :: proc(shader : Shader, name : string, m : glm.mat4) {
m := m // so we can pass a pointer
gl.UniformMatrix4fv(shader.uniforms[name].location, 1, false, &m[0, 0])
}
set_shader_uniform_int :: proc(shader : Shader, name : string, i : i32) {
gl.Uniform1i(shader.uniforms[name].location, i)
}
package main
import glm "core:math/linalg/glsl"
mat4_identity :: proc() -> glm.mat4 {
return glm.mat4{1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0}
}
package main
import "core:fmt"
import gl "vendor:OpenGL"
import SDL "vendor:sdl2"
GL_VERSION_MAJOR :: 4
GL_VERSION_MINOR :: 6
WINDOW_WIDTH :: 1920
WINDOW_HEIGHT :: 1080
ASPECT :: WINDOW_WIDTH / WINDOW_HEIGHT
UI_SCALE :: 1.0 // todo compute this dynamically based on window and display dimensions
SDL_Window_Context :: struct {
window : ^SDL.Window,
gl_context : SDL.GLContext,
}
init_sdl_opengl_context :: proc() -> (SDL_Window_Context, bool) {
SDL.SetHint("SDL_WINDOWS_DPI_AWARENESS", "permonitorv2")
window_context : SDL_Window_Context
SDL.Init({.VIDEO})
window := SDL.CreateWindow(
"Odin SDL2 Demo",
SDL.WINDOWPOS_UNDEFINED,
SDL.WINDOWPOS_UNDEFINED,
WINDOW_WIDTH,
WINDOW_HEIGHT,
{.OPENGL, .ALLOW_HIGHDPI},
)
if window == nil {
fmt.eprintln("Failed to create window")
return window_context, false
}
SDL.GL_SetAttribute(.CONTEXT_PROFILE_MASK, i32(SDL.GLprofile.CORE))
SDL.GL_SetAttribute(.CONTEXT_MAJOR_VERSION, GL_VERSION_MAJOR)
SDL.GL_SetAttribute(.CONTEXT_MINOR_VERSION, GL_VERSION_MINOR)
gl_context := SDL.GL_CreateContext(window)
// vsync
SDL.GL_SetSwapInterval(1)
gl.load_up_to(GL_VERSION_MAJOR, GL_VERSION_MINOR, SDL.gl_set_proc_address)
window_context.window = window
window_context.gl_context = gl_context
return window_context, true
}
sdl_opengl_cleanup :: proc(window_context : SDL_Window_Context) {
SDL.Quit()
SDL.DestroyWindow(window_context.window)
SDL.GL_DeleteContext(window_context.gl_context)
}
get_content_scale_for_window :: proc(window : ^SDL.Window) -> f32 {
dpi : f32 = 0.0
display_index := SDL.GetWindowDisplayIndex(window)
if SDL.GetDisplayDPI(display_index, &dpi, nil, nil) == 0 {
return dpi / 96.0
}
return 1.0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment