ona/src/gfx/rendering.zig

447 lines
11 KiB
Zig
Raw Normal View History

2024-07-22 00:46:13 +01:00
const Resources = @import("./Resources.zig");
const gfx = @import("./gfx.zig");
2024-07-22 00:46:13 +01:00
const ext = @cImport({
@cInclude("SDL2/SDL.h");
});
2024-07-22 00:46:13 +01:00
const ona = @import("ona");
2024-07-22 00:46:13 +01:00
const sokol = @import("sokol");
const spirv = @import("./spirv.zig");
2024-07-22 00:46:13 +01:00
const std = @import("std");
const Frame = struct {
texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
2024-07-22 00:46:13 +01:00
quad_index_buffer: sokol.gfx.Buffer,
quad_vertex_buffer: sokol.gfx.Buffer,
drawn_count: usize = 0,
flushed_count: usize = 0,
current_source_texture: gfx.Texture = .default,
current_target_texture: gfx.Texture = .backbuffer,
current_effect: gfx.Effect = .default,
2024-07-22 00:46:13 +01:00
const DrawTexture = extern struct {
transform: gfx.Transform2D,
2024-07-22 00:46:13 +01:00
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
depth: f32 = 0,
texture_offset: @Vector(2, f32) = @splat(0),
texture_size: @Vector(2, f32) = @splat(1),
};
const batches_per_buffer = 512;
pub fn deinit(self: *Frame) void {
for (self.texture_batch_buffers.values) |buffer| {
sokol.gfx.destroyBuffer(buffer);
}
self.texture_batch_buffers.deinit();
self.* = undefined;
}
pub fn init() !Frame {
const Vertex = struct {
xy: @Vector(2, f32),
uv: @Vector(2, f32),
};
const quad_index_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}),
.type = .INDEXBUFFER,
});
const quad_vertex_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&[_]Vertex{
.{.xy = .{-0.5, -0.5}, .uv = .{0, 1}},
.{.xy = .{0.5, -0.5}, .uv = .{1, 1}},
.{.xy = .{0.5, 0.5}, .uv = .{1, 0}},
.{.xy = .{-0.5, 0.5}, .uv = .{0, 0}},
}),
.type = .VERTEXBUFFER,
});
return .{
.texture_batch_buffers = .{.allocator = ona.heap.allocator},
2024-07-22 00:46:13 +01:00
.quad_index_buffer = quad_index_buffer,
.quad_vertex_buffer = quad_vertex_buffer,
};
}
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
2024-07-22 00:46:13 +01:00
if (command.texture != self.current_source_texture) {
self.flush(resources);
2024-07-22 00:46:13 +01:00
}
self.current_source_texture = command.texture;
const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
const buffer_count = self.drawn_count / batches_per_buffer;
if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) {
const instance_buffer = sokol.gfx.makeBuffer(.{
.size = @sizeOf(DrawTexture) * batches_per_buffer,
.usage = .STREAM,
});
errdefer sokol.gfx.destroyBuffer(instance_buffer);
try self.texture_batch_buffers.push_grow(instance_buffer);
}
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{
.transform = command.transform,
}));
self.drawn_count += 1;
}
pub fn finish(self: *Frame, resources: *Resources) void {
self.flush(resources);
2024-07-22 00:46:13 +01:00
self.drawn_count = 0;
self.flushed_count = 0;
self.current_source_texture = .default;
self.current_target_texture = .backbuffer;
self.current_effect = .default;
}
pub fn flush(self: *Frame, resources: *Resources) void {
2024-07-22 00:46:13 +01:00
if (self.flushed_count == self.drawn_count) {
return;
}
var bindings = sokol.gfx.Bindings{
.index_buffer = self.quad_index_buffer,
};
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
switch (resources.get_texture(self.current_source_texture).?.access) {
2024-07-22 00:46:13 +01:00
.render => |render| {
bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = default_sampler;
},
.static => |static| {
bindings.fs.images[0] = static.image;
bindings.fs.samplers[0] = default_sampler;
},
.empty => {
@panic("Cannot render empty textures");
},
}
const effect = resources.get_effect(self.current_effect).?;
2024-07-22 00:46:13 +01:00
sokol.gfx.applyPipeline(effect.pipeline);
const texture = resources.get_texture(self.current_target_texture).?;
2024-07-22 00:46:13 +01:00
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
2024-07-22 00:46:13 +01:00
.left = 0,
.top = 0,
.right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height),
})));
if (effect.properties.len != 0) {
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
}
while (true) {
const buffer_index = self.flushed_count / batches_per_buffer;
const buffer_offset = self.flushed_count % batches_per_buffer;
const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count);
self.flushed_count += instances_to_flush;
bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index];
bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset);
sokol.gfx.applyBindings(bindings);
sokol.gfx.draw(0, 6, @intCast(instances_to_flush));
if (self.flushed_count == self.drawn_count) {
break;
}
}
}
pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Commands.SetEffectCommand) void {
2024-07-22 00:46:13 +01:00
if (command.effect != self.current_effect) {
self.flush(resources);
2024-07-22 00:46:13 +01:00
}
self.current_effect = command.effect;
if (resources.get_effect(self.current_effect)) |effect| {
2024-07-22 00:46:13 +01:00
@memcpy(effect.properties, command.properties);
}
}
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void {
2024-07-22 00:46:13 +01:00
sokol.gfx.endPass();
var pass = sokol.gfx.Pass{
.action = .{
.stencil = .{
.load_action = .CLEAR,
},
},
};
if (command.clear_color) |color| {
pass.action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = @bitCast(color),
};
} else {
pass.action.colors[0] = .{.load_action = .LOAD};
}
if (command.clear_depth) |depth| {
pass.action.depth = .{
.load_action = .CLEAR,
.clear_value = depth,
};
} else {
pass.action.depth = .{.load_action = .LOAD};
}
pass.attachments = switch (resources.get_texture(self.current_target_texture).?.access) {
2024-07-22 00:46:13 +01:00
.static => @panic("Cannot render to static textures"),
.empty => @panic("Cannot render to empty textures"),
.render => |render| render.attachments,
};
self.current_target_texture = command.texture;
sokol.gfx.beginPass(pass);
}
};
fn Matrix(comptime n: usize, comptime Element: type) type {
return [n]@Vector(n, Element);
}
2024-07-22 00:46:13 +01:00
var default_sampler: sokol.gfx.Sampler = undefined;
const vertex_indices = .{
.mesh = 0,
.instance = 1,
};
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) {
const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top;
return .{
.{2 / width, 0, 0, 0},
.{0, 2 / height, 0, 0},
.{0, 0, 1 / (far - near), 0},
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
};
}
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
2024-07-22 00:46:13 +01:00
const context = configure_and_create: {
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_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
if (result != 0) {
return error.Unsupported;
}
break: configure_and_create ext.SDL_GL_CreateContext(window);
};
defer ext.SDL_GL_DeleteContext(context);
sokol.gfx.setup(.{
.environment = .{
.defaults = .{
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.sample_count = 1,
},
},
.logger = .{
.func = sokol.log.func,
},
});
defer {
sokol.gfx.shutdown();
}
var resources = try Resources.init();
defer {
resources.deinit();
}
var frame = try Frame.init();
defer {
frame.deinit();
}
default_sampler = sokol.gfx.makeSampler(.{});
while (true) {
switch (pending_work.dequeue()) {
.load_effect => |load| {
const effect = try resources.create_effect(load.desc);
if (!load.loaded.resolve(effect)) {
std.debug.assert(resources.destroy_effect(effect));
}
},
.load_texture => |load| {
const texture = try resources.create_texture(load.desc);
if (!load.loaded.resolve(texture)) {
std.debug.assert(resources.destroy_texture(texture));
}
},
.render_frame => |render_frame| {
const backbuffer = resources.get_texture(.backbuffer).?;
if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) {
backbuffer.deinit();
backbuffer.* = try Resources.Texture.init(.{
.format = .rgba8,
.access = .{
.render = .{
.width = render_frame.width,
.height = render_frame.height,
},
},
});
}
sokol.gfx.beginPass(pass: {
var pass = sokol.gfx.Pass{
.action = .{
.stencil = .{
.load_action = .CLEAR,
},
.depth = .{
.load_action = .CLEAR,
.clear_value = 0,
}
},
};
pass.action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = @bitCast(render_frame.clear_color),
};
pass.attachments = resources.get_texture(.backbuffer).?.access.render.attachments;
break: pass pass;
});
var has_command_params = render_frame.has_command_params;
while (has_command_params) |command_params| : (has_command_params = command_params.has_next) {
for (command_params.param.submitted_commands()) |command| {
try switch (command) {
.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture),
.set_effect => |set_effect| frame.set_effect(&resources, set_effect),
.set_target => |set_target| frame.set_target(&resources, set_target),
};
}
frame.flush(&resources);
if (frame.current_target_texture != .backbuffer) {
frame.set_target(&resources, .{
.texture = .backbuffer,
.clear_color = null,
.clear_depth = null,
.clear_stencil = null,
});
}
}
sokol.gfx.endPass();
sokol.gfx.beginPass(swapchain_pass: {
var pass = sokol.gfx.Pass{
.swapchain = .{
.width = render_frame.width,
.height = render_frame.height,
.sample_count = 1,
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.gl = .{.framebuffer = 0},
},
.action = .{
.stencil = .{.load_action = .CLEAR},
.depth = .{.load_action = .CLEAR},
},
};
pass.action.colors[0] = .{.load_action = .CLEAR};
break: swapchain_pass pass;
});
try frame.draw_texture(&resources, .{
.texture = .backbuffer,
.transform = .{
.origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2},
.xbasis = .{@floatFromInt(render_frame.width), 0},
.ybasis = .{0, @floatFromInt(render_frame.height)},
},
});
frame.finish(&resources);
sokol.gfx.endPass();
sokol.gfx.commit();
ext.SDL_GL_SwapWindow(window);
render_frame.finished.set();
},
.shutdown => {
break;
},
.unload_effect => |unload| {
if (!resources.destroy_effect(unload.handle)) {
@panic("Attempt to unload a non-existent effect");
}
},
.unload_texture => |unload| {
if (!resources.destroy_texture(unload.handle)) {
@panic("Attempt to unload a non-existent texture");
}
},
}
}
}
var work_thread: std.Thread = undefined;