2024-07-22 00:46:13 +01:00
|
|
|
const Resources = @import("./Resources.zig");
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
const gfx = @import("./gfx.zig");
|
2024-07-22 00:46:13 +01:00
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
const ext = @cImport({
|
|
|
|
@cInclude("SDL2/SDL.h");
|
|
|
|
});
|
2024-07-22 00:46:13 +01:00
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
const ona = @import("ona");
|
2024-07-22 00:46:13 +01:00
|
|
|
|
|
|
|
const sokol = @import("sokol");
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
const spirv = @import("./spirv.zig");
|
|
|
|
|
2024-07-22 00:46:13 +01:00
|
|
|
const std = @import("std");
|
|
|
|
|
|
|
|
const Frame = struct {
|
2024-07-24 00:25:18 +01:00
|
|
|
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,
|
2024-07-24 22:09:27 +01:00
|
|
|
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 {
|
2024-07-24 22:09:27 +01:00
|
|
|
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 .{
|
2024-07-24 00:25:18 +01:00
|
|
|
.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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
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) {
|
2024-07-24 00:25:18 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
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;
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
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");
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
const effect = resources.get_effect(self.current_effect).?;
|
2024-07-22 00:46:13 +01:00
|
|
|
|
|
|
|
sokol.gfx.applyPipeline(effect.pipeline);
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
const texture = resources.get_texture(self.current_target_texture).?;
|
2024-07-22 00:46:13 +01:00
|
|
|
|
2024-07-24 00:25:18 +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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
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) {
|
2024-07-24 00:25:18 +01:00
|
|
|
self.flush(resources);
|
2024-07-22 00:46:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
self.current_effect = command.effect;
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
if (resources.get_effect(self.current_effect)) |effect| {
|
2024-07-22 00:46:13 +01:00
|
|
|
@memcpy(effect.properties, command.properties);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
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};
|
|
|
|
}
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-07-24 00:25:18 +01:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) {
|
2024-07-24 00:25:18 +01:00
|
|
|
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},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-07-24 22:09:27 +01:00
|
|
|
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;
|