From 7700000bd73ec769af1d84a4e2d7977d6a3c2c63 Mon Sep 17 00:00:00 2001 From: abux Date: Tue, 28 Jul 2026 23:30:07 +0100 Subject: [PATCH] Hello Triangle! --- README.md | 8 +- src/main.zig | 18 ++++ src/plugins/input/root.zig | 29 ++++++ src/plugins/input/system/input.zig | 17 +++ src/plugins/renderer/resource/renderer.zig | 73 +++++++++++++ src/plugins/renderer/root.zig | 17 ++- src/plugins/renderer/system/frame.zig | 39 +++++++ src/plugins/renderer/system/lifetime.zig | 20 ++++ src/plugins/renderer/system/present.zig | 114 +++++++++++++++++++++ src/plugins/window/resource/window.zig | 5 + src/plugins/window/root.zig | 77 +++++++++++++- 11 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 src/plugins/input/root.zig create mode 100644 src/plugins/input/system/input.zig create mode 100644 src/plugins/renderer/resource/renderer.zig create mode 100644 src/plugins/renderer/system/frame.zig create mode 100644 src/plugins/renderer/system/lifetime.zig create mode 100644 src/plugins/renderer/system/present.zig create mode 100644 src/plugins/window/resource/window.zig diff --git a/README.md b/README.md index 1fb3a9e..87a2acb 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,14 @@ paru -S --needed sdl3 wgpu-native ``` ## TODO + +### Core - [x] Platform - [x] Window - [x] Device - [x] Cache -- [ ] Renderer \ No newline at end of file +- [ ] Renderer + +### Engine +- [ ] ClassDB +- [ ] UI diff --git a/src/main.zig b/src/main.zig index c2c567f..624543d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,6 +18,7 @@ const platform = @import("plugins/platform/root.zig"); const device = @import("plugins/device/root.zig"); const window = @import("plugins/window/root.zig"); const cache = @import("plugins/cache/root.zig"); +const input = @import("plugins/input/root.zig"); const renderer = @import("plugins/renderer/root.zig"); /// ---------------------------------------------------- @@ -33,6 +34,7 @@ pub fn main(init: std.process.Init) !void { device.plugin, window.plugin, cache.plugin, + input.plugin, renderer.plugin, }); try app.plugins.build(&app); @@ -48,4 +50,20 @@ pub fn main(init: std.process.Init) !void { // --- LIFETIME --- try app.schedules.run(App.stage.init, &app); defer app.schedules.runReversed(App.stage.deinit, &app) catch unreachable; + + // --- MAIN LOOP --- + while (true) { + // --- # --- + if (app.events.consume(App.event.AppExit)) |_| break; + + // --- # --- + try app.schedules.runMany(&.{ + App.stage.begin_frame, + App.stage.inputs, + App.stage.fixed_update, + App.stage.update, + App.stage.draw, + App.stage.end_frame, + }, &app); + } } diff --git a/src/plugins/input/root.zig b/src/plugins/input/root.zig new file mode 100644 index 0000000..a23f30a --- /dev/null +++ b/src/plugins/input/root.zig @@ -0,0 +1,29 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +// +// PRIVATE +// + +const App = @import("app"); +const input = @import("system/input.zig"); + +// +// PUBLIC +// + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const plugin: App.plugin.Plugin = .{ + .name = "Input", + .build = build, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn build( + _: *App.plugin.Plugin, + app: *App, +) !void { + try app.schedules.add(App.stage.inputs, input.pollEvents); +} diff --git a/src/plugins/input/system/input.zig b/src/plugins/input/system/input.zig new file mode 100644 index 0000000..ed53d97 --- /dev/null +++ b/src/plugins/input/system/input.zig @@ -0,0 +1,17 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const sdl = @import("sdl"); +const App = @import("app"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn pollEvents(app: *App) !void { + var event: sdl.SDL_Event = undefined; + while (sdl.SDL_PollEvent(&event)) { + switch (event.type) { + sdl.SDL_EVENT_QUIT => try app.events.send(App.event.AppExit{}), + else => {}, + } + } +} diff --git a/src/plugins/renderer/resource/renderer.zig b/src/plugins/renderer/resource/renderer.zig new file mode 100644 index 0000000..4c3d3c3 --- /dev/null +++ b/src/plugins/renderer/resource/renderer.zig @@ -0,0 +1,73 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const gtl = @import("gtl"); +const Material = @import("../../common/component/material.zig"); +const Handle = gtl.Handle; +const Self = @This(); + +// TODO: +const Mesh = struct {}; +const Camera = struct {}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const DrawCMD = union(enum) { + draw: struct { + mesh: Handle(Mesh), + material: Material.Unique, + }, + + draw_indexed: struct { + mesh: Handle(Mesh), + material: Material.Unique, + }, + + begin_pass: struct { + camera: Handle(Camera), + color: [4]f32 = .{ 0, 0, 0, 255 }, + }, + + end_pass, +}; + +// +// FIELDS +// + +commands: std.ArrayList(DrawCMD), +alloc: std.mem.Allocator, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(alloc: std.mem.Allocator) Self { + return .{ + .commands = .empty, + .alloc = alloc, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + self.commands.deinit(self.alloc); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn clear(self: *Self) void { + self.commands.clearRetainingCapacity(); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn draw( + self: *Self, + cmd: DrawCMD, +) !void { + try self.commands.append( + self.alloc, + cmd, + ); +} diff --git a/src/plugins/renderer/root.zig b/src/plugins/renderer/root.zig index b5719d7..d58367d 100644 --- a/src/plugins/renderer/root.zig +++ b/src/plugins/renderer/root.zig @@ -7,10 +7,16 @@ const App = @import("app"); +const lifetime = @import("system/lifetime.zig"); +const frame = @import("system/frame.zig"); +const present = @import("system/present.zig"); + // // PUBLIC // +const Renderer = @import("resource/renderer.zig"); + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const plugin: App.plugin.Plugin = .{ @@ -24,5 +30,14 @@ fn build( _: *App.plugin.Plugin, app: *App, ) !void { - _ = app; + // --- LIFETIME --- + try app.schedules.add(App.stage.init, lifetime.init); + try app.schedules.add(App.stage.deinit, lifetime.deinit); + + // --- FRAME --- + try app.schedules.add(App.stage.begin_frame, frame.begin); + try app.schedules.add(App.stage.end_frame, frame.end); + + // --- DRAW --- + try app.schedules.add(App.stage.draw, present.draw); } diff --git a/src/plugins/renderer/system/frame.zig b/src/plugins/renderer/system/frame.zig new file mode 100644 index 0000000..0d0d1cf --- /dev/null +++ b/src/plugins/renderer/system/frame.zig @@ -0,0 +1,39 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const App = @import("app"); +const Renderer = @import("../resource/renderer.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn begin(app: *App) !void { + // --- # --- + const res = try app.resources.getMany(struct { + renderer: *Renderer, + }); + + // --- # --- + res.renderer.clear(); + + { // TEST + try res.renderer.draw(.{ + .begin_pass = .{ + .camera = undefined, + .color = .{ 0.2, 0.8, 0.2, 1.0 }, + }, + }); + try res.renderer.draw(.{ + .draw = .{ + .mesh = undefined, + .material = .{}, + }, + }); + try res.renderer.draw(.end_pass); + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn end(app: *App) !void { + _ = app; +} diff --git a/src/plugins/renderer/system/lifetime.zig b/src/plugins/renderer/system/lifetime.zig new file mode 100644 index 0000000..5081136 --- /dev/null +++ b/src/plugins/renderer/system/lifetime.zig @@ -0,0 +1,20 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const App = @import("app"); +const Renderer = @import("../resource/renderer.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(app: *App) !void { + try app.resources.set(Renderer.init( + app.alloc, + )); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(app: *App) !void { + const renderer = app.resources.getPtr(Renderer) orelse return error.RendererNotSet; + renderer.deinit(); +} diff --git a/src/plugins/renderer/system/present.zig b/src/plugins/renderer/system/present.zig new file mode 100644 index 0000000..b70536f --- /dev/null +++ b/src/plugins/renderer/system/present.zig @@ -0,0 +1,114 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const wgpu = @import("wgpu"); +const App = @import("app"); + +const Window = @import("../../window/resource/window.zig"); +const Device = @import("../../device/resource/wgpu_device.zig"); +const Renderer = @import("../resource/renderer.zig"); +const PipelineCache = @import("../../cache/resource/pipeline_cache.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn draw(app: *App) !void { + // --- # --- + const res = try app.resources.getMany(struct { + window: *Window, + device: *Device, + renderer: *Renderer, + pipeline_cache: *PipelineCache, + }); + + // --- SURFACE TEXTURE --- + var surface_texture: wgpu.WGPUSurfaceTexture = .{}; + defer wgpu.wgpuTextureRelease(surface_texture.texture); + + // --- GET SURFACE TEXTURE --- + wgpu.wgpuSurfaceGetCurrentTexture( + @ptrCast(res.window.surface), + &surface_texture, + ); + + // --- GET SURFACE COLOR VIEW --- + const target_view = wgpu.wgpuTextureCreateView(surface_texture.texture, &wgpu.WGPUTextureViewDescriptor{ + .format = wgpu.wgpuTextureGetFormat(surface_texture.texture), + .dimension = wgpu.WGPUTextureViewDimension_2D, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .arrayLayerCount = 1, + .aspect = wgpu.WGPUTextureAspect_All, + }); + defer wgpu.wgpuTextureViewRelease(target_view); + + // --- ENCODER --- + const encoder = wgpu.wgpuDeviceCreateCommandEncoder( + res.device.raw, + &wgpu.WGPUCommandEncoderDescriptor{}, + ); + defer wgpu.wgpuCommandEncoderRelease(encoder); + + // --- FRAME --- + var render_pass: ?*wgpu.WGPURenderPassEncoderImpl = null; + + for (res.renderer.commands.items) |cmd| { + switch (cmd) { + .begin_pass => |v| { + // --- END PREV PASS --- + if (render_pass) |rp| { + wgpu.wgpuRenderPassEncoderEnd(rp); + wgpu.wgpuRenderPassEncoderRelease(rp); + } + + // --- NEW RENDER PASS --- + render_pass = wgpu.wgpuCommandEncoderBeginRenderPass( + encoder, + &wgpu.WGPURenderPassDescriptor{ + .colorAttachmentCount = 1, + .colorAttachments = &wgpu.WGPURenderPassColorAttachment{ + .view = target_view, + .resolveTarget = null, + .loadOp = wgpu.WGPULoadOp_Clear, + .storeOp = wgpu.WGPUStoreOp_Store, + .clearValue = .{ + .r = v.color[0], + .g = v.color[1], + .b = v.color[2], + .a = v.color[3], + }, + .depthSlice = wgpu.WGPU_DEPTH_SLICE_UNDEFINED, + }, + }, + ); + }, + .end_pass => { + if (render_pass) |rp| { + wgpu.wgpuRenderPassEncoderEnd(rp); + wgpu.wgpuRenderPassEncoderRelease(rp); + render_pass = null; + } + }, + .draw => |v| { + // --- ENSURE PIPELINE --- + const pipeline = try res.pipeline_cache.getOrCreate( + v.material, + ); + + // --- DRAW --- + wgpu.wgpuRenderPassEncoderSetPipeline(render_pass, pipeline.raw); + wgpu.wgpuRenderPassEncoderDraw(render_pass, 3, 1, 0, 0); + }, + else => {}, + } + } + + // --- FINISH ENCODER | CMD BUFFER --- + const cmd_buff = wgpu.wgpuCommandEncoderFinish(encoder, &.{}); + defer wgpu.wgpuCommandBufferRelease(cmd_buff); + + // --- SUBMIT AND PRESENT --- + wgpu.wgpuQueueSubmit(res.device.queue, 1, &cmd_buff); + _ = wgpu.wgpuSurfacePresent(@ptrCast(res.window.surface)); +} diff --git a/src/plugins/window/resource/window.zig b/src/plugins/window/resource/window.zig new file mode 100644 index 0000000..67018ed --- /dev/null +++ b/src/plugins/window/resource/window.zig @@ -0,0 +1,5 @@ +const sdl = @import("sdl"); +const sdl_glue = @import("sdl_glue"); + +raw: *sdl.SDL_Window, +surface: *sdl_glue.WGPUSurfaceImpl, diff --git a/src/plugins/window/root.zig b/src/plugins/window/root.zig index de05945..5eee02e 100644 --- a/src/plugins/window/root.zig +++ b/src/plugins/window/root.zig @@ -5,12 +5,19 @@ // PRIVATE // +const sdl = @import("sdl"); +const sdl_glue = @import("sdl_glue"); + const App = @import("app"); +const WGPUInstance = @import("../platform/resource/wgpu_instance.zig"); +const WGPUDevice = @import("../device/resource/wgpu_device.zig"); // // PUBLIC // +pub const Window = @import("resource/window.zig"); + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const plugin: App.plugin.Plugin = .{ @@ -24,5 +31,73 @@ fn build( _: *App.plugin.Plugin, app: *App, ) !void { - _ = app; + try app.schedules.add(App.stage.init, init); + try app.schedules.add(App.stage.deinit, deinit); + + try app.schedules.addAfter(App.stage.init, App.stage.init, configure); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init(app: *App) !void { + // --- GET WGPU INSTANCE --- + const instance = app.resources.getPtr( + WGPUInstance, + ) orelse return error.WGPUInstanceNotSet; + + // --- WINDOW --- + const raw_window = sdl.SDL_CreateWindow( + "NYXEngine", + 800, + 600, + 0, + ) orelse return error.CreateWindow; + + // --- SURFACE --- + const raw_surface = sdl_glue.SDL_GetWGPUSurface( + @ptrCast(instance.raw), + @ptrCast(raw_window), + ) orelse return error.GetSurface; + + // --- RESOURCE --- + try app.resources.set(Window{ + .raw = raw_window, + .surface = raw_surface, + }); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(app: *App) !void { + // --- # --- + const res = try app.resources.getMany(struct { + window: *Window, + }); + + // --- # --- + sdl_glue.wgpuSurfaceUnconfigure(res.window.surface); + sdl_glue.wgpuSurfaceRelease(res.window.surface); + sdl.SDL_DestroyWindow(res.window.raw); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn configure(app: *App) !void { + // --- # --- + const res = try app.resources.getMany(struct { + window: *Window, + device: *WGPUDevice, + }); + + // --- CONFIGURE SURFACE --- + sdl_glue.wgpuSurfaceConfigure( + res.window.surface, + &sdl_glue.WGPUSurfaceConfiguration{ + .device = @ptrCast(res.device.raw), + .format = 28, // TODO: Get format properly, idk, store it somewhere + .width = 800, + .height = 600, + .usage = sdl_glue.WGPUTextureUsage_RenderAttachment, + }, + ); }