Defer to augmented GLSL for shader compilation

This commit is contained in:
kayomn 2025-07-29 15:28:39 +01:00
parent 8566345f5e
commit 8dacc9b080
19 changed files with 477 additions and 5733 deletions

190
build.zig
View File

@ -1,5 +1,92 @@
const std = @import("std");
const BuildConfig = struct {
module_target: std.Build.ResolvedTarget,
spirv_target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
fn scan_demos(self: BuildConfig, ona_module: *std.Build.Module) void {
const b = ona_module.owner;
const build_demos_step = b.step("demos", "Build demos");
b.default_step.dependOn(build_demos_step);
const cwd = std.fs.cwd();
var demos_dir = cwd.openDir("src/demos/", .{ .iterate = true }) catch {
return build_demos_step.dependOn(&b.addFail("failed to open demo files directory").step);
};
defer {
demos_dir.close();
}
var demos_iterator = demos_dir.iterate();
while (demos_iterator.next() catch {
return build_demos_step.dependOn(&b.addFail("failed to iterate over next entry in demos directory").step);
}) |entry| {
if (entry.kind != .file) {
continue;
}
if (!std.mem.eql(u8, std.fs.path.extension(entry.name), ".zig")) {
continue;
}
const demo_executable = b.addExecutable(.{
.name = std.fmt.allocPrint(b.allocator, "{s}_demo", .{std.fs.path.stem(entry.name)}) catch {
return build_demos_step.dependOn(&b.addFail("failed to allocate demo name buffer").step);
},
.root_module = b.createModule(.{
.root_source_file = b.path(b.pathJoin(&.{ "src/demos/", entry.name })),
.target = self.module_target,
.optimize = self.optimize,
.imports = &.{
.{
.name = "ona",
.module = ona_module,
},
},
}),
});
demo_executable.linkSystemLibrary2("SDL3", .{
.needed = true,
.preferred_link_mode = .dynamic,
});
const demo_installation = b.addInstallArtifact(demo_executable, .{});
build_demos_step.dependOn(&demo_installation.step);
}
}
// TODO: Revisit Zig shaders once the SPIRV target becomes more mature.
// pub fn addShaders(self: BuildConfig, module: *std.Build.Module, shader_paths: []const []const u8) void {
// const b = module.owner;
// for (shader_paths) |shader_path| {
// const shader_path_stem = std.fs.path.stem(shader_path);
// const module_identifier = std.mem.join(b.allocator, ".", &.{ shader_path_stem, "spirv" }) catch {
// @panic("OOM");
// };
// const spirv_object = b.addObject(.{
// .name = module_identifier,
// .root_source_file = b.path(shader_path),
// .target = self.spirv_target,
// .use_llvm = false,
// });
// module.addAnonymousImport(module_identifier, .{ .root_source_file = spirv_object.getEmittedBin() });
// }
// }
};
const CommonArgs = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
@ -7,19 +94,31 @@ const CommonArgs = struct {
};
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const config = BuildConfig{
.optimize = b.standardOptimizeOption(.{}),
.module_target = b.standardTargetOptions(.{}),
.spirv_target = b.resolveTargetQuery(.{
.cpu_arch = .spirv64,
.os_tag = .vulkan,
.cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 },
.cpu_features_add = std.Target.spirv.featureSet(&.{.int64}),
.ofmt = .spirv,
}),
};
const shaderc_dependency = b.dependency("shaderc_zig", .{});
const coral_module = b.addModule("coral", .{
.root_source_file = b.path("src/coral/coral.zig"),
.target = target,
.optimize = optimize,
.target = config.module_target,
.optimize = config.optimize,
});
const ext_module = b.createModule(.{
.root_source_file = b.path("src/ext/ext.zig"),
.target = target,
.optimize = optimize,
.target = config.module_target,
.optimize = config.optimize,
.link_libc = true,
});
@ -28,10 +127,12 @@ pub fn build(b: *std.Build) void {
.preferred_link_mode = .dynamic,
});
ext_module.linkLibrary(shaderc_dependency.artifact("shaderc"));
const ona_module = b.addModule("ona", .{
.root_source_file = b.path("src/ona/ona.zig"),
.target = target,
.optimize = optimize,
.target = config.module_target,
.optimize = config.optimize,
.link_libc = true,
.imports = &.{
@ -46,17 +147,18 @@ pub fn build(b: *std.Build) void {
},
});
scan_demos(b, .{
.target = target,
.optimize = optimize,
.ona_module = ona_module,
});
// config.addShaders(ona_module, &.{
// "./src/ona/gfx/effect_shader.zig",
// "./src/ona/gfx/effect_fragment.zig",
// });
config.scan_demos(ona_module);
const test_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/tests.zig"),
.target = target,
.optimize = optimize,
.target = config.module_target,
.optimize = config.optimize,
.imports = &.{
.{
@ -72,61 +174,3 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&b.addRunArtifact(test_tests).step);
test_step.dependOn(&b.addInstallArtifact(test_tests, .{}).step);
}
fn scan_demos(b: *std.Build, common: CommonArgs) void {
const build_demos_step = b.step("demos", "Build demos");
b.default_step.dependOn(build_demos_step);
const cwd = std.fs.cwd();
var demos_dir = cwd.openDir("src/demos/", .{ .iterate = true }) catch {
return build_demos_step.dependOn(&b.addFail("failed to open demo files directory").step);
};
defer {
demos_dir.close();
}
var demos_iterator = demos_dir.iterate();
while (demos_iterator.next() catch {
return build_demos_step.dependOn(&b.addFail("failed to iterate over next entry in demos directory").step);
}) |entry| {
if (entry.kind != .file) {
continue;
}
if (!std.mem.eql(u8, std.fs.path.extension(entry.name), ".zig")) {
continue;
}
const demo_executable = b.addExecutable(.{
.name = std.fmt.allocPrint(b.allocator, "{s}_demo", .{std.fs.path.stem(entry.name)}) catch {
return build_demos_step.dependOn(&b.addFail("failed to allocate demo name buffer").step);
},
.root_module = b.createModule(.{
.root_source_file = b.path(b.pathJoin(&.{ "src/demos/", entry.name })),
.target = common.target,
.optimize = common.optimize,
.imports = &.{
.{
.name = "ona",
.module = common.ona_module,
},
},
}),
});
demo_executable.linkSystemLibrary2("SDL3", .{
.needed = true,
.preferred_link_mode = .dynamic,
});
const demo_installation = b.addInstallArtifact(demo_executable, .{});
build_demos_step.dependOn(&demo_installation.step);
}
}

View File

@ -1,6 +1,14 @@
.{
.name = .ona,
.version = "0.0.0",
.dependencies = .{
.shaderc_zig = .{
.url = "git+https://github.com/tiawl/shaderc.zig#06565d2af3beec9780b11524984211ebd104fd21",
.hash = "shaderc_zig-1.0.0-mOl840tjAwBiAnMSfRskq0Iq3JJ9jPRHy2JoEgnUvSpV",
},
},
.fingerprint = 0x7d0142e88b22421d,
.minimum_zig_version = "0.14.0",

View File

@ -16,8 +16,6 @@ pub const map = @import("./map.zig");
pub const scalars = @import("./scalars.zig");
pub const shaders = @import("./shaders.zig");
const std = @import("std");
pub const tree = @import("./tree.zig");

View File

@ -1,349 +0,0 @@
pub const Field = @import("./shaders/Field.zig");
pub const Root = @import("./shaders/Root.zig");
pub const Scope = @import("./shaders/Scope.zig");
pub const Type = @import("./shaders/Type.zig");
const coral = @import("./coral.zig");
const std = @import("std");
const tokens = @import("./shaders/tokens.zig");
pub const Argument = struct {
expression: *const Expression,
has_next: ?*const Argument = null,
};
pub const Block = struct {
scope: *const Scope,
depth: usize,
statements: ?*const Statement = null,
pub fn hasLastStatement(self: Block) ?*const Statement {
var statement = self.statements orelse {
return null;
};
while (true) {
statement = switch (statement.*) {
.declare_local => |local_declaration| local_declaration.has_next orelse {
return statement;
},
.mutate_local => |local_mutation| local_mutation.has_next orelse {
return statement;
},
.mutate_output => |output_mutation| output_mutation.has_next orelse {
return statement;
},
.return_expression => {
return statement;
},
};
}
}
};
pub const DefinitionError = std.mem.Allocator.Error || error{
TooManySymbols,
DuplicateIdentifier,
};
pub const Expression = union(enum) {
constant: [:0]const u8,
group_expression: *const Expression,
add: BinaryOperation,
subtract: BinaryOperation,
multiply: BinaryOperation,
divide: BinaryOperation,
equal: BinaryOperation,
greater_than: BinaryOperation,
greater_equal: BinaryOperation,
lesser_than: BinaryOperation,
lesser_equal: BinaryOperation,
negate_expression: *const Expression,
get_local: *const Local,
get_parameter: *const Parameter,
get_uniform: GetUniform,
get_object: GetObject,
mutate_local: LocalMutation,
mutate_output: OutputMutation,
get_texture: *const Texture,
get_output: *const Output,
get_input: *const Input,
invoke: Invocation,
convert: Conversion,
pow: Intrinsic,
abs: Intrinsic,
sin: Intrinsic,
sample: Builtin,
pub const BinaryOperation = struct {
rhs_expression: *const Expression,
lhs_expression: *const Expression,
type: *const Type,
};
pub const Builtin = struct {
has_argument: ?*const Argument,
signatures: []const Signature,
pub const Signature = struct {
parameter_types: []const *const Type,
return_type: *const Type,
};
pub fn inferType(self: Builtin) TypeError!*const Type {
signature_matching: for (self.signatures) |signature| {
var parameter_index: usize = 0;
var has_argument = self.has_argument;
while (has_argument) |argument| : ({
has_argument = argument.has_next;
parameter_index += 1;
}) {
if (signature.parameter_types.len == parameter_index) {
continue :signature_matching;
}
if (signature.parameter_types[parameter_index] != try argument.expression.inferType()) {
continue :signature_matching;
}
}
return signature.return_type;
}
return error.IncompatibleArguments;
}
};
pub const Conversion = struct {
target_type: *const Type,
first_argument: *const Argument,
parameter_types: []const *const Type,
};
pub const Intrinsic = struct {
allowed_parameter_types: []const *const Type,
expected_parameter_count: usize,
first_argument: *const Argument,
pub fn inferType(self: Intrinsic) TypeError!*const Type {
std.debug.assert(self.expected_parameter_count != 0);
const return_type = try self.first_argument.expression.inferType();
if (std.mem.indexOfScalar(*const Type, self.allowed_parameter_types, return_type) == null) {
return error.IncompatibleTypes;
}
var has_next_argument = self.first_argument.has_next;
var arguments_remaining = self.expected_parameter_count - 1;
while (has_next_argument) |next_argument| : ({
has_next_argument = next_argument.has_next;
arguments_remaining -= 1;
}) {
if (arguments_remaining == 0) {
return error.IncompatibleArguments;
}
if (try next_argument.expression.inferType() != return_type) {
return error.IncompatibleTypes;
}
}
if (arguments_remaining != 0) {
return error.IncompatibleArguments;
}
return return_type;
}
};
pub const GetObject = struct {
field: *const Field,
object_expression: *const Expression,
};
pub const GetUniform = struct {
field: *const Field,
uniform: *const Uniform,
};
pub const Invocation = struct {
function: *const Function,
arguments: ?*const Argument = null,
argument_count: usize = 0,
pub fn inferType(self: Invocation) TypeError!*const Type {
var parameters = self.function.parameters;
var arguments = self.arguments;
while (parameters) |parameter| {
const argument = arguments orelse {
return error.IncompatibleArguments;
};
defer {
parameters = parameter.has_next;
arguments = argument.has_next;
}
if (parameter.type != try argument.expression.inferType()) {
return error.IncompatibleTypes;
}
}
if (arguments != null) {
return error.IncompatibleArguments;
}
return self.function.has_return_type orelse error.IncompatibleTypes;
}
};
pub const OutputMutation = struct {
output: *const Output,
expression: *const Expression,
};
pub const LocalMutation = struct {
local: *const Local,
expression: *const Expression,
};
pub fn inferType(self: Expression) TypeError!*const Type {
return switch (self) {
.constant => |constant| switch (std.mem.indexOfScalar(u8, constant, '.') == null) {
true => .int,
false => .float,
},
.negate_expression, .group_expression => |expression| expression.inferType(),
.abs, .pow, .sin => |generic| generic.inferType(),
.convert => |convert| convert.target_type,
.invoke => |invoke| invoke.inferType(),
.mutate_local => |mutate_local| mutate_local.local.type,
.get_local => |local| local.type,
.get_object => |get_object| get_object.field.type,
.get_parameter => |get_parameter| get_parameter.type,
.sample => |builtin| builtin.inferType(),
.get_uniform => |uniform| uniform.field.type,
.get_input => |input| input.type,
.get_output => |output| output.type,
.mutate_output => |mutate_output| mutate_output.output.type,
.get_texture => |texture| switch (texture.layout) {
.dimensions_2 => .texture2,
},
.add,
.subtract,
.multiply,
.divide,
.equal,
.greater_than,
.greater_equal,
.lesser_than,
.lesser_equal,
=> |binary_op| binary_op.type,
};
}
};
pub const Function = struct {
identifier: [:0]const u8,
signature: [:0]const u8,
has_return_type: ?*const Type = null,
parameters: ?*const Parameter = null,
parameter_count: usize = 0,
block: *const Block,
};
pub const Input = struct {
identifier: [:0]const u8,
type: *const Type,
location: u8,
};
pub const InternError = std.mem.Allocator.Error || error{
TooManyConstants,
};
pub const Output = struct {
identifier: [:0]const u8,
type: *const Type,
location: u8,
};
pub const Parameter = struct {
identifier: [:0]const u8,
type: *const Type,
has_next: ?*const Parameter = null,
};
pub const ParsingError = std.mem.Allocator.Error || tokens.ExpectationError || DefinitionError || TypeError || InternError || error{
ImmutableStorage,
UndefinedIdentifier,
MissingReturn,
};
pub const Texture = struct {
identifier: [:0]const u8,
binding: u8,
layout: Layout,
pub const Layout = enum {
dimensions_2,
};
};
pub const Statement = union(enum) {
declare_local: LocalDeclaration,
mutate_local: LocalMutation,
mutate_output: OutputMutation,
return_expression: *const Expression,
pub const LocalDeclaration = struct {
local: *const Local,
has_next: ?*const Statement = null,
};
pub const LocalMutation = struct {
local: *const Local,
expression: *const Expression,
has_next: ?*const Statement = null,
};
pub const OutputMutation = struct {
output: *const Output,
expression: *const Expression,
has_next: ?*const Statement = null,
};
};
pub const Local = struct {
identifier: [:0]const u8,
expression: *const Expression,
type: *const Type,
is_constant: bool,
};
pub const TypeError = error{
IncompatibleTypes,
IncompatibleArguments,
};
pub const Uniform = struct {
identifier: [:0]const u8,
semantic: [:0]const u8,
binding: u8,
has_field: ?*const Field = null,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,266 +0,0 @@
const coral = @import("../coral.zig");
const spirv = @import("./spirv.zig");
const std = @import("std");
const tokens = @import("./tokens.zig");
scope: *coral.shaders.Scope,
inputs: std.BoundedArray(*const coral.shaders.Input, 15) = .{},
outputs: std.BoundedArray(*const coral.shaders.Output, 15) = .{},
uniforms: std.BoundedArray(*const coral.shaders.Uniform, 15) = .{},
textures: std.BoundedArray(*const coral.shaders.Texture, 15) = .{},
functions: std.BoundedArray(*const coral.shaders.Function, max_functions) = .{},
pub const BuildError = spirv.BuildError || error{
InvalidEntryPoint,
};
pub const ParseResult = union(enum) {
ok,
failure: [:0]const u8,
};
const Self = @This();
pub fn buildSpirvFragment(self: *Self, arena: *std.heap.ArenaAllocator, identifier: []const u8) BuildError!spirv.Module {
var module = spirv.Module{
.capabilities = &.{.shader},
.memory_model = .{ .logical, .glsl450 },
};
_ = try module.shaderEntryPoint(arena, .fragment, self.hasFunction(identifier) orelse {
return error.InvalidEntryPoint;
});
return module;
}
pub fn clear(self: *Self) void {
self.scope.clear();
self.inputs.clear();
self.outputs.clear();
self.uniforms.clear();
self.textures.clear();
self.functions.clear();
}
pub fn defineFunction(self: *Self, arena: *std.heap.ArenaAllocator, function: coral.shaders.Function) coral.shaders.DefinitionError!void {
self.functions.append(try self.scope.define(arena, function)) catch |append_error| {
return switch (append_error) {
error.Overflow => error.TooManySymbols,
};
};
}
pub fn defineInput(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Input) coral.shaders.DefinitionError!void {
self.inputs.append(try self.scope.define(arena, input)) catch |append_error| {
return switch (append_error) {
error.Overflow => error.TooManySymbols,
};
};
}
pub fn defineOutput(self: *Self, arena: *std.heap.ArenaAllocator, output: coral.shaders.Output) coral.shaders.DefinitionError!void {
self.outputs.append(try self.scope.define(arena, output)) catch |append_error| {
return switch (append_error) {
error.Overflow => error.TooManySymbols,
};
};
}
pub fn defineTexture(self: *Self, arena: *std.heap.ArenaAllocator, texture: coral.shaders.Texture) coral.shaders.DefinitionError!void {
self.textures.append(try self.scope.define(arena, texture)) catch |append_error| {
return switch (append_error) {
error.Overflow => error.TooManySymbols,
};
};
}
pub fn defineUniform(self: *Self, arena: *std.heap.ArenaAllocator, uniform: coral.shaders.Uniform) coral.shaders.DefinitionError!void {
self.uniforms.append(try self.scope.define(arena, uniform)) catch |append_error| {
return switch (append_error) {
error.Overflow => error.TooManySymbols,
};
};
}
pub fn hasFunction(self: Self, identifier: []const u8) ?*const coral.shaders.Function {
return self.scope.hasLocal(coral.shaders.Function, identifier);
}
pub fn hasInput(self: Self, identifier: []const u8) ?*const coral.shaders.Input {
return self.scope.hasLocal(coral.shaders.Input, identifier);
}
pub fn hasOutput(self: Self, identifier: []const u8) ?*const coral.shaders.Output {
return self.scope.hasLocal(coral.shaders.Output, identifier);
}
pub fn hasTexture(self: Self, identifier: []const u8) ?*const coral.shaders.Texture {
return self.scope.hasLocal(coral.shaders.Texture, identifier);
}
pub fn hasUniform(self: Self, identifier: []const u8) ?*const coral.shaders.Uniform {
return self.scope.hasLocal(coral.shaders.Uniform, identifier);
}
pub fn init(arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!Self {
const scope = try arena.allocator().create(coral.shaders.Scope);
scope.* = .{};
return .{ .scope = scope };
}
const max_functions = 255;
pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source_text: []const u8) std.mem.Allocator.Error!ParseResult {
errdefer {
self.clear();
}
var source = tokens.Stream.init(source_text);
self.parseDocument(arena, &source) catch |parse_error| {
const arena_allocator = arena.allocator();
return .{
.failure = try switch (parse_error) {
error.OutOfMemory => error.OutOfMemory,
error.TooManyConstants => coral.bytes.allocFormatted(arena_allocator, "{location}: number of literals in the current scope exceeded", .{
.location = source.location,
}),
error.ImmutableStorage => coral.bytes.allocFormatted(arena_allocator, "{location}: attempt to modify an immutable value", .{
.location = source.location,
}),
error.TooManySymbols => coral.bytes.allocFormatted(arena_allocator, "{location}: number of definitions in the current scope exceeded", .{
.location = source.location,
}),
error.IncompatibleArguments => coral.bytes.allocFormatted(arena_allocator, "{location}: incompatible set of arguments", .{
.location = source.location,
}),
error.IncompatibleTypes => coral.bytes.allocFormatted(arena_allocator, "{location}: incompatible types", .{
.location = source.location,
}),
error.DuplicateIdentifier => coral.bytes.allocFormatted(arena_allocator, "{location}: {kind} {token} already defined", .{
.location = source.location,
.token = source.current,
.kind = std.meta.activeTag(source.current),
}),
error.UnexpectedToken => coral.bytes.allocFormatted(arena_allocator, "{location}: unexpected {token}", .{
.location = source.location,
.token = source.current,
}),
error.UndefinedIdentifier => coral.bytes.allocFormatted(arena_allocator, "{location}: undefined identifier {token}", .{
.location = source.location,
.token = source.current,
}),
error.MissingReturn => coral.bytes.allocFormatted(arena_allocator, "{location}: value-returning function does not return at end", .{
.location = source.location,
}),
},
};
};
return .ok;
}
fn parseDocument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void {
while (source.skip(.newline) != .end) {
try switch (try source.current.expectAny(&.{.keyword_function})) {
.keyword_function => self.parseFunction(arena, source),
};
}
}
fn parseFunction(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void {
const identifier = try arena.allocator().dupeZ(u8, try source.skip(.newline).expectIdentifier());
try source.skip(.newline).expect(.symbol_paren_left);
const inner_scope = try self.scope.createScope(arena);
const parameters = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) {
.identifier => try inner_scope.parseParameter(arena, source),
.symbol_paren_right => null,
};
const parameter_count = inner_scope.defined;
try source.skip(.newline).expect(.symbol_arrow);
var peeking = source.*;
const has_return_type = if (peeking.skip(.newline) == .symbol_brace_left) null else try self.scope.parseType(source);
const block = try inner_scope.parseBlock(arena, source);
if (has_return_type) |return_type| {
const last_statement = block.hasLastStatement() orelse {
return error.MissingReturn;
};
if (last_statement.* != .return_expression) {
return error.MissingReturn;
}
if (try last_statement.return_expression.inferType() != return_type) {
return error.IncompatibleTypes;
}
}
try self.defineFunction(arena, .{
.has_return_type = has_return_type,
.parameters = parameters,
.parameter_count = parameter_count,
.identifier = identifier,
.block = block,
.signature = try self.scope.intern(arena, try signature(arena, parameters, has_return_type)),
});
}
fn signature(arena: *std.heap.ArenaAllocator, parameters: ?*const coral.shaders.Parameter, has_return_type: ?*const coral.shaders.Type) std.mem.Allocator.Error![:0]const u8 {
var buffer_size = 2 + (if (has_return_type) |return_type| return_type.identifier.len else 0);
if (parameters) |first_parameter| {
buffer_size += first_parameter.type.identifier.len;
var has_parameter = first_parameter.has_next;
while (has_parameter) |parameter| : (has_parameter = parameter.has_next) {
buffer_size += 1 + parameter.type.identifier.len;
}
}
var buffer = coral.bytes.span(try arena.allocator().allocSentinel(u8, buffer_size, 0));
std.debug.assert(buffer.put('('));
if (parameters) |first_parameter| {
std.debug.assert(buffer.write(first_parameter.type.identifier) == first_parameter.type.identifier.len);
var has_parameter = first_parameter.has_next;
while (has_parameter) |parameter| : (has_parameter = parameter.has_next) {
std.debug.assert(buffer.put(','));
std.debug.assert(buffer.write(parameter.type.identifier) == parameter.type.identifier.len);
}
}
std.debug.assert(buffer.put(')'));
if (has_return_type) |return_type| {
std.debug.assert(buffer.write(return_type.identifier) == return_type.identifier.len);
}
return @ptrCast(buffer.bytes);
}

View File

@ -1,747 +0,0 @@
const coral = @import("../coral.zig");
const std = @import("std");
const tokens = @import("./tokens.zig");
interned: coral.tree.Binary([:0]const u8, void, coral.tree.sliceTraits([:0]const u8)) = .empty,
identifiers: [max_definitions][:0]const u8 = undefined,
symbols: [max_definitions]coral.Box = undefined,
defined: usize = 0,
has_enclosing: ?*Self = null,
const Self = @This();
pub fn clear(self: *Self) void {
self.defined = 0;
if (self.has_enclosing) |enclosing| {
enclosing.clear();
}
}
fn create(arena: *std.heap.ArenaAllocator, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) {
const allocation = try arena.allocator().create(@TypeOf(node));
allocation.* = node;
return allocation;
}
pub fn createScope(self: *Self, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!*Self {
return create(arena, Self{
.has_enclosing = self,
});
}
pub fn define(self: *Self, arena: *std.heap.ArenaAllocator, symbol: anytype) coral.shaders.DefinitionError!*@TypeOf(symbol) {
const Symbol = @TypeOf(symbol);
const identifier_field = "identifier";
const Identifier = [:0]const u8;
const identifier = switch (@hasField(Symbol, identifier_field)) {
true => switch (@TypeOf(symbol.identifier)) {
Identifier => symbol.identifier,
else => @compileError(std.fmt.comptimePrint("field {s}.{s} must be of type `{s}`", .{
@typeName(Symbol),
identifier_field,
@typeName(Identifier),
})),
},
false => @compileError(std.fmt.comptimePrint("Type `{s}` must contain a field identifierd `{s}`", .{
@typeName(Symbol),
identifier_field,
})),
};
if (self.definitionExists(identifier)) {
return error.DuplicateIdentifier;
}
if (self.defined >= max_definitions) {
return error.TooManySymbols;
}
const stored_symbol = &self.symbols[self.defined];
self.identifiers[self.defined] = identifier;
stored_symbol.* = try .initWithAllocator(arena.allocator(), symbol);
self.defined += 1;
return stored_symbol.has(Symbol).?;
}
fn definitionExists(self: Self, identifier: []const u8) bool {
for (self.identifiers[0..self.defined]) |existing_identifier| {
if (std.mem.eql(u8, existing_identifier, identifier)) {
return true;
}
}
if (self.has_enclosing) |enclosing| {
return enclosing.definitionExists(identifier);
}
return false;
}
pub fn hasGlobal(self: *const Self, comptime Symbol: type, identifier: []const u8) ?*const Symbol {
if (self.hasLocal(Symbol, identifier)) |local| {
return local;
}
if (self.has_enclosing) |enclosing| {
return enclosing.hasGlobal(Symbol, identifier);
}
return null;
}
pub fn hasLocal(self: *const Self, comptime Symbol: type, identifier: []const u8) ?*const Symbol {
for (0..self.defined) |i| {
if (std.mem.eql(u8, self.identifiers[i], identifier)) {
if (self.symbols[i].has(Symbol)) |symbol| {
return symbol;
}
}
}
return null;
}
pub fn intern(self: *Self, arena: *std.heap.ArenaAllocator, value: [:0]const u8) std.mem.Allocator.Error![:0]const u8 {
var scope: ?*Self = self;
while (scope) |current| : (scope = current.has_enclosing) {
if (current.interned.getKey(value)) |existing| {
return existing;
}
}
std.debug.assert(try self.interned.insert(arena.allocator(), value, {}) != null);
return value;
}
const max_definitions = 256;
fn parseArgument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Argument {
const expression = try self.parseExpression(arena, source);
return switch (try source.current.expectAny(&.{ .symbol_paren_right, .symbol_comma })) {
.symbol_paren_right => try create(arena, coral.shaders.Argument{
.expression = expression,
}),
.symbol_comma => {
var peeking = source.*;
if (peeking.skip(.newline) == .symbol_paren_right) {
source.* = peeking;
return try create(arena, coral.shaders.Argument{ .expression = expression });
}
return try create(arena, coral.shaders.Argument{
.expression = expression,
.has_next = try self.parseArgument(arena, source),
});
},
};
}
pub fn parseBlock(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Block {
try source.skip(.newline).expect(.symbol_brace_left);
return create(arena, coral.shaders.Block{
.scope = self,
.depth = source.depth,
.statements = switch (source.skip(.newline)) {
.symbol_brace_right => null,
else => try self.parseBlockStatement(arena, source),
},
});
}
fn parseBlockStatement(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Statement {
switch (try source.current.expectAny(&.{ .keyword_let, .keyword_var, .keyword_return, .identifier })) {
.identifier => {
const identifier = try source.current.expectIdentifier();
if (self.hasLocal(coral.shaders.Local, identifier)) |local| {
if (local.is_constant) {
return error.ImmutableStorage;
}
try source.skip(.newline).expect(.symbol_equals);
const expression = try self.parseExpression(arena, source);
if (try expression.inferType() != local.type) {
return error.IncompatibleTypes;
}
return try create(arena, coral.shaders.Statement{
.mutate_local = .{
.local = local,
.expression = expression,
.has_next = switch (source.current) {
.symbol_brace_right => null,
else => try self.parseBlockStatement(arena, source),
},
},
});
}
if (self.hasGlobal(coral.shaders.Output, identifier)) |output| {
try source.skip(.newline).expect(.symbol_equals);
const expression = try self.parseExpression(arena, source);
if (try expression.inferType() != output.type) {
return error.IncompatibleTypes;
}
return try create(arena, coral.shaders.Statement{
.mutate_output = .{
.output = output,
.expression = expression,
.has_next = switch (source.current) {
.symbol_brace_right => null,
else => try self.parseBlockStatement(arena, source),
},
},
});
}
return error.ImmutableStorage;
},
.keyword_return => {
return try create(arena, coral.shaders.Statement{
.return_expression = try self.parseExpression(arena, source),
});
},
.keyword_var, .keyword_let => {
const local = try self.define(arena, coral.shaders.Local{
.is_constant = source.current != .keyword_var,
.identifier = try arena.allocator().dupeZ(u8, try source.next().expectIdentifier()),
.expression = &.{ .constant = try self.intern(arena, "0") },
.type = .int,
});
try source.skip(.newline).expect(.symbol_equals);
local.expression = try self.parseExpression(arena, source);
local.type = try local.expression.inferType();
return try create(arena, coral.shaders.Statement{
.declare_local = .{
.local = local,
.has_next = try switch (source.current) {
.symbol_brace_right => null,
else => self.parseBlockStatement(arena, source),
},
},
});
},
}
}
fn parseExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
const expression = try self.parseAdditiveExpression(arena, source);
if (source.current == .symbol_equals) {
return switch (expression.*) {
.get_local => |local| switch (local.is_constant) {
false => try create(arena, coral.shaders.Expression{
.mutate_local = .{
.local = local,
.expression = try self.parseExpression(arena, source),
},
}),
true => error.ImmutableStorage,
},
else => error.UnexpectedToken,
};
}
return expression;
}
fn parseAdditiveExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
const lhs_expression = try self.parseEqualityExpression(arena, source);
if (source.current == .symbol_plus) {
const rhs_expression = try self.parseEqualityExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.add = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
if (source.current == .symbol_minus) {
const rhs_expression = try self.parseEqualityExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.subtract = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
return lhs_expression;
}
fn parseComparativeExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
const lhs_expression = try self.parseTermExpression(arena, source);
if (source.current == .symbol_greater_than) {
const rhs_expression = try self.parseTermExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.greater_than = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
if (source.current == .symbol_greater_equals) {
const rhs_expression = try self.parseTermExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.greater_equal = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
if (source.current == .symbol_lesser_than) {
const rhs_expression = try self.parseTermExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.divide = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
if (source.current == .symbol_lesser_equals) {
const rhs_expression = try self.parseTermExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.divide = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
return lhs_expression;
}
fn parseEqualityExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
const lhs_expression = try self.parseComparativeExpression(arena, source);
if (source.current == .symbol_double_equals) {
const rhs_expression = try self.parseComparativeExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return create(arena, coral.shaders.Expression{
.equal = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
return lhs_expression;
}
fn parseFactorExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
var expression = try self.parseOperandExpression(arena, source);
while (true) {
switch (source.skip(.newline)) {
.symbol_period => {
const object_type = try expression.inferType();
expression = try create(arena, coral.shaders.Expression{
.get_object = .{
.object_expression = expression,
.field = object_type.hasField(try source.skip(.newline).expectIdentifier()) orelse {
return error.UndefinedIdentifier;
},
},
});
},
else => {
return expression;
},
}
}
}
fn parseOperandExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
switch (source.skip(.newline)) {
.symbol_minus => {
return try create(arena, coral.shaders.Expression{
.negate_expression = try self.parseExpression(arena, source),
});
},
.symbol_paren_left => {
return try create(arena, coral.shaders.Expression{
.group_expression = try self.parseExpression(arena, source),
});
},
.scalar => {
return try create(arena, coral.shaders.Expression{
.constant = try arena.allocator().dupeZ(u8, try source.current.expectScalar()),
});
},
.identifier => |identifier| {
if (self.hasGlobal(coral.shaders.Function, identifier)) |function| {
try source.skip(.newline).expect(.symbol_paren_left);
var peeking = source.*;
switch (peeking.skip(.newline)) {
.symbol_paren_left => {
source.* = peeking;
return try create(arena, coral.shaders.Expression{
.invoke = .{
.function = function,
},
});
},
else => {
const arguments = try self.parseArgument(arena, source);
var argument_count: usize = 1;
{
var subsequent_arguments = arguments.has_next;
while (subsequent_arguments) |argument| : (subsequent_arguments = argument.has_next) {
argument_count += 1;
}
}
return try create(arena, coral.shaders.Expression{
.invoke = .{
.function = function,
.argument_count = argument_count,
.arguments = arguments,
},
});
},
}
}
if (self.hasGlobal(coral.shaders.Uniform, identifier)) |uniform| {
try source.skip(.newline).expect(.symbol_period);
const field_name = try source.skip(.newline).expectIdentifier();
const first_field = uniform.has_field orelse {
return error.UndefinedIdentifier;
};
return try create(arena, coral.shaders.Expression{
.get_uniform = .{
.uniform = uniform,
.field = first_field.has(field_name) orelse {
return error.UndefinedIdentifier;
},
},
});
}
if (self.hasGlobal(coral.shaders.Texture, identifier)) |texture| {
return try create(arena, coral.shaders.Expression{ .get_texture = texture });
}
if (self.hasGlobal(coral.shaders.Output, identifier)) |output| {
return try create(arena, coral.shaders.Expression{ .get_output = output });
}
if (self.hasGlobal(coral.shaders.Input, identifier)) |input| {
return try create(arena, coral.shaders.Expression{ .get_input = input });
}
if (self.hasLocal(coral.shaders.Local, identifier)) |local| {
return try create(arena, coral.shaders.Expression{ .get_local = local });
}
if (self.hasLocal(coral.shaders.Parameter, identifier)) |parameter| {
return try create(arena, coral.shaders.Expression{ .get_parameter = parameter });
}
return error.UndefinedIdentifier;
},
.keyword_pow => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.pow = .{
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
.expected_parameter_count = 2,
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_abs => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.abs = .{
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
.expected_parameter_count = 1,
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_sin => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.sin = .{
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
.expected_parameter_count = 1,
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_int => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .int,
.parameter_types = &.{.float},
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_float => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .float,
.parameter_types = &.{.int},
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_float2 => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .float2,
.parameter_types = &.{ .float, .float },
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_float3 => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .float3,
.parameter_types = &.{ .float, .float, .float },
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_float4 => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .float4,
.parameter_types = &.{ .float, .float, .float, .float },
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_float4x4 => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.convert = .{
.target_type = .float4x4,
.parameter_types = &.{ .float4, .float4, .float4, .float4 },
.first_argument = try self.parseArgument(arena, source),
},
});
},
.keyword_sample => {
try source.skip(.newline).expect(.symbol_paren_left);
return try create(arena, coral.shaders.Expression{
.sample = .{
.signatures = &.{
.{
.parameter_types = &.{ .texture2, .float2 },
.return_type = .float4,
},
},
.has_argument = try self.parseArgument(arena, source),
},
});
},
else => {
return error.UnexpectedToken;
},
}
}
fn parseTermExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
const lhs_expression = try self.parseFactorExpression(arena, source);
if (source.current == .symbol_asterisk) {
const rhs_expression = try self.parseFactorExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return try create(arena, coral.shaders.Expression{
.multiply = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
if (source.current == .symbol_forward_slash) {
const rhs_expression = try self.parseFactorExpression(arena, source);
const lhs_type = try lhs_expression.inferType();
if (lhs_type != try rhs_expression.inferType()) {
return error.IncompatibleTypes;
}
return try create(arena, coral.shaders.Expression{
.divide = .{
.rhs_expression = rhs_expression,
.lhs_expression = lhs_expression,
.type = lhs_type,
},
});
}
return lhs_expression;
}
pub fn parseParameter(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!?*const coral.shaders.Parameter {
const parameter = try self.define(arena, coral.shaders.Parameter{
.identifier = try arena.allocator().dupeZ(u8, try source.current.expectIdentifier()),
.type = .int,
});
parameter.type = try self.parseType(source);
if (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .symbol_comma }) == .symbol_comma) {
parameter.has_next = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) {
.symbol_paren_right => null,
.identifier => try self.parseParameter(arena, source),
};
}
return parameter;
}
pub fn parseType(self: *const Self, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Type {
return switch (source.skip(.newline)) {
.keyword_float => .float,
.keyword_float4x4 => .float4x4,
.keyword_float2 => .float2,
.keyword_float3 => .float3,
.keyword_float4 => .float4,
.keyword_int => .int,
.identifier => |identifier| self.hasGlobal(coral.shaders.Type, identifier) orelse error.UndefinedIdentifier,
else => error.UnexpectedToken,
};
}

View File

@ -1,984 +0,0 @@
const coral = @import("../coral.zig");
const std = @import("std");
identifier: [:0]const u8,
layout: Layout,
pub const Layout = union(enum) {
float: Scalar,
signed: Scalar,
unsigned: Scalar,
vector: Vector,
matrix: Matrix,
texture: Texture,
record: Record,
};
const Self = @This();
pub const Dimensions = enum(u2) {
@"2",
@"3",
@"4",
pub fn count(self: Dimensions) std.math.IntFittingRange(2, 4) {
return switch (self) {
.@"2" => 2,
.@"3" => 3,
.@"4" => 4,
};
}
};
pub const Matrix = struct {
element: *const Self,
dimensions: Dimensions,
};
pub const Record = struct {
field_count: usize = 0,
fields: ?*const coral.shaders.Field = null,
};
pub const Scalar = struct {
bits: u8,
};
pub const Texture = struct {
dimensions: Dimensions,
is_depth: bool,
is_arary: bool,
is_multi_sampled: bool,
};
pub const Vector = struct {
element: *const Self,
dimensions: Dimensions,
};
pub const float = &Self{
.identifier = "float",
.layout = .{ .float = .{ .bits = 32 } },
};
pub const float2 = &Self{
.identifier = "float2",
.layout = .{
.vector = .{
.element = .float,
.dimensions = .@"2",
},
},
};
pub const float3 = &Self{
.identifier = "float3",
.layout = .{
.vector = .{
.element = .float,
.dimensions = .@"3",
},
},
};
pub const float4 = &Self{
.identifier = "float4",
.layout = .{
.vector = .{
.element = .float,
.dimensions = .@"4",
},
},
};
pub const float4x4 = &Self{
.identifier = "float4x4",
.layout = .{
.matrix = .{
.element = .float,
.dimensions = .@"2",
},
},
};
pub fn hasField(self: Self, field_identifier: []const u8) ?*const coral.shaders.Field {
return switch (self.layout) {
.float, .signed, .unsigned, .matrix, .texture => null,
.record => |record| if (record.fields) |field| field.has(field_identifier) else null,
.vector => |vector| switch (vector.dimensions) {
.@"2" => switch (field_identifier.len) {
1 => switch (field_identifier[0]) {
'x', 'r' => .vector_x,
'y', 'g' => .vector_y,
else => null,
},
2 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => .vector_xx,
'y', 'g' => .vector_xy,
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => .vector_yx,
'y', 'g' => .vector_yy,
else => null,
},
else => null,
},
else => null,
},
.@"3" => switch (field_identifier.len) {
1 => switch (field_identifier[0]) {
'x', 'r' => .vector_x,
'y', 'g' => .vector_y,
'z', 'b' => .vector_z,
else => null,
},
2 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => .vector_xx,
'y', 'g' => .vector_xy,
'z', 'b' => .vector_xz,
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => .vector_yx,
'y', 'g' => .vector_yy,
'z', 'b' => .vector_yz,
else => null,
},
else => null,
},
3 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_xxx,
'y', 'g' => .vector_xxy,
'z', 'b' => .vector_xxz,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_xyx,
'y', 'g' => .vector_xyy,
'z', 'b' => .vector_xyz,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_xzx,
'y', 'g' => .vector_xzy,
'z', 'b' => .vector_xzz,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_yxx,
'y', 'g' => .vector_yxy,
'z', 'b' => .vector_yxz,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_yyx,
'y', 'g' => .vector_yyy,
'z', 'b' => .vector_yyz,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_yzx,
'y', 'g' => .vector_yzy,
'z', 'b' => .vector_yzz,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_zxx,
'y', 'g' => .vector_zxy,
'z', 'b' => .vector_zxz,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_zyx,
'y', 'g' => .vector_zyy,
'z', 'b' => .vector_zyz,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_zzx,
'y', 'g' => .vector_zzy,
'z', 'b' => .vector_zzz,
else => null,
},
else => null,
},
else => null,
},
else => null,
},
.@"4" => switch (field_identifier.len) {
1 => switch (field_identifier[0]) {
'x', 'r' => .vector_x,
'y', 'g' => .vector_y,
'z', 'b' => .vector_z,
'w', 'a' => .vector_w,
else => null,
},
2 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => .vector_xx,
'y', 'g' => .vector_xy,
'z', 'b' => .vector_xz,
'w', 'a' => .vector_xw,
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => .vector_yx,
'y', 'g' => .vector_yy,
'z', 'b' => .vector_yz,
'w', 'a' => .vector_yz,
else => null,
},
else => null,
},
3 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_xxx,
'y', 'g' => .vector_xxy,
'z', 'b' => .vector_xxz,
'w', 'a' => .vector_xxw,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_xyx,
'y', 'g' => .vector_xyy,
'z', 'b' => .vector_xyz,
'w', 'a' => .vector_xyw,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_xzx,
'y', 'g' => .vector_xzy,
'z', 'b' => .vector_xzz,
'w', 'a' => .vector_xzw,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_yxx,
'y', 'g' => .vector_yxy,
'z', 'b' => .vector_yxz,
'w', 'a' => .vector_yxw,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_yyx,
'y', 'g' => .vector_yyy,
'z', 'b' => .vector_yyz,
'w', 'a' => .vector_yyw,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_yzx,
'y', 'g' => .vector_yzy,
'z', 'b' => .vector_yzz,
'w', 'a' => .vector_yzw,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => .vector_zxx,
'y', 'g' => .vector_zxy,
'z', 'b' => .vector_zxz,
'w', 'a' => .vector_zxw,
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => .vector_zyx,
'y', 'g' => .vector_zyy,
'z', 'b' => .vector_zyz,
'w', 'a' => .vector_zyw,
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => .vector_zzx,
'y', 'g' => .vector_zzy,
'z', 'b' => .vector_zzz,
'w', 'a' => .vector_zzw,
else => null,
},
else => null,
},
else => null,
},
4 => switch (field_identifier[0]) {
'x', 'r' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_xxxx,
'y', 'g' => .vector_xxxy,
'z', 'b' => .vector_xxxz,
'w', 'a' => .vector_xxxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_xxyx,
'y', 'g' => .vector_xxyy,
'z', 'b' => .vector_xxyz,
'w', 'a' => .vector_xxyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_xxzx,
'y', 'g' => .vector_xxzy,
'z', 'b' => .vector_xxzz,
'w', 'a' => .vector_xxzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_xxwx,
'y', 'g' => .vector_xxwy,
'z', 'b' => .vector_xxwz,
'w', 'a' => .vector_xxww,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_xyxx,
'y', 'g' => .vector_xyxy,
'z', 'b' => .vector_xyxz,
'w', 'a' => .vector_xyxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_xyyx,
'y', 'g' => .vector_xyyy,
'z', 'b' => .vector_xyyz,
'w', 'a' => .vector_xyyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_xyzx,
'y', 'g' => .vector_xyzy,
'z', 'b' => .vector_xyzz,
'w', 'a' => .vector_xyzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_xywx,
'y', 'g' => .vector_xywy,
'z', 'b' => .vector_xywz,
'w', 'a' => .vector_xyww,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_xzxx,
'y', 'g' => .vector_xzxy,
'z', 'b' => .vector_xzxz,
'w', 'a' => .vector_xzxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_xzyx,
'y', 'g' => .vector_xzyy,
'z', 'b' => .vector_xzyz,
'w', 'a' => .vector_xzyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_xzzx,
'y', 'g' => .vector_xzzy,
'z', 'b' => .vector_xzzz,
'w', 'a' => .vector_xzzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_xzwx,
'y', 'g' => .vector_xzwy,
'z', 'b' => .vector_xzwz,
'w', 'a' => .vector_xzww,
else => null,
},
else => null,
},
'w', 'a' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_xwxx,
'y', 'g' => .vector_xwxy,
'z', 'b' => .vector_xwxz,
'w', 'a' => .vector_xwxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_xwyx,
'y', 'g' => .vector_xwyy,
'z', 'b' => .vector_xwyz,
'w', 'a' => .vector_xwyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_xwzx,
'y', 'g' => .vector_xwzy,
'z', 'b' => .vector_xwzz,
'w', 'a' => .vector_xwzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_xwwx,
'y', 'g' => .vector_xwwy,
'z', 'b' => .vector_xwwz,
'w', 'a' => .vector_xwww,
else => null,
},
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_yxxx,
'y', 'g' => .vector_yxxy,
'z', 'b' => .vector_yxxz,
'w', 'a' => .vector_yxxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_yxyx,
'y', 'g' => .vector_yxyy,
'z', 'b' => .vector_yxyz,
'w', 'a' => .vector_yxyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_yxzx,
'y', 'g' => .vector_yxzy,
'z', 'b' => .vector_yxzz,
'w', 'a' => .vector_yxzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_yxwx,
'y', 'g' => .vector_yxwy,
'z', 'b' => .vector_yxwz,
'w', 'a' => .vector_yxww,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_yyxx,
'y', 'g' => .vector_yyxy,
'z', 'b' => .vector_yyxz,
'w', 'a' => .vector_yyxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_yyyx,
'y', 'g' => .vector_yyyy,
'z', 'b' => .vector_yyyz,
'w', 'a' => .vector_yyyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_yyzx,
'y', 'g' => .vector_yyzy,
'z', 'b' => .vector_yyzz,
'w', 'a' => .vector_yyzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_yywx,
'y', 'g' => .vector_yywy,
'z', 'b' => .vector_yywz,
'w', 'a' => .vector_yyww,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_yzxx,
'y', 'g' => .vector_yzxy,
'z', 'b' => .vector_yzxz,
'w', 'a' => .vector_yzxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_yzyx,
'y', 'g' => .vector_yzyy,
'z', 'b' => .vector_yzyz,
'w', 'a' => .vector_yzyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_yzzx,
'y', 'g' => .vector_yzzy,
'z', 'b' => .vector_yzzz,
'w', 'a' => .vector_yzzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_yzwx,
'y', 'g' => .vector_yzwy,
'z', 'b' => .vector_yzwz,
'w', 'a' => .vector_yzww,
else => null,
},
else => null,
},
'w', 'a' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_ywxx,
'y', 'g' => .vector_ywxy,
'z', 'b' => .vector_ywxz,
'w', 'a' => .vector_ywxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_ywyx,
'y', 'g' => .vector_ywyy,
'z', 'b' => .vector_ywyz,
'w', 'a' => .vector_ywyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_ywzx,
'y', 'g' => .vector_ywzy,
'z', 'b' => .vector_ywzz,
'w', 'a' => .vector_ywzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_ywwx,
'y', 'g' => .vector_ywwy,
'z', 'b' => .vector_ywwz,
'w', 'a' => .vector_ywww,
else => null,
},
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_zxxx,
'y', 'g' => .vector_zxxy,
'z', 'b' => .vector_zxxz,
'w', 'a' => .vector_zxxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_zxyx,
'y', 'g' => .vector_zxyy,
'z', 'b' => .vector_zxyz,
'w', 'a' => .vector_zxyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_zxzx,
'y', 'g' => .vector_zxzy,
'z', 'b' => .vector_zxzz,
'w', 'a' => .vector_zxzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_zxwx,
'y', 'g' => .vector_zxwy,
'z', 'b' => .vector_zxwz,
'w', 'a' => .vector_zxww,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_zyxx,
'y', 'g' => .vector_zyxy,
'z', 'b' => .vector_zyxz,
'w', 'a' => .vector_zyxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_zyyx,
'y', 'g' => .vector_zyyy,
'z', 'b' => .vector_zyyz,
'w', 'a' => .vector_zyyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_zyzx,
'y', 'g' => .vector_zyzy,
'z', 'b' => .vector_zyzz,
'w', 'a' => .vector_zyzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_zywx,
'y', 'g' => .vector_zywy,
'z', 'b' => .vector_zywz,
'w', 'a' => .vector_zyww,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_zzxx,
'y', 'g' => .vector_zzxy,
'z', 'b' => .vector_zzxz,
'w', 'a' => .vector_zzxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_zzyx,
'y', 'g' => .vector_zzyy,
'z', 'b' => .vector_zzyz,
'w', 'a' => .vector_zzyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_zzzx,
'y', 'g' => .vector_zzzy,
'z', 'b' => .vector_zzzz,
'w', 'a' => .vector_zzzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_zzwx,
'y', 'g' => .vector_zzwy,
'z', 'b' => .vector_zzwz,
'w', 'a' => .vector_zzww,
else => null,
},
else => null,
},
'w', 'a' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_zwxx,
'y', 'g' => .vector_zwxy,
'z', 'b' => .vector_zwxz,
'w', 'a' => .vector_zwxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_zwyx,
'y', 'g' => .vector_zwyy,
'z', 'b' => .vector_zwyz,
'w', 'a' => .vector_zwyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_zwzx,
'y', 'g' => .vector_zwzy,
'z', 'b' => .vector_zwzz,
'w', 'a' => .vector_zwzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_zwwx,
'y', 'g' => .vector_zwwy,
'z', 'b' => .vector_zwwz,
'w', 'a' => .vector_zwww,
else => null,
},
else => null,
},
else => null,
},
'w', 'a' => switch (field_identifier[1]) {
'x', 'r' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_wxxx,
'y', 'g' => .vector_wxxy,
'z', 'b' => .vector_wxxz,
'w', 'a' => .vector_wxxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_wxyx,
'y', 'g' => .vector_wxyy,
'z', 'b' => .vector_wxyz,
'w', 'a' => .vector_wxyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_wxzx,
'y', 'g' => .vector_wxzy,
'z', 'b' => .vector_wxzz,
'w', 'a' => .vector_wxzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_wxwx,
'y', 'g' => .vector_wxwy,
'z', 'b' => .vector_wxwz,
'w', 'a' => .vector_wxww,
else => null,
},
else => null,
},
'y', 'g' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_wyxx,
'y', 'g' => .vector_wyxy,
'z', 'b' => .vector_wyxz,
'w', 'a' => .vector_wyxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_wyyx,
'y', 'g' => .vector_wyyy,
'z', 'b' => .vector_wyyz,
'w', 'a' => .vector_wyyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_wyzx,
'y', 'g' => .vector_wyzy,
'z', 'b' => .vector_wyzz,
'w', 'a' => .vector_wyzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_wywx,
'y', 'g' => .vector_wywy,
'z', 'b' => .vector_wywz,
'w', 'a' => .vector_wyww,
else => null,
},
else => null,
},
'z', 'b' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_wzxx,
'y', 'g' => .vector_wzxy,
'z', 'b' => .vector_wzxz,
'w', 'a' => .vector_wzxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_wzyx,
'y', 'g' => .vector_wzyy,
'z', 'b' => .vector_wzyz,
'w', 'a' => .vector_wzyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_wzzx,
'y', 'g' => .vector_wzzy,
'z', 'b' => .vector_wzzz,
'w', 'a' => .vector_wzzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_wzwx,
'y', 'g' => .vector_wzwy,
'z', 'b' => .vector_wzwz,
'w', 'a' => .vector_wzww,
else => null,
},
else => null,
},
'w', 'a' => switch (field_identifier[2]) {
'x', 'r' => switch (field_identifier[3]) {
'x', 'r' => .vector_wwxx,
'y', 'g' => .vector_wwxy,
'z', 'b' => .vector_wwxz,
'w', 'a' => .vector_wwxw,
else => null,
},
'y', 'g' => switch (field_identifier[3]) {
'x', 'r' => .vector_wwyx,
'y', 'g' => .vector_wwyy,
'z', 'b' => .vector_wwyz,
'w', 'a' => .vector_wwyw,
else => null,
},
'z', 'b' => switch (field_identifier[3]) {
'x', 'r' => .vector_wwzx,
'y', 'g' => .vector_wwzy,
'z', 'b' => .vector_wwzz,
'w', 'a' => .vector_wwzw,
else => null,
},
'w', 'a' => switch (field_identifier[3]) {
'x', 'r' => .vector_wwwx,
'y', 'g' => .vector_wwwy,
'z', 'b' => .vector_wwwz,
'w', 'a' => .vector_wwww,
else => null,
},
else => null,
},
else => null,
},
else => null,
},
else => null,
},
},
};
}
pub const int = &Self{
.identifier = "int",
.layout = .{ .signed = .{ .bits = 32 } },
};
pub const texture2 = &Self{
.identifier = "texture2",
.layout = .{
.texture = .{
.dimensions = .@"2",
.is_arary = false,
.is_depth = false,
.is_multi_sampled = false,
},
},
};

