/ src / main.zig
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  }