Remove old au work

This commit is contained in:
2024-11-24 22:25:28 -05:00
parent ccee3733b0
commit f6969589b7
5 changed files with 2 additions and 741 deletions

View File

@@ -1,5 +1,4 @@
const std = @import("std");
const au = @import("au.zig");
const vk = @import("vk");
const shaders = @import("shaders");

View File

@@ -1,395 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const vk = @import("vk");
const nu = @import("../../nu.zig");
pub const SwapChain = @import("au/SwapChain.zig");
pub const Flights = @import("au/Flights.zig");
pub const VkAllocator = @import("au/VkAllocator.zig");
const config = nu.config.render;
pub const apis: []const vk.ApiInfo = &.{
vk.features.version_1_0,
vk.features.version_1_1,
vk.features.version_1_2,
vk.features.version_1_3,
vk.extensions.khr_surface,
vk.extensions.khr_swapchain,
vk.extensions.khr_dynamic_rendering,
if (config.use_debug_messenger) vk.extensions.ext_debug_utils else .{},
};
pub const device_extensions: []const [*:0]const u8 = &.{
// todo somehow sync this with APIs above?
vk.extensions.khr_swapchain.name,
vk.extensions.khr_dynamic_rendering.name,
};
pub const BaseWrapper = vk.BaseWrapper(apis);
pub const InstanceWrapper = vk.InstanceWrapper(apis);
pub const DeviceWrapper = vk.DeviceWrapper(apis);
pub const InstanceProxy = vk.InstanceProxy(apis);
pub const DeviceProxy = vk.DeviceProxy(apis);
pub const QueueProxy = vk.QueueProxy(apis);
pub const CommandBufferProxy = vk.CommandBufferProxy(apis);
pub const B: *const BaseWrapper = &_bw;
pub const I: *const InstanceProxy = &_ip;
pub const D: *const DeviceProxy = &_dp;
pub const Q: *const QueueProxy = &_qp;
pub const S: *const vk.SurfaceKHR = &_surface;
pub const device_config: *const CandidateDeviceInfo = &_dconfig;
var _bw: BaseWrapper = undefined;
var _iw: InstanceWrapper = undefined;
var _dw: DeviceWrapper = undefined;
var _ip: InstanceProxy = undefined;
var _dp: DeviceProxy = undefined;
var _qp: QueueProxy = undefined;
var _instance: vk.Instance = undefined;
var _surface: vk.SurfaceKHR = undefined;
var _device: vk.Device = undefined;
var _dconfig: CandidateDeviceInfo = undefined;
var _queue: vk.Queue = undefined;
pub fn init(alloc: std.mem.Allocator) !void {
try init_base();
errdefer deinit_base();
try init_instance(alloc);
errdefer deinit_instance();
try init_device(alloc);
errdefer deinit_device();
}
pub fn deinit() void {
deinit_device();
deinit_instance();
deinit_base();
}
fn init_base() !void {
if (glfwVulkanSupported() != nu.Window.c.GLFW_TRUE)
return error.glfwNoVulkan;
if (config.use_debug_messenger) {
_bw = try BaseWrapper.load(glfwGetInstanceProcAddress);
} else {
_bw = BaseWrapper.loadNoFail(glfwGetInstanceProcAddress);
}
}
fn deinit_base() void {}
fn init_instance(alloc: std.mem.Allocator) !void {
var extensions = std.ArrayList([*:0]const u8).init(alloc);
defer extensions.deinit();
var layers = std.ArrayList([*:0]const u8).init(alloc);
defer layers.deinit();
if (config.use_debug_messenger) {
try extensions.appendSlice(&.{
vk.extensions.ext_debug_utils.name,
});
try layers.appendSlice(&.{
"VK_LAYER_KHRONOS_validation",
});
}
var glfw_exts_count: u32 = 0;
const glfw_exts: [*]const [*:0]const u8 =
@ptrCast(glfwGetRequiredInstanceExtensions(&glfw_exts_count));
try extensions.appendSlice(glfw_exts[0..glfw_exts_count]);
const mci: vk.DebugUtilsMessengerCreateInfoEXT = .{
.message_severity = .{
.error_bit_ext = true,
.info_bit_ext = true,
.verbose_bit_ext = true,
.warning_bit_ext = true,
},
.message_type = .{
.device_address_binding_bit_ext = true,
.general_bit_ext = false,
.performance_bit_ext = true,
.validation_bit_ext = true,
},
.pfn_user_callback = &debug_callback,
.p_user_data = null,
};
_instance = try B.createInstance(&.{
.p_application_info = &.{
.p_application_name = config.app_name,
.application_version = vk.makeApiVersion(
config.app_version.variant,
config.app_version.major,
config.app_version.minor,
config.app_version.patch,
),
.p_engine_name = config.engine_name,
.engine_version = vk.makeApiVersion(
config.engine_version.variant,
config.engine_version.major,
config.engine_version.minor,
config.engine_version.patch,
),
.api_version = vk.API_VERSION_1_3,
},
.enabled_extension_count = @intCast(extensions.items.len),
.pp_enabled_extension_names = extensions.items.ptr,
.enabled_layer_count = @intCast(layers.items.len),
.pp_enabled_layer_names = layers.items.ptr,
.p_next = if (config.use_debug_messenger) &mci else null,
}, null);
if (config.use_debug_messenger) {
_iw = try InstanceWrapper.load(_instance, _bw.dispatch.vkGetInstanceProcAddr);
} else {
_iw = InstanceWrapper.loadNoFail(_instance, _bw.dispatch.vkGetInstanceProcAddr);
}
_ip = InstanceProxy.init(_instance, &_iw);
if (glfwCreateWindowSurface(_instance, nu.Window.handle, null, &_surface) != .success) {
return error.glfwCreateWindowSurfaceFailed;
}
}
fn deinit_instance() void {
_ip.destroySurfaceKHR(_surface, null);
_ip.destroyInstance(null);
}
const CandidateDeviceInfo = struct {
pdev: vk.PhysicalDevice,
format: vk.SurfaceFormatKHR,
mode: vk.PresentModeKHR,
family: u32, // must support graphics and present for now
fn init(alloc: std.mem.Allocator, pdev: vk.PhysicalDevice) !struct { i32, CandidateDeviceInfo } {
var score: i32 = 0;
var res: CandidateDeviceInfo = undefined;
res.pdev = pdev;
const props = I.getPhysicalDeviceProperties(pdev);
score += switch (props.device_type) {
vk.PhysicalDeviceType.discrete_gpu => 1000,
vk.PhysicalDeviceType.integrated_gpu => 500,
else => 0,
};
var format_count: u32 = undefined;
_ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, _surface, &format_count, null);
if (format_count == 0) return error.NoSurfaceFormats;
const formats = try alloc.alloc(vk.SurfaceFormatKHR, format_count);
defer alloc.free(formats);
_ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, _surface, &format_count, formats.ptr);
for (formats) |fmt| {
if (fmt.color_space == .srgb_nonlinear_khr) {
res.format = fmt;
break;
}
} else {
res.format = formats[0];
score -= 100;
}
var mode_count: u32 = undefined;
_ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, _surface, &mode_count, null);
if (mode_count == 0) return error.NoSurfacePresentModes;
const modes = try alloc.alloc(vk.PresentModeKHR, mode_count);
defer alloc.free(modes);
_ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, _surface, &mode_count, modes.ptr);
if (std.mem.indexOfAny(vk.PresentModeKHR, modes, &.{
vk.PresentModeKHR.mailbox_khr,
vk.PresentModeKHR.immediate_khr,
})) |idx| {
res.mode = modes[idx];
} else {
score -= 50;
res.mode = .fifo_khr; // this is guaranteed
}
var ext_count: u32 = undefined;
_ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, null);
const exts = try alloc.alloc(vk.ExtensionProperties, ext_count);
defer alloc.free(exts);
_ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, exts.ptr);
for (device_extensions) |needle| {
for (exts) |ext| {
if (std.mem.eql(
u8,
std.mem.span(needle),
std.mem.sliceTo(&ext.extension_name, 0),
))
break;
} else {
return error.MissingDeviceExtension;
}
}
var family_count: u32 = undefined;
I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, null);
const families = try alloc.alloc(vk.QueueFamilyProperties, family_count);
defer alloc.free(families);
I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, families.ptr);
for (families, 0..) |prop, idx| {
const graphics_support = prop.queue_flags.graphics_bit;
const present_support = try I.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(idx), _surface) == vk.TRUE;
if (graphics_support and present_support) {
res.family = @intCast(idx);
break;
}
} else {
return error.NoSuitableFamily;
}
return .{ score, res };
}
};
fn init_device(alloc: std.mem.Allocator) !void {
var pdev_count: u32 = undefined;
_ = try I.enumeratePhysicalDevices(&pdev_count, null);
if (pdev_count == 0) return error.NoDevice;
const pdevs = try alloc.alloc(vk.PhysicalDevice, pdev_count);
defer alloc.free(pdevs);
_ = try I.enumeratePhysicalDevices(&pdev_count, pdevs.ptr);
// const scores = std.ArrayList(i32).
var scores: std.MultiArrayList(struct { score: i32, ci: CandidateDeviceInfo }) = .{};
defer scores.deinit(alloc);
for (pdevs) |pdev| {
const score, const ci = CandidateDeviceInfo.init(alloc, pdev) catch continue;
try scores.append(alloc, .{ .score = score, .ci = ci });
}
const idx = std.sort.argMax(i32, scores.items(.score), {}, std.sort.asc(i32)) orelse
return error.NoSuitableDevice;
_dconfig = scores.get(idx).ci;
const qci: []const vk.DeviceQueueCreateInfo = &.{
vk.DeviceQueueCreateInfo{
.queue_family_index = _dconfig.family,
.queue_count = 1,
.p_queue_priorities = &[_]f32{1.0},
},
};
_device = try I.createDevice(_dconfig.pdev, &.{
.queue_create_info_count = @intCast(qci.len),
.p_queue_create_infos = qci.ptr,
.enabled_extension_count = @intCast(device_extensions.len),
.pp_enabled_extension_names = device_extensions.ptr,
.p_next = &vk.PhysicalDeviceDynamicRenderingFeaturesKHR{
.dynamic_rendering = vk.TRUE,
},
}, null);
if (config.use_debug_messenger) {
_dw = try DeviceWrapper.load(_device, _iw.dispatch.vkGetDeviceProcAddr);
} else {
_dw = DeviceWrapper.loadNoFail(_device, _iw.dispatch.vkGetDeviceProcAddr);
}
_dp = DeviceProxy.init(_device, &_dw);
errdefer D.destroyDevice(null);
_queue = D.getDeviceQueue(_dconfig.family, 0);
_qp = QueueProxy.init(_queue, &_dw);
// todo i'm thinking this needs to be a more complex pointer structure... i'm making assumptions here about how the
// command pools are meant to work. probably I am cooking too much.
}
fn deinit_device() void {
D.destroyDevice(null);
}
pub fn debug_callback(
msg_severity: vk.DebugUtilsMessageSeverityFlagsEXT,
msg_type: vk.DebugUtilsMessageTypeFlagsEXT,
p_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT,
_: ?*anyopaque,
) callconv(vk.vulkan_call_conv) vk.Bool32 {
// ripped from std.log.defaultLog
const data = p_data orelse return vk.FALSE;
const message = data.p_message orelse return vk.FALSE;
const severity_prefix = if (msg_severity.verbose_bit_ext)
"verbose:"
else if (msg_severity.info_bit_ext)
"info:"
else if (msg_severity.warning_bit_ext)
"warning:"
else if (msg_severity.error_bit_ext)
"error:"
else
"?:";
const type_prefix = if (msg_type.general_bit_ext)
""
else if (msg_type.validation_bit_ext)
"validation:"
else if (msg_type.performance_bit_ext)
"performance:"
else if (msg_type.device_address_binding_bit_ext)
"device_address_binding:"
else
"?:";
const stderr = std.io.getStdErr().writer();
var bw = std.io.bufferedWriter(stderr);
const writer = bw.writer();
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
nosuspend {
writer.print("vk-{s}{s} {s}\n", .{ severity_prefix, type_prefix, message }) catch return vk.FALSE;
bw.flush() catch return vk.FALSE;
}
return vk.FALSE;
}
pub extern fn glfwVulkanSupported() c_int;
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: *nu.Window.c.GLFWwindow,
allocation_callbacks: ?*const vk.AllocationCallbacks,
surface: *vk.SurfaceKHR,
) vk.Result;
pub extern fn glfwGetRequiredInstanceExtensions(
count: *u32,
) [*][*:0]const u8;