View File

@ -1,339 +0,0 @@
const coral = @import("../coral.zig");
const std = @import("std");
pub const Version = enum {
core_430,
};
pub fn generate(source: coral.bytes.Writable, root: coral.shaders.Root, version: Version) coral.shaders.GenerationError!void {
try coral.bytes.writeFormatted(source, "#version {version_name}\n", .{
.version_name = switch (version) {
.core_430 => "430 core",
},
});
for (root.uniforms.slice()) |uniform| {
try coral.bytes.writeFormatted(source, "\nlayout (binding = {binding}) uniform {semantic} {{\n", .{
.binding = coral.utf8.cDec(uniform.binding),
.semantic = uniform.semantic,
});
var has_field = uniform.has_field;
while (has_field) |field| : (has_field = field.has_next) {
try coral.bytes.writeAll(source, "\t");
try generateType(source, field.type);
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = field.identifier });
}
try coral.bytes.writeFormatted(source, "}} {identifier};\n", .{
.identifier = uniform.identifier,
});
}
for (root.textures.slice()) |texture| {
try coral.bytes.writeFormatted(source, "\nlayout (binding = {binding}) uniform {layout} {identifier};\n", .{
.binding = coral.utf8.cDec(texture.binding),
.identifier = texture.identifier,
.layout = switch (texture.layout) {
.dimensions_2 => "sampler2D",
},
});
}
for (root.inputs.slice()) |input| {
try coral.bytes.writeFormatted(source, "\nlayout (location = {location}) in ", .{
.location = coral.utf8.cDec(input.location),
});
try generateType(source, input.type);
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = input.identifier });
}
for (root.outputs.slice()) |output| {
try coral.bytes.writeFormatted(source, "\nlayout (location = {location}) out ", .{
.location = coral.utf8.cDec(output.location),
});
try generateType(source, output.type);
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = output.identifier });
}
for (root.functions.slice()) |function| {
if (function.has_return_type) |return_type| {
try coral.bytes.writeAll(source, "\n");
try generateType(source, return_type);
} else {
try coral.bytes.writeAll(source, "\nvoid");
}
try coral.bytes.writeFormatted(source, " {identifier}(", .{ .identifier = function.identifier });
var has_parameter = function.has_parameter;
if (has_parameter) |first_parameter| {
try generateType(source, first_parameter.type);
try coral.bytes.writeFormatted(source, " {identifier}", .{ .identifier = first_parameter.identifier });
has_parameter = first_parameter.has_next;
while (has_parameter) |parameter| : (has_parameter = parameter.has_next) {
try coral.bytes.writeAll(source, ", ");
try generateType(source, parameter.type);
try coral.bytes.writeFormatted(source, " {identifier}", .{ .identifier = parameter.identifier });
}
}
try coral.bytes.writeAll(source, ") ");
try generateBlock(source, function.block);
}
}
fn generateArguments(source: coral.bytes.Writable, first_argument: *const coral.shaders.Argument) coral.bytes.ReadWriteError!void {
try generateExpression(source, first_argument.expression);
var has_next_argument = first_argument.has_next;
while (has_next_argument) |next_argument| : (has_next_argument = next_argument.has_next) {
try coral.bytes.writeAll(source, ", ");
try generateExpression(source, next_argument.expression);
}
}
fn generateBlock(source: coral.bytes.Writable, block: *const coral.shaders.Block) coral.bytes.ReadWriteError!void {
try coral.bytes.writeAll(source, "{\n");
var has_next_statement = block.has_statement;
while (has_next_statement) |statement| {
try coral.bytes.writeN(source, "\t", block.depth);
switch (statement.*) {
.return_expression => |return_expression| {
try coral.bytes.writeAll(source, "return ");
if (return_expression) |expression| {
try generateExpression(source, expression);
}
try coral.bytes.writeAll(source, ";\n");
has_next_statement = null;
},
.declare_local => |declare_local| {
try generateType(source, declare_local.local.type);
try coral.bytes.writeFormatted(source, " {identifier} = ", .{ .identifier = declare_local.local.identifier });
try generateExpression(source, declare_local.local.expression);
try coral.bytes.writeAll(source, ";\n");
has_next_statement = declare_local.has_next;
},
.mutate_local => |mutate_local| {
try coral.bytes.writeFormatted(source, "{identifier} = ", .{ .identifier = mutate_local.local.identifier });
try generateExpression(source, mutate_local.expression);
try coral.bytes.writeAll(source, ";\n");
has_next_statement = mutate_local.has_next;
},
.mutate_output => |mutate_output| {
try coral.bytes.writeFormatted(source, "{identifier} = ", .{ .identifier = mutate_output.output.identifier });
try generateExpression(source, mutate_output.expression);
try coral.bytes.writeAll(source, ";\n");
has_next_statement = mutate_output.has_next;
},
}
}
try coral.bytes.writeAll(source, "}\n");
}
fn generateExpression(source: coral.bytes.Writable, expression: *const coral.shaders.Expression) coral.bytes.ReadWriteError!void {
switch (expression.*) {
.float => |float| {
try coral.bytes.writeFormatted(source, "{whole}.{decimal}", float);
},
.int => |int| {
try coral.bytes.writeAll(source, int.literal);
},
.group_expression => |group_expression| {
try coral.bytes.writeAll(source, "(");
try generateExpression(source, group_expression);
try coral.bytes.writeAll(source, ")");
},
.add => |add| {
try generateExpression(source, add.lhs_expression);
try coral.bytes.writeAll(source, " + ");
try generateExpression(source, add.rhs_expression);
},
.subtract => |subtract| {
try generateExpression(source, subtract.lhs_expression);
try coral.bytes.writeAll(source, " - ");
try generateExpression(source, subtract.rhs_expression);
},
.multiply => |multiply| {
try generateExpression(source, multiply.lhs_expression);
try coral.bytes.writeAll(source, " * ");
try generateExpression(source, multiply.rhs_expression);
},
.divide => |divide| {
try generateExpression(source, divide.lhs_expression);
try coral.bytes.writeAll(source, " / ");
try generateExpression(source, divide.rhs_expression);
},
.equal => |equal| {
try generateExpression(source, equal.lhs_expression);
try coral.bytes.writeAll(source, " == ");
try generateExpression(source, equal.rhs_expression);
},
.greater_than => |greater_than| {
try generateExpression(source, greater_than.lhs_expression);
try coral.bytes.writeAll(source, " > ");
try generateExpression(source, greater_than.rhs_expression);
},
.greater_equal => |greater_equal| {
try generateExpression(source, greater_equal.lhs_expression);
try coral.bytes.writeAll(source, " >= ");
try generateExpression(source, greater_equal.rhs_expression);
},
.lesser_than => |lesser_than| {
try generateExpression(source, lesser_than.lhs_expression);
try coral.bytes.writeAll(source, " < ");
try generateExpression(source, lesser_than.rhs_expression);
},
.lesser_equal => |lesser_equal| {
try generateExpression(source, lesser_equal.lhs_expression);
try coral.bytes.writeAll(source, " <= ");
try generateExpression(source, lesser_equal.rhs_expression);
},
.negate_expression => |negate_expression| {
try coral.bytes.writeAll(source, "-");
try generateExpression(source, negate_expression);
},
.get_local => |local| {
try coral.bytes.writeAll(source, local.identifier);
},
.mutate_local => |mutate_local| {
try coral.bytes.writeAll(source, mutate_local.local.identifier);
try coral.bytes.writeAll(source, " = ");
try generateExpression(source, mutate_local.expression);
},
.get_object => |get_object| {
try generateExpression(source, get_object.object_expression);
try coral.bytes.writeAll(source, ".");
try coral.bytes.writeAll(source, get_object.field.identifier);
},
.get_uniform => |get_uniform| {
try coral.bytes.writeFormatted(source, "{identifier}.", .{ .identifier = get_uniform.uniform.identifier });
try coral.bytes.writeAll(source, get_uniform.field.identifier);
},
.get_texture => |texture| {
try coral.bytes.writeAll(source, texture.identifier);
},
.get_parameter => |parameter| {
try coral.bytes.writeAll(source, parameter.identifier);
},
.get_output => |output| {
try coral.bytes.writeAll(source, output.identifier);
},
.mutate_output => |mutate_output| {
try coral.bytes.writeAll(source, mutate_output.output.identifier);
try coral.bytes.writeAll(source, " = ");
try generateExpression(source, mutate_output.expression);
},
.get_input => |input| {
try coral.bytes.writeAll(source, input.identifier);
},
.invoke => |invoke| {
try coral.bytes.writeFormatted(source, "{name}(", .{ .name = invoke.function.identifier });
if (invoke.has_argument) |first_argument| {
try generateArguments(source, first_argument);
}
try coral.bytes.writeAll(source, ")");
},
.convert => |convert| {
try generateType(source, convert.target_type);
try coral.bytes.writeAll(source, "(");
try generateArguments(source, convert.first_argument);
try coral.bytes.writeAll(source, ")");
},
.pow => |pow| {
try coral.bytes.writeAll(source, "pow(");
try generateArguments(source, pow.first_argument);
try coral.bytes.writeAll(source, ")");
},
.abs => |abs| {
try coral.bytes.writeAll(source, "abs(");
try generateArguments(source, abs.first_argument);
try coral.bytes.writeAll(source, ")");
},
.sin => |sin| {
try coral.bytes.writeAll(source, "sin(");
try generateArguments(source, sin.first_argument);
try coral.bytes.writeAll(source, ")");
},
.sample => |sample| {
try coral.bytes.writeAll(source, "texture(");
if (sample.has_argument) |first_argument| {
try generateArguments(source, first_argument);
}
try coral.bytes.writeAll(source, ")");
},
}
}
fn generateType(source: coral.bytes.Writable, @"type": *const coral.shaders.Type) coral.bytes.ReadWriteError!void {
if (@"type" == coral.shaders.Type.float2) {
return try coral.bytes.writeAll(source, "vec2");
}
if (@"type" == coral.shaders.Type.float3) {
return try coral.bytes.writeAll(source, "vec3");
}
if (@"type" == coral.shaders.Type.float4) {
return try coral.bytes.writeAll(source, "vec4");
}
if (@"type" == coral.shaders.Type.float4x4) {
return try coral.bytes.writeAll(source, "mat4");
}
try coral.bytes.writeAll(source, @"type".identifier);
}

