diff --git a/README.md b/README.md index be67e9e..7bddb3a 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ const vkzig_dep = b.dependency("vulkan_zig", .{ .registry = @as([]const u8, b.pathFromRoot("path/to/vk.xml")), }); const vkzig_bindings = vkzig_dep.module("vulkan-zig"); -exe.addModule("vulkan", vkzig_bindings); +exe.root_module.addImport("vulkan", vkzig_bindings); ``` That will allow you to `@import("vulkan")` in your executable's source. @@ -94,27 +94,6 @@ exe.root_module.addImport("vulkan", vulkan_zig); See [examples/build.zig](examples/build.zig) and [examples/build.zig.zon](examples/build.zig.zon) for a concrete example. -### (Deprecated) Generation from build.zig - -Vulkan bindings can be generated from the Vulkan XML registry at compile time with build.zig, by using the provided Vulkan generation step. This requires adding vulkan-zig to your dependencies as shown above. After than, vulkan-zig can be imported at build time as follows: -```zig -const vkgen = @import("vulkan_zig"); - -pub fn build(b: *Builder) void { - ... - const exe = b.addExecutable("my-executable", "src/main.zig"); - - // Create a step that generates vk.zig (stored in zig-cache) from the provided vulkan registry. - const gen = vkgen.VkGenerateStep.create(b, "path/to/vk.xml"); - - // Add the generated file as package to the final executable - exe.addModule("vulkan", gen.getModule()); -} -``` -This reads vk.xml, parses its contents, and renders the Vulkan bindings to "vk.zig", which is then formatted and placed in `zig-cache`. The resulting file can then be added to an executable by using `addModule`, after which the bindings will be made available to the executable under the name passed to `getModule`. - -This feature is only provided for legacy reasons. If you are still using this, please migrate to one of the methods that involve the package manager, as this method will be removed in the near future. - ### Function & field renaming Functions and fields are renamed to be more or less in line with [Zig's standard library style](https://ziglang.org/documentation/master/#Style-Guide): @@ -355,31 +334,32 @@ For some times (such as those from Google Games Platform) no default is known, b ### Shader compilation -vulkan-zig provides functionality to help compile shaders to SPIR-V using glslc. It can be used from build.zig as follows: - +Shaders should be compiled by invoking a shader compiler via the build system. For example: ```zig -const vkgen = @import("vulkan_zig"); - pub fn build(b: *Builder) void { ... - const shader_comp = vkgen.ShaderCompileStep.create( - builder, - &[_][]const u8{"glslc", "--target-env=vulkan1.2"}, - "-o", - ); - shader_comp.add("shader_frag", "path/to/shader.frag", .{}); - shader_comp.add("shader_vert", "path/to/shader.vert", .{}); - exe.addModule("shaders", shader_comp.getModule()); + const vert_cmd = b.addSystemCommand(&.{ + "glslc", + "--target-env=vulkan1.2", + "-o" + }); + const vert_spv = vert_cmd.addOutputFileArg("vert.spv"); + vert_cmd.addFileArg(b.path("shaders/triangle.vert")); + exe.root_module.addAnonymousImport("vertex_shader", .{ + .root_source_file = vert_spv + }); + ... } ``` -Upon compilation, glslc is invoked to compile each shader, and the result is placed within `zig-cache`. All shaders which are compiled using a particular `ShaderCompileStep` are imported in a single Zig file using `@embedFile`, and this file can be added to an executable as a module using `addModule`. To slightly improve compile times, shader compilation is cached; as long as a shader's source and its compile commands stay the same, the shader is not recompiled. The SPIR-V code for any particular shader is aligned to that of a 32-bit integer as follows, as required by vkCreateShaderModule: - +Note that SPIR-V must be 32-bit aligned when fed to Vulkan. The easiest way to do this is to dereference the shader's bytecode and manually align it as follows: ```zig -pub const ${name} align(@alignOf(u32)) = @embedFile("${path}").*; +const vert_spv align(@alignOf(u32)) = @embedFile("vertex_shader").*; ``` -See [build.zig](build.zig) for a working example. +See [examples/build.zig](examples/build.zig) for a working example. + +For more advanced shader compiler usage, one may consider a library such as [shader_compiler](https://github.com/Games-by-Mason/shader_compiler). ## Limitations @@ -391,3 +371,4 @@ See [build.zig](build.zig) for a working example. * Alternative binding generator: https://github.com/SpexGuy/Zig-Vulkan-Headers * Zig bindings for GLFW: https://github.com/hexops/mach-glfw * With vulkan-zig integration example: https://github.com/hexops/mach-glfw-vulkan-example +* Advanced shader compilation: https://github.com/Games-by-Mason/shader_compiler diff --git a/build.zig b/build.zig index 38bb89b..debf85c 100644 --- a/build.zig +++ b/build.zig @@ -1,9 +1,5 @@ const std = @import("std"); -const vkgen = @import("src/main.zig"); -pub const ShaderCompileStep = vkgen.ShaderCompileStep; -pub const VkGenerateStep = vkgen.VkGenerateStep; - pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..16732a7 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = "vulkan", + .version = "0.0.0", + .minimum_zig_version = "0.14.0-dev.1359+e9a00ba7f", + .paths = .{ + "build.zig", + "LICENSE", + "README.md", + "src", + }, +} diff --git a/examples/build.zig b/examples/build.zig index ea92c67..b49d41b 100644 --- a/examples/build.zig +++ b/examples/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); const vkgen = @import("vulkan_zig"); -const ShaderCompileStep = vkgen.ShaderCompileStep; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -33,14 +32,27 @@ pub fn build(b: *std.Build) void { .root_source_file = vk_generate_cmd.addOutputFileArg("vk.zig"), }); - const shaders = ShaderCompileStep.create( - b, - &[_][]const u8{ "glslc", "--target-env=vulkan1.2" }, + const vert_cmd = b.addSystemCommand(&.{ + "glslc", + "--target-env=vulkan1.2", "-o", - ); - shaders.add("triangle_vert", "shaders/triangle.vert", .{}); - shaders.add("triangle_frag", "shaders/triangle.frag", .{}); - triangle_exe.root_module.addImport("shaders", shaders.getModule()); + }); + const vert_spv = vert_cmd.addOutputFileArg("vert.spv"); + vert_cmd.addFileArg(b.path("shaders/triangle.vert")); + triangle_exe.root_module.addAnonymousImport("vertex_shader", .{ + .root_source_file = vert_spv, + }); + + const frag_cmd = b.addSystemCommand(&.{ + "glslc", + "--target-env=vulkan1.2", + "-o", + }); + const frag_spv = frag_cmd.addOutputFileArg("frag.spv"); + frag_cmd.addFileArg(b.path("shaders/triangle.frag")); + triangle_exe.root_module.addAnonymousImport("fragment_shader", .{ + .root_source_file = frag_spv, + }); const triangle_run_cmd = b.addRunArtifact(triangle_exe); triangle_run_cmd.step.dependOn(b.getInstallStep()); diff --git a/examples/c.zig b/examples/c.zig index 77c40f3..0e0914c 100644 --- a/examples/c.zig +++ b/examples/c.zig @@ -1,14 +1,32 @@ -pub usingnamespace @cImport({ +const c = @cImport({ @cDefine("GLFW_INCLUDE_NONE", {}); @cInclude("GLFW/glfw3.h"); }); const vk = @import("vulkan"); -const c = @This(); + +// Re-export the GLFW things that we need +pub const GLFW_TRUE = c.GLFW_TRUE; +pub const GLFW_FALSE = c.GLFW_FALSE; +pub const GLFW_CLIENT_API = c.GLFW_CLIENT_API; +pub const GLFW_NO_API = c.GLFW_NO_API; + +pub const GLFWwindow = c.GLFWwindow; + +pub const glfwInit = c.glfwInit; +pub const glfwTerminate = c.glfwTerminate; +pub const glfwVulkanSupported = c.glfwVulkanSupported; +pub const glfwWindowHint = c.glfwWindowHint; +pub const glfwCreateWindow = c.glfwCreateWindow; +pub const glfwDestroyWindow = c.glfwDestroyWindow; +pub const glfwWindowShouldClose = c.glfwWindowShouldClose; +pub const glfwGetRequiredInstanceExtensions = c.glfwGetRequiredInstanceExtensions; +pub const glfwGetFramebufferSize = c.glfwGetFramebufferSize; +pub const glfwPollEvents = c.glfwPollEvents; // usually the GLFW vulkan functions are exported if Vulkan is included, -// but since thats not the case here, they are manually imported. +// but since thats not the case here, they are manually imported. pub extern fn glfwGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction; pub extern fn glfwGetPhysicalDevicePresentationSupport(instance: vk.Instance, pdev: vk.PhysicalDevice, queuefamily: u32) c_int; -pub extern fn glfwCreateWindowSurface(instance: vk.Instance, window: *c.GLFWwindow, allocation_callbacks: ?*const vk.AllocationCallbacks, surface: *vk.SurfaceKHR) vk.Result; +pub extern fn glfwCreateWindowSurface(instance: vk.Instance, window: *GLFWwindow, allocation_callbacks: ?*const vk.AllocationCallbacks, surface: *vk.SurfaceKHR) vk.Result; diff --git a/examples/triangle.zig b/examples/triangle.zig index 60702b0..ca205be 100644 --- a/examples/triangle.zig +++ b/examples/triangle.zig @@ -1,11 +1,13 @@ const std = @import("std"); const vk = @import("vulkan"); const c = @import("c.zig"); -const shaders = @import("shaders"); const GraphicsContext = @import("graphics_context.zig").GraphicsContext; const Swapchain = @import("swapchain.zig").Swapchain; const Allocator = std.mem.Allocator; +const vert_spv align(@alignOf(u32)) = @embedFile("vertex_shader").*; +const frag_spv align(@alignOf(u32)) = @embedFile("fragment_shader").*; + const app_name = "vulkan-zig triangle example"; const Vertex = struct { @@ -362,14 +364,14 @@ fn createPipeline( render_pass: vk.RenderPass, ) !vk.Pipeline { const vert = try gc.dev.createShaderModule(&.{ - .code_size = shaders.triangle_vert.len, - .p_code = @ptrCast(&shaders.triangle_vert), + .code_size = vert_spv.len, + .p_code = @ptrCast(&vert_spv), }, null); defer gc.dev.destroyShaderModule(vert, null); const frag = try gc.dev.createShaderModule(&.{ - .code_size = shaders.triangle_frag.len, - .p_code = @ptrCast(&shaders.triangle_frag), + .code_size = frag_spv.len, + .p_code = @ptrCast(&frag_spv), }, null); defer gc.dev.destroyShaderModule(frag, null); diff --git a/src/build_integration.zig b/src/build_integration.zig deleted file mode 100644 index 35f5bb4..0000000 --- a/src/build_integration.zig +++ /dev/null @@ -1,235 +0,0 @@ -const std = @import("std"); -const Build = std.Build; - -/// Utility functionality to help with compiling shaders from build.zig. -/// Invokes a shader compile command (e.g., glslc ...) for each shader -/// added via `addShader`. -pub const ShaderCompileStep = struct { - /// The directory within the zig-cache directory that is used to store - /// shader artifacts. - pub const cache_dir = "shaders"; - - /// This structure contains additional options that pertain to specific shaders only. - pub const ShaderOptions = struct { - /// Additional arguments that should be passed to the shader compiler. - args: []const []const u8 = &.{}, - - /// Paths of additional files that should be watched for changes to - /// trigger recompilation. - watched_files: []const []const u8 = &.{}, - - /// To ensure that if compilation options change, the shader is recompiled - /// properly. - fn hash(self: ShaderOptions, b: *Build, hasher: anytype) !void { - for (self.args) |arg| { - hasher.update(arg); - } - for (self.watched_files) |file_path| { - const full_path = b.build_root.join(b.allocator, &.{file_path}) catch unreachable; - - const source = std.fs.cwd().readFileAlloc( - b.allocator, - full_path, - std.math.maxInt(usize), - ) catch |err| switch (err) { - error.FileNotFound => { - std.log.err("could not open file '{s}'", .{file_path}); - return error.FileNotFound; - }, - else => |e| return e, - }; - hasher.update(source); - } - } - }; - - /// Structure representing a shader to be compiled. - const Shader = struct { - /// The name of the shader in the generated file. - /// Must be unique for all shaders added to this ShaderCompileStep. - name: []const u8, - - /// The path to the shader, relative to the current build root. - source_path: []const u8, - - /// The final hash of the shader - hash: [64]u8, - - /// Miscellaneous options to pass when compiling the shader. - options: ShaderOptions, - }; - - step: Build.Step, - - /// The command and optional arguments used to invoke the shader compiler. - compile_command: []const []const u8, - - /// The compiler flag used to specify the output path, `-o` most of the time - output_flag: []u8, - - /// List of shaders that are to be compiled. - shaders: std.ArrayList(Shader), - - /// The main Zig file that contains all the shaders. Each shader is included as - /// `pub const ${name} align(@alignOf(u32))= @embedFile("${path").*;` - generated_file: Build.GeneratedFile, - - /// Create a ShaderCompileStep for `builder`. When this step is invoked by the build - /// system, ` ` is invoked for each shader. - /// For example, if one calls this with `create(b, "glslc", "-o")` and then - /// `c.addShader("vertex", "vertex.glsl", .{})`, the command will be `glslc vertex.glsl -o ` - pub fn create(builder: *Build, compile_command: []const []const u8, output_flag: []const u8) *ShaderCompileStep { - const self = builder.allocator.create(ShaderCompileStep) catch unreachable; - self.* = .{ - .step = Build.Step.init(.{ - .id = .custom, - .name = "shaders", - .owner = builder, - .makeFn = make, - }), - .compile_command = builder.dupeStrings(compile_command), - .output_flag = builder.dupe(output_flag), - .shaders = std.ArrayList(Shader).init(builder.allocator), - .generated_file = undefined, - }; - self.generated_file = .{ .step = &self.step }; - return self; - } - - /// Returns the shaders module with name. - pub fn getModule(self: *ShaderCompileStep) *Build.Module { - return self.step.owner.createModule(.{ - .root_source_file = self.getSource(), - }); - } - - /// Returns the file source for the generated shader resource code. - pub fn getSource(self: *ShaderCompileStep) Build.LazyPath { - return .{ .generated = .{ .file = &self.generated_file } }; - } - - /// Add a shader to be compiled. `src` is shader source path, relative to the project root. - /// Returns the full path where the compiled binary will be stored upon successful compilation. - /// This path can then be used to include the binary into an executable, for example by passing it - /// to @embedFile via an additional generated file. - pub fn add(self: *ShaderCompileStep, name: []const u8, src: []const u8, options: ShaderOptions) void { - const b = self.step.owner; - const full_source_path = b.build_root.join(b.allocator, &.{src}) catch unreachable; - self.shaders.append(.{ - .name = name, - .source_path = full_source_path, - .hash = undefined, - .options = options, - }) catch unreachable; - } - - /// Create a hash of a shader's source contents. - fn hashShaderToFileName(self: *ShaderCompileStep, shader: Shader) ![64]u8 { - const b = self.step.owner; - const source = std.fs.cwd().readFileAlloc( - b.allocator, - shader.source_path, - std.math.maxInt(usize), - ) catch |err| switch (err) { - error.FileNotFound => { - std.log.err("could not open shader '{s}'", .{shader.source_path}); - return error.FileNotFound; - }, - else => |e| return e, - }; - - var hasher = std.crypto.hash.blake2.Blake2b384.init(.{}); - // Random bytes to make ShaderCompileStep unique. Refresh with new random - // bytes when the implementation is changed in a non-backwards-compatible way. - hasher.update("Pw7Z*9Q8r!fLY8&!"); - // Make sure that there is no cache hit if the shader's source has changed. - hasher.update(source); - // Not only the shader source must be the same to ensure uniqueness - - // the compilation options must be the same as well! - try shader.options.hash(b, &hasher); - // And the compile command, too. - for (self.compile_command) |cmd| { - hasher.update(cmd); - } - - return digest(&hasher); - } - - /// Create a base-64 hash digest from a hasher, which we can use as file name. - fn digest(hasher: anytype) [64]u8 { - var hash_digest: [48]u8 = undefined; - hasher.final(&hash_digest); - var hash: [64]u8 = undefined; - _ = std.fs.base64_encoder.encode(&hash, &hash_digest); - return hash; - } - - /// Internal build function. - fn make(step: *Build.Step, progress: std.Progress.Node) !void { - _ = progress; - const b = step.owner; - const self: *ShaderCompileStep = @fieldParentPtr("step", step); - const cwd = std.fs.cwd(); - - var cmd = std.ArrayList([]const u8).init(b.allocator); - try cmd.appendSlice(self.compile_command); - const base_cmd_len = cmd.items.len; - - var shaders_file_contents = std.ArrayList(u8).init(b.allocator); - const shaders_out = shaders_file_contents.writer(); - - const shaders_dir = try b.cache_root.join( - b.allocator, - &.{cache_dir}, - ); - try cwd.makePath(shaders_dir); - - for (self.shaders.items) |*shader| { - shader.hash = try self.hashShaderToFileName(shader.*); - const shader_out_path = try std.fs.path.join(b.allocator, &.{ - shaders_dir, - &shader.hash, - }); - - // This path must be relative to the shaders zig file - which is in the same directory - try shaders_out.print("pub const {s} align(@alignOf(u32)) = @embedFile(\"{s}\").*;\n", .{ - shader.name, - &shader.hash, - }); - - // If we have a cache hit, we can save some compile time by not invoking the compile command. - compile_shader: { - std.fs.accessAbsolute(shader_out_path, .{}) catch |err| switch (err) { - error.FileNotFound => break :compile_shader, - else => |e| return e, - }; - - continue; - } - - cmd.items.len = base_cmd_len; - - try cmd.appendSlice(shader.options.args); - try cmd.appendSlice(&.{ shader.source_path, self.output_flag, shader_out_path }); - _ = try step.evalChildProcess(cmd.items); - } - - // Generate a file name for the shaders zig source based on the contents of shaders_file_contents. - // In this case we don't need to omit writing the file - Zig does this check already for us. - var hasher = std.crypto.hash.blake2.Blake2b384.init(.{}); - // Note: don't need to seed the hasher - it transitively contains the seed from - // hashShaderToFileName. Change that if the implementation changes. - hasher.update(shaders_file_contents.items); - - const shaders_path = try std.fs.path.join( - b.allocator, - &.{ shaders_dir, &digest(&hasher) }, - ); - - try cwd.writeFile(.{ - .sub_path = shaders_path, - .data = shaders_file_contents.items, - }); - self.generated_file.path = shaders_path; - } -}; diff --git a/src/main.zig b/src/main.zig index 9ad2f95..b062907 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,10 +1,6 @@ const std = @import("std"); const generator = @import("vulkan/generator.zig"); -pub const generateVk = generator.generate; -pub const VkGenerateStep = @import("vulkan/build_integration.zig").GenerateStep; -pub const ShaderCompileStep = @import("build_integration.zig").ShaderCompileStep; - fn invalidUsage(prog_name: []const u8, comptime fmt: []const u8, args: anytype) noreturn { std.log.err(fmt, args); std.log.err("see {s} --help for usage", .{prog_name}); diff --git a/src/vulkan/build_integration.zig b/src/vulkan/build_integration.zig deleted file mode 100644 index c5ae127..0000000 --- a/src/vulkan/build_integration.zig +++ /dev/null @@ -1,202 +0,0 @@ -const std = @import("std"); -const generator = @import("generator.zig"); -const Build = std.Build; - -/// build.zig integration for Vulkan binding generation. This step can be used to generate -/// Vulkan bindings at compiletime from vk.xml, by providing the path to vk.xml and the output -/// path relative to zig-cache. The final package can then be obtained by `package()`, the result -/// of which can be added to the project using `std.Build.addModule`. -pub const GenerateStep = struct { - step: Build.Step, - generated_file: Build.GeneratedFile, - /// The path to vk.xml - spec_path: []const u8, - /// The API to generate for. - /// Defaults to Vulkan. - // Note: VulkanSC is experimental. - api: generator.Api = .vulkan, - - /// Initialize a Vulkan generation step, for `builder`. `spec_path` is the path to - /// vk.xml, relative to the project root. The generated bindings will be placed at - /// `out_path`, which is relative to the zig-cache directory. - pub fn create(builder: *Build, spec_path: []const u8) *GenerateStep { - const self = builder.allocator.create(GenerateStep) catch unreachable; - self.* = .{ - .step = Build.Step.init(.{ - .id = .custom, - .name = "vulkan-generate", - .owner = builder, - .makeFn = make, - }), - .generated_file = .{ - .step = &self.step, - }, - .spec_path = spec_path, - }; - return self; - } - - /// Initialize a Vulkan generation step for `builder`, by extracting vk.xml from the LunarG installation - /// root. Typically, the location of the LunarG SDK root can be retrieved by querying for the VULKAN_SDK - /// environment variable, set by activating the environment setup script located in the SDK root. - /// `builder` and `out_path` are used in the same manner as `init`. - pub fn createFromSdk(builder: *Build, sdk_path: []const u8, output_name: []const u8) *GenerateStep { - const spec_path = std.fs.path.join( - builder.allocator, - &[_][]const u8{ sdk_path, "share/vulkan/registry/vk.xml" }, - ) catch unreachable; - - return create(builder, spec_path, output_name); - } - - /// Set the API to generate for. - pub fn setApi(self: *GenerateStep, api_to_generate: generator.Api) void { - self.api = api_to_generate; - } - - /// Returns the module with the generated budings, with name `module_name`. - pub fn getModule(self: *GenerateStep) *Build.Module { - return self.step.owner.createModule(.{ - .root_source_file = self.getSource(), - }); - } - - /// Returns the file source for the generated bindings. - pub fn getSource(self: *GenerateStep) Build.LazyPath { - return .{ .generated = .{ .file = &self.generated_file } }; - } - - /// Internal build function. This reads `vk.xml`, and passes it to `generate`, which then generates - /// the final bindings. The resulting generated bindings are not formatted, which is why an ArrayList - /// writer is passed instead of a file writer. This is then formatted into standard formatting - /// by parsing it and rendering with `std.zig.parse` and `std.zig.render` respectively. - fn make(step: *Build.Step, progress: std.Progress.Node) !void { - _ = progress; - - const b = step.owner; - const self: *GenerateStep = @fieldParentPtr("step", step); - const cwd = std.fs.cwd(); - - var man = b.graph.cache.obtain(); - defer man.deinit(); - - const spec = try cwd.readFileAlloc(b.allocator, self.spec_path, std.math.maxInt(usize)); - // TODO: Look into whether this is the right way to be doing - // this - maybe the file-level caching API has some benefits I - // don't understand. - man.hash.addBytes(spec); - - const already_exists = try step.cacheHit(&man); - const digest = man.final(); - const output_file_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, "vk.zig" }); - if (already_exists) { - self.generated_file.path = output_file_path; - return; - } - - var out_buffer = std.ArrayList(u8).init(b.allocator); - generator.generate(b.allocator, self.api, spec, out_buffer.writer()) catch |err| switch (err) { - error.InvalidXml => { - std.log.err("invalid vulkan registry - invalid xml", .{}); - std.log.err("please check that the correct vk.xml file is passed", .{}); - return err; - }, - error.InvalidRegistry => { - std.log.err("invalid vulkan registry - registry is valid xml but contents are invalid", .{}); - std.log.err("please check that the correct vk.xml file is passed", .{}); - return err; - }, - error.UnhandledBitfieldStruct => { - std.log.err("unhandled struct with bit fields detected in vk.xml", .{}); - std.log.err("this is a bug in vulkan-zig", .{}); - std.log.err("please make a bug report at https://github.com/Snektron/vulkan-zig/issues/", .{}); - return err; - }, - error.OutOfMemory => return error.OutOfMemory, - }; - try out_buffer.append(0); - - const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0]; - const tree = try std.zig.Ast.parse(b.allocator, src, .zig); - - if (tree.errors.len > 0) { - var start: usize = undefined; - var end: usize = undefined; - var index: usize = undefined; - var repeat: usize = undefined; - var spaces: []const u8 = undefined; - var carets: []const u8 = undefined; - var current_token: std.zig.Token = undefined; - - std.debug.print("{s}\n", .{ - src, - }); - - var tokens = try std.ArrayList(std.zig.Ast.Error).initCapacity(b.allocator, tree.errors.len); - try tokens.appendSlice(tree.errors); - - std.mem.sort(std.zig.Ast.Error, tokens.items, {}, struct { - pub fn desc(_: void, l_err: std.zig.Ast.Error, r_err: std.zig.Ast.Error) bool { - return l_err.token > r_err.token; - } - }.desc); - - var iterator = std.zig.Tokenizer.init(src); - - index = 1; - current_token = iterator.next(); - - while (current_token.tag != std.zig.Token.Tag.eof and tokens.items.len > 0) { - if (tokens.items[tokens.items.len - 1].token == index) { - start = std.mem.lastIndexOf(u8, src[0..current_token.loc.start], "\n") orelse 0; - end = (std.mem.indexOf(u8, src[current_token.loc.end..], "\n") orelse src.len - current_token.loc.end) + current_token.loc.end; - - repeat = 1; - spaces = ""; - while (repeat < current_token.loc.start - start) { - spaces = try std.fmt.allocPrint(b.allocator, "{s} ", .{ - spaces, - }); - repeat += 1; - } - - repeat = 1; - carets = ""; - while (repeat < current_token.loc.end + 1 - current_token.loc.start) { - carets = try std.fmt.allocPrint(b.allocator, "{s}^", .{ - carets, - }); - repeat += 1; - } - - std.debug.print("ERROR: {}\nTOKEN: {}\n\n{s}\n{s}{s}\n", .{ - tokens.items[tokens.items.len - 1], - current_token, - if (src[start] == '\n') src[start + 1 .. end] else src[start..end], - spaces, - carets, - }); - - _ = tokens.pop(); - } - - current_token = iterator.next(); - index += 1; - } - } - - std.debug.assert(tree.errors.len == 0); // If this triggers, vulkan-zig produced invalid code. - - const formatted = try tree.render(b.allocator); - - const output_dir_path = std.fs.path.dirname(output_file_path).?; - cwd.makePath(output_dir_path) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ output_dir_path, @errorName(err) }); - return err; - }; - - try cwd.writeFile(.{ .sub_path = output_file_path, .data = formatted }); - self.generated_file.path = output_file_path; - try step.writeManifest(&man); - } -}; diff --git a/src/vulkan/render.zig b/src/vulkan/render.zig index a5c5e5f..45d3c3c 100644 --- a/src/vulkan/render.zig +++ b/src/vulkan/render.zig @@ -26,6 +26,7 @@ const preamble = \\ .AAPCSVFP \\ else \\ .C; + // Note: Keep in sync with flag_functions \\pub fn FlagsMixin(comptime FlagsType: type) type { \\ return struct { \\ pub const IntType = @typeInfo(FlagsType).Struct.backing_integer.?; @@ -50,9 +51,9 @@ const preamble = \\ pub fn contains(lhs: FlagsType, rhs: FlagsType) bool { \\ return toInt(intersect(lhs, rhs)) == toInt(rhs); \\ } - \\ pub usingnamespace FlagFormatMixin(FlagsType); \\ }; \\} + // Note: Keep in sync with flag_functions \\fn FlagFormatMixin(comptime FlagsType: type) type { \\ return struct { \\ pub fn format( @@ -63,7 +64,7 @@ const preamble = \\ ) !void { \\ try writer.writeAll(@typeName(FlagsType) ++ "{"); \\ var first = true; - \\ @setEvalBranchQuota(10_000); + \\ @setEvalBranchQuota(100_000); \\ inline for (comptime std.meta.fieldNames(FlagsType)) |name| { \\ if (name[0] == '_') continue; \\ if (@field(self, name)) { @@ -104,6 +105,76 @@ const preamble = \\}; ; +// Keep in sync with above definition of FlagsMixin +const flag_functions: []const []const u8 = &.{ + "toInt", + "fromInt", + "merge", + "intersect", + "complement", + "subtract", + "contains", +}; + +// Keep in sync with definition of command_flag_functions +const command_flags_mixin = + \\pub fn CommandFlagsMixin(comptime CommandFlags: type) type { + \\ return struct { + \\ pub fn merge(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { + \\ var result: CommandFlags = .{}; + \\ @setEvalBranchQuota(10_000); + \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { + \\ @field(result, field.name) = @field(lhs, field.name) or @field(rhs, field.name); + \\ } + \\ return result; + \\ } + \\ pub fn intersect(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { + \\ var result: CommandFlags = .{}; + \\ @setEvalBranchQuota(10_000); + \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { + \\ @field(result, field.name) = @field(lhs, field.name) and @field(rhs, field.name); + \\ } + \\ return result; + \\ } + \\ pub fn complement(self: CommandFlags) CommandFlags { + \\ var result: CommandFlags = .{}; + \\ @setEvalBranchQuota(10_000); + \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { + \\ @field(result, field.name) = !@field(self, field.name); + \\ } + \\ return result; + \\ } + \\ pub fn subtract(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { + \\ var result: CommandFlags = .{}; + \\ @setEvalBranchQuota(10_000); + \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { + \\ @field(result, field.name) = @field(lhs, field.name) and !@field(rhs, field.name); + \\ } + \\ return result; + \\ } + \\ pub fn contains(lhs: CommandFlags, rhs: CommandFlags) bool { + \\ @setEvalBranchQuota(10_000); + \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { + \\ if (!@field(lhs, field.name) and @field(rhs, field.name)) { + \\ return false; + \\ } + \\ } + \\ return true; + \\ } + \\ }; + \\} + \\ +; + +// Keep in sync with above definition of CommandFlagsMixin +const command_flag_functions: []const []const u8 = &.{ + "merge", + "intersect", + "complement", + "subtract", + "contains", +}; + const builtin_types = std.StaticStringMap([]const u8).initComptime(.{ .{ "void", @typeName(void) }, .{ "char", @typeName(u8) }, @@ -1009,9 +1080,8 @@ fn Renderer(comptime WriterType: type) type { try self.writer.writeAll(": bool = false,"); } } - try self.writer.writeAll("pub usingnamespace FlagsMixin("); - try self.renderName(name); - try self.writer.writeAll(");\n};\n"); + try self.renderFlagFunctions(name, "FlagsMixin", flag_functions, null); + try self.writer.writeAll("};\n"); } fn renderBitmask(self: *Self, name: []const u8, bitmask: reg.Bitmask) !void { @@ -1026,13 +1096,30 @@ fn Renderer(comptime WriterType: type) type { try self.writer.print( \\ = packed struct {{ \\_reserved_bits: {s} = 0, - \\pub usingnamespace FlagsMixin( , .{flags_type}); - try self.renderName(name); - try self.writer.writeAll(");\n};\n"); + try self.renderFlagFunctions(name, "FlagsMixin", flag_functions, null); + try self.writer.writeAll("};\n"); } } + fn renderFlagFunctions( + self: *Self, + name: []const u8, + mixin: []const u8, + functions: []const []const u8, + name_suffix: ?[]const u8, + ) !void { + try self.writer.writeAll("\n"); + for (functions) |function| { + try self.writer.print("pub const {s} = {s}(", .{ function, mixin }); + try self.renderName(name); + try self.writer.print("{s}).{s};\n", .{ name_suffix orelse "", function }); + } + try self.writer.writeAll("pub const format = FlagFormatMixin("); + try self.renderName(name); + try self.writer.print("{s}).format;\n", .{name_suffix orelse ""}); + } + fn renderHandle(self: *Self, name: []const u8, handle: reg.Handle) !void { const backing_type: []const u8 = if (handle.is_dispatchable) "usize" else "u64"; @@ -1253,55 +1340,7 @@ fn Renderer(comptime WriterType: type) type { } fn renderWrappers(self: *Self) !void { - try self.writer.writeAll( - \\pub fn CommandFlagsMixin(comptime CommandFlags: type) type { - \\ return struct { - \\ pub fn merge(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { - \\ var result: CommandFlags = .{}; - \\ @setEvalBranchQuota(10_000); - \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { - \\ @field(result, field.name) = @field(lhs, field.name) or @field(rhs, field.name); - \\ } - \\ return result; - \\ } - \\ pub fn intersect(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { - \\ var result: CommandFlags = .{}; - \\ @setEvalBranchQuota(10_000); - \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { - \\ @field(result, field.name) = @field(lhs, field.name) and @field(rhs, field.name); - \\ } - \\ return result; - \\ } - \\ pub fn complement(self: CommandFlags) CommandFlags { - \\ var result: CommandFlags = .{}; - \\ @setEvalBranchQuota(10_000); - \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { - \\ @field(result, field.name) = !@field(self, field.name); - \\ } - \\ return result; - \\ } - \\ pub fn subtract(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { - \\ var result: CommandFlags = .{}; - \\ @setEvalBranchQuota(10_000); - \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { - \\ @field(result, field.name) = @field(lhs, field.name) and !@field(rhs, field.name); - \\ } - \\ return result; - \\ } - \\ pub fn contains(lhs: CommandFlags, rhs: CommandFlags) bool { - \\ @setEvalBranchQuota(10_000); - \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| { - \\ if (!@field(lhs, field.name) and @field(rhs, field.name)) { - \\ return false; - \\ } - \\ } - \\ return true; - \\ } - \\ pub usingnamespace FlagFormatMixin(CommandFlags); - \\ }; - \\} - \\ - ); + try self.writer.writeAll(command_flags_mixin); try self.renderWrappersOfDispatchType(.base); try self.renderWrappersOfDispatchType(.instance); try self.renderWrappersOfDispatchType(.device); @@ -1380,13 +1419,10 @@ fn Renderer(comptime WriterType: type) type { } try self.writer.writeAll(" };\n}"); - try self.writer.print( - \\ pub usingnamespace CommandFlagsMixin({s}CommandFlags); - \\}}; - \\ - , .{name}); + try self.renderFlagFunctions(name, "CommandFlagsMixin", command_flag_functions, "CommandFlags"); try self.writer.print( + \\}}; \\pub fn {0s}Wrapper(comptime apis: []const ApiInfo) type {{ \\ return struct {{ \\ dispatch: Dispatch, @@ -1400,7 +1436,7 @@ fn Renderer(comptime WriterType: type) type { \\ break :blk cmds; \\ }}; \\ pub const Dispatch = blk: {{ - \\ @setEvalBranchQuota(10_000); + \\ @setEvalBranchQuota(1_000_000); \\ const Type = std.builtin.Type; \\ const fields_len = fields_len: {{ \\ var fields_len: u32 = 0; @@ -1522,6 +1558,7 @@ fn Renderer(comptime WriterType: type) type { try self.writer.print( \\pub fn {0s}Proxy(comptime apis: []const ApiInfo) type {{ + \\ @setEvalBranchQuota(100_000); \\ return struct {{ \\ const Self = @This(); \\ pub const Wrapper = {1s}Wrapper(apis); @@ -2031,7 +2068,7 @@ fn Renderer(comptime WriterType: type) type { if (returns_vk_result) { try self.writer.writeAll( \\data = try allocator.realloc(data, count); - \\result = try + \\result = try ); } else { try self.writer.writeAll("const data = try allocator.alloc("); diff --git a/test/ref_all_decls.zig b/test/ref_all_decls.zig index ed710fe..bcfecba 100644 --- a/test/ref_all_decls.zig +++ b/test/ref_all_decls.zig @@ -93,7 +93,7 @@ pub const StdVideoEncodeH265ReferenceInfoFlags = u32; pub const StdVideoEncodeH265ReferenceModificationFlags = u32; comptime { - @setEvalBranchQuota(100000); + @setEvalBranchQuota(1000000); reallyRefAllDecls(vk); }