package vk_backend import bk ".." import gpu "../../core" import "../../pipeline" import "core:log" import "core:mem" import vk "vendor:vulkan" active_pool_entry_vk :: proc(pool: ^[$N]$E, handle: $T) -> (^E, bool) { idx, ok := bk.handle_index(handle, N) if !ok do return nil, false entry := &pool[idx] if !entry.active do return nil, false return entry, true } buffer_entry :: proc(handle: bk.Buffer_Handle) -> (^Vk_Buffer_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.buffers, handle) } texture_entry :: proc(handle: bk.Texture_Handle) -> (^Vk_Texture_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.textures, handle) } shader_entry :: proc(handle: bk.Shader_Handle) -> (^Vk_Shader_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.shaders, handle) } pipeline_entry :: proc(handle: bk.Pipeline_Handle) -> (^Vk_Pipeline_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.pipelines, handle) } descriptor_entry :: proc(handle: bk.Descriptor_Handle) -> (^Vk_Descriptor_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.descriptors, handle) } descriptor_entry_of_kind :: proc( handle: bk.Descriptor_Handle, kind: Descriptor_Kind, ) -> ( ^Vk_Descriptor_Entry, bool, ) { entry, ok := descriptor_entry(handle) if !ok || entry.kind != kind do return nil, false return entry, true } render_pass_entry :: proc(handle: bk.Render_Pass_Handle) -> (^Vk_Render_Pass_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.render_passes, handle) } framebuffer_entry :: proc(handle: bk.Framebuffer_Handle) -> (^Vk_Framebuffer_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.framebuffers, handle) } sampler_entry :: proc(handle: bk.Sampler_Handle) -> (^Vk_Sampler_Entry, bool) { if g_vk == nil do return nil, false return active_pool_entry_vk(&g_vk.samplers, handle) } // ============================================================================ // Render pass commands // ============================================================================ begin_render_pass_vk :: proc(ctx: bk.Frame_Context, desc: bk.Render_Pass_Begin_Desc) { cmd := get_cmd(ctx) pass, pass_ok := render_pass_entry(desc.pass) framebuffer, framebuffer_ok := framebuffer_entry(desc.framebuffer) if !pass_ok || !framebuffer_ok do return pass_desc := pass.desc clear_values: [bk.MAX_COLOR_TARGETS + 1]vk.ClearValue clear_count: u32 if pass_desc.has_color { color_count := desc.color_count if color_count == 0 { color_count = pass_desc.color_count } if color_count == 0 { color_count = 1 } for i in 0.. 0 else desc.clear_color clear_count += 1 } } if pass_desc.has_depth { clear_values[clear_count].depthStencil = { depth = desc.clear_depth, stencil = u32(desc.clear_stencil), } clear_count += 1 } rp_info := vk.RenderPassBeginInfo { sType = .RENDER_PASS_BEGIN_INFO, renderPass = pass.handle, framebuffer = framebuffer.handle, renderArea = {offset = {0, 0}, extent = {desc.width, desc.height}}, clearValueCount = clear_count, pClearValues = &clear_values[0], } vk.CmdBeginRenderPass(cmd, &rp_info, .INLINE) } begin_default_pass_vk :: proc(ctx: bk.Frame_Context, clear_color: [4]f32) { cmd := get_cmd(ctx) sc := &g_vk.swapchain clear_values: [2]vk.ClearValue clear_values[0].color.float32 = clear_color clear_values[1].depthStencil = { depth = 1.0, stencil = 0, } rp_info := vk.RenderPassBeginInfo { sType = .RENDER_PASS_BEGIN_INFO, renderPass = sc.render_pass, framebuffer = sc.framebuffers[g_vk.image_index], renderArea = {offset = {0, 0}, extent = sc.extent}, clearValueCount = 2, pClearValues = &clear_values[0], } vk.CmdBeginRenderPass(cmd, &rp_info, .INLINE) // Set default viewport and scissor viewport := vk.Viewport { x = 0, y = 0, width = f32(sc.extent.width), height = f32(sc.extent.height), minDepth = 0, maxDepth = 1, } vk.CmdSetViewport(cmd, 0, 1, &viewport) scissor := vk.Rect2D { offset = {0, 0}, extent = sc.extent, } vk.CmdSetScissor(cmd, 0, 1, &scissor) } end_render_pass_vk :: proc(ctx: bk.Frame_Context) { vk.CmdEndRenderPass(get_cmd(ctx)) } set_viewport_vk :: proc(ctx: bk.Frame_Context, x, y, w, h: f32) { viewport := vk.Viewport { x = x, y = y, width = w, height = h, minDepth = 0, maxDepth = 1, } vk.CmdSetViewport(get_cmd(ctx), 0, 1, &viewport) } set_scissor_vk :: proc(ctx: bk.Frame_Context, x, y: i32, w, h: u32) { scissor := vk.Rect2D { offset = {x, y}, extent = {w, h}, } vk.CmdSetScissor(get_cmd(ctx), 0, 1, &scissor) } set_depth_bias_vk :: proc(ctx: bk.Frame_Context, constant, slope: f32) { vk.CmdSetDepthBias(get_cmd(ctx), constant, 0, slope) } // ============================================================================ // Draw commands // ============================================================================ draw_vk :: proc( ctx: bk.Frame_Context, vertex_count, instance_count: u32, first_vertex: u32, first_instance: u32, ) { vk.CmdDraw(get_cmd(ctx), vertex_count, instance_count, first_vertex, first_instance) } draw_indexed_vk :: proc( ctx: bk.Frame_Context, index_count, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32, ) { vk.CmdDrawIndexed( get_cmd(ctx), index_count, instance_count, first_index, vertex_offset, first_instance, ) } draw_indirect_vk :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { if draw_count != 1 { log.error("gpu/vulkan: draw_indirect currently supports one command") return } entry, ok := buffer_entry(argument_buffer) if !ok { log.error("gpu/vulkan: draw_indirect received invalid argument buffer") return } vk.CmdDrawIndirect(get_cmd(ctx), entry.buffer.buffer, vk.DeviceSize(argument_offset), draw_count, stride) } draw_indexed_indirect_vk :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { if draw_count != 1 { log.error("gpu/vulkan: draw_indexed_indirect currently supports one command") return } entry, ok := buffer_entry(argument_buffer) if !ok { log.error("gpu/vulkan: draw_indexed_indirect received invalid argument buffer") return } vk.CmdDrawIndexedIndirect(get_cmd(ctx), entry.buffer.buffer, vk.DeviceSize(argument_offset), draw_count, stride) } // ============================================================================ // Compute commands // ============================================================================ dispatch_compute_vk :: proc(ctx: bk.Frame_Context, groups_x, groups_y, groups_z: u32) { vk.CmdDispatch(get_cmd(ctx), groups_x, groups_y, groups_z) } compute_barrier_vk :: proc(ctx: bk.Frame_Context) { barrier := vk.MemoryBarrier { sType = .MEMORY_BARRIER, srcAccessMask = {.SHADER_WRITE}, dstAccessMask = { .SHADER_READ, .SHADER_WRITE, .VERTEX_ATTRIBUTE_READ, .INDEX_READ, .INDIRECT_COMMAND_READ, }, } vk.CmdPipelineBarrier( get_cmd(ctx), {.COMPUTE_SHADER}, {.DRAW_INDIRECT, .VERTEX_INPUT, .VERTEX_SHADER, .FRAGMENT_SHADER, .COMPUTE_SHADER}, {}, 1, &barrier, 0, nil, 0, nil, ) } // ============================================================================ // Buffer operations // ============================================================================ create_buffer_vk :: proc(desc: bk.Buffer_Desc) -> (bk.Buffer_Handle, bool) { handle, ok := alloc_buffer_handle() if !ok {return bk.NULL_BUFFER, false} buf, buf_ok := gpu.create_buffer( &g_vk.device, vk.DeviceSize(desc.size), to_vk_buffer_usage(desc.usage), to_vk_memory_properties(desc.memory), ) if !buf_ok { return bk.NULL_BUFFER, false } g_vk.buffers[handle].buffer = buf g_vk.buffers[handle].memory = desc.memory g_vk.buffers[handle].active = true return handle, true } create_buffer_staged_vk :: proc( data: rawptr, size: int, usage: bk.Buffer_Usage_Flags, ) -> ( bk.Buffer_Handle, bool, ) { handle, ok := alloc_buffer_handle() if !ok {return bk.NULL_BUFFER, false} buf, buf_ok := gpu.create_buffer_staged( &g_vk.device, g_vk.cmd_pool, g_vk.device.graphics_queue, data, size, to_vk_buffer_usage(usage), ) if !buf_ok { return bk.NULL_BUFFER, false } g_vk.buffers[handle].buffer = buf g_vk.buffers[handle].active = true return handle, true } destroy_buffer_vk :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} gpu.destroy_buffer(&g_vk.device, &entry.buffer) entry.active = false } map_buffer_vk :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok {return nil} if .Host_Visible not_in entry.memory { log.error("gpu/vk: map_buffer requires a Host_Visible buffer") return nil } mapped: rawptr result := vk.MapMemory( g_vk.device.device, entry.buffer.memory, 0, entry.buffer.size, {}, &mapped, ) if result != .SUCCESS {return nil} entry.buffer.mapped = mapped return mapped } unmap_buffer_vk :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} vk.UnmapMemory(g_vk.device.device, entry.buffer.memory) entry.buffer.mapped = nil } get_buffer_mapped_vk :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok {return nil} if .Host_Visible not_in entry.memory { log.error("gpu/vk: get_buffer_mapped requires a Host_Visible buffer") return nil } return entry.buffer.mapped } bind_vertex_buffer_vk :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { bind_vertex_buffer_slot_vk(ctx, 0, handle, 0, 0) } bind_vertex_buffer_slot_vk :: proc( ctx: bk.Frame_Context, slot: u32, handle: bk.Buffer_Handle, offset: u64, stride: u32, ) { entry, ok := buffer_entry(handle) if !ok {return} buf := entry.buffer.buffer vk_offset := vk.DeviceSize(offset) vk.CmdBindVertexBuffers(get_cmd(ctx), slot, 1, &buf, &vk_offset) } bind_index_buffer_vk :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} buf := entry.buffer.buffer vk.CmdBindIndexBuffer(get_cmd(ctx), buf, 0, .UINT32) } // ============================================================================ // Texture operations // ============================================================================ create_texture_vk :: proc(desc: bk.Texture_Desc, pixels: rawptr) -> (bk.Texture_Handle, bool) { handle, ok := alloc_texture_handle() if !ok {return bk.NULL_TEXTURE, false} vk_format := to_vk_format(desc.format) pxl_size := format_pixel_size(desc.format) image_size := vk.DeviceSize(desc.width) * vk.DeviceSize(desc.height) * vk.DeviceSize(pxl_size) // Create staging buffer staging, staging_ok := gpu.create_buffer( &g_vk.device, image_size, {.TRANSFER_SRC}, {.HOST_VISIBLE, .HOST_COHERENT}, ) if !staging_ok { return bk.NULL_TEXTURE, false } // Copy pixels to staging mapped: rawptr vk.MapMemory(g_vk.device.device, staging.memory, 0, image_size, {}, &mapped) mem.copy(mapped, pixels, int(image_size)) vk.UnmapMemory(g_vk.device.device, staging.memory) // Create GPU image img, img_ok := gpu.create_image( &g_vk.device, desc.width, desc.height, vk_format, .OPTIMAL, {.SAMPLED, .TRANSFER_DST}, {.DEVICE_LOCAL}, ) if !img_ok { gpu.destroy_buffer(&g_vk.device, &staging) return bk.NULL_TEXTURE, false } // Transition to TRANSFER_DST gpu.transition_image_layout( &g_vk.device, g_vk.cmd_pool, g_vk.device.graphics_queue, img.image, .UNDEFINED, .TRANSFER_DST_OPTIMAL, ) // Copy staging buffer to image gpu.copy_buffer_to_image( &g_vk.device, g_vk.cmd_pool, g_vk.device.graphics_queue, staging.buffer, img.image, desc.width, desc.height, ) // Transition to SHADER_READ_ONLY gpu.transition_image_layout( &g_vk.device, g_vk.cmd_pool, g_vk.device.graphics_queue, img.image, .TRANSFER_DST_OPTIMAL, .SHADER_READ_ONLY_OPTIMAL, ) // Create image view view, view_ok := gpu.create_image_view(&g_vk.device, img.image, vk_format) if !view_ok { gpu.destroy_image(&g_vk.device, &img) gpu.destroy_buffer(&g_vk.device, &staging) return bk.NULL_TEXTURE, false } img.view = view // Cleanup staging gpu.destroy_buffer(&g_vk.device, &staging) g_vk.textures[handle].image = img g_vk.textures[handle].active = true return handle, true } destroy_texture_vk :: proc(handle: bk.Texture_Handle) { entry, ok := texture_entry(handle) if !ok {return} gpu.destroy_image(&g_vk.device, &entry.image) entry.active = false } readback_layout_access_stage_vk :: proc( layout: bk.Image_Layout, ) -> ( access: vk.AccessFlags, stage: vk.PipelineStageFlags, ok: bool, ) { switch layout { case .Color_Attachment: return {.COLOR_ATTACHMENT_WRITE}, {.COLOR_ATTACHMENT_OUTPUT}, true case .Shader_Read_Only: return {.SHADER_READ}, {.FRAGMENT_SHADER}, true case .Transfer_Dst: return {.TRANSFER_WRITE}, {.TRANSFER}, true case .Undefined, .Depth_Stencil_Attachment, .Depth_Stencil_Read_Only, .Present_Src: return {}, {}, false } return {}, {}, false } read_texture_rgba8_vk :: proc(desc: bk.Readback_Texture_Desc, out: []u8) -> bool { entry, ok := texture_entry(desc.texture) if !ok do return false if entry.image.format != .R8G8B8A8_UNORM && entry.image.format != .R8G8B8A8_SRGB && entry.image.format != .B8G8R8A8_SRGB { log.error("gpu/vk: read_texture_rgba8 requires an 8-bit RGBA/BGRA color texture") return false } if desc.width != entry.image.width || desc.height != entry.image.height { log.error("gpu/vk: read_texture_rgba8 dimensions do not match texture") return false } required_size := int(desc.width * desc.height * 4) if len(out) < required_size { log.error("gpu/vk: read_texture_rgba8 output buffer is too small") return false } src_access, src_stage, layout_ok := readback_layout_access_stage_vk(desc.current_layout) if !layout_ok { log.error("gpu/vk: read_texture_rgba8 unsupported source layout") return false } staging, staging_ok := gpu.create_buffer( &g_vk.device, vk.DeviceSize(required_size), {.TRANSFER_DST}, {.HOST_VISIBLE, .HOST_COHERENT}, ) if !staging_ok do return false defer gpu.destroy_buffer(&g_vk.device, &staging) cmd, cmd_ok := gpu.begin_single_time_commands(&g_vk.device, g_vk.cmd_pool) if !cmd_ok do return false old_layout := to_vk_image_layout(desc.current_layout) barrier_to_copy := vk.ImageMemoryBarrier { sType = .IMAGE_MEMORY_BARRIER, srcAccessMask = src_access, dstAccessMask = {.TRANSFER_READ}, oldLayout = old_layout, newLayout = .TRANSFER_SRC_OPTIMAL, srcQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED, dstQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED, image = entry.image.image, subresourceRange = { aspectMask = {.COLOR}, baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1, }, } vk.CmdPipelineBarrier(cmd, src_stage, {.TRANSFER}, {}, 0, nil, 0, nil, 1, &barrier_to_copy) region := vk.BufferImageCopy { bufferOffset = 0, bufferRowLength = 0, bufferImageHeight = 0, imageSubresource = { aspectMask = {.COLOR}, mipLevel = 0, baseArrayLayer = 0, layerCount = 1, }, imageOffset = {0, 0, 0}, imageExtent = {desc.width, desc.height, 1}, } vk.CmdCopyImageToBuffer(cmd, entry.image.image, .TRANSFER_SRC_OPTIMAL, staging.buffer, 1, ®ion) dst_access, dst_stage, _ := readback_layout_access_stage_vk(desc.current_layout) barrier_from_copy := vk.ImageMemoryBarrier { sType = .IMAGE_MEMORY_BARRIER, srcAccessMask = {.TRANSFER_READ}, dstAccessMask = dst_access, oldLayout = .TRANSFER_SRC_OPTIMAL, newLayout = old_layout, srcQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED, dstQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED, image = entry.image.image, subresourceRange = barrier_to_copy.subresourceRange, } vk.CmdPipelineBarrier(cmd, {.TRANSFER}, dst_stage, {}, 0, nil, 0, nil, 1, &barrier_from_copy) gpu.end_single_time_commands(&g_vk.device, g_vk.cmd_pool, g_vk.device.graphics_queue, cmd) mapped: rawptr result := vk.MapMemory(g_vk.device.device, staging.memory, 0, vk.DeviceSize(required_size), {}, &mapped) if result != .SUCCESS { log.errorf("gpu/vk: read_texture_rgba8 map failed: %v", result) return false } defer vk.UnmapMemory(g_vk.device.device, staging.memory) src := ([^]u8)(mapped) for i in 0.. (bk.Texture_Handle, bool) { handle, ok := alloc_texture_handle() if !ok {return bk.NULL_TEXTURE, false} img, img_ok := gpu.create_image( &g_vk.device, desc.width, desc.height, to_vk_format(desc.format), .OPTIMAL, to_vk_image_usage(desc.usage), {.DEVICE_LOCAL}, ) if !img_ok { return bk.NULL_TEXTURE, false } g_vk.textures[handle].image = img g_vk.textures[handle].active = true return handle, true } create_image_view_vk :: proc( texture: bk.Texture_Handle, format: bk.Format, aspect: bk.Image_Aspect_Flags, ) -> bool { entry, ok := texture_entry(texture) if !ok {return false} // Destroy existing view if any if entry.image.view != 0 { vk.DestroyImageView(g_vk.device.device, entry.image.view, nil) entry.image.view = 0 } view, view_ok := gpu.create_image_view( &g_vk.device, entry.image.image, to_vk_format(format), to_vk_image_aspect(aspect), ) if !view_ok {return false} entry.image.view = view return true } destroy_image_vk :: proc(handle: bk.Texture_Handle) { destroy_texture_vk(handle) } // ============================================================================ // Sampler operations // ============================================================================ create_sampler_vk :: proc(desc: bk.Sampler_Desc) -> (bk.Sampler_Handle, bool) { handle, ok := alloc_sampler_handle() if !ok {return bk.NULL_SAMPLER, false} max_aniso := g_vk.device.properties.limits.maxSamplerAnisotropy sampler_info := vk.SamplerCreateInfo { sType = .SAMPLER_CREATE_INFO, magFilter = to_vk_filter(desc.mag_filter), minFilter = to_vk_filter(desc.min_filter), addressModeU = to_vk_address_mode(desc.address_mode_u), addressModeV = to_vk_address_mode(desc.address_mode_v), addressModeW = to_vk_address_mode(desc.address_mode_u), anisotropyEnable = b32(desc.enable_aniso), maxAnisotropy = max_aniso if desc.enable_aniso else 1.0, borderColor = .INT_OPAQUE_BLACK, unnormalizedCoordinates = false, compareEnable = b32(desc.enable_compare), compareOp = to_vk_compare_op(desc.compare_op), mipmapMode = .LINEAR if desc.mipmap_mode == .Linear else .NEAREST, mipLodBias = 0, minLod = 0, maxLod = 0, } sampler: vk.Sampler result := vk.CreateSampler(g_vk.device.device, &sampler_info, nil, &sampler) if result != .SUCCESS { log.errorf("gpu/vk: vkCreateSampler failed: %v", result) return bk.NULL_SAMPLER, false } entry := &g_vk.samplers[handle] entry.handle = sampler entry.active = true return handle, true } destroy_sampler_vk :: proc(handle: bk.Sampler_Handle) { entry, ok := sampler_entry(handle) if !ok {return} vk.DestroySampler(g_vk.device.device, entry.handle, nil) entry.active = false } // ============================================================================ // Shader operations // ============================================================================ create_shader_module_vk :: proc(desc: bk.Shader_Module_Desc) -> (bk.Shader_Handle, bool) { handle, ok := alloc_shader_handle() if !ok {return bk.NULL_SHADER, false} if desc.format != .SPIRV { log.errorf("gpu/vk: shader '%s' must be SPIR-V", desc.name) return bk.NULL_SHADER, false } mod, mod_ok := pipeline.create_shader_module_from_bytes(g_vk.device.device, desc.data) if !mod_ok { return bk.NULL_SHADER, false } g_vk.shaders[handle].module = mod g_vk.shaders[handle].active = true return handle, true } destroy_shader_vk :: proc(handle: bk.Shader_Handle) { entry, ok := shader_entry(handle) if !ok {return} pipeline.destroy_shader_module(g_vk.device.device, &entry.module) entry.active = false } // ============================================================================ // Graphics pipeline operations // ============================================================================ create_graphics_pipeline_vk :: proc(desc: bk.Pipeline_Desc) -> (bk.Pipeline_Handle, bool) { handle, ok := alloc_pipeline_handle() if !ok {return bk.NULL_PIPELINE, false} // Get shader modules vert_entry, vert_ok := shader_entry(desc.vert_shader) frag_entry, frag_ok := shader_entry(desc.frag_shader) if !vert_ok || !frag_ok { log.error("gpu/vk: graphics pipeline shader handle is invalid") return bk.NULL_PIPELINE, false } vert_mod := vert_entry.module.handle frag_mod := frag_entry.module.handle // Get render pass render_pass, render_pass_ok := render_pass_entry(desc.render_pass) if !render_pass_ok { log.error("gpu/vk: graphics pipeline render pass handle is invalid") return bk.NULL_PIPELINE, false } rp := render_pass.handle // Convert descriptor layouts layouts := make([]vk.DescriptorSetLayout, len(desc.descriptor_layouts), context.temp_allocator) for i in 0 ..< len(desc.descriptor_layouts) { layout_entry, layout_ok := descriptor_entry_of_kind( desc.descriptor_layouts[i], .Set_Layout, ) if !layout_ok { log.error("gpu/vk: graphics pipeline descriptor layout handle is invalid") return bk.NULL_PIPELINE, false } layouts[i] = layout_entry.set_layout } // Convert vertex bindings bindings := make( []vk.VertexInputBindingDescription, len(desc.vertex_bindings), context.temp_allocator, ) for i in 0 ..< len(desc.vertex_bindings) { bindings[i] = vk.VertexInputBindingDescription { binding = desc.vertex_bindings[i].binding, stride = desc.vertex_bindings[i].stride, inputRate = .INSTANCE if desc.vertex_bindings[i].input_rate == .Instance else .VERTEX, } } // Convert vertex attributes attrs := make( []vk.VertexInputAttributeDescription, len(desc.vertex_attributes), context.temp_allocator, ) for i in 0 ..< len(desc.vertex_attributes) { attrs[i] = vk.VertexInputAttributeDescription { location = desc.vertex_attributes[i].location, binding = desc.vertex_attributes[i].binding, format = to_vk_vertex_format(desc.vertex_attributes[i].format), offset = desc.vertex_attributes[i].offset, } } config := pipeline.Pipeline_Config { vert_module = vert_mod, frag_module = frag_mod, render_pass = rp, topology = to_vk_topology(desc.topology), polygon_mode = .FILL, cull_mode = to_vk_cull_mode(desc.cull_mode), front_face = to_vk_front_face(desc.front_face), enable_blending = desc.enable_blending, blend_mode = desc.blend_mode, enable_depth_test = desc.enable_depth_test, enable_depth_bias = desc.enable_depth_bias, stencil = desc.stencil, depth_only = desc.depth_only, color_attachment_count = desc.color_attachment_count, push_constant_size = desc.push_constant_size, push_constant_stages = to_vk_shader_stages(desc.push_constant_stages), descriptor_set_layouts = layouts, binding_descriptions = bindings, attribute_descriptions = attrs, } gp, gp_ok := pipeline.create_graphics_pipeline(&g_vk.device, config) if !gp_ok { return bk.NULL_PIPELINE, false } g_vk.pipelines[handle].graphics = gp g_vk.pipelines[handle].is_compute = false g_vk.pipelines[handle].active = true return handle, true } destroy_graphics_pipeline_vk :: proc(handle: bk.Pipeline_Handle) { p, ok := pipeline_entry(handle) if !ok {return} pipeline.destroy_graphics_pipeline(&g_vk.device, &p.graphics) p.active = false } bind_graphics_pipeline_vk :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { p, ok := pipeline_entry(handle) if !ok {return} vk.CmdBindPipeline(get_cmd(ctx), .GRAPHICS, p.graphics.pipeline) } push_constants_vk :: proc( ctx: bk.Frame_Context, pipeline_handle: bk.Pipeline_Handle, stages: bk.Shader_Stage_Flags, offset, size: u32, data: rawptr, ) { p, ok := pipeline_entry(pipeline_handle) if !ok {return} layout := p.compute.layout if p.is_compute else p.graphics.layout vk.CmdPushConstants(get_cmd(ctx), layout, to_vk_shader_stages(stages), offset, size, data) } // ============================================================================ // Compute pipeline operations // ============================================================================ create_compute_pipeline_vk :: proc( shader: bk.Shader_Handle, num_buffers: u32, push_constant_size: u32, ) -> ( bk.Pipeline_Handle, bool, ) { handle, ok := alloc_pipeline_handle() if !ok {return bk.NULL_PIPELINE, false} shader_entry, shader_ok := shader_entry(shader) if !shader_ok {return bk.NULL_PIPELINE, false} shader_mod := shader_entry.module.handle cp, cp_ok := pipeline.create_compute_pipeline( &g_vk.device, shader_mod, num_buffers, push_constant_size, ) if !cp_ok { return bk.NULL_PIPELINE, false } g_vk.pipelines[handle].compute = cp g_vk.pipelines[handle].is_compute = true g_vk.pipelines[handle].active = true return handle, true } destroy_compute_pipeline_vk :: proc(handle: bk.Pipeline_Handle) { p, ok := pipeline_entry(handle) if !ok {return} pipeline.destroy_compute_pipeline(&g_vk.device, &p.compute) p.active = false } bind_compute_pipeline_vk :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { p, ok := pipeline_entry(handle) if !ok {return} vk.CmdBindPipeline(get_cmd(ctx), .COMPUTE, p.compute.pipeline) } // ============================================================================ // Descriptor operations // ============================================================================ create_descriptor_set_layout_vk :: proc( bindings: []bk.Descriptor_Set_Layout_Binding, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} vk_bindings := make([]vk.DescriptorSetLayoutBinding, len(bindings), context.temp_allocator) for i in 0 ..< len(bindings) { vk_bindings[i] = vk.DescriptorSetLayoutBinding { binding = bindings[i].binding, descriptorType = to_vk_descriptor_type(bindings[i].type), descriptorCount = bindings[i].count, stageFlags = to_vk_shader_stages(bindings[i].stages), } } create_info := vk.DescriptorSetLayoutCreateInfo { sType = .DESCRIPTOR_SET_LAYOUT_CREATE_INFO, bindingCount = u32(len(vk_bindings)), pBindings = raw_data(vk_bindings), } layout: vk.DescriptorSetLayout result := vk.CreateDescriptorSetLayout(g_vk.device.device, &create_info, nil, &layout) if result != .SUCCESS { log.errorf("gpu/vk: vkCreateDescriptorSetLayout failed: %v", result) return bk.NULL_DESCRIPTOR, false } entry := &g_vk.descriptors[handle] entry.set_layout = layout entry.layout_count = u32(min(len(bindings), 16)) for i in 0 ..< int(entry.layout_count) { entry.layout_bindings[i] = bindings[i] } entry.kind = .Set_Layout entry.active = true return handle, true } destroy_descriptor_set_layout_vk :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Set_Layout) if !ok {return} vk.DestroyDescriptorSetLayout(g_vk.device.device, entry.set_layout, nil) entry.active = false } create_descriptor_pool_vk :: proc( max_sets: u32, types: []bk.Descriptor_Type, counts: []u32, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} pool_sizes := make([]vk.DescriptorPoolSize, len(types), context.temp_allocator) for i in 0 ..< len(types) { pool_sizes[i] = vk.DescriptorPoolSize { type = to_vk_descriptor_type(types[i]), descriptorCount = counts[i], } } create_info := vk.DescriptorPoolCreateInfo { sType = .DESCRIPTOR_POOL_CREATE_INFO, flags = {.FREE_DESCRIPTOR_SET}, maxSets = max_sets, poolSizeCount = u32(len(pool_sizes)), pPoolSizes = raw_data(pool_sizes), } pool: vk.DescriptorPool result := vk.CreateDescriptorPool(g_vk.device.device, &create_info, nil, &pool) if result != .SUCCESS { log.errorf("gpu/vk: vkCreateDescriptorPool failed: %v", result) return bk.NULL_DESCRIPTOR, false } entry := &g_vk.descriptors[handle] entry.pool = pool entry.kind = .Pool entry.active = true return handle, true } destroy_descriptor_pool_vk :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Pool) if !ok {return} vk.DestroyDescriptorPool(g_vk.device.device, entry.pool, nil) entry.active = false } allocate_descriptor_set_vk :: proc( pool: bk.Descriptor_Handle, layout: bk.Descriptor_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} pool_entry, pool_ok := descriptor_entry_of_kind(pool, .Pool) layout_entry, layout_ok := descriptor_entry_of_kind(layout, .Set_Layout) if !pool_ok || !layout_ok {return bk.NULL_DESCRIPTOR, false} vk_pool := pool_entry.pool vk_layout := layout_entry.set_layout layout_copy := vk_layout alloc_info := vk.DescriptorSetAllocateInfo { sType = .DESCRIPTOR_SET_ALLOCATE_INFO, descriptorPool = vk_pool, descriptorSetCount = 1, pSetLayouts = &layout_copy, } set: vk.DescriptorSet result := vk.AllocateDescriptorSets(g_vk.device.device, &alloc_info, &set) if result != .SUCCESS { log.errorf("gpu/vk: vkAllocateDescriptorSets failed: %v", result) return bk.NULL_DESCRIPTOR, false } entry := &g_vk.descriptors[handle] entry.set = set entry.binding_count = layout_entry.layout_count for i in 0 ..< int(entry.binding_count) { entry.bindings[i].binding = layout_entry.layout_bindings[i].binding entry.bindings[i].type = layout_entry.layout_bindings[i].type } entry.kind = .Set entry.active = true return handle, true } bind_descriptor_set_vk :: proc( ctx: bk.Frame_Context, pipeline_handle: bk.Pipeline_Handle, set: bk.Descriptor_Handle, index: u32, ) { cmd := get_cmd(ctx) p, pipeline_ok := pipeline_entry(pipeline_handle) set_entry, set_ok := descriptor_entry_of_kind(set, .Set) if !pipeline_ok || !set_ok {return} vk_set := set_entry.set bind_point := vk.PipelineBindPoint.COMPUTE if p.is_compute else vk.PipelineBindPoint.GRAPHICS layout := p.compute.layout if p.is_compute else p.graphics.layout vk.CmdBindDescriptorSets(cmd, bind_point, layout, index, 1, &vk_set, 0, nil) } update_descriptor_image_vk :: proc( set: bk.Descriptor_Handle, binding: u32, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, layout: bk.Image_Layout, ) { set_entry, set_ok := descriptor_entry_of_kind(set, .Set) texture_entry, texture_ok := texture_entry(texture) sampler_entry, sampler_ok := sampler_entry(sampler) if !set_ok || !texture_ok || !sampler_ok {return} vk_set := set_entry.set image_view := texture_entry.image.view vk_sampler := sampler_entry.handle image_info := vk.DescriptorImageInfo { sampler = vk_sampler, imageView = image_view, imageLayout = to_vk_image_layout(layout), } write := vk.WriteDescriptorSet { sType = .WRITE_DESCRIPTOR_SET, dstSet = vk_set, dstBinding = binding, dstArrayElement = 0, descriptorCount = 1, descriptorType = .COMBINED_IMAGE_SAMPLER, pImageInfo = &image_info, } vk.UpdateDescriptorSets(g_vk.device.device, 1, &write, 0, nil) } update_descriptor_buffer_vk :: proc( set: bk.Descriptor_Handle, binding: u32, buffer: bk.Buffer_Handle, size: u64, ) { entry, entry_ok := descriptor_entry_of_kind(set, .Set) buffer_entry, buffer_ok := buffer_entry(buffer) if !entry_ok || !buffer_ok {return} vk_set := entry.set vk_buffer := buffer_entry.buffer.buffer descriptor_type: vk.DescriptorType binding_found := false for i in 0 ..< int(entry.binding_count) { if entry.bindings[i].binding == binding { descriptor_type = to_vk_descriptor_type(entry.bindings[i].type) binding_found = true break } } if !binding_found { log.errorf("gpu/vk: descriptor buffer binding %d is not in set layout", binding) return } buffer_info := vk.DescriptorBufferInfo { buffer = vk_buffer, offset = 0, range = vk.DeviceSize(size), } write := vk.WriteDescriptorSet { sType = .WRITE_DESCRIPTOR_SET, dstSet = vk_set, dstBinding = binding, dstArrayElement = 0, descriptorCount = 1, descriptorType = descriptor_type, pBufferInfo = &buffer_info, } vk.UpdateDescriptorSets(g_vk.device.device, 1, &write, 0, nil) } // ============================================================================ // Render pass objects // ============================================================================ @(private) resolve_color_final_layout_vk :: proc(desc: bk.Render_Pass_Desc) -> bk.Image_Layout { if desc.color_final_layout != .Undefined { return desc.color_final_layout } return .Present_Src } @(private) resolve_depth_final_layout_vk :: proc(desc: bk.Render_Pass_Desc) -> bk.Image_Layout { if desc.depth_final_layout != .Undefined { return desc.depth_final_layout } return .Depth_Stencil_Read_Only if desc.depth_only else .Depth_Stencil_Attachment } @(private) attachment_initial_layout_vk :: proc( load_op: bk.Attachment_Load_Op, attachment_layout: bk.Image_Layout, ) -> vk.ImageLayout { switch load_op { case .Load: return to_vk_image_layout(attachment_layout) case .Clear: return .UNDEFINED case .Dont_Care: return .UNDEFINED } return .UNDEFINED } create_render_pass_vk :: proc(desc: bk.Render_Pass_Desc) -> (bk.Render_Pass_Handle, bool) { handle, ok := alloc_render_pass_handle() if !ok {return bk.NULL_RENDER_PASS, false} if desc.depth_only { depth_final_layout := resolve_depth_final_layout_vk(desc) stencil_load := desc.stencil_load_op if desc.has_stencil else .Dont_Care stencil_store := desc.stencil_store_op if desc.has_stencil else .Dont_Care // Depth-only render pass (shadow maps) depth_attachment := vk.AttachmentDescription { format = to_vk_format(desc.depth_format), samples = {._1}, loadOp = to_vk_attachment_load_op(desc.depth_load_op), storeOp = to_vk_attachment_store_op(desc.depth_store_op), stencilLoadOp = to_vk_attachment_load_op(stencil_load), stencilStoreOp = to_vk_attachment_store_op(stencil_store), initialLayout = attachment_initial_layout_vk( desc.depth_load_op, .Depth_Stencil_Attachment, ), finalLayout = to_vk_image_layout(depth_final_layout), } depth_ref := vk.AttachmentReference { attachment = 0, layout = .DEPTH_STENCIL_ATTACHMENT_OPTIMAL, } subpass := vk.SubpassDescription { pipelineBindPoint = .GRAPHICS, colorAttachmentCount = 0, pDepthStencilAttachment = &depth_ref, } dependencies := [2]vk.SubpassDependency { { srcSubpass = vk.SUBPASS_EXTERNAL, dstSubpass = 0, srcStageMask = {.FRAGMENT_SHADER}, dstStageMask = {.EARLY_FRAGMENT_TESTS}, srcAccessMask = {.SHADER_READ}, dstAccessMask = {.DEPTH_STENCIL_ATTACHMENT_WRITE}, dependencyFlags = {.BY_REGION}, }, { srcSubpass = 0, dstSubpass = vk.SUBPASS_EXTERNAL, srcStageMask = {.LATE_FRAGMENT_TESTS}, dstStageMask = {.FRAGMENT_SHADER}, srcAccessMask = {.DEPTH_STENCIL_ATTACHMENT_WRITE}, dstAccessMask = {.SHADER_READ}, dependencyFlags = {.BY_REGION}, }, } rp_info := vk.RenderPassCreateInfo { sType = .RENDER_PASS_CREATE_INFO, attachmentCount = 1, pAttachments = &depth_attachment, subpassCount = 1, pSubpasses = &subpass, dependencyCount = 2, pDependencies = &dependencies[0], } rp: vk.RenderPass result := vk.CreateRenderPass(g_vk.device.device, &rp_info, nil, &rp) if result != .SUCCESS { log.errorf("gpu/vk: vkCreateRenderPass (depth-only) failed: %v", result) return bk.NULL_RENDER_PASS, false } g_vk.render_passes[handle].handle = rp g_vk.render_passes[handle].desc = desc g_vk.render_passes[handle].active = true return handle, true } // Standard color + optional depth render pass attachments: [bk.MAX_COLOR_TARGETS + 1]vk.AttachmentDescription attachment_count: u32 = 0 depth_final_layout := resolve_depth_final_layout_vk(desc) color_count := desc.color_count legacy_single_color_desc := false if desc.has_color && color_count == 0 { color_count = 1 legacy_single_color_desc = true } if desc.has_color { for i in 0.. (bk.Framebuffer_Handle, bool) { handle, ok := alloc_framebuffer_handle() if !ok {return bk.NULL_FRAMEBUFFER, false} pass_entry, pass_ok := render_pass_entry(desc.pass) if !pass_ok { log.error("gpu/vk: framebuffer render pass is not active") return bk.NULL_FRAMEBUFFER, false } attachments: [bk.MAX_COLOR_TARGETS + 1]vk.ImageView attachment_count: u32 if pass_entry.desc.has_color { color_count := desc.color_count if color_count == 0 { color_count = pass_entry.desc.color_count } if color_count == 0 { color_count = 1 } for i in 0..