View File

@ -1,625 +0,0 @@
const coral = @import("../coral.zig");
const std = @import("std");
pub const AccessQualifier = enum(u32) {
read_only = 0,
write_only = 1,
read_write = 2,
};
pub const AddressingModel = enum(u32) {
logical = 0,
};
pub const BuildError = std.mem.Allocator.Error || error{OutOfIds};
pub const Capability = enum(u32) {
matrix = 0,
shader = 1,
};
pub const Dim = enum(u32) {
@"1d" = 0,
@"2d" = 1,
@"3d" = 2,
cube = 4,
};
pub const ExecutionModel = enum(u32) {
fragment = 4,
};
pub const FloatType = struct {
id: u32,
bits: u32,
};
pub const FunctionControl = packed struct(u32) {
@"inline": bool = false,
dont_inline: bool = false,
pure: bool = false,
@"const": bool = false,
reserved: u28 = 0,
};
pub const FunctionType = struct {
id: u32,
parameter_types: []const *const Type,
return_type: *const Type,
};
pub const ImageFormat = enum(u32) {
unknown = 0,
};
pub const ImageType = struct {
id: u32,
sampled_type: *const Type,
dimensions: Dim,
depth: u32,
arrayed: u32,
multi_sampled: u32,
sampled: u32,
format: ImageFormat,
access: AccessQualifier,
};
pub const IntType = struct {
id: u32,
bits: u32,
is_signed: bool,
};
pub const MemoryModel = enum(u32) {
glsl450 = 1,
};
pub const Module = struct {
ids_assigned: u32 = 0,
capabilities: []const Capability,
memory_model: struct { AddressingModel, MemoryModel },
entry_points: PtrTree(*const coral.shaders.Function, EntryPoint) = .empty,
types: PtrTree(*allowzero const anyopaque, Type) = .empty,
inputs: PtrTree(*const coral.shaders.Input, Value) = .empty,
functions: PtrTree(*const coral.shaders.Function, Function) = .empty,
constants: PtrTree([*:0]const u8, Constant) = .empty,
pub const Block = struct {
id: u32,
variables: PtrTree(*const coral.shaders.Local, Value) = .empty,
instructions: coral.list.Linked(Instruction, 8) = .empty,
pub fn shaderExpression(self: *Block, module: *Module, function: *Function, arena: *std.heap.ArenaAllocator, shader_expression: *const coral.shaders.Expression) BuildError!*const Value {
const arena_allocator = arena.allocator();
switch (shader_expression.*) {
.group_expression => |expression| {
return self.shaderExpression(module, function, arena, expression);
},
.constant => |constant| {
return module.shaderConstant(arena, constant);
},
.get_input => |input| {
return module.shaderInput(arena, input);
},
.get_parameter => |parameter| {
return function.parameters.get(parameter) orelse {
@panic("parameter does not exist in function scope");
};
},
.convert => |_| {
// TODO: Review how type conversions are represented in AST.
unreachable;
},
.invoke => |invoke| {
const arguments = try arena.allocator().alloc(*const Value, invoke.argument_count);
{
var shader_arguments = invoke.arguments;
for (arguments) |*argument| {
argument.* = try self.shaderExpression(module, function, arena, shader_arguments.?.expression);
shader_arguments = shader_arguments.?.has_next;
}
}
const instruction = try self.instructions.append(arena_allocator, .{
.call_function = .{
.arguments = arguments,
.function = try module.shaderFunction(arena, invoke.function),
.result = .{
.type = try module.shaderValueType(arena, invoke.function.has_return_type),
.id = try module.nextId(),
},
},
});
return &instruction.call_function.result;
},
.add => |binary_operation| {
const instruction = try self.instructions.append(arena_allocator, .{
.add = .{
.operands = .{
try self.shaderExpression(module, function, arena, binary_operation.lhs_expression),
try self.shaderExpression(module, function, arena, binary_operation.rhs_expression),
},
.result = .{
.type = try module.shaderValueType(arena, binary_operation.type),
.id = try module.nextId(),
},
},
});
return &instruction.add.result;
},
.subtract => |binary_operation| {
const instruction = try self.instructions.append(arena_allocator, .{
.subtract = .{
.operands = .{
try self.shaderExpression(module, function, arena, binary_operation.lhs_expression),
try self.shaderExpression(module, function, arena, binary_operation.rhs_expression),
},
.result = .{
.type = try module.shaderValueType(arena, binary_operation.type),
.id = try module.nextId(),
},
},
});
return &instruction.subtract.result;
},
.multiply => |binary_operation| {
const instruction = try self.instructions.append(arena_allocator, .{
.add = .{
.operands = .{
try self.shaderExpression(module, function, arena, binary_operation.lhs_expression),
try self.shaderExpression(module, function, arena, binary_operation.rhs_expression),
},
.result = .{
.type = try module.shaderValueType(arena, binary_operation.type),
.id = try module.nextId(),
},
},
});
return &instruction.multiply.result;
},
.divide => |binary_operation| {
const instruction = try self.instructions.append(arena_allocator, .{
.add = .{
.operands = .{
try self.shaderExpression(module, function, arena, binary_operation.lhs_expression),
try self.shaderExpression(module, function, arena, binary_operation.rhs_expression),
},
.result = .{
.type = try module.shaderValueType(arena, binary_operation.type),
.id = try module.nextId(),
},
},
});
return &instruction.divide.result;
},
else => unreachable,
}
}
pub fn shaderVariable(self: *Block, module: *Module, function: *Function, arena: *std.heap.ArenaAllocator, local: *const coral.shaders.Local) BuildError!*const Value {
return self.variables.get(local) orelse {
const arena_allocator = arena.allocator();
const variable = (try self.variables.insert(arena_allocator, local, .{
.type = try module.shaderValueType(arena, local.type),
.id = try module.nextId(),
})).?;
_ = try self.instructions.append(arena_allocator, .{
.store = .{
.target = variable,
.source = try self.shaderExpression(module, function, arena, local.expression),
},
});
return variable;
};
}
};
pub const Constant = struct {
value: Value,
data: []const u32,
};
pub const EntryPoint = struct {
id: u32,
function: *const Function,
execution_model: ExecutionModel,
interfaces: PtrTree(*const Value, void) = .empty,
};
pub const Function = struct {
id: u32,
type: *const Type,
control: FunctionControl,
parameters: PtrTree(*const coral.shaders.Parameter, Value) = .empty,
block: Block,
};
pub const Instruction = union(enum) {
store: struct { target: *const Value, source: *const Value },
call_function: FunctionCall,
add: BinaryOp,
subtract: BinaryOp,
multiply: BinaryOp,
divide: BinaryOp,
pub const BinaryOp = struct {
result: Value,
operands: [2]*const Value,
};
pub const FunctionCall = struct {
result: Value,
function: *const Function,
arguments: []*const Value,
};
};
fn PtrTree(comptime Ptr: type, comptime Node: type) type {
return coral.tree.Binary(Ptr, Node, coral.tree.scalarTraits(Ptr));
}
fn nextId(self: *Module) BuildError!u32 {
self.ids_assigned = coral.scalars.add(self.ids_assigned, 1) orelse {
return error.OutOfIds;
};
return self.ids_assigned;
}
pub fn shaderConstant(self: *Module, arena: *std.heap.ArenaAllocator, shader_constant: [:0]const u8) BuildError!*const Value {
if (self.constants.get(shader_constant)) |constant| {
return &constant.value;
}
const arena_allocator = arena.allocator();
const data = try arena_allocator.alloc(u32, 1);
if (std.mem.indexOfScalar(u8, shader_constant, '.') == null) {
data[0] = std.mem.nativeToLittle(u32, @bitCast(coral.utf8.DecFormat.c.parse(i32, shader_constant) orelse {
@panic("malformed constant");
}));
const constant = (try self.constants.insert(arena_allocator, shader_constant, .{
.data = data,
.value = .{
.type = try self.shaderValueType(arena, .int),
.id = try self.nextId(),
},
})).?;
return &constant.value;
} else {
data[0] = std.mem.nativeToLittle(u32, @bitCast(coral.utf8.DecFormat.c.parse(f32, shader_constant) orelse {
@panic("malformed constant");
}));
const constant = (try self.constants.insert(arena_allocator, shader_constant, .{
.data = data,
.value = .{
.type = try self.shaderValueType(arena, .float),
.id = try self.nextId(),
},
})).?;
return &constant.value;
}
}
pub fn shaderEntryPoint(
self: *Module,
arena: *std.heap.ArenaAllocator,
execution_model: ExecutionModel,
shader_function: *const coral.shaders.Function,
) BuildError!*const EntryPoint {
return self.entry_points.get(shader_function) orelse {
const arena_allocator = arena.allocator();
const entry_point = (try self.entry_points.insert(arena_allocator, shader_function, .{
.execution_model = execution_model,
.function = try self.shaderFunction(arena, shader_function),
.id = try self.nextId(),
})).?;
// try entry_point.registerBlockInterfaces(shader_function.block);
return entry_point;
};
}
pub fn shaderFunction(
self: *Module,
arena: *std.heap.ArenaAllocator,
shader_function: *const coral.shaders.Function,
) BuildError!*const Function {
return self.functions.get(shader_function) orelse {
const arena_allocator = arena.allocator();
const function = (try self.functions.insert(arena_allocator, shader_function, .{
.control = .{},
.type = try self.shaderFunctionType(arena, shader_function),
.id = try self.nextId(),
.block = .{ .id = try self.nextId() },
})).?;
{
var parameters = shader_function.parameters;
while (parameters) |parameter| : (parameters = parameter.has_next) {
_ = try function.parameters.insert(arena_allocator, parameter, .{
.type = try self.shaderValueType(arena, parameter.type),
.id = try self.nextId(),
});
}
}
{
var statements = shader_function.block.statements;
while (statements) |statement| {
switch (statement.*) {
.declare_local => |local_declaration| {
_ = try function.block.shaderVariable(self, function, arena, local_declaration.local);
statements = local_declaration.has_next;
},
.mutate_local => {},
.mutate_output => {},
.return_expression => |_| {},
}
}
}
return function;
};
}
pub fn shaderFunctionType(
self: *Module,
arena: *std.heap.ArenaAllocator,
shader_function: *const coral.shaders.Function,
) BuildError!*const Type {
const function_signature_address: *allowzero const anyopaque = @ptrCast(shader_function.signature);
return self.types.get(function_signature_address) orelse {
const arena_allocator = arena.allocator();
const parameter_types = try arena_allocator.alloc(*const Type, shader_function.parameter_count);
{
var parameters = shader_function.parameters;
for (parameter_types) |*parameter_type| {
parameter_type.* = try self.shaderValueType(arena, parameters.?.type);
parameters = parameters.?.has_next;
}
}
return (try self.types.insert(arena_allocator, function_signature_address, .{
.function = .{
.parameter_types = parameter_types,
.return_type = try self.shaderValueType(arena, shader_function.has_return_type),
.id = try self.nextId(),
},
})).?;
};
}
pub fn shaderInput(
self: *Module,
arena: *std.heap.ArenaAllocator,
shader_input: *const coral.shaders.Input,
) BuildError!*const Value {
return self.inputs.get(shader_input) orelse (try self.inputs.insert(arena.allocator(), shader_input, .{
.type = try self.shaderValueType(arena, shader_input.type),
.id = try self.nextId(),
})).?;
}
pub fn shaderValueType(
self: *Module,
arena: *std.heap.ArenaAllocator,
has_shader_type: ?*const coral.shaders.Type,
) BuildError!*const Type {
const arena_allocator = arena.allocator();
const symbol_address: *allowzero const anyopaque = @ptrCast(has_shader_type);
return self.types.get(symbol_address) orelse {
const id = try self.nextId();
const symbol = has_shader_type orelse {
return (try self.types.insert(arena_allocator, symbol_address, .{
.void = .{
.id = id,
},
})).?;
};
switch (symbol.layout) {
.signed, .unsigned => |scalar| {
return (try self.types.insert(arena_allocator, symbol_address, .{
.int = .{
.bits = scalar.bits,
.is_signed = (symbol.layout == .signed),
.id = id,
},
})).?;
},
.float => |float| {
return (try self.types.insert(arena_allocator, symbol_address, .{
.float = .{
.bits = float.bits,
.id = id,
},
})).?;
},
.vector => |vector| {
return (try self.types.insert(arena_allocator, symbol_address, .{
.vector = .{
.id = id,
.component_type = try self.shaderValueType(arena, vector.element),
.component_len = vector.dimensions.count(),
},
})).?;
},
.matrix, .record => unreachable,
.texture => {
const image_type = try arena_allocator.create(Type);
image_type.* = .{
.image = .{
.depth = 0,
.arrayed = 0,
.multi_sampled = 0,
.sampled = 1,
.dimensions = .@"2d",
.format = .unknown,
.access = .read_only,
.sampled_type = try self.shaderValueType(arena, .float),
.id = try self.nextId(),
},
};
return (try self.types.insert(arena_allocator, symbol_address, .{
.sampled_image = .{
.image_type = image_type,
.id = try self.nextId(),
},
})).?;
},
}
};
}
pub fn writeTo(self: Module, spirv: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
const Header = extern struct {
magic_number: u32 = 0x07230203, // SPIR-V magic number in little-endian
version: u32, // SPIR-V version (e.g., 0x00010000 for 1.0)
generator_magic: u32, // Tool or generator identifier
id_upper_bound: u32, // Upper bound for IDs
reserved: u32 = 0, // Reserved, always 0
};
const OpInstruction = enum(u32) {
memory_model = 14,
entry_point = 15,
execution_mode = 16,
};
try coral.bytes.writeLittle(spirv, Header{
.magic_number = 0x07230203,
.version = 0x00010000,
.generator_magic = 0,
.id_upper_bound = self.ids_assigned,
});
{
const addressing, const memory = self.memory_model;
try coral.bytes.writeLittle(spirv, OpInstruction.memory_model);
try coral.bytes.writeLittle(spirv, addressing);
try coral.bytes.writeLittle(spirv, memory);
}
{
var entry_points = self.entry_points.keyValues();
while (entry_points.next()) |entry_point| {
try coral.bytes.writeLittle(spirv, OpInstruction.entry_point);
try coral.bytes.writeLittle(spirv, entry_point.value.execution_model);
try coral.bytes.writeLittle(spirv, entry_point.value.function.id);
try writeString(spirv, entry_point.key.identifier);
var interfaces = entry_point.value.interfaces.keyValues();
while (interfaces.nextKey()) |interface| {
try coral.bytes.writeLittle(spirv, interface.*.id);
}
try coral.bytes.writeLittle(spirv, OpInstruction.execution_mode);
try coral.bytes.writeLittle(spirv, entry_point.value.function.id);
try coral.bytes.writeLittle(spirv, @as(u32, 7));
}
}
}
};
pub const Value = struct {
id: u32,
type: *const Type,
};
pub const SampledImageType = struct {
id: u32,
image_type: *const Type,
};
pub const StructType = struct {
id: u32,
member_types: []const *const Type,
};
pub const Type = union(enum) {
void: VoidType,
float: FloatType,
vector: VectorType,
image: ImageType,
@"struct": StructType,
function: FunctionType,
int: IntType,
sampled_image: SampledImageType,
};
pub const VectorType = struct {
id: u32,
component_type: *const Type,
component_len: u32,
};
pub const VoidType = struct {
id: u32,
};
fn writeString(spirv: coral.bytes.Writable, data: []const u8) coral.bytes.ReadWriteError!void {
const data_len_with_sentinel = data.len + 1;
const word_size = @sizeOf(u32);
const word_count = (data_len_with_sentinel + word_size) / word_size;
try coral.bytes.writeSentineled(spirv, data, 0);
const padding = (word_count * word_size) - data_len_with_sentinel;
try coral.bytes.writeN(spirv, "\x00", padding);
}

