Hello Triangle!

This commit is contained in:
abux 2026-07-28 23:30:07 +01:00
parent c16eb13ba1
commit 7700000bd7
11 changed files with 414 additions and 3 deletions

View file

@ -7,8 +7,14 @@ paru -S --needed sdl3 wgpu-native
```
## TODO
### Core
- [x] Platform
- [x] Window
- [x] Device
- [x] Cache
- [ ] Renderer
### Engine
- [ ] ClassDB
- [ ] UI

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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 => {},
}
}
}

View file

@ -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,
);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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));
}

View file

@ -0,0 +1,5 @@
const sdl = @import("sdl");
const sdl_glue = @import("sdl_glue");
raw: *sdl.SDL_Window,
surface: *sdl_glue.WGPUSurfaceImpl,

View file

@ -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,
},
);
}