diff --git a/README.md b/README.md index 2ac2875..ca724e5 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,22 @@ Low cortisol crossplatform RHI (Rendering Hardware Interface) - Dead simple + Performance + Flexible - Handles -# Examples +## Architecture +```text +examples/simple/simple.zig + +src/gfx.zig + └─ Selected Backend + +src/rhi/root.zig + └─ Common / Handles / Configs + +src/rhi/vulkan/vulkan.zig + └─ Vulkan Backend / RHI + +src/rhi/vulkan/resource.zig + └─ Adapter / Device / Shader +``` + +## Examples - ```zig build run-simple``` diff --git a/assets/shaders/compiled/simple.frag.spv b/assets/shaders/compiled/simple.frag.spv deleted file mode 100644 index fa65e2c..0000000 Binary files a/assets/shaders/compiled/simple.frag.spv and /dev/null differ diff --git a/assets/shaders/compiled/simple.vert.spv b/assets/shaders/compiled/simple.vert.spv deleted file mode 100644 index 6195065..0000000 Binary files a/assets/shaders/compiled/simple.vert.spv and /dev/null differ diff --git a/assets/shaders/simple.frag b/assets/shaders/simple.frag deleted file mode 100644 index 0fda67f..0000000 --- a/assets/shaders/simple.frag +++ /dev/null @@ -1,7 +0,0 @@ -#version 450 - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = vec4(0.2, 0.2, 0.8, 1.0); -} diff --git a/examples/assets/shaders/compiled/simple.frag.spv b/examples/assets/shaders/compiled/simple.frag.spv new file mode 100644 index 0000000..2fa8f39 Binary files /dev/null and b/examples/assets/shaders/compiled/simple.frag.spv differ diff --git a/examples/assets/shaders/compiled/simple.vert.spv b/examples/assets/shaders/compiled/simple.vert.spv new file mode 100644 index 0000000..ccbe25d Binary files /dev/null and b/examples/assets/shaders/compiled/simple.vert.spv differ diff --git a/examples/assets/shaders/simple.frag b/examples/assets/shaders/simple.frag new file mode 100644 index 0000000..7ab54a5 --- /dev/null +++ b/examples/assets/shaders/simple.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(binding = 0) uniform UnlitMaterial { + vec4 albedo; +} material; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = material.albedo; +} diff --git a/assets/shaders/simple.vert b/examples/assets/shaders/simple.vert similarity index 72% rename from assets/shaders/simple.vert rename to examples/assets/shaders/simple.vert index 3b34e99..dd85e36 100644 --- a/assets/shaders/simple.vert +++ b/examples/assets/shaders/simple.vert @@ -1,5 +1,9 @@ #version 450 +layout(binding = 0) uniform UnlitMaterial { + vec4 albedo; +} material; + layout(location = 0) in vec2 inPosition; layout(location = 1) in vec2 inNormal; layout(location = 2) in vec2 inUV; diff --git a/examples/simple/simple.zig b/examples/simple/simple.zig index 946a799..3e764da 100644 --- a/examples/simple/simple.zig +++ b/examples/simple/simple.zig @@ -5,6 +5,12 @@ const std = @import("std"); const sdl = @import("sdl"); const Gfx = @import("gfx"); +/// ---------------------------------------------------- +/// ---------------------------------------------------- +const UnlitMaterial = struct { + albedo: [4]f32 = .{ 1, 1, 1, 1 }, +}; + /// ---------------------------------------------------- /// ---------------------------------------------------- const Vertex = struct { @@ -33,6 +39,12 @@ const indices = [_]u32{ /// ---------------------------------------------------- pub fn main(init: std.process.Init) !void { + // + // DATA + // + + var material: UnlitMaterial = .{ .albedo = .{ 1.0, 0.2, 0.2, 1.0 } }; + // // SDL // @@ -42,7 +54,7 @@ pub fn main(init: std.process.Init) !void { defer sdl.SDL_Quit(); // --- # --- - const window = sdl.SDL_CreateWindow("NYXGFX", 800, 600, 0) orelse return error.FailedToCreateWindow; + const window = sdl.SDL_CreateWindow("NYXGFX", 800, 600, 0) orelse return error.FailedTomakeWindow; defer sdl.SDL_DestroyWindow(window); // --- # --- @@ -66,23 +78,21 @@ pub fn main(init: std.process.Init) !void { defer gfx.deinit(); // --- # --- - 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 adapter = try gfx.makeAdapter(.{}); + const surface = try gfx.makeSurface(); + const device = try gfx.makeDevice(adapter, surface); + const swapchain = try gfx.makeSwapchain(adapter, device, surface); // --- BUFFERS --- - const vbuf = try gfx.createBuffer(vertices, .vertex, adapter, device); - const ibuf = try gfx.createBuffer(indices, .index, adapter, device); + const vbuf = try gfx.makeBuffer(vertices, .vertex, adapter, device); + const ibuf = try gfx.makeBuffer(indices, .index, adapter, device); // --- SHADERS --- - const simple_shader = try gfx.createShader( - "assets/shaders/compiled/simple.vert.spv", - "assets/shaders/compiled/simple.frag.spv", - device, - ); + const simple_vert = try gfx.makeShader("examples/assets/shaders/compiled/simple.vert.spv", device); + const simple_frag = try gfx.makeShader("examples/assets/shaders/compiled/simple.frag.spv", device); - const pipeline = try gfx.createPipeline(.{ + // --- PIPELINE --- + const pipeline = try gfx.makePipeline(.{ .vertex_attributes = &.{ .{ .location = 0, .format = .vec2, .offset = @offsetOf(Vertex, "pos") }, .{ .location = 1, .format = .vec2, .offset = @offsetOf(Vertex, "normal") }, @@ -90,18 +100,24 @@ pub fn main(init: std.process.Init) !void { }, .vertex_binding = .{ .stride = @sizeOf(Vertex) }, - .shader = simple_shader, + .uniforms = &.{ + .{ .name = "material", .offset = 0, .size = @sizeOf(UnlitMaterial) }, + }, + + .vert_shader = simple_vert, + .frag_shader = simple_frag, + .device = device, .swapchain = swapchain, }); - try pipeline.setUniform("uTime", 0.1); - try pipeline.setUniform("uTime", 0.1); - + // --- MISC --- var event: sdl.SDL_Event = undefined; var running: bool = true; + var i: f32 = 0; while (running) { + // --- INPUTS --- while (sdl.SDL_PollEvent(&event)) { switch (event.type) { sdl.SDL_EVENT_QUIT => running = false, @@ -109,24 +125,34 @@ pub fn main(init: std.process.Init) !void { } } + // --- ANIMATION --- + i += 0.001; + i = @mod(i, 1); + material.albedo[1] = i; + try pipeline.setUniform("material", material); + + // --- FRAME --- try gfx.beginFrame(swapchain, device); defer gfx.endFrame(swapchain, device) catch unreachable; - { - try gfx.beginRendering(swapchain); - defer gfx.endRendering(swapchain) catch unreachable; + // --- NEW PASS --- + if (gfx.beginPass(swapchain, .{ + .clear_color = .{ 0.01, 0.01, 0.01, 1.0 }, + })) |pass| { + defer pass.end(); - try pipeline.bind(); try vbuf.bind(); try ibuf.bind(); - try gfx.drawIndexed(indices.len, 1, 0, 0, 0); + + try pipeline.bind(); + pass.drawIndexed(indices.len, 1, 0, 0, 0); } - // if (gfx.beginRendering(...)) |pass| { - // defer pass.end(); - // - // try pass.bindPipeline(...); - // try pass.draw(...); - // } + // --- NEW PASS --- + if (gfx.beginPass(swapchain, .{ + .load_op = .load, + })) |pass| { + defer pass.end(); + } } } diff --git a/src/gfx.zig b/src/gfx.zig index ac4ea09..10d5884 100644 --- a/src/gfx.zig +++ b/src/gfx.zig @@ -11,8 +11,11 @@ const Handle = gtl.Handle; const Self = @This(); // --- CONFIGS --- +const AdapterConfig = common.AdapterConfig; +const RenderPassConfig = common.RenderPassConfig; const PipelineConfig = common.PipelineConfig; const BufferUsage = common.BufferUsage; +const Pass = common.Pass; // --- HANDLES --- const Window = @import("rhi/root.zig").Window; @@ -88,21 +91,21 @@ pub fn deinit(self: *Self) void { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createAdapter(self: *Self) !Handle(Adapter) { +pub fn makeAdapter(self: *Self, config: AdapterConfig) !Handle(Adapter) { return try switch (self.ctx) { - .vulkan => |*v| v.createAdapter(), + .vulkan => |*v| v.makeAdapter(config), }; } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createDevice( +pub fn makeDevice( self: *Self, adapter: Handle(Adapter), surface: Handle(Surface), ) !Handle(Device) { return try switch (self.ctx) { - .vulkan => |*v| v.createDevice(adapter, surface), + .vulkan => |*v| v.makeDevice(adapter, surface), }; } @@ -116,7 +119,7 @@ pub fn destroyDevice(self: *Self, device: Handle(Device)) !void { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createBuffer( +pub fn makeBuffer( self: *Self, value: anytype, usage: BufferUsage, @@ -126,7 +129,7 @@ pub fn createBuffer( const size = @sizeOf(@TypeOf(value)); const handle = try switch (self.ctx) { - .vulkan => |*v| v.createBuffer(size, usage, .exclusive, adapter, device), + .vulkan => |*v| v.makeBuffer(size, usage, .exclusive, adapter, device), }; var buffer: Buffer = .{ @@ -140,45 +143,52 @@ pub fn createBuffer( } /// ---------------------------------------------------- +/// TODO: /// ---------------------------------------------------- -pub fn createSurface(self: *Self) !Handle(Surface) { +pub fn getRawVKBuffer(self: *Self, buffer: Handle(Buffer)) !void { + _ = self; + _ = buffer; +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn makeSurface(self: *Self) !Handle(Surface) { return try switch (self.ctx) { - .vulkan => |*v| v.createSurface(), + .vulkan => |*v| v.makeSurface(), }; } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createSwapchain( +pub fn makeSwapchain( 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), + .vulkan => |*v| v.makeSwapchain(adapter, device, surface), }; } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createShader( +pub fn makeShader( self: *Self, - vert_spv_path: []const u8, - frag_spv_path: []const u8, + 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), + .vulkan => |*v| v.makeShader(spv_path, device), }; } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createPipeline(self: *Self, config: PipelineConfig) !Pipeline { +pub fn makePipeline(self: *Self, config: PipelineConfig) !Pipeline { return .{ .handle = try switch (self.ctx) { - .vulkan => |*v| v.createPipeline(config), + .vulkan => |*v| v.makePipeline(config), }, .device = config.device, .gfx = self, @@ -211,43 +221,17 @@ pub fn endFrame( /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn beginRendering( +pub fn beginPass( 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 { + config: RenderPassConfig, +) ?Pass { 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); - } - }, + .vulkan => |*v| v.beginRendering(swapchain, config) catch return null, } + + return .{ + .swapchain = swapchain, + .gfx = self, + }; } diff --git a/src/rhi/root.zig b/src/rhi/root.zig index 74a65a3..8482be9 100644 --- a/src/rhi/root.zig +++ b/src/rhi/root.zig @@ -32,7 +32,24 @@ pub const Window = union(enum) { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub const AdapterConfig = struct {}; +pub const GpuType = enum { + any, + discrete, + integrated, + software, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const AdapterConfig = struct { + /// Preferred class of GPU + /// `.any` disables filtering by class + gpu_type: GpuType = .discrete, + + /// Select the adapter at a specific enumeration index + /// `null` picks the first suitable one + index: ?u32 = null, +}; /// ---------------------------------------------------- /// ---------------------------------------------------- @@ -72,16 +89,28 @@ pub const VertexAttribute = struct { format: Format, }; +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const UniformDesc = struct { + name: []const u8, + offset: u32, + size: u32, +}; + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const PipelineConfig = struct { - shader: Handle(Shader), + vert_shader: Handle(Shader), + frag_shader: Handle(Shader), + swapchain: Handle(Swapchain), device: Handle(Device), vertex_binding: ?VertexBinding = null, vertex_attributes: []const VertexAttribute = &.{}, + uniforms: []const UniformDesc = &.{}, + topology: enum { triangle_list, line_list, @@ -100,6 +129,65 @@ pub const PipelineConfig = struct { } = .cw, }; +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Pass = struct { + swapchain: Handle(Swapchain), + gfx: *Gfx, + + pub fn end(self: *const Pass) void { + switch (self.gfx.ctx) { + .vulkan => |*v| v.endRendering(self.swapchain), + } + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn draw(self: *const Pass, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) void { + switch (self.gfx.ctx) { + .vulkan => |*v| v.draw(vertex_count, instance_count, first_vertex, first_instance) catch {}, + } + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn drawIndexed(self: *const Pass, index_count: u32, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32) void { + switch (self.gfx.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); + } + }, + } + } +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const LoadOp = enum { + clear, + load, + dont_care, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const StoreOp = enum { + store, + dont_care, +}; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const RenderPassConfig = struct { + clear_color: [4]f32 = .{ 0.0, 0.0, 0.0, 1.0 }, + load_op: LoadOp = .clear, + store_op: StoreOp = .store, + width: ?u32 = null, + height: ?u32 = null, +}; + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const BufferUsage = enum { @@ -175,8 +263,8 @@ pub const Pipeline = struct { } pub fn setUniform(self: *const Pipeline, name: []const u8, value: anytype) !void { - _ = self; - _ = name; - _ = value; + try switch (self.gfx.ctx) { + .vulkan => |*v| v.setUniform(self.handle, name, value), + }; } }; diff --git a/src/rhi/vulkan/resource/adapter.zig b/src/rhi/vulkan/resource/adapter.zig index 0c7c3d2..9e4142a 100644 --- a/src/rhi/vulkan/resource/adapter.zig +++ b/src/rhi/vulkan/resource/adapter.zig @@ -6,6 +6,8 @@ const std = @import("std"); const gtl = @import("gtl"); const vk = @import("vulkan"); const VKBackend = @import("../vulkan.zig"); +const AdapterConfig = @import("../../root.zig").AdapterConfig; +const GpuType = @import("../../root.zig").GpuType; const Self = @This(); // @@ -16,7 +18,7 @@ raw: *vk.VkPhysicalDevice_T, /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn init(backend: *const VKBackend) !Self { +pub fn init(backend: *const VKBackend, config: AdapterConfig) !Self { var physical: vk.VkPhysicalDevice = null; var count: u32 = 0; @@ -38,12 +40,18 @@ pub fn init(backend: *const VKBackend) !Self { } // --- CHECK WHICH DEVICE IS SUITABLE --- - for (devices) |device| { - if (try isDeviceSuitable(device)) { + for (devices, 0..) |device, i| { + if (config.index) |index| { + if (index != i) continue; + } + if (try isDeviceSuitable(device, config.gpu_type)) { physical = device; break; } } + if (physical == null) { + return error.FailedToFindSuitableDevice; + } // --- FIND GFX QUEUE FAMILY --- var queue_family_index: u32 = 0; @@ -83,7 +91,7 @@ fn findGraphicsQueueFamily( /// ---------------------------------------------------- /// ---------------------------------------------------- -fn isDeviceSuitable(device: vk.VkPhysicalDevice) !bool { +fn isDeviceSuitable(device: vk.VkPhysicalDevice, gpu_type: GpuType) !bool { var properties: vk.VkPhysicalDeviceProperties = .{}; vk.vkGetPhysicalDeviceProperties(device, &properties); @@ -102,5 +110,12 @@ fn isDeviceSuitable(device: vk.VkPhysicalDevice) !bool { }); } - return properties.deviceType == vk.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU and features.geometryShader == 1; + const matches_class = switch (gpu_type) { + .any => true, + .discrete => properties.deviceType == vk.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, + .integrated => properties.deviceType == vk.VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, + .software => properties.deviceType == vk.VK_PHYSICAL_DEVICE_TYPE_CPU, + }; + + return matches_class and features.geometryShader == 1; } diff --git a/src/rhi/vulkan/resource/device.zig b/src/rhi/vulkan/resource/device.zig index b461049..b0fa8a0 100644 --- a/src/rhi/vulkan/resource/device.zig +++ b/src/rhi/vulkan/resource/device.zig @@ -20,6 +20,7 @@ pub const QueueFamilies = struct { // raw: *vk.VkDevice_T, +physical_device: vk.VkPhysicalDevice, graphics_queue: *vk.VkQueue_T, present_queue: *vk.VkQueue_T, graphics_command_pool: *vk.VkCommandPool_T, @@ -109,6 +110,7 @@ pub fn init( return .{ .raw = device.?, + .physical_device = adapter.raw, .graphics_queue = graphics_queue.?, .present_queue = present_queue.?, .graphics_command_pool = graphics_command_pool.?, diff --git a/src/rhi/vulkan/resource/pipeline.zig b/src/rhi/vulkan/resource/pipeline.zig index c7e5498..654ad5a 100644 --- a/src/rhi/vulkan/resource/pipeline.zig +++ b/src/rhi/vulkan/resource/pipeline.zig @@ -5,55 +5,116 @@ const std = @import("std"); const vk = @import("vulkan"); const gtl = @import("gtl"); +const VulkanRHI = @import("../vulkan.zig"); const Self = @This(); const Device = @import("device.zig"); -const Shader = @import("shader.zig"); const Swapchain = @import("swapchain.zig"); const Config = @import("../../root.zig").PipelineConfig; +const UniformDesc = @import("../../root.zig").UniformDesc; // // FIELDS // -raw: *vk.VkPipeline_T, -layout: *vk.VkPipelineLayout_T, +raw: vk.VkPipeline = null, +layout: vk.VkPipelineLayout = null, device: *const Device, +descriptor_set_layout: vk.VkDescriptorSetLayout = null, +descriptor_pool: vk.VkDescriptorPool = null, +descriptor_set: vk.VkDescriptorSet = null, + +uniform_buffer: vk.VkBuffer = null, +uniform_memory: vk.VkDeviceMemory = null, +uniforms: []UniformDesc, + +alloc: std.mem.Allocator, + /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn init( - shader: *const Shader, swapchain: *const Swapchain, device: *const Device, config: Config, + vulkan: *VulkanRHI, alloc: std.mem.Allocator, ) !Self { + var self: Self = .{ + .device = device, + .uniforms = &.{}, + .alloc = alloc, + }; + errdefer self.deinit(); + + // --- COMPUTE UNIFORM LAYOUT --- + var uniform_size: u32 = 0; + for (config.uniforms) |u| { + uniform_size = @max(uniform_size, u.offset + u.size); + } + uniform_size = std.mem.alignForward(u32, uniform_size, 16); + + // --- COPY UNIFORMS --- + if (config.uniforms.len > 0) { + const uniforms = try alloc.alloc(UniformDesc, config.uniforms.len); + @memcpy(uniforms, config.uniforms); + self.uniforms = uniforms; + } // // LAYOUT // + var set_layout: vk.VkDescriptorSetLayout = null; + + if (uniform_size > 0) { + const bindings = [_]vk.VkDescriptorSetLayoutBinding{ + .{ + .binding = 0, + .descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = vk.VK_SHADER_STAGE_VERTEX_BIT | vk.VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = null, + }, + }; + + var set_layout_info: vk.VkDescriptorSetLayoutCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = bindings.len, + .pBindings = &bindings, + }; + + if (vk.vkCreateDescriptorSetLayout(device.raw, &set_layout_info, null, &set_layout) != vk.VK_SUCCESS) { + return error.FailedToCreateDescriptorSetLayout; + } + self.descriptor_set_layout = set_layout.?; + } + var pipeline_layout_info: vk.VkPipelineLayoutCreateInfo = .{ .sType = vk.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = if (set_layout != null) 1 else 0, + .pSetLayouts = if (set_layout != null) @ptrCast(&set_layout) else null, }; 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); + self.layout = pipeline_layout.?; // // PIPELINE // + const raw_vert_shader = vulkan.resource.shaders.get(config.vert_shader) orelse return error.VertexShaderNotFound; + const raw_frag_shader = vulkan.resource.shaders.get(config.frag_shader) orelse return error.FragmentShaderNotFound; + 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, + .module = raw_vert_shader.raw, }; const frag_shader_stage_info: vk.VkPipelineShaderStageCreateInfo = .{ @@ -61,7 +122,7 @@ pub fn init( .stage = vk.VK_SHADER_STAGE_FRAGMENT_BIT, .pName = "main", - .module = shader.frag, + .module = raw_frag_shader.raw, }; var shader_stages = [_]vk.VkPipelineShaderStageCreateInfo{ @@ -231,17 +292,167 @@ pub fn init( if (vk.vkCreateGraphicsPipelines(device.raw, null, 1, &pipeline_info, null, &pipeline) != vk.VK_SUCCESS) { return error.FailedToCreateGraphicsPipelines; } + self.raw = pipeline.?; - return .{ - .raw = pipeline.?, - .layout = pipeline_layout.?, - .device = device, - }; + // + // UNIFORM BUFFER + DESCRIPTORS + // + + if (uniform_size > 0) { + var buffer_info: vk.VkBufferCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = uniform_size, + .usage = vk.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + .sharingMode = vk.VK_SHARING_MODE_EXCLUSIVE, + }; + + var uniform_buffer: vk.VkBuffer = null; + if (vk.vkCreateBuffer(device.raw, &buffer_info, null, &uniform_buffer) != vk.VK_SUCCESS) { + return error.FailedToCreateUniformBuffer; + } + self.uniform_buffer = uniform_buffer.?; + + var mem_req: vk.VkMemoryRequirements = .{}; + var mem_props: vk.VkPhysicalDeviceMemoryProperties = .{}; + + vk.vkGetBufferMemoryRequirements(device.raw, uniform_buffer, &mem_req); + vk.vkGetPhysicalDeviceMemoryProperties(device.physical_device, &mem_props); + + var mem_type: u32 = 0; + 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, + }; + + var uniform_memory: vk.VkDeviceMemory = null; + if (vk.vkAllocateMemory(device.raw, &alloc_info, null, &uniform_memory) != vk.VK_SUCCESS) { + return error.FailedToAllocateUniformMemory; + } + self.uniform_memory = uniform_memory; + + _ = vk.vkBindBufferMemory(device.raw, uniform_buffer, uniform_memory, 0); + + // --- DESCRIPTOR POOL --- + const pool_sizes = [_]vk.VkDescriptorPoolSize{ + .{ + .type = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + }, + }; + + var pool_info: vk.VkDescriptorPoolCreateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .poolSizeCount = pool_sizes.len, + .pPoolSizes = &pool_sizes, + .maxSets = 1, + }; + + var descriptor_pool: vk.VkDescriptorPool = null; + if (vk.vkCreateDescriptorPool(device.raw, &pool_info, null, &descriptor_pool) != vk.VK_SUCCESS) { + return error.FailedToCreateDescriptorPool; + } + self.descriptor_pool = descriptor_pool.?; + + // --- DESCRIPTOR SET --- + var set_alloc_info: vk.VkDescriptorSetAllocateInfo = .{ + .sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = @ptrCast(&self.descriptor_set_layout), + }; + + if (vk.vkAllocateDescriptorSets(device.raw, &set_alloc_info, &self.descriptor_set) != vk.VK_SUCCESS) { + return error.FailedToAllocateDescriptorSets; + } + + // --- WRITE DESCRIPTOR SET --- + const buffer_descriptor: vk.VkDescriptorBufferInfo = .{ + .buffer = uniform_buffer, + .offset = 0, + .range = uniform_size, + }; + + const write_descriptor: vk.VkWriteDescriptorSet = .{ + .sType = vk.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = self.descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &buffer_descriptor, + }; + + vk.vkUpdateDescriptorSets(device.raw, 1, &write_descriptor, 0, null); + } + + return self; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - vk.vkDestroyPipelineLayout(self.device.raw, self.layout, null); - vk.vkDestroyPipeline(self.device.raw, self.raw, null); + if (self.descriptor_set_layout) |set_layout| { + vk.vkDestroyDescriptorSetLayout(self.device.raw, set_layout, null); + } + if (self.descriptor_pool) |pool| { + vk.vkDestroyDescriptorPool(self.device.raw, pool, null); + } + if (self.uniform_memory) |memory| { + vk.vkFreeMemory(self.device.raw, memory, null); + } + if (self.uniform_buffer) |buffer| { + vk.vkDestroyBuffer(self.device.raw, buffer, null); + } + if (self.layout) |layout| { + vk.vkDestroyPipelineLayout(self.device.raw, layout, null); + } + if (self.raw) |raw| { + vk.vkDestroyPipeline(self.device.raw, raw, null); + } + self.alloc.free(self.uniforms); +} + +/// ---------------------------------------------------- +/// Set a uniform value by name +/// ---------------------------------------------------- +pub fn setUniform(self: *Self, name: []const u8, value: anytype) !void { + for (self.uniforms) |uniform| { + if (std.mem.eql(u8, uniform.name, name)) { + if (@sizeOf(@TypeOf(value)) > uniform.size) { + return error.UniformSizeMismatch; + } + + var mapped: ?*anyopaque = null; + if (vk.vkMapMemory( + self.device.raw, + self.uniform_memory, + uniform.offset, + uniform.size, + 0, + &mapped, + ) != vk.VK_SUCCESS) { + return error.FailedToMapMemory; + } + + const dst: [*]u8 = @ptrCast(mapped.?); + const src: [*]const u8 = @ptrCast(&value); + @memcpy(dst[0..@sizeOf(@TypeOf(value))], src[0..@sizeOf(@TypeOf(value))]); + + vk.vkUnmapMemory(self.device.raw, self.uniform_memory); + return; + } + } + return error.UniformNotFound; } diff --git a/src/rhi/vulkan/resource/shader.zig b/src/rhi/vulkan/resource/shader.zig index 079ac0c..1a5a354 100644 --- a/src/rhi/vulkan/resource/shader.zig +++ b/src/rhi/vulkan/resource/shader.zig @@ -12,31 +12,27 @@ const Self = @This(); // FIELDS // -vert: *vk.VkShaderModule_T, -frag: *vk.VkShaderModule_T, +raw: *vk.VkShaderModule_T, device: *const Device, /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn init( - vert_spv_path: []const u8, - frag_spv_path: []const u8, + 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 code = try readFile(spv_path, alloc, io); + defer alloc.free(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); + const module = try createModule( + code, + device, + ); return .{ - .vert = vert_module, - .frag = frag_module, + .raw = module, .device = device, }; } @@ -44,8 +40,7 @@ pub fn init( /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - vk.vkDestroyShaderModule(self.device.raw, self.vert, null); - vk.vkDestroyShaderModule(self.device.raw, self.frag, null); + vk.vkDestroyShaderModule(self.device.raw, self.raw, null); } /// ---------------------------------------------------- diff --git a/src/rhi/vulkan/vulkan.zig b/src/rhi/vulkan/vulkan.zig index f901923..b21fb82 100644 --- a/src/rhi/vulkan/vulkan.zig +++ b/src/rhi/vulkan/vulkan.zig @@ -19,6 +19,8 @@ const BufferUsage = common.BufferUsage; const BufferSharingMode = common.BufferSharingMode; // --- CONFIGS --- +const AdapterConfig = common.AdapterConfig; +const RenderPassConfig = common.RenderPassConfig; const PipelineConfig = common.PipelineConfig; // --- HANDLES --- @@ -63,10 +65,6 @@ 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( @@ -114,13 +112,13 @@ pub fn deinit(self: *Self) void { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createAdapter(self: *Self) !Handle(Adapter) { - return try self.resource.adapters.put(try .init(self)); +pub fn makeAdapter(self: *Self, config: AdapterConfig) !Handle(Adapter) { + return try self.resource.adapters.put(try .init(self, config)); } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createDevice( +pub fn makeDevice( self: *Self, adapter: Handle(Adapter), surface: Handle(Surface), @@ -141,13 +139,13 @@ pub fn destroyDevice(self: *Self, device: Handle(Device)) !void { /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createSurface(self: *Self) !Handle(Surface) { +pub fn makeSurface(self: *Self) !Handle(Surface) { return try self.resource.surfaces.put(try .init(&self.instance, self.config.window)); } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createSwapchain( +pub fn makeSwapchain( self: *Self, adapter: Handle(Adapter), device: Handle(Device), @@ -168,28 +166,26 @@ pub fn createSwapchain( /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createShader( +pub fn makeShader( self: *Self, - vert_spv_path: []const u8, - frag_spv_path: []const u8, + 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)); + return try self.resource.shaders.put(try .init(spv_path, raw_device, self.alloc, self.io)); } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createPipeline(self: *Self, config: PipelineConfig) !Handle(Pipeline) { +pub fn makePipeline(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)); + return try self.resource.pipelines.put(try .init(raw_swapchain, raw_device, config, self, self.alloc)); } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn createBuffer( +pub fn makeBuffer( self: *Self, size: u32, usage: BufferUsage, @@ -202,25 +198,6 @@ pub fn createBuffer( 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 // @@ -355,11 +332,17 @@ pub fn endFrame( pub fn beginRendering( self: *Self, swapchain: Handle(Swapchain), + config: RenderPassConfig, ) !void { const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; const frame = self.currentFrame(); + const extent: vk.VkExtent2D = .{ + .width = config.width orelse raw_swapchain.extent.width, + .height = config.height orelse raw_swapchain.extent.height, + }; + const barrier: vk.VkImageMemoryBarrier = .{ .sType = vk.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -405,11 +388,18 @@ pub fn beginRendering( .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, + .loadOp = switch (config.load_op) { + .clear => vk.VK_ATTACHMENT_LOAD_OP_CLEAR, + .load => vk.VK_ATTACHMENT_LOAD_OP_LOAD, + .dont_care => vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, + }, + .storeOp = switch (config.store_op) { + .store => vk.VK_ATTACHMENT_STORE_OP_STORE, + .dont_care => vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, + }, .clearValue = .{ .color = .{ - .float32 = .{ 0.01, 0.01, 0.01, 1.0 }, + .float32 = config.clear_color, }, }, }; @@ -421,7 +411,7 @@ pub fn beginRendering( .x = 0, .y = 0, }, - .extent = raw_swapchain.extent, + .extent = extent, }, .layerCount = 1, .colorAttachmentCount = 1, @@ -436,8 +426,8 @@ pub fn beginRendering( const viewport: vk.VkViewport = .{ .x = 0, .y = 0, - .width = @floatFromInt(raw_swapchain.extent.width), - .height = @floatFromInt(raw_swapchain.extent.height), + .width = @floatFromInt(extent.width), + .height = @floatFromInt(extent.height), .minDepth = 0.0, .maxDepth = 1.0, }; @@ -447,7 +437,7 @@ pub fn beginRendering( .x = 0, .y = 0, }, - .extent = raw_swapchain.extent, + .extent = extent, }; vk.vkCmdSetViewport(frame.cmd_buf, 0, 1, &viewport); @@ -459,8 +449,8 @@ pub fn beginRendering( pub fn endRendering( self: *Self, swapchain: Handle(Swapchain), -) !void { - const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return error.SwapchainNotFound; +) void { + const raw_swapchain = self.resource.swapchains.get(swapchain) orelse return; const frame = self.currentFrame(); @@ -512,7 +502,27 @@ pub fn endRendering( /// ---------------------------------------------------- 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); + const frame = self.currentFrame(); + vk.vkCmdBindPipeline(frame.cmd_buf, vk.VK_PIPELINE_BIND_POINT_GRAPHICS, raw_pipeline.raw.?); + if (raw_pipeline.descriptor_set) |set| { + vk.vkCmdBindDescriptorSets( + frame.cmd_buf, + vk.VK_PIPELINE_BIND_POINT_GRAPHICS, + raw_pipeline.layout.?, + 0, + 1, + &set, + 0, + null, + ); + } +} + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn setUniform(self: *Self, pipeline: Handle(Pipeline), name: []const u8, value: anytype) !void { + const raw_pipeline = self.resource.pipelines.get(pipeline) orelse return error.PipelineNotFound; + try raw_pipeline.setUniform(name, value); } /// ---------------------------------------------------- @@ -522,7 +532,13 @@ pub fn draw(self: *Self, vertex_count: u32, instance_count: u32, first_vertex: u } /// ---------------------------------------------------- -/// Lazily create the frame sync objects (semaphores + fence) +/// ---------------------------------------------------- +pub fn currentFrame(self: *Self) *Frame { + return &self.frames[self.current_frame]; +} + +/// ---------------------------------------------------- +/// Lazily make the frame sync objects (semaphores + fence) /// ---------------------------------------------------- fn ensureFrameSync( self: *Self, @@ -550,7 +566,7 @@ fn ensureFrameSync( for (render_finished, 0..) |*semaphore, i| { _ = i; if (vk.vkCreateSemaphore(device, &semaphore_info, null, semaphore) != vk.VK_SUCCESS) { - return error.FailedToCreateSemaphore; + return error.FailedTomakeSemaphore; } } self.render_finished = render_finished; @@ -558,13 +574,13 @@ fn ensureFrameSync( 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; + return error.FailedTomakeSemaphore; } 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; + return error.FailedTomakeFence; } frame.image_available = image_available.?; diff --git a/zig-out/bin/simple b/zig-out/bin/simple new file mode 100755 index 0000000..ac329b5 Binary files /dev/null and b/zig-out/bin/simple differ