View File

@ -1,494 +0,0 @@
const coral = @import("../coral.zig");
const std = @import("std");
pub const ExpectationError = error{
UnexpectedToken,
};
pub const Kind = enum {
end,
unknown,
newline,
keyword_function,
keyword_return,
keyword_sample,
keyword_float,
keyword_float4x4,
keyword_float2,
keyword_float3,
keyword_float4,
keyword_int,
keyword_let,
keyword_var,
keyword_pow,
keyword_abs,
keyword_sin,
identifier,
scalar,
symbol_plus,
symbol_minus,
symbol_comma,
symbol_arrow,
symbol_equals,
symbol_period,
symbol_asterisk,
symbol_paren_left,
symbol_paren_right,
symbol_brace_left,
symbol_brace_right,
symbol_double_equals,
symbol_lesser_than,
symbol_lesser_equals,
symbol_greater_than,
symbol_greater_equals,
symbol_forward_slash,
pub fn writeFormat(self: Kind, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
try coral.bytes.writeAll(writer, self.name());
}
pub fn name(self: Kind) [:0]const u8 {
return switch (self) {
.end => "end",
.unknown => "unknown",
.newline => "newline",
.keyword_function => "`function` keyword",
.keyword_return => "`return` keyword",
.keyword_sample => "`sample` keyword",
.keyword_float => "`float` keyword",
.keyword_float4x4 => "`float4x4` keyword",
.keyword_float2 => "`float2` keyword",
.keyword_float3 => "`float3` keyword",
.keyword_float4 => "`float4` keyword",
.keyword_int => "`int` keyword",
.keyword_let => "`let` keyword",
.keyword_var => "`var` keyword",
.keyword_pow => "`pow` keyword",
.keyword_abs => "`abs` keyword",
.keyword_sin => "`sin` keyword",
.identifier => "identifier",
.scalar => "scalar literal",
.symbol_plus => "`+`",
.symbol_minus => "`-`",
.symbol_comma => "`,`",
.symbol_arrow => "`->`",
.symbol_equals => "`=`",
.symbol_period => "`.`",
.symbol_asterisk => "`*`",
.symbol_paren_left => "`(`",
.symbol_paren_right => "`)`",
.symbol_brace_left => "`{`",
.symbol_brace_right => "`}`",
.symbol_double_equals => "`==`",
.symbol_lesser_than => "`<`",
.symbol_lesser_equals => "`<=`",
.symbol_greater_than => "`>`",
.symbol_greater_equals => "`>=`",
.symbol_forward_slash => "`/`",
};
}
};
pub const Location = struct {
line_number: u32 = 1,
column_number: u32 = 1,
pub fn writeFormat(self: Location, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
try coral.bytes.writeFormatted(writer, "({line}, {column})", .{
.line = coral.utf8.cDec(self.line_number),
.column = coral.utf8.cDec(self.column_number),
});
}
};
pub const Stream = struct {
source: []const u8,
current: Token,
location: Location,
depth: u32 = 0,
pub fn isFinished(self: Stream) bool {
return self.source.len == 0;
}
pub fn init(source: []const u8) Stream {
return .{
.source = source,
.current = .end,
.location = .{},
};
}
pub fn next(self: *Stream) Token {
const initial_len = self.source.len;
self.current = consume(&self.source);
switch (self.current) {
.end => {
self.depth = 0;
},
.symbol_brace_left => {
self.depth += 1;
},
.symbol_brace_right => {
self.depth -= 1;
},
.newline => {
self.location.line_number += 1;
self.location.column_number = 1;
},
else => {
const consumed_len: u32 = @intCast(initial_len - self.source.len);
self.location.column_number += consumed_len;
},
}
return self.current;
}
pub fn skip(self: *Stream, skip_kind: Kind) Token {
while (true) {
if (std.meta.activeTag(self.next()) != skip_kind) {
return self.current;
}
}
}
};
pub const Token = union(Kind) {
end,
unknown: []const u8,
newline,
keyword_function,
keyword_return,
keyword_sample,
keyword_float,
keyword_float4x4,
keyword_float2,
keyword_float3,
keyword_float4,
keyword_int,
keyword_let,
keyword_var,
keyword_pow,
keyword_abs,
keyword_sin,
identifier: []const u8,
scalar: []const u8,
symbol_plus,
symbol_minus,
symbol_comma,
symbol_arrow,
symbol_equals,
symbol_period,
symbol_asterisk,
symbol_paren_left,
symbol_paren_right,
symbol_brace_left,
symbol_brace_right,
symbol_double_equals,
symbol_lesser_than,
symbol_lesser_equals,
symbol_greater_than,
symbol_greater_equals,
symbol_forward_slash,
pub fn ExpectedAnyEnum(comptime expecteds: []const Kind) type {
comptime var fields: [expecteds.len]std.builtin.Type.EnumField = undefined;
inline for (0..expecteds.len) |i| {
fields[i] = .{
.name = @tagName(expecteds[i]),
.value = i,
};
}
return @Type(.{
.@"enum" = .{
.tag_type = @typeInfo(Kind).@"enum".tag_type,
.fields = &fields,
.decls = &.{},
.is_exhaustive = true,
},
});
}
pub fn expect(self: Token, expected: Kind) ExpectationError!void {
if (std.meta.activeTag(self) != expected) {
return error.UnexpectedToken;
}
}
pub fn expectAny(self: Token, comptime expecteds: []const Kind) ExpectationError!ExpectedAnyEnum(expecteds) {
if (std.mem.indexOfScalar(Kind, expecteds, std.meta.activeTag(self))) |index| {
return @enumFromInt(index);
}
return error.UnexpectedToken;
}
pub fn expectIdentifier(self: Token) ExpectationError![]const u8 {
try self.expect(.identifier);
return self.identifier;
}
pub fn expectScalar(self: Token) ExpectationError![]const u8 {
try self.expect(.scalar);
return self.scalar;
}
pub fn writeFormat(self: Token, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
try switch (self) {
.unknown => |unknown| coral.bytes.writeFormatted(writer, "`{name}`", .{
.name = unknown,
}),
.identifier => |identifier| coral.bytes.writeFormatted(writer, "`{name}`", .{
.name = identifier,
}),
.scalar => |literal| coral.bytes.writeFormatted(writer, "`{name}`", .{
.name = literal,
}),
else => std.meta.activeTag(self).writeFormat(writer),
};
}
};
pub fn consume(source: *[]const u8) Token {
var cursor = @as(usize, 0);
defer {
source.* = source.*[cursor..];
}
while (cursor < source.len) {
switch (source.*[cursor]) {
'#' => {
cursor += 1;
while (cursor < source.len and source.*[cursor] != '\n') {
cursor += 1;
}
},
' ', '\t' => {
cursor += 1;
},
'\n' => {
cursor += 1;
return .newline;
},
'0'...'9' => {
const begin = cursor;
cursor += 1;
while (cursor < source.len) {
switch (source.*[cursor]) {
'0'...'9' => {
cursor += 1;
},
'.' => {
cursor += 1;
while (cursor < source.len) {
switch (source.*[cursor]) {
'0'...'9' => {
cursor += 1;
},
else => {
break;
},
}
}
return .{ .scalar = source.*[begin..cursor] };
},
else => {
break;
},
}
}
return .{ .scalar = source.*[begin..cursor] };
},
'A'...'Z', 'a'...'z', '_' => {
const begin = cursor;
cursor += 1;
while (cursor < source.len) {
switch (source.*[cursor]) {
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
else => break,
}
}
const identifier = source.*[begin..cursor];
std.debug.assert(identifier.len != 0);
const identifier_keyword_pairs = [_]struct { [:0]const u8, Kind }{
.{ "float", .keyword_float },
.{ "float2", .keyword_float2 },
.{ "float3", .keyword_float3 },
.{ "float4", .keyword_float4 },
.{ "float4x4", .keyword_float4x4 },
.{ "function", .keyword_function },
.{ "let", .keyword_let },
.{ "var", .keyword_var },
.{ "return", .keyword_return },
.{ "var", .keyword_var },
.{ "pow", .keyword_pow },
.{ "abs", .keyword_abs },
.{ "sin", .keyword_sin },
.{ "sample", .keyword_sample },
};
inline for (identifier_keyword_pairs) |pair| {
const keyword_identifier, const keyword = pair;
if (std.mem.eql(u8, identifier, keyword_identifier)) {
return keyword;
}
}
return .{ .identifier = identifier };
},
'{' => {
cursor += 1;
return .symbol_brace_left;
},
'}' => {
cursor += 1;
return .symbol_brace_right;
},
',' => {
cursor += 1;
return .symbol_comma;
},
')' => {
cursor += 1;
return .symbol_paren_right;
},
'(' => {
cursor += 1;
return .symbol_paren_left;
},
'/' => {
cursor += 1;
return .symbol_forward_slash;
},
'*' => {
cursor += 1;
return .symbol_asterisk;
},
'-' => {
cursor += 1;
if (cursor < source.len and source.*[cursor] == '>') {
cursor += 1;
return .symbol_arrow;
}
return .symbol_minus;
},
'+' => {
cursor += 1;
return .symbol_plus;
},
'=' => {
cursor += 1;
if (cursor < source.len and source.*[cursor] == '=') {
cursor += 1;
return .symbol_double_equals;
}
return .symbol_equals;
},
'<' => {
cursor += 1;
if (cursor < source.len and (source.*[cursor] == '=')) {
cursor += 1;
return .symbol_lesser_equals;
}
return .symbol_lesser_than;
},
'>' => {
cursor += 1;
if (cursor < source.len and (source.*[cursor] == '=')) {
cursor += 1;
return .symbol_greater_equals;
}
return .symbol_greater_than;
},
'.' => {
cursor += 1;
return .symbol_period;
},
else => {
const begin = cursor;
cursor += 1;
while (cursor < source.len and !std.ascii.isWhitespace(source.*[cursor])) {
cursor += 1;
}
return .{ .unknown = source.*[begin..cursor] };
},
}
}
return .end;
}

