Implement CRT shader effect support
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2024-07-21 18:59:28 +01:00
parent 56342d9d8e
commit 67dee07f8e
9 changed files with 332 additions and 154 deletions

52
debug/crt.frag Normal file
View File

@ -0,0 +1,52 @@
#version 430
layout (binding = 0) uniform sampler2D sprite;
layout (location = 0) in vec4 color;
layout (location = 1) in vec2 uv;
layout (location = 0) out vec4 texel;
layout (binding = 0) uniform Effect {
float screen_width;
float screen_height;
float time;
};
vec3 scanline(vec2 coord, vec3 screen)
{
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
return screen;
}
vec2 crt(vec2 coord, float bend)
{
// put in symmetrical coords
coord = (coord - 0.5) * 2.0;
coord *= 1.0;
// deform coords
coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0);
coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0);
// transform back to 0.0 - 1.0 space
coord = (coord / 2.0) + 0.5;
return coord;
}
void main()
{
vec2 crtCoords = crt(uv, 4.8);
// Split the color channels
texel.rgb = texture(sprite, crtCoords).rgb;
texel.a = 1;
// HACK: this bend produces a shitty moire pattern.
// Up the bend for the scanline
vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
texel.rgb = scanline(screenSpace, texel.rgb);
}

View File

@ -9,11 +9,20 @@ const ChromaticAberration = extern struct {
padding: [12]u8 = undefined, padding: [12]u8 = undefined,
}; };
const CRT = extern struct {
width: f32,
height: f32,
time: f32,
padding: [4]u8 = undefined,
};
const Actors = struct { const Actors = struct {
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
body_texture: ona.gfx.Texture = .default, body_texture: ona.gfx.Texture = .default,
render_texture: ona.gfx.Texture = .default, render_texture: ona.gfx.Texture = .default,
ca_effect: ona.gfx.Effect = .default, ca_effect: ona.gfx.Effect = .default,
crt_effect: ona.gfx.Effect = .default,
staging_texture: ona.gfx.Texture = .default,
}; };
const Player = struct { const Player = struct {
@ -44,6 +53,7 @@ fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: on
}); });
actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv"); actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv");
actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv");
try actors.res.instances.push_grow(.{0, 0}); try actors.res.instances.push_grow(.{0, 0});
} }
@ -52,22 +62,46 @@ fn exit(actors: ona.Write(Actors)) void {
actors.res.instances.deinit(); actors.res.instances.deinit();
} }
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void {
try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ try commands.set_target(.{
.effect_magnitude = 15.0, .texture = actors.res.render_texture,
.clear_color = ona.gfx.colors.black,
.clear_depth = 0,
.clear_stencil = 0,
}); });
for (actors.res.instances.values) |instance| {
try commands.draw_texture(.{ try commands.draw_texture(.{
.texture = actors.res.body_texture, .texture = .default,
.transform = .{ .transform = .{
.origin = instance, .origin = .{1280 / 2, 720 / 2},
.xbasis = .{64, 0}, .xbasis = .{1280, 0},
.ybasis = .{0, 64}, .ybasis = .{0, 720},
},
});
try commands.set_effect(actors.res.crt_effect, CRT{
.width = 1280,
.height = 720,
.time = @floatCast(app.res.elapsed_time),
});
try commands.set_target(.{
.texture = .backbuffer,
.clear_color = null,
.clear_depth = null,
.clear_stencil = null,
});
try commands.draw_texture(.{
.texture = actors.res.render_texture,
.transform = .{
.origin = .{1280 / 2, 720 / 2},
.xbasis = .{1280, 0},
.ybasis = .{0, 720},
}, },
}); });
}
} }
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {

View File

@ -4,6 +4,7 @@ const flow = @import("flow");
events: *const Events, events: *const Events,
target_frame_time: f64, target_frame_time: f64,
elapsed_time: f64,
is_running: bool, is_running: bool,
pub const Events = struct { pub const Events = struct {

View File

@ -17,7 +17,12 @@ fn Handle(comptime HandleDesc: type) type {
}; };
} }
pub const Texture = Handle(struct { pub const Texture = enum (u32) {
default,
backbuffer,
_,
pub const Desc = struct {
format: Format, format: Format,
access: Access, access: Access,
@ -46,4 +51,6 @@ pub const Texture = Handle(struct {
}; };
} }
}; };
}); };
};

View File

