Makefile working with imgui

This commit is contained in:
2026-04-27 13:41:50 +02:00
parent 0fed24b3c4
commit 21476a3b95
11 changed files with 187 additions and 131 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
app.* app.*
.vscode/* .vscode/*
lib/
.vscode
.claude

63
CLAUDE.md Normal file
View File

@@ -0,0 +1,63 @@
# CLAUDE.md
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

View File

@@ -1,17 +0,0 @@
emcc -O3 src/main.c \
../cimgui/cimgui.cpp \
../cimgui/imgui.cpp \
../cimgui/imgui_draw.cpp \
../cimgui/imgui_tables.cpp \
../cimgui/imgui_widgets.cpp \
-o app.html \
-sUSE_WEBGPU \
-sWASM_BIGINT \
-sALLOW_MEMORY_GROWTH \
-I../sokol \
-I../cimgui \
-msimd128 \
-flto \
--llvm-lto 1 \
--shell-file=shell.html \
-sFILESYSTEM=0

View File

@@ -1,18 +0,0 @@
xxd -i src/shaders/sprite.wgsl src/generated/sprite.h
emcc src/main.c \
../cimgui/cimgui.cpp \
../cimgui/imgui.cpp \
../cimgui/imgui_draw.cpp \
../cimgui/imgui_tables.cpp \
../cimgui/imgui_widgets.cpp \
-o app.html \
-sUSE_WEBGPU \
-sASSERTIONS \
-sWASM_BIGINT \
-sALLOW_MEMORY_GROWTH \
-I../sokol \
-I../cimgui \
-msimd128 \
--shell-file=shell.html \
-sFILESYSTEM=0

50
fetch_libs.sh Normal file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
set -e
LIB_DIR="lib"
echo "=== Fetching library dependencies ==="
# 1. Sokol (single-file headers)
mkdir -p "$LIB_DIR/sokol"
mkdir -p "$LIB_DIR/imgui"
mkdir -p "$LIB_DIR/util"
if [ ! -f "$LIB_DIR/sokol/sokol_gfx.h" ]; then
echo " > Fetching sokol (pinned to emdawnwebgpu-compatible version)..."
git clone --depth 500 https://github.com/floooh/sokol.git "$LIB_DIR/sokol_tmp"
git -C "$LIB_DIR/sokol_tmp" checkout 36debc7
cp "$LIB_DIR/sokol_tmp"/*.h "$LIB_DIR/sokol/"
cp "$LIB_DIR/sokol_tmp"/util/*.h "$LIB_DIR/util/" 2>/dev/null || true
rm -rf "$LIB_DIR/sokol_tmp"
else
echo " > Sokol already present"
fi
# 2. Dear ImGui
if [ ! -f "$LIB_DIR/imgui/imgui/imgui.cpp" ]; then
echo " > Fetching Dear ImGui..."
mkdir -p "$LIB_DIR/imgui/imgui"
git clone --depth 1 --branch v1.92.7-docking https://github.com/ocornut/imgui.git "$LIB_DIR/imgui_src"
cp "$LIB_DIR/imgui_src"/*.cpp "$LIB_DIR/imgui/imgui/"
cp "$LIB_DIR/imgui_src"/*.h "$LIB_DIR/imgui/imgui/"
rm -rf "$LIB_DIR/imgui_src"
else
echo " > Dear ImGui already present"
fi
# 3. cimgui
if [ ! -f "$LIB_DIR/imgui/cimgui.cpp" ]; then
echo " > Fetching cimgui..."
git clone --depth 1 https://github.com/cimgui/cimgui.git "$LIB_DIR/cimgui_tmp"
cp "$LIB_DIR/cimgui_tmp/cimgui.h" "$LIB_DIR/imgui/"
cp "$LIB_DIR/cimgui_tmp/cimgui.cpp" "$LIB_DIR/imgui/"
cp "$LIB_DIR/cimgui_tmp/cimconfig.h" "$LIB_DIR/imgui/"
cp "$LIB_DIR/cimgui_tmp/cimgui_impl.h" "$LIB_DIR/imgui/"
cp "$LIB_DIR/cimgui_tmp/cimgui_impl.cpp" "$LIB_DIR/imgui/"
rm -rf "$LIB_DIR/cimgui_tmp"
else
echo " > cimgui already present"
fi
echo "=== Done ==="

View File

@@ -1,52 +1,56 @@
# Compiler and tools # Compiler and tools
CC = emcc CC = emcc
XXD = xxd XXD = xxd
FETCH = ./fetch_libs.sh
# Directories # Directories
SRC_DIR = src SRC_DIR = src
LIB_DIR = lib
SHADER_DIR = $(SRC_DIR)/shaders SHADER_DIR = $(SRC_DIR)/shaders
GENERATED_DIR = $(SRC_DIR)/generated GENERATED_DIR = $(SRC_DIR)/generated
SOKOL_DIR = ../sokol
NUKLEAR_DIR = ../nuklear
# Output # Output
TARGET = app.html TARGET = app.html
# Source files # Source files
C_SOURCES = $(SRC_DIR)/main.c C_SOURCES = $(SRC_DIR)/main.c
IMGUI_SOURCES = $(LIB_DIR)/imgui/imgui/imgui.cpp \
$(LIB_DIR)/imgui/imgui/imgui_draw.cpp \
$(LIB_DIR)/imgui/imgui/imgui_tables.cpp \
$(LIB_DIR)/imgui/imgui/imgui_widgets.cpp \
$(LIB_DIR)/imgui/cimgui.cpp
# Dynamic shader processing # Dynamic shader processing
SHADER_FILES = $(wildcard $(SHADER_DIR)/*) SHADER_FILES = $(wildcard $(SHADER_DIR)/*.wgsl)
SHADER_HEADERS = $(patsubst $(SHADER_DIR)/%,$(GENERATED_DIR)/%.h,$(SHADER_FILES)) SHADER_HEADERS = $(patsubst $(SHADER_DIR)/%.wgsl,$(GENERATED_DIR)/%.h,$(SHADER_FILES))
# Compiler flags # Compiler flags
EMCC_FLAGS = -sUSE_WEBGPU \ EMCC_FLAGS = --use-port=emdawnwebgpu \
-sASSERTIONS \
-sWASM_BIGINT \ -sWASM_BIGINT \
-sALLOW_MEMORY_GROWTH \ -sALLOW_MEMORY_GROWTH \
-msimd128 \ -msimd128 \
-sFILESYSTEM=0 -sFILESYSTEM=0
# Include directories
INCLUDES = -I$(SOKOL_DIR) -I$(NUKLEAR_DIR)
# Shell template # Shell template
SHELL_FILE = shell.html SHELL_FILE = shell.html
# Default target # Default target
all: $(TARGET) all: $(FETCH) $(TARGET)
# Main build target # Main build target
$(TARGET): $(SHADER_HEADERS) $(C_SOURCES) $(SHELL_FILE) $(TARGET): $(SHADER_HEADERS) $(C_SOURCES) $(IMGUI_SOURCES) $(SHELL_FILE)
$(CC) $(C_SOURCES) \ $(CC) $(C_SOURCES) $(IMGUI_SOURCES) \
-o $(TARGET) \ -o $(TARGET) \
$(EMCC_FLAGS) \ $(EMCC_FLAGS) \
-O3 \ -O3 \
$(INCLUDES) \ -I$(LIB_DIR)/sokol \
-I$(LIB_DIR)/imgui \
-I$(LIB_DIR)/imgui/imgui \
-I$(LIB_DIR)/util \
--shell-file=$(SHELL_FILE) --shell-file=$(SHELL_FILE)
# Pattern rule for generating shader headers # Shader header generation
$(GENERATED_DIR)/%.h: $(SHADER_DIR)/% | $(GENERATED_DIR) $(GENERATED_DIR)/%.h: $(SHADER_DIR)/%.wgsl | $(GENERATED_DIR)
@echo "Generating header for $<" @echo "Generating header for $<"
$(XXD) -i $< $@ $(XXD) -i $< $@
@@ -54,12 +58,15 @@ $(GENERATED_DIR)/%.h: $(SHADER_DIR)/% | $(GENERATED_DIR)
$(GENERATED_DIR): $(GENERATED_DIR):
mkdir -p $(GENERATED_DIR) mkdir -p $(GENERATED_DIR)
debug: $(SHADER_HEADERS) $(C_SOURCES) $(SHELL_FILE) debug: $(FETCH) $(SHADER_HEADERS) $(C_SOURCES) $(IMGUI_SOURCES) $(SHELL_FILE)
$(CC) $(C_SOURCES) \ $(CC) $(C_SOURCES) $(IMGUI_SOURCES) \
-o $(TARGET) \ -o $(TARGET) \
$(EMCC_FLAGS) \ $(EMCC_FLAGS) \
-g -gsource-map=inline \ -g -gsource-map=inline \
$(INCLUDES) \ -I$(LIB_DIR)/sokol \
-I$(LIB_DIR)/imgui \
-I$(LIB_DIR)/imgui/imgui \
-I$(LIB_DIR)/util \
--shell-file=$(SHELL_FILE) --shell-file=$(SHELL_FILE)
# Clean build artifacts # Clean build artifacts
@@ -79,4 +86,4 @@ list-shaders:
@echo "$(SHADER_HEADERS)" @echo "$(SHADER_HEADERS)"
# Phony targets # Phony targets
.PHONY: all clean rebuild list-shaders .PHONY: all clean rebuild list-shaders $(FETCH)

View File

@@ -1,29 +1,26 @@
#ifndef API_DEFINITION #ifndef API_DEFINITION
#define API_DEFINITION #define API_DEFINITION
#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS
#define SOKOL_IMPL #define SOKOL_IMPL
#define SOKOL_WGPU #define SOKOL_WGPU
#define SOKOL_IMGUI_IMPL
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_DEFAULT_FONT
#define NK_IMPLEMENTATION
#include "sokol_gfx.h" #include "sokol_gfx.h"
#include "sokol_app.h" #include "sokol_app.h"
#include "sokol_glue.h" #include "sokol_glue.h"
#include "sokol_log.h" #include "sokol_log.h"
#include "nuklear.h" #include "cimgui.h"
#include "util/sokol_memtrack.h" #include "sokol_imgui.h"
#include "util/sokol_nuklear.h" #include "sokol_memtrack.h"
#include "math.h" #include "math.h"
#include "rand.h" #include "rand.h"
#include "util.h" #include "util.h"
#include "sprite.h" #include "sprite.h"
#include "generated/sprite.wgsl.h" #include "generated/sprite.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>

View File

@@ -140,6 +140,6 @@ unsigned char src_shaders_sprite_wgsl[] = {
0x6d, 0x70, 0x2c, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x75, 0x76, 0x6d, 0x70, 0x2c, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x75, 0x76,
0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65,
0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a 0x0d, 0x0a, 0x7d
}; };
unsigned int src_shaders_sprite_wgsl_len = 1697; unsigned int src_shaders_sprite_wgsl_len = 1695;

View File

@@ -55,23 +55,6 @@ static void frame(void* _userdata)
{ {
userdata_t* userdata = (userdata_t*) _userdata; userdata_t* userdata = (userdata_t*) _userdata;
struct nk_context *nk_ctx = snk_new_frame();
if(nk_begin(nk_ctx, "Menu", nk_rect(20, 20, 600, 200), NK_WINDOW_BORDER | NK_WINDOW_BACKGROUND | NK_WINDOW_MOVABLE | NK_WINDOW_CLOSABLE))
{
nk_layout_row_dynamic(nk_ctx, 0, 1);
nk_label(nk_ctx, format("Frame duration: %.4fms", sapp_frame_duration() * 1000.0), NK_TEXT_ALIGN_CENTERED);
nk_layout_row_dynamic(nk_ctx, 0, 1);
nk_label(nk_ctx, format("Zoom: %.3f", userdata->zoom), NK_TEXT_ALIGN_CENTERED);
nk_layout_row_dynamic(nk_ctx, 0, 1);
nk_label(nk_ctx, format("Pan: %.3f/%.3f", userdata->pan.x, userdata->pan.y), NK_TEXT_ALIGN_CENTERED);
}
nk_end(nk_ctx);
sg_begin_pass(&(sg_pass){ sg_begin_pass(&(sg_pass){
.action = userdata->renderer.clear_pass, .action = userdata->renderer.clear_pass,
.swapchain = sglue_swapchain(), .swapchain = sglue_swapchain(),
@@ -89,7 +72,8 @@ static void frame(void* _userdata)
if(texture->dirty) if(texture->dirty)
{ {
const sg_range range = vector_range(&texture->sprites); const sg_range range = vector_range(&texture->sprites);
sg_update_buffer(texture->binding.storage_buffers[0], &range); sg_buffer buf = sg_query_view_buffer(texture->binding.views[1]);
sg_update_buffer(buf, &range);
texture->dirty = false; texture->dirty = false;
} }
@@ -100,8 +84,6 @@ static void frame(void* _userdata)
} }
} }
snk_render(userdata->width, userdata->height);
sg_end_pass(); sg_end_pass();
sg_commit(); sg_commit();
} }
@@ -128,6 +110,12 @@ static void init(void* _userdata)
{2.0f, -2.0f}, // top right {2.0f, -2.0f}, // top right
{-2.0f, -2.0f}, // top left {-2.0f, -2.0f}, // top left
}; };
const vec2f uv[4] = {
{0.0f, 1.0f}, // bottom left
{1.0f, 1.0f}, // bottom right
{1.0f, 0.0f}, // top right
{0.0f, 0.0f}, // top left
};
const uint16_t indices[] = { const uint16_t indices[] = {
0, 1, 2, 0, 2, 3, 0, 1, 2, 0, 2, 3,
}; };
@@ -146,14 +134,23 @@ static void init(void* _userdata)
.source = (const char*) src_shaders_sprite_wgsl, .source = (const char*) src_shaders_sprite_wgsl,
.entry = "fs_main", .entry = "fs_main",
}, },
.images = { .views = {
[0] = { [0] = {
.texture = {
.stage = SG_SHADERSTAGE_FRAGMENT, .stage = SG_SHADERSTAGE_FRAGMENT,
.image_type = SG_IMAGETYPE_2D, .image_type = SG_IMAGETYPE_2D,
.wgsl_group1_binding_n = 0, .wgsl_group1_binding_n = 0,
.sample_type = SG_IMAGESAMPLETYPE_FLOAT, .sample_type = SG_IMAGESAMPLETYPE_FLOAT,
}, },
}, },
[1] = {
.storage_buffer = {
.stage = SG_SHADERSTAGE_VERTEX,
.readonly = true,
.wgsl_group1_binding_n = 2,
},
},
},
.samplers = { .samplers = {
[0] = { [0] = {
.stage = SG_SHADERSTAGE_FRAGMENT, .stage = SG_SHADERSTAGE_FRAGMENT,
@@ -161,20 +158,13 @@ static void init(void* _userdata)
.wgsl_group1_binding_n = 1, .wgsl_group1_binding_n = 1,
} }
}, },
.image_sampler_pairs = { .texture_sampler_pairs = {
[0] = { [0] = {
.stage = SG_SHADERSTAGE_FRAGMENT, .stage = SG_SHADERSTAGE_FRAGMENT,
.image_slot = 0, .view_slot = 0,
.sampler_slot = 0, .sampler_slot = 0,
} }
}, },
.storage_buffers = {
[0] = {
.wgsl_group1_binding_n = 2,
.stage = SG_SHADERSTAGE_VERTEX,
.readonly = true,
},
},
.uniform_blocks = { .uniform_blocks = {
[0] = { [0] = {
.size = sizeof(uniform_t), .size = sizeof(uniform_t),
@@ -190,13 +180,14 @@ static void init(void* _userdata)
userdata->renderer = (renderer_t) { userdata->renderer = (renderer_t) {
.clear_pass = (sg_pass_action) { .clear_pass = (sg_pass_action) {
.colors[0] = { .clear_value = { 1.0f, 1.0f, 1.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR } .colors[0] = { .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR }
}, },
.pipeline = sg_make_pipeline(&(sg_pipeline_desc) { .pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
.shader = sprite_shader, .shader = sprite_shader,
.index_type = SG_INDEXTYPE_UINT16, .index_type = SG_INDEXTYPE_UINT16,
.layout.attrs = { .layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, [0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2,
}, },
.label = "Sprite pipeline", .label = "Sprite pipeline",
}), }),
@@ -213,15 +204,6 @@ static void init(void* _userdata)
gs_init(&userdata->manager); gs_init(&userdata->manager);
compute_mvp(userdata); compute_mvp(userdata);
snk_setup(&(snk_desc_t) {
.enable_set_mouse_cursor = true,
.logger.func = slog_func,
.allocator = {
.alloc_fn = smemtrack_alloc,
.free_fn = smemtrack_free,
},
});
} }
static void cleanup(void* _userdata) static void cleanup(void* _userdata)
@@ -232,7 +214,6 @@ static void cleanup(void* _userdata)
FREE(userdata); FREE(userdata);
snk_shutdown();
sg_shutdown(); sg_shutdown();
} }
@@ -240,9 +221,6 @@ static void event(const sapp_event* event, void* _userdata)
{ {
userdata_t* userdata = (userdata_t*) _userdata; userdata_t* userdata = (userdata_t*) _userdata;
if(snk_handle_event(event))
return;
switch(event->type) switch(event->type)
{ {
case SAPP_EVENTTYPE_FILES_DROPPED: case SAPP_EVENTTYPE_FILES_DROPPED:
@@ -264,9 +242,7 @@ static void event(const sapp_event* event, void* _userdata)
.buffer = (sapp_range) { .ptr = buffer, .size = size }, .buffer = (sapp_range) { .ptr = buffer, .size = size },
.dropped_file_index = i, .dropped_file_index = i,
.callback = gs_import_file, .callback = gs_import_file,
.user_data = &(import_t) { .user_data = userdata
.userdata = userdata,
},
}); });
} }

View File

@@ -23,24 +23,19 @@ typedef struct manager_t {
vector_t textures; vector_t textures;
} manager_t; } manager_t;
typedef struct import_t {
userdata_t *userdata;
} import_t;
typedef struct loader_t { typedef struct loader_t {
uint8_t buffer[MAX_FILE_SIZE]; uint8_t buffer[MAX_FILE_SIZE];
} loader_t; } loader_t;
typedef void (*ForEachTexture)(texture_t *texture, userdata_t *userdata); typedef void (*ForEachTexture)(texture_t *texture, void *userdata);
typedef void (*ForEachSprite)(sprite_t *sprite, userdata_t *userdata); typedef void (*ForEachSprite)(sprite_t *sprite, void *userdata);
void gs_init(manager_t *manager); void gs_init(manager_t *manager);
void gs_shutdown(manager_t *manager); void gs_shutdown(manager_t *manager);
void gs_import_file(const sapp_html5_fetch_response *response); void gs_import_file(const sapp_html5_fetch_response *response);
void gs_each_textures(manager_t *manager, ForEachTexture callback, userdata_t *userdata); void gs_each_textures(manager_t *manager, ForEachTexture callback, void *userdata);
void gs_each_sprites(manager_t *manager, ForEachSprite callback, userdata_t *userdata); void gs_each_sprites(manager_t *manager, ForEachSprite callback, void *userdata);
void gs_init(manager_t *manager) void gs_init(manager_t *manager)
{ {
@@ -51,7 +46,7 @@ void gs_shutdown(manager_t *manager)
vector_free(&manager->textures); vector_free(&manager->textures);
} }
void fs_import_file(const sapp_html5_fetch_response *response) void gs_import_file(const sapp_html5_fetch_response *response)
{ {
const char* filename = sapp_get_dropped_file_path(response->file_index); const char* filename = sapp_get_dropped_file_path(response->file_index);
@@ -64,12 +59,12 @@ void fs_import_file(const sapp_html5_fetch_response *response)
//Toast error //Toast error
} }
} }
void gs_each_textures(manager_t *manager, ForEachTexture callback, userdata_t *userdata) void gs_each_textures(manager_t *manager, ForEachTexture callback, void *userdata)
{ {
for(uint32_t i = 0; i < manager->textures.size; i++) for(uint32_t i = 0; i < manager->textures.size; i++)
callback((texture_t*) manager->textures.data[i], userdata); callback((texture_t*) manager->textures.data[i], userdata);
} }
void gs_each_sprites(manager_t *manager, ForEachSprite callback, userdata_t *userdata) void gs_each_sprites(manager_t *manager, ForEachSprite callback, void *userdata)
{ {
for(uint32_t i = 0; i < manager->textures.size; i++) for(uint32_t i = 0; i < manager->textures.size; i++)
{ {