View File

@ -1,3 +1,4 @@
pub usingnamespace @cImport({
@cInclude("SDL3/SDL.h");
@cInclude("shaderc/shaderc.h");
});

View File

@ -12,7 +12,11 @@ const std = @import("std");
initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.scalarTraits(*const coral.TypeId)),
named_systems: coral.tree.Binary([]const u8, SystemGraph, coral.tree.sliceTraits([]const u8)),
is_running: bool,
pub const Exit = union(enum) {
success,
failure: anyerror,
};
pub const RunError = std.mem.Allocator.Error || error{
MissingDependency,
@ -65,7 +69,6 @@ pub fn init() std.mem.Allocator.Error!Self {
var self = Self{
.initialized_states = .empty,
.named_systems = .empty,
.is_running = true,
};
try self.setState(Time{

View File

@ -4,6 +4,8 @@ const coral = @import("coral");
const ext = @import("ext");
const glsl = @import("./gfx/glsl.zig");
const ona = @import("./ona.zig");
const std = @import("std");
@ -17,11 +19,55 @@ pub const Display = struct {
const Context = struct {
window: *ext.SDL_Window,
gpu_device: *ext.SDL_GPUDevice,
basic_shader: *ext.SDL_GPUShader = undefined,
shader_compiler: ext.shaderc_compiler_t,
spirv_options: ext.shaderc_compile_options_t,
pub const AssembleError = std.mem.Allocator.Error || error{BadSyntax};
pub const AssemblyKind = enum(c_int) {
vertex,
fragment,
};
pub fn assemble(self: *Context, allocator: std.mem.Allocator, kind: AssemblyKind, name: [*:0]const u8, source: []const u8) AssembleError![]u8 {
const result = ext.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (kind) {
.fragment => ext.shaderc_glsl_vertex_shader,
.vertex => ext.shaderc_glsl_vertex_shader,
}, name, "main", self.spirv_options) orelse {
return error.OutOfMemory;
};
defer {
ext.shaderc_result_release(result);
}
return switch (ext.shaderc_result_get_compilation_status(result)) {
ext.shaderc_compilation_status_success => {
const compiled_len = ext.shaderc_result_get_length(result);
const compiled_ptr = ext.shaderc_result_get_bytes(result);
return allocator.dupe(u8, compiled_ptr[0..compiled_len]);
},
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.debug("problematic shader:\n{s}", .{source});
return error.BadSyntax;
},
ext.shaderc_compilation_status_internal_error => @panic("shaderc internal compiler error"),
else => unreachable,
};
}
pub fn deinit(self: *Context) void {
ext.shaderc_compile_options_release(self.spirv_options);
ext.shaderc_compiler_release(self.shader_compiler);
ext.SDL_DestroyGPUDevice(self.gpu_device);
ext.SDL_DestroyWindow(self.window);
self.* = undefined;
}
pub const traits = ona.Traits{
@ -29,86 +75,89 @@ const Context = struct {
};
};
fn compile_shaders(_: ona.Write(Context), assets: ona.Assets) !void {
fn compile_shaders(context: ona.Write(Context)) !void {
const Camera = extern struct {
projection: [4]@Vector(4, f32),
};
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
defer {
arena.deinit();
}
const shader_path = "graphics_demo.eff";
var root = try coral.shaders.Root.init(&arena);
const arena_allocator = arena.allocator();
const Constants = extern struct {
screen_width: f32,
screen_height: f32,
time: f32,
};
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 },
try root.defineUniform(&arena, .{
.identifier = "effect",
.semantic = "Effect",
.binding = 0,
.has_field = .fieldsOf(Constants),
});
try root.defineTexture(&arena, .{
.identifier = "albedo",
.binding = 0,
.layout = .dimensions_2,
});
try root.defineInput(&arena, .{
.identifier = "uv",
.type = .float2,
.location = 0,
});
try root.defineOutput(&arena, .{
.identifier = "color",
.type = .float4,
.location = 0,
});
switch (try root.parse(&arena, try assets.load(shader_path, arena.allocator()))) {
.ok => {
const spirv_module = try root.buildSpirvFragment(&arena, "frag");
var codes = coral.Stack(u8).empty;
defer {
codes.deinit();
}
try spirv_module.writeTo(coral.bytes.stackWriter(&codes));
std.log.info("{s}", .{codes.values});
.{ "instance_rect", .vec4 },
.{ "instance_xbasis", .vec2 },
.{ "instance_ybasis", .vec2 },
.{ "instance_origin", .vec2 },
.{ "instance_color", .vec4 },
.{ "instance_depth", .float },
},
.failure => |failure| {
std.log.err("failed to parse shader {s}: {s}", .{ shader_path, failure });
return error.ShaderParseFailure;
.outputs = &.{
.{ "color", .vec4 },
.{ "uv", .vec2 },
},
}
@breakpoint();
.uniforms = &.{
.{ "camera", .init(0, Camera) },
},
}));
// const basic_shader = ext.SDL_CreateGPUShader(gpu_device, &.{
// .code = spirv_code,
// .code_size = spirv_code.len,
// .stage = ext.SDL_GPU_SHADERSTAGE_FRAGMENT,
// .entrypoint = "frag_main",
// }) orelse {
// return sdl_failure("Failed to create basic shader");
// };
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, &.{
.code = vertex_spirv.ptr,
.code_size = vertex_spirv.len,
.format = ext.SDL_GPU_SHADERFORMAT_SPIRV,
.entrypoint = "main",
});
_ = effect_fragment;
_ = effect_vertex;
}
pub fn poll(input_events: ona.Send(ona.hid.Event)) !void {
pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void {
var event: ext.SDL_Event = undefined;
while (ext.SDL_PollEvent(&event)) {
try input_events.push(switch (event.type) {
ext.SDL_EVENT_QUIT => .quit,
try hid_events.push(switch (event.type) {
ext.SDL_EVENT_QUIT => {
return exit.push(.success);
},
ext.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
ext.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) },
@ -117,7 +166,10 @@ pub fn poll(input_events: ona.Send(ona.hid.Event)) !void {
ext.SDL_BUTTON_LEFT => .left,
ext.SDL_BUTTON_RIGHT => .right,
ext.SDL_BUTTON_MIDDLE => .middle,
else => continue,
else => {
continue;
},
},
},
@ -126,7 +178,10 @@ pub fn poll(input_events: ona.Send(ona.hid.Event)) !void {
ext.SDL_BUTTON_LEFT => .left,
ext.SDL_BUTTON_RIGHT => .right,
ext.SDL_BUTTON_MIDDLE => .middle,
else => continue,
else => {
continue;
},
},
},
@ -137,7 +192,9 @@ pub fn poll(input_events: ona.Send(ona.hid.Event)) !void {
},
},
else => continue,
else => {
continue;
},
});
}
}
@ -215,7 +272,33 @@ pub fn setup(app: *ona.App) !void {
return error.SdlFailure;
}
const shader_compiler = ext.shaderc_compiler_initialize() orelse {
return error.OutOfMemory;
};
errdefer {
ext.shaderc_compiler_release(shader_compiler);
}
const spirv_options = ext.shaderc_compile_options_initialize() orelse {
return error.OutOfMemory;
};
errdefer {
ext.shaderc_compile_options_release(spirv_options);
}
ext.shaderc_compile_options_set_target_env(spirv_options, ext.shaderc_target_env_vulkan, ext.shaderc_env_version_vulkan_1_1);
ext.shaderc_compile_options_set_optimization_level(spirv_options, switch (builtin.mode) {
.Debug, .ReleaseSafe => ext.shaderc_optimization_level_zero,
.ReleaseSmall => ext.shaderc_optimization_level_size,
.ReleaseFast => ext.shaderc_optimization_level_performance,
});
try app.setState(Context{
.shader_compiler = shader_compiler,
.spirv_options = spirv_options,
.window = window,
.gpu_device = gpu_device,
});

