diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index cbf5ca59..86dbaab6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -359,7 +359,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, - E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug}, + E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug, + E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack, + E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size}, .IsCompatibilityProfile = false }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false}} {} diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 1ef665fa..2bf009ac 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -409,7 +409,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElementsIndirect called with null GL context"); - pVulkanRenderer->MultiDrawElementsIndirectCount(mode, type, indirect, 0, drawcount, stride); + pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirect called with null VulkanRenderer"); @@ -418,6 +418,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (drawcount <= 0) { return; } + + // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written + // (e.g. by a compute shader), so consume them natively on the GPU. + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (drawBuffer) { + pVulkanRenderer->MultiDrawArraysIndirect(mode, indirect, drawcount, stride); + return; + } + if (stride == 0) { stride = sizeof(DrawArraysIndirectCommand); } @@ -427,6 +436,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } + // No indirect buffer bound: the pointer refers to client memory. const auto* commandBytes = ResolveIndirectCommandBytes( indirect, static_cast(stride) * static_cast(drawcount - 1) + sizeof(DrawArraysIndirectCommand), @@ -541,6 +551,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } + // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written + // (e.g. by a compute shader), so consume them natively on the GPU. + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (drawBuffer) { + pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, 1, 0); + return; + } + + // No indirect buffer bound: the pointer refers to client memory. const auto* commandBytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawElementsIndirectCommand), "DrawElementsIndirect"); if (!commandBytes) { @@ -585,6 +604,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysIndirect called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArraysIndirect called with null GL context"); + // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written + // (e.g. by a compute shader), so consume them natively on the GPU. + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (drawBuffer) { + pVulkanRenderer->MultiDrawArraysIndirect(mode, indirect, 1, 0); + return; + } + + // No indirect buffer bound: the pointer refers to client memory. const auto* commandBytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect"); if (!commandBytes) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2262dd09..85f1e1b1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -5225,7 +5225,10 @@ void main() { } MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); - if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount) { + // vkCmdDrawIndexedIndirectCount with maxDrawCount > 1 additionally requires the + // multiDrawIndirect device feature; fall back to the CPU readback loop otherwise. + if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount && + (m_multiDrawIndirectFeatureEnabled || maxdrawcount == 1)) { MGLOG_D("DirectVulkan: glMultiDrawElementsIndirectCountARB(max=%d stride=%d)", maxdrawcount, stride); s_vkCmdDrawIndexedIndirectCount(frame.commandBuffer, drawSlice.buffer, @@ -5254,6 +5257,149 @@ void main() { } } + void VulkanRenderer::MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, + GLsizei drawcount, GLsizei stride) { + auto& frame = m_frameContext.GetCurrent(); + + if (drawcount <= 0) { + return; + } + if (stride == 0) { + stride = sizeof(DrawIndexedCmdParam); + } + if (stride < static_cast(sizeof(DrawIndexedCmdParam))) { + MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", + stride, sizeof(DrawIndexedCmdParam)); + return; + } + + const SizeT indexSize = MG_Util::GetGLTypeSize(type); + if (indexSize == 0) { + MGLOG_E("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); + return; + } + + const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); + const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); + if (!indexBuffer) { + MGLOG_E("MultiDrawElementsIndirect skipped: no element array buffer is bound"); + return; + } + + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + + static_cast(stride) * static_cast(drawcount - 1) + sizeof(DrawIndexedCmdParam); + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { + MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + return; + } + + // The command parameters live on the GPU; the CPU-visible range that any single + // command may address is the whole element array buffer. + DrawCmdParam vertexRange{}; + vertexRange.vertexCount = static_cast(indexBuffer->GetSize() / indexSize); + vertexRange.instanceCount = 1; + + IndexBufferView indexBufferView{}; + indexBufferView.indexType = type; + indexBufferView.indexByteOffset = 0; + indexBufferView.indexByteSize = indexBuffer->GetSize(); + + if (!SetupDraw(frame, mode, DrawSetupAspect::IndexBuffer | DrawSetupAspect::IndirectDrawBuffer, + vertexRange, &indexBufferView)) { + return; + } + + BufferSlice drawSlice{}; + if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) { + MGLOG_E("MultiDrawElementsIndirect skipped: failed to sync draw indirect buffer"); + return; + } + + MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); + MGLOG_D("DirectVulkan: glMultiDrawElementsIndirect(drawcount=%d stride=%d)", drawcount, stride); + if (drawcount == 1 || (m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) { + vkCmdDrawIndexedIndirect(frame.commandBuffer, + drawSlice.buffer, + drawSlice.offset + static_cast(commandOffset), + static_cast(drawcount), + static_cast(stride)); + return; + } + + // multiDrawIndirect device feature unavailable: one indirect draw per command is + // valid without it and still consumes the GPU-written parameters. + for (GLsizei idraw = 0; idraw < drawcount; ++idraw) { + vkCmdDrawIndexedIndirect(frame.commandBuffer, + drawSlice.buffer, + drawSlice.offset + static_cast(commandOffset) + + static_cast(idraw) * static_cast(stride), + 1, 0); + } + } + + void VulkanRenderer::MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, + GLsizei stride) { + auto& frame = m_frameContext.GetCurrent(); + + if (drawcount <= 0) { + return; + } + if (stride == 0) { + stride = sizeof(DrawCmdParam); + } + if (stride < static_cast(sizeof(DrawCmdParam))) { + MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", + stride, sizeof(DrawCmdParam)); + return; + } + + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + + static_cast(stride) * static_cast(drawcount - 1) + sizeof(DrawCmdParam); + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { + MGLOG_E("MultiDrawArraysIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + return; + } + + // The command parameters live on the GPU, so the vertex range is unknown here; + // resident vertex buffers are uploaded in full regardless. + DrawCmdParam vertexRange{}; + vertexRange.vertexCount = 0; + vertexRange.instanceCount = 1; + + if (!SetupDraw(frame, mode, DrawSetupAspect::IndirectDrawBuffer, vertexRange)) { + return; + } + + BufferSlice drawSlice{}; + if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) { + MGLOG_E("MultiDrawArraysIndirect skipped: failed to sync draw indirect buffer"); + return; + } + + MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); + MGLOG_D("DirectVulkan: glMultiDrawArraysIndirect(drawcount=%d stride=%d)", drawcount, stride); + if (drawcount == 1 || (m_multiDrawIndirectFeatureEnabled && stride % 4 == 0)) { + vkCmdDrawIndirect(frame.commandBuffer, + drawSlice.buffer, + drawSlice.offset + static_cast(commandOffset), + static_cast(drawcount), + static_cast(stride)); + return; + } + + for (GLsizei idraw = 0; idraw < drawcount; ++idraw) { + vkCmdDrawIndirect(frame.commandBuffer, + drawSlice.buffer, + drawSlice.offset + static_cast(commandOffset) + + static_cast(idraw) * static_cast(stride), + 1, 0); + } + } + VkCommandBuffer VulkanRenderer::AcquireBufferCopyCommandBuffer() { if (m_device == VK_NULL_HANDLE || m_frameContext.GetFrameCount() == 0) { return VK_NULL_HANDLE; @@ -5755,6 +5901,8 @@ void main() { m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE; deviceFeatures.shaderInt64 = supportedDeviceFeatures.shaderInt64; deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance; + deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect; + m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE; m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE; VkDeviceCreateInfo deviceCreateInfo{}; @@ -5787,18 +5935,19 @@ void main() { indexTypeUint8ExtensionName = VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME; } + auto getPhysicalDeviceFeatures2 = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2")); + if (getPhysicalDeviceFeatures2 == nullptr) { + getPhysicalDeviceFeatures2 = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR")); + } + VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{}; indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES; if (indexTypeUint8ExtensionName != nullptr) { VkPhysicalDeviceFeatures2 featureQuery{}; featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; featureQuery.pNext = &indexTypeUint8Features; - auto getPhysicalDeviceFeatures2 = reinterpret_cast( - vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2")); - if (getPhysicalDeviceFeatures2 == nullptr) { - getPhysicalDeviceFeatures2 = reinterpret_cast( - vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR")); - } MOBILEGL_ASSERT(getPhysicalDeviceFeatures2 != nullptr, "CreateLogicalDeviceAndQueues: vkGetPhysicalDeviceFeatures2 is unavailable"); getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery); @@ -5818,10 +5967,32 @@ void main() { MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay disabled"); } + m_shaderDrawParametersFeatureEnabled = false; + VkPhysicalDeviceShaderDrawParametersFeatures shaderDrawParametersFeatures{}; + shaderDrawParametersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + if (m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 && getPhysicalDeviceFeatures2 != nullptr) { + VkPhysicalDeviceFeatures2 featureQuery{}; + featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featureQuery.pNext = &shaderDrawParametersFeatures; + getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery); + if (shaderDrawParametersFeatures.shaderDrawParameters == VK_TRUE) { + shaderDrawParametersFeatures.pNext = const_cast(deviceCreateInfo.pNext); + deviceCreateInfo.pNext = &shaderDrawParametersFeatures; + m_shaderDrawParametersFeatureEnabled = true; + } + } else if (m_shaderDrawParametersExtensionEnabled) { + // Vulkan 1.0 device: enabling VK_KHR_shader_draw_parameters alone exposes the SPIR-V + // DrawParameters capability; the shaderDrawParameters feature struct only exists from 1.1. + m_shaderDrawParametersFeatureEnabled = true; + } + if (!m_shaderDrawParametersFeatureEnabled) { + MGLOG_W("shaderDrawParameters is unavailable; shaders using gl_DrawID/gl_BaseInstance will not work"); + } + deviceCreateInfo.enabledExtensionCount = static_cast(enabledDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); MGLOG_I("Device feature support: geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s " - "shaderCullDistance=%s wideLines=%s shaderInt64=%s drawIndirectFirstInstance=%s", + "shaderCullDistance=%s wideLines=%s shaderInt64=%s drawIndirectFirstInstance=%s multiDrawIndirect=%s", supportedDeviceFeatures.geometryShader ? "true" : "false", supportedDeviceFeatures.independentBlend ? "true" : "false", supportedDeviceFeatures.logicOp ? "true" : "false", @@ -5829,9 +6000,11 @@ void main() { supportedDeviceFeatures.shaderCullDistance ? "true" : "false", supportedDeviceFeatures.wideLines ? "true" : "false", supportedDeviceFeatures.shaderInt64 ? "true" : "false", - supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false"); + supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false", + supportedDeviceFeatures.multiDrawIndirect ? "true" : "false"); MGLOG_I("Device feature enabled: geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s " - "shaderCullDistance=%s wideLines=%s shaderInt64=%s drawIndirectFirstInstance=%s", + "shaderCullDistance=%s wideLines=%s shaderInt64=%s drawIndirectFirstInstance=%s multiDrawIndirect=%s " + "shaderDrawParameters=%s", deviceFeatures.geometryShader ? "true" : "false", deviceFeatures.independentBlend ? "true" : "false", deviceFeatures.logicOp ? "true" : "false", @@ -5839,7 +6012,9 @@ void main() { deviceFeatures.shaderCullDistance ? "true" : "false", deviceFeatures.wideLines ? "true" : "false", deviceFeatures.shaderInt64 ? "true" : "false", - deviceFeatures.drawIndirectFirstInstance ? "true" : "false"); + deviceFeatures.drawIndirectFirstInstance ? "true" : "false", + deviceFeatures.multiDrawIndirect ? "true" : "false", + m_shaderDrawParametersFeatureEnabled ? "true" : "false"); VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice"); s_vkCmdDrawIndexedIndirectCount = reinterpret_cast( @@ -6067,8 +6242,9 @@ void main() { Vector& inOutEnabledExtensions) { m_drawIndirectCountExtensionEnabled = EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions, VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME); - EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions, - VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME); + m_shaderDrawParametersExtensionEnabled = + EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions, + VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME); #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index f275d724..d468a821 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -156,6 +156,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); + void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, + GLsizei stride); + void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); void Present(); @@ -229,6 +232,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool m_drawIndirectCountExtensionEnabled = false; Bool m_indexTypeUint8ExtensionEnabled = false; Bool m_logicOpFeatureEnabled = false; + Bool m_multiDrawIndirectFeatureEnabled = false; + Bool m_shaderDrawParametersExtensionEnabled = false; + Bool m_shaderDrawParametersFeatureEnabled = false; using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, Uint32 maxDrawCount,