main.zig
1 const std = @import("std"); 2 const cimgui = @import("cimgui"); 3 const sdl = @import("sdl"); 4 const math = std.math; 5 const build_config = @import("build_config.zig"); 6 7 const rendering = @import("rendering/renderer.zig"); 8 9 const WIN_WIDTH = 1280; 10 const WIN_HEIGHT = 720; 11 const SPRITE_W = 32; 12 const SPRITE_H = 32; 13 const MOVE_SPEED = 50.0; 14 const SPRITES_PER_CLICK = 100; 15 16 pub const Sprite = struct { 17 rects: std.ArrayList(rendering.Rect), 18 vx: std.ArrayList(f32), 19 vy: std.ArrayList(f32), 20 21 pub fn init(allocator: std.mem.Allocator) !Sprite { 22 return Sprite{ 23 .rects = std.ArrayList(rendering.Rect).init(allocator), 24 .vx = std.ArrayList(f32).init(allocator), 25 .vy = std.ArrayList(f32).init(allocator), 26 }; 27 } 28 29 pub fn deinit(self: *Sprite) void { 30 self.rects.deinit(); 31 self.vx.deinit(); 32 self.vy.deinit(); 33 } 34 }; 35 36 pub fn main() !void { 37 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 38 const allocator = gpa.allocator(); 39 defer _ = gpa.deinit(); 40 const perf_file = try std.fs.cwd().createFile("perf.csv", .{ .truncate = true }); 41 defer perf_file.close(); 42 43 try perf_file.writer().print("sprite_count,fps\n", .{}); 44 45 try sdl.errify(sdl.c.SDL_Init(sdl.c.SDL_INIT_VIDEO)); 46 defer sdl.c.SDL_Quit(); 47 48 const backend = build_config.renderer; 49 50 var window_flags = sdl.c.SDL_WINDOW_RESIZABLE | sdl.c.SDL_WINDOW_HIDDEN; 51 if (backend == .OpenGL) { 52 // Example: Add OpenGL flag if needed by that backend's init 53 window_flags |= sdl.c.SDL_WINDOW_OPENGL; 54 try sdl.errify(sdl.c.SDL_GL_SetAttribute(sdl.c.SDL_GL_DOUBLEBUFFER, 1)); 55 try sdl.errify(sdl.c.SDL_GL_SetAttribute(sdl.c.SDL_GL_DEPTH_SIZE, 24)); 56 try sdl.errify(sdl.c.SDL_GL_SetAttribute(sdl.c.SDL_GL_STENCIL_SIZE, 8)); 57 std.log.warn("OpenGL backend selected - Ensure necessary SDL_GL attributes are set BEFORE window creation if required.", .{}); 58 } 59 60 const window = sdl.c.SDL_CreateWindow( 61 "Ember Engine", 62 WIN_WIDTH, 63 WIN_HEIGHT, 64 window_flags, 65 ); 66 if (window == null) { 67 std.log.err("SDL_CreateWindow failed: {s}", .{sdl.c.SDL_GetError()}); 68 return error.SDLWindowCreationFailed; 69 } 70 defer sdl.c.SDL_DestroyWindow(window); 71 72 try sdl.errify(sdl.c.SDL_SetWindowPosition( 73 window, 74 sdl.c.SDL_WINDOWPOS_CENTERED, 75 sdl.c.SDL_WINDOWPOS_CENTERED, 76 )); 77 78 // Setup Dear ImGui context 79 const ig_context = cimgui.c.igCreateContext(null); // Store the returned context pointer 80 if (ig_context == null) { 81 // Check if context creation failed 82 std.log.err("Failed to create ImGui context!", .{}); 83 sdl.c.SDL_DestroyWindow(window); 84 sdl.c.SDL_Quit(); 85 return error.ImGuiContextCreationFailed; 86 } 87 defer cimgui.c.igDestroyContext(ig_context); 88 89 const io = cimgui.c.igGetIO(); 90 io.*.ConfigFlags |= cimgui.c.ImGuiConfigFlags_NavEnableKeyboard; 91 io.*.ConfigFlags |= cimgui.c.ImGuiConfigFlags_NavEnableGamepad; 92 io.*.ConfigFlags |= cimgui.c.ImGuiConfigFlags_DockingEnable; 93 io.*.ConfigFlags |= cimgui.c.ImGuiConfigFlags_ViewportsEnable; 94 95 cimgui.c.igStyleColorsDark(null); 96 97 defer cimgui.ImGui_ImplSDL3_Shutdown(); 98 // Create renderer 99 const renderer_ctx = rendering.init(allocator, window.?) catch { 100 cimgui.c.igDestroyContext(ig_context); 101 sdl.c.SDL_DestroyWindow(window); 102 sdl.c.SDL_Quit(); 103 return error.InitializationFailed; 104 }; 105 defer rendering.deinit(allocator, renderer_ctx); 106 try sdl.errify(sdl.c.SDL_ShowWindow(window)); 107 try rendering.setVSync(renderer_ctx, true); 108 109 try rendering.initImGuiBackend(renderer_ctx); 110 defer rendering.deinitImGuiBackend(); 111 112 // State 113 const clear_color = cimgui.c.ImVec4{ .x = 0.45, .y = 0.55, .z = 0.60, .w = 1.00 }; 114 115 const sprite_path = "assets/amogus.png"; 116 const stress_texture = try rendering.loadTexture(renderer_ctx, sprite_path); 117 defer rendering.destroyTexture(stress_texture); 118 119 var prng = std.Random.DefaultPrng.init(blk: { 120 var seed: u64 = 49; 121 try std.posix.getrandom(std.mem.asBytes(&seed)); 122 break :blk seed; 123 }); 124 const rand = prng.random(); 125 126 var sprites = try allocator.create(Sprite); 127 defer sprites.deinit(); 128 defer allocator.destroy(sprites); 129 sprites.* = try Sprite.init(allocator); 130 131 var done = false; 132 var left_down: bool = false; 133 var event: sdl.c.SDL_Event = undefined; 134 var win_w: c_int = 0; 135 var win_h: c_int = 0; 136 var last_ticks = sdl.c.SDL_GetTicks(); 137 // var last_log_time: u64 = 0; 138 139 try sdl.errify(sdl.c.SDL_GetWindowSize(window, &win_w, &win_h)); 140 141 std.log.debug("Window size: {}x{}", .{ win_w, win_h }); 142 143 while (!done) { 144 const now = sdl.c.SDL_GetTicks(); 145 const dt: f32 = @as(f32, @floatFromInt(now - last_ticks)) / 1000.0; 146 last_ticks = now; 147 148 while (sdl.c.SDL_PollEvent(&event)) { 149 _ = cimgui.ImGui_ImplSDL3_ProcessEvent(&event); 150 if (io.*.WantCaptureMouse) { 151 continue; 152 } 153 154 switch (event.type) { 155 sdl.c.SDL_EVENT_QUIT => done = true, 156 sdl.c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => { 157 if (event.window.windowID == sdl.c.SDL_GetWindowID(window)) { 158 done = true; 159 } 160 }, 161 sdl.c.SDL_EVENT_MOUSE_BUTTON_DOWN => if (event.button.button == sdl.c.SDL_BUTTON_LEFT) { 162 left_down = true; 163 for (0..SPRITES_PER_CLICK) |_| { 164 try spawnSprite(sprites, rand.float(f32), event.button.x, event.button.y); 165 } 166 // try sprites.append(sdl.c.SDL_FRect{ 167 // .x = event.button.x, 168 // .y = event.button.y, 169 // .w = SPRITE_W, 170 // .h = SPRITE_H, 171 // }); 172 }, 173 sdl.c.SDL_EVENT_MOUSE_BUTTON_UP => if (event.button.button == sdl.c.SDL_BUTTON_LEFT) { 174 left_down = false; 175 }, 176 sdl.c.SDL_EVENT_MOUSE_MOTION => if (left_down) { 177 // try sprites.append(sdl.c.SDL_FRect{ 178 // .x = event.motion.x, 179 // .y = event.motion.y, 180 // .w = SPRITE_W, 181 // .h = SPRITE_H, 182 // }); 183 for (0..SPRITES_PER_CLICK) |_| { 184 try spawnSprite(sprites, rand.float(f32), event.motion.x, event.motion.y); 185 } 186 }, 187 else => {}, 188 } 189 } 190 191 for (0..sprites.rects.items.len) |i| { 192 sprites.rects.items[i].x += sprites.vx.items[i] * dt; 193 sprites.rects.items[i].y += sprites.vy.items[i] * dt; 194 } 195 196 // Minimized? Sleep and skip frame 197 if ((sdl.c.SDL_GetWindowFlags(window) & sdl.c.SDL_WINDOW_MINIMIZED) != 0) { 198 sdl.c.SDL_Delay(10); 199 continue; 200 } 201 202 cimgui.ImGui_ImplSDL3_NewFrame(); 203 rendering.newImGuiFrame(); 204 cimgui.c.igNewFrame(); 205 206 { 207 if (cimgui.c.igBegin("Ember Debug Console", null, 0)) { 208 cimgui.c.igText("Sprites: %d", @as(c_int, @intCast(sprites.rects.items.len))); 209 cimgui.c.igText( 210 "Perf: %.3f ms/frame (%.1f FPS)", 211 1000.0 / io.*.Framerate, 212 io.*.Framerate, 213 ); 214 } 215 // if (now - last_log_time >= 500) { // log every 500ms 216 // try perf_file.writer().print("{d},{d:.2}\n", .{ 217 // sprites.rects.items.len, 218 // io.*.Framerate, 219 // }); 220 // last_log_time = now; 221 // } 222 cimgui.c.igEnd(); 223 } 224 225 // Rendering 226 cimgui.c.igRender(); 227 const draw_data = cimgui.igGetDrawData(); 228 // SDL_RenderSetScale(renderer, io.*.DisplayFramebufferScale.x, io.*.DisplayFramebufferScale.y); 229 try rendering.beginFrame(renderer_ctx, clear_color); 230 231 // old 232 // for (sprites.items) |s| { 233 // try rendering.drawTexture(renderer_ctx, stress_texture, null, s.rect); 234 // } 235 236 try rendering.drawTextureBatch(renderer_ctx, stress_texture, null, sprites.rects.items); 237 238 if (draw_data) |data| { // Check draw_data is not null 239 if (data.Valid and data.CmdListsCount > 0) { 240 rendering.renderImGui(renderer_ctx, data); 241 } 242 } else { 243 std.log.warn("ImGui draw data was null!", .{}); 244 } 245 246 // This does nothing if the backend doesn't support it. 247 // So far sdlrenderer3 does not support multi-viewports. 248 249 try rendering.endFrame(renderer_ctx); 250 } 251 } 252 253 fn spawnSprite(sprites: *Sprite, r: f32, mx: f32, my: f32) !void { 254 const angle = r * math.pi * 2.0; 255 const vx = math.cos(angle) * MOVE_SPEED; 256 const vy = math.sin(angle) * MOVE_SPEED; 257 try sprites.rects.append(rendering.Rect{ 258 .x = mx - (@as(f32, @floatFromInt(SPRITE_W / 2))), 259 .y = my - (@as(f32, @floatFromInt(SPRITE_H / 2))), 260 .w = SPRITE_W, 261 .h = SPRITE_H, 262 }); 263 try sprites.vx.append(vx); 264 try sprites.vy.append(vy); 265 }