From 9173c8c9e9cb9ebba1cf44a231a79a5da087faf3 Mon Sep 17 00:00:00 2001 From: abux Date: Tue, 11 Aug 2026 21:06:28 +0100 Subject: [PATCH] HelloWorld --- README.md | 12 + assets/shaders/compiled/simple.frag.spv | Bin 0 -> 440 bytes assets/shaders/compiled/simple.vert.spv | Bin 0 -> 1004 bytes assets/shaders/simple.frag | 7 + assets/shaders/simple.vert | 9 + build.zig | 6 + build.zig.zon | 8 +- compile.sh | 3 + src/gfx.zig | 302 +++++++++- src/rhi/root.zig | 149 ++++- src/rhi/vulkan/instance.zig | 124 +++- src/rhi/vulkan/resource.zig | 80 ++- .../{device.zig => resource/adapter.zig} | 70 ++- src/rhi/vulkan/resource/buffer.zig | 131 +++++ src/rhi/vulkan/resource/device.zig | 167 ++++++ src/rhi/vulkan/resource/pipeline.zig | 247 ++++++++ src/rhi/vulkan/resource/shader.zig | 84 +++ src/rhi/vulkan/resource/surface.zig | 53 ++ src/rhi/vulkan/resource/swapchain.zig | 169 ++++++ src/rhi/vulkan/vulkan.zig | 551 +++++++++++++++++- tools/shaders.zig | 5 + 21 files changed, 2094 insertions(+), 83 deletions(-) create mode 100644 README.md create mode 100644 assets/shaders/compiled/simple.frag.spv create mode 100644 assets/shaders/compiled/simple.vert.spv create mode 100644 assets/shaders/simple.frag create mode 100644 assets/shaders/simple.vert create mode 100755 compile.sh rename src/rhi/vulkan/{device.zig => resource/adapter.zig} (55%) create mode 100644 src/rhi/vulkan/resource/buffer.zig create mode 100644 src/rhi/vulkan/resource/device.zig create mode 100644 src/rhi/vulkan/resource/surface.zig create mode 100644 src/rhi/vulkan/resource/swapchain.zig create mode 100644 tools/shaders.zig diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb1cf8c --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# NYXGFX +Low cortisol crossplatform RHI (Rendering Hardware Interface) + +## Backends +[x] Vulkan +[ ] OpenGL +[ ] DX12 + +## Features +- Fire and forget +- Dead simple + Performance + Flexible +- Handles diff --git a/assets/shaders/compiled/simple.frag.spv b/assets/shaders/compiled/simple.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..fa65e2c56897067b0c03e19e3a4c025273355e5c GIT binary patch literal 440 zcmYjO!AiqG6daqT*4DO)U~f|Ks1y%XghG1|*~>x?{eVT192R0y+Eno7w|TDr<3(_0 zYat7hw=+9C?=6WMH^;ycy6B;g{W?d33aGV0HFjyon<^bo9`5DnBML@&2J|#pg|}B< z=-`AnqemLUxr>+xeDOjP9T@A(VqxryOI9nlsXp?|<-W*V>epFP`S(n|cFpIDB;TfW zLrh1#XN)-XEw|NknU`y}gP}vST#0a9a0<kN9n~>b26R z|6G~UZpr2C5Z^mHJMiwF&ky{R9Qd5yF-18&LU#mtb^gftHJkcsDgTE`%sL}ePu=_F IA9*7F0L*7CumAu6 literal 0 HcmV?d00001 diff --git a/assets/shaders/compiled/simple.vert.spv b/assets/shaders/compiled/simple.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..6195065a1fe8350c5660e5e578346b9db52193a3 GIT binary patch literal 1004 zcmYk4Sxdt}6otpOao_5`Q}Iy{A5;WU5taB*To8Q?r42F=r=?9ree$>Yt9%hW-=wLX zaC7IJId{pO6bm~e#tfMWGi5eSw#ud;#)upCKCC`g_d01~@8EDB#k47UA(|O8=JzGN zUu)I@ret%nb=iijsHY(Rt$>mlH(@mj@9x6tbyRP)qE7me#8F~h95rk^uBY}bMmIX3 zv#y?W8-s#Ciou$JdYw>>mn6E6+s|=3jo$^uW;}b&njM?krV~5j^?ZZptxF&6rz#VZ zozb(-E)(0jvYoW%>T&;!32@d;lKxqs@8sKAfgWaHUB9H~n2B|_&Gu_8$#f+@BkP`? z!J%^S9n`*21bbHQb1u6dZth=43_5ztb?o7oRj4;TZOK?*`T>&%%zc5$HLo{*TN%m2 zeo4Dw`HE~=di^{elYdcoRK6l(?e;K#2>CaJ$K{EK+tQBS@*JFD73n7B^WIN-nAyTn zXWHwsywm~D$XN6>D^DGICSO^e9^lCbW)^V7qg{}(a=hr#*E#X4+Ebfe^LS=Qe2$qN z_(=CgU1q`}?ng`@e=3$9=4I4>5KHZ)9w*o5mU|pI?^T)FD>Ag4(Jwt-C .{ .vulkan = try .init(config, alloc) }, + .vulkan => .{ .vulkan = try .init(config, alloc, io) }, }; return .{ .config = config, .ctx = backend_ctx, .alloc = alloc, + .io = io, }; } @@ -66,13 +86,213 @@ pub fn deinit(self: *Self) void { } } +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createAdapter(self: *Self) !Handle(Adapter) { + return try switch (self.ctx) { + .vulkan => |*v| v.createAdapter(), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createDevice( + self: *Self, + adapter: Handle(Adapter), + surface: Handle(Surface), +) !Handle(Device) { + return try switch (self.ctx) { + .vulkan => |*v| v.createDevice(adapter, surface), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn destroyDevice(self: *Self, device: Handle(Device)) !void { + try switch (self.ctx) { + .vulkan => |*v| v.destroyDevice(device), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createBuffer( + self: *Self, + value: anytype, + usage: BufferUsage, + adapter: Handle(Adapter), + device: Handle(Device), +) !Buffer { + const size = @sizeOf(@TypeOf(value)); + + const handle = try switch (self.ctx) { + .vulkan => |*v| v.createBuffer(size, usage, .exclusive, adapter, device), + }; + + var buffer: Buffer = .{ + .handle = handle, + .device = device, + .gfx = self, + }; + try buffer.set(value); + + return buffer; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createSurface(self: *Self) !Handle(Surface) { + return try switch (self.ctx) { + .vulkan => |*v| v.createSurface(), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createSwapchain( + self: *Self, + adapter: Handle(Adapter), + device: Handle(Device), + surface: Handle(Surface), +) !Handle(Swapchain) { + return try switch (self.ctx) { + .vulkan => |*v| v.createSwapchain(adapter, device, surface), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createShader( + self: *Self, + vert_spv_path: []const u8, + frag_spv_path: []const u8, + device: Handle(Device), +) !Handle(Shader) { + return try switch (self.ctx) { + .vulkan => |*v| v.createShader(vert_spv_path, frag_spv_path, device), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createPipeline(self: *Self, config: PipelineConfig) !Pipeline { + return .{ + .handle = try switch (self.ctx) { + .vulkan => |*v| v.createPipeline(config), + }, + .device = config.device, + .gfx = self, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn beginFrame( + self: *Self, + swapchain: Handle(Swapchain), + device: Handle(Device), +) !void { + try switch (self.ctx) { + .vulkan => |*v| v.beginFrame(swapchain, device), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn endFrame( + self: *Self, + swapchain: Handle(Swapchain), + device: Handle(Device), +) !void { + try switch (self.ctx) { + .vulkan => |*v| v.endFrame(swapchain, device), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn beginRendering( + self: *Self, + swapchain: Handle(Swapchain), +) !void { + try switch (self.ctx) { + .vulkan => |*v| v.beginRendering(swapchain), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn endRendering( + self: *Self, + swapchain: Handle(Swapchain), +) !void { + try switch (self.ctx) { + .vulkan => |*v| v.endRendering(swapchain), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn draw(self: *Self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) !void { + try switch (self.ctx) { + .vulkan => |*v| v.draw(vertex_count, instance_count, first_vertex, first_instance), + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn drawIndexed(self: *Self, index_count: u32, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32) !void { + switch (self.ctx) { + .vulkan => |*v| { + const frame = v.currentFrame(); + if (frame.cmd_buf) |cmd| { + vk.vkCmdDrawIndexed(cmd, index_count, instance_count, first_index, vertex_offset, first_instance); + } + }, + } +} + // // TESTS // const sdl = @import("sdl"); +const math = @import("math"); + +const Vertex = struct { + pos: [2]f32, + normal: [2]f32, + uv: [2]f32, +}; + +// const vertices = [_]Vertex{ +// .{ .pos = .{ -0.5, -0.5 }, .normal = .{ 0, 0 }, .uv = .{ 0, 0 } }, +// .{ .pos = .{ 0.5, -0.5 }, .normal = .{ 0, 0 }, .uv = .{ 1, 0 } }, +// .{ .pos = .{ 0.5, 0.5 }, .normal = .{ 0, 0 }, .uv = .{ 1, 1 } }, +// +// .{ .pos = .{ -0.5, -0.5 }, .normal = .{ 0, 0 }, .uv = .{ 0, 0 } }, +// .{ .pos = .{ 0.5, 0.5 }, .normal = .{ 0, 0 }, .uv = .{ 1, 1 } }, +// .{ .pos = .{ -0.5, 0.5 }, .normal = .{ 0, 0 }, .uv = .{ 0, 1 } }, +// }; + +const vertices = [_]Vertex{ + .{ .pos = .{ -0.5, -0.5 }, .normal = .{ 0, 0 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, -0.5 }, .normal = .{ 0, 0 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 0.5, 0.5 }, .normal = .{ 0, 0 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -0.5, 0.5 }, .normal = .{ 0, 0 }, .uv = .{ 0, 1 } }, +}; + +const indices = [_]u32{ + 0, 1, 2, + 0, 2, 3, +}; test "Main" { + + // + // SDL + // + // --- # --- if (!sdl.SDL_Init(sdl.SDL_INIT_VIDEO)) return error.FailedToInitSDL; defer sdl.SDL_Quit(); @@ -86,6 +306,10 @@ test "Main" { const display = sdl.SDL_GetPointerProperty(props, sdl.SDL_PROP_WINDOW_X11_DISPLAY_POINTER, null); const xwindow = sdl.SDL_GetNumberProperty(props, sdl.SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + // + // GFX + // + // --- # --- var gfx: Self = try .init(Config{ .window = .{ @@ -94,12 +318,39 @@ test "Main" { .display = display.?, }, }, - }, std.testing.allocator); + }, std.testing.allocator, std.testing.io); defer gfx.deinit(); // --- # --- - var count: u32 = 0; - _ = vk.vkEnumerateInstanceExtensionProperties(null, &count, null); + const adapter = try gfx.createAdapter(); + const surface = try gfx.createSurface(); + const device = try gfx.createDevice(adapter, surface); + const swapchain = try gfx.createSwapchain(adapter, device, surface); + + const vbuf = try gfx.createBuffer(vertices, .vertex, adapter, device); + const ibuf = try gfx.createBuffer(indices, .index, adapter, device); + + const simple_shader = try gfx.createShader( + "assets/shaders/compiled/simple.vert.spv", + "assets/shaders/compiled/simple.frag.spv", + device, + ); + + const pipeline = try gfx.createPipeline(.{ + .vertex_attributes = &.{ + .{ .location = 0, .format = .vec2, .offset = @offsetOf(Vertex, "pos") }, + .{ .location = 1, .format = .vec2, .offset = @offsetOf(Vertex, "normal") }, + .{ .location = 2, .format = .vec2, .offset = @offsetOf(Vertex, "uv") }, + }, + .vertex_binding = .{ .stride = @sizeOf(Vertex) }, + + .shader = simple_shader, + .device = device, + .swapchain = swapchain, + }); + + try pipeline.setUniform("uTime", 0.1); + try pipeline.setUniform("uTime", 0.1); // --- # --- if (gtl.log.print(.debug, "WINDOW", gtl.ansi.blue)) |v| { @@ -108,6 +359,37 @@ test "Main" { v.write("Title: {s}", .{"NYXGFX"}); v.write("Width: {}", .{800}); v.write("Height: {}", .{600}); - v.write("Count: {}", .{count}); + } + + var event: sdl.SDL_Event = undefined; + var running: bool = true; + + while (running) { + while (sdl.SDL_PollEvent(&event)) { + switch (event.type) { + sdl.SDL_EVENT_QUIT => running = false, + else => {}, + } + } + + try gfx.beginFrame(swapchain, device); + defer gfx.endFrame(swapchain, device) catch unreachable; + + { + try gfx.beginRendering(swapchain); + defer gfx.endRendering(swapchain) catch unreachable; + + try pipeline.bind(); + try vbuf.bind(); + try ibuf.bind(); + try gfx.drawIndexed(indices.len, 1, 0, 0, 0); + } + + // if (gfx.beginRendering(...)) |pass| { + // defer pass.end(); + // + // try pass.bindPipeline(...); + // try pass.draw(...); + // } } } diff --git a/src/rhi/root.zig b/src/rhi/root.zig index 88fca25..74a65a3 100644 --- a/src/rhi/root.zig +++ b/src/rhi/root.zig @@ -1,3 +1,11 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const vk = @import("vulkan"); +const gtl = @import("gtl"); +const Gfx = @import("../gfx.zig"); +const Handle = gtl.Handle; + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const Window = union(enum) { @@ -22,16 +30,153 @@ pub const Window = union(enum) { }, }; +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const AdapterConfig = struct {}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const DeviceConfig = struct { + adapter: Handle(Adapter), + surface: Handle(Surface), +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Format = enum { + int, + float, + + vec2, + vec3, + vec4, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const VertexBinding = struct { + binding: u32 = 0, + stride: u32, + input_rate: enum { + vertex, + instance, + } = .vertex, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const VertexAttribute = struct { + location: u32, + binding: u32 = 0, + offset: u32, + format: Format, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const PipelineConfig = struct { + shader: Handle(Shader), + swapchain: Handle(Swapchain), + device: Handle(Device), + + vertex_binding: ?VertexBinding = null, + vertex_attributes: []const VertexAttribute = &.{}, + + topology: enum { + triangle_list, + line_list, + } = .triangle_list, + + cull_mode: enum { + front_and_back, + front, + back, + none, + } = .back, + + front_face: enum { + ccw, + cw, + } = .cw, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const BufferUsage = enum { + vertex, + index, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const BufferSharingMode = enum { + exclusive, +}; + // // HANDLE TYPES // -pub const Buffer = struct {}; +pub const Adapter = struct {}; +pub const Device = struct {}; + +pub const Surface = struct {}; +pub const Swapchain = struct {}; + +pub const Buffer = struct { + handle: Handle(Buffer), + device: Handle(Device), + gfx: *Gfx, + + pub fn set(self: *const Buffer, value: anytype) !void { + switch (self.gfx.ctx) { + .vulkan => |*v| { + const raw = v.resource.buffers.get(self.handle) orelse return error.BufferNotFound; + try raw.set(value, @sizeOf(@TypeOf(value))); + }, + } + } + + pub fn bind(self: *const Buffer) !void { + switch (self.gfx.ctx) { + .vulkan => |*v| { + const raw = v.resource.buffers.get(self.handle) orelse return error.BufferNotFound; + const frame = v.currentFrame(); + if (frame.cmd_buf) |cmd| { + const buffers = [_]vk.VkBuffer{raw.raw}; + const offsets = [_]vk.VkDeviceSize{0}; + + switch (raw.usage) { + .vertex => vk.vkCmdBindVertexBuffers(cmd, 0, 1, &buffers, &offsets), + .index => vk.vkCmdBindIndexBuffer(cmd, raw.raw, 0, vk.VK_INDEX_TYPE_UINT32), + } + } + }, + } + } +}; pub const Image = struct {}; pub const ImageView = struct {}; pub const Sampler = struct {}; pub const Shader = struct {}; -pub const Pipeline = struct {}; pub const PipelineLayout = struct {}; + +pub const Pipeline = struct { + handle: Handle(Pipeline), + device: Handle(Device), + gfx: *Gfx, + + pub fn bind(self: *const Pipeline) !void { + try switch (self.gfx.ctx) { + .vulkan => |*v| v.bindPipeline(self.handle), + }; + } + + pub fn setUniform(self: *const Pipeline, name: []const u8, value: anytype) !void { + _ = self; + _ = name; + _ = value; + } +}; diff --git a/src/rhi/vulkan/instance.zig b/src/rhi/vulkan/instance.zig index 969cfcd..a8a75fe 100644 --- a/src/rhi/vulkan/instance.zig +++ b/src/rhi/vulkan/instance.zig @@ -3,6 +3,7 @@ //! ---------------------------------------------------- const std = @import("std"); +const gtl = @import("gtl"); const vk = @import("vulkan"); const Resource = @import("resource.zig"); const Window = @import("../root.zig").Window; @@ -14,6 +15,8 @@ const Self = @This(); // raw: *vk.VkInstance_T, +messenger: ?vk.VkDebugUtilsMessengerEXT = null, +pfn_destroy_debug_utils_messenger: vk.PFN_vkDestroyDebugUtilsMessengerEXT = null, /// ---------------------------------------------------- /// ---------------------------------------------------- @@ -21,49 +24,79 @@ pub fn init( config: MainConfig, alloc: std.mem.Allocator, ) !Self { - const validation_layers: []const []const u8 = &.{ + const validation_layers: []const [*:0]const u8 = &.{ "VK_LAYER_KHRONOS_validation", }; const extensions = getWindowExtensions( config.window, + config.enable_validation, ); var app_info: vk.VkApplicationInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = config.app_name.ptr, .pEngineName = config.engine_name.ptr, - .apiVersion = vk.VK_API_VERSION_1_4, + .apiVersion = vk.VK_API_VERSION_1_3, }; + var messenger_info: vk.VkDebugUtilsMessengerCreateInfoEXT = undefined; + var create_info: vk.VkInstanceCreateInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledExtensionCount = @intCast(extensions.len), .ppEnabledExtensionNames = extensions.ptr, - .flags = vk.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, }; - if (config.enable_validation and !try checkValidationLayerSupport(validation_layers, alloc)) { - return error.ValidationLayerNotAvailable; - } else { + if (config.enable_validation) { + if (!try checkValidationLayerSupport(validation_layers, alloc)) { + return error.ValidationLayerNotAvailable; + } + create_info.enabledLayerCount = @intCast(validation_layers.len); - create_info.ppEnabledLayerNames = @ptrCast(validation_layers.ptr); + create_info.ppEnabledLayerNames = validation_layers.ptr; + + messenger_info = makeMessengerInfo(); + create_info.pNext = &messenger_info; } var instance: vk.VkInstance = null; if (vk.vkCreateInstance(&create_info, null, &instance) != vk.VK_SUCCESS) { return error.FailedToCreateVulkanInstance; } + errdefer vk.vkDestroyInstance(instance.?, null); + + var messenger: vk.VkDebugUtilsMessengerEXT = null; + var pfn_destroy: vk.PFN_vkDestroyDebugUtilsMessengerEXT = null; + if (config.enable_validation) { + const pfn_create: vk.PFN_vkCreateDebugUtilsMessengerEXT = @ptrCast( + vk.vkGetInstanceProcAddr(instance.?, "vkCreateDebugUtilsMessengerEXT"), + ); + pfn_destroy = @ptrCast( + vk.vkGetInstanceProcAddr(instance.?, "vkDestroyDebugUtilsMessengerEXT"), + ); + + if (pfn_create.?(instance.?, &messenger_info, null, &messenger) != vk.VK_SUCCESS) { + return error.FailedToCreateDebugUtilsMessenger; + } + } return .{ .raw = instance.?, + .messenger = messenger, + .pfn_destroy_debug_utils_messenger = pfn_destroy, }; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { + if (self.messenger) |messenger| { + if (self.pfn_destroy_debug_utils_messenger) |pfn_destroy| { + pfn_destroy(self.raw, messenger, null); + } + } vk.vkDestroyInstance(self.raw, null); } @@ -72,27 +105,84 @@ pub fn deinit(self: *Self) void { /// ---------------------------------------------------- fn getWindowExtensions( window: Window, + enable_validation: bool, ) []const [*:0]const u8 { return switch (window) { - .xlib => &.{ - "VK_KHR_surface", - "VK_KHR_xlib_surface", - }, + .xlib => if (enable_validation) + &.{ "VK_KHR_surface", "VK_KHR_xlib_surface", "VK_EXT_debug_utils" } + else + &.{ "VK_KHR_surface", "VK_KHR_xlib_surface" }, - .wayland => &.{ - "VK_KHR_surface", - "VK_KHR_wayland_surface", - }, + .wayland => if (enable_validation) + &.{ "VK_KHR_surface", "VK_KHR_wayland_surface", "VK_EXT_debug_utils" } + else + &.{ "VK_KHR_surface", "VK_KHR_wayland_surface" }, else => @panic("Not implemented yet..."), }; } +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn makeMessengerInfo() vk.VkDebugUtilsMessengerCreateInfoEXT { + return .{ + .sType = vk.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = @intCast( + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + ), + .messageType = @intCast( + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + ), + .pfnUserCallback = debugCallback, + }; +} + +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn debugCallback( + message_severity: vk.VkDebugUtilsMessageSeverityFlagBitsEXT, + message_types: vk.VkDebugUtilsMessageTypeFlagsEXT, + callback_data: ?*const vk.VkDebugUtilsMessengerCallbackDataEXT, + user_data: ?*anyopaque, +) callconv(.c) vk.VkBool32 { + _ = user_data; + + const severity: []const u8 = switch (message_severity) { + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT => gtl.ansi.yellow ++ "[WARN]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT => gtl.ansi.red ++ "[ERROR]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT => gtl.ansi.blue ++ "[FLAG_BITS_MAX_ENUM]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT => gtl.ansi.blue ++ "[INFO_BIT]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT => gtl.ansi.red ++ "[VERBOSE_BIT]" ++ gtl.ansi.reset, + else => "IDK", + }; + + const message_type = switch (message_types) { + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT => gtl.ansi.yellow ++ "[VALIDATION]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT => gtl.ansi.purple ++ "[GENERAL]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT => gtl.ansi.purple ++ "[ADDRESS_BINDING_BIT]" ++ gtl.ansi.reset, + vk.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT => gtl.ansi.red ++ "[PERFORMANCE_BIT]" ++ gtl.ansi.reset, + else => "IDK", + }; + + if (callback_data) |data| { + if (data.pMessage) |message| { + std.debug.print("{s}{s} {s}\n", .{ severity, message_type, message }); + } + } + + return vk.VK_FALSE; +} + /// ---------------------------------------------------- /// Helper /// ---------------------------------------------------- fn checkValidationLayerSupport( - layers: []const []const u8, + layers: []const [*:0]const u8, alloc: std.mem.Allocator, ) !bool { // --- GET LAYER COUNT --- @@ -116,7 +206,7 @@ fn checkValidationLayerSupport( for (available_layers) |*prop| { const available_name: []const u8 = std.mem.sliceTo(&prop.layerName, 0); - if (std.mem.eql(u8, name, available_name)) { + if (std.mem.eql(u8, std.mem.span(name), available_name)) { found = true; break; } diff --git a/src/rhi/vulkan/resource.zig b/src/rhi/vulkan/resource.zig index efb413d..e800487 100644 --- a/src/rhi/vulkan/resource.zig +++ b/src/rhi/vulkan/resource.zig @@ -1,11 +1,28 @@ //! ---------------------------------------------------- //! ---------------------------------------------------- +const vk = @import("vulkan"); const std = @import("std"); const gtl = @import("gtl"); const common = @import("../root.zig"); +const Instance = @import("instance.zig"); const Self = @This(); +const Adapter = common.Adapter; +const VKAdapter = @import("resource/adapter.zig"); + +const Device = common.Device; +const VKDevice = @import("resource/device.zig"); + +const Buffer = common.Buffer; +const VKBuffer = @import("resource/buffer.zig"); + +const Surface = common.Surface; +const VKSurface = @import("resource/surface.zig"); + +const Swapchain = common.Swapchain; +const VKSwapchain = @import("resource/swapchain.zig"); + const Shader = common.Shader; const VKShader = @import("resource/shader.zig"); @@ -17,6 +34,13 @@ const VKPipeline = @import("resource/pipeline.zig"); // FIELDS // +adapters: gtl.ResourceMap(Adapter, VKAdapter), +devices: gtl.ResourceMap(Device, VKDevice), +buffers: gtl.ResourceMap(Buffer, VKBuffer), + +surfaces: gtl.ResourceMap(Surface, VKSurface), +swapchains: gtl.ResourceMap(Swapchain, VKSwapchain), + shaders: gtl.ResourceMap(Shader, VKShader), pipelines: gtl.ResourceMap(Pipeline, VKPipeline), @@ -24,6 +48,13 @@ pipelines: gtl.ResourceMap(Pipeline, VKPipeline), /// ---------------------------------------------------- pub fn init(alloc: std.mem.Allocator) Self { return .{ + .adapters = .init(alloc), + .devices = .init(alloc), + .buffers = .init(alloc), + + .surfaces = .init(alloc), + .swapchains = .init(alloc), + .shaders = .init(alloc), .pipelines = .init(alloc), }; @@ -31,12 +62,59 @@ pub fn init(alloc: std.mem.Allocator) Self { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn deinit(self: *Self) void { +pub fn deinit( + self: *Self, + instance: *Instance, +) void { + { // FREE SWAPCHAINS + var it = self.swapchains.valueIterator(); + while (it.next()) |v| { + v.deinit(); + } + self.swapchains.deinit(); + } + + { // FREE SURFACES + var it = self.surfaces.valueIterator(); + while (it.next()) |v| { + v.deinit(instance); + } + self.surfaces.deinit(); + } + { // FREE SHADERS + var it = self.shaders.valueIterator(); + while (it.next()) |v| { + v.deinit(); + } self.shaders.deinit(); } { // FREE PIPELINES + var it = self.pipelines.valueIterator(); + while (it.next()) |v| { + v.deinit(); + } self.pipelines.deinit(); } + + { // FREE BUFFERS + var it = self.buffers.valueIterator(); + while (it.next()) |v| { + v.deinit(); + } + self.buffers.deinit(); + } + + { // FREE ADAPTERS + self.adapters.deinit(); + } + + { // FREE DEVICES + var it = self.devices.valueIterator(); + while (it.next()) |v| { + v.deinit(); + } + self.devices.deinit(); + } } diff --git a/src/rhi/vulkan/device.zig b/src/rhi/vulkan/resource/adapter.zig similarity index 55% rename from src/rhi/vulkan/device.zig rename to src/rhi/vulkan/resource/adapter.zig index ba7d89c..0c7c3d2 100644 --- a/src/rhi/vulkan/device.zig +++ b/src/rhi/vulkan/resource/adapter.zig @@ -1,43 +1,43 @@ //! ---------------------------------------------------- -//! `🗲` Vulkan Device `🗲` +//! `🗲` Vulkan Adapter `🗲` //! ---------------------------------------------------- -const gtl = @import("gtl"); const std = @import("std"); +const gtl = @import("gtl"); const vk = @import("vulkan"); -const Instance = @import("instance.zig"); +const VKBackend = @import("../vulkan.zig"); const Self = @This(); // // FIELDS // -raw: *vk.VkDevice_T, +raw: *vk.VkPhysicalDevice_T, /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn init( - instance: *Instance, - alloc: std.mem.Allocator, -) !Self { +pub fn init(backend: *const VKBackend) !Self { var physical: vk.VkPhysicalDevice = null; var count: u32 = 0; - if (vk.vkEnumeratePhysicalDevices(instance.raw, &count, null) != vk.VK_SUCCESS) { + // --- GET PHYSICAL DEVICES COUNT --- + if (vk.vkEnumeratePhysicalDevices(backend.instance.raw, &count, null) != vk.VK_SUCCESS) { return error.FailedToEnumeratePhysicalDevices; } - if (count == 0) { return error.FailedToFindGPUWithVKSupport; } - const devices = try alloc.alloc(vk.VkPhysicalDevice, count); - defer alloc.free(devices); + // --- # --- + const devices = try backend.alloc.alloc(vk.VkPhysicalDevice, count); + defer backend.alloc.free(devices); - if (vk.vkEnumeratePhysicalDevices(instance.raw, &count, devices.ptr) != vk.VK_SUCCESS) { + // --- GET PHYSICAL DEVICES --- + if (vk.vkEnumeratePhysicalDevices(backend.instance.raw, &count, devices.ptr) != vk.VK_SUCCESS) { return error.FailedToEnumeratePhysicalDevices; } + // --- CHECK WHICH DEVICE IS SUITABLE --- for (devices) |device| { if (try isDeviceSuitable(device)) { physical = device; @@ -45,32 +45,40 @@ pub fn init( } } - var features: vk.VkPhysicalDeviceFeatures = .{}; - var queue_create_info: vk.VkDeviceQueueCreateInfo = .{ - .sType = vk.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - }; - - var create_info: vk.VkDeviceCreateInfo = .{ - .sType = vk.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pQueueCreateInfos = &queue_create_info, - .queueCreateInfoCount = 1, - .pEnabledFeatures = &features, - }; - - var device: vk.VkDevice = null; - if (vk.vkCreateDevice(physical, &create_info, null, &device) != vk.VK_SUCCESS) { - return error.FailedToCreateVulkanDevice; + // --- FIND GFX QUEUE FAMILY --- + var queue_family_index: u32 = 0; + if (!try findGraphicsQueueFamily(physical, backend.alloc, &queue_family_index)) { + return error.FailedToFindGraphicsQueueFamily; } return .{ - .raw = device.?, + .raw = physical.?, }; } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn deinit(self: *Self) void { - vk.vkDestroyDevice(self.raw, null); +fn findGraphicsQueueFamily( + device: vk.VkPhysicalDevice, + alloc: std.mem.Allocator, + out_index: *u32, +) !bool { + var count: u32 = 0; + vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null); + + const families = try alloc.alloc(vk.VkQueueFamilyProperties, count); + defer alloc.free(families); + + vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, families.ptr); + + for (families, 0..) |family, i| { + if (family.queueFlags & @as(vk.VkQueueFlags, @intCast(vk.VK_QUEUE_GRAPHICS_BIT)) != 0) { + out_index.* = @intCast(i); + return true; + } + } + + return false; } /// ---------------------------------------------------- diff --git a/src/rhi/vulkan/resource/buffer.zig b/src/rhi/vulkan/resource/buffer.zig new file mode 100644 index 0000000..51e2422 --- /dev/null +++ b/src/rhi/vulkan/resource/buffer.zig @@ -0,0 +1,131 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Buffer `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const Self = @This(); + +const common = @import("../../root.zig"); +const Adapter = @import("adapter.zig"); +const Device = @import("device.zig"); +const BufferUsage = common.BufferUsage; +const BufferSharingMode = common.BufferSharingMode; + +// +// FIELDS +// + +raw: *vk.VkBuffer_T, +data: ?*vk.VkDeviceMemory_T, +usage: BufferUsage, + +// --- TEMP --- +adapter: *const Adapter, +device: *const Device, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + size: u32, + usage: BufferUsage, + sharing: BufferSharingMode, + adapter: *const Adapter, + device: *const Device, +) !Self { + var info: vk.VkBufferCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .usage = switch (usage) { + .vertex => vk.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .index => vk.VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + }, + .sharingMode = switch (sharing) { + .exclusive => vk.VK_SHARING_MODE_EXCLUSIVE, + }, + .size = size, + }; + + var raw: vk.VkBuffer = null; + if (vk.vkCreateBuffer(device.raw, &info, null, &raw) != vk.VK_SUCCESS) { + return error.FailedToCreateBuffer; + } + + return .{ + .raw = raw.?, + .data = null, + .usage = usage, + .adapter = adapter, + .device = device, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + if (self.data) |data| { + vk.vkFreeMemory(self.device.raw, data, null); + } + vk.vkDestroyBuffer(self.device.raw, self.raw, null); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn set( + self: *Self, + value: anytype, + size: u32, +) !void { + if (self.data == null) { + var mem_req: vk.VkMemoryRequirements = .{}; + var mem_props: vk.VkPhysicalDeviceMemoryProperties = .{}; + var mem_type: u32 = 0; + + vk.vkGetBufferMemoryRequirements(self.device.raw, self.raw, &mem_req); + vk.vkGetPhysicalDeviceMemoryProperties(self.adapter.raw, &mem_props); + + for (0..mem_props.memoryTypeCount) |i| { + if ((mem_req.memoryTypeBits & (@as(u32, 1) << @intCast(i))) != 0 and + (mem_props.memoryTypes[i].propertyFlags & + (vk.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + vk.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) != 0) + { + mem_type = @intCast(i); + break; + } + } + + var alloc_info: vk.VkMemoryAllocateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_req.size, + .memoryTypeIndex = mem_type, + }; + + if (vk.vkAllocateMemory(self.device.raw, &alloc_info, null, &self.data) != vk.VK_SUCCESS) { + return error.FailedToAllocateMemory; + } + + _ = vk.vkBindBufferMemory(self.device.raw, self.raw, self.data, 0); + } + + var mapped: ?*anyopaque = null; + + const result = vk.vkMapMemory( + self.device.raw, + self.data, + 0, + size, + 0, + &mapped, + ); + + if (result != vk.VK_SUCCESS) { + return error.FailedToMapMemory; + } + + const dst: [*]u8 = @ptrCast(mapped.?); + const src: [*]const u8 = @ptrCast(&value); + + @memcpy(dst[0..size], src[0..size]); + + vk.vkUnmapMemory(self.device.raw, self.data); +} diff --git a/src/rhi/vulkan/resource/device.zig b/src/rhi/vulkan/resource/device.zig new file mode 100644 index 0000000..b461049 --- /dev/null +++ b/src/rhi/vulkan/resource/device.zig @@ -0,0 +1,167 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Device `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const Adapter = @import("adapter.zig"); +const Surface = @import("surface.zig"); +const Self = @This(); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const QueueFamilies = struct { + graphics: ?u32 = null, + present: ?u32 = null, +}; + +// +// FIELDS +// + +raw: *vk.VkDevice_T, +graphics_queue: *vk.VkQueue_T, +present_queue: *vk.VkQueue_T, +graphics_command_pool: *vk.VkCommandPool_T, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + adapter: *Adapter, + surface: *Surface, + alloc: std.mem.Allocator, +) !Self { + // --- # --- + const extensions: []const [*:0]const u8 = &.{ + "VK_KHR_swapchain", + }; + + // --- # --- + var features13: vk.VkPhysicalDeviceVulkan13Features = .{ + .sType = vk.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + .dynamicRendering = vk.VK_TRUE, + }; + + // --- FIND QUEUE FAMILIES --- + const queue_families = try findQueueFamilies(adapter.raw, surface.raw, alloc); + if (queue_families.graphics == null or queue_families.present == null) { + return error.FailedToFindSuitableQueueFamily; + } + + // --- CREATE QUEUE INFOS --- + const queue_priority: f32 = 1.0; + var queue_create_infos: [2]vk.VkDeviceQueueCreateInfo = undefined; + var queue_create_info_count: u32 = 0; + + queue_create_infos[0] = .{ + .sType = vk.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_families.graphics.?, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + queue_create_info_count += 1; + + if (queue_families.present.? != queue_families.graphics.?) { + queue_create_infos[1] = .{ + .sType = vk.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_families.present.?, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + queue_create_info_count += 1; + } + + // --- DEVICE CREATE INFO --- + var features: vk.VkPhysicalDeviceFeatures = .{}; + var create_info: vk.VkDeviceCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queue_create_infos, + .queueCreateInfoCount = queue_create_info_count, + .pEnabledFeatures = &features, + .enabledExtensionCount = extensions.len, + .ppEnabledExtensionNames = extensions.ptr, + .pNext = &features13, + }; + + // --- # --- + var device: vk.VkDevice = null; + if (vk.vkCreateDevice(adapter.raw, &create_info, null, &device) != vk.VK_SUCCESS) { + return error.FailedToCreateVulkanDevice; + } + + // --- # --- + var pool_info: vk.VkCommandPoolCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = vk.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = queue_families.graphics.?, + }; + var graphics_command_pool: vk.VkCommandPool = null; + if (vk.vkCreateCommandPool(device, &pool_info, null, &graphics_command_pool) != vk.VK_SUCCESS) { + return error.FailedToCreateCommandPool; + } + + // --- GET QUEUES --- + var graphics_queue: vk.VkQueue = null; + vk.vkGetDeviceQueue(device, queue_families.graphics.?, 0, &graphics_queue); + + var present_queue: vk.VkQueue = null; + vk.vkGetDeviceQueue(device, queue_families.present.?, 0, &present_queue); + + return .{ + .raw = device.?, + .graphics_queue = graphics_queue.?, + .present_queue = present_queue.?, + .graphics_command_pool = graphics_command_pool.?, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + vk.vkDestroyCommandPool(self.raw, self.graphics_command_pool, null); + vk.vkDestroyDevice(self.raw, null); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +fn findQueueFamilies( + adapter: vk.VkPhysicalDevice, + surface: *vk.VkSurfaceKHR_T, + alloc: std.mem.Allocator, +) !QueueFamilies { + var indices: QueueFamilies = .{}; + + // --- GET FAMILY COUNT --- + var queue_family_count: u32 = 0; + vk.vkGetPhysicalDeviceQueueFamilyProperties(adapter, &queue_family_count, null); + + // --- CREATE BUFFER --- + const queue_families = try alloc.alloc(vk.VkQueueFamilyProperties, queue_family_count); + defer alloc.free(queue_families); + vk.vkGetPhysicalDeviceQueueFamilyProperties(adapter, &queue_family_count, queue_families.ptr); + + // --- FIND SUPPORTED FAMILIES --- + for (queue_families, 0..) |family, i| { + var supported: vk.VkBool32 = vk.VK_FALSE; + + if (family.queueFlags & @as(vk.VkQueueFlags, @intCast(vk.VK_QUEUE_GRAPHICS_BIT)) != 0) { + indices.graphics = @intCast(i); + } + + _ = vk.vkGetPhysicalDeviceSurfaceSupportKHR( + adapter, + @intCast(i), + surface, + &supported, + ); + if (supported == vk.VK_TRUE) { + indices.present = @intCast(i); + } + + if (indices.graphics != null and indices.present != null) { + break; + } + } + + return indices; +} diff --git a/src/rhi/vulkan/resource/pipeline.zig b/src/rhi/vulkan/resource/pipeline.zig index e69de29..c7e5498 100644 --- a/src/rhi/vulkan/resource/pipeline.zig +++ b/src/rhi/vulkan/resource/pipeline.zig @@ -0,0 +1,247 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Pipeline `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const gtl = @import("gtl"); +const Self = @This(); + +const Device = @import("device.zig"); +const Shader = @import("shader.zig"); +const Swapchain = @import("swapchain.zig"); +const Config = @import("../../root.zig").PipelineConfig; + +// +// FIELDS +// + +raw: *vk.VkPipeline_T, +layout: *vk.VkPipelineLayout_T, +device: *const Device, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + shader: *const Shader, + swapchain: *const Swapchain, + device: *const Device, + config: Config, + alloc: std.mem.Allocator, +) !Self { + + // + // LAYOUT + // + + var pipeline_layout_info: vk.VkPipelineLayoutCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + }; + + var pipeline_layout: vk.VkPipelineLayout = null; + if (vk.vkCreatePipelineLayout(device.raw, &pipeline_layout_info, null, &pipeline_layout) != vk.VK_SUCCESS) { + return error.FailedToCreatePipelineLayout; + } + errdefer vk.vkDestroyPipelineLayout(device.raw, pipeline_layout, null); + + // + // PIPELINE + // + + const vert_shader_stage_info: vk.VkPipelineShaderStageCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = vk.VK_SHADER_STAGE_VERTEX_BIT, + + .pName = "main", + .module = shader.vert, + }; + + const frag_shader_stage_info: vk.VkPipelineShaderStageCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = vk.VK_SHADER_STAGE_FRAGMENT_BIT, + + .pName = "main", + .module = shader.frag, + }; + + var shader_stages = [_]vk.VkPipelineShaderStageCreateInfo{ + vert_shader_stage_info, + frag_shader_stage_info, + }; + + var dynamic_states = [_]vk.VkDynamicState{ + vk.VK_DYNAMIC_STATE_VIEWPORT, + vk.VK_DYNAMIC_STATE_SCISSOR, + }; + + var dynamic_state: vk.VkPipelineDynamicStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pDynamicStates = &dynamic_states, + .dynamicStateCount = dynamic_states.len, + }; + + var vertex_binding: vk.VkVertexInputBindingDescription = undefined; + var vertex_binding_present = false; + if (config.vertex_binding) |binding| { + vertex_binding = .{ + .binding = binding.binding, + .stride = binding.stride, + .inputRate = switch (binding.input_rate) { + .vertex => vk.VK_VERTEX_INPUT_RATE_VERTEX, + .instance => vk.VK_VERTEX_INPUT_RATE_INSTANCE, + }, + }; + vertex_binding_present = true; + } + + const vertex_attributes = try alloc.alloc(vk.VkVertexInputAttributeDescription, config.vertex_attributes.len); + defer alloc.free(vertex_attributes); + for (config.vertex_attributes, 0..) |attr, i| { + vertex_attributes[i] = .{ + .location = attr.location, + .binding = attr.binding, + .format = switch (attr.format) { + .int => vk.VK_FORMAT_R32_SINT, + .float => vk.VK_FORMAT_R32_SFLOAT, + .vec2 => vk.VK_FORMAT_R32G32_SFLOAT, + .vec3 => vk.VK_FORMAT_R32G32B32_SFLOAT, + .vec4 => vk.VK_FORMAT_R32G32B32A32_SFLOAT, + }, + .offset = attr.offset, + }; + } + + var vert_input_info: vk.VkPipelineVertexInputStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + + .vertexBindingDescriptionCount = if (vertex_binding_present) 1 else 0, + .pVertexBindingDescriptions = if (vertex_binding_present) &vertex_binding else null, + + .vertexAttributeDescriptionCount = @intCast(vertex_attributes.len), + .pVertexAttributeDescriptions = vertex_attributes.ptr, + }; + + var input_assembly: vk.VkPipelineInputAssemblyStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = switch (config.topology) { + .triangle_list => vk.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .line_list => vk.VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + }, + .primitiveRestartEnable = vk.VK_FALSE, + }; + + var viewport: vk.VkViewport = .{ + .x = 0.0, + .y = 0.0, + .width = @floatFromInt(swapchain.extent.width), + .height = @floatFromInt(swapchain.extent.height), + .minDepth = 0, + .maxDepth = 1, + }; + + var scissor: vk.VkRect2D = .{ + .offset = .{ .x = 0, .y = 0 }, + .extent = swapchain.extent, + }; + + var viewport_state: vk.VkPipelineViewportStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + + .pViewports = &viewport, + .viewportCount = 1, + + .pScissors = &scissor, + .scissorCount = 1, + }; + + var rasterizer: vk.VkPipelineRasterizationStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = vk.VK_FALSE, + .rasterizerDiscardEnable = vk.VK_FALSE, + .polygonMode = vk.VK_POLYGON_MODE_FILL, + .cullMode = switch (config.cull_mode) { + .front_and_back => vk.VK_CULL_MODE_FRONT_AND_BACK, + .front => vk.VK_CULL_MODE_FRONT_BIT, + .back => vk.VK_CULL_MODE_BACK_BIT, + .none => vk.VK_CULL_MODE_NONE, + }, + .frontFace = switch (config.front_face) { + .cw => vk.VK_FRONT_FACE_CLOCKWISE, + .ccw => vk.VK_FRONT_FACE_COUNTER_CLOCKWISE, + }, + .depthBiasEnable = vk.VK_FALSE, + .lineWidth = 1.0, + }; + + var multisampling: vk.VkPipelineMultisampleStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = vk.VK_SAMPLE_COUNT_1_BIT, + .minSampleShading = 1.0, + }; + + var color_blend_attachment: vk.VkPipelineColorBlendAttachmentState = .{ + .colorWriteMask = vk.VK_COLOR_COMPONENT_R_BIT | vk.VK_COLOR_COMPONENT_G_BIT | vk.VK_COLOR_COMPONENT_B_BIT | vk.VK_COLOR_COMPONENT_A_BIT, + + .srcColorBlendFactor = vk.VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = vk.VK_BLEND_FACTOR_ZERO, + + .srcAlphaBlendFactor = vk.VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = vk.VK_BLEND_FACTOR_ZERO, + + .colorBlendOp = vk.VK_BLEND_OP_ADD, + .alphaBlendOp = vk.VK_BLEND_OP_ADD, + }; + + var color_blending: vk.VkPipelineColorBlendStateCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = vk.VK_FALSE, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + }; + + var rendering_info: vk.VkPipelineRenderingCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapchain.format, + }; + + var pipeline_info: vk.VkGraphicsPipelineCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + + .stageCount = shader_stages.len, + .pStages = &shader_stages, + + .pVertexInputState = &vert_input_info, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &color_blending, + .pDynamicState = &dynamic_state, + + .layout = pipeline_layout, + + .renderPass = null, + .subpass = 0, + + .pNext = &rendering_info, + }; + + var pipeline: vk.VkPipeline = null; + if (vk.vkCreateGraphicsPipelines(device.raw, null, 1, &pipeline_info, null, &pipeline) != vk.VK_SUCCESS) { + return error.FailedToCreateGraphicsPipelines; + } + + return .{ + .raw = pipeline.?, + .layout = pipeline_layout.?, + .device = device, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + vk.vkDestroyPipelineLayout(self.device.raw, self.layout, null); + vk.vkDestroyPipeline(self.device.raw, self.raw, null); +} diff --git a/src/rhi/vulkan/resource/shader.zig b/src/rhi/vulkan/resource/shader.zig index e69de29..079ac0c 100644 --- a/src/rhi/vulkan/resource/shader.zig +++ b/src/rhi/vulkan/resource/shader.zig @@ -0,0 +1,84 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Shader `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const gtl = @import("gtl"); +const Device = @import("device.zig"); +const Self = @This(); + +// +// FIELDS +// + +vert: *vk.VkShaderModule_T, +frag: *vk.VkShaderModule_T, +device: *const Device, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + vert_spv_path: []const u8, + frag_spv_path: []const u8, + device: *const Device, + alloc: std.mem.Allocator, + io: std.Io, +) !Self { + const vert_code = try readFile(vert_spv_path, alloc, io); + defer alloc.free(vert_code); + + const frag_code = try readFile(frag_spv_path, alloc, io); + defer alloc.free(frag_code); + + const vert_module = try createModule(vert_code, device); + const frag_module = try createModule(frag_code, device); + + return .{ + .vert = vert_module, + .frag = frag_module, + .device = device, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + vk.vkDestroyShaderModule(self.device.raw, self.vert, null); + vk.vkDestroyShaderModule(self.device.raw, self.frag, null); +} + +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn createModule( + code: []u8, + device: *const Device, +) !*vk.VkShaderModule_T { + var create_info: vk.VkShaderModuleCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pCode = @ptrCast(@alignCast(code.ptr)), + .codeSize = code.len, + }; + + var module: vk.VkShaderModule = null; + if (vk.vkCreateShaderModule(device.raw, &create_info, null, &module) != vk.VK_SUCCESS) { + return error.FailedToCreateShaderModule; + } + + return module.?; +} + +/// ---------------------------------------------------- +/// Helper +/// ---------------------------------------------------- +fn readFile( + path: []const u8, + alloc: std.mem.Allocator, + io: std.Io, +) ![]u8 { + return std.Io.Dir.cwd().readFileAlloc(io, path, alloc, .unlimited) catch |err| { + gtl.log.err("{s}: {s}\n", .{ @errorName(err), path }, null); + return err; + }; +} diff --git a/src/rhi/vulkan/resource/surface.zig b/src/rhi/vulkan/resource/surface.zig new file mode 100644 index 0000000..17f9ff0 --- /dev/null +++ b/src/rhi/vulkan/resource/surface.zig @@ -0,0 +1,53 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Surface `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const vk = @import("vulkan"); +const Instance = @import("../instance.zig"); +const Window = @import("../../root.zig").Window; +const Self = @This(); + +// +// FIELDS +// + +raw: *vk.VkSurfaceKHR_T, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + instance: *Instance, + window: Window, +) !Self { + const surface: *vk.VkSurfaceKHR_T = blk: { + var raw: vk.VkSurfaceKHR = null; + switch (window) { + .xlib => |v| { + var info: vk.VkXlibSurfaceCreateInfoKHR = .{ + .sType = vk.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .window = v.window, + .dpy = @ptrCast(v.display), + }; + + if (vk.vkCreateXlibSurfaceKHR(instance.raw, &info, null, &raw) != vk.VK_SUCCESS) { + return error.FailedToCreateXlibSurfaceKHR; + } + + break :blk raw.?; + }, + else => return error.NotImplementedYet, + } + return error.FailedToCreateSurface; + }; + + return .{ + .raw = surface, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self, instance: *Instance) void { + vk.vkDestroySurfaceKHR(instance.raw, self.raw, null); +} diff --git a/src/rhi/vulkan/resource/swapchain.zig b/src/rhi/vulkan/resource/swapchain.zig new file mode 100644 index 0000000..48f4e75 --- /dev/null +++ b/src/rhi/vulkan/resource/swapchain.zig @@ -0,0 +1,169 @@ +//! ---------------------------------------------------- +//! `🗲` Vulkan Swapchain `🗲` +//! ---------------------------------------------------- + +const std = @import("std"); +const gtl = @import("gtl"); +const vk = @import("vulkan"); +const Self = @This(); + +const Adapter = @import("adapter.zig"); +const Device = @import("device.zig"); +const Surface = @import("surface.zig"); + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Config = struct { + width: u32, + height: u32, +}; + +// +// FIELDS +// + +raw: *vk.VkSwapchainKHR_T, + +images: []vk.VkImage, +image_views: []vk.VkImageView, + +format: vk.VkFormat, +extent: vk.VkExtent2D, + +device: *const Device, +alloc: std.mem.Allocator, + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn init( + adapter: *const Adapter, + device: *const Device, + surface: *const Surface, + config: Config, + alloc: std.mem.Allocator, +) !Self { + // --- # --- + var capabilities: vk.VkSurfaceCapabilitiesKHR = .{}; + _ = vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(adapter.raw, surface.raw, &capabilities); + + // --- # --- + var format_count: u32 = 0; + _ = vk.vkGetPhysicalDeviceSurfaceFormatsKHR(adapter.raw, surface.raw, &format_count, null); + + const formats = try alloc.alloc(vk.VkSurfaceFormatKHR, format_count); + defer alloc.free(formats); + _ = vk.vkGetPhysicalDeviceSurfaceFormatsKHR(adapter.raw, surface.raw, &format_count, formats.ptr); + + // --- # --- + var present_mode_count: u32 = 0; + _ = vk.vkGetPhysicalDeviceSurfacePresentModesKHR(adapter.raw, surface.raw, &present_mode_count, null); + + const present_modes = try alloc.alloc(vk.VkPresentModeKHR, present_mode_count); + defer alloc.free(present_modes); + _ = vk.vkGetPhysicalDeviceSurfacePresentModesKHR(adapter.raw, surface.raw, &present_mode_count, present_modes.ptr); + + // --- # --- + const extent: vk.VkExtent2D = blk: { + if (capabilities.currentExtent.width != std.math.maxInt(u32)) break :blk capabilities.currentExtent; + break :blk .{ .width = config.width, .height = config.height }; + }; + + // --- # --- + const format: vk.VkSurfaceFormatKHR = blk: { + for (formats) |v| { + if (v.format == vk.VK_FORMAT_B8G8R8A8_SRGB and v.colorSpace == vk.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + break :blk v; + } + } + break :blk formats[0]; + }; + + // --- # --- + var image_count: u32 = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 and image_count > capabilities.maxImageCount) { + image_count = capabilities.maxImageCount; + } + + var create_info: vk.VkSwapchainCreateInfoKHR = .{ + .sType = vk.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface.raw, + .minImageCount = image_count, + .imageFormat = format.format, + .imageColorSpace = format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = vk.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + + .preTransform = capabilities.currentTransform, + .compositeAlpha = vk.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_modes[0], + .clipped = 1, + + .imageSharingMode = vk.VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = null, + }; + + var swapchain: vk.VkSwapchainKHR = null; + if (vk.vkCreateSwapchainKHR(device.raw, &create_info, null, &swapchain) != vk.VK_SUCCESS) { + return error.FailedToCreateSwapchainKHR; + } + + // --- GET IMAGES --- + _ = vk.vkGetSwapchainImagesKHR(device.raw, swapchain, &image_count, null); + const images = try alloc.alloc(vk.VkImage, image_count); + _ = vk.vkGetSwapchainImagesKHR(device.raw, swapchain, &image_count, images.ptr); + + // --- GET IMAGE VIEWS --- + const image_views = try alloc.alloc(vk.VkImageView, images.len); + for (images, 0..) |image, i| { + var image_view_create_info: vk.VkImageViewCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = image, + .viewType = vk.VK_IMAGE_VIEW_TYPE_2D, + .format = format.format, + .components = .{ + .r = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .g = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .b = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + .a = vk.VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = .{ + .aspectMask = vk.VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + if (vk.vkCreateImageView(device.raw, &image_view_create_info, null, &image_views[i]) != vk.VK_SUCCESS) { + return error.FailedToCreateImageView; + } + } + + return .{ + .raw = swapchain.?, + .images = images, + .image_views = image_views, + + .extent = extent, + .format = format.format, + + .device = device, + .alloc = alloc, + }; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn deinit(self: *Self) void { + for (self.image_views) |view| { + vk.vkDestroyImageView(self.device.raw, view, null); + } + + vk.vkDestroySwapchainKHR(self.device.raw, self.raw, null); + + self.alloc.free(self.images); + self.alloc.free(self.image_views); +} diff --git a/src/rhi/vulkan/vulkan.zig b/src/rhi/vulkan/vulkan.zig index c103849..f901923 100644 --- a/src/rhi/vulkan/vulkan.zig +++ b/src/rhi/vulkan/vulkan.zig @@ -4,59 +4,570 @@ const std = @import("std"); const vk = @import("vulkan"); +const gtl = @import("gtl"); +const common = @import("../root.zig"); const Instance = @import("instance.zig"); -const Surface = @import("surface.zig"); -const Device = @import("device.zig"); - const Resource = @import("resource.zig"); + const MainConfig = @import("../../gfx.zig").Config; +const Handle = gtl.Handle; const Self = @This(); +// --- ENUMS --- +const BufferUsage = common.BufferUsage; +const BufferSharingMode = common.BufferSharingMode; + +// --- CONFIGS --- +const PipelineConfig = common.PipelineConfig; + +// --- HANDLES --- +const Adapter = common.Adapter; +const Device = common.Device; +const Window = common.Window; +const Surface = common.Surface; +const Swapchain = common.Swapchain; +const Buffer = common.Buffer; +const Shader = common.Shader; +const Pipeline = common.Pipeline; + +// --- # --- +const MAX_FRAMES_IN_FLIGHT: usize = 3; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const Frame = struct { + cmd_buf: vk.VkCommandBuffer = null, + image_index: u32 = 0, + + image_available: vk.VkSemaphore = null, + + fence: vk.VkFence = null, +}; + // // FIELDS // instance: Instance, -surface: Surface, -device: Device, - resource: Resource, +config: MainConfig, + +frames: [MAX_FRAMES_IN_FLIGHT]Frame = .{ .{}, .{}, .{} }, +current_frame: usize = 0, + +render_finished: []vk.VkSemaphore = &.{}, + +sync_device: ?*vk.VkDevice_T = null, + alloc: std.mem.Allocator, +io: std.Io, + +pub fn currentFrame(self: *Self) *Frame { + return &self.frames[self.current_frame]; +} /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn init( config: MainConfig, alloc: std.mem.Allocator, + io: std.Io, ) !Self { // --- INSTANCE --- var instance: Instance = try .init(config, alloc); errdefer instance.deinit(); - // --- SURFACE --- - var surface: Surface = try .init(&instance, config.window); - errdefer surface.deinit(&instance); - - // --- DEVICE --- - var device: Device = try .init(&instance, alloc); - errdefer device.deinit(); - return .{ .instance = instance, - .surface = surface, - .device = device, - .resource = .init(alloc), + .config = config, .alloc = alloc, + .io = io, }; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - self.surface.deinit(&self.instance); - self.resource.deinit(); - self.device.deinit(); + if (self.sync_device) |device| { + _ = vk.vkDeviceWaitIdle(device); + + for (self.render_finished) |semaphore| { + vk.vkDestroySemaphore(device, semaphore, null); + } + self.alloc.free(self.render_finished); + + for (&self.frames) |*frame| { + if (frame.fence) |fence| { + vk.vkDestroyFence(device, fence, null); + } + if (frame.image_available) |semaphore| { + vk.vkDestroySemaphore(device, semaphore, null); + } + } + } + + self.resource.deinit(&self.instance); self.instance.deinit(); } + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createAdapter(self: *Self) !Handle(Adapter) { + return try self.resource.adapters.put(try .init(self)); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createDevice( + self: *Self, + adapter: Handle(Adapter), + surface: Handle(Surface), +) !Handle(Device) { + const raw_adapter = self.resource.adapters.get(adapter) orelse return error.AdapterNotFound; + const raw_surface = self.resource.surfaces.get(surface) orelse return error.SurfaceNotFound; + return try self.resource.devices.put(try .init(raw_adapter, raw_surface, self.alloc)); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn destroyDevice(self: *Self, device: Handle(Device)) !void { + if (self.resource.devices.get(device)) |raw| { + raw.deinit(); + try self.resource.devices.remove(device); + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createSurface(self: *Self) !Handle(Surface) { + return try self.resource.surfaces.put(try .init(&self.instance, self.config.window)); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createSwapchain( + self: *Self, + adapter: Handle(Adapter), + device: Handle(Device), + surface: Handle(Surface), +) !Handle(Swapchain) { + const raw_adapter = self.resource.adapters.get(adapter) orelse return error.AdapterNotFound; + const raw_device = self.resource.devices.get(device) orelse return error.DeviceNotFound; + const raw_surface = self.resource.surfaces.get(surface) orelse return error.SurfaceNotFound; + + return try self.resource.swapchains.put(try .init( + raw_adapter, + raw_device, + raw_surface, + .{ .width = 800, .height = 600 }, + self.alloc, + )); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createShader( + self: *Self, + vert_spv_path: []const u8, + frag_spv_path: []const u8, + device: Handle(Device), +) !Handle(Shader) { + const raw_device = self.resource.devices.get(device) orelse return error.DeviceNotFound; + return try self.resource.shaders.put(try .init(vert_spv_path, frag_spv_path, raw_device, self.alloc, self.io)); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createPipeline(self: *Self, config: PipelineConfig) !Handle(Pipeline) { + const raw_device = self.resource.devices.get(config.device) orelse return error.DeviceNotFound; + const raw_swapchain = self.resource.swapchains.get(config.swapchain) orelse return error.SwapchainNotFound; + const raw_shader = self.resource.shaders.get(config.shader) orelse return error.ShaderNotFound; + return try self.resource.pipelines.put(try .init(raw_shader, raw_swapchain, raw_device, config, self.alloc)); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn createBuffer( + self: *Self, + size: u32, + usage: BufferUsage, + sharing: BufferSharingMode, + adapter: Handle(Adapter), + device: Handle(Device), +) !Handle(Buffer) { + const raw_adapter = self.resource.adapters.get(adapter) orelse return error.AdapterNotFound; + const raw_device = self.resource.devices.get(device) orelse return error.DeviceNotFound; + return try self.resource.buffers.put(try .init(size, usage, sharing, raw_adapter, raw_device)); +} + +// /// ---------------------------------------------------- +// /// ---------------------------------------------------- +// pub const AttributeDesc = struct { +// binding: u32, +// location: u32, +// uniform_type: UniformType, +// offset: u32, +// }; + +// /// ---------------------------------------------------- +// /// ---------------------------------------------------- +// pub fn createUniform( +// self: *Self, +// attributes: []const AttributeDesc, +// ) !void { +// _ = self; +// _ = attributes; +// } + +// +// FRAME + RENDERING +// + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn beginFrame( + self: *Self, + swapchain: Handle(Swapchain), + device: Handle(Device), +) !void { + const raw_device = self.resource.devices.get(device) orelse return error.DeviceNotFound; + const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; + + try self.ensureFrameSync(raw_device.raw, raw_swapchain.raw); + + const frame = self.currentFrame(); + + if (frame.fence) |fence| { + _ = vk.vkWaitForFences(raw_device.raw, 1, &fence, vk.VK_TRUE, std.math.maxInt(u64)); + _ = vk.vkResetFences(raw_device.raw, 1, &fence); + } + + if (frame.cmd_buf == null) { + const alloc_info: vk.VkCommandBufferAllocateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = raw_device.graphics_command_pool, + .level = vk.VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + + if (vk.vkAllocateCommandBuffers( + raw_device.raw, + &alloc_info, + &frame.cmd_buf, + ) != vk.VK_SUCCESS) { + return error.FailedToAllocateCommandBuffers; + } + } + + const begin_info: vk.VkCommandBufferBeginInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = vk.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = null, + }; + + if (vk.vkBeginCommandBuffer( + frame.cmd_buf, + &begin_info, + ) != vk.VK_SUCCESS) { + return error.FailedToBeginCommandBuffer; + } + + const result = vk.vkAcquireNextImageKHR( + raw_device.raw, + raw_swapchain.raw, + std.math.maxInt(u64), + frame.image_available, + null, + &frame.image_index, + ); + + switch (result) { + vk.VK_SUCCESS, vk.VK_SUBOPTIMAL_KHR => {}, + vk.VK_ERROR_OUT_OF_DATE_KHR => return error.SwapchainOutOfDate, + else => return error.FailedToAcquireSwapchainImage, + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn endFrame( + self: *Self, + swapchain: Handle(Swapchain), + device: Handle(Device), +) !void { + const raw_device = self.resource.devices.get(device) orelse return error.DeviceNotFound; + const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; + + const frame = self.currentFrame(); + + if (vk.vkEndCommandBuffer(frame.cmd_buf) != vk.VK_SUCCESS) { + return error.FailedToEndCommandBuffer; + } + + const render_finished = self.render_finished[frame.image_index]; + + const wait_stage = [_]u32{ + vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + }; + + const submit_info: vk.VkSubmitInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_SUBMIT_INFO, + + .waitSemaphoreCount = 1, + .pWaitSemaphores = &frame.image_available, + .pWaitDstStageMask = &wait_stage, + + .commandBufferCount = 1, + .pCommandBuffers = &frame.cmd_buf, + + .signalSemaphoreCount = 1, + .pSignalSemaphores = &render_finished, + }; + + if (vk.vkQueueSubmit(raw_device.graphics_queue, 1, &submit_info, frame.fence) != vk.VK_SUCCESS) { + return error.FailedToSubmitCommandBuffer; + } + + const present_info: vk.VkPresentInfoKHR = .{ + .sType = vk.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + + .waitSemaphoreCount = 1, + .pWaitSemaphores = &render_finished, + + .swapchainCount = 1, + .pSwapchains = &raw_swapchain.raw, + + .pImageIndices = &frame.image_index, + }; + + _ = vk.vkQueuePresentKHR( + raw_device.present_queue, + &present_info, + ); + + self.current_frame = (self.current_frame + 1) % MAX_FRAMES_IN_FLIGHT; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn beginRendering( + self: *Self, + swapchain: Handle(Swapchain), +) !void { + const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; + + const frame = self.currentFrame(); + + const barrier: vk.VkImageMemoryBarrier = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + + .srcAccessMask = 0, + .dstAccessMask = vk.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + + .oldLayout = vk.VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + + .srcQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + + .image = raw_swapchain.images[frame.image_index], + + .subresourceRange = .{ + .aspectMask = vk.VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vk.vkCmdPipelineBarrier( + frame.cmd_buf, + + vk.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + + 0, + + 0, + null, + + 0, + null, + + 1, + &barrier, + ); + + const color_attachment: vk.VkRenderingAttachmentInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = raw_swapchain.image_views[frame.image_index], + .imageLayout = vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = vk.VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = vk.VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = .{ + .color = .{ + .float32 = .{ 0.01, 0.01, 0.01, 1.0 }, + }, + }, + }; + + const rendering_info: vk.VkRenderingInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_RENDERING_INFO, + .renderArea = .{ + .offset = .{ + .x = 0, + .y = 0, + }, + .extent = raw_swapchain.extent, + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment, + }; + + vk.vkCmdBeginRendering( + frame.cmd_buf, + &rendering_info, + ); + + const viewport: vk.VkViewport = .{ + .x = 0, + .y = 0, + .width = @floatFromInt(raw_swapchain.extent.width), + .height = @floatFromInt(raw_swapchain.extent.height), + .minDepth = 0.0, + .maxDepth = 1.0, + }; + + const scissor: vk.VkRect2D = .{ + .offset = .{ + .x = 0, + .y = 0, + }, + .extent = raw_swapchain.extent, + }; + + vk.vkCmdSetViewport(frame.cmd_buf, 0, 1, &viewport); + vk.vkCmdSetScissor(frame.cmd_buf, 0, 1, &scissor); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn endRendering( + self: *Self, + swapchain: Handle(Swapchain), +) !void { + const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; + + const frame = self.currentFrame(); + + vk.vkCmdEndRendering(frame.cmd_buf); + + const barrier: vk.VkImageMemoryBarrier = .{ + .sType = vk.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + + .srcAccessMask = vk.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = 0, + + .oldLayout = vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .newLayout = vk.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + + .srcQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = vk.VK_QUEUE_FAMILY_IGNORED, + + .image = raw_swapchain.images[frame.image_index], + + .subresourceRange = .{ + .aspectMask = vk.VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vk.vkCmdPipelineBarrier( + frame.cmd_buf, + + vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + vk.VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + + 0, + + 0, + null, + + 0, + null, + + 1, + &barrier, + ); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn bindPipeline(self: *Self, pipeline: Handle(Pipeline)) !void { + const raw_pipeline = self.resource.pipelines.get(pipeline) orelse return error.PipelineNotFound; + vk.vkCmdBindPipeline(self.currentFrame().cmd_buf, vk.VK_PIPELINE_BIND_POINT_GRAPHICS, raw_pipeline.raw); +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn draw(self: *Self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) !void { + vk.vkCmdDraw(self.currentFrame().cmd_buf, vertex_count, instance_count, first_vertex, first_instance); +} + +/// ---------------------------------------------------- +/// Lazily create the frame sync objects (semaphores + fence) +/// ---------------------------------------------------- +fn ensureFrameSync( + self: *Self, + device: *vk.VkDevice_T, + raw_swapchain: *vk.VkSwapchainKHR_T, +) !void { + if (self.sync_device != null) return; + self.sync_device = device; + + const semaphore_info: vk.VkSemaphoreCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + const fence_info: vk.VkFenceCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = vk.VK_FENCE_CREATE_SIGNALED_BIT, + }; + + var image_count: u32 = 0; + _ = vk.vkGetSwapchainImagesKHR(device, raw_swapchain, &image_count, null); + + const render_finished = try self.alloc.alloc(vk.VkSemaphore, image_count); + errdefer self.alloc.free(render_finished); + + for (render_finished, 0..) |*semaphore, i| { + _ = i; + if (vk.vkCreateSemaphore(device, &semaphore_info, null, semaphore) != vk.VK_SUCCESS) { + return error.FailedToCreateSemaphore; + } + } + self.render_finished = render_finished; + + for (&self.frames) |*frame| { + var image_available: vk.VkSemaphore = null; + if (vk.vkCreateSemaphore(device, &semaphore_info, null, &image_available) != vk.VK_SUCCESS) { + return error.FailedToCreateSemaphore; + } + + var fence: vk.VkFence = null; + if (vk.vkCreateFence(device, &fence_info, null, &fence) != vk.VK_SUCCESS) { + vk.vkDestroySemaphore(device, image_available, null); + return error.FailedToCreateFence; + } + + frame.image_available = image_available.?; + frame.fence = fence.?; + } +} diff --git a/tools/shaders.zig b/tools/shaders.zig new file mode 100644 index 0000000..8db7e3f --- /dev/null +++ b/tools/shaders.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + _ = init; +}