renderer-mvp/post-processing #56
|
@ -31,14 +31,14 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
for (&self.thread_restricted_resources) |*resources| {
|
|
||||||
resources.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (self.event_systems.values) |*schedule| {
|
for (self.event_systems.values) |*schedule| {
|
||||||
schedule.deinit(self);
|
schedule.deinit(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (&self.thread_restricted_resources) |*resources| {
|
||||||
|
resources.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
if (self.thread_pool) |thread_pool| {
|
if (self.thread_pool) |thread_pool| {
|
||||||
thread_pool.deinit();
|
thread_pool.deinit();
|
||||||
coral.heap.allocator.destroy(thread_pool);
|
coral.heap.allocator.destroy(thread_pool);
|
||||||
|
|
|
@ -255,7 +255,7 @@ fn parameter_type(comptime Value: type) *const system.Info.Parameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Value.unbind(node);
|
Value.unbind(&node.param, context);
|
||||||
allocator.destroy(node);
|
allocator.destroy(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,13 @@ pub const colors = @import("./gfx/colors.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const bmp = @import("./gfx/bmp.zig");
|
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
const ext = @import("./ext.zig");
|
||||||
|
|
||||||
const flow = @import("flow");
|
const flow = @import("flow");
|
||||||
|
|
||||||
const msg = @import("./msg.zig");
|
const msg = @import("./msg.zig");
|
||||||
|
|
||||||
const sokol = @import("./gfx/sokol.zig");
|
const rendering = @import("./gfx/rendering.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
@ -261,9 +259,9 @@ pub const Commands = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(state: *Param) Commands {
|
pub fn init(param: *Param) Commands {
|
||||||
return .{
|
return .{
|
||||||
.pending = state.pending_list(),
|
.pending = param.pending_list(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +278,10 @@ pub const Commands = struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unbind(param: *Param, _: flow.system.UnbindContext) void {
|
||||||
|
param.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
|
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
|
||||||
try self.pending.stack.push_grow(.{.set_target = command});
|
try self.pending.stack.push_grow(.{.set_target = command});
|
||||||
}
|
}
|
||||||
|
@ -361,6 +363,80 @@ pub const Transform2D = extern struct {
|
||||||
const Vector = @Vector(2, f32);
|
const Vector = @Vector(2, f32);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !Texture.Desc {
|
||||||
|
const header = try storage.read_little(path, 0, extern struct {
|
||||||
|
type: [2]u8 align (1),
|
||||||
|
file_size: u32 align (1),
|
||||||
|
reserved: [2]u16 align (1),
|
||||||
|
image_offset: u32 align (1),
|
||||||
|
header_size: u32 align (1),
|
||||||
|
pixel_width: i32 align (1),
|
||||||
|
pixel_height: i32 align (1),
|
||||||
|
color_planes: u16 align (1),
|
||||||
|
bits_per_pixel: u16 align (1),
|
||||||
|
compression_method: u32 align (1),
|
||||||
|
image_size: u32 align(1),
|
||||||
|
pixels_per_meter_x: i32 align (1),
|
||||||
|
pixels_per_meter_y: i32 align (1),
|
||||||
|
palette_colors_used: u32 align (1),
|
||||||
|
important_colors_used: u32 align (1),
|
||||||
|
}) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, &header.type, "BM")) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
|
||||||
|
const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
|
||||||
|
const alignment = 4;
|
||||||
|
const byte_stride = pixel_width * bytes_per_pixel;
|
||||||
|
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
||||||
|
const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
||||||
|
var buffer_offset: usize = 0;
|
||||||
|
var file_offset = @as(usize, header.image_offset);
|
||||||
|
|
||||||
|
switch (header.bits_per_pixel) {
|
||||||
|
32 => {
|
||||||
|
while (buffer_offset < pixels.len) {
|
||||||
|
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
||||||
|
|
||||||
|
if (try storage.read(path, line, file_offset) != byte_stride) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. pixel_width) |i| {
|
||||||
|
const line_offset = i * 4;
|
||||||
|
const pixel = line[line_offset .. line_offset + 4];
|
||||||
|
|
||||||
|
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_offset += line.len + byte_padding;
|
||||||
|
buffer_offset += padded_byte_stride;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return error.FormatUnsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.format = .rgba8,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.static = .{
|
||||||
|
.width = pixel_width,
|
||||||
|
.data = pixels,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
|
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
|
||||||
var event = @as(ext.SDL_Event, undefined);
|
var event = @as(ext.SDL_Event, undefined);
|
||||||
|
|
||||||
|
@ -395,12 +471,15 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std
|
||||||
assets.deinit();
|
assets.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
assets.has_worker_thread = try std.Thread.spawn(.{}, sokol.run, .{&assets.pending_work, assets.window});
|
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
|
||||||
|
&assets.pending_work,
|
||||||
|
assets.window,
|
||||||
|
});
|
||||||
|
|
||||||
const builtin_texture_formats = [_]Assets.TextureFormat{
|
const builtin_texture_formats = [_]Assets.TextureFormat{
|
||||||
.{
|
.{
|
||||||
.extension = "bmp",
|
.extension = "bmp",
|
||||||
.load_file = bmp.load_file,
|
.load_file = load_bmp_texture,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,518 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
|
const spirv = @import("./spirv.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
effects: EffectPool,
|
||||||
|
textures: TexturePool,
|
||||||
|
|
||||||
|
pub const Effect = struct {
|
||||||
|
shader: sokol.gfx.Shader,
|
||||||
|
pipeline: sokol.gfx.Pipeline,
|
||||||
|
properties: []coral.io.Byte,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Effect) void {
|
||||||
|
coral.heap.allocator.free(self.properties);
|
||||||
|
sokol.gfx.destroyPipeline(self.pipeline);
|
||||||
|
sokol.gfx.destroyShader(self.shader);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
|
||||||
|
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
spirv_arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const spirv_program = try spirv.analyze(&spirv_arena, .{
|
||||||
|
.target = try switch (sokol.gfx.queryBackend()) {
|
||||||
|
.GLCORE => spirv.Target.glsl,
|
||||||
|
else => error.InvalidSPIRV,
|
||||||
|
},
|
||||||
|
|
||||||
|
.vertex_source = .{
|
||||||
|
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
|
||||||
|
},
|
||||||
|
|
||||||
|
.fragment_source = .{
|
||||||
|
.ops = desc.fragment_spirv_ops,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const shader = sokol.gfx.makeShader(shader_desc: {
|
||||||
|
const shader_desc = sokol.gfx.ShaderDesc{
|
||||||
|
.vs = stage_desc(spirv_program.vertex_stage),
|
||||||
|
.fs = stage_desc(spirv_program.fragment_stage),
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Vertex attributes, for some reason they aren't needed?
|
||||||
|
|
||||||
|
break: shader_desc shader_desc;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Review blending rules.
|
||||||
|
const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
|
||||||
|
var pipeline_desc = sokol.gfx.PipelineDesc{
|
||||||
|
.label = "Effect pipeline",
|
||||||
|
.layout = vertex_layout_state,
|
||||||
|
.shader = shader,
|
||||||
|
.index_type = .UINT16,
|
||||||
|
.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0},
|
||||||
|
};
|
||||||
|
|
||||||
|
pipeline_desc.colors[0] = .{
|
||||||
|
.write_mask = .RGBA,
|
||||||
|
|
||||||
|
.blend = .{
|
||||||
|
.enabled = true,
|
||||||
|
.src_factor_rgb = .SRC_ALPHA,
|
||||||
|
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
break: pipeline_desc pipeline_desc;
|
||||||
|
});
|
||||||
|
|
||||||
|
const properties = try coral.heap.allocator.alloc(
|
||||||
|
coral.io.Byte,
|
||||||
|
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
coral.heap.allocator.free(properties);
|
||||||
|
sokol.gfx.destroyPipeline(pipeline);
|
||||||
|
sokol.gfx.destroyShader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.shader = shader,
|
||||||
|
.pipeline = pipeline,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
|
||||||
|
var stage = sokol.gfx.ShaderStageDesc{
|
||||||
|
.entry = spirv_stage.entry_point,
|
||||||
|
.source = spirv_stage.source,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
|
||||||
|
const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const stage_uniform_block = &stage.uniform_blocks[slot];
|
||||||
|
|
||||||
|
stage_uniform_block.layout = switch (uniform_block.layout) {
|
||||||
|
.std140 => .STD140,
|
||||||
|
};
|
||||||
|
|
||||||
|
stage_uniform_block.size = uniform_block.size();
|
||||||
|
|
||||||
|
for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
|
||||||
|
stage_uniform.* = .{
|
||||||
|
.type = switch (uniform.type) {
|
||||||
|
.float => .FLOAT,
|
||||||
|
.float2 => .FLOAT2,
|
||||||
|
.float3 => .FLOAT3,
|
||||||
|
.float4 => .FLOAT4,
|
||||||
|
.integer => .INT,
|
||||||
|
.integer2 => .INT2,
|
||||||
|
.integer3 => .INT3,
|
||||||
|
.integer4 => .INT4,
|
||||||
|
.matrix4 => .MAT4,
|
||||||
|
},
|
||||||
|
|
||||||
|
.name = uniform.name,
|
||||||
|
.array_count = uniform.len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. spirv.Stage.max_images) |slot| {
|
||||||
|
const image = &(spirv_stage.has_images[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.images[slot] = .{
|
||||||
|
.multisampled = image.is_multisampled,
|
||||||
|
.image_type = ._2D,
|
||||||
|
.sample_type = .FLOAT,
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. spirv.Stage.max_samplers) |slot| {
|
||||||
|
const sampler = &(spirv_stage.has_samplers[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.samplers[slot] = .{
|
||||||
|
.sampler_type = switch (sampler.*) {
|
||||||
|
.filtering => .FILTERING,
|
||||||
|
.non_filtering => .NONFILTERING,
|
||||||
|
.comparison => .COMPARISON,
|
||||||
|
},
|
||||||
|
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
|
||||||
|
const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.image_sampler_pairs[slot] = .{
|
||||||
|
.glsl_name = image_sampler_pair.name,
|
||||||
|
.image_slot = @intCast(image_sampler_pair.image_slot),
|
||||||
|
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertex_layout_state = sokol.gfx.VertexLayoutState{
|
||||||
|
.attrs = get: {
|
||||||
|
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||||
|
|
||||||
|
attrs[0] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[1] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[2] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[3] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[4] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[5] = .{
|
||||||
|
.format = .UBYTE4N,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[6] = .{
|
||||||
|
.format = .FLOAT,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[7] = .{
|
||||||
|
.format = .FLOAT4,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
break: get attrs;
|
||||||
|
},
|
||||||
|
|
||||||
|
.buffers = get: {
|
||||||
|
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||||
|
|
||||||
|
vertex_buffer_layouts[1].step_func = .PER_INSTANCE;
|
||||||
|
|
||||||
|
break: get vertex_buffer_layouts;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const EffectPool = coral.Pool(Effect);
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Texture = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
access: Access,
|
||||||
|
|
||||||
|
pub const Access = union (enum) {
|
||||||
|
empty,
|
||||||
|
render: RenderAccess,
|
||||||
|
static: StaticAccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RenderAccess = struct {
|
||||||
|
color_image: sokol.gfx.Image,
|
||||||
|
depth_image: sokol.gfx.Image,
|
||||||
|
attachments: sokol.gfx.Attachments,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StaticAccess = struct {
|
||||||
|
image: sokol.gfx.Image,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: *Texture) void {
|
||||||
|
switch (self.access) {
|
||||||
|
.render => |render| {
|
||||||
|
sokol.gfx.destroyImage(render.color_image);
|
||||||
|
sokol.gfx.destroyImage(render.depth_image);
|
||||||
|
sokol.gfx.destroyAttachments(render.attachments);
|
||||||
|
},
|
||||||
|
|
||||||
|
.static => |static| {
|
||||||
|
sokol.gfx.destroyImage(static.image);
|
||||||
|
},
|
||||||
|
|
||||||
|
.empty => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||||
|
const pixel_format = switch (desc.format) {
|
||||||
|
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||||
|
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (desc.access) {
|
||||||
|
.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(.{
|
||||||
|
.pixel_format = pixel_format,
|
||||||
|
.width = render.width,
|
||||||
|
.height = render.height,
|
||||||
|
.render_target = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const depth_image = sokol.gfx.makeImage(.{
|
||||||
|
.width = render.width,
|
||||||
|
.height = render.height,
|
||||||
|
.render_target = true,
|
||||||
|
.pixel_format = .DEPTH_STENCIL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
|
||||||
|
var attachments_desc = sokol.gfx.AttachmentsDesc{
|
||||||
|
.depth_stencil = .{
|
||||||
|
.image = depth_image,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
attachments_desc.colors[0] = .{
|
||||||
|
.image = color_image,
|
||||||
|
};
|
||||||
|
|
||||||
|
break: attachments_desc attachments_desc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = render.width,
|
||||||
|
.height = render.height,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.render = .{
|
||||||
|
.attachments = attachments,
|
||||||
|
.color_image = color_image,
|
||||||
|
.depth_image = depth_image,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.static => |static| {
|
||||||
|
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
|
||||||
|
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: {
|
||||||
|
var image_desc = sokol.gfx.ImageDesc{
|
||||||
|
.height = height,
|
||||||
|
.pixel_format = pixel_format,
|
||||||
|
.width = static.width,
|
||||||
|
};
|
||||||
|
|
||||||
|
image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
|
||||||
|
|
||||||
|
break: image_desc image_desc;
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
sokol.gfx.destroyImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = static.width,
|
||||||
|
.height = height,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.static = .{
|
||||||
|
.image = image,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TexturePool = coral.Pool(Texture);
|
||||||
|
|
||||||
|
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
|
||||||
|
var effect = try Effect.init(desc);
|
||||||
|
|
||||||
|
errdefer effect.deinit();
|
||||||
|
|
||||||
|
return @enumFromInt(try self.effects.insert(effect));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
|
||||||
|
var texture = try Texture.init(desc);
|
||||||
|
|
||||||
|
errdefer texture.deinit();
|
||||||
|
|
||||||
|
return @enumFromInt(try self.textures.insert(texture));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
var textures = self.textures.values();
|
||||||
|
|
||||||
|
while (textures.next()) |texture| {
|
||||||
|
texture.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textures.deinit();
|
||||||
|
|
||||||
|
var effects = self.effects.values();
|
||||||
|
|
||||||
|
while (effects.next()) |effect| {
|
||||||
|
effect.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.effects.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
||||||
|
switch (handle) {
|
||||||
|
.default => {},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
var effect = self.effects.remove(@intFromEnum(handle)) orelse {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
effect.deinit();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
||||||
|
switch (handle) {
|
||||||
|
.default => {},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
var texture = self.textures.remove(@intFromEnum(handle)) orelse {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
texture.deinit();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
|
||||||
|
return self.effects.get(@intFromEnum(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture {
|
||||||
|
return self.textures.get(@intFromEnum(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() !Self {
|
||||||
|
var pools = Self{
|
||||||
|
.effects = EffectPool.init(coral.heap.allocator),
|
||||||
|
.textures = TexturePool.init(coral.heap.allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
pools.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = struct {
|
||||||
|
fn is_handle(expected: anytype, actual: @TypeOf(expected)) void {
|
||||||
|
std.debug.assert(actual == expected);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.is_handle(gfx.Effect.default, try pools.create_effect(.{
|
||||||
|
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
|
||||||
|
.format = .rgba8,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.static = .{
|
||||||
|
.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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
|
||||||
|
.format = .rgba8,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.render = .{
|
||||||
|
.width = 0,
|
||||||
|
.height = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return pools;
|
||||||
|
}
|
|
@ -1,79 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !gfx.Texture.Desc {
|
|
||||||
const header = try storage.read_little(path, 0, extern struct {
|
|
||||||
type: [2]u8 align (1),
|
|
||||||
file_size: u32 align (1),
|
|
||||||
reserved: [2]u16 align (1),
|
|
||||||
image_offset: u32 align (1),
|
|
||||||
header_size: u32 align (1),
|
|
||||||
pixel_width: i32 align (1),
|
|
||||||
pixel_height: i32 align (1),
|
|
||||||
color_planes: u16 align (1),
|
|
||||||
bits_per_pixel: u16 align (1),
|
|
||||||
compression_method: u32 align (1),
|
|
||||||
image_size: u32 align(1),
|
|
||||||
pixels_per_meter_x: i32 align (1),
|
|
||||||
pixels_per_meter_y: i32 align (1),
|
|
||||||
palette_colors_used: u32 align (1),
|
|
||||||
important_colors_used: u32 align (1),
|
|
||||||
}) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!std.mem.eql(u8, &header.type, "BM")) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
|
|
||||||
const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
|
|
||||||
const alignment = 4;
|
|
||||||
const byte_stride = pixel_width * bytes_per_pixel;
|
|
||||||
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
|
||||||
const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
|
||||||
var buffer_offset: usize = 0;
|
|
||||||
var file_offset = @as(usize, header.image_offset);
|
|
||||||
|
|
||||||
switch (header.bits_per_pixel) {
|
|
||||||
32 => {
|
|
||||||
while (buffer_offset < pixels.len) {
|
|
||||||
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
|
||||||
|
|
||||||
if (try storage.read(path, line, file_offset) != byte_stride) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. pixel_width) |i| {
|
|
||||||
const line_offset = i * 4;
|
|
||||||
const pixel = line[line_offset .. line_offset + 4];
|
|
||||||
|
|
||||||
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_offset += line.len + byte_padding;
|
|
||||||
buffer_offset += padded_byte_stride;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.FormatUnsupported,
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.width = pixel_width,
|
|
||||||
.data = pixels,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -0,0 +1,428 @@
|
||||||
|
const Resources = @import("./Resources.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ext = @import("../ext.zig");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
|
const spirv = @import("./spirv.zig");
|
||||||
|
|
||||||
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Frame = struct {
|
||||||
|
texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
||||||
|
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,
|
||||||
|
|
||||||
|
const DrawTexture = extern struct {
|
||||||
|
transform: gfx.Transform2D,
|
||||||
|
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 = coral.heap.allocator},
|
||||||
|
.quad_index_buffer = quad_index_buffer,
|
||||||
|
.quad_vertex_buffer = quad_vertex_buffer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
|
||||||
|
if (command.texture != self.current_source_texture) {
|
||||||
|
self.flush(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, pools: *Resources) void {
|
||||||
|
self.flush(pools);
|
||||||
|
|
||||||
|
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, pools: *Resources) void {
|
||||||
|
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 (pools.get_texture(self.current_source_texture).?.access) {
|
||||||
|
.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 = pools.get_effect(self.current_effect).?;
|
||||||
|
|
||||||
|
sokol.gfx.applyPipeline(effect.pipeline);
|
||||||
|
|
||||||
|
const texture = pools.get_texture(self.current_target_texture).?;
|
||||||
|
|
||||||
|
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{
|
||||||
|
.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, pools: *Resources, command: gfx.Commands.SetEffectCommand) void {
|
||||||
|
if (command.effect != self.current_effect) {
|
||||||
|
self.flush(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_effect = command.effect;
|
||||||
|
|
||||||
|
if (pools.get_effect(self.current_effect)) |effect| {
|
||||||
|
@memcpy(effect.properties, command.properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void {
|
||||||
|
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 (pools.get_texture(self.current_target_texture).?.access) {
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var default_sampler: sokol.gfx.Sampler = undefined;
|
||||||
|
|
||||||
|
const vertex_indices = .{
|
||||||
|
.mesh = 0,
|
||||||
|
.instance = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
|
||||||
|
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;
|
|
@ -1,908 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const ext = @import("../ext.zig");
|
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
|
||||||
|
|
||||||
const spirv = @import("./spirv.zig");
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Frame = struct {
|
|
||||||
swapchain: sokol.gfx.Swapchain,
|
|
||||||
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,
|
|
||||||
|
|
||||||
const DrawTexture = extern struct {
|
|
||||||
transform: gfx.Transform2D,
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
|
|
||||||
if (command.texture != self.current_source_texture) {
|
|
||||||
self.flush(pools);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == 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 texture_batch_buffers.push_grow(instance_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = sokol.gfx.appendBuffer(texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{
|
|
||||||
.transform = command.transform,
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.drawn_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flush(self: *Frame, pools: *Resources) void {
|
|
||||||
if (self.flushed_count == self.drawn_count) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bindings = sokol.gfx.Bindings{
|
|
||||||
.index_buffer = quad_index_buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
|
|
||||||
|
|
||||||
switch (pools.get_texture(self.current_source_texture).?.access) {
|
|
||||||
.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 = pools.get_effect(self.current_effect).?;
|
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(effect.pipeline);
|
|
||||||
|
|
||||||
const texture = pools.get_texture(self.current_target_texture).?;
|
|
||||||
|
|
||||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{
|
|
||||||
.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] = 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, pools: *Resources, command: gfx.Commands.SetEffectCommand) void {
|
|
||||||
if (command.effect != self.current_effect) {
|
|
||||||
self.flush(pools);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_effect = command.effect;
|
|
||||||
|
|
||||||
if (pools.get_effect(self.current_effect)) |effect| {
|
|
||||||
@memcpy(effect.properties, command.properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void {
|
|
||||||
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 (pools.get_texture(self.current_target_texture).?.access) {
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const storage_bindings = .{
|
|
||||||
.engine = 0,
|
|
||||||
.material = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator};
|
|
||||||
|
|
||||||
const batches_per_buffer = 512;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Resources = struct {
|
|
||||||
effects: EffectPool,
|
|
||||||
textures: TexturePool,
|
|
||||||
|
|
||||||
pub const Effect = struct {
|
|
||||||
shader: sokol.gfx.Shader,
|
|
||||||
pipeline: sokol.gfx.Pipeline,
|
|
||||||
properties: []coral.io.Byte,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Effect) void {
|
|
||||||
coral.heap.allocator.free(self.properties);
|
|
||||||
sokol.gfx.destroyPipeline(self.pipeline);
|
|
||||||
sokol.gfx.destroyShader(self.shader);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
|
|
||||||
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
spirv_arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const spirv_program = try spirv.analyze(&spirv_arena, .{
|
|
||||||
.target = try switch (sokol.gfx.queryBackend()) {
|
|
||||||
.GLCORE => spirv.Target.glsl,
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
.vertex_source = .{
|
|
||||||
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
|
|
||||||
},
|
|
||||||
|
|
||||||
.fragment_source = .{
|
|
||||||
.ops = desc.fragment_spirv_ops,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const shader = sokol.gfx.makeShader(shader_desc: {
|
|
||||||
const shader_desc = sokol.gfx.ShaderDesc{
|
|
||||||
.vs = stage_desc(spirv_program.vertex_stage),
|
|
||||||
.fs = stage_desc(spirv_program.fragment_stage),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Vertex attributes, for some reason they aren't needed?
|
|
||||||
|
|
||||||
break: shader_desc shader_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Review blending rules.
|
|
||||||
const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
|
|
||||||
var pipeline_desc = sokol.gfx.PipelineDesc{
|
|
||||||
.label = "Effect pipeline",
|
|
||||||
.layout = vertex_layout_state,
|
|
||||||
.shader = shader,
|
|
||||||
.index_type = .UINT16,
|
|
||||||
.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0},
|
|
||||||
};
|
|
||||||
|
|
||||||
pipeline_desc.colors[0] = .{
|
|
||||||
.write_mask = .RGBA,
|
|
||||||
|
|
||||||
.blend = .{
|
|
||||||
.enabled = true,
|
|
||||||
.src_factor_rgb = .SRC_ALPHA,
|
|
||||||
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
break: pipeline_desc pipeline_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
const properties = try coral.heap.allocator.alloc(
|
|
||||||
coral.io.Byte,
|
|
||||||
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
coral.heap.allocator.free(properties);
|
|
||||||
sokol.gfx.destroyPipeline(pipeline);
|
|
||||||
sokol.gfx.destroyShader(shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.shader = shader,
|
|
||||||
.pipeline = pipeline,
|
|
||||||
.properties = properties,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
|
|
||||||
var stage = sokol.gfx.ShaderStageDesc{
|
|
||||||
.entry = spirv_stage.entry_point,
|
|
||||||
.source = spirv_stage.source,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
|
|
||||||
const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
const stage_uniform_block = &stage.uniform_blocks[slot];
|
|
||||||
|
|
||||||
stage_uniform_block.layout = switch (uniform_block.layout) {
|
|
||||||
.std140 => .STD140,
|
|
||||||
};
|
|
||||||
|
|
||||||
stage_uniform_block.size = uniform_block.size();
|
|
||||||
|
|
||||||
for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
|
|
||||||
stage_uniform.* = .{
|
|
||||||
.type = switch (uniform.type) {
|
|
||||||
.float => .FLOAT,
|
|
||||||
.float2 => .FLOAT2,
|
|
||||||
.float3 => .FLOAT3,
|
|
||||||
.float4 => .FLOAT4,
|
|
||||||
.integer => .INT,
|
|
||||||
.integer2 => .INT2,
|
|
||||||
.integer3 => .INT3,
|
|
||||||
.integer4 => .INT4,
|
|
||||||
.matrix4 => .MAT4,
|
|
||||||
},
|
|
||||||
|
|
||||||
.name = uniform.name,
|
|
||||||
.array_count = uniform.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_images) |slot| {
|
|
||||||
const image = &(spirv_stage.has_images[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.images[slot] = .{
|
|
||||||
.multisampled = image.is_multisampled,
|
|
||||||
.image_type = ._2D,
|
|
||||||
.sample_type = .FLOAT,
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_samplers) |slot| {
|
|
||||||
const sampler = &(spirv_stage.has_samplers[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.samplers[slot] = .{
|
|
||||||
.sampler_type = switch (sampler.*) {
|
|
||||||
.filtering => .FILTERING,
|
|
||||||
.non_filtering => .NONFILTERING,
|
|
||||||
.comparison => .COMPARISON,
|
|
||||||
},
|
|
||||||
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
|
|
||||||
const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.image_sampler_pairs[slot] = .{
|
|
||||||
.glsl_name = image_sampler_pair.name,
|
|
||||||
.image_slot = @intCast(image_sampler_pair.image_slot),
|
|
||||||
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vertex_layout_state = sokol.gfx.VertexLayoutState{
|
|
||||||
.attrs = get: {
|
|
||||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
|
||||||
|
|
||||||
attrs[0] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[1] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[2] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[3] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[4] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[5] = .{
|
|
||||||
.format = .UBYTE4N,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[6] = .{
|
|
||||||
.format = .FLOAT,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[7] = .{
|
|
||||||
.format = .FLOAT4,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
break: get attrs;
|
|
||||||
},
|
|
||||||
|
|
||||||
.buffers = get: {
|
|
||||||
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
|
||||||
|
|
||||||
vertex_buffer_layouts[1].step_func = .PER_INSTANCE;
|
|
||||||
|
|
||||||
break: get vertex_buffer_layouts;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Texture = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = union (enum) {
|
|
||||||
empty,
|
|
||||||
render: RenderAccess,
|
|
||||||
static: StaticAccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RenderAccess = struct {
|
|
||||||
color_image: sokol.gfx.Image,
|
|
||||||
depth_image: sokol.gfx.Image,
|
|
||||||
attachments: sokol.gfx.Attachments,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StaticAccess = struct {
|
|
||||||
image: sokol.gfx.Image,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Texture) void {
|
|
||||||
switch (self.access) {
|
|
||||||
.render => |render| {
|
|
||||||
sokol.gfx.destroyImage(render.color_image);
|
|
||||||
sokol.gfx.destroyImage(render.depth_image);
|
|
||||||
sokol.gfx.destroyAttachments(render.attachments);
|
|
||||||
},
|
|
||||||
|
|
||||||
.static => |static| {
|
|
||||||
sokol.gfx.destroyImage(static.image);
|
|
||||||
},
|
|
||||||
|
|
||||||
.empty => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
|
|
||||||
const pixel_format = switch (desc.format) {
|
|
||||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
|
||||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (desc.access) {
|
|
||||||
.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(.{
|
|
||||||
.pixel_format = pixel_format,
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
.render_target = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const depth_image = sokol.gfx.makeImage(.{
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
.render_target = true,
|
|
||||||
.pixel_format = .DEPTH_STENCIL,
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
|
|
||||||
var attachments_desc = sokol.gfx.AttachmentsDesc{
|
|
||||||
.depth_stencil = .{
|
|
||||||
.image = depth_image,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
attachments_desc.colors[0] = .{
|
|
||||||
.image = color_image,
|
|
||||||
};
|
|
||||||
|
|
||||||
break: attachments_desc attachments_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.render = .{
|
|
||||||
.attachments = attachments,
|
|
||||||
.color_image = color_image,
|
|
||||||
.depth_image = depth_image,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.static => |static| {
|
|
||||||
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
|
|
||||||
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: {
|
|
||||||
var image_desc = sokol.gfx.ImageDesc{
|
|
||||||
.height = height,
|
|
||||||
.pixel_format = pixel_format,
|
|
||||||
.width = static.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
|
|
||||||
|
|
||||||
break: image_desc image_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
sokol.gfx.destroyImage(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.width = static.width,
|
|
||||||
.height = height,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.image = image,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const EffectPool = coral.Pool(Effect);
|
|
||||||
|
|
||||||
const TexturePool = coral.Pool(Texture);
|
|
||||||
|
|
||||||
pub fn create_effect(self: *Resources, desc: gfx.Effect.Desc) !gfx.Effect {
|
|
||||||
var effect = try Effect.init(desc);
|
|
||||||
|
|
||||||
errdefer effect.deinit();
|
|
||||||
|
|
||||||
return @enumFromInt(try self.effects.insert(effect));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_texture(self: *Resources, desc: gfx.Texture.Desc) !gfx.Texture {
|
|
||||||
var texture = try Texture.init(desc);
|
|
||||||
|
|
||||||
errdefer texture.deinit();
|
|
||||||
|
|
||||||
return @enumFromInt(try self.textures.insert(texture));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Resources) void {
|
|
||||||
var textures = self.textures.values();
|
|
||||||
|
|
||||||
while (textures.next()) |texture| {
|
|
||||||
texture.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.textures.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy_effect(self: *Resources, handle: gfx.Effect) bool {
|
|
||||||
switch (handle) {
|
|
||||||
.default => {},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
var effect = self.effects.remove(@intFromEnum(handle)) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
effect.deinit();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy_texture(self: *Resources, handle: gfx.Texture) bool {
|
|
||||||
switch (handle) {
|
|
||||||
.default => {},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
var texture = self.textures.remove(@intFromEnum(handle)) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
texture.deinit();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_effect(self: *Resources, handle: gfx.Effect) ?*Effect {
|
|
||||||
return self.effects.get(@intFromEnum(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_texture(self: *Resources, handle: gfx.Texture) ?*Texture {
|
|
||||||
return self.textures.get(@intFromEnum(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) !Resources {
|
|
||||||
var pools = Resources{
|
|
||||||
.effects = EffectPool.init(allocator),
|
|
||||||
.textures = TexturePool.init(allocator),
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
pools.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const assert = struct {
|
|
||||||
fn is_handle(expected: anytype, actual: @TypeOf(expected)) void {
|
|
||||||
std.debug.assert(actual == expected);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.is_handle(gfx.Effect.default, try pools.create_effect(.{
|
|
||||||
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
|
|
||||||
}));
|
|
||||||
|
|
||||||
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.render = .{
|
|
||||||
.width = 0,
|
|
||||||
.height = 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return pools;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var default_sampler: sokol.gfx.Sampler = undefined;
|
|
||||||
|
|
||||||
var quad_index_buffer: sokol.gfx.Buffer = undefined;
|
|
||||||
|
|
||||||
var quad_vertex_buffer: sokol.gfx.Buffer = undefined;
|
|
||||||
|
|
||||||
const vertex_indices = .{
|
|
||||||
.mesh = 0,
|
|
||||||
.instance = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn run(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
|
|
||||||
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 pools = try Resources.init(coral.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
pools.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Vertex = struct {
|
|
||||||
xy: @Vector(2, f32),
|
|
||||||
uv: @Vector(2, f32),
|
|
||||||
};
|
|
||||||
|
|
||||||
const quad_indices = [_]u16{0, 1, 2, 0, 2, 3};
|
|
||||||
|
|
||||||
const quad_vertices = [_]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}},
|
|
||||||
};
|
|
||||||
|
|
||||||
quad_index_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(&quad_indices),
|
|
||||||
.type = .INDEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
quad_vertex_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(&quad_vertices),
|
|
||||||
.type = .VERTEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
default_sampler = sokol.gfx.makeSampler(.{});
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (pending_work.dequeue()) {
|
|
||||||
.load_effect => |load| {
|
|
||||||
const effect = try pools.create_effect(load.desc);
|
|
||||||
|
|
||||||
if (!load.loaded.resolve(effect)) {
|
|
||||||
std.debug.assert(pools.destroy_effect(effect));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.load_texture => |load| {
|
|
||||||
const texture = try pools.create_texture(load.desc);
|
|
||||||
|
|
||||||
if (!load.loaded.resolve(texture)) {
|
|
||||||
std.debug.assert(pools.destroy_texture(texture));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.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{
|
|
||||||
.swapchain = .{
|
|
||||||
.width = render_frame.width,
|
|
||||||
.height = render_frame.height,
|
|
||||||
.sample_count = 1,
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.gl = .{.framebuffer = 0},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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_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(&pools, draw_texture),
|
|
||||||
.set_effect => |set_effect| frame.set_effect(&pools, set_effect),
|
|
||||||
.set_target => |set_target| frame.set_target(&pools, set_target),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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: {
|
|
||||||
var pass = sokol.gfx.Pass{
|
|
||||||
.swapchain = frame.swapchain,
|
|
||||||
|
|
||||||
.action = .{
|
|
||||||
.stencil = .{.load_action = .CLEAR},
|
|
||||||
.depth = .{.load_action = .CLEAR},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pass.action.colors[0] = .{.load_action = .CLEAR};
|
|
||||||
|
|
||||||
break: swapchain_pass pass;
|
|
||||||
});
|
|
||||||
|
|
||||||
try frame.draw_texture(&pools, .{
|
|
||||||
.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.flush(&pools);
|
|
||||||
sokol.gfx.endPass();
|
|
||||||
sokol.gfx.commit();
|
|
||||||
ext.SDL_GL_SwapWindow(window);
|
|
||||||
render_frame.finished.set();
|
|
||||||
},
|
|
||||||
|
|
||||||
.shutdown => {
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
|
|
||||||
.unload_effect => |unload| {
|
|
||||||
if (!pools.destroy_effect(unload.handle)) {
|
|
||||||
@panic("Attempt to unload a non-existent effect");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.unload_texture => |unload| {
|
|
||||||
if (!pools.destroy_texture(unload.handle)) {
|
|
||||||
@panic("Attempt to unload a non-existent texture");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var work_thread: std.Thread = undefined;
|
|
|
@ -4,8 +4,6 @@ const ext = @cImport({
|
||||||
@cInclude("spirv-cross/spirv_cross_c.h");
|
@cInclude("spirv-cross/spirv_cross_c.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Error = std.mem.Allocator.Error || error {
|
pub const Error = std.mem.Allocator.Error || error {
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
pub const Format = enum {
|
|
||||||
rgba8,
|
|
||||||
bgra8,
|
|
||||||
|
|
||||||
pub fn byte_size(self: Format) usize {
|
|
||||||
return switch (self) {
|
|
||||||
.rgba8, .bgra8 => 4,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
format: Format,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = union (enum) {
|
|
||||||
static: StaticAccess,
|
|
||||||
render: RenderAccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StaticAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
data: []const coral.io.Byte,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RenderAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Handle = enum (u32) {
|
|
||||||
default,
|
|
||||||
backbuffer,
|
|
||||||
_,
|
|
||||||
};
|
|
Loading…
Reference in New Issue