// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "VideoBackends/Vulkan/Util.h" #include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/MathUtil.h" #include "Common/MsgHandler.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/ShaderCompiler.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/StreamBuffer.h" #include "VideoBackends/Vulkan/VulkanContext.h" namespace Vulkan { namespace Util { size_t AlignValue(size_t value, size_t alignment) { // Have to use mod rather than masking bits in case alignment is not a power of two. size_t offset = value % alignment; if (offset != 0) value += (alignment - offset); return value; } size_t AlignBufferOffset(size_t offset, size_t alignment) { // Assume an offset of zero is already aligned to a value larger than alignment. if (offset == 0) return 0; return AlignValue(offset, alignment); } u32 MakeRGBA8Color(float r, float g, float b, float a) { return (static_cast(MathUtil::Clamp(static_cast(r * 255.0f), 0, 255)) << 0) | (static_cast(MathUtil::Clamp(static_cast(g * 255.0f), 0, 255)) << 8) | (static_cast(MathUtil::Clamp(static_cast(b * 255.0f), 0, 255)) << 16) | (static_cast(MathUtil::Clamp(static_cast(a * 255.0f), 0, 255)) << 24); } bool IsDepthFormat(VkFormat format) { switch (format) { case VK_FORMAT_D16_UNORM: case VK_FORMAT_D16_UNORM_S8_UINT: case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D32_SFLOAT_S8_UINT: return true; default: return false; } } VkFormat GetLinearFormat(VkFormat format) { switch (format) { case VK_FORMAT_R8_SRGB: return VK_FORMAT_R8_UNORM; case VK_FORMAT_R8G8_SRGB: return VK_FORMAT_R8G8_UNORM; case VK_FORMAT_R8G8B8_SRGB: return VK_FORMAT_R8G8B8_UNORM; case VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM; case VK_FORMAT_B8G8R8_SRGB: return VK_FORMAT_B8G8R8_UNORM; case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM; default: return format; } } u32 GetTexelSize(VkFormat format) { // Only contains pixel formats we use. switch (format) { case VK_FORMAT_R32_SFLOAT: return 4; case VK_FORMAT_D32_SFLOAT: return 4; case VK_FORMAT_R8G8B8A8_UNORM: return 4; case VK_FORMAT_B8G8R8A8_UNORM: return 4; default: PanicAlert("Unhandled pixel format"); return 1; } } VkBlendFactor GetAlphaBlendFactor(VkBlendFactor factor) { switch (factor) { case VK_BLEND_FACTOR_SRC_COLOR: return VK_BLEND_FACTOR_SRC_ALPHA; case VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; case VK_BLEND_FACTOR_DST_COLOR: return VK_BLEND_FACTOR_DST_ALPHA; case VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR: return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; default: return factor; } } RasterizationState GetNoCullRasterizationState() { RasterizationState state = {}; state.cull_mode = VK_CULL_MODE_NONE; state.samples = VK_SAMPLE_COUNT_1_BIT; state.per_sample_shading = VK_FALSE; state.depth_clamp = VK_FALSE; return state; } DepthStencilState GetNoDepthTestingDepthStencilState() { DepthStencilState state = {}; state.test_enable = VK_FALSE; state.write_enable = VK_FALSE; state.compare_op = VK_COMPARE_OP_ALWAYS; return state; } BlendState GetNoBlendingBlendState() { BlendState state = {}; state.blend_enable = VK_FALSE; state.blend_op = VK_BLEND_OP_ADD; state.src_blend = VK_BLEND_FACTOR_ONE; state.dst_blend = VK_BLEND_FACTOR_ZERO; state.alpha_blend_op = VK_BLEND_OP_ADD; state.src_alpha_blend = VK_BLEND_FACTOR_ONE; state.dst_alpha_blend = VK_BLEND_FACTOR_ZERO; state.logic_op_enable = VK_FALSE; state.logic_op = VK_LOGIC_OP_CLEAR; state.write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; return state; } void SetViewportAndScissor(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth /*= 0.0f*/, float max_depth /*= 1.0f*/) { VkViewport viewport = {static_cast(x), static_cast(y), static_cast(width), static_cast(height), min_depth, max_depth}; VkRect2D scissor = {{x, y}, {static_cast(width), static_cast(height)}}; vkCmdSetViewport(command_buffer, 0, 1, &viewport); vkCmdSetScissor(command_buffer, 0, 1, &scissor); } void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, VkDeviceSize offset, VkDeviceSize size, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask) { VkBufferMemoryBarrier buffer_info = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, // VkStructureType sType nullptr, // const void* pNext src_access_mask, // VkAccessFlags srcAccessMask dst_access_mask, // VkAccessFlags dstAccessMask VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex buffer, // VkBuffer buffer offset, // VkDeviceSize offset size // VkDeviceSize size }; vkCmdPipelineBarrier(command_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 1, &buffer_info, 0, nullptr); } void ExecuteCurrentCommandsAndRestoreState(StateTracker* state_tracker, bool execute_off_thread, bool wait_for_completion) { state_tracker->EndRenderPass(); g_command_buffer_mgr->ExecuteCommandBuffer(execute_off_thread, wait_for_completion); state_tracker->InvalidateDescriptorSets(); state_tracker->SetPendingRebind(); } VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count) { VkShaderModuleCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; info.codeSize = spv_word_count * sizeof(u32); info.pCode = spv; VkShaderModule module; VkResult res = vkCreateShaderModule(g_vulkan_context->GetDevice(), &info, nullptr, &module); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateShaderModule failed: "); return VK_NULL_HANDLE; } return module; } VkShaderModule CompileAndCreateVertexShader(const std::string& source_code, bool prepend_header) { ShaderCompiler::SPIRVCodeVector code; if (!ShaderCompiler::CompileVertexShader(&code, source_code.c_str(), source_code.length(), prepend_header)) { return VK_NULL_HANDLE; } return CreateShaderModule(code.data(), code.size()); } VkShaderModule CompileAndCreateGeometryShader(const std::string& source_code, bool prepend_header) { ShaderCompiler::SPIRVCodeVector code; if (!ShaderCompiler::CompileGeometryShader(&code, source_code.c_str(), source_code.length(), prepend_header)) { return VK_NULL_HANDLE; } return CreateShaderModule(code.data(), code.size()); } VkShaderModule CompileAndCreateFragmentShader(const std::string& source_code, bool prepend_header) { ShaderCompiler::SPIRVCodeVector code; if (!ShaderCompiler::CompileFragmentShader(&code, source_code.c_str(), source_code.length(), prepend_header)) { return VK_NULL_HANDLE; } return CreateShaderModule(code.data(), code.size()); } } // namespace Util UtilityShaderDraw::UtilityShaderDraw(VkCommandBuffer command_buffer, VkPipelineLayout pipeline_layout, VkRenderPass render_pass, VkShaderModule vertex_shader, VkShaderModule geometry_shader, VkShaderModule pixel_shader) : m_command_buffer(command_buffer) { // Populate minimal pipeline state m_pipeline_info.vertex_format = g_object_cache->GetUtilityShaderVertexFormat(); m_pipeline_info.pipeline_layout = pipeline_layout; m_pipeline_info.render_pass = render_pass; m_pipeline_info.vs = vertex_shader; m_pipeline_info.gs = geometry_shader; m_pipeline_info.ps = pixel_shader; m_pipeline_info.rasterization_state.bits = Util::GetNoCullRasterizationState().bits; m_pipeline_info.depth_stencil_state.bits = Util::GetNoDepthTestingDepthStencilState().bits; m_pipeline_info.blend_state.bits = Util::GetNoBlendingBlendState().bits; m_pipeline_info.primitive_topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; } UtilityShaderVertex* UtilityShaderDraw::ReserveVertices(VkPrimitiveTopology topology, size_t count) { m_pipeline_info.primitive_topology = topology; if (!g_object_cache->GetUtilityShaderVertexBuffer()->ReserveMemory( sizeof(UtilityShaderVertex) * count, sizeof(UtilityShaderVertex), true, true, true)) PanicAlert("Failed to allocate space for vertices in backend shader"); m_vertex_buffer = g_object_cache->GetUtilityShaderVertexBuffer()->GetBuffer(); m_vertex_buffer_offset = g_object_cache->GetUtilityShaderVertexBuffer()->GetCurrentOffset(); return reinterpret_cast( g_object_cache->GetUtilityShaderVertexBuffer()->GetCurrentHostPointer()); } void UtilityShaderDraw::CommitVertices(size_t count) { g_object_cache->GetUtilityShaderVertexBuffer()->CommitMemory(sizeof(UtilityShaderVertex) * count); m_vertex_count = static_cast(count); } void UtilityShaderDraw::UploadVertices(VkPrimitiveTopology topology, UtilityShaderVertex* vertices, size_t count) { UtilityShaderVertex* upload_vertices = ReserveVertices(topology, count); memcpy(upload_vertices, vertices, sizeof(UtilityShaderVertex) * count); CommitVertices(count); } u8* UtilityShaderDraw::AllocateVSUniforms(size_t size) { if (!g_object_cache->GetUtilityShaderUniformBuffer()->ReserveMemory( size, g_vulkan_context->GetUniformBufferAlignment(), true, true, true)) PanicAlert("Failed to allocate util uniforms"); return g_object_cache->GetUtilityShaderUniformBuffer()->GetCurrentHostPointer(); } void UtilityShaderDraw::CommitVSUniforms(size_t size) { m_vs_uniform_buffer.buffer = g_object_cache->GetUtilityShaderUniformBuffer()->GetBuffer(); m_vs_uniform_buffer.offset = 0; m_vs_uniform_buffer.range = size; m_ubo_offsets[UBO_DESCRIPTOR_SET_BINDING_VS] = static_cast(g_object_cache->GetUtilityShaderUniformBuffer()->GetCurrentOffset()); g_object_cache->GetUtilityShaderUniformBuffer()->CommitMemory(size); } u8* UtilityShaderDraw::AllocatePSUniforms(size_t size) { if (!g_object_cache->GetUtilityShaderUniformBuffer()->ReserveMemory( size, g_vulkan_context->GetUniformBufferAlignment(), true, true, true)) PanicAlert("Failed to allocate util uniforms"); return g_object_cache->GetUtilityShaderUniformBuffer()->GetCurrentHostPointer(); } void UtilityShaderDraw::CommitPSUniforms(size_t size) { m_ps_uniform_buffer.buffer = g_object_cache->GetUtilityShaderUniformBuffer()->GetBuffer(); m_ps_uniform_buffer.offset = 0; m_ps_uniform_buffer.range = size; m_ubo_offsets[UBO_DESCRIPTOR_SET_BINDING_PS] = static_cast(g_object_cache->GetUtilityShaderUniformBuffer()->GetCurrentOffset()); g_object_cache->GetUtilityShaderUniformBuffer()->CommitMemory(size); } void UtilityShaderDraw::SetPushConstants(const void* data, size_t data_size) { _assert_(static_cast(data_size) < PUSH_CONSTANT_BUFFER_SIZE); vkCmdPushConstants(m_command_buffer, m_pipeline_info.pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, static_cast(data_size), data); } void UtilityShaderDraw::SetPSSampler(size_t index, VkImageView view, VkSampler sampler) { m_ps_samplers[index].sampler = sampler; m_ps_samplers[index].imageView = view; m_ps_samplers[index].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } void UtilityShaderDraw::SetRasterizationState(const RasterizationState& state) { m_pipeline_info.rasterization_state.bits = state.bits; } void UtilityShaderDraw::SetDepthStencilState(const DepthStencilState& state) { m_pipeline_info.depth_stencil_state.bits = state.bits; } void UtilityShaderDraw::SetBlendState(const BlendState& state) { m_pipeline_info.blend_state.bits = state.bits; } void UtilityShaderDraw::BeginRenderPass(VkFramebuffer framebuffer, const VkRect2D& region, const VkClearValue* clear_value) { VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_pipeline_info.render_pass, framebuffer, region, clear_value ? 1u : 0u, clear_value}; vkCmdBeginRenderPass(m_command_buffer, &begin_info, VK_SUBPASS_CONTENTS_INLINE); } void UtilityShaderDraw::EndRenderPass() { vkCmdEndRenderPass(m_command_buffer); } void UtilityShaderDraw::Draw() { BindVertexBuffer(); BindDescriptors(); if (!BindPipeline()) return; vkCmdDraw(m_command_buffer, m_vertex_count, 1, 0, 0); } void UtilityShaderDraw::DrawQuad(int x, int y, int width, int height, float z) { UtilityShaderVertex vertices[4]; vertices[0].SetPosition(-1.0f, 1.0f, z); vertices[0].SetTextureCoordinates(0.0f, 1.0f); vertices[0].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[1].SetPosition(1.0f, 1.0f, z); vertices[1].SetTextureCoordinates(1.0f, 1.0f); vertices[1].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[2].SetPosition(-1.0f, -1.0f, z); vertices[2].SetTextureCoordinates(0.0f, 0.0f); vertices[2].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[3].SetPosition(1.0f, -1.0f, z); vertices[3].SetTextureCoordinates(1.0f, 0.0f); vertices[3].SetColor(1.0f, 1.0f, 1.0f, 1.0f); Util::SetViewportAndScissor(m_command_buffer, x, y, width, height); UploadVertices(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, vertices, ArraySize(vertices)); Draw(); } void UtilityShaderDraw::DrawQuad(int dst_x, int dst_y, int dst_width, int dst_height, int src_x, int src_y, int src_layer, int src_width, int src_height, int src_full_width, int src_full_height, float z) { float u0 = float(src_x) / float(src_full_width); float v0 = float(src_y) / float(src_full_height); float u1 = float(src_x + src_width) / float(src_full_width); float v1 = float(src_y + src_height) / float(src_full_height); float w = static_cast(src_layer); UtilityShaderVertex vertices[4]; vertices[0].SetPosition(-1.0f, 1.0f, z); vertices[0].SetTextureCoordinates(u0, v1, w); vertices[0].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[1].SetPosition(1.0f, 1.0f, z); vertices[1].SetTextureCoordinates(u1, v1, w); vertices[1].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[2].SetPosition(-1.0f, -1.0f, z); vertices[2].SetTextureCoordinates(u0, v0, w); vertices[2].SetColor(1.0f, 1.0f, 1.0f, 1.0f); vertices[3].SetPosition(1.0f, -1.0f, z); vertices[3].SetTextureCoordinates(u1, v0, w); vertices[3].SetColor(1.0f, 1.0f, 1.0f, 1.0f); Util::SetViewportAndScissor(m_command_buffer, dst_x, dst_y, dst_width, dst_height); UploadVertices(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, vertices, ArraySize(vertices)); Draw(); } void UtilityShaderDraw::DrawColoredQuad(int x, int y, int width, int height, float r, float g, float b, float a, float z) { return DrawColoredQuad(x, y, width, height, Util::MakeRGBA8Color(r, g, b, a), z); } void UtilityShaderDraw::DrawColoredQuad(int x, int y, int width, int height, u32 color, float z) { UtilityShaderVertex vertices[4]; vertices[0].SetPosition(-1.0f, 1.0f, z); vertices[0].SetTextureCoordinates(0.0f, 1.0f); vertices[0].SetColor(color); vertices[1].SetPosition(1.0f, 1.0f, z); vertices[1].SetTextureCoordinates(1.0f, 1.0f); vertices[1].SetColor(color); vertices[2].SetPosition(-1.0f, -1.0f, z); vertices[2].SetTextureCoordinates(0.0f, 0.0f); vertices[2].SetColor(color); vertices[3].SetPosition(1.0f, -1.0f, z); vertices[3].SetTextureCoordinates(1.0f, 0.0f); vertices[3].SetColor(color); Util::SetViewportAndScissor(m_command_buffer, x, y, width, height); UploadVertices(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, vertices, ArraySize(vertices)); Draw(); } void UtilityShaderDraw::SetViewportAndScissor(int x, int y, int width, int height) { Util::SetViewportAndScissor(m_command_buffer, x, y, width, height, 0.0f, 1.0f); } void UtilityShaderDraw::DrawWithoutVertexBuffer(VkPrimitiveTopology primitive_topology, u32 vertex_count) { m_pipeline_info.vertex_format = nullptr; m_pipeline_info.primitive_topology = primitive_topology; BindDescriptors(); if (!BindPipeline()) return; vkCmdDraw(m_command_buffer, vertex_count, 1, 0, 0); } void UtilityShaderDraw::BindVertexBuffer() { vkCmdBindVertexBuffers(m_command_buffer, 0, 1, &m_vertex_buffer, &m_vertex_buffer_offset); } void UtilityShaderDraw::BindDescriptors() { // TODO: This method is a mess, clean it up std::array bind_descriptor_sets = {}; std::array set_writes = {}; uint32_t num_set_writes = 0; VkDescriptorBufferInfo dummy_uniform_buffer = { g_object_cache->GetUtilityShaderUniformBuffer()->GetBuffer(), 0, 1}; // uniform buffers if (m_vs_uniform_buffer.buffer != VK_NULL_HANDLE || m_ps_uniform_buffer.buffer != VK_NULL_HANDLE) { VkDescriptorSet set = g_command_buffer_mgr->AllocateDescriptorSet( g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_UNIFORM_BUFFERS)); if (set == VK_NULL_HANDLE) PanicAlert("Failed to allocate descriptor set for utility draw"); set_writes[num_set_writes++] = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, UBO_DESCRIPTOR_SET_BINDING_VS, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, nullptr, (m_vs_uniform_buffer.buffer != VK_NULL_HANDLE) ? &m_vs_uniform_buffer : &dummy_uniform_buffer, nullptr}; set_writes[num_set_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, UBO_DESCRIPTOR_SET_BINDING_GS, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, nullptr, &dummy_uniform_buffer, nullptr}; set_writes[num_set_writes++] = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, UBO_DESCRIPTOR_SET_BINDING_PS, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, nullptr, (m_ps_uniform_buffer.buffer != VK_NULL_HANDLE) ? &m_ps_uniform_buffer : &dummy_uniform_buffer, nullptr}; bind_descriptor_sets[DESCRIPTOR_SET_UNIFORM_BUFFERS] = set; } // PS samplers size_t first_active_sampler; for (first_active_sampler = 0; first_active_sampler < NUM_PIXEL_SHADER_SAMPLERS; first_active_sampler++) { if (m_ps_samplers[first_active_sampler].imageView != VK_NULL_HANDLE && m_ps_samplers[first_active_sampler].sampler != VK_NULL_HANDLE) { break; } } // Check if we have any at all, skip the binding process entirely if we don't if (first_active_sampler != NUM_PIXEL_SHADER_SAMPLERS) { // Allocate a new descriptor set VkDescriptorSet set = g_command_buffer_mgr->AllocateDescriptorSet( g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS)); if (set == VK_NULL_HANDLE) PanicAlert("Failed to allocate descriptor set for utility draw"); for (size_t i = 0; i < NUM_PIXEL_SHADER_SAMPLERS; i++) { const VkDescriptorImageInfo& info = m_ps_samplers[i]; if (info.imageView != VK_NULL_HANDLE && info.sampler != VK_NULL_HANDLE) { set_writes[num_set_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, static_cast(i), 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &info, nullptr, nullptr}; } } bind_descriptor_sets[DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS] = set; } vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), num_set_writes, set_writes.data(), 0, nullptr); // Bind only the sets we updated if (bind_descriptor_sets[0] != VK_NULL_HANDLE && bind_descriptor_sets[1] == VK_NULL_HANDLE) { // UBO only vkCmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_info.pipeline_layout, DESCRIPTOR_SET_UNIFORM_BUFFERS, 1, &bind_descriptor_sets[0], NUM_UBO_DESCRIPTOR_SET_BINDINGS, m_ubo_offsets.data()); } else if (bind_descriptor_sets[0] == VK_NULL_HANDLE && bind_descriptor_sets[1] != VK_NULL_HANDLE) { // Samplers only vkCmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_info.pipeline_layout, DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS, 1, &bind_descriptor_sets[1], 0, nullptr); } else if (bind_descriptor_sets[0] != VK_NULL_HANDLE && bind_descriptor_sets[1] != VK_NULL_HANDLE) { // Both vkCmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_info.pipeline_layout, DESCRIPTOR_SET_UNIFORM_BUFFERS, 2, bind_descriptor_sets.data(), NUM_UBO_DESCRIPTOR_SET_BINDINGS, m_ubo_offsets.data()); } } bool UtilityShaderDraw::BindPipeline() { VkPipeline pipeline = g_object_cache->GetPipeline(m_pipeline_info); if (pipeline == VK_NULL_HANDLE) { PanicAlert("Failed to get pipeline for backend shader draw"); return false; } vkCmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); return true; } } // namespace Vulkan