4
src/ona/gfx/effect.frag Normal file
View File

@ -0,0 +1,4 @@
void main() {
color = vertex_color * texture(source_texture, vertex_uv);
}

11
src/ona/gfx/effect.vert Normal file
View File

@ -0,0 +1,11 @@
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);
}

162
src/ona/gfx/glsl.zig Normal file
View File

@ -0,0 +1,162 @@
const coral = @import("coral");
const ona = @import("../ona.zig");
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 {
name: [:0]const u8,
primitive: Primitive,
has_next: ?*const Field,
fn of(comptime uniform_fields: []const std.builtin.Type.StructField) *const Field {
if (uniform_fields.len == 0) {
@compileError("Struct contains no fields");
}
const field = struct {
const instance = Field{
.name = uniform_fields[0].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) {
1 => null,
else => .of(uniform_fields[1..]),
},
};
};
return &field.instance;
}
};
pub fn init(binding: usize, comptime Struct: type) Uniform {
// TODO: Review how GLSL identifier-safe names are generated.
const struct_name = @typeName(Struct);
const struct_type = switch (@typeInfo(Struct)) {
.@"struct" => |@"struct"| @"struct",
else => @compileError("`Struct` must be a struct type"),
};
if (struct_type.is_tuple) {
@compileError("`Struct` must be a non-tuple struct");
}
if (struct_type.layout != .@"extern") {
@compileError("`Struct` must use an extern layout");
}
return .{
.name = if (std.mem.lastIndexOfScalar(u8, struct_name, '.')) |index| struct_name[index + 1 ..] else struct_name,
.field = .of(struct_type.fields),
.binding = binding,
};
}
};
pub fn inject(allocator: std.mem.Allocator, source_base: []const u8, options: InjectOptions) std.mem.Allocator.Error![:0]u8 {
return coral.bytes.allocFormatted(allocator, "{injected_source}\n{original_source}\n", .{
.injected_source = options,
.original_source = source_base,
});
}