View File

@@ -1,62 +0,0 @@
const std = @import("std");
const vk = @import("vk");
const au = @import("../au.zig");
const Self = @This();
pub const Flight = struct {
acquire: vk.Semaphore = .null_handle,
complete: vk.Semaphore = .null_handle,
fence: vk.Fence = .null_handle,
pool: vk.CommandPool = .null_handle,
cmd: vk.CommandBuffer = .null_handle,
pub fn wait(self: Flight) !void {
_ = try au.D.waitForFences(1, &.{self.fence}, vk.TRUE, std.math.maxInt(u64));
try au.D.resetFences(1, &.{self.fence});
}
};
alloc: std.mem.Allocator,
flights: []Flight,
idx: usize,
pub fn init(alloc: std.mem.Allocator, n: usize) !Self {
var self: Self = .{
.alloc = alloc,
.flights = try alloc.alloc(Flight, n),
.idx = 0,
};
errdefer self.deinit();
for (self.flights) |*flight| {
flight.acquire = try au.D.createSemaphore(&.{}, null);
flight.complete = try au.D.createSemaphore(&.{}, null);
flight.fence = try au.D.createFence(&.{ .flags = .{ .signaled_bit = true } }, null);
flight.pool = try au.D.createCommandPool(&.{ .queue_family_index = au.device_config.family }, null);
try au.D.allocateCommandBuffers(&vk.CommandBufferAllocateInfo{
.command_buffer_count = 1,
.command_pool = flight.pool,
.level = .primary,
}, @ptrCast(&flight.cmd));
}
return self;
}
pub fn deinit(self: Self) void {
for (self.flights) |flight| {
au.D.destroySemaphore(flight.acquire, null);
au.D.destroySemaphore(flight.complete, null);
au.D.destroyFence(flight.fence, null);
au.D.freeCommandBuffers(flight.pool, 1, &.{flight.cmd});
au.D.destroyCommandPool(flight.pool, null);
}
self.alloc.free(self.flights);
}
pub fn next(self: *Self) Flight {
const idx = self.idx;
self.idx = (self.idx + 1) % self.flights.len;
return self.flights[idx];
}

