diff --git a/src/flow/World.zig b/src/flow/World.zig index 71db4e9..9041cc9 100644 --- a/src/flow/World.zig +++ b/src/flow/World.zig @@ -31,14 +31,14 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even } pub fn deinit(self: *Self) void { - for (&self.thread_restricted_resources) |*resources| { - resources.deinit(); - } - for (self.event_systems.values) |*schedule| { schedule.deinit(self); } + for (&self.thread_restricted_resources) |*resources| { + resources.deinit(); + } + if (self.thread_pool) |thread_pool| { thread_pool.deinit(); coral.heap.allocator.destroy(thread_pool); diff --git a/src/flow/flow.zig b/src/flow/flow.zig index 566de79..c9fd36d 100644 --- a/src/flow/flow.zig +++ b/src/flow/flow.zig @@ -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); } } diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index d393cff..d0567c9 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -4,15 +4,13 @@ pub const colors = @import("./gfx/colors.zig"); const coral = @import("coral"); -const bmp = @import("./gfx/bmp.zig"); - const ext = @import("./ext.zig"); const flow = @import("flow"); const msg = @import("./msg.zig"); -const sokol = @import("./gfx/sokol.zig"); +const rendering = @import("./gfx/rendering.zig"); 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 .{ - .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 { try self.pending.stack.push_grow(.{.set_target = command}); } @@ -361,6 +363,80 @@ pub const Transform2D = extern struct { 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 { 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.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{ .{ .extension = "bmp", - .load_file = bmp.load_file, + .load_file = load_bmp_texture, }, }; diff --git a/src/ona/gfx/Resources.zig b/src/ona/gfx/Resources.zig new file mode 100644 index 0000000..b4718de --- /dev/null +++ b/src/ona/gfx/Resources.zig @@ -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; +} diff --git a/src/ona/gfx/bmp.zig b/src/ona/gfx/bmp.zig deleted file mode 100644 index 8bd194a..0000000 --- a/src/ona/gfx/bmp.zig +++ /dev/null @@ -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, - }, - }, - }; -} diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig new file mode 100644 index 0000000..bba5fa6 --- /dev/null +++ b/src/ona/gfx/rendering.zig @@ -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; diff --git a/src/ona/gfx/sokol.zig b/src/ona/gfx/sokol.zig deleted file mode 100644 index 815fcf1..0000000 --- a/src/ona/gfx/sokol.zig +++ /dev/null @@ -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; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index def2040..6972e8a 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -4,8 +4,6 @@ const ext = @cImport({ @cInclude("spirv-cross/spirv_cross_c.h"); }); -const sokol = @import("sokol"); - const std = @import("std"); pub const Error = std.mem.Allocator.Error || error { diff --git a/src/ona/gfx/textures.zig b/src/ona/gfx/textures.zig deleted file mode 100644 index 206a49f..0000000 --- a/src/ona/gfx/textures.zig +++ /dev/null @@ -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, - _, -};