@ -30,7 +30,7 @@ pub const Command = union (enum) {
}; };
pub const SetTarget = struct { pub const SetTarget = struct {
texture: ?handles.Texture = null, texture: handles.Texture,
clear_color: ?lina.Color, clear_color: ?lina.Color,
clear_depth: ?f32, clear_depth: ?f32,
clear_stencil: ?u8, clear_stencil: ?u8,
@ -135,7 +135,7 @@ const Frame = struct {
drawn_count: usize = 0, drawn_count: usize = 0,
flushed_count: usize = 0, flushed_count: usize = 0,
current_source_texture: handles.Texture = .default, current_source_texture: handles.Texture = .default,
current_target_texture: ?handles.Texture = null, current_target_texture: handles.Texture = .backbuffer,
current_effect: handles.Effect = .default, current_effect: handles.Effect = .default,
const DrawTexture = extern struct { const DrawTexture = extern struct {
@ -185,24 +185,27 @@ const Frame = struct {
bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) { switch (pools.get_texture(self.current_source_texture).?.access) {
.render => |render| { .render => |render| {
bindings.fs.images[0] = render.color_image; bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = render.sampler; bindings.fs.samplers[0] = default_sampler;
}, },
.static => |static| { .static => |static| {
bindings.fs.images[0] = static.image; bindings.fs.images[0] = static.image;
bindings.fs.samplers[0] = static.sampler; bindings.fs.samplers[0] = default_sampler;
},
.empty => {
@panic("Cannot render empty textures");
}, },
} }
const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; const effect = pools.get_effect(self.current_effect).?;
sokol.gfx.applyPipeline(effect.pipeline); sokol.gfx.applyPipeline(effect.pipeline);
if (self.current_target_texture) |target_texture| { const texture = pools.get_texture(self.current_target_texture).?;
const texture = pools.textures.get(@intFromEnum(target_texture)).?;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0, .left = 0,
@ -210,16 +213,10 @@ const Frame = struct {
.right = @floatFromInt(texture.width), .right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height), .bottom = @floatFromInt(texture.height),
}))); })));
} else {
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(self.swapchain.width),
.bottom = @floatFromInt(self.swapchain.height),
})));
}
if (effect.properties.len != 0) {
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
}
while (true) { while (true) {
const buffer_index = self.flushed_count / batches_per_buffer; const buffer_index = self.flushed_count / batches_per_buffer;
@ -246,7 +243,7 @@ const Frame = struct {
self.current_effect = command.effect; self.current_effect = command.effect;
if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| { if (pools.get_effect(self.current_effect)) |effect| {
@memcpy(effect.properties, command.properties); @memcpy(effect.properties, command.properties);
} }
} }
@ -280,17 +277,13 @@ const Frame = struct {
pass.action.depth = .{.load_action = .LOAD}; pass.action.depth = .{.load_action = .LOAD};
} }
if (command.texture) |texture| { pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) {
pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) {
.static => @panic("Cannot render to static textures"), .static => @panic("Cannot render to static textures"),
.empty => @panic("Cannot render to empty textures"),
.render => |render| render.attachments, .render => |render| render.attachments,
}; };
self.current_target_texture = command.texture; self.current_target_texture = command.texture;
} else {
pass.swapchain = self.swapchain;
self.current_target_texture = null;
}
sokol.gfx.beginPass(pass); sokol.gfx.beginPass(pass);
} }
@ -313,7 +306,7 @@ const Pools = struct {
const TexturePool = coral.Pool(resources.Texture); const TexturePool = coral.Pool(resources.Texture);
fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { pub fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect {
var effect = try resources.Effect.init(desc); var effect = try resources.Effect.init(desc);
errdefer effect.deinit(); errdefer effect.deinit();
@ -321,7 +314,7 @@ const Pools = struct {
return @enumFromInt(try self.effects.insert(effect)); return @enumFromInt(try self.effects.insert(effect));
} }
fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { pub fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture {
var texture = try resources.Texture.init(desc); var texture = try resources.Texture.init(desc);
errdefer texture.deinit(); errdefer texture.deinit();
@ -329,7 +322,7 @@ const Pools = struct {
return @enumFromInt(try self.textures.insert(texture)); return @enumFromInt(try self.textures.insert(texture));
} }
fn deinit(self: *Pools) void { pub fn deinit(self: *Pools) void {
var textures = self.textures.values(); var textures = self.textures.values();
while (textures.next()) |texture| { while (textures.next()) |texture| {
@ -339,7 +332,7 @@ const Pools = struct {
self.textures.deinit(); self.textures.deinit();
} }
fn destroy_effect(self: *Pools, handle: handles.Effect) bool { pub fn destroy_effect(self: *Pools, handle: handles.Effect) bool {
switch (handle) { switch (handle) {
.default => {}, .default => {},
@ -355,7 +348,7 @@ const Pools = struct {
return true; return true;
} }
fn destroy_texture(self: *Pools, handle: handles.Texture) bool { pub fn destroy_texture(self: *Pools, handle: handles.Texture) bool {
switch (handle) { switch (handle) {
.default => {}, .default => {},
@ -371,7 +364,15 @@ const Pools = struct {
return true; return true;
} }
fn init(allocator: std.mem.Allocator) !Pools { pub fn get_effect(self: *Pools, handle: handles.Effect) ?*resources.Effect {
return self.effects.get(@intFromEnum(handle));
}
pub fn get_texture(self: *Pools, handle: handles.Texture) ?*resources.Texture {
return self.textures.get(@intFromEnum(handle));
}
pub fn init(allocator: std.mem.Allocator) !Pools {
var pools = Pools{ var pools = Pools{
.effects = EffectPool.init(allocator), .effects = EffectPool.init(allocator),
.textures = TexturePool.init(allocator), .textures = TexturePool.init(allocator),
@ -381,31 +382,47 @@ const Pools = struct {
pools.deinit(); pools.deinit();
} }
_ = try pools.create_effect(.{ const assert = struct {
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), fn is_handle(expected: anytype, actual: @TypeOf(expected)) void {
}); std.debug.assert(actual == expected);
}
const default_texture_data = [_]u32{
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
}; };
_ = try pools.create_texture(.{ assert.is_handle(handles.Effect.default, try pools.create_effect(.{
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
}));
assert.is_handle(handles.Texture.default, try pools.create_texture(.{
.format = .rgba8, .format = .rgba8,
.access = .{ .access = .{
.static = .{ .static = .{
.data = std.mem.asBytes(&default_texture_data), .data = std.mem.asBytes(&[_]u32{
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
}),
.width = 8, .width = 8,
}, },
}, },
}); }));
assert.is_handle(handles.Texture.backbuffer, try pools.create_texture(.{
.format = .rgba8,
.access = .{
.render = .{
.width = 0,
.height = 0,
},
}
}));
return pools; return pools;
} }
@ -457,6 +474,8 @@ pub const Work = union (enum) {
var pending: coral.asyncio.BlockingQueue(1024, Work) = .{}; var pending: coral.asyncio.BlockingQueue(1024, Work) = .{};
}; };
var default_sampler: sokol.gfx.Sampler = undefined;
pub fn enqueue_work(work: Work) void { pub fn enqueue_work(work: Work) void {
Work.pending.enqueue(work); Work.pending.enqueue(work);
} }
@ -541,6 +560,8 @@ fn run(window: *ext.SDL_Window) !void {
.type = .VERTEXBUFFER, .type = .VERTEXBUFFER,
}); });
default_sampler = sokol.gfx.makeSampler(.{});
var has_commands_head: ?*Commands = null; var has_commands_head: ?*Commands = null;
var has_commands_tail: ?*Commands = null; var has_commands_tail: ?*Commands = null;
@ -588,6 +609,23 @@ fn run(window: *ext.SDL_Window) !void {
}, },
.render_frame => |render_frame| { .render_frame => |render_frame| {
const backbuffer = pools.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,
},
},
});
}
var frame = Frame{ var frame = Frame{
.swapchain = .{ .swapchain = .{
.width = render_frame.width, .width = render_frame.width,
@ -599,6 +637,51 @@ fn run(window: *ext.SDL_Window) !void {
} }
}; };
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 = pools.get_texture(.backbuffer).?.access.render.attachments;
break: pass pass;
});
var has_commands = has_commands_head;
while (has_commands) |commands| : (has_commands = commands.has_next) {
for (commands.submitted_commands()) |command| {
try command.process(&pools, &frame);
}
frame.flush(&pools);
if (frame.current_target_texture != .backbuffer) {
frame.set_target(&pools, .{
.texture = .backbuffer,
.clear_color = null,
.clear_depth = null,
.clear_stencil = null,
});
}
}
sokol.gfx.endPass();
sokol.gfx.beginPass(swapchain_pass: { sokol.gfx.beginPass(swapchain_pass: {
var pass = sokol.gfx.Pass{ var pass = sokol.gfx.Pass{
.swapchain = frame.swapchain, .swapchain = frame.swapchain,
@ -609,29 +692,20 @@ fn run(window: *ext.SDL_Window) !void {
}, },
}; };
pass.action.colors[0] = .{ pass.action.colors[0] = .{.load_action = .CLEAR};
.clear_value = @bitCast(render_frame.clear_color),
.load_action = .CLEAR,
};
break: swapchain_pass pass; break: swapchain_pass pass;
}); });
var has_commands = has_commands_head; try frame.draw_texture(&pools, .{
.texture = .backbuffer,
while (has_commands) |commands| : (has_commands = commands.has_next) { .transform = .{
for (commands.submitted_commands()) |command| { .origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2},
try command.process(&pools, &frame); .xbasis = .{@floatFromInt(render_frame.width), 0},
} .ybasis = .{0, @floatFromInt(render_frame.height)},
},
if (frame.current_target_texture != .default) {
frame.set_target(&pools, .{
.clear_color = null,
.clear_depth = null,
.clear_stencil = null,
}); });
}
}
frame.flush(&pools); frame.flush(&pools);
sokol.gfx.endPass(); sokol.gfx.endPass();

View File

@ -123,11 +123,11 @@ pub const Effect = struct {
.float2 => .FLOAT2, .float2 => .FLOAT2,
.float3 => .FLOAT3, .float3 => .FLOAT3,
.float4 => .FLOAT4, .float4 => .FLOAT4,
.int => .INT, .integer => .INT,
.int2 => .INT2, .integer2 => .INT2,
.int3 => .INT3, .integer3 => .INT3,
.int4 => .INT4, .integer4 => .INT4,
.mat4 => .MAT4, .matrix4 => .MAT4,
}, },
.name = uniform.name, .name = uniform.name,
@ -244,19 +244,18 @@ pub const Texture = struct {
access: Access, access: Access,
pub const Access = union (enum) { pub const Access = union (enum) {
empty,
render: RenderAccess, render: RenderAccess,
static: StaticAccess, static: StaticAccess,
}; };
pub const RenderAccess = struct { pub const RenderAccess = struct {
sampler: sokol.gfx.Sampler,
color_image: sokol.gfx.Image, color_image: sokol.gfx.Image,
depth_image: sokol.gfx.Image, depth_image: sokol.gfx.Image,
attachments: sokol.gfx.Attachments, attachments: sokol.gfx.Attachments,
}; };
pub const StaticAccess = struct { pub const StaticAccess = struct {
sampler: sokol.gfx.Sampler,
image: sokol.gfx.Image, image: sokol.gfx.Image,
}; };
@ -265,14 +264,14 @@ pub const Texture = struct {
.render => |render| { .render => |render| {
sokol.gfx.destroyImage(render.color_image); sokol.gfx.destroyImage(render.color_image);
sokol.gfx.destroyImage(render.depth_image); sokol.gfx.destroyImage(render.depth_image);
sokol.gfx.destroySampler(render.sampler);
sokol.gfx.destroyAttachments(render.attachments); sokol.gfx.destroyAttachments(render.attachments);
}, },
.static => |static| { .static => |static| {
sokol.gfx.destroyImage(static.image); sokol.gfx.destroyImage(static.image);
sokol.gfx.destroySampler(static.sampler);
}, },
.empty => {},
} }
self.* = undefined; self.* = undefined;
@ -286,6 +285,14 @@ pub const Texture = struct {
switch (desc.access) { switch (desc.access) {
.render => |render| { .render => |render| {
if (render.width == 0 or render.height == 0) {
return .{
.width = render.width,
.height = render.height,
.access = .empty,
};
}
const color_image = sokol.gfx.makeImage(.{ const color_image = sokol.gfx.makeImage(.{
.pixel_format = pixel_format, .pixel_format = pixel_format,
.width = render.width, .width = render.width,
@ -314,8 +321,6 @@ pub const Texture = struct {
break: attachments_desc attachments_desc; break: attachments_desc attachments_desc;
}); });
const sampler = sokol.gfx.makeSampler(.{});
return .{ return .{
.width = render.width, .width = render.width,
.height = render.height, .height = render.height,
@ -323,11 +328,10 @@ pub const Texture = struct {
.access = .{ .access = .{
.render = .{ .render = .{
.attachments = attachments, .attachments = attachments,
.sampler = sampler,
.color_image = color_image, .color_image = color_image,
.depth_image = depth_image, .depth_image = depth_image,
}, },
} },
}; };
}, },
@ -336,6 +340,14 @@ pub const Texture = struct {
return error.OutOfMemory; return error.OutOfMemory;
}; };
if (static.width == 0 or height == 0) {
return .{
.width = static.width,
.height = height,
.access = .empty,
};
}
const image = sokol.gfx.makeImage(image_desc: { const image = sokol.gfx.makeImage(image_desc: {
var image_desc = sokol.gfx.ImageDesc{ var image_desc = sokol.gfx.ImageDesc{
.height = height, .height = height,
@ -348,10 +360,7 @@ pub const Texture = struct {
break: image_desc image_desc; break: image_desc image_desc;
}); });
const sampler = sokol.gfx.makeSampler(.{});
errdefer { errdefer {
sokol.gfx.destroySampler(sampler);
sokol.gfx.destroyImage(image); sokol.gfx.destroyImage(image);
} }
@ -362,7 +371,6 @@ pub const Texture = struct {
.access = .{ .access = .{
.static = .{ .static = .{
.image = image, .image = image,
.sampler = sampler,
}, },
}, },
}; };

View File

@ -21,8 +21,9 @@ void main() {
const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis;
const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy;
const vec2 rect_size = instance_rect.zw - instance_rect.xy; const vec2 rect_size = instance_rect.zw - instance_rect.xy;
const vec4 screen_position = vec4(projected_position, instance_depth, 1.0);
gl_Position = vec4(projected_position, instance_depth, 1.0); gl_Position = screen_position;
color = instance_tint; color = instance_tint;
uv = instance_rect.xy + (mesh_uv * rect_size); uv = instance_rect.xy + (mesh_uv * rect_size);
} }

View File

@ -67,11 +67,11 @@ pub const Stage = struct {
float2, float2,
float3, float3,
float4, float4,
int, integer,
int2, integer2,
int3, integer3,
int4, integer4,
mat4, matrix4,
}; };
}; };
@ -82,29 +82,27 @@ pub const Stage = struct {
pub const max_uniforms = 16; pub const max_uniforms = 16;
pub fn size(self: UniformBlock) usize { pub fn size(self: UniformBlock) usize {
const alignment: usize = switch (self.layout) {
.std140 => 16,
};
var accumulated_size: usize = 0; var accumulated_size: usize = 0;
for (self.uniforms) |uniform| { for (self.uniforms) |uniform| {
const type_size = @max(1, uniform.len) * @as(usize, switch (uniform.type) { accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) {
.float => 4, .float => 4,
.float2 => 8, .float2 => 8,
.float3 => 12, .float3 => 12,
.float4 => 16, .float4 => 16,
.int => 4, .integer => 4,
.int2 => 8, .integer2 => 8,
.int3 => 12, .integer3 => 12,
.int4 => 16, .integer4 => 16,
.mat4 => 64, .matrix4 => 64,
}); });
accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1);
} }
return accumulated_size; const alignment: usize = switch (self.layout) {
.std140 => 16,
};
return (accumulated_size + (alignment - 1)) & ~(alignment - 1);
} }
}; };
@ -212,7 +210,7 @@ pub const Stage = struct {
.type = try switch (ext.spvc_type_get_basetype(member_type_handle)) { .type = try switch (ext.spvc_type_get_basetype(member_type_handle)) {
ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) { ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) {
4 => switch (ext.spvc_type_get_columns(member_type_handle)) { 4 => switch (ext.spvc_type_get_columns(member_type_handle)) {
4 => Uniform.Type.mat4, 4 => Uniform.Type.matrix4,
1 => Uniform.Type.float4, 1 => Uniform.Type.float4,
else => error.UnsupportedSPIRV, else => error.UnsupportedSPIRV,
}, },
@ -224,10 +222,10 @@ pub const Stage = struct {
}, },
ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) { ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) {
1 => Uniform.Type.int, 1 => Uniform.Type.integer,
2 => Uniform.Type.int2, 2 => Uniform.Type.integer2,
3 => Uniform.Type.int3, 3 => Uniform.Type.integer3,
4 => Uniform.Type.int4, 4 => Uniform.Type.integer4,
else => error.UnsupportedSPIRV, else => error.UnsupportedSPIRV,
}, },

View File

@ -75,6 +75,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
const app = try world.set_get_resource(App{ const app = try world.set_get_resource(App{
.events = &events, .events = &events,
.target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)),
.elapsed_time = 0,
.is_running = true, .is_running = true,
}); });
@ -85,7 +86,8 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
try setup(&world, events); try setup(&world, events);
try world.run_event(events.load); try world.run_event(events.load);
var ticks_previous = std.time.milliTimestamp(); const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0); var accumulated_time = @as(f64, 0);
while (app.is_running) { while (app.is_running) {
@ -93,6 +95,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
const milliseconds_per_second = 1000.0; const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current; ticks_previous = ticks_current;
accumulated_time += delta_time; accumulated_time += delta_time;