forked from mirror/vulkan-zig
Merge pull request #177 from Snektron/remove-commandflags
remove wrapper/proxy configurability, just load everything unconditionally
This commit is contained in:
140
README.md
140
README.md
@@ -127,9 +127,9 @@ Functions and fields are renamed to be more or less in line with [Zig's standard
|
||||
* Container fields and function parameter names are generated in (lower) snake case in a similar manner: `ppEnabledLayerNames` becomes `pp_enabled_layer_names`.
|
||||
* Any name which is either an illegal Zig name or a reserved identifier is rendered using `@"name"` syntax. For example, `VK_IMAGE_TYPE_2D` is translated to `@"2d"`.
|
||||
|
||||
### Function pointers & Wrappers
|
||||
### Dispatch Tables
|
||||
|
||||
vulkan-zig provides no integration for statically linking libvulkan, and these symbols are not generated at all. Instead, vulkan functions are to be loaded dynamically. For each Vulkan function, a function pointer type is generated using the exact parameters and return types as defined by the Vulkan specification:
|
||||
Vulkan-zig provides no integration for statically linking libvulkan, and these symbols are not generated at all. Instead, vulkan functions are to be loaded dynamically. For each Vulkan function, a function pointer type is generated using the exact parameters and return types as defined by the Vulkan specification:
|
||||
```zig
|
||||
pub const PfnCreateInstance = fn (
|
||||
p_create_info: *const InstanceCreateInfo,
|
||||
@@ -138,77 +138,58 @@ pub const PfnCreateInstance = fn (
|
||||
) callconv(vulkan_call_conv) Result;
|
||||
```
|
||||
|
||||
For each function, a wrapper is generated into one of three structs:
|
||||
* BaseWrapper. This contains wrappers for functions which are loaded by `vkGetInstanceProcAddr` without an instance, such as `vkCreateInstance`, `vkEnumerateInstanceVersion`, etc.
|
||||
* InstanceWrapper. This contains wrappers for functions which are otherwise loaded by `vkGetInstanceProcAddr`.
|
||||
* DeviceWrapper. This contains wrappers for functions which are loaded by `vkGetDeviceProcAddr`.
|
||||
A set of _dispatch table_ structures is generated. A dispatch table simply contains a set of (optional) function pointers to Vulkan API functions, and not much else. Function pointers grouped by the nature of the function as follows:
|
||||
* Vulkan functions which are loaded by `vkGetInstanceProcAddr` without the need for passing an instance are placed in `BaseDispatch`.
|
||||
* Vulkan functions which are loaded by `vkGetInstanceProcAddr` but do need an instance are placed in `InstanceDispatch`.
|
||||
* Vulkan functions which are loaded by `vkGetDeviceProcAddr` are placed in `DeviceDispatch`.
|
||||
|
||||
To create a wrapper type, an "api specification" should be passed to it. This is a list of `ApiInfo` structs, which allows one to specify the functions that should be made available. An `ApiInfo` structure is initialized 3 optional fields, `base_commands`, `instance_commands`, and `device_commands`. Each of these takes a set of the vulkan functions that should be made available for that category, for example, setting `.createInstance = true` in `base_commands` makes the `createInstance` function available (loaded from `vkCreateInstance`). An entire feature level or extension can be pulled in at once too, for example, `vk.features.version_1_0` contains all functions for Vulkan 1.0. `vk.extensions.khr_surface` contains all functions for the `VK_KHR_surface` extension.
|
||||
### Wrappers
|
||||
|
||||
To provide more interesting functionality, a set of _wrapper_ types is also generated, one for each dispatch table type. These contain the Zig-versions of each Vulkan API function, along with corresponding error set definitions, return type definitions, etc, where appropriate.
|
||||
|
||||
```zig
|
||||
const vk = @import("vulkan");
|
||||
/// To construct base, instance and device wrappers for vulkan-zig, you need to pass a list of 'apis' to it.
|
||||
const apis: []const vk.ApiInfo = &.{
|
||||
// You can either add invidiual functions by manually creating an 'api'
|
||||
.{
|
||||
.base_commands = .{
|
||||
.createInstance = true,
|
||||
},
|
||||
.instance_commands = .{
|
||||
.createDevice = true,
|
||||
},
|
||||
},
|
||||
// Or you can add entire feature sets or extensions
|
||||
vk.features.version_1_0,
|
||||
vk.extensions.khr_surface,
|
||||
vk.extensions.khr_swapchain,
|
||||
};
|
||||
const BaseDispatch = vk.BaseWrapper(apis);
|
||||
```
|
||||
The wrapper struct then provides wrapper functions for each function pointer in the dispatch struct:
|
||||
```zig
|
||||
pub const BaseWrapper(comptime cmds: anytype) type {
|
||||
...
|
||||
pub const BaseWrapper = struct {
|
||||
const Self = @This();
|
||||
const Dispatch = CreateDispatchStruct(cmds);
|
||||
return struct {
|
||||
dispatch: Dispatch,
|
||||
|
||||
pub const CreateInstanceError = error{
|
||||
OutOfHostMemory,
|
||||
OutOfDeviceMemory,
|
||||
InitializationFailed,
|
||||
LayerNotPresent,
|
||||
ExtensionNotPresent,
|
||||
IncompatibleDriver,
|
||||
Unknown,
|
||||
};
|
||||
pub fn createInstance(
|
||||
self: Self,
|
||||
create_info: InstanceCreateInfo,
|
||||
p_allocator: ?*const AllocationCallbacks,
|
||||
) CreateInstanceError!Instance {
|
||||
var instance: Instance = undefined;
|
||||
const result = self.dispatch.vkCreateInstance(
|
||||
&create_info,
|
||||
p_allocator,
|
||||
&instance,
|
||||
);
|
||||
switch (result) {
|
||||
.success => {},
|
||||
.error_out_of_host_memory => return error.OutOfHostMemory,
|
||||
.error_out_of_device_memory => return error.OutOfDeviceMemory,
|
||||
.error_initialization_failed => return error.InitializationFailed,
|
||||
.error_layer_not_present => return error.LayerNotPresent,
|
||||
.error_extension_not_present => return error.ExtensionNotPresent,
|
||||
.error_incompatible_driver => return error.IncompatibleDriver,
|
||||
else => return error.Unknown,
|
||||
}
|
||||
return instance;
|
||||
dispatch: Dispatch,
|
||||
|
||||
pub const CreateInstanceError = error{
|
||||
OutOfHostMemory,
|
||||
OutOfDeviceMemory,
|
||||
InitializationFailed,
|
||||
LayerNotPresent,
|
||||
ExtensionNotPresent,
|
||||
IncompatibleDriver,
|
||||
Unknown,
|
||||
};
|
||||
pub fn createInstance(
|
||||
self: Self,
|
||||
create_info: InstanceCreateInfo,
|
||||
p_allocator: ?*const AllocationCallbacks,
|
||||
) CreateInstanceError!Instance {
|
||||
var instance: Instance = undefined;
|
||||
const result = self.dispatch.vkCreateInstance.?(
|
||||
&create_info,
|
||||
p_allocator,
|
||||
&instance,
|
||||
);
|
||||
switch (result) {
|
||||
.success => {},
|
||||
.error_out_of_host_memory => return error.OutOfHostMemory,
|
||||
.error_out_of_device_memory => return error.OutOfDeviceMemory,
|
||||
.error_initialization_failed => return error.InitializationFailed,
|
||||
.error_layer_not_present => return error.LayerNotPresent,
|
||||
.error_extension_not_present => return error.ExtensionNotPresent,
|
||||
.error_incompatible_driver => return error.IncompatibleDriver,
|
||||
else => return error.Unknown,
|
||||
}
|
||||
|
||||
...
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
};
|
||||
```
|
||||
Wrappers are generated according to the following rules:
|
||||
* The return type is determined from the original return type and the parameters.
|
||||
@@ -219,10 +200,12 @@ Wrappers are generated according to the following rules:
|
||||
* Error codes are translated into Zig errors.
|
||||
* As of yet, there is no specific handling of enumeration style commands or other commands which accept slices.
|
||||
|
||||
Furthermore, each wrapper contains a function to load each function pointer member when passed either `PfnGetInstanceProcAddr` or `PfnGetDeviceProcAddr`, which attempts to load each member as function pointer and casts it to the appropriate type. These functions are loaded literally, and any wrongly named member or member with a wrong function pointer type will result in problems.
|
||||
* For `BaseWrapper`, this function has signature `fn load(loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr` (with optionally having a different calling convention).
|
||||
* For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr`.
|
||||
* For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetDeviceProcAddr`.
|
||||
#### Initializing Wrappers
|
||||
|
||||
Wrapper types are initialized by the `load` function, which must be passed a _loader_: A function which loads a function pointer by name.
|
||||
* For `BaseWrapper`, this function has signature `fn load(loader: anytype) Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr` (with optionally having a different calling convention).
|
||||
* For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr`.
|
||||
* For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetDeviceProcAddr`.
|
||||
|
||||
Note that these functions accepts a loader with the signature of `anytype` instead of `PfnGetInstanceProcAddr`. This is because it is valid for `vkGetInstanceProcAddr` to load itself, in which case the returned function is to be called with the vulkan calling convention. This calling convention is not required for loading vulkan-zig itself, though, and a loader to be called with any calling convention with the target architecture may be passed in. This is particularly useful when interacting with C libraries that provide `vkGetInstanceProcAddr`.
|
||||
|
||||
@@ -238,32 +221,33 @@ fn customGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8)
|
||||
...
|
||||
}
|
||||
|
||||
// Both calls are valid, even
|
||||
const vkb = try BaseDispatch.load(glfwGetInstanceProcAddress);
|
||||
const vkb = try BaseDispatch.load(customGetInstanceProcAddress);
|
||||
// Both calls are valid.
|
||||
const vkb = BaseWrapper.load(glfwGetInstanceProcAddress);
|
||||
const vkb = BaseWrapper.load(customGetInstanceProcAddress);
|
||||
```
|
||||
|
||||
By default, wrapper `load` functions return `error.CommandLoadFailure` if a call to the loader resulted in `null`. If this behaviour is not desired, one can use `loadNoFail`. This function accepts the same parameters as `load`, but does not return an error any function pointer fails to load and sets its value to `undefined` instead. It is at the programmer's discretion not to invoke invalid functions, which can be tested for by checking whether the required core and extension versions the function requires are supported.
|
||||
The `load` function tries to load all function pointers unconditionally, regardless of enabled extensions or platform. If a function pointer could not be loaded, its entry in the dispatch table is set to `null`. When invoking a function on a wrapper table, the function pointer is checked for null, and there will be a crash or undefined behavior if it was not loaded properly. That means that **it is up to the programmer to ensure that a function pointer is valid for the platform before calling it**, either by checking whether the associated extension or Vulkan version is supported or simply by checking whether the function pointer is non-null.
|
||||
|
||||
One can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant(..)`.
|
||||
One can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant.?(..)`.
|
||||
|
||||
#### Proxying Wrappers
|
||||
|
||||
Proxying wrappers wrap a wrapper and a pointer to the associated handle in a single struct, and automatically passes this handle to commands as appropriate. Besides the proxying wrappers for instances and devices, there are also proxying wrappers for queues and command buffers. Proxying wrapper type are constructed in the same way as a regular wrapper, by passing an api specification to them. To initialize a proxying wrapper, it must be passed a handle and a pointer to an appropriate wrapper. For queue and command buffer proxying wrappers, a pointer to a device wrapper must be passed.
|
||||
|
||||
```zig
|
||||
// Create the dispatch tables
|
||||
const InstanceDispatch = vk.InstanceWrapper(apis);
|
||||
const Instance = vk.InstanceProxy(apis);
|
||||
const InstanceWrapper = vk.InstanceWrapper;
|
||||
const Instance = vk.InstanceProxy;
|
||||
|
||||
const instance_handle = try vkb.createInstance(...);
|
||||
const vki = try InstanceDispatch.load(instance_handle, vkb.vkGetInstanceProcAddr);
|
||||
const vki = try InstanceWrapper.load(instance_handle, vkb.dispatch.vkGetInstanceProcAddr.?);
|
||||
const instance = Instance.load(instance_handle, &vki);
|
||||
defer instance.destroyInstance(null);
|
||||
```
|
||||
|
||||
For queue and command buffer proxying wrappers, the `queue` and `cmd` prefix is removed for functions where appropriate. Note that the device proxying wrappers also have the queue and command buffer functions made available for convenience, but there the prefix is not stripped.
|
||||
|
||||
Note that the proxy must be passed a _pointer_ to a wrapper. This is because there was a limitation with LLVM in the past, where a struct with an object pointer and its associated function pointers wouldn't be optimized properly. By using a separate function pointer, LLVM knows that the "vtable" dispatch struct can never be modified and so it can subject each call to vtable optimizations.
|
||||
|
||||
### Bitflags
|
||||
|
||||
Packed structs of bools are used for bit flags in vulkan-zig, instead of both a `FlagBits` and `Flags` variant. Places where either of these variants are used are both replaced by this packed struct instead. This means that even in places where just one flag would normally be accepted, the packed struct is accepted. The programmer is responsible for only enabling a single bit.
|
||||
|
||||
@@ -5,38 +5,34 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const required_device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
|
||||
|
||||
/// To construct base, instance and device wrappers for vulkan-zig, you need to pass a list of 'apis' to it.
|
||||
const apis: []const vk.ApiInfo = &.{
|
||||
// You can either add invidiual functions by manually creating an 'api'
|
||||
.{
|
||||
.base_commands = .{
|
||||
.createInstance = true,
|
||||
},
|
||||
.instance_commands = .{
|
||||
.createDevice = true,
|
||||
},
|
||||
},
|
||||
// Or you can add entire feature sets or extensions
|
||||
vk.features.version_1_0,
|
||||
vk.extensions.khr_surface,
|
||||
vk.extensions.khr_swapchain,
|
||||
};
|
||||
/// There are 3 levels of bindings in vulkan-zig:
|
||||
/// - The Dispatch types (vk.BaseDispatch, vk.InstanceDispatch, vk.DeviceDispatch)
|
||||
/// are "plain" structs which just contain the function pointers for a particular
|
||||
/// object.
|
||||
/// - The Wrapper types (vk.Basewrapper, vk.InstanceWrapper, vk.DeviceWrapper) contains
|
||||
/// the Dispatch type, as well as Ziggified Vulkan functions - these return Zig errors,
|
||||
/// etc.
|
||||
/// - The Proxy types (vk.InstanceProxy, vk.DeviceProxy, vk.CommandBufferProxy,
|
||||
/// vk.QueueProxy) contain a pointer to a Wrapper and also contain the object's handle.
|
||||
/// Calling Ziggified functions on these types automatically passes the handle as
|
||||
/// the first parameter of each function. Note that this type accepts a pointer to
|
||||
/// a wrapper struct as there is a problem with LLVM where embedding function pointers
|
||||
/// and object pointer in the same struct leads to missed optimizations. If the wrapper
|
||||
/// member is a pointer, LLVM will try to optimize it as any other vtable.
|
||||
/// The wrappers contain
|
||||
const BaseWrapper = vk.BaseWrapper;
|
||||
const InstanceWrapper = vk.InstanceWrapper;
|
||||
const DeviceWrapper = vk.DeviceWrapper;
|
||||
|
||||
/// Next, pass the `apis` to the wrappers to create dispatch tables.
|
||||
const BaseDispatch = vk.BaseWrapper(apis);
|
||||
const InstanceDispatch = vk.InstanceWrapper(apis);
|
||||
const DeviceDispatch = vk.DeviceWrapper(apis);
|
||||
|
||||
// Also create some proxying wrappers, which also have the respective handles
|
||||
const Instance = vk.InstanceProxy(apis);
|
||||
const Device = vk.DeviceProxy(apis);
|
||||
const Instance = vk.InstanceProxy;
|
||||
const Device = vk.DeviceProxy;
|
||||
|
||||
pub const GraphicsContext = struct {
|
||||
pub const CommandBuffer = vk.CommandBufferProxy(apis);
|
||||
pub const CommandBuffer = vk.CommandBufferProxy;
|
||||
|
||||
allocator: Allocator,
|
||||
|
||||
vkb: BaseDispatch,
|
||||
vkb: BaseWrapper,
|
||||
|
||||
instance: Instance,
|
||||
surface: vk.SurfaceKHR,
|
||||
@@ -51,7 +47,7 @@ pub const GraphicsContext = struct {
|
||||
pub fn init(allocator: Allocator, app_name: [*:0]const u8, window: *c.GLFWwindow) !GraphicsContext {
|
||||
var self: GraphicsContext = undefined;
|
||||
self.allocator = allocator;
|
||||
self.vkb = try BaseDispatch.load(c.glfwGetInstanceProcAddress);
|
||||
self.vkb = BaseWrapper.load(c.glfwGetInstanceProcAddress);
|
||||
|
||||
var glfw_exts_count: u32 = 0;
|
||||
const glfw_exts = c.glfwGetRequiredInstanceExtensions(&glfw_exts_count);
|
||||
@@ -70,9 +66,9 @@ pub const GraphicsContext = struct {
|
||||
.pp_enabled_extension_names = @ptrCast(glfw_exts),
|
||||
}, null);
|
||||
|
||||
const vki = try allocator.create(InstanceDispatch);
|
||||
const vki = try allocator.create(InstanceWrapper);
|
||||
errdefer allocator.destroy(vki);
|
||||
vki.* = try InstanceDispatch.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr);
|
||||
vki.* = InstanceWrapper.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr.?);
|
||||
self.instance = Instance.init(instance, vki);
|
||||
errdefer self.instance.destroyInstance(null);
|
||||
|
||||
@@ -85,9 +81,9 @@ pub const GraphicsContext = struct {
|
||||
|
||||
const dev = try initializeCandidate(self.instance, candidate);
|
||||
|
||||
const vkd = try allocator.create(DeviceDispatch);
|
||||
const vkd = try allocator.create(DeviceWrapper);
|
||||
errdefer allocator.destroy(vkd);
|
||||
vkd.* = try DeviceDispatch.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr);
|
||||
vkd.* = DeviceWrapper.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr.?);
|
||||
self.dev = Device.init(dev, vkd);
|
||||
errdefer self.dev.destroyDevice(null);
|
||||
|
||||
|
||||
@@ -93,9 +93,6 @@ const preamble =
|
||||
\\pub const ApiInfo = struct {
|
||||
\\ name: [:0]const u8 = "custom",
|
||||
\\ version: Version = makeApiVersion(0, 0, 0, 0),
|
||||
\\ base_commands: BaseCommandFlags = .{},
|
||||
\\ instance_commands: InstanceCommandFlags = .{},
|
||||
\\ device_commands: DeviceCommandFlags = .{},
|
||||
\\};
|
||||
;
|
||||
|
||||
@@ -208,6 +205,14 @@ const foreign_types = std.StaticStringMap([]const u8).initComptime(.{
|
||||
.{ "_screen_window", "opaque {}" },
|
||||
.{ "IDirectFB", "opaque {}" },
|
||||
.{ "IDirectFBSurface", "opaque {}" },
|
||||
.{ "NvSciSyncAttrList", "*opaque{}" },
|
||||
.{ "NvSciSyncObj", "*opaque{}" },
|
||||
.{ "NvSciSyncFence", "*opaque{}" },
|
||||
.{ "NvSciBufAttrList", "*opaque{}" },
|
||||
.{ "NvSciBufObj", "*opaque{}" },
|
||||
// We don't know the true size of these but whatever Stadia is dead anyway.
|
||||
.{ "GgpStreamDescriptor", "*opaque{}" },
|
||||
.{ "GgpFrameToken", "*opaque{}" },
|
||||
});
|
||||
|
||||
const CommandDispatchType = enum {
|
||||
@@ -642,6 +647,7 @@ fn Renderer(comptime WriterType: type) type {
|
||||
try self.renderCommandPtrs();
|
||||
try self.renderFeatureInfo();
|
||||
try self.renderExtensionInfo();
|
||||
try self.renderDispatchTables();
|
||||
try self.renderWrappers();
|
||||
try self.renderProxies();
|
||||
}
|
||||
@@ -1349,63 +1355,15 @@ fn Renderer(comptime WriterType: type) type {
|
||||
\\pub const features = struct {
|
||||
\\
|
||||
);
|
||||
// The commands in a feature level are not pre-sorted based on if they are instance or device functions.
|
||||
var base_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer base_commands.deinit();
|
||||
var instance_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer instance_commands.deinit();
|
||||
var device_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer device_commands.deinit();
|
||||
for (self.registry.features) |feature| {
|
||||
base_commands.clearRetainingCapacity();
|
||||
instance_commands.clearRetainingCapacity();
|
||||
device_commands.clearRetainingCapacity();
|
||||
|
||||
try self.writer.writeAll("pub const ");
|
||||
try self.writeIdentifierWithCase(.snake, trimVkNamespace(feature.name));
|
||||
try self.writer.writeAll("= ApiInfo {\n");
|
||||
try self.writer.print(".name = \"{s}\", .version = makeApiVersion(0, {}, {}, 0),", .{
|
||||
try self.writer.print(".name = \"{s}\", .version = makeApiVersion(0, {}, {}, 0),\n}};\n", .{
|
||||
trimVkNamespace(feature.name),
|
||||
feature.level.major,
|
||||
feature.level.minor,
|
||||
});
|
||||
// collect feature information
|
||||
for (feature.requires) |require| {
|
||||
for (require.commands) |command_name| {
|
||||
const decl = self.resolveDeclaration(command_name) orelse continue;
|
||||
// If the target type does not exist, it was likely an empty enum -
|
||||
// assume spec is correct and that this was not a function alias.
|
||||
const decl_type = self.resolveAlias(decl) catch continue;
|
||||
const command = switch (decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
const class = classifyCommandDispatch(command_name, command);
|
||||
switch (class) {
|
||||
.base => try base_commands.put(command_name, {}),
|
||||
.instance => try instance_commands.put(command_name, {}),
|
||||
.device => try device_commands.put(command_name, {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
// and write them out
|
||||
// clear command lists for next iteration
|
||||
if (base_commands.count() != 0) {
|
||||
try self.writer.writeAll(".base_commands = ");
|
||||
try self.renderCommandFlags(base_commands.keys());
|
||||
}
|
||||
|
||||
if (instance_commands.count() != 0) {
|
||||
try self.writer.writeAll(".instance_commands = ");
|
||||
try self.renderCommandFlags(instance_commands.keys());
|
||||
}
|
||||
|
||||
if (device_commands.count() != 0) {
|
||||
try self.writer.writeAll(".device_commands = ");
|
||||
try self.renderCommandFlags(device_commands.keys());
|
||||
}
|
||||
|
||||
try self.writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
try self.writer.writeAll("};\n");
|
||||
@@ -1416,18 +1374,7 @@ fn Renderer(comptime WriterType: type) type {
|
||||
\\pub const extensions = struct {
|
||||
\\
|
||||
);
|
||||
// The commands in an extension are not pre-sorted based on if they are instance or device functions.
|
||||
var base_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer base_commands.deinit();
|
||||
var instance_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer instance_commands.deinit();
|
||||
var device_commands = std.StringArrayHashMap(void).init(self.allocator);
|
||||
defer device_commands.deinit();
|
||||
for (self.registry.extensions) |ext| {
|
||||
base_commands.clearRetainingCapacity();
|
||||
instance_commands.clearRetainingCapacity();
|
||||
device_commands.clearRetainingCapacity();
|
||||
|
||||
try self.writer.writeAll("pub const ");
|
||||
if (ext.extension_type == .video) {
|
||||
// These are already in the right form, and the auto-casing style transformer
|
||||
@@ -1445,55 +1392,41 @@ fn Renderer(comptime WriterType: type) type {
|
||||
.alias => |alias| try self.renderName(alias),
|
||||
.unknown => try self.writer.writeAll("makeApiVersion(0, 0, 0, 0)"),
|
||||
}
|
||||
try self.writer.writeByte(',');
|
||||
// collect extension functions
|
||||
for (ext.requires) |require| {
|
||||
for (require.commands) |command_name| {
|
||||
const decl = self.resolveDeclaration(command_name) orelse continue;
|
||||
// If the target type does not exist, it was likely an empty enum -
|
||||
// assume spec is correct and that this was not a function alias.
|
||||
const decl_type = self.resolveAlias(decl) catch continue;
|
||||
const command = switch (decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
const class = classifyCommandDispatch(command_name, command);
|
||||
switch (class) {
|
||||
.base => try base_commands.put(command_name, {}),
|
||||
.instance => try instance_commands.put(command_name, {}),
|
||||
.device => try device_commands.put(command_name, {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
// and write them out
|
||||
if (base_commands.count() != 0) {
|
||||
try self.writer.writeAll(".base_commands = ");
|
||||
try self.renderCommandFlags(base_commands.keys());
|
||||
}
|
||||
|
||||
if (instance_commands.count() != 0) {
|
||||
try self.writer.writeAll(".instance_commands = ");
|
||||
try self.renderCommandFlags(instance_commands.keys());
|
||||
}
|
||||
|
||||
if (device_commands.count() != 0) {
|
||||
try self.writer.writeAll(".device_commands = ");
|
||||
try self.renderCommandFlags(device_commands.keys());
|
||||
}
|
||||
|
||||
try self.writer.writeAll("};\n");
|
||||
try self.writer.writeAll(",};\n");
|
||||
}
|
||||
try self.writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
fn renderCommandFlags(self: *Self, commands: []const []const u8) !void {
|
||||
try self.writer.writeAll(".{\n");
|
||||
for (commands) |command_name| {
|
||||
try self.writer.writeAll(".");
|
||||
try self.writeIdentifierWithCase(.camel, trimVkNamespace(command_name));
|
||||
try self.writer.writeAll(" = true, \n");
|
||||
fn renderDispatchTables(self: *Self) !void {
|
||||
try self.renderDispatchTable(.base);
|
||||
try self.renderDispatchTable(.instance);
|
||||
try self.renderDispatchTable(.device);
|
||||
}
|
||||
|
||||
fn renderDispatchTable(self: *Self, dispatch_type: CommandDispatchType) !void {
|
||||
try self.writer.print(
|
||||
"pub const {s}Dispatch = struct {{\n",
|
||||
.{dispatch_type.name()},
|
||||
);
|
||||
|
||||
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| {
|
||||
const final_decl_type = self.resolveAlias(decl_type) catch continue;
|
||||
const command = switch (final_decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
if (classifyCommandDispatch(name, command) != dispatch_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try self.writeIdentifier(name);
|
||||
try self.writer.writeAll(": ?");
|
||||
try self.renderCommandPtrName(name);
|
||||
try self.writer.writeAll(" = null,\n");
|
||||
}
|
||||
try self.writer.writeAll("},\n");
|
||||
|
||||
try self.writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
fn renderWrappers(self: *Self) !void {
|
||||
@@ -1505,130 +1438,16 @@ fn Renderer(comptime WriterType: type) type {
|
||||
|
||||
fn renderWrappersOfDispatchType(self: *Self, dispatch_type: CommandDispatchType) !void {
|
||||
const name = dispatch_type.name();
|
||||
const name_lower = dispatch_type.nameLower();
|
||||
|
||||
try self.writer.print(
|
||||
\\pub const {0s}CommandFlags = packed struct {{
|
||||
\\pub const {0s}Wrapper = struct {{
|
||||
\\ const Self = @This();
|
||||
\\ pub const Dispatch = {0s}Dispatch;
|
||||
\\
|
||||
\\ dispatch: Dispatch,
|
||||
\\
|
||||
\\
|
||||
, .{name});
|
||||
for (self.registry.decls) |decl| {
|
||||
// If the target type does not exist, it was likely an empty enum -
|
||||
// assume spec is correct and that this was not a function alias.
|
||||
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
|
||||
const command = switch (decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
|
||||
try self.writer.writeAll(" ");
|
||||
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
|
||||
try self.writer.writeAll(": bool = false,\n");
|
||||
}
|
||||
}
|
||||
|
||||
try self.writer.print(
|
||||
\\pub fn CmdType(comptime tag: std.meta.FieldEnum({0s}CommandFlags)) type {{
|
||||
\\ return switch (tag) {{
|
||||
\\
|
||||
, .{name});
|
||||
for (self.registry.decls) |decl| {
|
||||
// If the target type does not exist, it was likely an empty enum -
|
||||
// assume spec is correct and that this was not a function alias.
|
||||
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
|
||||
const command = switch (decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
|
||||
try self.writer.writeByte('.');
|
||||
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
|
||||
try self.writer.writeAll(" => ");
|
||||
try self.renderCommandPtrName(decl.name);
|
||||
try self.writer.writeAll(",\n");
|
||||
}
|
||||
}
|
||||
try self.writer.writeAll(" };\n}");
|
||||
|
||||
try self.writer.print(
|
||||
\\pub fn cmdName(tag: std.meta.FieldEnum({0s}CommandFlags)) [:0]const u8 {{
|
||||
\\ return switch(tag) {{
|
||||
\\
|
||||
, .{name});
|
||||
for (self.registry.decls) |decl| {
|
||||
// If the target type does not exist, it was likely an empty enum -
|
||||
// assume spec is correct and that this was not a function alias.
|
||||
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
|
||||
const command = switch (decl_type) {
|
||||
.command => |cmd| cmd,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
|
||||
try self.writer.writeByte('.');
|
||||
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
|
||||
try self.writer.print(
|
||||
\\ => "{s}",
|
||||
\\
|
||||
, .{decl.name});
|
||||
}
|
||||
}
|
||||
try self.writer.writeAll(" };\n}");
|
||||
|
||||
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,
|
||||
\\
|
||||
\\ const Self = @This();
|
||||
\\ pub const commands = blk: {{
|
||||
\\ var cmds: {0s}CommandFlags = .{{}};
|
||||
\\ for (apis) |api| {{
|
||||
\\ cmds = cmds.merge(api.{1s}_commands);
|
||||
\\ }}
|
||||
\\ break :blk cmds;
|
||||
\\ }};
|
||||
\\ pub const Dispatch = blk: {{
|
||||
\\ @setEvalBranchQuota(1_000_000);
|
||||
\\ const Type = std.builtin.Type;
|
||||
\\ const fields_len = fields_len: {{
|
||||
\\ var fields_len: u32 = 0;
|
||||
\\ for (@typeInfo({0s}CommandFlags).@"struct".fields) |field| {{
|
||||
\\ fields_len += @intCast(@intFromBool(@field(commands, field.name)));
|
||||
\\ }}
|
||||
\\ break :fields_len fields_len;
|
||||
\\ }};
|
||||
\\ var fields: [fields_len]Type.StructField = undefined;
|
||||
\\ var i: usize = 0;
|
||||
\\ for (@typeInfo({0s}CommandFlags).@"struct".fields) |field| {{
|
||||
\\ if (@field(commands, field.name)) {{
|
||||
\\ const field_tag = std.enums.nameCast(std.meta.FieldEnum({0s}CommandFlags), field.name);
|
||||
\\ const PfnType = {0s}CommandFlags.CmdType(field_tag);
|
||||
\\ fields[i] = .{{
|
||||
\\ .name = {0s}CommandFlags.cmdName(field_tag),
|
||||
\\ .type = PfnType,
|
||||
\\ .default_value_ptr = null,
|
||||
\\ .is_comptime = false,
|
||||
\\ .alignment = @alignOf(PfnType),
|
||||
\\ }};
|
||||
\\ i += 1;
|
||||
\\ }}
|
||||
\\ }}
|
||||
\\ break :blk @Type(.{{
|
||||
\\ .@"struct" = .{{
|
||||
\\ .layout = .auto,
|
||||
\\ .fields = &fields,
|
||||
\\ .decls = &[_]std.builtin.Type.Declaration{{}},
|
||||
\\ .is_tuple = false,
|
||||
\\ }},
|
||||
\\ }});
|
||||
\\ }};
|
||||
\\
|
||||
, .{ name, name_lower });
|
||||
|
||||
try self.renderWrapperLoader(dispatch_type);
|
||||
|
||||
@@ -1654,7 +1473,7 @@ fn Renderer(comptime WriterType: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
try self.writer.writeAll("};}\n");
|
||||
try self.writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
fn renderWrapperLoader(self: *Self, dispatch_type: CommandDispatchType) !void {
|
||||
@@ -1673,21 +1492,8 @@ fn Renderer(comptime WriterType: type) type {
|
||||
@setEvalBranchQuota(2000);
|
||||
|
||||
try self.writer.print(
|
||||
\\pub fn load({[params]s}) error{{CommandLoadFailure}}!Self {{
|
||||
\\ var self: Self = undefined;
|
||||
\\ inline for (std.meta.fields(Dispatch)) |field| {{
|
||||
\\ const name: [*:0]const u8 = @ptrCast(field.name ++ "\x00");
|
||||
\\ if (loader({[first_arg]s}, name)) |cmd_ptr| {{
|
||||
\\ @field(self.dispatch, field.name) = @ptrCast(cmd_ptr);
|
||||
\\ }} else {{
|
||||
\\ std.log.err("Command loading failed for \"{{s}}\".", .{{field.name}});
|
||||
\\ return error.CommandLoadFailure;
|
||||
\\ }}
|
||||
\\ }}
|
||||
\\ return self;
|
||||
\\}}
|
||||
\\pub fn loadNoFail({[params]s}) Self {{
|
||||
\\ var self: Self = undefined;
|
||||
\\pub fn load({[params]s}) Self {{
|
||||
\\ var self: Self = .{{ .dispatch = .{{}} }};
|
||||
\\ inline for (std.meta.fields(Dispatch)) |field| {{
|
||||
\\ const name: [*:0]const u8 = @ptrCast(field.name ++ "\x00");
|
||||
\\ const cmd_ptr = loader({[first_arg]s}, name) orelse undefined;
|
||||
@@ -1714,21 +1520,22 @@ fn Renderer(comptime WriterType: type) type {
|
||||
const loader_name = dispatch_type.name();
|
||||
|
||||
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);
|
||||
\\pub const {0s}Proxy = struct {{
|
||||
\\ const Self = @This();
|
||||
\\ pub const Wrapper = {1s}Wrapper;
|
||||
\\
|
||||
\\ handle: {0s},
|
||||
\\ wrapper: *const Wrapper,
|
||||
\\ handle: {0s},
|
||||
// Note: This is a pointer because in the past there were some performance
|
||||
// issues with putting an object and vtable in the same structure. This also
|
||||
// affected std.mem.Allocator, which is why its like that too.
|
||||
\\ wrapper: *const Wrapper,
|
||||
\\
|
||||
\\ pub fn init(handle: {0s}, wrapper: *const Wrapper) Self {{
|
||||
\\ return .{{
|
||||
\\ .handle = handle,
|
||||
\\ .wrapper = wrapper,
|
||||
\\ }};
|
||||
\\ }}
|
||||
\\ pub fn init(handle: {0s}, wrapper: *const Wrapper) Self {{
|
||||
\\ return .{{
|
||||
\\ .handle = handle,
|
||||
\\ .wrapper = wrapper,
|
||||
\\ }};
|
||||
\\ }}
|
||||
, .{ trimVkNamespace(dispatch_handle), loader_name });
|
||||
|
||||
for (self.registry.decls) |decl| {
|
||||
@@ -1764,8 +1571,8 @@ fn Renderer(comptime WriterType: type) type {
|
||||
}
|
||||
|
||||
try self.writer.writeAll(
|
||||
\\ };
|
||||
\\}
|
||||
\\};
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1841,11 +1648,13 @@ fn Renderer(comptime WriterType: type) type {
|
||||
const params = command.params[0 .. command.params.len - 2];
|
||||
const data_type = try getEnumerateFunctionDataType(command);
|
||||
|
||||
try self.writer.writeAll("pub const ");
|
||||
try self.renderErrorSetName(name);
|
||||
try self.writer.writeAll(" = Wrapper.");
|
||||
try self.renderErrorSetName(name);
|
||||
try self.writer.writeAll(";\n");
|
||||
if (returns_vk_result) {
|
||||
try self.writer.writeAll("pub const ");
|
||||
try self.renderErrorSetName(name);
|
||||
try self.writer.writeAll(" = Wrapper.");
|
||||
try self.renderErrorSetName(name);
|
||||
try self.writer.writeAll(";\n");
|
||||
}
|
||||
|
||||
try self.renderAllocWrapperPrototype(name, params, returns_vk_result, data_type, dispatch_handle, .proxy);
|
||||
try self.writer.writeAll(
|
||||
@@ -1974,7 +1783,7 @@ fn Renderer(comptime WriterType: type) type {
|
||||
) !void {
|
||||
try self.writer.writeAll("self.dispatch.");
|
||||
try self.writeIdentifier(name);
|
||||
try self.writer.writeAll("(");
|
||||
try self.writer.writeAll(".?(");
|
||||
|
||||
for (command.params) |param| {
|
||||
switch (try self.classifyParam(param)) {
|
||||
|
||||
Reference in New Issue
Block a user