Merge pull request 'renderer-mvp/refactor-tech' (#50) from renderer-mvp/refactor-tech into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #50
This commit is contained in:
commit
55fb1ce062
|
@ -1,2 +1,4 @@
|
||||||
|
# Generated assets
|
||||||
/zig-cache
|
/zig-cache
|
||||||
/zig-out
|
/zig-out
|
||||||
|
*.glsl.zig
|
||||||
|
|
33
src/main.zig
33
src/main.zig
|
@ -5,8 +5,8 @@ const std = @import("std");
|
||||||
const ona = @import("ona");
|
const ona = @import("ona");
|
||||||
|
|
||||||
const Actors = struct {
|
const Actors = struct {
|
||||||
instances: coral.stack.Sequential(ona.gfx.Queue.Instance2D) = .{.allocator = coral.heap.allocator},
|
instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
|
||||||
body_texture: ona.gfx.Queue.Handle = .none,
|
body_texture: ona.gfx.Handle = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Player = struct {
|
const Player = struct {
|
||||||
|
@ -21,15 +21,9 @@ pub fn main() !void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Queue) !void {
|
fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void {
|
||||||
display.res.resize(1280, 720);
|
display.res.resize(1280, 720);
|
||||||
|
|
||||||
try actors.res.instances.push_many(800, .{
|
|
||||||
.origin = .{75, 75},
|
|
||||||
.xbasis = .{100, 0},
|
|
||||||
.ybasis = .{0, 100},
|
|
||||||
});
|
|
||||||
|
|
||||||
const crap = [_]u32{
|
const crap = [_]u32{
|
||||||
0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
|
0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
|
||||||
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
||||||
|
@ -37,7 +31,7 @@ fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors
|
||||||
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
actors.res.body_texture = try gfx.buffer.open(.{
|
actors.res.body_texture = try gfx.queue.open(.{
|
||||||
.resource = .{
|
.resource = .{
|
||||||
.texture = .{
|
.texture = .{
|
||||||
.data = coral.io.bytes_of(&crap),
|
.data = coral.io.bytes_of(&crap),
|
||||||
|
@ -47,22 +41,33 @@ fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try actors.res.instances.push(.{0, 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(actors: coral.Write(Actors)) void {
|
fn exit(actors: coral.Write(Actors)) void {
|
||||||
actors.res.instances.deinit();
|
actors.res.instances.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(gfx: ona.gfx.Queue, actors: coral.Write(Actors)) !void {
|
fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void {
|
||||||
try gfx.buffer.draw_2d(.{
|
for (actors.res.instances.values) |instance| {
|
||||||
|
try gfx.queue.draw(.{
|
||||||
|
.instance_2d = .{
|
||||||
.mesh_2d = gfx.primitives.quad_mesh,
|
.mesh_2d = gfx.primitives.quad_mesh,
|
||||||
.instances = actors.res.instances.values,
|
|
||||||
.texture = actors.res.body_texture,
|
.texture = actors.res.body_texture,
|
||||||
|
|
||||||
|
.transform = .{
|
||||||
|
.origin = instance,
|
||||||
|
.xbasis = .{100, 0},
|
||||||
|
.ybasis = .{0, 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void {
|
fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void {
|
||||||
actors.res.instances.values[0].origin += .{
|
actors.res.instances.values[0] += .{
|
||||||
mapping.res.axis_strength(player.res.move_x),
|
mapping.res.axis_strength(player.res.move_x),
|
||||||
mapping.res.axis_strength(player.res.move_y),
|
mapping.res.axis_strength(player.res.move_y),
|
||||||
};
|
};
|
||||||
|
|
106
src/ona/gfx.zig
106
src/ona/gfx.zig
|
@ -1,22 +1,21 @@
|
||||||
const App = @import("./App.zig");
|
const App = @import("./App.zig");
|
||||||
|
|
||||||
const Device = @import("./gfx/Device.zig");
|
|
||||||
|
|
||||||
pub const Queue = @import("./gfx/Queue.zig");
|
pub const Queue = @import("./gfx/Queue.zig");
|
||||||
|
|
||||||
pub const color = @import("./gfx/color.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const Device = @import("./gfx/Device.zig");
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
const ext = @import("./ext.zig");
|
||||||
|
|
||||||
const msg = @import("./msg.zig");
|
const msg = @import("./msg.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Color = @Vector(4, f32);
|
||||||
|
|
||||||
pub const Display = struct {
|
pub const Display = struct {
|
||||||
sdl_window: *ext.SDL_Window,
|
sdl_window: *ext.SDL_Window,
|
||||||
clear_color: color.Value = color.black,
|
|
||||||
device: Device,
|
device: Device,
|
||||||
|
|
||||||
pub fn resize(self: Display, width: u16, height: u16) void {
|
pub fn resize(self: Display, width: u16, height: u16) void {
|
||||||
|
@ -35,11 +34,17 @@ pub const Display = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Error = error {
|
pub const Handle = enum (usize) {
|
||||||
SDLError,
|
none,
|
||||||
};
|
_,
|
||||||
|
|
||||||
pub const Handle = Queue.Handle;
|
pub fn index(self: Handle) ?usize {
|
||||||
|
return switch (self) {
|
||||||
|
.none => null,
|
||||||
|
_ => @intFromEnum(self) - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Input = union (enum) {
|
pub const Input = union (enum) {
|
||||||
key_up: Key,
|
key_up: Key,
|
||||||
|
@ -57,8 +62,85 @@ pub const Input = union (enum) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MeshPrimitives = struct {
|
pub const Point2D = @Vector(2, f32);
|
||||||
|
|
||||||
|
pub const Transform2D = extern struct {
|
||||||
|
xbasis: Point2D = .{1, 0},
|
||||||
|
ybasis: Point2D = .{0, 1},
|
||||||
|
origin: Point2D = @splat(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Work = struct {
|
||||||
|
queue: *Queue.Buffer,
|
||||||
|
primitives: *const Primitives,
|
||||||
|
|
||||||
|
const Primitives = struct {
|
||||||
quad_mesh: Handle,
|
quad_mesh: Handle,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const State = struct {
|
||||||
|
queue: *Queue,
|
||||||
|
primitives: *const Primitives,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
|
||||||
|
const queue = try Queue.create();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: {
|
||||||
|
const buffer = queue.pending();
|
||||||
|
const half_extent = 0.5;
|
||||||
|
|
||||||
|
try context.world.set_resource(.none, Primitives{
|
||||||
|
.quad_mesh = try buffer.open(.{
|
||||||
|
.label = "quad mesh primitive",
|
||||||
|
|
||||||
|
.resource = .{
|
||||||
|
.mesh_2d = .{
|
||||||
|
.indices = &.{0, 1, 2, 0, 2, 3},
|
||||||
|
|
||||||
|
.vertices = &.{
|
||||||
|
.{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}},
|
||||||
|
.{.xy = .{half_extent, half_extent}, .uv = .{1, 1}},
|
||||||
|
.{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}},
|
||||||
|
.{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
break: create (try context.register_read_only_resource_access(.none, Primitives)).?;
|
||||||
|
},
|
||||||
|
|
||||||
|
.queue = queue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(state: *State) Work {
|
||||||
|
return .{
|
||||||
|
.queue = state.queue.pending(),
|
||||||
|
.primitives = state.primitives,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unbind(state: *State) void {
|
||||||
|
state.queue.release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const colors = struct {
|
||||||
|
pub const black = greyscale(0);
|
||||||
|
|
||||||
|
pub const white = greyscale(1);
|
||||||
|
|
||||||
|
pub fn greyscale(v: f32) Color {
|
||||||
|
return .{v, v, v, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rgb(r: f32, g: f32, b: f32) Color {
|
||||||
|
return .{r, g, b, 1};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
|
pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
|
||||||
|
@ -74,7 +156,7 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(world: *coral.World, events: App.Events) (Error || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
||||||
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
|
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
|
||||||
return error.SDLError;
|
return error.SDLError;
|
||||||
}
|
}
|
||||||
|
@ -112,5 +194,5 @@ pub fn stop(display: coral.WriteBlocking(Display)) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit(display: coral.WriteBlocking(Display)) void {
|
pub fn submit(display: coral.WriteBlocking(Display)) void {
|
||||||
display.res.device.submit(display.res.sdl_window, display.res.clear_color);
|
display.res.device.submit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +1,144 @@
|
||||||
const Queue = @import("./Queue.zig");
|
const Queue = @import("./Queue.zig");
|
||||||
|
|
||||||
const color = @import("./color.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const draw_2d = @import("./shaders/draw_2d.glsl.zig");
|
|
||||||
|
|
||||||
const ext = @import("../ext.zig");
|
const ext = @import("../ext.zig");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
thread: std.Thread,
|
thread: std.Thread,
|
||||||
render_state: *RenderState,
|
clear_color: gfx.Color = gfx.colors.black,
|
||||||
|
state: *State,
|
||||||
|
|
||||||
const AtomicBool = std.atomic.Value(bool);
|
const AtomicBool = std.atomic.Value(bool);
|
||||||
|
|
||||||
const RenderWork = struct {
|
const Frame = struct {
|
||||||
pipeline_2d: sokol.gfx.Pipeline,
|
width: u16,
|
||||||
instance_2d_sampler: sokol.gfx.Sampler,
|
height: u16,
|
||||||
instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
flushed_instance_2d_count: usize = 0,
|
||||||
|
pushed_instance_2d_count: usize = 0,
|
||||||
|
mesh_2d: gfx.Handle = .none,
|
||||||
|
texture: gfx.Handle = .none,
|
||||||
|
|
||||||
|
fn unflushed_instance_2d_count(self: Frame) usize {
|
||||||
|
return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Render = struct {
|
||||||
resources: coral.stack.Sequential(Resource),
|
resources: coral.stack.Sequential(Resource),
|
||||||
|
instance_2d_pipeline: sokol.gfx.Pipeline,
|
||||||
|
instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
||||||
|
|
||||||
const Resource = union (enum) {
|
const Instance2D = extern struct {
|
||||||
mesh_2d: struct {
|
transform: gfx.Transform2D,
|
||||||
index_count: u32,
|
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||||
vertex_buffer: sokol.gfx.Buffer,
|
depth: f32 = 0,
|
||||||
index_buffer: sokol.gfx.Buffer,
|
texture_offset: gfx.Point2D = @splat(0),
|
||||||
},
|
texture_size: gfx.Point2D = @splat(1),
|
||||||
|
|
||||||
texture: struct {
|
|
||||||
image: sokol.gfx.Image,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const buffer_indices = .{
|
const buffer_indices = .{
|
||||||
.mesh = 0,
|
.mesh = 0,
|
||||||
.instance = 1,
|
.instance = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn deinit(self: *RenderWork) void {
|
const instances_per_buffer = 512;
|
||||||
sokol.gfx.destroyPipeline(self.pipeline_2d);
|
|
||||||
|
|
||||||
|
const shader = @import("./shaders/instance_2d.glsl.zig");
|
||||||
|
};
|
||||||
|
|
||||||
|
const Resource = union (enum) {
|
||||||
|
empty,
|
||||||
|
mesh_2d: Mesh2D,
|
||||||
|
texture: Texture,
|
||||||
|
|
||||||
|
const Mesh2D = struct {
|
||||||
|
index_count: u32,
|
||||||
|
vertex_buffer: sokol.gfx.Buffer,
|
||||||
|
index_buffer: sokol.gfx.Buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Texture = struct {
|
||||||
|
image: sokol.gfx.Image,
|
||||||
|
sampler: sokol.gfx.Sampler,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn deinit(self: *Render) void {
|
||||||
for (self.instance_2d_buffers.values) |buffer| {
|
for (self.instance_2d_buffers.values) |buffer| {
|
||||||
sokol.gfx.destroyBuffer(buffer);
|
sokol.gfx.destroyBuffer(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.instance_2d_buffers.deinit();
|
self.instance_2d_buffers.deinit();
|
||||||
|
sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
|
||||||
|
self.resources.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
for (self.resources.values) |resource| {
|
fn init() Render {
|
||||||
switch (resource) {
|
sokol.gfx.setup(.{
|
||||||
.mesh_2d => |mesh_2d| {
|
.environment = .{
|
||||||
sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
|
.defaults = .{
|
||||||
sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
|
.color_format = .RGBA8,
|
||||||
|
.depth_format = .DEPTH_STENCIL,
|
||||||
|
.sample_count = 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.texture => |texture| {
|
.logger = .{
|
||||||
sokol.gfx.destroyImage(texture.image);
|
.func = sokol.log.func,
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
self.resources.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) RenderWork {
|
|
||||||
return .{
|
return .{
|
||||||
.pipeline_2d = sokol.gfx.makePipeline(.{
|
.instance_2d_pipeline = sokol.gfx.makePipeline(.{
|
||||||
.label = "2D drawing pipeline",
|
.label = "2D drawing pipeline",
|
||||||
|
|
||||||
.layout = .{
|
.layout = .{
|
||||||
.attrs = get: {
|
.attrs = get: {
|
||||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_mesh_xy] = .{
|
attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
|
||||||
.format = .FLOAT2,
|
.format = .FLOAT2,
|
||||||
.buffer_index = buffer_indices.mesh,
|
.buffer_index = Instance2D.buffer_indices.mesh,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_mesh_uv] = .{
|
attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
|
||||||
.format = .FLOAT2,
|
.format = .FLOAT2,
|
||||||
.buffer_index = buffer_indices.mesh,
|
.buffer_index = Instance2D.buffer_indices.mesh,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_xbasis] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
|
||||||
.format = .FLOAT2,
|
.format = .FLOAT2,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_ybasis] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
|
||||||
.format = .FLOAT2,
|
.format = .FLOAT2,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_origin] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
|
||||||
.format = .FLOAT2,
|
.format = .FLOAT2,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_color] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
|
||||||
.format = .UBYTE4N,
|
.format = .UBYTE4N,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_depth] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
|
||||||
.format = .FLOAT,
|
.format = .FLOAT,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
attrs[draw_2d.ATTR_vs_instance_rect] = .{
|
attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
|
||||||
.format = .FLOAT4,
|
.format = .FLOAT4,
|
||||||
.buffer_index = buffer_indices.instance,
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
break: get attrs;
|
break: get attrs;
|
||||||
|
@ -122,64 +147,68 @@ const RenderWork = struct {
|
||||||
.buffers = get: {
|
.buffers = get: {
|
||||||
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||||
|
|
||||||
buffers[buffer_indices.instance].step_func = .PER_INSTANCE;
|
buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
|
||||||
|
|
||||||
break: get buffers;
|
break: get buffers;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.shader = sokol.gfx.makeShader(draw_2d.draw2dShaderDesc(sokol.gfx.queryBackend())),
|
.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
|
||||||
.index_type = .UINT16,
|
.index_type = .UINT16,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
.instance_2d_sampler = sokol.gfx.makeSampler(.{
|
|
||||||
.label = "instance 2D sampler",
|
|
||||||
}),
|
|
||||||
|
|
||||||
.instance_2d_buffers = .{.allocator = coral.heap.allocator},
|
.instance_2d_buffers = .{.allocator = coral.heap.allocator},
|
||||||
.resources = .{.allocator = allocator},
|
.resources = .{.allocator = coral.heap.allocator},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_draw_2d_commands(self: *RenderWork, commands: []const Queue.Buffer.Draw2DCommand, target: Queue.Target) std.mem.Allocator.Error!void {
|
fn insert_resource(self: *Render, handle: gfx.Handle, resource: Resource) !void {
|
||||||
const max_instances = 512;
|
const handle_index = handle.index() orelse {
|
||||||
var instance_2d_buffers_used = @as(usize, 0);
|
return error.InvalidHandle;
|
||||||
|
};
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(self.pipeline_2d);
|
const resource_count = self.resources.len();
|
||||||
|
|
||||||
sokol.gfx.applyUniforms(.VS, draw_2d.SLOT_Screen, sokol.gfx.asRange(&draw_2d.Screen{
|
if (handle_index < resource_count) {
|
||||||
.screen_size = .{target.width, target.height},
|
const empty_resource = &self.resources.values[handle_index];
|
||||||
|
|
||||||
|
if (empty_resource.* != .empty) {
|
||||||
|
return error.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_resource.* = resource;
|
||||||
|
} else {
|
||||||
|
if (handle_index != resource_count) {
|
||||||
|
return error.InvalidIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.resources.push(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_instance_2ds(self: *Render, frame: *Frame) void {
|
||||||
|
const unflushed_count = frame.unflushed_instance_2d_count();
|
||||||
|
|
||||||
|
if (unflushed_count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sokol.gfx.applyPipeline(self.instance_2d_pipeline);
|
||||||
|
|
||||||
|
sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
|
||||||
|
.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (commands) |command| {
|
const mesh_2d = self.resources.values[frame.mesh_2d.index().?].mesh_2d;
|
||||||
const mesh_2d = &self.resources.values[command.mesh_2d.index().?].mesh_2d;
|
const texture = self.resources.values[frame.texture.index().?].texture;
|
||||||
const texture = &self.resources.values[command.texture.index().?].texture;
|
|
||||||
const instance_size = @sizeOf(Queue.Instance2D);
|
|
||||||
const full_instance_buffer_count = command.instances.len / max_instances;
|
|
||||||
|
|
||||||
for (0 .. full_instance_buffer_count) |i| {
|
var bindings = sokol.gfx.Bindings{
|
||||||
defer instance_2d_buffers_used += 1;
|
.vertex_buffers = get: {
|
||||||
|
|
||||||
if (instance_2d_buffers_used == self.instance_2d_buffers.len()) {
|
|
||||||
const instance_2d_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.size = @sizeOf(Queue.Instance2D) * max_instances,
|
|
||||||
.usage = .STREAM,
|
|
||||||
.label = "2D drawing instance buffer",
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyBuffer(instance_2d_buffer);
|
|
||||||
|
|
||||||
try self.instance_2d_buffers.push(instance_2d_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
sokol.gfx.applyBindings(.{
|
|
||||||
.vertex_buffers = get_buffers: {
|
|
||||||
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
||||||
|
|
||||||
buffers[buffer_indices.instance] = self.instance_2d_buffers.values[instance_2d_buffers_used];
|
buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
|
||||||
buffers[buffer_indices.mesh] = mesh_2d.vertex_buffer;
|
|
||||||
|
|
||||||
break: get_buffers buffers;
|
break: get buffers;
|
||||||
},
|
},
|
||||||
|
|
||||||
.index_buffer = mesh_2d.index_buffer,
|
.index_buffer = mesh_2d.index_buffer,
|
||||||
|
@ -196,79 +225,148 @@ const RenderWork = struct {
|
||||||
.samplers = get: {
|
.samplers = get: {
|
||||||
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
|
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
|
||||||
|
|
||||||
samplers[0] = self.instance_2d_sampler;
|
samplers[0] = texture.sampler;
|
||||||
|
|
||||||
break: get samplers;
|
break: get samplers;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
sokol.gfx.updateBuffer(self.instance_2d_buffers.values[instance_2d_buffers_used], .{
|
while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
|
||||||
.ptr = command.instances.ptr + (max_instances * i),
|
const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
|
||||||
.size = instance_size * max_instances,
|
const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
|
||||||
});
|
const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
|
||||||
|
|
||||||
sokol.gfx.draw(0, mesh_2d.index_count, max_instances);
|
bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
|
||||||
|
bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
|
||||||
|
|
||||||
|
sokol.gfx.applyBindings(bindings);
|
||||||
|
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
|
||||||
|
|
||||||
|
frame.flushed_instance_2d_count += instances_to_flush;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer instance_2d_buffers_used += 1;
|
fn push_instance_2d(self: *Render, frame: *Frame, command: Queue.DrawCommand.Instance) std.mem.Allocator.Error!void {
|
||||||
|
if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
|
||||||
|
self.flush_instance_2ds(frame);
|
||||||
|
}
|
||||||
|
|
||||||
if (instance_2d_buffers_used == self.instance_2d_buffers.len()) {
|
frame.mesh_2d = command.mesh_2d;
|
||||||
const instance_2d_buffer = sokol.gfx.makeBuffer(.{
|
frame.texture = command.texture;
|
||||||
.size = @sizeOf(Queue.Instance2D) * max_instances,
|
|
||||||
|
const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
|
||||||
|
const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
|
||||||
|
|
||||||
|
if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
|
||||||
|
const instance_buffer = sokol.gfx.makeBuffer(.{
|
||||||
|
.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
|
||||||
.usage = .STREAM,
|
.usage = .STREAM,
|
||||||
.label = "2D drawing instance buffer",
|
.label = "2D drawing instance buffer",
|
||||||
});
|
});
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyBuffer(instance_2d_buffer);
|
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
||||||
|
|
||||||
try self.instance_2d_buffers.push(instance_2d_buffer);
|
try self.instance_2d_buffers.push(instance_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
sokol.gfx.applyBindings(.{
|
_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
|
||||||
.vertex_buffers = get_buffers: {
|
.transform = command.transform,
|
||||||
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
}));
|
||||||
|
|
||||||
buffers[buffer_indices.instance] = self.instance_2d_buffers.values[instance_2d_buffers_used];
|
frame.pushed_instance_2d_count += 1;
|
||||||
buffers[buffer_indices.mesh] = mesh_2d.vertex_buffer;
|
}
|
||||||
|
|
||||||
break: get_buffers buffers;
|
fn remove_resource(self: *Render, handle: gfx.Handle) ?Resource {
|
||||||
},
|
if (handle.index()) |handle_index| {
|
||||||
|
const resource = self.resources.values[handle_index];
|
||||||
|
|
||||||
.fs = .{
|
if (resource != .empty) {
|
||||||
.images = get: {
|
self.resources.values[handle_index] = .empty;
|
||||||
var images = [_]sokol.gfx.Image{.{}} ** 12;
|
|
||||||
|
|
||||||
images[0] = texture.image;
|
return resource;
|
||||||
|
|
||||||
break: get images;
|
|
||||||
},
|
|
||||||
|
|
||||||
.samplers = get: {
|
|
||||||
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
|
|
||||||
|
|
||||||
samplers[0] = self.instance_2d_sampler;
|
|
||||||
|
|
||||||
break: get samplers;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.index_buffer = mesh_2d.index_buffer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const remaining_instances = command.instances.len % max_instances;
|
|
||||||
|
|
||||||
sokol.gfx.updateBuffer(self.instance_2d_buffers.values[instance_2d_buffers_used], .{
|
|
||||||
.ptr = command.instances.ptr + full_instance_buffer_count,
|
|
||||||
.size = instance_size * remaining_instances,
|
|
||||||
});
|
|
||||||
|
|
||||||
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(remaining_instances));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_open_commands(self: *RenderWork, commands: []const Queue.Buffer.OpenCommand) std.mem.Allocator.Error!void {
|
return null;
|
||||||
for (commands) |command| {
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const State = struct {
|
||||||
|
finished: std.Thread.Semaphore = .{},
|
||||||
|
is_running: AtomicBool = AtomicBool.init(true),
|
||||||
|
ready: std.Thread.Semaphore = .{},
|
||||||
|
clear_color: gfx.Color = gfx.colors.black,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.state.is_running.store(false, .monotonic);
|
||||||
|
self.state.ready.post();
|
||||||
|
self.thread.join();
|
||||||
|
coral.heap.allocator.destroy(self.state);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self {
|
||||||
|
const state = try coral.heap.allocator.create(State);
|
||||||
|
|
||||||
|
errdefer coral.heap.allocator.destroy(state);
|
||||||
|
|
||||||
|
state.* = .{};
|
||||||
|
|
||||||
|
const thread = try std.Thread.spawn(.{}, run, .{window, state});
|
||||||
|
|
||||||
|
thread.setName("Ona Graphics") catch {
|
||||||
|
std.log.warn("failed to name the graphics thread", .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.thread = thread,
|
||||||
|
.state = state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_close_command(command: Queue.CloseCommand, rendering: *Render) !void {
|
||||||
|
const resource = &rendering.resources.values[command.handle.index().?];
|
||||||
|
|
||||||
|
switch (resource.*) {
|
||||||
|
.empty => {}, // TODO: Handle this.
|
||||||
|
|
||||||
|
.mesh_2d => |mesh_2d| {
|
||||||
|
sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
|
||||||
|
sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
.texture => |texture| {
|
||||||
|
sokol.gfx.destroyImage(texture.image);
|
||||||
|
sokol.gfx.destroySampler(texture.sampler);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.* = .empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_draw_command(command: Queue.DrawCommand, render: *Render, frame: *Frame) !void {
|
||||||
|
switch (command) {
|
||||||
|
.instance_2d => |instance_2d| {
|
||||||
|
try render.push_instance_2d(frame, instance_2d);
|
||||||
|
},
|
||||||
|
|
||||||
|
.post_process => |post_process| {
|
||||||
|
render.flush_instance_2ds(frame);
|
||||||
|
// sokol.gfx.applyPipeline(self.post_process_pipeline);
|
||||||
|
|
||||||
|
_ = post_process;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render.flush_instance_2ds(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_open_command(command: Queue.OpenCommand, render: *Render) !void {
|
||||||
switch (command.resource) {
|
switch (command.resource) {
|
||||||
.texture => |texture| {
|
.texture => |texture| {
|
||||||
const stride = texture.width * texture.format.byte_size();
|
const stride = texture.width * texture.format.byte_size();
|
||||||
|
@ -290,8 +388,13 @@ const RenderWork = struct {
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyImage(image);
|
errdefer sokol.gfx.destroyImage(image);
|
||||||
|
|
||||||
try self.resources.push(.{
|
const sampler = sokol.gfx.makeSampler(.{});
|
||||||
|
|
||||||
|
errdefer sokol.gfx.destroySampler(sampler);
|
||||||
|
|
||||||
|
try render.insert_resource(command.handle, .{
|
||||||
.texture = .{
|
.texture = .{
|
||||||
|
.sampler = sampler,
|
||||||
.image = image,
|
.image = image,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -317,7 +420,7 @@ const RenderWork = struct {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.resources.push(.{
|
try render.insert_resource(command.handle, .{
|
||||||
.mesh_2d = .{
|
.mesh_2d = .{
|
||||||
.index_buffer = index_buffer,
|
.index_buffer = index_buffer,
|
||||||
.vertex_buffer = vertex_buffer,
|
.vertex_buffer = vertex_buffer,
|
||||||
|
@ -326,62 +429,10 @@ const RenderWork = struct {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_queue(self: *RenderWork, buffer: *const Queue.Buffer, target: Queue.Target) std.mem.Allocator.Error!void {
|
|
||||||
try self.process_open_commands(buffer.open_commands.values);
|
|
||||||
try self.process_draw_2d_commands(buffer.draw_2d_commands.values, target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const RenderState = struct {
|
|
||||||
finished: std.Thread.Semaphore = .{},
|
|
||||||
is_running: AtomicBool = AtomicBool.init(true),
|
|
||||||
ready: std.Thread.Semaphore = .{},
|
|
||||||
clear_color: color.Value = color.compress(color.black),
|
|
||||||
pixel_width: c_int = 0,
|
|
||||||
pixel_height: c_int = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.render_state.is_running.store(false, .monotonic);
|
|
||||||
self.render_state.ready.post();
|
|
||||||
self.thread.join();
|
|
||||||
coral.heap.allocator.destroy(self.render_state);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(sdl_window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self {
|
fn run(window: *ext.SDL_Window, state: *State) !void {
|
||||||
const render_state = try coral.heap.allocator.create(RenderState);
|
const context = configure_and_create: {
|
||||||
|
|
||||||
errdefer coral.heap.allocator.destroy(render_state);
|
|
||||||
|
|
||||||
render_state.* = .{};
|
|
||||||
|
|
||||||
const self = Self{
|
|
||||||
.thread = spawn_thread: {
|
|
||||||
const thread = try std.Thread.spawn(.{}, run, .{sdl_window, render_state});
|
|
||||||
|
|
||||||
thread.setName("Ona Graphics") catch {
|
|
||||||
std.log.warn("failed to name the graphics thread", .{});
|
|
||||||
};
|
|
||||||
|
|
||||||
break: spawn_thread thread;
|
|
||||||
},
|
|
||||||
|
|
||||||
.render_state = render_state,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.submit(sdl_window, .{0, 0, 0, 1});
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void {
|
|
||||||
var result = @as(c_int, 0);
|
var result = @as(c_int, 0);
|
||||||
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||||
|
@ -391,14 +442,11 @@ fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void {
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
std.log.err("failed to set necessary OpenGL flags in graphics", .{});
|
return error.Unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = ext.SDL_GL_CreateContext(sdl_window);
|
break: configure_and_create ext.SDL_GL_CreateContext(window);
|
||||||
|
};
|
||||||
defer ext.SDL_GL_DeleteContext(context);
|
|
||||||
|
|
||||||
render_state.finished.post();
|
|
||||||
|
|
||||||
sokol.gfx.setup(.{
|
sokol.gfx.setup(.{
|
||||||
.environment = .{
|
.environment = .{
|
||||||
|
@ -414,21 +462,38 @@ fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
defer sokol.gfx.shutdown();
|
defer {
|
||||||
|
sokol.gfx.shutdown();
|
||||||
|
ext.SDL_GL_DeleteContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
var render_work = RenderWork.init(coral.heap.allocator);
|
var render = Render.init();
|
||||||
|
|
||||||
defer render_work.deinit();
|
defer render.deinit();
|
||||||
|
|
||||||
while (render_state.is_running.load(.monotonic)) {
|
state.finished.post();
|
||||||
render_state.ready.wait();
|
|
||||||
|
|
||||||
defer render_state.finished.post();
|
while (state.is_running.load(.monotonic)) {
|
||||||
|
state.ready.wait();
|
||||||
|
|
||||||
|
defer state.finished.post();
|
||||||
|
|
||||||
|
var frame = init_frame: {
|
||||||
|
var width, var height = [_]c_int{0, 0};
|
||||||
|
|
||||||
|
ext.SDL_GL_GetDrawableSize(window, &width, &height);
|
||||||
|
std.debug.assert(width > 0 and height > 0);
|
||||||
|
|
||||||
|
break: init_frame Frame{
|
||||||
|
.width = @intCast(width),
|
||||||
|
.height = @intCast(height),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sokol.gfx.beginPass(.{
|
sokol.gfx.beginPass(.{
|
||||||
.swapchain = .{
|
.swapchain = .{
|
||||||
.width = render_state.pixel_width,
|
.width = frame.width,
|
||||||
.height = render_state.pixel_height,
|
.height = frame.height,
|
||||||
.sample_count = 1,
|
.sample_count = 1,
|
||||||
.color_format = .RGBA8,
|
.color_format = .RGBA8,
|
||||||
.depth_format = .DEPTH_STENCIL,
|
.depth_format = .DEPTH_STENCIL,
|
||||||
|
@ -441,32 +506,29 @@ fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void {
|
||||||
|
|
||||||
actions[0] = .{
|
actions[0] = .{
|
||||||
.load_action = .CLEAR,
|
.load_action = .CLEAR,
|
||||||
.clear_value = @as(sokol.gfx.Color, @bitCast(render_state.clear_color)),
|
.clear_value = @as(sokol.gfx.Color, @bitCast(state.clear_color)),
|
||||||
};
|
};
|
||||||
|
|
||||||
break: get actions;
|
break: get actions;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try Queue.consume_submitted(Queue.Consumer.bind(RenderWork, &render_work, RenderWork.process_queue), .{
|
try Queue.visit_open_commands(process_open_command, .{&render});
|
||||||
.width = @floatFromInt(render_state.pixel_width),
|
try Queue.visit_draw_commands(process_draw_command, .{&render, &frame});
|
||||||
.height = @floatFromInt(render_state.pixel_height),
|
try Queue.visit_close_commands(process_close_command, .{&render});
|
||||||
});
|
|
||||||
|
|
||||||
sokol.gfx.endPass();
|
sokol.gfx.endPass();
|
||||||
sokol.gfx.commit();
|
sokol.gfx.commit();
|
||||||
ext.SDL_GL_SwapWindow(sdl_window);
|
ext.SDL_GL_SwapWindow(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit(self: Self, sdl_window: *ext.SDL_Window, clear_color: color.Value) void {
|
pub fn submit(self: *Self) void {
|
||||||
self.render_state.finished.wait();
|
self.state.finished.wait();
|
||||||
ext.SDL_GL_GetDrawableSize(sdl_window, &self.render_state.pixel_width, &self.render_state.pixel_height);
|
|
||||||
std.debug.assert(self.render_state.pixel_width > 0 and self.render_state.pixel_height > 0);
|
|
||||||
|
|
||||||
self.render_state.clear_color = clear_color;
|
self.state.clear_color = self.clear_color;
|
||||||
|
|
||||||
Queue.swap();
|
Queue.swap();
|
||||||
self.render_state.ready.post();
|
self.state.ready.post();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
const color = @import("./color.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
buffer: *Buffer,
|
buffers: [2]Buffer,
|
||||||
primitives: *const Primitives,
|
is_swapped: bool = false,
|
||||||
|
ref_count: AtomicCount = AtomicCount.init(1),
|
||||||
|
has_next: ?*Self = null,
|
||||||
|
has_prev: ?*Self = null,
|
||||||
|
|
||||||
const AtomicCount = std.atomic.Value(usize);
|
const AtomicCount = std.atomic.Value(usize);
|
||||||
|
|
||||||
|
@ -13,65 +16,24 @@ pub const Buffer = struct {
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
closed_handles: coral.stack.Sequential(usize),
|
closed_handles: coral.stack.Sequential(usize),
|
||||||
open_commands: coral.stack.Sequential(OpenCommand),
|
open_commands: coral.stack.Sequential(OpenCommand),
|
||||||
draw_2d_commands: coral.stack.Sequential(Draw2DCommand),
|
draw_commands: coral.stack.Sequential(DrawCommand),
|
||||||
close_commands: coral.stack.Sequential(CloseCommand),
|
close_commands: coral.stack.Sequential(CloseCommand),
|
||||||
|
|
||||||
pub const CloseCommand = struct {
|
pub fn clear(self: *Buffer) void {
|
||||||
handle: Handle,
|
self.close_commands.clear();
|
||||||
};
|
self.draw_commands.clear();
|
||||||
|
self.open_commands.clear();
|
||||||
|
|
||||||
pub const Draw2DCommand = struct {
|
if (!self.arena.reset(.retain_capacity)) {
|
||||||
instances: []const Instance2D,
|
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
||||||
texture: Handle,
|
}
|
||||||
mesh_2d: Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OpenCommand = struct {
|
|
||||||
handle: Handle = .none,
|
|
||||||
label: ?[]const u8 = null,
|
|
||||||
|
|
||||||
resource: union (enum) {
|
|
||||||
texture: Texture,
|
|
||||||
mesh_2d: Mesh2D,
|
|
||||||
},
|
|
||||||
|
|
||||||
pub const Mesh2D = struct {
|
|
||||||
vertices: []const Vertex2D,
|
|
||||||
indices: []const u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Texture = struct {
|
|
||||||
data: []const coral.io.Byte,
|
|
||||||
width: u16,
|
|
||||||
format: Format,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = enum {
|
|
||||||
static,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Format = enum {
|
|
||||||
rgba8888,
|
|
||||||
bgra8888,
|
|
||||||
argb8888,
|
|
||||||
rgb888,
|
|
||||||
bgr888,
|
|
||||||
|
|
||||||
pub fn byte_size(self: Format) usize {
|
|
||||||
return switch (self) {
|
|
||||||
.rgba8888, .bgra8888, .argb8888 => 4,
|
|
||||||
.rgb888, .bgr888 => 3,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fn deinit(self: *Buffer) void {
|
fn deinit(self: *Buffer) void {
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.closed_handles.deinit();
|
self.closed_handles.deinit();
|
||||||
self.open_commands.deinit();
|
self.open_commands.deinit();
|
||||||
self.draw_2d_commands.deinit();
|
self.draw_commands.deinit();
|
||||||
self.close_commands.deinit();
|
self.close_commands.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,31 +42,20 @@ pub const Buffer = struct {
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.closed_handles = .{.allocator = allocator},
|
.closed_handles = .{.allocator = allocator},
|
||||||
.open_commands = .{.allocator = allocator},
|
.open_commands = .{.allocator = allocator},
|
||||||
.draw_2d_commands = .{.allocator = allocator},
|
.draw_commands = .{.allocator = allocator},
|
||||||
.close_commands = .{.allocator = allocator},
|
.close_commands = .{.allocator = allocator},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(self: *Buffer) void {
|
pub fn draw(self: *Buffer, command: DrawCommand) std.mem.Allocator.Error!void {
|
||||||
self.close_commands.clear();
|
try self.draw_commands.push(switch (command) {
|
||||||
self.draw_2d_commands.clear();
|
.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
|
||||||
self.open_commands.clear();
|
.post_process => |post_process| .{.post_process = post_process},
|
||||||
|
|
||||||
if (!self.arena.reset(.retain_capacity)) {
|
|
||||||
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_2d(self: *Buffer, command: Draw2DCommand) std.mem.Allocator.Error!void {
|
|
||||||
try self.draw_2d_commands.push(.{
|
|
||||||
.instances = try self.arena.allocator().dupe(Instance2D, command.instances),
|
|
||||||
.texture = command.texture,
|
|
||||||
.mesh_2d = command.mesh_2d,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!Handle {
|
pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!gfx.Handle {
|
||||||
const reserved_handle = @as(Handle, switch (command.handle) {
|
const reserved_handle = @as(gfx.Handle, switch (command.handle) {
|
||||||
.none => @enumFromInt(reserve_handle: {
|
.none => @enumFromInt(reserve_handle: {
|
||||||
if (self.closed_handles.get()) |handle| {
|
if (self.closed_handles.get()) |handle| {
|
||||||
std.debug.assert(self.closed_handles.pop());
|
std.debug.assert(self.closed_handles.pop());
|
||||||
|
@ -136,7 +87,7 @@ pub const Buffer = struct {
|
||||||
.mesh_2d => |mesh_2d| .{
|
.mesh_2d => |mesh_2d| .{
|
||||||
.mesh_2d = .{
|
.mesh_2d = .{
|
||||||
.indices = try arena_allocator.dupe(u16, mesh_2d.indices),
|
.indices = try arena_allocator.dupe(u16, mesh_2d.indices),
|
||||||
.vertices = try arena_allocator.dupe(Vertex2D, mesh_2d.vertices),
|
.vertices = try arena_allocator.dupe(OpenCommand.Mesh2D.Vertex, mesh_2d.vertices),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -149,46 +100,107 @@ pub const Buffer = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Consumer = coral.io.Generator(std.mem.Allocator.Error!void, &.{*const Buffer, Target});
|
pub const CloseCommand = struct {
|
||||||
|
handle: gfx.Handle,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Handle = enum (usize) {
|
pub const DrawCommand = union (enum) {
|
||||||
none,
|
instance_2d: Instance,
|
||||||
_,
|
post_process: PostProcess,
|
||||||
|
|
||||||
pub fn index(self: Handle) ?usize {
|
pub const Instance = struct {
|
||||||
|
texture: gfx.Handle,
|
||||||
|
mesh_2d: gfx.Handle,
|
||||||
|
transform: gfx.Transform2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PostProcess = struct {
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OpenCommand = struct {
|
||||||
|
handle: gfx.Handle = .none,
|
||||||
|
label: ?[]const u8 = null,
|
||||||
|
|
||||||
|
resource: union (enum) {
|
||||||
|
texture: Texture,
|
||||||
|
mesh_2d: Mesh2D,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub const Mesh2D = struct {
|
||||||
|
vertices: []const Vertex,
|
||||||
|
indices: []const u16,
|
||||||
|
|
||||||
|
pub const Vertex = struct {
|
||||||
|
xy: gfx.Point2D,
|
||||||
|
uv: gfx.Point2D,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Texture = struct {
|
||||||
|
data: []const coral.io.Byte,
|
||||||
|
width: u16,
|
||||||
|
format: Format,
|
||||||
|
access: Access,
|
||||||
|
|
||||||
|
pub const Access = enum {
|
||||||
|
static,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Format = enum {
|
||||||
|
rgba8888,
|
||||||
|
bgra8888,
|
||||||
|
argb8888,
|
||||||
|
rgb888,
|
||||||
|
bgr888,
|
||||||
|
|
||||||
|
pub fn byte_size(self: Format) usize {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.none => null,
|
.rgba8888, .bgra8888, .argb8888 => 4,
|
||||||
_ => @intFromEnum(self) - 1,
|
.rgb888, .bgr888 => 3,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Instance2D = extern struct {
|
pub fn acquire(self: *Self) void {
|
||||||
xbasis: Point2D = .{1, 0},
|
|
||||||
ybasis: Point2D = .{0, 1},
|
|
||||||
origin: Point2D = @splat(0),
|
|
||||||
color: color.Compressed = color.compress(color.white),
|
|
||||||
depth: f32 = 0,
|
|
||||||
texture_offset: Point2D = @splat(0),
|
|
||||||
texture_size: Point2D = @splat(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
const Node = struct {
|
|
||||||
buffers: [2]Buffer,
|
|
||||||
is_swapped: bool = false,
|
|
||||||
ref_count: AtomicCount = AtomicCount.init(1),
|
|
||||||
has_next: ?*Node = null,
|
|
||||||
has_prev: ?*Node = null,
|
|
||||||
|
|
||||||
fn acquire(self: *Node) void {
|
|
||||||
self.ref_count.fetchAdd(1, .monotonic);
|
self.ref_count.fetchAdd(1, .monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create() std.mem.Allocator.Error!*Self {
|
||||||
|
const queue = try coral.heap.allocator.create(Self);
|
||||||
|
|
||||||
|
errdefer coral.heap.allocator.destroy(queue);
|
||||||
|
|
||||||
|
queue.* = .{
|
||||||
|
.buffers = .{Buffer.init(coral.heap.allocator), Buffer.init(coral.heap.allocator)},
|
||||||
|
};
|
||||||
|
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
defer mutex.unlock();
|
||||||
|
|
||||||
|
if (has_tail) |tail| {
|
||||||
|
tail.has_next = queue;
|
||||||
|
queue.has_prev = tail;
|
||||||
|
} else {
|
||||||
|
std.debug.assert(has_head == null);
|
||||||
|
|
||||||
|
has_head = queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending(self: *Node) *Buffer {
|
has_tail = queue;
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending(self: *Self) *Buffer {
|
||||||
return &self.buffers[@intFromBool(self.is_swapped)];
|
return &self.buffers[@intFromBool(self.is_swapped)];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release(self: *Node) void {
|
pub fn release(self: *Self) void {
|
||||||
if (self.ref_count.fetchSub(1, .monotonic) == 1) {
|
if (self.ref_count.fetchSub(1, .monotonic) == 1) {
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
|
|
||||||
|
@ -212,121 +224,17 @@ const Node = struct {
|
||||||
|
|
||||||
coral.heap.allocator.destroy(self);
|
coral.heap.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submitted(self: *Node) *Buffer {
|
pub fn submitted(self: *Self) *Buffer {
|
||||||
return &self.buffers[@intFromBool(!self.is_swapped)];
|
return &self.buffers[@intFromBool(!self.is_swapped)];
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
pub const Point2D = @Vector(2, f32);
|
|
||||||
|
|
||||||
pub const Primitives = struct {
|
|
||||||
quad_mesh: Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const State = struct {
|
var has_head = @as(?*Self, null);
|
||||||
node: *Node,
|
|
||||||
primitives: *const Primitives,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Target = struct {
|
var has_tail = @as(?*Self, null);
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Vertex2D = struct {
|
|
||||||
xy: Point2D,
|
|
||||||
uv: Point2D,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
|
|
||||||
const queue = try coral.heap.allocator.create(Node);
|
|
||||||
|
|
||||||
errdefer coral.heap.allocator.destroy(queue);
|
|
||||||
|
|
||||||
queue.* = .{
|
|
||||||
.buffers = .{Buffer.init(coral.heap.allocator), Buffer.init(coral.heap.allocator)},
|
|
||||||
};
|
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
if (has_tail) |tail| {
|
|
||||||
tail.has_next = queue;
|
|
||||||
queue.has_prev = tail;
|
|
||||||
} else {
|
|
||||||
std.debug.assert(has_head == null);
|
|
||||||
|
|
||||||
has_head = queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
has_tail = queue;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: {
|
|
||||||
const buffer = queue.pending();
|
|
||||||
const half_extent = 0.5;
|
|
||||||
|
|
||||||
try context.world.set_resource(.none, Primitives{
|
|
||||||
.quad_mesh = try buffer.open(.{
|
|
||||||
.label = "quad mesh primitive",
|
|
||||||
|
|
||||||
.resource = .{
|
|
||||||
.mesh_2d = .{
|
|
||||||
.indices = &.{0, 1, 2, 0, 2, 3},
|
|
||||||
|
|
||||||
.vertices = &.{
|
|
||||||
.{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}},
|
|
||||||
.{.xy = .{half_extent, half_extent}, .uv = .{1, 1}},
|
|
||||||
.{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}},
|
|
||||||
.{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
break: create (try context.register_read_only_resource_access(.none, Primitives)).?;
|
|
||||||
},
|
|
||||||
|
|
||||||
.node = queue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn consume_submitted(consumer: Consumer, target: Target) std.mem.Allocator.Error!void {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
var has_node = has_head;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (has_node) |node| : ({
|
|
||||||
has_node = node.has_next;
|
|
||||||
iterations += 1;
|
|
||||||
}) {
|
|
||||||
const buffer = &node.buffers[@intFromBool(!node.is_swapped)];
|
|
||||||
|
|
||||||
try consumer.yield(.{buffer, target});
|
|
||||||
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var has_head = @as(?*Node, null);
|
|
||||||
|
|
||||||
var has_tail = @as(?*Node, null);
|
|
||||||
|
|
||||||
pub fn init(state: *State) Self {
|
|
||||||
return .{
|
|
||||||
.buffer = state.node.pending(),
|
|
||||||
.primitives = state.primitives,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var mutex = std.Thread.Mutex{};
|
var mutex = std.Thread.Mutex{};
|
||||||
|
|
||||||
|
@ -341,9 +249,61 @@ pub fn swap() void {
|
||||||
|
|
||||||
while (has_node) |node| : (has_node = node.has_next) {
|
while (has_node) |node| : (has_node = node.has_next) {
|
||||||
node.is_swapped = !node.is_swapped;
|
node.is_swapped = !node.is_swapped;
|
||||||
|
|
||||||
|
node.pending().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unbind(state: *State) void {
|
pub fn visit_close_commands(visit: anytype, args: anytype) !void {
|
||||||
state.node.release();
|
mutex.lock();
|
||||||
|
|
||||||
|
defer mutex.unlock();
|
||||||
|
|
||||||
|
var has_node = has_head;
|
||||||
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
|
while (has_node) |node| : ({
|
||||||
|
has_node = node.has_next;
|
||||||
|
iterations += 1;
|
||||||
|
}) {
|
||||||
|
for (node.submitted().close_commands.values) |command| {
|
||||||
|
try @call(.auto, visit, .{command} ++ args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit_draw_commands(visit: anytype, args: anytype) !void {
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
defer mutex.unlock();
|
||||||
|
|
||||||
|
var has_node = has_head;
|
||||||
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
|
while (has_node) |node| : ({
|
||||||
|
has_node = node.has_next;
|
||||||
|
iterations += 1;
|
||||||
|
}) {
|
||||||
|
for (node.submitted().draw_commands.values) |command| {
|
||||||
|
try @call(.auto, visit, .{command} ++ args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit_open_commands(visit: anytype, args: anytype) !void {
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
defer mutex.unlock();
|
||||||
|
|
||||||
|
var has_node = has_head;
|
||||||
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
|
while (has_node) |node| : ({
|
||||||
|
has_node = node.has_next;
|
||||||
|
iterations += 1;
|
||||||
|
}) {
|
||||||
|
for (node.submitted().open_commands.values) |command| {
|
||||||
|
try @call(.auto, visit, .{command} ++ args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
pub const Compressed = @Vector(4, u8);
|
|
||||||
|
|
||||||
pub const Value = @Vector(4, f32);
|
|
||||||
|
|
||||||
pub const black = Value{0, 0, 0, 1};
|
|
||||||
|
|
||||||
pub fn compress(color: Value) Compressed {
|
|
||||||
return @intFromFloat(color * @as(Value, @splat(255)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb(r: f32, g: f32, b: f32) Value {
|
|
||||||
return .{r, g, b, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const white = Value{1, 1, 1, 1};
|
|
|
@ -1 +0,0 @@
|
||||||
*.glsl.zig
|
|
|
@ -8,7 +8,7 @@ in vec2 mesh_uv;
|
||||||
in vec2 instance_xbasis;
|
in vec2 instance_xbasis;
|
||||||
in vec2 instance_ybasis;
|
in vec2 instance_ybasis;
|
||||||
in vec2 instance_origin;
|
in vec2 instance_origin;
|
||||||
in vec4 instance_color;
|
in vec4 instance_tint;
|
||||||
in float instance_depth;
|
in float instance_depth;
|
||||||
in vec4 instance_rect;
|
in vec4 instance_rect;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ void main() {
|
||||||
|
|
||||||
// Set the position of the vertex in clip space
|
// Set the position of the vertex in clip space
|
||||||
gl_Position = vec4(ndc_position, instance_depth, 1.0);
|
gl_Position = vec4(ndc_position, instance_depth, 1.0);
|
||||||
color = instance_color;
|
color = instance_tint;
|
||||||
|
|
||||||
// Calculate the width and height from left, top, right, bottom configuration
|
// Calculate the width and height from left, top, right, bottom configuration
|
||||||
const vec2 rect_pos = instance_rect.xy; // left, top
|
const vec2 rect_pos = instance_rect.xy; // left, top
|
Loading…
Reference in New Issue