View File

@ -1,5 +1,6 @@
const ona = @import("ona.zig");
pub const Event = union(enum) {
quit,
key_up: KeyScancode,
key_down: KeyScancode,
mouse_up: MouseButton,

View File

@ -141,6 +141,7 @@ fn run_realtime_loop(app: *App) !void {
const updates_per_frame = 60.0;
const target_frame_time = 1.0 / updates_per_frame;
const time = app.hasState(App.Time).?;
const exit_channel = app.hasState(Channel(App.Exit)).?;
const virtual_thread_count = std.Thread.getCpuCount() catch 0;
var tasks = try coral.asio.TaskQueue.init(virtual_thread_count / 2);
@ -155,7 +156,7 @@ fn run_realtime_loop(app: *App) !void {
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (app.is_running) {
while (true) {
const ticks_current = std.time.milliTimestamp();
const milliseconds_per_second = 1000.0;
@ -172,7 +173,21 @@ fn run_realtime_loop(app: *App) !void {
try app.run(&tasks, .post_update);
try app.run(&tasks, .render);
try app.run(&tasks, .finish);
}
try app.run(&tasks, .exit);
const exit_messages = exit_channel.messages();
if (exit_messages.len != 0) {
try app.run(&tasks, .exit);
switch (exit_messages[exit_messages.len - 1]) {
.success => {
break;
},
.failure => |failure| {
return failure;
},
}
}
}
}