diff --git a/README.md b/README.md index 338e11a..a65f21d 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ The platform and renderer layers are C-style APIs with opaque handles. `platform This project is written in C-style C++. We use `.cpp` files and a small subset of C++ features (default struct values, function overloading, namespaces where useful) but avoid the rest. No classes, no inheritance, limit templates, no exceptions, no RAII, avoid STL containers or algorithms. Data is plain structs. Functions operate on those structs, or pointers to them. +Opening braces go on the same line as the function signature (K&R style), not on the next line. + Memory is managed with arena allocators where possible rather than individual `malloc`/`free` or `new`/`delete` calls. Arenas make allocation fast, avoid fragmentation, and simplify cleanup. ## Dependencies diff --git a/nob.c b/nob.c index db9f051..b54209d 100644 --- a/nob.c +++ b/nob.c @@ -5,6 +5,7 @@ #include "nob.h" #define BUILD_DIR "build" +#define IMGUI_LIB BUILD_DIR "/imgui.lib" static const char *src_files[] = { "src/main.cpp", @@ -33,12 +34,72 @@ static const char *link_libs[] = { "dwmapi.lib", }; -int main(int argc, char **argv) -{ +static bool build_imgui_lib(void) { + int need = nob_needs_rebuild(IMGUI_LIB, imgui_files, NOB_ARRAY_LEN(imgui_files)); + if (need < 0) return false; + if (!need) { + nob_log(NOB_INFO, "%s is up to date", IMGUI_LIB); + return true; + } + + nob_log(NOB_INFO, "Building %s...", IMGUI_LIB); + + // Compile each imgui source to .obj + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cl.exe"); + nob_cmd_append(&cmd, "/nologo", "/std:c++17", "/EHsc", "/W3", "/c"); + nob_cmd_append(&cmd, "/Ivendor/imgui", "/Ivendor/imgui/backends"); + +#ifdef _DEBUG + nob_cmd_append(&cmd, "/Zi", "/Od", "/D_DEBUG"); +#else + nob_cmd_append(&cmd, "/O2", "/DNDEBUG"); +#endif + + nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", BUILD_DIR)); + nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/", BUILD_DIR)); + + for (size_t i = 0; i < NOB_ARRAY_LEN(imgui_files); i++) + nob_cmd_append(&cmd, imgui_files[i]); + + if (!nob_cmd_run(&cmd)) return false; + + // Archive .obj files into a .lib + cmd.count = 0; + nob_cmd_append(&cmd, "lib.exe", "/nologo"); + nob_cmd_append(&cmd, nob_temp_sprintf("/OUT:%s", IMGUI_LIB)); + + for (size_t i = 0; i < NOB_ARRAY_LEN(imgui_files); i++) { + const char *base = nob_temp_sprintf("%s", imgui_files[i]); + const char *slash = strrchr(base, '/'); + const char *name = slash ? slash + 1 : base; + size_t len = strlen(name); + char *obj = nob_temp_sprintf("%s/%.*s.obj", BUILD_DIR, (int)(len - 4), name); + nob_cmd_append(&cmd, obj); + } + + if (!nob_cmd_run(&cmd)) return false; + + // Clean up imgui .obj files + for (size_t i = 0; i < NOB_ARRAY_LEN(imgui_files); i++) { + const char *slash = strrchr(imgui_files[i], '/'); + const char *name = slash ? slash + 1 : imgui_files[i]; + size_t len = strlen(name); + nob_delete_file(nob_temp_sprintf("%s/%.*s.obj", BUILD_DIR, (int)(len - 4), name)); + } + + return true; +} + +int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); if (!nob_mkdir_if_not_exists(BUILD_DIR)) return 1; + // Build vendor libs (unless already built) + if (!build_imgui_lib()) return 1; + + // Compile and link Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cl.exe"); @@ -48,25 +109,34 @@ int main(int argc, char **argv) #ifdef _DEBUG nob_cmd_append(&cmd, "/Zi", "/Od", "/D_DEBUG"); #else - nob_cmd_append(&cmd, "/O2", "/DNDEBUG"); + nob_cmd_append(&cmd, "/Zi", "/O2", "/DNDEBUG"); #endif nob_cmd_append(&cmd, nob_temp_sprintf("/Fe:%s/autosample.exe", BUILD_DIR)); nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", BUILD_DIR)); - nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/", BUILD_DIR)); + nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/autosample.pdb", BUILD_DIR)); for (size_t i = 0; i < NOB_ARRAY_LEN(src_files); i++) nob_cmd_append(&cmd, src_files[i]); - for (size_t i = 0; i < NOB_ARRAY_LEN(imgui_files); i++) - nob_cmd_append(&cmd, imgui_files[i]); nob_cmd_append(&cmd, "/link"); nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE"); + nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", BUILD_DIR)); + nob_cmd_append(&cmd, "/DEBUG"); + nob_cmd_append(&cmd, IMGUI_LIB); for (size_t i = 0; i < NOB_ARRAY_LEN(link_libs); i++) nob_cmd_append(&cmd, link_libs[i]); if (!nob_cmd_run(&cmd)) return 1; + // Clean up .obj files + for (size_t i = 0; i < NOB_ARRAY_LEN(src_files); i++) { + const char *slash = strrchr(src_files[i], '/'); + const char *name = slash ? slash + 1 : src_files[i]; + size_t len = strlen(name); + nob_delete_file(nob_temp_sprintf("%s/%.*s.obj", BUILD_DIR, (int)(len - 4), name)); + } + nob_log(NOB_INFO, "Build complete: %s/autosample.exe", BUILD_DIR); return 0; } diff --git a/src/main.cpp b/src/main.cpp index 9083830..e61f88e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,8 +18,7 @@ enum MenuCmd { MENU_VIEW_DEMO, }; -static void setup_menus(PlatformWindow *window) -{ +static void setup_menus(PlatformWindow *window) { PlatformMenuItem file_items[] = { { "New", MENU_FILE_NEW }, { "Open...", MENU_FILE_OPEN }, @@ -51,8 +50,7 @@ static void setup_menus(PlatformWindow *window) platform_set_menu(window, menus, sizeof(menus) / sizeof(menus[0])); } -static void setup_theme() -{ +static void setup_theme() { ImGuiIO &io = ImGui::GetIO(); // Load Segoe UI from Windows system fonts @@ -155,8 +153,7 @@ static void setup_theme() c[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.15f, 1.00f); } -static void build_default_layout(ImGuiID dockspace_id) -{ +static void build_default_layout(ImGuiID dockspace_id) { ImGui::DockBuilderRemoveNode(dockspace_id); ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); @@ -174,8 +171,7 @@ static void build_default_layout(ImGuiID dockspace_id) ImGui::DockBuilderFinish(dockspace_id); } -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { (void)argc; (void)argv; diff --git a/src/platform/platform_win32.cpp b/src/platform/platform_win32.cpp index 757e264..168369e 100644 --- a/src/platform/platform_win32.cpp +++ b/src/platform/platform_win32.cpp @@ -20,8 +20,7 @@ struct PlatformWindow { static PlatformWindow *g_current_window = nullptr; -static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -{ +static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam)) return true; @@ -51,8 +50,7 @@ static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM return DefWindowProcW(hwnd, msg, wparam, lparam); } -PlatformWindow *platform_create_window(PlatformWindowDesc *desc) -{ +PlatformWindow *platform_create_window(PlatformWindowDesc *desc) { WNDCLASSEXW wc = {}; wc.cbSize = sizeof(wc); wc.style = CS_CLASSDC; @@ -100,8 +98,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) return window; } -void platform_destroy_window(PlatformWindow *window) -{ +void platform_destroy_window(PlatformWindow *window) { if (!window) return; if (window->hwnd) { @@ -115,8 +112,7 @@ void platform_destroy_window(PlatformWindow *window) delete window; } -bool platform_poll_events(PlatformWindow *window) -{ +bool platform_poll_events(PlatformWindow *window) { MSG msg; while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); @@ -128,19 +124,16 @@ bool platform_poll_events(PlatformWindow *window) return !window->should_close; } -void platform_get_size(PlatformWindow *window, int32_t *w, int32_t *h) -{ +void platform_get_size(PlatformWindow *window, int32_t *w, int32_t *h) { if (w) *w = window->width; if (h) *h = window->height; } -void *platform_get_native_handle(PlatformWindow *window) -{ +void *platform_get_native_handle(PlatformWindow *window) { return (void *)window->hwnd; } -void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu_count) -{ +void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu_count) { HMENU menu_bar = CreateMenu(); for (int32_t i = 0; i < menu_count; i++) { @@ -169,8 +162,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu SetMenu(window->hwnd, menu_bar); } -int32_t platform_poll_menu_command(PlatformWindow *window) -{ +int32_t platform_poll_menu_command(PlatformWindow *window) { int32_t cmd = window->pending_menu_cmd; window->pending_menu_cmd = 0; return cmd; diff --git a/src/renderer/renderer_dx12.cpp b/src/renderer/renderer_dx12.cpp index 42e2455..a28be6a 100644 --- a/src/renderer/renderer_dx12.cpp +++ b/src/renderer/renderer_dx12.cpp @@ -34,8 +34,7 @@ struct SrvHeapAllocator { int free_count; }; -static void srv_alloc_init(SrvHeapAllocator *a, ID3D12Device *device, ID3D12DescriptorHeap *heap) -{ +static void srv_alloc_init(SrvHeapAllocator *a, ID3D12Device *device, ID3D12DescriptorHeap *heap) { a->heap = heap; a->heap_type = heap->GetDesc().Type; a->start_cpu = heap->GetCPUDescriptorHandleForHeapStart(); @@ -48,15 +47,13 @@ static void srv_alloc_init(SrvHeapAllocator *a, ID3D12Device *device, ID3D12Desc a->free_indices[i] = a->free_count - 1 - i; } -static void srv_alloc_alloc(SrvHeapAllocator *a, D3D12_CPU_DESCRIPTOR_HANDLE *out_cpu, D3D12_GPU_DESCRIPTOR_HANDLE *out_gpu) -{ +static void srv_alloc_alloc(SrvHeapAllocator *a, D3D12_CPU_DESCRIPTOR_HANDLE *out_cpu, D3D12_GPU_DESCRIPTOR_HANDLE *out_gpu) { int idx = a->free_indices[--a->free_count]; out_cpu->ptr = a->start_cpu.ptr + (idx * a->increment); out_gpu->ptr = a->start_gpu.ptr + (idx * a->increment); } -static void srv_alloc_free(SrvHeapAllocator *a, D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE gpu) -{ +static void srv_alloc_free(SrvHeapAllocator *a, D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE gpu) { (void)gpu; int idx = (int)((cpu.ptr - a->start_cpu.ptr) / a->increment); a->free_indices[a->free_count++] = idx; @@ -90,8 +87,7 @@ struct Renderer { UINT64 fence_last_signaled; }; -static bool create_device(Renderer *r) -{ +static bool create_device(Renderer *r) { #ifdef DX12_ENABLE_DEBUG_LAYER ID3D12Debug *debug = nullptr; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug)))) { @@ -117,8 +113,7 @@ static bool create_device(Renderer *r) return true; } -static bool create_command_queue(Renderer *r) -{ +static bool create_command_queue(Renderer *r) { D3D12_COMMAND_QUEUE_DESC desc = {}; desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; @@ -126,8 +121,7 @@ static bool create_command_queue(Renderer *r) return r->device->CreateCommandQueue(&desc, IID_PPV_ARGS(&r->command_queue)) == S_OK; } -static bool create_descriptor_heaps(Renderer *r) -{ +static bool create_descriptor_heaps(Renderer *r) { { D3D12_DESCRIPTOR_HEAP_DESC desc = {}; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; @@ -159,8 +153,7 @@ static bool create_descriptor_heaps(Renderer *r) return true; } -static bool create_frame_resources(Renderer *r) -{ +static bool create_frame_resources(Renderer *r) { for (int i = 0; i < r->frame_count; i++) { if (r->device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&r->frames[i].command_allocator)) != S_OK) @@ -181,8 +174,7 @@ static bool create_frame_resources(Renderer *r) return r->fence_event != nullptr; } -static bool create_swap_chain(Renderer *r) -{ +static bool create_swap_chain(Renderer *r) { DXGI_SWAP_CHAIN_DESC1 sd = {}; sd.BufferCount = NUM_BACK_BUFFERS; sd.Width = 0; @@ -230,8 +222,7 @@ static bool create_swap_chain(Renderer *r) return true; } -static void create_render_targets(Renderer *r) -{ +static void create_render_targets(Renderer *r) { for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) { ID3D12Resource *back_buffer = nullptr; r->swap_chain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); @@ -240,8 +231,7 @@ static void create_render_targets(Renderer *r) } } -static void cleanup_render_targets(Renderer *r) -{ +static void cleanup_render_targets(Renderer *r) { for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) { if (r->render_targets[i]) { r->render_targets[i]->Release(); @@ -250,15 +240,13 @@ static void cleanup_render_targets(Renderer *r) } } -static void wait_for_pending(Renderer *r) -{ +static void wait_for_pending(Renderer *r) { r->command_queue->Signal(r->fence, ++r->fence_last_signaled); r->fence->SetEventOnCompletion(r->fence_last_signaled, r->fence_event); WaitForSingleObject(r->fence_event, INFINITE); } -static FrameContext *wait_for_next_frame(Renderer *r) -{ +static FrameContext *wait_for_next_frame(Renderer *r) { FrameContext *fc = &r->frames[r->frame_index % r->frame_count]; if (r->fence->GetCompletedValue() < fc->fence_value) { r->fence->SetEventOnCompletion(fc->fence_value, r->fence_event); @@ -270,8 +258,7 @@ static FrameContext *wait_for_next_frame(Renderer *r) return fc; } -static void init_imgui(Renderer *r) -{ +static void init_imgui(Renderer *r) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); @@ -301,8 +288,7 @@ static void init_imgui(Renderer *r) ImGui_ImplDX12_Init(&init_info); } -Renderer *renderer_create(RendererDesc *desc) -{ +Renderer *renderer_create(RendererDesc *desc) { Renderer *r = new Renderer(); memset(r, 0, sizeof(*r)); @@ -327,8 +313,7 @@ fail: return nullptr; } -void renderer_destroy(Renderer *r) -{ +void renderer_destroy(Renderer *r) { if (!r) return; wait_for_pending(r); @@ -362,8 +347,7 @@ void renderer_destroy(Renderer *r) delete r; } -bool renderer_begin_frame(Renderer *r) -{ +bool renderer_begin_frame(Renderer *r) { if ((r->swap_chain_occluded && r->swap_chain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) || IsIconic(r->hwnd)) { @@ -378,8 +362,7 @@ bool renderer_begin_frame(Renderer *r) return true; } -void renderer_end_frame(Renderer *r) -{ +void renderer_end_frame(Renderer *r) { ImGui::Render(); FrameContext *fc = wait_for_next_frame(r); @@ -418,8 +401,7 @@ void renderer_end_frame(Renderer *r) r->frame_index++; } -void renderer_resize(Renderer *r, int32_t width, int32_t height) -{ +void renderer_resize(Renderer *r, int32_t width, int32_t height) { if (width <= 0 || height <= 0) return; wait_for_pending(r);