View File

@@ -1,3 +1,5 @@
// todo look into Vulkan Memory Allocator
const std = @import("std");
const vk = @import("vk");
const au = @import("../au.zig");

View File

@@ -1,283 +0,0 @@
const std = @import("std");
const vk = @import("vk");
const ctx = @import("ctx.zig");
fn _choose_format(alloc: std.mem.Allocator) !vk.SurfaceFormatKHR {
var count: u32 = undefined;
std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR(
ctx.pdevice.*,
ctx.surface.*,
&count,
null,
));
const formats = try alloc.alloc(vk.SurfaceFormatKHR, count);
defer alloc.free(formats);
std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR(
ctx.pdevice.*,
ctx.surface.*,
&count,
formats.ptr,
));
for (formats) |format| {
if (format.color_space == .srgb_nonlinear_khr) return format;
} else {
return formats[0];
}
}
fn _choose_mode(alloc: std.mem.Allocator) !vk.PresentModeKHR {
_ = ctx;
_ = alloc;
return .fifo_khr;
}
pub fn SwapChain(F: type) type {
return struct {
const Self = @This();
pub const Target = struct {
image_index: u32,
flight_index: u32,
image: vk.Image,
view: vk.ImageView,
flight: *F,
acquired: vk.Semaphore, // this semaphore will be signaled when the target is acquired
complete: vk.Semaphore, // this semaphore should be signaled when the render is complete
available: vk.Fence, // this fence should be signaled when the target flight is available
};
alloc: std.mem.Allocator,
cur: u8 = 0,
flights: []F,
acquired_sems: []vk.Semaphore,
complete_sems: []vk.Semaphore,
available_fncs: []vk.Fence,
cinfo: vk.SwapchainCreateInfoKHR,
handle: vk.SwapchainKHR,
images: std.ArrayListUnmanaged(vk.Image),
views: std.ArrayListUnmanaged(vk.ImageView),
pub fn init(alloc: std.mem.Allocator, flights: []F) !Self {
const acquired_sems = try alloc.alloc(vk.Semaphore, flights.len);
errdefer alloc.free(acquired_sems);
@memset(acquired_sems, .null_handle);
errdefer for (acquired_sems) |semaphore| ctx.D.destroySemaphore(semaphore, null);
for (acquired_sems) |*sem| {
sem.* = try ctx.D.createSemaphore(&vk.SemaphoreCreateInfo{}, null);
}
const complete_sems = try alloc.alloc(vk.Semaphore, flights.len);
errdefer alloc.free(complete_sems);
@memset(complete_sems, .null_handle);
errdefer for (complete_sems) |semaphore| ctx.D.destroySemaphore(semaphore, null);
for (complete_sems) |*sem| {
sem.* = try ctx.D.createSemaphore(&vk.SemaphoreCreateInfo{}, null);
}
const available_fncs = try alloc.alloc(vk.Fence, flights.len);
errdefer alloc.free(available_fncs);
@memset(available_fncs, .null_handle);
errdefer for (available_fncs) |fence| ctx.D.destroyFence(fence, null);
for (available_fncs) |*fnc| {
fnc.* = try ctx.D.createFence(&vk.FenceCreateInfo{ .flags = .{ .signaled_bit = true } }, null);
}
const capabilities = try ctx.I.getPhysicalDeviceSurfaceCapabilitiesKHR(ctx.pdevice.*, ctx.surface.*);
const format = try _choose_format(alloc);
const mode = try _choose_mode(alloc);
var min_image_count = @min(3, capabilities.min_image_count + 1);
if (capabilities.max_image_count > 0) {
min_image_count = @min(min_image_count, capabilities.max_image_count);
}
const cinfo: vk.SwapchainCreateInfoKHR = .{
.surface = ctx.surface.*,
.min_image_count = min_image_count,
.image_format = format.format,
.image_color_space = format.color_space,
.image_extent = undefined, // set in rebuild
.image_array_layers = 1,
.image_usage = .{ .color_attachment_bit = true },
.image_sharing_mode = .exclusive,
.pre_transform = .{ .identity_bit_khr = true },
.composite_alpha = .{ .opaque_bit_khr = true },
.present_mode = mode,
.clipped = vk.TRUE,
.old_swapchain = .null_handle,
};
return .{
.alloc = alloc,
.flights = flights,
.acquired_sems = acquired_sems,
.complete_sems = complete_sems,
.available_fncs = available_fncs,
.cinfo = cinfo,
.handle = .null_handle,
.images = .{},
.views = .{},
};
}
pub fn deinit(self: *Self) void {
for (self.views.items) |view| ctx.D.destroyImageView(view, null);
self.views.deinit(self.alloc);
// images are owned by swapchain and not explicitly destroyed
self.images.deinit(self.alloc);
ctx.D.destroySwapchainKHR(self.handle, null);
// The easiest way to ensure fences and semaphores are not in use for deletion.
ctx.D.deviceWaitIdle() catch |err| switch (err) {
error.OutOfHostMemory,
error.OutOfDeviceMemory,
=> {},
error.DeviceLost,
=> return, // If the devices is lost there isn't much I know to do. I guess deinit is not needed?
else => unreachable,
};
for (self.available_fncs) |fnc| ctx.D.destroyFence(fnc, null);
self.alloc.free(self.available_fncs);
for (self.complete_sems) |sem| ctx.D.destroySemaphore(sem, null);
self.alloc.free(self.complete_sems);
for (self.acquired_sems) |sem| ctx.D.destroySemaphore(sem, null);
self.alloc.free(self.acquired_sems);
}
pub fn acquire(self: *Self) !Target {
const flight_index = self.cur;
const acquired = self.acquired_sems[flight_index];
const complete = self.complete_sems[flight_index];
const available = self.available_fncs[flight_index];
const timeout = std.math.maxInt(u64);
for (0..5) |_| {
if (self.handle == .null_handle) {
try self.rebuild();
std.debug.assert(self.handle != .null_handle);
}
const fences: [1]vk.Fence = .{available};
std.debug.assert(.success == try ctx.D.waitForFences(
1,
&fences,
vk.TRUE,
std.math.maxInt(u64),
));
if (ctx.D.acquireNextImageKHR(
self.handle,
timeout,
acquired,
.null_handle,
)) |res| {
switch (res.result) {
.success, .suboptimal_khr => {},
else => unreachable,
}
try ctx.D.resetFences(1, &.{available});
self.cur = @intCast(@mod(self.cur + 1, self.flights.len));
return Target{
.image_index = res.image_index,
.flight_index = flight_index,
.image = self.images.items[res.image_index],
.view = self.views.items[res.image_index],
.flight = &self.flights[flight_index],
.acquired = acquired,
.complete = complete,
.available = available,
};
} else |err| switch (err) {
error.OutOfDateKHR => {
self.handle = .null_handle;
continue;
},
else => return err,
}
} else {
return error.CannotRecreateSwapchain;
}
}
pub fn present(self: *Self, target: Target) !void {
if (ctx.Q.presentKHR(&vk.PresentInfoKHR{
.wait_semaphore_count = 1, // todo extra semaphores?
.p_wait_semaphores = &.{target.complete},
.swapchain_count = 1,
.p_swapchains = &.{self.handle},
.p_image_indices = &.{target.image_index},
.p_results = null,
})) |res| {
switch (res) {
.success => {},
.suboptimal_khr => {
self.handle = .null_handle;
return;
},
else => unreachable,
}
} else |err| switch (err) {
error.OutOfDateKHR => {
self.handle = .null_handle;
std.log.debug("Dropped frame", .{});
return;
},
else => return err,
}
}
fn rebuild(self: *Self) !void {
std.debug.assert(self.handle == .null_handle);
const capabilities = try ctx.I.getPhysicalDeviceSurfaceCapabilitiesKHR(
ctx.pdevice.*,
ctx.surface.*,
);
self.cinfo.image_extent = capabilities.current_extent;
self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null);
ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null);
errdefer ctx.D.destroySwapchainKHR(self.handle, null);
self.cinfo.old_swapchain = self.handle;
for (self.views.items) |view| ctx.D.destroyImageView(view, null);
var count: u32 = undefined;
std.debug.assert(.success == try ctx.D.getSwapchainImagesKHR(self.handle, &count, null));
try self.images.resize(self.alloc, count);
try self.views.resize(self.alloc, count);
std.debug.assert(.success == try ctx.D.getSwapchainImagesKHR(self.handle, &count, self.images.items.ptr));
@memset(self.views.items, .null_handle);
errdefer for (self.views.items) |view| ctx.D.destroyImageView(view, null);
for (self.images.items, self.views.items) |image, *view| {
view.* = try ctx.D.createImageView(&vk.ImageViewCreateInfo{
.image = image,
.view_type = .@"2d",
.format = self.cinfo.image_format,
.components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity },
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
}, null);
}
}
};
}