Major update | Replaced previous renderer with a proper clean one

This commit is contained in:
abux 2026-08-04 12:21:52 +01:00
parent 7a3fe658ef
commit 6fab7b5ead
76 changed files with 2999 additions and 783 deletions

View file

@ -8,10 +8,8 @@ paru -S --needed sdl3 wgpu-native
## Architecture
```bash
[ENTITY]
| mesh: Handle(Mesh)
| material: Handle(Material)
| transform: Transform2D or Transform3D
RenderData -> RenderPhase -> RenderGraph
Extract -> Prepare -> Queue -> Present
```
## TODO
@ -21,7 +19,7 @@ paru -S --needed sdl3 wgpu-native
- [x] Window
- [x] Device
- [x] Cache
- [ ] Renderer
- [x] RenderGraph
- [ ] Hot Reloading
### Engine

View file

@ -66,6 +66,12 @@ pub fn build(b: *std.Build) void {
});
exe.root_module.addImport("math", math.module("NYXMath"));
const world = b.dependency("NYXWorld", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("world", world.module("NYXWorld"));
//
// SYSTEM DEPENDENCIES
//

View file

@ -13,17 +13,21 @@
.hash = "zla-0.0.0-z62_impQAABHbrWUmoFLsl_HuD8CNS-k91z6laMJEQ62",
},
.app = .{
.url = "git+http://2.120.146.66:25569/abux/NYXA.git#9cbd0804a6008243b4cff387285a5e09cf1e03ed",
.hash = "app-0.0.0-AOV9_YDvAAAjjuCldX-9p7mt3Oswbv8bTB2rvcZ67Fn1",
.url = "git+http://192.168.0.11:25569/abux/NYXA.git#97c629a78b5a99860cd16405a8741ed91c2411ce",
.hash = "app-0.0.0-AOV9_dxlAADNgU13ahJF7Qn934LLC-5M1KWAxVwDgaoW",
},
.gtl = .{
.url = "git+http://192.168.0.11:25569/abux/GTL.git#f9772950b5097bbbda39cd68d86f6b6062c41fec",
.hash = "gtl-0.0.0-e3QoJPNxAABpkY1nvYNN7r02CI4z2aki1_FHDPzdWi9P",
.url = "git+http://192.168.0.11:25569/abux/GTL.git#3c411f25b9a60d4633fa283a076a1cc141eb9902",
.hash = "gtl-0.0.0-e3QoJM_pAADNA1GlFiA-h7cRWuLH5rAspOCDy4aMXLd_",
},
.NYXMath = .{
.url = "git+http://192.168.0.11:25569/abux/NYXMath.git#cb7b54995bdb46321f112bba2a704d58cf53aeb1",
.hash = "NYXMath-0.0.0-BD6ST9rOAAA0uSpPK_woZrta3nTh0f5w7hFewJ-e2Kv1",
},
.NYXWorld = .{
.url = "git+http://192.168.0.11:25569/abux/NYXWorld.git#e6c46e23716614473500931dbad16de9c7b36f3c",
.hash = "NYXWorld-0.0.0-jIP7UtZ3AAB4FoXBjV1iAO-0EWFssFr-cxAnIK4Y7_LR",
},
},
.paths = .{

View file

@ -15,6 +15,7 @@ const App = @import("app");
//
const template = @import("plugins/template/root.zig");
const world = @import("plugins/world/root.zig");
const playground = @import("plugins/playground/root.zig");
const lua = @import("plugins/lua/root.zig");
const common = @import("plugins/common/root.zig");
@ -32,8 +33,15 @@ pub fn main(init: std.process.Init) !void {
var app: App = .init(init.gpa);
defer app.deinit();
// --- # ---
errdefer {
app.schedules.runReversed(App.stage.deinit, &app) catch unreachable;
app.deinit();
}
// --- ADD PLUGINS ---
try app.plugins.addMany(&.{
world.plugin,
lua.plugin,
platform.plugin,
device.plugin,
@ -45,25 +53,10 @@ pub fn main(init: std.process.Init) !void {
});
try app.plugins.build(&app);
// DEBUG
if (gtl.log.print(.debug, "PLUGINS", gtl.ansi.yellow)) |v| {
defer v.end();
for (app.plugins.data.items) |plugin| {
v.write("{s}", .{plugin.name});
}
}
// --- LIFETIME ---
try app.schedules.run(App.stage.init, &app);
defer app.schedules.runReversed(App.stage.deinit, &app) catch unreachable;
// --- # ---
errdefer {
app.schedules.runReversed(App.stage.deinit, &app) catch unreachable;
app.deinit();
}
// --- MAIN LOOP ---
while (true) {
// --- # ---

View file

@ -0,0 +1,10 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const wgpu = @import("wgpu");
//
// FIELDS
//
raw: *wgpu.WGPUDeviceImpl,

View file

@ -0,0 +1,30 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const App = @import("app");
//
// PUBLIC
//
pub const GPUDevice = @import("resource/gpu_device.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "Device",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
_ = app;
}

View file

@ -0,0 +1,3 @@
const wgpu = @import("wgpu");
raw: *wgpu.WGPUInstanceImpl,

View file

@ -0,0 +1,64 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const std = @import("std");
const sdl = @import("sdl");
const wgpu = @import("wgpu");
const App = @import("app");
//
// PUBLIC
//
pub const WGPUInstance = @import("resource/wgpu_instance.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
.name = "Platform",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, init);
try app.schedules.add(App.stage.deinit, deinit);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(app: *App) !void {
// --- SDL3 INIT ---
if (!sdl.SDL_Init(sdl.SDL_INIT_VIDEO)) {
std.debug.print("[SDL_ERROR] SDL_Init: {s}\n", .{sdl.SDL_GetError()});
return error.SDL_INIT;
}
// --- WGPU INIT ---
try app.resources.set(WGPUInstance{
.raw = wgpu.wgpuCreateInstance(&wgpu.WGPUInstanceDescriptor{}) orelse return {
return error.WGPU_Init;
},
});
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
instance: *WGPUInstance,
});
// --- # ---
wgpu.wgpuInstanceRelease(res.instance.raw);
sdl.SDL_Quit();
}

View file

@ -0,0 +1,17 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
const Node = @import("../template/node.zig");
const Slot = @import("../template/slot.zig");
const Edge = @import("../template/edge.zig").Edge;
const Handle = gtl.Handle;
//
// FIELDS
//
nodes: gtl.SlotMap(Node),
edges: gtl.SlotMap(Edge),
slots: gtl.SlotMap(Slot),
order: ?[]Handle(Node) = null,

View file

@ -0,0 +1,119 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const std = @import("std");
const gtl = @import("gtl");
const App = @import("app");
//
// PUBLIC
//
pub const RenderGraph = @import("resource/render_graph.zig");
pub const Node = @import("template/node.zig");
pub const Edge = @import("template/edge.zig").Edge;
pub const Slot = @import("template/slot.zig");
pub const RenderCTX = @import("template/render_ctx.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "Render",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, init);
try app.schedules.add(App.stage.deinit, deinit);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn init(app: *App) !void {
const graph = try app.resources.getOrSet(RenderGraph{
.edges = .init(app.alloc),
.nodes = .init(app.alloc),
.slots = .init(app.alloc),
});
const main = try graph.nodes.put(.{
.name = "main",
.run = mainNode,
});
const present = try graph.nodes.put(.{
.name = "present",
.run = presentNode,
});
_ = try graph.edges.put(
.{
.order = .{
.from = main,
.to = present,
},
},
);
var render_ctx: RenderCTX = .{
.graph = graph,
.device = undefined,
.encoder = undefined,
};
if (graph.order == null) {
var order: std.ArrayList(gtl.Handle(Node)) = .empty;
defer order.deinit(app.alloc);
try order.append(app.alloc, main);
try order.append(app.alloc, present);
graph.order = try order.toOwnedSlice(app.alloc);
}
if (graph.order) |order| {
for (order) |node_id| {
const node = graph.nodes.get(
node_id,
) orelse return error.MissingNode;
try node.run(&render_ctx);
}
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn deinit(app: *App) !void {
const graph = app.resources.getPtr(
RenderGraph,
) orelse return error.RenderGraphNotSet;
if (graph.order) |order| app.alloc.free(order);
graph.nodes.deinit();
graph.edges.deinit();
graph.slots.deinit();
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn mainNode(ctx: *RenderCTX) !void {
std.debug.print("[MAIN_PASS]\n", .{});
_ = ctx;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn presentNode(ctx: *RenderCTX) !void {
std.debug.print("[PRESENT_PASS]\n", .{});
_ = ctx;
}

View file

@ -0,0 +1,14 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
const Node = @import("node.zig");
const Slot = @import("slot.zig");
const Handle = gtl.Handle;
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Edge = union(enum) {
data: struct { producer: Handle(Slot), consumer: Handle(Slot) },
order: struct { from: Handle(Node), to: Handle(Node) },
};

View file

@ -0,0 +1,16 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
const Slot = @import("slot.zig");
const RenderCTX = @import("render_ctx.zig");
const Handle = gtl.Handle;
//
// FIELDS
//
name: []const u8,
inputs: []const Handle(Slot) = &.{},
outputs: []const Handle(Slot) = &.{},
run: *const fn (*RenderCTX) anyerror!void,

View file

@ -0,0 +1,14 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const wgpu = @import("wgpu");
const RenderGraph = @import("../resource/render_graph.zig");
//
// FIELDS
//
device: *wgpu.WGPUDeviceImpl,
encoder: *wgpu.WGPUCommandEncoderImpl,
graph: *const RenderGraph,

View file

@ -0,0 +1,19 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const wgpu = @import("wgpu");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Value = union(enum) {
buffer: *wgpu.WGPUBufferImpl,
bind_group: *wgpu.WGPUBindGroupImpl,
texture_view: *wgpu.WGPUTextureViewImpl,
};
//
// FIELDS
//
name: []const u8,
value: ?Value = null,

View file

@ -0,0 +1,28 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const App = @import("app");
//
// PUBLIC
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "Template",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
_ = app;
}

View file

@ -0,0 +1,6 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const sdl = @import("sdl");
raw: *sdl.SDL_Window,

View file

@ -0,0 +1,8 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
title: []const u8 = "NYXEngine",
width: u32 = 800,
height: u32 = 600,
resizeable: bool = true,

View file

@ -0,0 +1,33 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const App = @import("app");
const lifetime = @import("system/lifetime.zig");
//
// PUBLIC
//
pub const Window = @import("resource/window.zig");
pub const GPUWindow = @import("resource/gpu_window.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "Window",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, lifetime.init);
try app.schedules.add(App.stage.deinit, lifetime.deinit);
}

View file

@ -0,0 +1,50 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const sdl = @import("sdl");
const gtl = @import("gtl");
const App = @import("app");
const Window = @import("../resource/window.zig");
const GPUWindow = @import("../resource/gpu_window.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(app: *App) !void {
// --- ENSURE WINDOW ---
const window = try app.resources.getOrSet(Window{});
// --- CREATE GPU WINDOW ---
try app.resources.set(GPUWindow{
.raw = sdl.SDL_CreateWindow(
window.title.ptr,
@intCast(window.width),
@intCast(window.height),
0,
) orelse {
gtl.log.err("{s}", .{sdl.SDL_GetError()}, @src());
return error.SDLWindow;
},
});
// --- DEBUG ---
if (gtl.log.print(.debug, "Window", gtl.ansi.blue)) |v| {
defer v.end();
v.write("title: \"{s}\"", .{window.title});
v.write("width: {}", .{window.width});
v.write("height: {}", .{window.height});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(app: *App) !void {
// --- GET GPU WINDOW ---
const gpu_window = app.resources.getPtr(
GPUWindow,
) orelse return error.GPUWindowNotSet;
// --- # ---
sdl.SDL_DestroyWindow(gpu_window.raw);
}

View file

@ -62,10 +62,11 @@ pub fn init(
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
// --- FREE BUFFERS ---
for (self.data.items()) |entry| {
wgpu.wgpuBufferRelease(entry.value.vbuf);
var it = self.data.valueIterator();
while (it.next()) |v| {
wgpu.wgpuBufferRelease(v.vbuf);
if (entry.value.ibuf) |ibuf| {
if (v.ibuf) |ibuf| {
wgpu.wgpuBufferRelease(ibuf);
}
}

View file

@ -20,3 +20,5 @@ target: union(enum) {
order: u32 = 0,
priority: u32 = 0,
clear_color: math.Color = .black,

View file

@ -1,5 +1,5 @@
const gtl = @import("gtl");
const Material = @import("../../renderer/template/material.zig");
const MaterialAsset = @import("../../renderer/resource/material_assets.zig").Asset;
const Handle = gtl.Handle;
value: Handle(Material),
value: Handle(MaterialAsset),

View file

@ -1,3 +1,7 @@
const MeshID = @import("../../cache/resource/mesh_cache.zig").MeshID;
const gtl = @import("gtl");
const MeshAssets = @import("../../renderer/resource/mesh_assets.zig");
value: MeshID,
value: gtl.Handle(MeshAssets.Asset),
// const MeshID = @import("../../cache/resource/mesh_cache.zig").MeshID;
// value: MeshID,

View file

@ -17,7 +17,7 @@ pub const WGPUDevice = @import("resource/wgpu_device.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Device",
.build = build,
};
@ -25,7 +25,7 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, lifetime.init);

View file

@ -14,7 +14,7 @@ const input = @import("system/input.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Input",
.build = build,
};
@ -22,7 +22,7 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.inputs, input.pollEvents);

View file

@ -8,6 +8,7 @@ const wgpu = @import("wgpu");
const App = @import("app");
const Window = @import("../../window/resource/window.zig");
const GPUWindow = @import("../../window/resource/gpu_window.zig");
const Resized = @import("../../window/event/resized.zig");
const WGPUInstance = @import("../../platform/resource/wgpu_instance.zig");
const WGPUDevice = @import("../../device/resource/wgpu_device.zig");
@ -18,8 +19,9 @@ pub fn pollEvents(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
instance: *WGPUInstance,
window: *Window,
device: *WGPUDevice,
window: *Window,
gpu_window: *GPUWindow,
});
var event: sdl.SDL_Event = undefined;
@ -33,12 +35,16 @@ pub fn pollEvents(app: *App) !void {
.height = @intCast(event.window.data2),
});
// --- SET SIZE ---
res.window.width = @intCast(event.window.data1);
res.window.height = @intCast(event.window.data2);
// --- RELEASE ---
sdl_glue.wgpuSurfaceUnconfigure(res.window.surface);
sdl_glue.wgpuSurfaceUnconfigure(res.gpu_window.surface);
// --- CONFIGURE SURFACE ---
sdl_glue.wgpuSurfaceConfigure(
res.window.surface,
res.gpu_window.surface,
&sdl_glue.WGPUSurfaceConfiguration{
.device = @ptrCast(res.device.raw),
.format = 28, // TODO: Get format properly, idk, store it somewhere

View file

@ -14,7 +14,7 @@ const Zla = @import("zla");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "LUA",
.build = build,
};
@ -22,7 +22,7 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
// --- LIFETIME ---

View file

@ -18,7 +18,7 @@ pub const WGPUInstance = @import("resource/wgpu_instance.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Platform",
.build = build,
};
@ -26,7 +26,7 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, init);
@ -54,11 +54,11 @@ pub fn init(app: *App) !void {
/// ----------------------------------------------------
pub fn deinit(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
instance: *WGPUInstance,
});
const instance = try app.resources.require(
WGPUInstance,
);
// --- # ---
wgpu.wgpuInstanceRelease(res.instance.raw);
wgpu.wgpuInstanceRelease(instance.raw);
sdl.SDL_Quit();
}

View file

@ -7,13 +7,17 @@
const std = @import("std");
const App = @import("app");
const World = @import("world");
const MeshAssets = @import("../renderer/resource/mesh_assets.zig");
const MaterialAssets = @import("../renderer/resource/material_assets.zig");
const Camera = @import("../common/component/camera.zig");
const Mesh = @import("../common/component/mesh.zig");
const Material = @import("../common/component/material.zig");
const Transform3D = @import("../common/component/transform3D.zig");
const RenderWorld = @import("../renderer/resource/render_world.zig");
// const RenderWorld = @import("../renderer/resource/render_world.zig");
//
// PUBLIC
@ -21,7 +25,7 @@ const RenderWorld = @import("../renderer/resource/render_world.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Playground",
.build = build,
};
@ -29,10 +33,14 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.addAfter(App.stage.init, App.stage.init, start);
try app.schedules.addAfter(
App.stage.init,
App.stage.init,
start,
);
}
const FlyCamera = struct {};
@ -41,79 +49,62 @@ const FlyCamera = struct {};
/// ----------------------------------------------------
fn start(app: *App) !void {
// --- # ---
const render_world = app.resources.getPtr(
RenderWorld,
).?;
const res = try app.resources.getMany(struct {
world: *World,
mesh_assets: *MeshAssets,
material_assets: *MaterialAssets,
});
// --- CAMERA ---
try app.world.spawn(.{
FlyCamera{},
try res.world.spawn(.{
Camera{
.projection = .{
.perspective = .{},
},
},
Transform3D{
.position = .new(.{ 0, -2.5, -6.5 }),
.rotation = .fromEuler(.init(
-std.math.degreesToRadians(30),
0,
0,
)),
},
});
// --- # ---
try app.world.spawn(.{
Mesh{ .value = .cube },
Material{
.value = try render_world.materials.put(.{
.style = .{
.albedo = .{ 0.8, 0.2, 0.2, 1.0 },
},
.unique = .{
.cull_mode = .none,
},
}),
},
Transform3D{},
});
const count: usize = 200;
const radius: f32 = 5.0;
for (0..count) |i| {
const angle = (@as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(count))) * std.math.tau;
try app.world.spawn(.{
Mesh{ .value = .cube },
Material{
.value = try render_world.materials.put(.{
.style = .{
.albedo = .{
0.2,
0.0 + (angle * std.math.pi) * 0.01,
0.0 + (angle * std.math.pi) * 0.02,
1,
},
},
.unique = .{
.cull_mode = .none,
},
}),
},
Transform3D{
.position = .new(.{
@cos(angle) * radius,
0,
@sin(angle) * radius,
}),
.rotation = .fromEuler(.init(
std.math.degreesToRadians(180),
0,
0,
)),
},
try res.world.spawn(.{
Mesh{ .value = try res.mesh_assets.put(.{}) },
Material{ .value = try res.material_assets.put(.{}) },
Transform3D{},
});
}
// const count: usize = 200;
// const radius: f32 = 5.0;
//
// for (0..count) |i| {
// const angle = (@as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(count))) * std.math.tau;
//
// try res.world.spawn(.{
// Mesh{ .value = .cube },
// Material{
// .value = try res.render_world.materials.put(.{
// .style = .{
// .albedo = .{
// 0.2,
// 0.0 + (angle * std.math.pi) * 0.01,
// 0.0 + (angle * std.math.pi) * 0.02,
// 1,
// },
// },
// .unique = .{
// .cull_mode = .none,
// },
// }),
// },
// Transform3D{
// .position = .new(.{
// @cos(angle) * radius,
// 0,
// @sin(angle) * radius,
// }),
// .rotation = .fromEuler(.init(
// std.math.degreesToRadians(180),
// 0,
// 0,
// )),
// },
// });
// }
}

View file

@ -0,0 +1,80 @@
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexInput {
@location(0) position: vec3f,
@location(1) normal: vec3f,
@location(2) uv: vec2f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
@location(1) normal: vec3f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct CameraUniform {
view: mat4x4<f32>,
projection: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct ModelUniform {
data: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct Material {
albedo: vec4f,
metallic: f32,
roughness: f32,
_pad0: vec2f,
};
//
// UNIFORMS
//
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
@group(1) @binding(0)
var<uniform> model: ModelUniform;
@group(2) @binding(0)
var<uniform> material: Material;
//
// VERTEX
//
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
// --- # ---
var out: VertexOutput;
// --- # ---
out.position = camera.projection * camera.view * model.data * vec4f(in.position, 1.0);
out.uv = in.uv;
out.normal = in.normal;
// --- # ---
return out;
}
//
// FRAGMENT
//
@fragment
fn fs_main(out: VertexOutput) -> @location(0) vec4f {
var final_color: vec4f = material.albedo;
final_color.x += out.uv.x * 0.2;
final_color.y += out.uv.y * 0.2;
return vec4f(final_color);
}

View file

@ -0,0 +1,17 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
pub const Node = @import("../template/node.zig");
pub const Slot = @import("../template/slot.zig");
pub const Edge = @import("../template/edge.zig").Edge;
const Handle = gtl.Handle;
//
// FIELDS
//
nodes: gtl.FixedSlotMap(Node, 24),
edges: gtl.FixedSlotMap(Edge, 24),
slots: gtl.FixedSlotMap(Slot, 24),
order: ?[]Handle(Node) = null,

View file

@ -5,7 +5,8 @@
const std = @import("std");
const gtl = @import("gtl");
const wgpu = @import("wgpu");
pub const Entity = u64; // TODO: You lazy retard
const App = @import("app");
const World = @import("world");
// --- CAMERA ---
pub const GPUCamera = @import("../template/gpu_camera.zig");
@ -39,12 +40,12 @@ pub const UnlitCode = @embedFile("embeded/shaders/pbr.wgsl");
// FIELDS
//
gpu_cameras: gtl.KeyedSlotMap(Entity, GPUCamera),
gpu_objects: gtl.KeyedSlotMap(Entity, GPUObject),
gpu_cameras: gtl.KeyedSlotMap(World.Entity, GPUCamera),
gpu_objects: gtl.KeyedSlotMap(World.Entity, GPUObject),
gpu_shaders: gtl.KeyedSlotMap(ShaderID, GPUShader),
gpu_pipelines: gtl.KeyedSlotMap(Pipeline, GPUPipeline),
gpu_materials: gtl.KeyedSlotMap(gtl.Handle(Material), GPUMaterial),
frame_views: std.ArrayList(FrameView),
frame_objects: std.ArrayList(FrameObject),
frame_views: gtl.FixedArrayList(FrameView, 8),
frame_objects: gtl.FixedArrayList(FrameObject, 256),
materials: gtl.SlotMap(Material), // TODO: Move this to assets resource or sum

View file

@ -3,6 +3,8 @@
const std = @import("std");
const gtl = @import("gtl");
const App = @import("app");
const World = @import("world");
const Material = @import("../../common/component/material.zig");
const Mesh = @import("../../cache/resource/mesh_cache.zig").MeshID;
const Handle = gtl.Handle;
@ -13,18 +15,18 @@ const Self = @This();
pub const DrawCMD = union(enum) {
draw: struct {
mesh: Mesh,
entity: u64,
entity: World.Entity,
material: Handle(Material),
},
draw_indexed: struct {
mesh: Mesh,
entity: u64,
entity: World.Entity,
material: Handle(Material),
},
begin_pass: struct {
camera: u64, // TODO: Change it to Entity type instead
camera: World.Entity,
color: [4]f32 = .{ 0, 0, 0, 255 },
load_op: enum {
load,

View file

@ -0,0 +1,62 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const App = @import("app");
const lifetime = @import("system/lifetime.zig");
const frame = @import("system/frame.zig");
const prepare = @import("system/prepare.zig");
const present = @import("system/present.zig");
//
// PUBLIC
//
const Renderer = @import("resource/renderer.zig");
const RenderWorld = @import("resource/render_world.zig");
const RenderLayout = @import("resource/render_layout.zig");
pub const RenderGraph = @import("resource/render_graph.zig");
pub const Node = @import("template/node.zig");
pub const Slot = @import("template/slot.zig");
pub const Edge = @import("template/edge.zig").Edge;
pub const RenderCTX = @import("template/render_ctx.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "Renderer",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
// --- 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);
// --- PRE DRAW ---
try app.schedules.addManyBefore(App.stage.draw, App.stage.draw, &.{
prepare.shaders,
prepare.pipelines,
prepare.cameras,
prepare.objects,
prepare.materials,
});
// --- DRAW ---
try app.schedules.add(App.stage.draw, present.draw);
}

View file

@ -23,7 +23,7 @@ pub fn begin(app: *App) !void {
res.renderer.clear();
// --- # ---
for (res.render_world.frame_views.items) |view| {
for (res.render_world.frame_views.items()) |view| {
// --- NEW PASS ---
try res.renderer.draw(.{
.begin_pass = .{
@ -34,7 +34,7 @@ pub fn begin(app: *App) !void {
defer res.renderer.draw(.end_pass) catch unreachable;
// --- DRAW ---
for (res.render_world.frame_objects.items) |obj| {
for (res.render_world.frame_objects.items()) |obj| {
try res.renderer.draw(.{
.draw = .{
.material = obj.material,
@ -46,8 +46,8 @@ pub fn begin(app: *App) !void {
}
// --- YES ---
res.render_world.frame_views.clearRetainingCapacity();
res.render_world.frame_objects.clearRetainingCapacity();
res.render_world.frame_views.clear();
res.render_world.frame_objects.clear();
}
/// ----------------------------------------------------

View file

@ -6,6 +6,7 @@ const App = @import("app");
const Zla = @import("zla");
const WGPUDevice = @import("../../device/resource/wgpu_device.zig");
const RenderGraph = @import("../resource/render_graph.zig");
const Renderer = @import("../resource/renderer.zig");
const RenderWorld = @import("../resource/render_world.zig");
const RenderLayout = @import("../resource/render_layout.zig");
@ -23,6 +24,14 @@ pub fn init(app: *App) !void {
device: *WGPUDevice,
});
// --- # ---
try app.resources.set(RenderGraph{
.nodes = .empty,
.edges = .empty,
.slots = .empty,
.order = null,
});
// --- # ---
try app.resources.set(Renderer.init(
app.alloc,
@ -97,10 +106,17 @@ pub fn deinit(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
ren: *Renderer,
ren_graph: *RenderGraph,
ren_world: *RenderWorld,
ren_layout: *RenderLayout,
});
// --- FREE RENDER GRAPH ---
// res.ren_graph.nodes.deinit();
// res.ren_graph.edges.deinit();
// res.ren_graph.slots.deinit();
if (res.ren_graph.order) |v| app.alloc.free(v);
// --- FREE SHADERS ---
for (res.ren_world.gpu_shaders.items()) |entry| {
wgpu.wgpuShaderModuleRelease(entry.value.raw);
@ -137,8 +153,6 @@ pub fn deinit(app: *App) !void {
// --- # ---
res.ren_world.materials.deinit();
res.ren_world.frame_views.deinit(app.alloc);
res.ren_world.frame_objects.deinit(app.alloc);
res.ren_world.gpu_cameras.deinit();
res.ren_world.gpu_objects.deinit();
res.ren_world.gpu_shaders.deinit();

View file

@ -0,0 +1,515 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
// --- # ---
const std = @import("std");
const gtl = @import("gtl");
const math = @import("math");
const wgpu = @import("wgpu");
const App = @import("app");
const World = @import("world");
// --- # ---
const Vertex = @import("../../cache/resource/mesh_cache.zig").Vertex;
const Window = @import("../../window/resource/window.zig");
const WGPUDevice = @import("../../device/resource/wgpu_device.zig");
const RenderWorld = @import("../resource/render_world.zig");
const RenderLayout = @import("../resource/render_layout.zig");
const RenderGraph = @import("../resource/render_graph.zig");
// --- # ---
const Transform2D = @import("../../common/component/transform2D.zig");
const Transform3D = @import("../../common/component/transform3D.zig");
// --- MESH ---
const CMesh = @import("../../common/component/mesh.zig");
const TMesh = @import("../template/gpu_object.zig");
// --- MATERIAL ---
const CMaterial = @import("../../common/component/material.zig");
const TMaterial = @import("../template/material.zig");
// --- CAMERA ---
const CCamera = @import("../../common/component/camera.zig");
const TCamera = @import("../template/gpu_camera.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn ensureGraph(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
device: *WGPUDevice,
render_graph: *RenderGraph,
});
// --- # ---
if (res.render_graph.order == null) {
const order: std.ArrayList(gtl.Handle(RenderGraph.Node)) = .empty;
defer order.deinit(app.alloc);
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn cameras(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = res.world.query(struct {
camera: *const CCamera,
transform2D: ?*const Transform2D,
transform3D: ?*const Transform3D,
}, .{});
while (query.next()) |e| {
// --- GPU ---
const raw = try res.render_world.gpu_cameras.getOrPut(query.entity(), blk: {
// --- # ---
if (res.render_world.gpu_cameras.getByKey(
query.entity(),
)) |v| break :blk v.*;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TCamera.Uniform),
}) orelse return error.Buffer;
// --- VIEW ---
const view: math.Mat4x4 = if (e.transform3D) |t3| cmp: { // 3D
const eye = t3.position;
const forward = t3.rotation.rotate(math.Vec3f.new(.{ 0, 0, 1 }));
const up = t3.rotation.rotate(math.Vec3f.new(.{ 0, 1, 0 }));
break :cmp math.Mat4x4.lookAt(eye, eye.add(forward), up);
} else if (e.transform2D) |t2| cmp: { // 2D
const eye: math.Vec3f = .new(.{ t2.position.value[0], t2.position.value[1], 0 });
break :cmp math.Mat4x4.rotateZ(-t2.angle)
.mul(math.Mat4x4.translate(-eye.value[0], -eye.value[1], 0));
} else math.Mat4x4.identity();
// --- PROJECTION ---
const projection: math.Mat4x4 = switch (e.camera.projection) {
.perspective => |v| .perspective(std.math.degreesToRadians(v.fov), 800.0 / 600.0, v.near, v.far),
.ortho => |v| .orthographic(800.0, 0, 600.0, 0, v.near, v.far),
};
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&TCamera.Uniform{
.view = view.transpose(),
.projection = projection.transpose(),
},
@sizeOf(TCamera.Uniform),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.camera,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TCamera.Uniform),
},
},
}) orelse return error.BindGroup;
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
});
// --- FRAME ---
try res.render_world.frame_views.append(.{
.order = e.camera.order,
.priority = e.camera.priority,
.camera = query.entity(),
.raw = raw,
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn objects(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = res.world.query(struct {
mesh: *const CMesh,
material: *const CMaterial,
transform2D: ?*const Transform2D,
transform3D: ?*const Transform3D,
}, .{});
while (query.next()) |e| {
// --- ENSURE TRANSFORM ---
if (e.transform2D == null and
e.transform3D == null) continue;
// --- GPU ---
_ = try res.render_world.gpu_objects.getOrPut(query.entity(), blk: {
// --- # ---
if (res.render_world.gpu_objects.getByKey(
query.entity(),
)) |v| break :blk v.*;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TMesh.Uniform),
}) orelse return error.Buffer;
// --- MODEL ---
const pos: math.Vec3f = e.transform3D.?.position;
const scl: math.Vec3f = e.transform3D.?.scale;
const rot: math.Quat = e.transform3D.?.rotation;
// --- FINAL MODEL ---
const model =
math.Mat4x4.translate(pos.value[0], pos.value[1], pos.value[2])
.mul(rot.toMat4())
.mul(math.Mat4x4.scale(scl.value[0], scl.value[1], scl.value[2]));
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&TMesh.Uniform{
.data = model.transpose(),
},
@sizeOf(TMesh.Uniform),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.model,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TMesh.Uniform),
},
},
}) orelse return error.BindGroup;
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
});
// --- FRAME ---
try res.render_world.frame_objects.append(.{
.mesh = e.mesh.value,
.entity = query.entity(),
.material = e.material.value,
.transform = blk: {
if (e.transform2D) |v| break :blk .{ .@"2D" = v.* };
if (e.transform3D) |v| break :blk .{ .@"3D" = v.* };
break :blk .{ .@"2D" = .{} };
},
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn shaders(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_world: *RenderWorld,
device: *WGPUDevice,
});
// --- # ---
var query = res.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
// --- GET MATERIAL ---
const material = res.render_world.materials.get(
e.material.value,
) orelse return error.MaterialNotFound;
// --- ENSURE SHADER ---
_ = try res.render_world.gpu_shaders.getOrPut(material.unique.shader, blk: {
// --- # ---
if (res.render_world.gpu_shaders.contains(
material.unique.shader,
)) return;
// --- CODE ---
const code: []const u8 = switch (material.unique.shader) {
.pbr => RenderWorld.PBRCode,
.unlit => RenderWorld.UnlitCode,
.custom => return error.TooLazyToImplement, // TODO: Yes
};
// --- SOURCE ---
var source: wgpu.WGPUShaderSourceWGSL = .{
.chain = .{ .sType = wgpu.WGPUSType_ShaderSourceWGSL },
.code = .{ .data = code.ptr, .length = wgpu.WGPU_STRLEN },
};
// --- RESULT ---
break :blk .{
.raw = wgpu.wgpuDeviceCreateShaderModule(res.device.raw, &wgpu.WGPUShaderModuleDescriptor{
.label = .{ .data = @tagName(material.unique.shader), .length = wgpu.WGPU_STRLEN },
.nextInChain = &source.chain,
}) orelse {
return error.ShaderModuleCreationFailed;
},
};
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn pipelines(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = res.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
// --- GET MATERIAL ---
const material = res.render_world.materials.get(
e.material.value,
) orelse return error.MaterialNotFound;
// --- ENSURE PIPELINE ---
_ = try res.render_world.gpu_pipelines.getOrPut(material.unique, blk: {
// --- # ---
if (res.render_world.gpu_pipelines.contains(
material.unique,
)) return;
// --- LAYOUT ---
var bind_group_layouts = [_]wgpu.WGPUBindGroupLayout{
res.render_layout.camera,
res.render_layout.model,
res.render_layout.material,
};
// --- PIPELINE LAYOUT ---
const pipeline_layout = wgpu.wgpuDeviceCreatePipelineLayout(
res.device.raw,
&wgpu.WGPUPipelineLayoutDescriptor{
.bindGroupLayoutCount = 3,
.bindGroupLayouts = &bind_group_layouts,
},
) orelse return error.Layout;
// --- CREATE PIPELINE ---
const pipeline = try createPipeline(
material.unique,
pipeline_layout,
if (res.render_world.gpu_shaders.getByKey(
material.unique.shader,
)) |v| v.raw else return error.ShaderNotFound,
res.device.raw,
);
// --- RESULT ---
break :blk .{
.raw = pipeline,
.layout = pipeline_layout,
};
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn materials(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = res.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
// --- ENSURE MATERIAL ---
_ = try res.render_world.gpu_materials.getOrPut(
e.material.value,
blk: {
// --- # ---
if (res.render_world.gpu_materials.contains(
e.material.value,
)) return;
// --- # ---
const material = res.render_world.materials.get(
e.material.value,
) orelse return error.MaterialNotFound;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TMaterial.Style),
}) orelse return error.Buffer;
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&material.style,
@sizeOf(TMaterial.Style),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.material,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TMaterial.Style),
},
},
}) orelse return error.BindGroup;
// --- RESULT ---
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
},
);
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn createPipeline(
key: TMaterial.Unique,
layout: *wgpu.WGPUPipelineLayoutImpl,
shader: *wgpu.WGPUShaderModuleImpl,
device: *wgpu.WGPUDeviceImpl,
) !*wgpu.WGPURenderPipelineImpl {
return wgpu.wgpuDeviceCreateRenderPipeline(device, &wgpu.WGPURenderPipelineDescriptor{
.label = .{ .data = @tagName(key.shader), .length = wgpu.WGPU_STRLEN },
.layout = layout,
.vertex = .{
.module = shader,
.entryPoint = .{ .data = "vs_main", .length = wgpu.WGPU_STRLEN },
.bufferCount = 1,
.buffers = &wgpu.WGPUVertexBufferLayout{
.attributeCount = 3,
.attributes = &[_]wgpu.WGPUVertexAttribute{ // TODO: Mabe mabe allow for custom, mabe mabe later tho
.{
.format = wgpu.WGPUVertexFormat_Float32x3,
.offset = @offsetOf(Vertex, "position"),
.shaderLocation = 0,
},
.{
.format = wgpu.WGPUVertexFormat_Float32x3,
.offset = @offsetOf(Vertex, "normal"),
.shaderLocation = 1,
},
.{
.format = wgpu.WGPUVertexFormat_Float32x2,
.offset = @offsetOf(Vertex, "uv"),
.shaderLocation = 2,
},
},
.arrayStride = @sizeOf(Vertex),
.stepMode = wgpu.WGPUVertexStepMode_Vertex,
},
},
.fragment = &wgpu.WGPUFragmentState{
.module = shader,
.entryPoint = .{ .data = "fs_main", .length = wgpu.WGPU_STRLEN },
.targetCount = 1,
.targets = &wgpu.WGPUColorTargetState{
.format = 28, // FIX: Use proper format
.blend = &wgpu.WGPUBlendState{
.color = .{
.srcFactor = wgpu.WGPUBlendFactor_SrcAlpha,
.dstFactor = wgpu.WGPUBlendFactor_OneMinusSrcAlpha,
.operation = wgpu.WGPUBlendOperation_Add,
},
.alpha = .{
.srcFactor = wgpu.WGPUBlendFactor_Zero,
.dstFactor = wgpu.WGPUBlendFactor_One,
.operation = wgpu.WGPUBlendOperation_Add,
},
},
.writeMask = wgpu.WGPUColorWriteMask_All,
},
},
.primitive = .{
.topology = switch (key.topology) {
.triangle_list => wgpu.WGPUPrimitiveTopology_TriangleList,
.point_list => wgpu.WGPUPrimitiveTopology_PointList,
.line_list => wgpu.WGPUPrimitiveTopology_LineList,
},
.frontFace = switch (key.front_face) {
.ccw => wgpu.WGPUFrontFace_CCW,
.cw => wgpu.WGPUFrontFace_CW,
},
.cullMode = switch (key.cull_mode) {
.back => wgpu.WGPUCullMode_Back,
.front => wgpu.WGPUCullMode_Front,
.none => wgpu.WGPUCullMode_None,
},
},
.multisample = .{
.count = 1,
.mask = ~@as(u32, 0),
.alphaToCoverageEnabled = 0,
},
// .depthStencil = &wgpu.WGPUDepthStencilState{
// .format = wgpu.WGPUTextureFormat_Depth24Plus,
// .depthCompare = wgpu.WGPUCompareFunction_Less,
// .depthWriteEnabled = 1,
//
// .stencilReadMask = 0,
// .stencilWriteMask = 0,
// },
}) orelse {
return error.PipelineCreationFailed;
};
}

View file

@ -0,0 +1,160 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const App = @import("app");
const World = @import("world");
const GPUWindow = @import("../../window/resource/gpu_window.zig");
const Resized = @import("../../window/event/resized.zig");
const Device = @import("../../device/resource/wgpu_device.zig");
const Renderer = @import("../resource/renderer.zig");
const RenderWorld = @import("../resource/render_world.zig");
const MeshCache = @import("../../cache/resource/mesh_cache.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn draw(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
window: *GPUWindow,
device: *Device,
renderer: *Renderer,
render_world: *RenderWorld,
mesh_cache: *MeshCache,
});
// --- DO NONE ON WINDOW RESIZE ---
if (app.events.consume(Resized)) |_| {
return;
}
// --- SURFACE TEXTURE ---
var surface_texture: wgpu.WGPUSurfaceTexture = .{};
defer wgpu.wgpuTextureRelease(surface_texture.texture);
// --- GET SURFACE TEXTURE ---
wgpu.wgpuSurfaceGetCurrentTexture(
@ptrCast(res.window.surface),
&surface_texture,
);
// --- DEBUG ---
switch (surface_texture.status) {
wgpu.WGPUSurfaceGetCurrentTextureStatus_Error => {
std.debug.print("\x1b[31m[SURFACE_TEXTURE_ERROR]\x1b[0m\n", .{});
},
wgpu.WGPUSurfaceGetCurrentTextureStatus_Outdated => {
std.debug.print("\x1b[31m[SURFACE_TEXTURE_OUTDATED]\x1b[0m\n", .{});
},
else => {},
}
// --- 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;
var camera: ?World.Entity = 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 = switch (v.load_op) {
.load => wgpu.WGPULoadOp_Load,
.clear => wgpu.WGPULoadOp_Clear,
},
.storeOp = switch (v.store_op) {
.store => wgpu.WGPUStoreOp_Store,
.discard => wgpu.WGPUStoreOp_Discard,
},
.clearValue = .{
.r = v.color[0],
.g = v.color[1],
.b = v.color[2],
.a = v.color[3],
},
.depthSlice = wgpu.WGPU_DEPTH_SLICE_UNDEFINED,
},
},
);
// --- SET CAMERA ---
camera = v.camera;
},
.end_pass => {
if (render_pass) |rp| {
wgpu.wgpuRenderPassEncoderEnd(rp);
wgpu.wgpuRenderPassEncoderRelease(rp);
render_pass = null;
}
},
.draw => |v| {
// --- ENSURE ---
const material = res.render_world.materials.get(v.material) orelse return error.MaterialNotFound;
const gpu_material = res.render_world.gpu_materials.getByKey(v.material) orelse return error.GPUMaterialNotFound;
const gpu_camera = res.render_world.gpu_cameras.getByKey(camera orelse return error.CameraNotSet) orelse return error.GPUCameraNotFound;
const pipeline = res.render_world.gpu_pipelines.getByKey(material.unique) orelse return error.GPUPipelineNotFound;
const gpu_mesh = res.render_world.gpu_objects.getByKey(v.entity) orelse return error.GPUMeshNotFound;
const mesh = try res.mesh_cache.getOrCreate(v.mesh);
// --- BIND ---
wgpu.wgpuRenderPassEncoderSetPipeline(render_pass, pipeline.raw);
wgpu.wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, mesh.vbuf, 0, wgpu.wgpuBufferGetSize(mesh.vbuf));
// --- BIND SHADER GROUPS ---
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 0, gpu_camera.bind_group, 0, null);
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 1, gpu_mesh.bind_group, 0, null);
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 2, gpu_material.bind_group, 0, null);
// --- DRAW ---
if (mesh.ibuf) |ibuf| {
wgpu.wgpuRenderPassEncoderSetIndexBuffer(render_pass, ibuf, wgpu.WGPUIndexFormat_Uint32, 0, wgpu.wgpuBufferGetSize(ibuf));
wgpu.wgpuRenderPassEncoderDrawIndexed(render_pass, mesh.index_count, 1, 0, 0, 0);
} else {
wgpu.wgpuRenderPassEncoderDraw(render_pass, mesh.vertex_count, 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,14 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
const Node = @import("node.zig");
const Slot = @import("slot.zig");
const Handle = gtl.Handle;
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Edge = union(enum) {
data: struct { producer: Handle(Slot), consumer: Handle(Slot) },
order: struct { from: Handle(Node), to: Handle(Node) },
};

View file

@ -2,6 +2,8 @@
//! ----------------------------------------------------
const gtl = @import("gtl");
const App = @import("app");
const World = @import("world");
const Material = @import("material.zig");
const Transform2D = @import("../../common/component/transform2D.zig");
const Transform3D = @import("../../common/component/transform3D.zig");
@ -13,7 +15,7 @@ const Handle = gtl.Handle;
//
mesh: MeshID,
entity: u64, // TODO: Change to Entity type
entity: World.Entity,
material: Handle(Material),
transform: union(enum) {
@"2D": Transform2D,

View file

@ -1,6 +1,8 @@
const App = @import("app");
const GPUCamera = @import("gpu_camera.zig");
const World = @import("world");
raw: *GPUCamera,
order: u32 = 0,
priority: u32 = 0,
camera: u64, // TODO: Change to ENTITY Type
camera: World.Entity,

View file

@ -0,0 +1,16 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const gtl = @import("gtl");
const Slot = @import("slot.zig");
const RenderCTX = @import("render_ctx.zig");
const Handle = gtl.Handle;
//
// FIELDS
//
name: []const u8,
inputs: []const Handle(Slot) = &.{},
outputs: []const Handle(Slot) = &.{},
run: *const fn (*RenderCTX) anyerror!void,

View file

@ -0,0 +1,14 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const wgpu = @import("wgpu");
const RenderGraph = @import("../resource/render_graph.zig");
//
// FIELDS
//
device: *wgpu.WGPUDeviceImpl,
encoder: *wgpu.WGPUCommandEncoderImpl,
graph: *const RenderGraph,

View file

@ -0,0 +1,19 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const wgpu = @import("wgpu");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Value = union(enum) {
buffer: *wgpu.WGPUBufferImpl,
bind_group: *wgpu.WGPUBindGroupImpl,
texture_view: *wgpu.WGPUTextureViewImpl,
};
//
// FIELDS
//
name: []const u8,
value: ?Value = null,

View file

@ -0,0 +1,54 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const MaterialAsset = @import("material_assets.zig").Asset;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const GPUBindGroup = struct {
raw: *wgpu.WGPUBindGroupImpl,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
const BindingRef = union(enum) {
buffer: struct {},
texture_view: u64,
sampler: u64,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const BindGroup = struct {
layout: u64,
bindings: []const BindingRef,
};
//
// FIELDS
//
groups: std.AutoHashMap(BindGroup, GPUBindGroup),
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.groups = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.groups.valueIterator();
while (it.next()) |group| {
wgpu.wgpuBindGroupRelease(group.raw);
}
self.groups.deinit();
}

View file

@ -1,80 +1,17 @@
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexInput {
@location(0) position: vec3f,
@location(1) normal: vec3f,
@location(2) uv: vec2f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
@location(1) normal: vec3f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct CameraUniform {
view: mat4x4<f32>,
projection: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct ModelUniform {
data: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct Material {
albedo: vec4f,
metallic: f32,
roughness: f32,
_pad0: vec2f,
};
//
// UNIFORMS
//
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
@group(1) @binding(0)
var<uniform> model: ModelUniform;
@group(2) @binding(0)
var<uniform> material: Material;
//
// VERTEX
//
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
// --- # ---
var out: VertexOutput;
// --- # ---
out.position = camera.projection * camera.view * model.data * vec4f(in.position, 1.0);
out.uv = in.uv;
out.normal = in.normal;
// --- # ---
return out;
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f {
var p = vec2f(0.0, 0.0);
if in_vertex_index == 0u {
p = vec2f(-0.5, -0.5);
} else if in_vertex_index == 1u {
p = vec2f(0.5, -0.5);
} else {
p = vec2f(0.0, 0.5);
}
return vec4f(p, 0.0, 1.0);
}
//
// FRAGMENT
//
@fragment
fn fs_main(out: VertexOutput) -> @location(0) vec4f {
var final_color: vec4f = material.albedo;
final_color.x += out.uv.x * 0.2;
final_color.y += out.uv.y * 0.2;
return vec4f(final_color);
fn fs_main() -> @location(0) vec4f {
return vec4f(0.0, 0.4, 1.0, 1.0);
}

View file

@ -0,0 +1,80 @@
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexInput {
@location(0) position: vec3f,
@location(1) normal: vec3f,
@location(2) uv: vec2f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
@location(1) normal: vec3f,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct CameraUniform {
view: mat4x4<f32>,
projection: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct ModelUniform {
data: mat4x4<f32>,
};
// ----------------------------------------------------
// ----------------------------------------------------
struct Material {
albedo: vec4f,
metallic: f32,
roughness: f32,
_pad0: vec2f,
};
//
// UNIFORMS
//
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
@group(1) @binding(0)
var<uniform> model: ModelUniform;
@group(2) @binding(0)
var<uniform> material: Material;
//
// VERTEX
//
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
// --- # ---
var out: VertexOutput;
// --- # ---
out.position = camera.projection * camera.view * model.data * vec4f(in.position, 1.0);
out.uv = in.uv;
out.normal = in.normal;
// --- # ---
return out;
}
//
// FRAGMENT
//
@fragment
fn fs_main(out: VertexOutput) -> @location(0) vec4f {
var final_color: vec4f = material.albedo;
final_color.x += out.uv.x * 0.2;
final_color.y += out.uv.y * 0.2;
return vec4f(final_color);
}

View file

@ -0,0 +1,13 @@
//! ----------------------------------------------------
//! GPU Frame data
//! ----------------------------------------------------
const wgpu = @import("wgpu");
//
// FIELDS
//
surface_texture: wgpu.WGPUSurfaceTexture = .{},
target_view: *wgpu.WGPUTextureViewImpl = undefined,
encoder: *wgpu.WGPUCommandEncoderImpl = undefined,

View file

@ -0,0 +1,89 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const gtl = @import("gtl");
const ShaderID = @import("shader_cache.zig").ShaderID;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Asset = struct {
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Style = struct {
albedo: @Vector(4, f32) = .{ 1, 1, 1, 1 },
metallic: f32 = 0.0,
roughness: f32 = 0.5,
_pad0: [2]f32 = .{ 0, 0 },
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Unique = struct {
shader: ShaderID = .pbr,
front_face: enum {
cw,
ccw,
} = .ccw,
cull_mode: enum {
back,
front,
none,
} = .back,
topology: enum {
triangle_list,
point_list,
line_list,
} = .triangle_list,
blend_mode: enum {
none,
alpha,
additive,
multiply,
} = .none,
};
//
// FIELDS
//
style: Style = .{},
unique: Unique = .{},
};
//
// FIELDS
//
assets: gtl.SlotMap(Asset),
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.assets = .init(alloc),
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
self.assets.deinit();
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn put(self: *Self, asset: Asset) !gtl.Handle(Asset) {
return try self.assets.put(asset);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn get(self: *Self, handle: gtl.Handle(Asset)) ?*Asset {
return self.assets.get(handle);
}

View file

@ -0,0 +1,41 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const gtl = @import("gtl");
const wgpu = @import("wgpu");
const MaterialAsset = @import("material_assets.zig").Asset;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const GPUMaterial = struct {
uniform_buffer: u64,
bind_group: *wgpu.WGPUBindGroupImpl,
};
//
// FIELDS
//
map: std.AutoHashMap(gtl.Handle(MaterialAsset), GPUMaterial),
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.map = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.map.valueIterator();
while (it.next()) |gpu| {
wgpu.wgpuBindGroupRelease(gpu.bind_group);
}
self.map.deinit();
}

View file

@ -0,0 +1,52 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const gtl = @import("gtl");
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Asset = struct {
pub const MeshID = union(enum) {
cube,
quad,
square,
triangle,
custom: u32, // TODO: idk, someday
};
mesh: MeshID = .cube,
};
//
// FIELDS
//
assets: gtl.SlotMap(Asset),
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.assets = .init(alloc),
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
self.assets.deinit();
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn put(self: *Self, asset: Asset) !gtl.Handle(Asset) {
return try self.assets.put(asset);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn get(self: *Self, handle: gtl.Handle(Asset)) ?*Asset {
return self.assets.get(handle);
}

View file

@ -0,0 +1,47 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const ShaderID = @import("shader_cache.zig").ShaderID;
const MaterialAsset = @import("material_assets.zig").Asset;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const GPUPipeline = struct {
raw: *wgpu.WGPURenderPipelineImpl,
};
/// ----------------------------------------------------
/// TODO: Add layout
/// ----------------------------------------------------
pub const Pipeline = struct {
unique: MaterialAsset.Unique,
};
//
// FIELDS
//
data: std.AutoHashMap(Pipeline, GPUPipeline),
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.data = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.data.valueIterator();
while (it.next()) |pipe| {
wgpu.wgpuRenderPipelineRelease(pipe.raw);
}
self.data.deinit();
}

View file

@ -0,0 +1,47 @@
//! ----------------------------------------------------
//! RenderData -> RenderPhase -> Present
//! ----------------------------------------------------
const std = @import("std");
const gtl = @import("gtl");
const math = @import("math");
const World = @import("world");
const MeshAsset = @import("mesh_assets.zig").Asset;
const MaterialAsset = @import("material_assets.zig").Asset;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const ExtractedMesh = struct {
source: World.Entity,
mesh: gtl.Handle(MeshAsset),
material: gtl.Handle(MaterialAsset),
transform: math.Mat4x4,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const ExtractedCamera = struct {
source: World.Entity,
view: math.Mat4x4,
projection: math.Mat4x4,
clear_color: math.Color,
depth: struct { near: f32, far: f32 } = .{ .near = 0.05, .far = 4000.0 },
active: bool = true,
};
//
// FIELDS
//
meshes: std.ArrayList(ExtractedMesh) = .empty,
cameras: std.ArrayList(ExtractedCamera) = .empty,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn clear(self: *Self) void {
self.meshes.clearRetainingCapacity();
self.cameras.clearRetainingCapacity();
}

View file

@ -0,0 +1,359 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const App = @import("app");
const World = @import("world");
const std = @import("std");
const gtl = @import("gtl");
const math = @import("math");
const wgpu = @import("wgpu");
const PipelineCache = @import("pipeline_cache.zig");
const Handle = gtl.Handle;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Node = struct {
name: []const u8,
inputs: std.ArrayList(Handle(Slot)) = .empty,
outputs: std.ArrayList(Handle(Slot)) = .empty,
run: *const fn (*RenderCTX) anyerror!void,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Edge = union(enum) {
data: struct {
producer: Handle(Slot),
consumer: Handle(Slot),
},
order: struct {
from: Handle(Node),
to: Handle(Node),
},
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Slot = struct {
pub const Value = union(enum) {
buffer: *wgpu.WGPUBufferImpl,
sampler: *wgpu.WGPUSamplerImpl,
texture_view: *wgpu.WGPUTextureViewImpl,
};
name: []const u8,
value: ?Value = null,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const RenderCTX = struct {
// --- CORE ---
app: *App,
world: *World,
graph: *Self,
node: ?Handle(Node) = null,
// --- RAW ---
device: *wgpu.WGPUDeviceImpl,
encoder: *wgpu.WGPUCommandEncoderImpl,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn input(self: *RenderCTX, i: usize) !?Slot.Value {
const n = self.node orelse return error.NodeNotSet;
const node = self.graph.nodes.get(n) orelse return error.UnknownNode;
if (i >= node.inputs.items.len) return error.SlotOutOfBounds;
const slot = self.graph.slots.get(node.inputs.items[i]) orelse return error.UnknownSlot;
return slot.value;
}
};
/// ----------------------------------------------------
/// TODO: create Encoder type instead that will return RenderPass
/// ----------------------------------------------------
pub const RenderPass = struct {
raw: *wgpu.WGPURenderPassEncoderImpl,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn begin(
view: *wgpu.WGPUTextureViewImpl,
encoder: *wgpu.WGPUCommandEncoderImpl,
color: math.Color,
) !RenderPass {
return .{
.raw = wgpu.wgpuCommandEncoderBeginRenderPass(
encoder,
&wgpu.WGPURenderPassDescriptor{
.colorAttachmentCount = 1,
.colorAttachments = &wgpu.WGPURenderPassColorAttachment{
.view = view,
.resolveTarget = null,
.loadOp = wgpu.WGPULoadOp_Clear,
.storeOp = wgpu.WGPUStoreOp_Store,
.clearValue = .{
.r = color.r,
.g = color.g,
.b = color.b,
.a = color.a,
},
.depthSlice = wgpu.WGPU_DEPTH_SLICE_UNDEFINED,
},
},
) orelse return error.WGPURenderPassFailed,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn end(self: *const RenderPass) void {
wgpu.wgpuRenderPassEncoderEnd(self.raw);
wgpu.wgpuRenderPassEncoderRelease(self.raw);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn bindPipeline(self: *const RenderPass, pipe: *const PipelineCache.GPUPipeline) void {
wgpu.wgpuRenderPassEncoderSetPipeline(self.raw, pipe.raw);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn draw(
self: *const RenderPass,
vertex_count: u32,
instance_count: u32,
) void {
wgpu.wgpuRenderPassEncoderDraw(self.raw, vertex_count, instance_count, 0, 0);
}
};
//
// FIELDS
//
nodes: gtl.SlotMap(Node),
edges: gtl.SlotMap(Edge),
slots: gtl.SlotMap(Slot),
order: ?[]Handle(Node) = null,
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.nodes = .init(alloc),
.edges = .init(alloc),
.slots = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.nodes.valueIterator();
while (it.next()) |node| {
node.inputs.deinit(self.alloc);
node.outputs.deinit(self.alloc);
}
self.nodes.deinit();
self.edges.deinit();
self.slots.deinit();
if (self.order) |order| {
self.alloc.free(order);
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn run(
self: *Self,
ctx: *RenderCTX,
) !void {
if (self.order) |order| {
for (order) |handle| {
ctx.node = handle;
const node = self.nodes.get(handle) orelse return error.UnknownNode;
try node.run(ctx);
}
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addNode(self: *Self, node: Node) !Handle(Node) {
self.order = null;
return try self.nodes.put(node);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addEdge(self: *Self, edge: Edge) !Handle(Edge) {
self.order = null;
return try self.edges.put(edge);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addSlot(self: *Self, slot: Slot) !Handle(Slot) {
self.order = null;
return try self.slots.put(slot);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addOrderEdge(self: *Self, from: Handle(Node), to: Handle(Node)) !void {
_ = try self.addEdge(.{ .order = .{ .from = from, .to = to } });
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addDataEdge(self: *Self, producer: Handle(Slot), consumer: Handle(Slot)) !void {
_ = try self.addEdge(.{ .data = .{ .producer = producer, .consumer = consumer } });
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn addInput(self: *Self, node: Handle(Node), slot: Slot) !Handle(Slot) {
const h = try self.slots.put(slot);
try self.nodes.get(node).?.inputs.append(self.alloc, h);
return h;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn compile(self: *Self) !void {
if (self.order != null) return;
{ // --- VALIDATE EDGES ---
var it = self.edges.valueIterator();
while (it.next()) |edge| {
switch (edge.*) {
.data => |v| {
if (!self.slots.contains(v.producer)) return error.UnknownSlot;
if (!self.slots.contains(v.consumer)) return error.UnknownSlot;
if (v.producer.eql(v.consumer)) return error.SelfLoop;
},
.order => |v| {
if (!self.nodes.contains(v.from)) return error.UnknownNode;
if (!self.nodes.contains(v.to)) return error.UnknownNode;
},
}
}
}
// --- SLOT OWNER ---
var slot_owner = try self.buildSlotOwners();
defer slot_owner.deinit();
// --- TOPO SORT ---
var indegree: std.AutoHashMap(Handle(Node), u32) = .init(self.alloc);
defer indegree.deinit();
var adj: std.AutoHashMap(Handle(Node), std.ArrayList(Handle(Node))) = .init(self.alloc);
defer {
var it = adj.valueIterator();
while (it.next()) |l| l.deinit(self.alloc);
adj.deinit();
}
{
var it = self.nodes.handleIterator();
while (it.next()) |handle| {
try indegree.put(handle, 0);
try adj.put(handle, .empty);
}
}
{
var it = self.edges.iterator();
while (it.next()) |entry| {
const from = switch (entry.value.*) {
.data => |v| slot_owner.get(v.producer).?,
.order => |v| v.from,
};
const to = switch (entry.value.*) {
.data => |v| slot_owner.get(v.consumer).?,
.order => |v| v.to,
};
const d = indegree.getPtr(to).?;
d.* += 1;
try adj.getPtr(from).?.append(self.alloc, to);
}
}
// --- NERDY SHIT ---
var order: std.ArrayList(Handle(Node)) = .empty;
defer order.deinit(self.alloc);
var ready: std.ArrayList(Handle(Node)) = .empty;
defer ready.deinit(self.alloc);
{
var it = indegree.iterator();
while (it.next()) |entry| {
if (entry.value_ptr.* == 0) try ready.append(self.alloc, entry.key_ptr.*);
}
}
while (ready.pop()) |v| {
try order.append(self.alloc, v);
for (adj.get(v).?.items) |m| {
const d = indegree.getPtr(m).?;
d.* -= 1;
if (d.* == 0) try ready.append(self.alloc, m);
}
}
if (order.items.len != self.nodes.len()) return error.CycleDetected;
// --- RESULT ---
self.order = try order.toOwnedSlice(self.alloc);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn buildSlotOwners(self: *Self) !std.AutoHashMap(Handle(Slot), Handle(Node)) {
// --- OWNERS ---
var owners: std.AutoHashMap(Handle(Slot), Handle(Node)) = .init(self.alloc);
errdefer owners.deinit();
{ // --- PORTS CHECK ---
var it = self.nodes.iterator();
while (it.next()) |entry| {
// --- # ---
const node_handle = entry.handle;
const node: *const Node = entry.value;
// --- ENSURE STUFF ---
const lists = [_][]const Handle(Slot){ node.inputs.items, node.outputs.items };
for (lists) |list| for (list) |slot| {
if (!self.slots.contains(slot)) return error.UnknownSlot;
const gop = try owners.getOrPut(slot);
if (gop.found_existing) return error.SlotHasMultipleOwners;
gop.value_ptr.* = node_handle;
};
}
}
{ // --- ORPHAN CHECK 🥀 ---
var it = self.slots.handleIterator();
while (it.next()) |handle| {
if (!owners.contains(handle)) return error.OrphanSlot;
}
}
return owners;
}

View file

@ -0,0 +1,33 @@
//! ----------------------------------------------------
//! Final render data holder 💪
//! ----------------------------------------------------
const std = @import("std");
const gtl = @import("gtl");
const wgpu = @import("wgpu");
const World = @import("world");
const MaterialAsset = @import("material_assets.zig").Asset;
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const DrawItem = struct {
source: World.Entity,
material: MaterialAsset.Unique,
// TODO: vbuf, ibuf, etc
};
//
// FIELDS
//
opaques: std.ArrayList(DrawItem) = .empty,
transparents: std.ArrayList(DrawItem) = .empty,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn clear(self: *Self) void {
self.opaques.clearRetainingCapacity();
self.transparents.clearRetainingCapacity();
}

View file

@ -0,0 +1,35 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const Self = @This();
pub const GPUSampler = struct { raw: *wgpu.WGPUSamplerImpl };
pub const Sampler = struct { filter: u8, wrap: u8 };
//
// FIELDS
//
map: std.AutoHashMap(Sampler, GPUSampler),
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.map = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.map.valueIterator();
while (it.next()) |sampler| {
wgpu.wgpuSamplerRelease(sampler.raw);
}
self.map.deinit();
}

View file

@ -0,0 +1,54 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const Self = @This();
//
// EMBEDED
//
pub const PBRCode = @embedFile("embeded/shaders/pbr.wgsl");
pub const UnlitCode = @embedFile("embeded/shaders/pbr.wgsl");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const ShaderID = union(enum) {
pbr,
unlit,
custom: u32,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const GPUShader = struct {
raw: *wgpu.WGPUShaderModuleImpl,
};
//
// FIELDS
//
modules: std.AutoHashMap(ShaderID, GPUShader),
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.modules = .init(alloc),
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
var it = self.modules.valueIterator();
while (it.next()) |module| {
wgpu.wgpuShaderModuleRelease(module.raw);
}
self.modules.deinit();
}

View file

@ -0,0 +1,26 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const wgpu = @import("wgpu");
const Self = @This();
//
// FIELDS
//
alloc: std.mem.Allocator,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
_ = self;
}

View file

@ -5,24 +5,44 @@
// PRIVATE
//
const App = @import("app");
const std = @import("std");
const wgpu = @import("wgpu");
const lifetime = @import("system/lifetime.zig");
const frame = @import("system/frame.zig");
const prepare = @import("system/prepare.zig");
const present = @import("system/present.zig");
const App = @import("app");
const World = @import("world");
const Device = @import("../device/resource/wgpu_device.zig");
const Window = @import("../window/resource/gpu_window.zig");
const extract_sys = @import("system/extract.zig");
const prepare_sys = @import("system/prepare.zig");
const present_sys = @import("system/present.zig");
const queue_sys = @import("system/queue.zig");
//
// PUBLIC
//
const Renderer = @import("resource/renderer.zig");
const RenderWorld = @import("resource/render_world.zig");
const RenderLayout = @import("resource/render_layout.zig");
// --- ASSETS ---
pub const MeshAssets = @import("resource/mesh_assets.zig");
pub const MaterialAssets = @import("resource/material_assets.zig");
// --- CACHE ---
pub const SamplerCache = @import("resource/sampler_cache.zig");
pub const TextureCache = @import("resource/texture_cache.zig");
pub const MaterialCache = @import("resource/material_cache.zig");
pub const ShaderCache = @import("resource/shader_cache.zig");
pub const PipelineCache = @import("resource/pipeline_cache.zig");
pub const BindGroupCache = @import("resource/bind_group_cache.zig");
// --- DRAW ---
pub const GPUFrame = @import("resource/gpu_frame.zig");
pub const RenderData = @import("resource/render_data.zig");
pub const RenderPhase = @import("resource/render_phase.zig");
pub const RenderGraph = @import("resource/render_graph.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Renderer",
.build = build,
};
@ -30,27 +50,158 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
// --- LIFETIME ---
try app.schedules.add(App.stage.init, lifetime.init);
try app.schedules.add(App.stage.deinit, lifetime.deinit);
try app.schedules.add(App.stage.init, init);
try app.schedules.add(App.stage.deinit, deinit);
try app.schedules.addMany(App.stage.update, &.{
extract_sys.meshes,
extract_sys.cameras,
// --- FRAME ---
try app.schedules.add(App.stage.begin_frame, frame.begin);
try app.schedules.add(App.stage.end_frame, frame.end);
prepare_sys.shaders,
prepare_sys.pipelines,
// --- PRE DRAW ---
try app.schedules.addManyBefore(App.stage.draw, App.stage.draw, &.{
prepare.shaders,
prepare.pipelines,
queue_sys.meshes,
});
try app.schedules.add(App.stage.draw, present_sys.draw);
}
prepare.cameras,
prepare.objects,
prepare.materials,
/// ----------------------------------------------------
/// ----------------------------------------------------
fn init(app: *App) !void {
// --- # ---
try app.resources.set(MeshAssets.init(app.alloc));
try app.resources.set(MaterialAssets.init(app.alloc));
// --- # ---
try app.resources.set(SamplerCache.init(app.alloc));
try app.resources.set(TextureCache.init(app.alloc));
try app.resources.set(MaterialCache.init(app.alloc));
try app.resources.set(ShaderCache.init(app.alloc));
try app.resources.set(PipelineCache.init(app.alloc));
try app.resources.set(BindGroupCache.init(app.alloc));
// --- # ---
try app.resources.set(GPUFrame{});
try app.resources.set(RenderData{});
try app.resources.set(RenderPhase{});
try app.resources.set(RenderGraph.init(app.alloc));
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
device: *Device,
window: *Window,
render_graph: *RenderGraph,
frame: *GPUFrame,
});
// --- DRAW ---
try app.schedules.add(App.stage.draw, present.draw);
const clear = try res.render_graph.addNode(.{
.name = "clear",
.run = clearNode,
});
// const main_color = try res.render_graph.addSlot(.{ .name = "main_color" });
const main = try res.render_graph.addNode(.{
.name = "main",
// .outputs = &.{
// main_color,
// },
.run = mainNode,
});
// const present_target = try res.render_graph.addSlot(.{ .name = "present_target" });
const present = try res.render_graph.addNode(.{
.name = "present",
// .inputs = &.{
// present_target,
// },
.run = presentNode,
});
try res.render_graph.addOrderEdge(clear, main);
// try res.render_graph.addDataEdge(main_color, present_target);
try res.render_graph.addOrderEdge(main, present);
// --- # ---
try res.render_graph.compile();
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn deinit(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
mesh_assets: *MeshAssets,
material_assets: *MaterialAssets,
shader_cache: *ShaderCache,
pipeline_cache: *PipelineCache,
render_data: *RenderData,
render_phase: *RenderPhase,
render_graph: *RenderGraph,
});
// --- # ---
res.mesh_assets.deinit();
res.material_assets.deinit();
// --- # ---
res.shader_cache.deinit();
res.pipeline_cache.deinit();
// --- # ---
res.render_graph.deinit();
res.render_data.meshes.deinit(app.alloc);
res.render_data.cameras.deinit(app.alloc);
res.render_phase.opaques.deinit(app.alloc);
res.render_phase.transparents.deinit(app.alloc);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn clearNode(ctx: *RenderGraph.RenderCTX) !void {
// --- # ---
const res = try ctx.app.resources.getMany(struct {
frame: *GPUFrame,
pipelines: *PipelineCache,
render_data: *RenderData,
render_phase: *RenderPhase,
});
// --- # ---
for (res.render_data.cameras.items) |camera| {
const pass: RenderGraph.RenderPass = try .begin(
res.frame.target_view,
res.frame.encoder,
camera.clear_color,
);
defer pass.end();
// --- # ---
for (res.render_phase.opaques.items) |draw_item| {
// --- GET PIPELINE ---
const pipeline = res.pipelines.data.getPtr(.{
.unique = draw_item.material,
}) orelse continue;
// --- DRAW ---
pass.bindPipeline(pipeline);
pass.draw(3, 1);
}
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn mainNode(ctx: *RenderGraph.RenderCTX) !void {
_ = ctx;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn presentNode(ctx: *RenderGraph.RenderCTX) !void {
_ = ctx;
}

View file

@ -0,0 +1,100 @@
//! ----------------------------------------------------
//! Extract -> Prepare -> Present
//! ----------------------------------------------------
// --- # ---
const std = @import("std");
const math = @import("math");
// --- RESOURCES ---
const App = @import("app");
const World = @import("world");
const MeshAssets = @import("../resource/mesh_assets.zig");
const MaterialAssets = @import("../resource/material_assets.zig");
const RenderData = @import("../resource/render_data.zig");
// --- COMPONENTS ---
const Mesh = @import("../../common/component/mesh.zig");
const Camera = @import("../../common/component/camera.zig");
const Material = @import("../../common/component/material.zig");
const Transform3D = @import("../../common/component/transform3D.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn meshes(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
mesh_assets: *MeshAssets,
material_assets: *MaterialAssets,
render_data: *RenderData,
});
// --- # ---
var query = res.world.query(struct {
mesh: *Mesh,
material: *Material,
transform: *Transform3D,
}, .{});
while (query.next()) |e| {
// --- TRANSFORM ---
const transform = math.Mat4x4.translate(
e.transform.position.value[0],
e.transform.position.value[1],
e.transform.position.value[2],
)
.mul(e.transform.rotation.toMat4())
.mul(math.Mat4x4.scale(
e.transform.scale.value[0],
e.transform.scale.value[1],
e.transform.scale.value[2],
));
// --- RESULT ---
try res.render_data.meshes.append(app.alloc, .{
.source = query.entity(),
.transform = transform,
.mesh = e.mesh.value,
.material = e.material.value,
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn cameras(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_data: *RenderData,
});
// --- # ---
var query = res.world.query(struct {
camera: *Camera,
transform: *Transform3D,
}, .{});
while (query.next()) |e| {
// --- VIEW ---
const eye = e.transform.position;
const forward = e.transform.rotation.rotate(math.Vec3f.new(.{ 0, 0, 1 }));
const up = e.transform.rotation.rotate(math.Vec3f.new(.{ 0, 1, 0 }));
const view: math.Mat4x4 = .lookAt(eye, eye.add(forward), up);
// --- PROJECTION ---
const projection: math.Mat4x4 = switch (e.camera.projection) {
.perspective => |v| .perspective(std.math.degreesToRadians(v.fov), 800.0 / 600.0, v.near, v.far),
.ortho => |v| .orthographic(800.0, 0, 600.0, 0, v.near, v.far),
};
// --- RESULT ---
try res.render_data.cameras.append(app.alloc, .{
.source = query.entity(),
.projection = projection,
.view = view,
.clear_color = e.camera.clear_color,
});
}
}

View file

@ -1,248 +1,51 @@
//! ----------------------------------------------------
//! Extract -> Prepare -> Present
//! ----------------------------------------------------
// --- # ---
const std = @import("std");
const math = @import("math");
const gtl = @import("gtl");
const wgpu = @import("wgpu");
// --- RESOURCES ---
const App = @import("app");
const Zla = @import("zla");
// --- # ---
const Vertex = @import("../../cache/resource/mesh_cache.zig").Vertex;
const WGPUDevice = @import("../../device/resource/wgpu_device.zig");
const RenderWorld = @import("../resource/render_world.zig");
const RenderLayout = @import("../resource/render_layout.zig");
// --- # ---
const Transform2D = @import("../../common/component/transform2D.zig");
const Transform3D = @import("../../common/component/transform3D.zig");
// --- MESH ---
const CMesh = @import("../../common/component/mesh.zig");
const TMesh = @import("../template/gpu_object.zig");
// --- MATERIAL ---
const CMaterial = @import("../../common/component/material.zig");
const TMaterial = @import("../template/material.zig");
// --- CAMERA ---
const CCamera = @import("../../common/component/camera.zig");
const TCamera = @import("../template/gpu_camera.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn cameras(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = app.world.query(struct {
camera: *const CCamera,
transform2D: ?*const Transform2D,
transform3D: ?*const Transform3D,
}, .{});
while (query.next()) |e| {
// --- GPU ---
const raw = try res.render_world.gpu_cameras.getOrPut(query.entity(), blk: {
// --- # ---
if (res.render_world.gpu_cameras.getByKey(
query.entity(),
)) |v| break :blk v.*;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TCamera.Uniform),
}) orelse return error.Buffer;
// --- VIEW ---
const view: math.Mat4x4 = if (e.transform3D) |t3| cmp: { // 3D
const eye = t3.position;
const forward = t3.rotation.rotate(math.Vec3f.new(.{ 0, 0, 1 }));
const up = t3.rotation.rotate(math.Vec3f.new(.{ 0, 1, 0 }));
break :cmp math.Mat4x4.lookAt(eye, eye.add(forward), up);
} else if (e.transform2D) |t2| cmp: { // 2D
const eye: math.Vec3f = .new(.{ t2.position.value[0], t2.position.value[1], 0 });
break :cmp math.Mat4x4.rotateZ(-t2.angle)
.mul(math.Mat4x4.translate(-eye.value[0], -eye.value[1], 0));
} else math.Mat4x4.identity();
// --- PROJECTION ---
const projection: math.Mat4x4 = switch (e.camera.projection) {
.perspective => |v| .perspective(std.math.degreesToRadians(v.fov), 800.0 / 600.0, v.near, v.far),
.ortho => |v| .orthographic(800.0, 0, 600.0, 0, v.near, v.far),
};
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&TCamera.Uniform{
.view = view.transpose(),
.projection = projection.transpose(),
},
@sizeOf(TCamera.Uniform),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.camera,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TCamera.Uniform),
},
},
}) orelse return error.BindGroup;
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
});
// --- FRAME ---
try res.render_world.frame_views.append(app.alloc, .{
.order = e.camera.order,
.priority = e.camera.priority,
.camera = query.entity(),
.raw = raw,
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn objects(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
});
// --- # ---
var query = app.world.query(struct {
mesh: *const CMesh,
material: *const CMaterial,
transform2D: ?*const Transform2D,
transform3D: ?*const Transform3D,
}, .{});
while (query.next()) |e| {
// --- ENSURE TRANSFORM ---
if (e.transform2D == null and
e.transform3D == null) continue;
// --- GPU ---
_ = try res.render_world.gpu_objects.getOrPut(query.entity(), blk: {
// --- # ---
if (res.render_world.gpu_objects.getByKey(
query.entity(),
)) |v| break :blk v.*;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TMesh.Uniform),
}) orelse return error.Buffer;
// --- MODEL ---
const pos: math.Vec3f = e.transform3D.?.position;
const scl: math.Vec3f = e.transform3D.?.scale;
const rot: math.Quat = e.transform3D.?.rotation;
// --- FINAL MODEL ---
const model =
math.Mat4x4.translate(pos.value[0], pos.value[1], pos.value[2])
.mul(rot.toMat4())
.mul(math.Mat4x4.scale(scl.value[0], scl.value[1], scl.value[2]));
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&TMesh.Uniform{
.data = model.transpose(),
},
@sizeOf(TMesh.Uniform),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.model,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TMesh.Uniform),
},
},
}) orelse return error.BindGroup;
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
});
// --- FRAME ---
try res.render_world.frame_objects.append(app.alloc, .{
.mesh = e.mesh.value,
.entity = query.entity(),
.material = e.material.value,
.transform = blk: {
if (e.transform2D) |v| break :blk .{ .@"2D" = v.* };
if (e.transform3D) |v| break :blk .{ .@"3D" = v.* };
break :blk .{ .@"2D" = .{} };
},
});
}
}
const World = @import("world");
const GPUDevice = @import("../../device/resource/wgpu_device.zig");
const MeshAssets = @import("../resource/mesh_assets.zig");
const MaterialAssets = @import("../resource/material_assets.zig");
const RenderData = @import("../resource/render_data.zig");
const ShaderCache = @import("../resource/shader_cache.zig");
const PipelineCache = @import("../resource/pipeline_cache.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn shaders(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
render_world: *RenderWorld,
device: *WGPUDevice,
world: *World,
device: *GPUDevice,
mesh_assets: *MeshAssets,
material_assets: *MaterialAssets,
render_data: *RenderData,
shader_cache: *ShaderCache,
});
// --- # ---
var query = app.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
for (res.render_data.meshes.items) |extracted_mesh| {
// --- GET MATERIAL ---
const material = res.render_world.materials.get(
e.material.value,
const material = res.material_assets.get(
extracted_mesh.material,
) orelse return error.MaterialNotFound;
// --- ENSURE SHADER ---
_ = try res.render_world.gpu_shaders.getOrPut(material.unique.shader, blk: {
// --- # ---
if (res.render_world.gpu_shaders.contains(
// --- GOP ---
const entry = try res.shader_cache.modules.getOrPut(
material.unique.shader,
)) return;
);
if (!entry.found_existing) {
// --- CODE ---
const code: []const u8 = switch (material.unique.shader) {
.pbr => RenderWorld.PBRCode,
.unlit => RenderWorld.UnlitCode,
.pbr => ShaderCache.PBRCode,
.unlit => ShaderCache.UnlitCode,
.custom => return error.TooLazyToImplement, // TODO: Yes
};
@ -253,7 +56,7 @@ pub fn shaders(app: *App) !void {
};
// --- RESULT ---
break :blk .{
entry.value_ptr.* = .{
.raw = wgpu.wgpuDeviceCreateShaderModule(res.device.raw, &wgpu.WGPUShaderModuleDescriptor{
.label = .{ .data = @tagName(material.unique.shader), .length = wgpu.WGPU_STRLEN },
.nextInChain = &source.chain,
@ -261,7 +64,7 @@ pub fn shaders(app: *App) !void {
return error.ShaderModuleCreationFailed;
},
};
});
}
}
}
@ -270,172 +73,66 @@ pub fn shaders(app: *App) !void {
pub fn pipelines(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
world: *World,
device: *GPUDevice,
mesh_assets: *MeshAssets,
material_assets: *MaterialAssets,
render_data: *RenderData,
shader_cache: *ShaderCache,
pipeline_cache: *PipelineCache,
});
// --- # ---
var query = app.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
for (res.render_data.meshes.items) |extracted_mesh| {
// --- GET MATERIAL ---
const material = res.render_world.materials.get(
e.material.value,
const material = res.material_assets.get(
extracted_mesh.material,
) orelse return error.MaterialNotFound;
// --- ENSURE PIPELINE ---
_ = try res.render_world.gpu_pipelines.getOrPut(material.unique, blk: {
// --- # ---
if (res.render_world.gpu_pipelines.contains(
material.unique,
)) return;
// --- LAYOUT ---
var bind_group_layouts = [_]wgpu.WGPUBindGroupLayout{
res.render_layout.camera,
res.render_layout.model,
res.render_layout.material,
};
// --- PIPELINE LAYOUT ---
const pipeline_layout = wgpu.wgpuDeviceCreatePipelineLayout(
res.device.raw,
&wgpu.WGPUPipelineLayoutDescriptor{
.bindGroupLayoutCount = 3,
.bindGroupLayouts = &bind_group_layouts,
},
) orelse return error.Layout;
// --- CREATE PIPELINE ---
const pipeline = try createPipeline(
material.unique,
pipeline_layout,
if (res.render_world.gpu_shaders.getByKey(
// --- GET SHADER ---
const shader = res.shader_cache.modules.getPtr(
material.unique.shader,
)) |v| v.raw else return error.ShaderNotFound,
res.device.raw,
);
) orelse return error.ShaderNotFound;
// --- RESULT ---
break :blk .{
.raw = pipeline,
.layout = pipeline_layout,
};
});
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn materials(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
render_world: *RenderWorld,
render_layout: *RenderLayout,
device: *WGPUDevice,
// --- GOP ---
const entry = try res.pipeline_cache.data.getOrPut(.{
.unique = material.unique,
});
// --- # ---
var query = app.world.query(struct {
material: *const CMaterial,
}, .{});
while (query.next()) |e| {
// --- ENSURE MATERIAL ---
_ = try res.render_world.gpu_materials.getOrPut(
e.material.value,
blk: {
// --- # ---
if (res.render_world.gpu_materials.contains(
e.material.value,
)) return;
// --- # ---
const material = res.render_world.materials.get(
e.material.value,
) orelse return error.MaterialNotFound;
// --- UNIFORM ---
const uniform = wgpu.wgpuDeviceCreateBuffer(res.device.raw, &wgpu.WGPUBufferDescriptor{
.usage = wgpu.WGPUBufferUsage_CopyDst | wgpu.WGPUBufferUsage_Uniform,
.size = @sizeOf(TMaterial.Style),
}) orelse return error.Buffer;
// --- WRITE TO UNIFORM ---
wgpu.wgpuQueueWriteBuffer(
res.device.queue,
uniform,
0,
&material.style,
@sizeOf(TMaterial.Style),
);
// --- BIND GROUP ---
const bind_group = wgpu.wgpuDeviceCreateBindGroup(res.device.raw, &wgpu.WGPUBindGroupDescriptor{
.layout = res.render_layout.material,
.entryCount = 1,
.entries = &[_]wgpu.WGPUBindGroupEntry{
.{
.binding = 0,
.buffer = uniform,
.offset = 0,
.size = @sizeOf(TMaterial.Style),
},
},
}) orelse return error.BindGroup;
// --- RESULT ---
break :blk .{
.buffer = uniform,
.bind_group = bind_group,
.dirty = false,
};
},
);
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn createPipeline(
key: TMaterial.Unique,
layout: *wgpu.WGPUPipelineLayoutImpl,
shader: *wgpu.WGPUShaderModuleImpl,
device: *wgpu.WGPUDeviceImpl,
) !*wgpu.WGPURenderPipelineImpl {
return wgpu.wgpuDeviceCreateRenderPipeline(device, &wgpu.WGPURenderPipelineDescriptor{
.label = .{ .data = @tagName(key.shader), .length = wgpu.WGPU_STRLEN },
.layout = layout,
if (!entry.found_existing) {
entry.value_ptr.* = .{
.raw = wgpu.wgpuDeviceCreateRenderPipeline(res.device.raw, &wgpu.WGPURenderPipelineDescriptor{
.label = .{ .data = @tagName(material.unique.shader), .length = wgpu.WGPU_STRLEN },
// .layout = layout,
.vertex = .{
.module = shader,
.module = shader.raw,
.entryPoint = .{ .data = "vs_main", .length = wgpu.WGPU_STRLEN },
.bufferCount = 1,
.buffers = &wgpu.WGPUVertexBufferLayout{
.attributeCount = 3,
.attributes = &[_]wgpu.WGPUVertexAttribute{ // TODO: Mabe mabe allow for custom, mabe mabe later tho
.{
.format = wgpu.WGPUVertexFormat_Float32x3,
.offset = @offsetOf(Vertex, "position"),
.shaderLocation = 0,
},
.{
.format = wgpu.WGPUVertexFormat_Float32x3,
.offset = @offsetOf(Vertex, "normal"),
.shaderLocation = 1,
},
.{
.format = wgpu.WGPUVertexFormat_Float32x2,
.offset = @offsetOf(Vertex, "uv"),
.shaderLocation = 2,
},
},
.arrayStride = @sizeOf(Vertex),
.stepMode = wgpu.WGPUVertexStepMode_Vertex,
},
// .bufferCount = 1,
// .buffers = &wgpu.WGPUVertexBufferLayout{
// .attributeCount = 3,
// .attributes = &[_]wgpu.WGPUVertexAttribute{ // TODO: Mabe mabe allow for custom, mabe mabe later tho
// .{
// .format = wgpu.WGPUVertexFormat_Float32x3,
// .offset = @offsetOf(Vertex, "position"),
// .shaderLocation = 0,
// },
// .{
// .format = wgpu.WGPUVertexFormat_Float32x3,
// .offset = @offsetOf(Vertex, "normal"),
// .shaderLocation = 1,
// },
// .{
// .format = wgpu.WGPUVertexFormat_Float32x2,
// .offset = @offsetOf(Vertex, "uv"),
// .shaderLocation = 2,
// },
// },
// .arrayStride = @sizeOf(Vertex),
// .stepMode = wgpu.WGPUVertexStepMode_Vertex,
// },
},
.fragment = &wgpu.WGPUFragmentState{
.module = shader,
.module = shader.raw,
.entryPoint = .{ .data = "fs_main", .length = wgpu.WGPU_STRLEN },
.targetCount = 1,
.targets = &wgpu.WGPUColorTargetState{
@ -456,16 +153,16 @@ fn createPipeline(
},
},
.primitive = .{
.topology = switch (key.topology) {
.topology = switch (material.unique.topology) {
.triangle_list => wgpu.WGPUPrimitiveTopology_TriangleList,
.point_list => wgpu.WGPUPrimitiveTopology_PointList,
.line_list => wgpu.WGPUPrimitiveTopology_LineList,
},
.frontFace = switch (key.front_face) {
.frontFace = switch (material.unique.front_face) {
.ccw => wgpu.WGPUFrontFace_CCW,
.cw => wgpu.WGPUFrontFace_CW,
},
.cullMode = switch (key.cull_mode) {
.cullMode = switch (material.unique.cull_mode) {
.back => wgpu.WGPUCullMode_Back,
.front => wgpu.WGPUCullMode_Front,
.none => wgpu.WGPUCullMode_None,
@ -486,5 +183,15 @@ fn createPipeline(
// },
}) orelse {
return error.PipelineCreationFailed;
},
};
if (gtl.log.print(.debug, "PIPELINE", gtl.ansi.blue)) |v| {
defer v.end();
v.write("shader: {s}", .{@tagName(material.unique.shader)});
v.write("topology: {s}", .{@tagName(material.unique.topology)});
}
}
}
}

View file

@ -3,44 +3,46 @@
const std = @import("std");
const wgpu = @import("wgpu");
const App = @import("app");
const Window = @import("../../window/resource/window.zig");
const Resized = @import("../../window/event/resized.zig");
const Device = @import("../../device/resource/wgpu_device.zig");
const Renderer = @import("../resource/renderer.zig");
const RenderWorld = @import("../resource/render_world.zig");
const MeshCache = @import("../../cache/resource/mesh_cache.zig");
const App = @import("app");
const World = @import("world");
const GPUDevice = @import("../../device/resource/wgpu_device.zig");
const GPUWindow = @import("../../window/resource/gpu_window.zig");
const GPUFrame = @import("../resource/gpu_frame.zig");
const RenderGraph = @import("../resource/render_graph.zig");
const RenderData = @import("../resource/render_data.zig");
const RenderPhase = @import("../resource/render_phase.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn draw(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
window: *Window,
device: *Device,
renderer: *Renderer,
render_world: *RenderWorld,
mesh_cache: *MeshCache,
world: *World,
window: *GPUWindow,
device: *GPUDevice,
frame: *GPUFrame,
render_data: *RenderData,
render_phase: *RenderPhase,
render_graph: *RenderGraph,
});
// --- DO NONE ON WINDOW RESIZE ---
if (app.events.consume(Resized)) |_| {
return;
}
// --- SURFACE TEXTURE ---
var surface_texture: wgpu.WGPUSurfaceTexture = .{};
defer wgpu.wgpuTextureRelease(surface_texture.texture);
defer wgpu.wgpuTextureRelease(
res.frame.surface_texture.texture,
);
// --- GET SURFACE TEXTURE ---
wgpu.wgpuSurfaceGetCurrentTexture(
@ptrCast(res.window.surface),
&surface_texture,
&res.frame.surface_texture,
);
// --- DEBUG ---
switch (surface_texture.status) {
switch (res.frame.surface_texture.status) {
wgpu.WGPUSurfaceGetCurrentTextureStatus_Error => {
std.debug.print("\x1b[31m[SURFACE_TEXTURE_ERROR]\x1b[0m\n", .{});
},
@ -51,109 +53,47 @@ pub fn draw(app: *App) !void {
}
// --- GET SURFACE COLOR VIEW ---
const target_view = wgpu.wgpuTextureCreateView(surface_texture.texture, &wgpu.WGPUTextureViewDescriptor{
.format = wgpu.wgpuTextureGetFormat(surface_texture.texture),
res.frame.target_view = wgpu.wgpuTextureCreateView(res.frame.surface_texture.texture, &wgpu.WGPUTextureViewDescriptor{
.format = wgpu.wgpuTextureGetFormat(res.frame.surface_texture.texture),
.dimension = wgpu.WGPUTextureViewDimension_2D,
.baseMipLevel = 0,
.mipLevelCount = 1,
.baseArrayLayer = 0,
.arrayLayerCount = 1,
.aspect = wgpu.WGPUTextureAspect_All,
});
defer wgpu.wgpuTextureViewRelease(target_view);
}) orelse return error.WGPUTargetView;
defer wgpu.wgpuTextureViewRelease(res.frame.target_view);
// --- ENCODER ---
const encoder = wgpu.wgpuDeviceCreateCommandEncoder(
res.frame.encoder = wgpu.wgpuDeviceCreateCommandEncoder(
res.device.raw,
&wgpu.WGPUCommandEncoderDescriptor{},
) orelse return error.WGPUEncoder;
defer wgpu.wgpuCommandEncoderRelease(res.frame.encoder);
// --- # ---
var render_ctx: RenderGraph.RenderCTX = .{
.app = app,
.world = res.world,
.graph = res.render_graph,
.device = res.device.raw,
.encoder = res.frame.encoder,
};
// --- # ---
try res.render_graph.run(
&render_ctx,
);
defer wgpu.wgpuCommandEncoderRelease(encoder);
// --- FRAME ---
var render_pass: ?*wgpu.WGPURenderPassEncoderImpl = null;
var camera: ?u64 = 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 = switch (v.load_op) {
.load => wgpu.WGPULoadOp_Load,
.clear => wgpu.WGPULoadOp_Clear,
},
.storeOp = switch (v.store_op) {
.store => wgpu.WGPUStoreOp_Store,
.discard => wgpu.WGPUStoreOp_Discard,
},
.clearValue = .{
.r = v.color[0],
.g = v.color[1],
.b = v.color[2],
.a = v.color[3],
},
.depthSlice = wgpu.WGPU_DEPTH_SLICE_UNDEFINED,
},
},
);
// --- SET CAMERA ---
camera = v.camera;
},
.end_pass => {
if (render_pass) |rp| {
wgpu.wgpuRenderPassEncoderEnd(rp);
wgpu.wgpuRenderPassEncoderRelease(rp);
render_pass = null;
}
},
.draw => |v| {
// --- ENSURE ---
const material = res.render_world.materials.get(v.material) orelse return error.MaterialNotFound;
const gpu_material = res.render_world.gpu_materials.getByKey(v.material) orelse return error.GPUMaterialNotFound;
const gpu_camera = res.render_world.gpu_cameras.getByKey(camera orelse return error.CameraNotSet) orelse return error.GPUCameraNotFound;
const pipeline = res.render_world.gpu_pipelines.getByKey(material.unique) orelse return error.GPUPipelineNotFound;
const gpu_mesh = res.render_world.gpu_objects.getByKey(v.entity) orelse return error.GPUMeshNotFound;
const mesh = try res.mesh_cache.getOrCreate(v.mesh);
// --- BIND ---
wgpu.wgpuRenderPassEncoderSetPipeline(render_pass, pipeline.raw);
wgpu.wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, mesh.vbuf, 0, wgpu.wgpuBufferGetSize(mesh.vbuf));
// --- BIND SHADER GROUPS ---
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 0, gpu_camera.bind_group, 0, null);
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 1, gpu_mesh.bind_group, 0, null);
wgpu.wgpuRenderPassEncoderSetBindGroup(render_pass, 2, gpu_material.bind_group, 0, null);
// --- DRAW ---
if (mesh.ibuf) |ibuf| {
wgpu.wgpuRenderPassEncoderSetIndexBuffer(render_pass, ibuf, wgpu.WGPUIndexFormat_Uint32, 0, wgpu.wgpuBufferGetSize(ibuf));
wgpu.wgpuRenderPassEncoderDrawIndexed(render_pass, mesh.index_count, 1, 0, 0, 0);
} else {
wgpu.wgpuRenderPassEncoderDraw(render_pass, mesh.vertex_count, 1, 0, 0);
}
},
else => {},
}
}
// --- FINISH ENCODER | CMD BUFFER ---
const cmd_buff = wgpu.wgpuCommandEncoderFinish(encoder, &.{});
const cmd_buff = wgpu.wgpuCommandEncoderFinish(res.frame.encoder, &.{});
defer wgpu.wgpuCommandBufferRelease(cmd_buff);
// --- SUBMIT AND PRESENT ---
wgpu.wgpuQueueSubmit(res.device.queue, 1, &cmd_buff);
_ = wgpu.wgpuSurfacePresent(@ptrCast(res.window.surface));
// --- CLEAR ---
res.render_data.clear();
res.render_phase.clear();
}

View file

@ -0,0 +1,38 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
// --- # ---
const std = @import("std");
const math = @import("math");
// --- RESOURCES ---
const App = @import("app");
const World = @import("world");
const RenderPhase = @import("../resource/render_phase.zig");
const RenderData = @import("../resource/render_data.zig");
const MaterialAssets = @import("../resource/material_assets.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn meshes(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
world: *World,
render_data: *RenderData,
render_phase: *RenderPhase,
material_assets: *MaterialAssets,
});
for (res.render_data.meshes.items) |mesh| {
// --- GET MATERIAL ---
const material = res.material_assets.get(
mesh.material,
) orelse return error.MaterialNotFound;
// --- # ---
try res.render_phase.opaques.append(app.alloc, .{
.source = mesh.source,
.material = material.unique,
});
}
}

View file

@ -13,7 +13,7 @@ const App = @import("app");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
pub const plugin: App.Plugin = .{
.name = "Template",
.build = build,
};
@ -21,7 +21,7 @@ pub const plugin: App.plugin.Plugin = .{
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
_: *App.Plugin,
app: *App,
) !void {
_ = app;

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

@ -1,5 +1,3 @@
const sdl = @import("sdl");
const sdl_glue = @import("sdl_glue");
raw: *sdl.SDL_Window,
surface: *sdl_glue.WGPUSurfaceImpl,
title: []const u8 = "NYXEngine",
width: u32 = 800,
height: u32 = 600,

View file

@ -5,6 +5,7 @@
// PRIVATE
//
const gtl = @import("gtl");
const sdl = @import("sdl");
const sdl_glue = @import("sdl_glue");
@ -17,6 +18,7 @@ const WGPUDevice = @import("../device/resource/wgpu_device.zig");
//
pub const Window = @import("resource/window.zig");
pub const GPUWindow = @import("resource/gpu_window.zig");
pub const Resized = @import("event/resized.zig");
/// ----------------------------------------------------
@ -46,11 +48,25 @@ pub fn init(app: *App) !void {
WGPUInstance,
) orelse return error.WGPUInstanceNotSet;
// --- ENSURE WINDOW ---
const window = try app.resources.getOrSet(
Window{},
);
// DEBUG
if (gtl.log.print(.debug, "WINDOW", gtl.ansi.yellow)) |v| {
defer v.end();
v.write("Title: \"{s}\"", .{window.title});
v.write("Width: {}", .{window.width});
v.write("Height: {}", .{window.height});
}
// --- WINDOW ---
const raw_window = sdl.SDL_CreateWindow(
"NYXEngine",
800,
600,
window.title.ptr,
@intCast(window.width),
@intCast(window.height),
sdl.SDL_WINDOW_RESIZABLE | sdl.SDL_WINDOW_HIGH_PIXEL_DENSITY,
) orelse return error.CreateWindow;
@ -61,7 +77,7 @@ pub fn init(app: *App) !void {
) orelse return error.GetSurface;
// --- RESOURCE ---
try app.resources.set(Window{
try app.resources.set(GPUWindow{
.raw = raw_window,
.surface = raw_surface,
});
@ -72,7 +88,7 @@ pub fn init(app: *App) !void {
pub fn deinit(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
window: *Window,
window: *GPUWindow,
});
// --- # ---
@ -86,7 +102,7 @@ pub fn deinit(app: *App) !void {
pub fn configure(app: *App) !void {
// --- # ---
const res = try app.resources.getMany(struct {
window: *Window,
window: *GPUWindow,
device: *WGPUDevice,
});

View file

@ -0,0 +1,40 @@
//! ----------------------------------------------------
//! ----------------------------------------------------
//
// PRIVATE
//
const App = @import("app");
const World = @import("world");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.Plugin = .{
.name = "World",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.Plugin,
app: *App,
) !void {
try app.schedules.add(App.stage.init, init);
try app.schedules.add(App.stage.deinit, deinit);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn init(app: *App) !void {
try app.resources.set(World.init(app.alloc));
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn deinit(app: *App) !void {
// --- # ---
const world = try app.resources.require(World);
world.deinit();
}