Tidy up GLSL code injection interface
This commit is contained in:
parent
8dacc9b080
commit
e1d41ded4a
18
readme.md
18
readme.md
@ -4,12 +4,14 @@
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Overview](#overview)
|
- [Ona](#ona)
|
||||||
1. [Goals](#goals)
|
- [Table of Contents](#table-of-contents)
|
||||||
1. [Technical Details](#technical-details)
|
- [Overview](#overview)
|
||||||
1. [Requirements](#requirements)
|
- [Goals](#goals)
|
||||||
1. [Building](#building)
|
- [Technical Details](#technical-details)
|
||||||
1. [Packaging](#packaging)
|
- [Requirements](#requirements)
|
||||||
|
- [Building](#building)
|
||||||
|
- [Packaging](#packaging)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -84,5 +86,5 @@ app.root_module.addImport("coral", ona_dependency.module("coral"));
|
|||||||
b.installArtifact(app);
|
b.installArtifact(app);
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Create a `main.zig` containing a valid Ona app declaration.
|
1. Create a `main.zig` containing a valid Ona app declaration.
|
||||||
6. Run `zig build` to build your new game application.
|
2. Run `zig build` to build your new game application.
|
||||||
|
@ -101,6 +101,18 @@ pub fn allocFormatted(allocator: std.mem.Allocator, comptime format: []const u8,
|
|||||||
return @constCast(printFormatted(buffer, format, args) catch unreachable);
|
return @constCast(printFormatted(buffer, format, args) catch unreachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn altFormat(value: anytype, comptime format: fn (@TypeOf(value), Writable) ReadWriteError!void) struct {
|
||||||
|
formattable: @TypeOf(value),
|
||||||
|
|
||||||
|
pub fn writeFormat(self: @This(), output: Writable) ReadWriteError!void {
|
||||||
|
return format(self.formattable, output);
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
return .{
|
||||||
|
.formattable = value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn countFormatted(comptime format: []const u8, args: anytype) usize {
|
pub fn countFormatted(comptime format: []const u8, args: anytype) usize {
|
||||||
var count = WriteCount{};
|
var count = WriteCount{};
|
||||||
|
|
||||||
|
131
src/ona/gfx.zig
131
src/ona/gfx.zig
@ -10,14 +10,9 @@ const ona = @import("./ona.zig");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Display = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
is_hidden: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Context = struct {
|
const Context = struct {
|
||||||
window: *ext.SDL_Window,
|
window: *ext.SDL_Window,
|
||||||
|
frame_arena: std.heap.ArenaAllocator,
|
||||||
gpu_device: *ext.SDL_GPUDevice,
|
gpu_device: *ext.SDL_GPUDevice,
|
||||||
shader_compiler: ext.shaderc_compiler_t,
|
shader_compiler: ext.shaderc_compiler_t,
|
||||||
spirv_options: ext.shaderc_compile_options_t,
|
spirv_options: ext.shaderc_compile_options_t,
|
||||||
@ -29,8 +24,11 @@ const Context = struct {
|
|||||||
fragment,
|
fragment,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn assemble(self: *Context, allocator: std.mem.Allocator, kind: AssemblyKind, name: [*:0]const u8, source: []const u8) AssembleError![]u8 {
|
pub fn assembleShader(self: *Context, kind: AssemblyKind, name: [*:0]const u8, source: []const u8, injections: anytype) AssembleError![]const u8 {
|
||||||
const result = ext.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (kind) {
|
const frame_allocator = self.frame_arena.allocator();
|
||||||
|
const injected_source = try glsl.inject(frame_allocator, source, 430, injections);
|
||||||
|
|
||||||
|
const result = ext.shaderc_compile_into_spv(self.shader_compiler, injected_source.ptr, injected_source.len, switch (kind) {
|
||||||
.fragment => ext.shaderc_glsl_vertex_shader,
|
.fragment => ext.shaderc_glsl_vertex_shader,
|
||||||
.vertex => ext.shaderc_glsl_vertex_shader,
|
.vertex => ext.shaderc_glsl_vertex_shader,
|
||||||
}, name, "main", self.spirv_options) orelse {
|
}, name, "main", self.spirv_options) orelse {
|
||||||
@ -46,12 +44,12 @@ const Context = struct {
|
|||||||
const compiled_len = ext.shaderc_result_get_length(result);
|
const compiled_len = ext.shaderc_result_get_length(result);
|
||||||
const compiled_ptr = ext.shaderc_result_get_bytes(result);
|
const compiled_ptr = ext.shaderc_result_get_bytes(result);
|
||||||
|
|
||||||
return allocator.dupe(u8, compiled_ptr[0..compiled_len]);
|
return frame_allocator.dupe(u8, compiled_ptr[0..compiled_len]);
|
||||||
},
|
},
|
||||||
|
|
||||||
ext.shaderc_compilation_status_compilation_error, ext.shaderc_compilation_status_invalid_stage => {
|
ext.shaderc_compilation_status_compilation_error, ext.shaderc_compilation_status_invalid_stage => {
|
||||||
std.log.err("{s}", .{ext.shaderc_result_get_error_message(result)});
|
std.log.err("{s}", .{ext.shaderc_result_get_error_message(result)});
|
||||||
std.log.debug("problematic shader:\n{s}", .{source});
|
std.log.debug("problematic shader:\n{s}", .{injected_source});
|
||||||
|
|
||||||
return error.BadSyntax;
|
return error.BadSyntax;
|
||||||
},
|
},
|
||||||
@ -61,11 +59,16 @@ const Context = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assembleShaderEmbedded(self: *Context, kind: AssemblyKind, comptime path: [:0]const u8, injections: anytype) AssembleError![]const u8 {
|
||||||
|
return self.assembleShader(kind, path, @embedFile(path), injections);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Context) void {
|
pub fn deinit(self: *Context) void {
|
||||||
ext.shaderc_compile_options_release(self.spirv_options);
|
ext.shaderc_compile_options_release(self.spirv_options);
|
||||||
ext.shaderc_compiler_release(self.shader_compiler);
|
ext.shaderc_compiler_release(self.shader_compiler);
|
||||||
ext.SDL_DestroyGPUDevice(self.gpu_device);
|
ext.SDL_DestroyGPUDevice(self.gpu_device);
|
||||||
ext.SDL_DestroyWindow(self.window);
|
ext.SDL_DestroyWindow(self.window);
|
||||||
|
self.frame_arena.deinit();
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
@ -75,67 +78,44 @@ const Context = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Display = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
is_hidden: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Effect = struct {
|
||||||
|
pipeline: *ext.SDL_GPUGraphicsPipeline,
|
||||||
|
};
|
||||||
|
|
||||||
fn compile_shaders(context: ona.Write(Context)) !void {
|
fn compile_shaders(context: ona.Write(Context)) !void {
|
||||||
const Camera = extern struct {
|
const Camera = extern struct {
|
||||||
projection: [4]@Vector(4, f32),
|
projection: [4]@Vector(4, f32),
|
||||||
};
|
};
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
const vertex_spirv = try context.ptr.assembleShaderEmbedded(.vertex, "./gfx/canvas.vert", .{
|
||||||
|
.model_xy = glsl.nativeInput(0, @Vector(2, f32)),
|
||||||
|
.model_uv = glsl.nativeInput(1, @Vector(2, f32)),
|
||||||
|
.model_rgba = glsl.nativeInput(2, @Vector(4, f32)),
|
||||||
|
.instance_uv_offset = glsl.nativeInput(3, @Vector(2, f32)),
|
||||||
|
.instance_uv_scale = glsl.nativeInput(4, @Vector(2, f32)),
|
||||||
|
.instance_xbasis = glsl.nativeInput(5, @Vector(2, f32)),
|
||||||
|
.instance_ybasis = glsl.nativeInput(6, @Vector(2, f32)),
|
||||||
|
.instance_origin = glsl.nativeInput(7, @Vector(2, f32)),
|
||||||
|
.instance_pivot = glsl.nativeInput(8, @Vector(2, f32)),
|
||||||
|
.instance_rgb = glsl.nativeInput(9, @Vector(3, f32)),
|
||||||
|
.instance_bits = glsl.nativeInput(10, u32),
|
||||||
|
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
|
||||||
|
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
|
||||||
|
.camera = glsl.nativeUniformBlock(0, Camera),
|
||||||
|
});
|
||||||
|
|
||||||
defer {
|
const fragment_spirv = try context.ptr.assembleShaderEmbedded(.fragment, "./gfx/canvas.frag", .{
|
||||||
arena.deinit();
|
.vertex_uv = glsl.nativeInput(0, @Vector(2, f32)),
|
||||||
}
|
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
|
||||||
|
.source_texture = glsl.Sampler{ .sampler_2d = 0 },
|
||||||
const arena_allocator = arena.allocator();
|
.color = glsl.nativeOutput(0, @Vector(4, f32)),
|
||||||
|
.camera = glsl.nativeUniformBlock(0, Camera),
|
||||||
const vertex_spirv = try context.ptr.assemble(arena_allocator, .vertex, "./gfx/effect.vert", try glsl.inject(arena_allocator, @embedFile("./gfx/effect.vert"), .{
|
|
||||||
.inputs = &.{
|
|
||||||
.{ "model_xy", .vec2 },
|
|
||||||
.{ "model_uv", .vec2 },
|
|
||||||
.{ "model_rgba", .vec4 },
|
|
||||||
|
|
||||||
.{ "instance_rect", .vec4 },
|
|
||||||
.{ "instance_xbasis", .vec2 },
|
|
||||||
.{ "instance_ybasis", .vec2 },
|
|
||||||
.{ "instance_origin", .vec2 },
|
|
||||||
.{ "instance_color", .vec4 },
|
|
||||||
.{ "instance_depth", .float },
|
|
||||||
},
|
|
||||||
|
|
||||||
.outputs = &.{
|
|
||||||
.{ "color", .vec4 },
|
|
||||||
.{ "uv", .vec2 },
|
|
||||||
},
|
|
||||||
|
|
||||||
.uniforms = &.{
|
|
||||||
.{ "camera", .init(0, Camera) },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const fragment_spirv = try context.ptr.assemble(arena_allocator, .fragment, "./gfx/effect.frag", try glsl.inject(arena_allocator, @embedFile("./gfx/effect.frag"), .{
|
|
||||||
.inputs = &.{
|
|
||||||
.{ "vertex_color", .vec4 },
|
|
||||||
.{ "vertex_uv", .vec2 },
|
|
||||||
},
|
|
||||||
|
|
||||||
.samplers = &.{
|
|
||||||
.{ "source_texture", .{ .sampler_2d = 0 } },
|
|
||||||
},
|
|
||||||
|
|
||||||
.outputs = &.{
|
|
||||||
.{ "color", .vec4 },
|
|
||||||
},
|
|
||||||
|
|
||||||
.uniforms = &.{
|
|
||||||
.{ "camera", .init(0, Camera) },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const effect_fragment = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
|
||||||
.code = fragment_spirv.ptr,
|
|
||||||
.code_size = fragment_spirv.len,
|
|
||||||
.format = ext.SDL_GPU_SHADERFORMAT_SPIRV,
|
|
||||||
.entrypoint = "main",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const effect_vertex = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
const effect_vertex = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
||||||
@ -145,8 +125,20 @@ fn compile_shaders(context: ona.Write(Context)) !void {
|
|||||||
.entrypoint = "main",
|
.entrypoint = "main",
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = effect_fragment;
|
defer {
|
||||||
_ = effect_vertex;
|
ext.SDL_ReleaseGPUShader(context.ptr.gpu_device, effect_vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const effect_fragment = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
||||||
|
.code = fragment_spirv.ptr,
|
||||||
|
.code_size = fragment_spirv.len,
|
||||||
|
.format = ext.SDL_GPU_SHADERFORMAT_SPIRV,
|
||||||
|
.entrypoint = "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
defer {
|
||||||
|
ext.SDL_ReleaseGPUShader(context.ptr.gpu_device, effect_fragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void {
|
pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void {
|
||||||
@ -297,6 +289,7 @@ pub fn setup(app: *ona.App) !void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try app.setState(Context{
|
try app.setState(Context{
|
||||||
|
.frame_arena = .init(coral.heap.allocator),
|
||||||
.shader_compiler = shader_compiler,
|
.shader_compiler = shader_compiler,
|
||||||
.spirv_options = spirv_options,
|
.spirv_options = spirv_options,
|
||||||
.window = window,
|
.window = window,
|
||||||
@ -324,4 +317,8 @@ pub fn synchronize(context: ona.Write(Context), display: ona.Read(Display)) !voi
|
|||||||
std.log.warn("failed to change window visibility", .{});
|
std.log.warn("failed to change window visibility", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.ptr.frame_arena.reset(.{ .retain_with_limit = 1024 * 1024 })) {
|
||||||
|
std.log.warn("failed to shrink frame arena during reset, freeing instead...", .{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
4
src/ona/gfx/canvas.frag
Normal file
4
src/ona/gfx/canvas.frag
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
void main() {
|
||||||
|
color = vertex_rgba * texture(source_texture, vertex_uv);
|
||||||
|
}
|
12
src/ona/gfx/canvas.vert
Normal file
12
src/ona/gfx/canvas.vert
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
void main() {
|
||||||
|
const vec2 local_xy = model_xy + instance_pivot;
|
||||||
|
const vec2 world_position = instance_origin + (local_xy.x * instance_xbasis) + (local_xy.y * instance_ybasis);
|
||||||
|
const vec2 projected = (camera.projection * vec4(world_position, 0, 1)).xy;
|
||||||
|
const vec2 depth_alpha = unpackHalf2x16(instance_bits);
|
||||||
|
|
||||||
|
gl_Position = vec4(projected, depth_alpha.x, 1.0);
|
||||||
|
|
||||||
|
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
|
||||||
|
uv = instance_uv_offset + (model_uv * instance_uv_scale);
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
void main() {
|
|
||||||
color = vertex_color * texture(source_texture, vertex_uv);
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
void main() {
|
|
||||||
const vec2 world_position = instance_origin + model_xy.x * instance_xbasis + model_xy.y * instance_ybasis;
|
|
||||||
const vec2 projected_position = (camera.projection * vec4(world_position, 0, 1)).xy;
|
|
||||||
const vec2 rect_size = instance_rect.zw - instance_rect.xy;
|
|
||||||
const vec4 screen_position = vec4(projected_position, instance_depth, 1.0);
|
|
||||||
|
|
||||||
gl_Position = screen_position;
|
|
||||||
color = model_rgba * instance_color;
|
|
||||||
uv = instance_rect.xy + (model_uv * rect_size);
|
|
||||||
}
|
|
@ -4,100 +4,6 @@ const ona = @import("../ona.zig");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const InjectOptions = struct {
|
|
||||||
version: u64 = 430,
|
|
||||||
inputs: []const Entry(Primitive) = &.{},
|
|
||||||
outputs: []const Entry(Primitive) = &.{},
|
|
||||||
uniforms: []const Entry(Uniform) = &.{},
|
|
||||||
samplers: []const Entry(Sampler) = &.{},
|
|
||||||
|
|
||||||
pub fn Entry(comptime Item: type) type {
|
|
||||||
return struct { [:0]const u8, Item };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeFormat(self: InjectOptions, source: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
|
||||||
try coral.bytes.writeFormatted(source, "#version {version}\n\n", .{ .version = coral.utf8.cDec(self.version) });
|
|
||||||
|
|
||||||
for (0..self.inputs.len, self.inputs) |location, input| {
|
|
||||||
const name, const primitive = input;
|
|
||||||
|
|
||||||
try coral.bytes.writeFormatted(source, "layout (location = {location}) in {primitive} {name};\n", .{
|
|
||||||
.location = coral.utf8.cDec(location),
|
|
||||||
.primitive = @tagName(primitive),
|
|
||||||
.name = name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try coral.bytes.writeAll(source, "\n");
|
|
||||||
|
|
||||||
for (0..self.outputs.len, self.outputs) |location, output| {
|
|
||||||
const name, const primitive = output;
|
|
||||||
|
|
||||||
try coral.bytes.writeFormatted(source, "layout (location = {location}) out {primitive} {name};\n", .{
|
|
||||||
.location = coral.utf8.cDec(location),
|
|
||||||
.primitive = @tagName(primitive),
|
|
||||||
.name = name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try coral.bytes.writeAll(source, "\n");
|
|
||||||
|
|
||||||
for (self.samplers) |named_sampler| {
|
|
||||||
const name, const sampler = named_sampler;
|
|
||||||
|
|
||||||
switch (sampler) {
|
|
||||||
.sampler_2d => |binding| {
|
|
||||||
try coral.bytes.writeFormatted(source, "layout (binding = {binding}) uniform sampler2D {name};\n\n", .{
|
|
||||||
.binding = coral.utf8.cDec(binding),
|
|
||||||
.name = name,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (self.uniforms) |named_uniform| {
|
|
||||||
const name, const uniform = named_uniform;
|
|
||||||
|
|
||||||
try coral.bytes.writeFormatted(source, "layout (binding = {binding}) uniform {type_name} {{\n", .{
|
|
||||||
.binding = coral.utf8.cDec(uniform.binding),
|
|
||||||
.type_name = uniform.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
var field = uniform.field;
|
|
||||||
|
|
||||||
while (true) : (field = field.has_next orelse {
|
|
||||||
break;
|
|
||||||
}) {
|
|
||||||
try coral.bytes.writeFormatted(source, "\t{primitive} {name};\n", .{
|
|
||||||
.primitive = @tagName(field.primitive),
|
|
||||||
.name = field.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try coral.bytes.writeFormatted(source, "}} {name};\n\n", .{ .name = name });
|
|
||||||
}
|
|
||||||
|
|
||||||
try coral.bytes.writeAll(source, "#line 1\n\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Primitive = enum {
|
|
||||||
float,
|
|
||||||
vec2,
|
|
||||||
vec3,
|
|
||||||
vec4,
|
|
||||||
mat4,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Sampler = union(enum) {
|
|
||||||
sampler_2d: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Uniform = struct {
|
|
||||||
name: [:0]const u8,
|
|
||||||
binding: usize,
|
|
||||||
field: *const Field,
|
|
||||||
|
|
||||||
pub const Field = struct {
|
pub const Field = struct {
|
||||||
name: [:0]const u8,
|
name: [:0]const u8,
|
||||||
primitive: Primitive,
|
primitive: Primitive,
|
||||||
@ -108,15 +14,12 @@ pub const Uniform = struct {
|
|||||||
@compileError("Struct contains no fields");
|
@compileError("Struct contains no fields");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const first_uniform_field = uniform_fields[0];
|
||||||
|
|
||||||
const field = struct {
|
const field = struct {
|
||||||
const instance = Field{
|
const instance = Field{
|
||||||
.name = uniform_fields[0].name,
|
.primitive = nativePrimitive(first_uniform_field.type),
|
||||||
|
.name = first_uniform_field.name,
|
||||||
.primitive = switch (uniform_fields[0].type) {
|
|
||||||
@Vector(4, f32) => .vec4,
|
|
||||||
[4]@Vector(4, f32) => .mat4,
|
|
||||||
else => @compileError("Unsupported uniform type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
.has_next = switch (uniform_fields.len) {
|
.has_next = switch (uniform_fields.len) {
|
||||||
1 => null,
|
1 => null,
|
||||||
@ -129,7 +32,111 @@ pub const Uniform = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(binding: usize, comptime Struct: type) Uniform {
|
pub const Input = struct {
|
||||||
|
location: usize,
|
||||||
|
primitive: Primitive,
|
||||||
|
|
||||||
|
pub fn serialize(self: Input, name: []const u8, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
return coral.bytes.writeFormatted(output, "layout (location = {location}) in {primitive} {name};\n", .{
|
||||||
|
.primitive = @tagName(self.primitive),
|
||||||
|
.location = coral.utf8.cDec(self.location),
|
||||||
|
.name = name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Output = struct {
|
||||||
|
location: usize,
|
||||||
|
primitive: Primitive,
|
||||||
|
|
||||||
|
pub fn serialize(self: Output, name: []const u8, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
return coral.bytes.writeFormatted(output, "layout (location = {location}) out {primitive} {name};\n", .{
|
||||||
|
.primitive = @tagName(self.primitive),
|
||||||
|
.location = coral.utf8.cDec(self.location),
|
||||||
|
.name = name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Primitive = enum {
|
||||||
|
float,
|
||||||
|
uint,
|
||||||
|
int,
|
||||||
|
vec2,
|
||||||
|
vec3,
|
||||||
|
vec4,
|
||||||
|
mat4,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Sampler = union(enum) {
|
||||||
|
sampler_2d: usize,
|
||||||
|
|
||||||
|
pub fn serialize(self: Sampler, name: []const u8, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
return switch (self) {
|
||||||
|
.sampler_2d => |binding| coral.bytes.writeFormatted(output, "layout (binding = {binding}) uniform sampler2D {name};\n", .{
|
||||||
|
.binding = coral.utf8.cDec(binding),
|
||||||
|
.name = name,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UniformBlock = struct {
|
||||||
|
name: [:0]const u8,
|
||||||
|
binding: usize,
|
||||||
|
field: *const Field,
|
||||||
|
|
||||||
|
pub fn serialize(self: UniformBlock, name: []const u8, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
var field = self.field;
|
||||||
|
|
||||||
|
try coral.bytes.writeFormatted(output, "layout (binding = {binding}) uniform {name} {{\n", .{
|
||||||
|
.binding = coral.utf8.cDec(self.binding),
|
||||||
|
.name = self.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) : (field = field.has_next orelse {
|
||||||
|
break;
|
||||||
|
}) {
|
||||||
|
try coral.bytes.writeFormatted(output, "\t{primitive} {name};\n", .{
|
||||||
|
.primitive = @tagName(field.primitive),
|
||||||
|
.name = field.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeFormatted(output, "}} {namespace};\n", .{
|
||||||
|
.namespace = name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn nativeInput(location: usize, comptime Value: type) Input {
|
||||||
|
return .{
|
||||||
|
.primitive = nativePrimitive(Value),
|
||||||
|
.location = location,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nativePrimitive(comptime Value: type) Primitive {
|
||||||
|
return switch (Value) {
|
||||||
|
f32 => .float,
|
||||||
|
i32 => .int,
|
||||||
|
u32 => .uint,
|
||||||
|
@Vector(2, f32) => .vec2,
|
||||||
|
@Vector(3, f32) => .vec3,
|
||||||
|
@Vector(4, f32) => .vec4,
|
||||||
|
[4]@Vector(4, f32) => .mat4,
|
||||||
|
else => @compileError(std.fmt.comptimePrint("{s} is not a GLSL-compatible type", .{@typeName(Value)})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nativeOutput(location: usize, comptime Value: type) Output {
|
||||||
|
return .{
|
||||||
|
.primitive = nativePrimitive(Value),
|
||||||
|
.location = location,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nativeUniformBlock(binding: usize, comptime Struct: type) UniformBlock {
|
||||||
// TODO: Review how GLSL identifier-safe names are generated.
|
// TODO: Review how GLSL identifier-safe names are generated.
|
||||||
const struct_name = @typeName(Struct);
|
const struct_name = @typeName(Struct);
|
||||||
|
|
||||||
@ -152,11 +159,39 @@ pub const Uniform = struct {
|
|||||||
.binding = binding,
|
.binding = binding,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inject(allocator: std.mem.Allocator, source_base: []const u8, version: usize, interface: anytype) std.mem.Allocator.Error![:0]u8 {
|
||||||
|
const Interface = @TypeOf(interface);
|
||||||
|
|
||||||
|
const interface_info = switch (@typeInfo(Interface)) {
|
||||||
|
.@"struct" => |@"struct"| @"struct",
|
||||||
|
else => @compileError("`interface` must be a struct"),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn inject(allocator: std.mem.Allocator, source_base: []const u8, options: InjectOptions) std.mem.Allocator.Error![:0]u8 {
|
if (interface_info.is_tuple) {
|
||||||
return coral.bytes.allocFormatted(allocator, "{injected_source}\n{original_source}\n", .{
|
@compileError("`interface` cannot be a tuple");
|
||||||
.injected_source = options,
|
}
|
||||||
|
|
||||||
|
const serialization = struct {
|
||||||
|
fn write(value: Interface, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
inline for (interface_info.fields) |field| {
|
||||||
|
const field_value = @field(value, field.name);
|
||||||
|
const FieldValue = @TypeOf(field_value);
|
||||||
|
|
||||||
|
if (!@hasDecl(FieldValue, "serialize")) {
|
||||||
|
@compileError(std.fmt.comptimePrint("`interface.{s}` is not a GLSL-serializable field", .{field.name}));
|
||||||
|
}
|
||||||
|
|
||||||
|
try field_value.serialize(field.name, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(output, "#line 1\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return coral.bytes.allocFormatted(allocator, "#version {version}\n{injected_source}\n{original_source}\n", .{
|
||||||
|
.version = coral.utf8.cDec(version),
|
||||||
|
.injected_source = coral.bytes.altFormat(interface, serialization.write),
|
||||||
.original_source = source_base,
|
.original_source = source_base,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
BIN
testing.spv
(Stored with Git LFS)
BIN
testing.spv
(Stored with Git LFS)
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user