[Fix] (MG_Backend/DirectVulkan): support Voxy rendering

Implemented:

- Advertise Voxy-required DirectVulkan extensions without raising the reported OpenGL version.

- Add DirectVulkan compute, indirect draw count, DSA, readback, and buffer state paths needed by Voxy.

Fixed:

- Enable Vulkan shaderInt64 and drawIndirectFirstInstance so Voxy baseInstance-driven LOD draws address the correct section data.

- Fix DirectVulkan synchronization, framebuffer, texture readback, and shader interface handling used by Voxy and Minecraft screenshots.

Tests:

- Add MG_Test coverage for DirectVulkan extension advertising, DSA buffer/texture/framebuffer/vertex-array behavior, persistent mapped readback, and shader/program paths.
This commit is contained in:
2026-06-09 00:37:37 +08:00
parent ab9db43599
commit cf165c0db5
65 changed files with 3724 additions and 239 deletions
@@ -125,7 +125,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage},
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},
.IsCompatibilityProfile = false // Is Compatibility Profile
},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
@@ -160,6 +163,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
funcsTable.GL.DrawRangeElements = DrawRangeElements;
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
@@ -175,12 +180,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
funcsTable.GL.ClearBufferfv = ClearBufferfv;
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
funcsTable.GL.ClearBufferiv = ClearBufferiv;
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
funcsTable.GL.GenerateMipmap = GenerateMipmap;
funcsTable.GL.ReadPixels = ReadPixels;
funcsTable.GL.GetTexImage = GetTexImage;
funcsTable.GL.GetTextureImage = GetTextureImage;
funcsTable.GL.DispatchCompute = DispatchCompute;
funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect;
funcsTable.GL.MemoryBarrier = MemoryBarrier;
@@ -206,6 +215,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull;
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
m_dynamicParameters.MaxShaderStorageBlockSize =
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
m_vulkanCaps.MaxShaderStorageBlockSize,
m_dynamicParameters.MaxShaderStorageBlockSize);
}
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -157,6 +157,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
StorageBlockResource block{};
block.name = blockName;
block.binding = binding->binding;
block.dataSize = static_cast<GLint>(binding->block.size);
const GLuint blockIndex = static_cast<GLuint>(cache.storageBlocks.size());
AddBufferVariablesRecursive(binding->block, blockName, blockIndex, cache.bufferVariables,
@@ -235,8 +236,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->ClearBufferiv(buffer, drawbuffer, value);
}
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 ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
GLint drawbuffer, const GLfloat* value) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfv called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearNamedFramebufferfv called with null GL context");
pVulkanRenderer->ClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value);
}
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
GLint drawbuffer, GLfloat depth, GLint stencil) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearNamedFramebufferfi called with null GL context");
pVulkanRenderer->ClearNamedFramebufferfi(framebuffer, buffer, drawbuffer, depth, stencil);
}
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);
}
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
MGLOG_W("DirectVulkan::MultiDrawArraysIndirect is not implemented yet (drawcount=%d)", drawcount);
}
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirectCount called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElementsIndirectCount called with null GL context");
pVulkanRenderer->MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride);
}
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
MGLOG_W("DirectVulkan::MultiDrawArraysIndirectCount is not implemented yet (maxdrawcount=%d)", maxdrawcount);
}
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex) {}
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {}
@@ -277,8 +308,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DispatchComputeIndirect(GLintptr indirect) {
MGLOG_W("DirectVulkan::DispatchComputeIndirect is not implemented yet (offset=%zu)",
static_cast<SizeT>(indirect));
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchComputeIndirect called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DispatchComputeIndirect called with null GL context");
pVulkanRenderer->DispatchComputeIndirect(indirect);
}
void MemoryBarrier(GLbitfield barriers) {
@@ -589,8 +621,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
cache.storageBlocks[storageBlockIndex].binding = storageBlockBinding;
}
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {}
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {}
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ReadPixels called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ReadPixels called with null GL context");
pVulkanRenderer->ReadPixels(x, y, width, height, format, type, pixels);
}
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTexImage called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GetTexImage called with null GL context");
pVulkanRenderer->GetTexImage(target, level, format, type, pixels);
}
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTextureImage called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GetTextureImage called with null GL context");
pVulkanRenderer->GetTextureImage(texture, uploadTarget, level, format, type, bufSize, pixels);
}
void Clear(GLbitfield mask) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer");
@@ -713,6 +759,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitNamedFramebuffer called with null VulkanRenderer");
pVulkanRenderer->BlitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0,
dstY0, dstX1, dstY1, mask, filter);
}
void Present() {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Present called with null VulkanRenderer");
pVulkanRenderer->Present();
@@ -17,6 +17,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
GLint drawbuffer, const GLfloat* value);
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
GLint drawbuffer, GLfloat depth, GLint stencil);
void Clear(GLbitfield mask);
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
void DrawArrays(GLenum mode, GLint first, GLsizei count);
@@ -27,6 +31,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GLsizei drawcount, const GLint* basevertex);
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 MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex);
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
@@ -44,6 +52,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DrawArraysIndirect(GLenum mode, const void* indirect);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter);
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
@@ -69,5 +82,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels);
void Present();
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -178,9 +178,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
packet.commandBuffer = frame.commandBuffer;
packet.submitInfo.waitSemaphoreCount = 1;
packet.submitInfo.pWaitSemaphores = &packet.waitSemaphore;
packet.submitInfo.pWaitDstStageMask = &packet.waitDstStageMask;
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
packet.submitInfo.signalSemaphoreCount = 1;
@@ -217,6 +217,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return result;
}
frame.imageAvailableSemaphoreConsumed = false;
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
&outImageIndex);
}
@@ -260,6 +261,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.hasCommandBufferRecorded = false;
frame.isCommandRecording = false;
frame.imageAvailableSemaphoreConsumed = false;
return VK_SUCCESS;
}
@@ -277,5 +279,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
frame.isCommandRecording = false;
frame.hasCommandBufferRecorded = false;
frame.imageAvailableSemaphoreConsumed = false;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -35,6 +35,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFence imageInFlightFence = VK_NULL_HANDLE;
Bool isCommandRecording = false;
Bool hasCommandBufferRecorded = false;
Bool imageAvailableSemaphoreConsumed = false;
};
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
@@ -994,16 +994,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
String NormalizeDescriptorName(const SpvReflectDescriptorBinding& binding,
ProgramFactory::DescriptorBindingKind kind) {
const char* rawName = binding.name;
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic &&
if ((kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic ||
kind == ProgramFactory::DescriptorBindingKind::StorageBuffer) &&
binding.type_description != nullptr && binding.type_description->type_name != nullptr) {
rawName = binding.type_description->type_name;
}
MOBILEGL_ASSERT(rawName != nullptr && rawName[0] != '\0',
"ProgramFactory: descriptor has empty name (spirvId=%u type=%d)", binding.spirv_id,
static_cast<Int>(binding.descriptor_type));
String name = rawName;
String name = (rawName != nullptr) ? rawName : "";
if (name.empty()) {
name = std::format("__mg_unnamed_descriptor_set{}_binding{}_id{}", binding.set, binding.binding,
binding.spirv_id);
MGLOG_W("ProgramFactory: descriptor has empty name; using generated name '%s' (type=%d)",
name.c_str(), static_cast<Int>(binding.descriptor_type));
}
if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler ||
kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer ||
kind == ProgramFactory::DescriptorBindingKind::StorageImage) {
@@ -202,7 +202,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, VkDescriptorImageInfo& outImageInfo) const {
(void)commandBuffer;
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
MOBILEGL_ASSERT(binding < programObj.samplerNameByBinding.size(),
@@ -254,23 +253,31 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Int attachmentLevel = 0;
if (drawFbo &&
FindFramebufferAttachmentForTexture(*drawFbo, *texture, attachmentType, attachmentLevel)) {
MOBILEGL_ASSERT(false,
"ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound "
"for sampling at binding=%u, but is also attached to drawFbo=%u as %s (level=%d, "
"trackedLayout=%d)",
texture->GetExternalIndex(), binding, drawFbo->GetExternalIndex(),
MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(),
attachmentLevel, static_cast<Int>(resource->layout));
MGLOG_W("ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound "
"for sampling at binding=%u, but is also attached to drawFbo=%u as %s (level=%d, "
"trackedLayout=%d)",
texture->GetExternalIndex(), binding, drawFbo->GetExternalIndex(),
MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(),
attachmentLevel, static_cast<Int>(resource->layout));
}
MOBILEGL_ASSERT(false,
const Bool readyForSampling = m_textureManager->TransitionTextureForSampling(commandBuffer, *texture);
if (!readyForSampling) {
MGLOG_E("ResolveSamplerDescriptor: failed to transition textureId=%d for sampler binding=%u",
texture->GetExternalIndex(), binding);
return false;
}
resource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(resource != nullptr,
"ResolveSamplerDescriptor: failed to resync textureId=%d after sampling transition",
texture->GetExternalIndex());
MOBILEGL_ASSERT(IsValidSampledImageLayout(resource->layout),
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
return false;
}
outImageInfo = {
.sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture),
.imageView = resource->fullView,
.imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
.imageLayout = resource->layout,
};
if (ShouldDumpDescriptorStats()) {
@@ -316,7 +323,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
*samplerBindingOverride.texture),
.imageView = samplerBindingOverride.imageView != VK_NULL_HANDLE ?
samplerBindingOverride.imageView :
resource->fullView,
(resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView),
.imageLayout = resource->layout,
};
return outImageInfo.sampler != VK_NULL_HANDLE;
@@ -67,7 +67,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.allocator = m_initInfo.allocator,
.frameCount = m_initInfo.frameCount,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags,
.minBufferSize = m_initInfo.minUploadBytes,
@@ -201,7 +201,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
case BufferKind::TextureBuffer:
return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
case BufferKind::ShaderStorage:
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
case BufferKind::Indirect:
return VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
default:
return 0;
}
@@ -21,6 +21,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uniform,
TextureBuffer,
ShaderStorage,
Indirect,
};
struct VkBufferManagerInitInfo {
@@ -61,6 +61,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return texture;
}
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil) {
DepthStencilAttachmentLoadInfo info{};
info.depthLoadOp = clearDepth
? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.stencilLoadOp = clearStencil
? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.initialLayout =
(trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED || (clearDepth && clearStencil)) ? VK_IMAGE_LAYOUT_UNDEFINED
: trackedLayout;
return info;
}
VkRenderPassManager::VkRenderPassManager(VkDevice device,
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager,
SwapchainObject& swapchainObject):
@@ -372,27 +389,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
depthAttachmentDescription.format = depthTextureResource->format;
}
depthAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachmentDescription.loadOp = clearDepth ?
VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD;
const auto loadInfo =
ResolveDepthStencilAttachmentLoadInfo(trackedDepthLayout, clearDepth, clearStencil);
depthAttachmentDescription.loadOp = loadInfo.depthLoadOp;
depthAttachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
depthAttachmentDescription.stencilLoadOp = clearStencil ?
VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD;
depthAttachmentDescription.stencilLoadOp = loadInfo.stencilLoadOp;
depthAttachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
depthAttachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
if (trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
if (!clearDepth) {
depthAttachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
if (!clearStencil) {
depthAttachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
depthAttachmentDescription.initialLayout = loadInfo.initialLayout;
if (trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED && (!clearDepth || !clearStencil)) {
MGLOG_W("GetOrCreateRenderPass: depth/stencil attachment textureId=%d starts with undefined layout "
"and partial/no clear; using DONT_CARE for uncleared aspects",
texture.GetExternalIndex());
}
depthAttachmentDescription.initialLayout =
(clearDepth && clearStencil) || trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED ?
VK_IMAGE_LAYOUT_UNDEFINED :
trackedDepthLayout;
if (hasClear) {
pendingClearAttachments.emplace_back(PendingClearAttachmentInfo {
.attachmentIndex = depthAttachmentIndex,
@@ -407,9 +416,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
});
attachmentViews.emplace_back(m_swapchainObject.GetDepthStencilImageView(swapchainImageIndex));
} else {
MOBILEGL_ASSERT(clearDepth || clearStencil || depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED,
MOBILEGL_ASSERT(depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED ||
depthAttachmentDescription.loadOp != VK_ATTACHMENT_LOAD_OP_LOAD,
"GetOrCreateRenderPass: depth attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD",
texture.GetExternalIndex());
MOBILEGL_ASSERT(depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED ||
depthAttachmentDescription.stencilLoadOp != VK_ATTACHMENT_LOAD_OP_LOAD,
"GetOrCreateRenderPass: stencil attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD",
texture.GetExternalIndex());
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
.target = TrackedAttachmentTarget::Texture,
.texture = &texture,
@@ -37,6 +37,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
};
struct DepthStencilAttachmentLoadInfo {
VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
};
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
struct RenderPassEntry {
static inline VkDevice s_device;
static inline Vector<VkTextureManager::TextureResource*> s_textureResourcesScratch;
@@ -621,7 +621,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
perMipSampledView = CreateImageView(resource->image, resource->format, resource->aspect, resource->viewType,
const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource->aspect);
perMipSampledView = CreateImageView(resource->image, resource->format, sampledAspect, resource->viewType,
mipLevel, 1, resource->arrayLayers, &sampledComponents);
if (perMipSampledView == VK_NULL_HANDLE) {
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(),
@@ -1014,6 +1015,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkTextureManager::DeferResourceRelease(TextureResource&& resource) {
if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE &&
resource.sampledView == VK_NULL_HANDLE &&
resource.perMipViews.empty() && resource.perMipSampledViews.empty()) {
return;
}
@@ -1088,6 +1090,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool needsRecreate =
resource.fullView == VK_NULL_HANDLE ||
resource.sampledView == VK_NULL_HANDLE ||
resource.sampledBaseMipLevel != baseMipLevel ||
resource.sampledLevelCount != levelCount ||
resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion();
@@ -1099,6 +1102,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DeferViewRelease(resource.fullView);
resource.fullView = VK_NULL_HANDLE;
}
if (resource.sampledView != VK_NULL_HANDLE) {
DeferViewRelease(resource.sampledView);
resource.sampledView = VK_NULL_HANDLE;
}
for (auto& sampledView : resource.perMipSampledViews) {
if (sampledView != VK_NULL_HANDLE) {
DeferViewRelease(sampledView);
@@ -1109,10 +1116,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, resource.viewType,
baseMipLevel, levelCount, resource.arrayLayers, &sampledComponents);
baseMipLevel, levelCount, resource.arrayLayers);
if (resource.fullView == VK_NULL_HANDLE) {
return false;
}
const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource.aspect);
resource.sampledView = CreateImageView(resource.image, resource.format, sampledAspect, resource.viewType,
baseMipLevel, levelCount, resource.arrayLayers, &sampledComponents);
if (resource.sampledView == VK_NULL_HANDLE) {
return false;
}
resource.sampledBaseMipLevel = baseMipLevel;
resource.sampledLevelCount = levelCount;
@@ -1441,4 +1454,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return VK_IMAGE_ASPECT_COLOR_BIT;
}
}
VkImageAspectFlags VkTextureManager::ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect) {
if ((imageAspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
return VK_IMAGE_ASPECT_COLOR_BIT;
}
if ((imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) {
return VK_IMAGE_ASPECT_DEPTH_BIT;
}
if ((imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
return VK_IMAGE_ASPECT_STENCIL_BIT;
}
return imageAspect;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -33,6 +33,7 @@ public:
VkImage image = VK_NULL_HANDLE;
VmaAllocation allocation = nullptr;
VkImageView fullView = VK_NULL_HANDLE;
VkImageView sampledView = VK_NULL_HANDLE;
Vector<VkImageView> perMipViews;
Vector<VkImageView> perMipSampledViews;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -53,6 +54,7 @@ public:
std::swap(this->image, that.image);
std::swap(this->allocation, that.allocation);
std::swap(this->fullView, that.fullView);
std::swap(this->sampledView, that.sampledView);
std::swap(this->perMipViews, that.perMipViews);
std::swap(this->perMipSampledViews, that.perMipSampledViews);
std::swap(this->layout, that.layout);
@@ -72,6 +74,9 @@ public:
if (fullView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, fullView, nullptr);
}
if (sampledView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, sampledView, nullptr);
}
for (const auto attachmentView : perMipViews) {
if (attachmentView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, attachmentView, nullptr);
@@ -86,6 +91,7 @@ public:
vmaDestroyImage(s_allocator, image, allocation);
}
fullView = VK_NULL_HANDLE;
sampledView = VK_NULL_HANDLE;
perMipViews.clear();
perMipSampledViews.clear();
image = VK_NULL_HANDLE;
@@ -127,6 +133,8 @@ public:
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
@@ -1226,6 +1226,127 @@ void main() {
const char* value = std::getenv("MOBILEGL_PRESENT_STATS");
return value != nullptr && value[0] == '1' && value[1] == '\0';
}
static Bool IsBgraVkFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
return true;
default:
return false;
}
}
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
}
static Int GetReadbackChannelCount(GLenum format) {
switch (format) {
case GL_RGB:
case GL_BGR:
return 3;
case GL_RGBA:
case GL_BGRA:
return 4;
default:
return 0;
}
}
static void StoreReadbackPixel(const Uint8* src, Bool srcIsBgra, GLenum dstFormat, Uint8* dst) {
const Uint8 r = srcIsBgra ? src[2] : src[0];
const Uint8 g = src[1];
const Uint8 b = srcIsBgra ? src[0] : src[2];
const Uint8 a = src[3];
switch (dstFormat) {
case GL_RGB:
dst[0] = r;
dst[1] = g;
dst[2] = b;
break;
case GL_BGR:
dst[0] = b;
dst[1] = g;
dst[2] = r;
break;
case GL_RGBA:
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
break;
case GL_BGRA:
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
break;
default:
break;
}
}
static Bool PackReadbackToClientOrPbo(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width,
GLsizei height, GLenum format, GLenum type, void* pixels) {
if (width <= 0 || height <= 0) {
return true;
}
if (type != GL_UNSIGNED_BYTE) {
MGLOG_E("DirectVulkan readback skipped: unsupported type=0x%x", type);
return false;
}
const Int dstChannels = GetReadbackChannelCount(format);
if (dstChannels == 0) {
MGLOG_E("DirectVulkan readback skipped: unsupported format=0x%x", format);
return false;
}
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT dstRowStride = AlignPixelRow(rowPixels * static_cast<SizeT>(dstChannels),
packParams.Alignment);
const SizeT dstOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) *
static_cast<SizeT>(dstChannels);
const SizeT packedSize = dstOffset +
(static_cast<SizeT>(height - 1) * dstRowStride) +
(static_cast<SizeT>(width) * static_cast<SizeT>(dstChannels));
Vector<Uint8> packed(packedSize, 0);
const Bool srcIsBgra = IsBgraVkFormat(srcFormat);
for (GLsizei row = 0; row < height; ++row) {
const Uint8* srcRow = srcPixels + static_cast<SizeT>(row) * static_cast<SizeT>(width) * 4;
Uint8* dstRow = packed.data() + dstOffset + static_cast<SizeT>(row) * dstRowStride;
for (GLsizei col = 0; col < width; ++col) {
StoreReadbackPixel(srcRow + static_cast<SizeT>(col) * 4,
srcIsBgra,
format,
dstRow + static_cast<SizeT>(col) * static_cast<SizeT>(dstChannels));
}
}
const auto& pixelPackBufferObject =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
if (pixelPackBufferObject) {
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
if (pboOffset + packed.size() > pixelPackBufferObject->GetSize()) {
MGLOG_E("DirectVulkan readback skipped: pixel pack buffer is too small");
return false;
}
pixelPackBufferObject->UploadSubData({packed.data(), packed.size()}, pboOffset);
return true;
}
if (pixels != nullptr && !packed.empty()) {
Memcpy(pixels, packed.data(), packed.size());
}
return true;
}
} // namespace
VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
@@ -1607,7 +1728,10 @@ void main() {
++syntheticBinding;
}
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
if (bindingCount > 0) {
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(),
vkOffsets.data());
}
return true;
}
@@ -2747,6 +2871,55 @@ void main() {
vkCmdDispatch(frame.commandBuffer, numGroupsX, numGroupsY, numGroupsZ);
}
void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) {
m_textureManager->CollectGarbage();
auto& frame = m_frameContext.GetCurrent();
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
ProgramFactory::CompileOptionFlags transformFlags = 0;
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const VkPipeline pipeline = GetOrCreateComputePipeline(programObj);
if (pipeline == VK_NULL_HANDLE) {
MGLOG_E("DispatchComputeIndirect skipped: compute pipeline creation failed for program=%u",
program.GetExternalIndex());
return;
}
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers(
frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(),
VK_PIPELINE_BIND_POINT_COMPUTE);
if (!boundUniforms) {
MGLOG_E("DispatchComputeIndirect skipped: BindProgramUniformBuffers failed");
return;
}
auto indirectBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DispatchIndirect).GetBoundObject();
if (!indirectBuffer) {
MGLOG_E("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound");
return;
}
indirectBuffer->MarkPersistentMappedRangeDirty();
BufferSlice slice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, indirectBuffer, slice)) {
MGLOG_E("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer");
return;
}
MGLOG_D("DirectVulkan: glDispatchComputeIndirect(offset=%zu)", static_cast<SizeT>(indirect));
vkCmdDispatchIndirect(frame.commandBuffer, slice.buffer, slice.offset + static_cast<VkDeviceSize>(indirect));
}
void VulkanRenderer::MemoryBarrier(GLbitfield barriers) {
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
@@ -2791,17 +2964,16 @@ void main() {
m_clearManager->QueueClear(mask, payload, *fbo);
}
void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload) {
void VulkanRenderer::QueueClearBufferPayloadForFramebuffer(
const MG_State::GLState::FramebufferObject& framebuffer, GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload) {
m_clearManager->CollectGarbage();
auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
MOBILEGL_ASSERT(fbo, "VulkanRenderer::QueueClearBufferPayload: draw framebuffer not found");
auto queueAttachmentClear = [&](FramebufferAttachmentType attachmentType) {
if (attachmentType == FramebufferAttachmentType::None) {
return;
}
const auto& attachment = fbo->GetAttachment(attachmentType);
const auto& attachment = framebuffer.GetAttachment(attachmentType);
if (!attachment.IsTexture() || attachment.IsRenderbuffer()) {
return;
}
@@ -2819,7 +2991,7 @@ void main() {
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "color drawbuffer index is out of range");
return;
}
queueAttachmentClear(fbo->GetDrawBuffers()[drawbuffer]);
queueAttachmentClear(framebuffer.GetDrawBuffers()[drawbuffer]);
return;
}
case GL_DEPTH:
@@ -2850,6 +3022,13 @@ void main() {
}
}
void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload) {
auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
MOBILEGL_ASSERT(fbo, "VulkanRenderer::QueueClearBufferPayload: draw framebuffer not found");
QueueClearBufferPayloadForFramebuffer(*fbo, buffer, drawbuffer, clearPayload);
}
void VulkanRenderer::ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
ClearAttachmentPayload payload{};
payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
@@ -2878,6 +3057,41 @@ void main() {
QueueClearBufferPayload(buffer, drawbuffer, payload);
}
void VulkanRenderer::ClearNamedFramebufferfv(
const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer, GLint drawbuffer,
const GLfloat* value) {
if (!framebuffer || value == nullptr) {
return;
}
ClearAttachmentPayload payload{};
switch (buffer) {
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.color = FloatVec4(value[0], value[1], value[2], value[3]);
break;
case GL_DEPTH:
payload.mask = GL_DEPTH_BUFFER_BIT;
payload.depth = value[0];
break;
default:
break;
}
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
}
void VulkanRenderer::ClearNamedFramebufferfi(
const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer, GLint drawbuffer,
GLfloat depth, GLint stencil) {
if (!framebuffer) {
return;
}
ClearAttachmentPayload payload{};
payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
payload.depth = depth;
payload.stencil = static_cast<Uint32>(stencil);
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
}
void VulkanRenderer::ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {
if (value == nullptr) {
return;
@@ -3137,6 +3351,16 @@ void main() {
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
void VulkanRenderer::BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
static constexpr GLbitfield kSupportedBlitMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
if ((mask & ~kSupportedBlitMask) != 0) {
MGLOG_E("BlitFramebuffer skipped: unsupported mask bits=0x%x", static_cast<Uint32>(mask));
@@ -3161,8 +3385,6 @@ void main() {
return;
}
auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
MOBILEGL_ASSERT(readFbo != nullptr, "VulkanRenderer::BlitFramebuffer: read framebuffer is null");
MOBILEGL_ASSERT(drawFbo != nullptr, "VulkanRenderer::BlitFramebuffer: draw framebuffer is null");
@@ -3461,10 +3683,18 @@ void main() {
MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__);
}
if (!drawIsDefaultFbo) {
VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags dstRestoreAccessMask = 0;
GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask);
VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags dstRestoreAccessMask = 0;
GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask);
if (drawIsDefaultFbo) {
VkImageLayout dstTrackedLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstBinding.image, dstTrackedLayout, dstRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask,
VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain destination image layout", __func__);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, dstTrackedLayout);
} else {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask,
@@ -3655,6 +3885,287 @@ void main() {
MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__);
}
Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) {
if (frame.isCommandRecording) {
m_frameContext.EndCommandRecording();
}
if (!frame.hasCommandBufferRecorded) {
return true;
}
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
VkSemaphore waitSemaphore = frame.imageAvailableSemaphore;
if (!frame.imageAvailableSemaphoreConsumed) {
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &waitSemaphore;
submitInfo.pWaitDstStageMask = &waitDstStageMask;
}
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &frame.commandBuffer;
VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, frame.imageInFlightFence);
if (result != VK_SUCCESS) {
MGLOG_E("DirectVulkan readback: vkQueueSubmit returned %d", result);
return false;
}
frame.imageAvailableSemaphoreConsumed = true;
result = vkWaitForFences(m_device, 1, &frame.imageInFlightFence, VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) {
MGLOG_E("DirectVulkan readback: vkWaitForFences returned %d", result);
return false;
}
result = vkResetFences(m_device, 1, &frame.imageInFlightFence);
if (result != VK_SUCCESS) {
MGLOG_E("DirectVulkan readback: vkResetFences returned %d", result);
return false;
}
frame.hasCommandBufferRecorded = false;
frame.isCommandRecording = false;
return true;
}
void VulkanRenderer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void* pixels) {
if (width <= 0 || height <= 0) {
return;
}
auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
if (readFbo == nullptr) {
MGLOG_E("DirectVulkan::ReadPixels skipped: no read framebuffer is bound");
return;
}
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const Bool readIsDefaultFbo =
(readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
BlitImageBinding srcBinding{};
if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager,
srcBinding)) {
return;
}
if (!readIsDefaultFbo) {
const auto& sourceAttachment = readFbo->GetAttachment(readFbo->GetReadBuffer());
auto sourceTexture = sourceAttachment.GetTexture();
if (sourceTexture != nullptr) {
const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture);
MOBILEGL_ASSERT(clearReady,
"ReadPixels: failed to materialize pending clear for source textureId=%d",
sourceTexture->GetExternalIndex());
}
}
const VkImageLayout srcOriginalLayout = readIsDefaultFbo
? m_swapchainObject.GetImageLayout(m_imageIndexAcquired)
: *srcBinding.trackedLayout;
if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
MGLOG_E("DirectVulkan::ReadPixels skipped: source image layout is undefined");
return;
}
const VkDeviceSize readbackSize = static_cast<VkDeviceSize>(width) * static_cast<VkDeviceSize>(height) * 4;
VkBufferObject readback;
if (!readback.Create({
.allocator = m_allocator,
.size = readbackSize,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
})) {
MGLOG_E("DirectVulkan::ReadPixels skipped: failed to create readback buffer");
return;
}
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcAccessMask = 0;
GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask);
if (readIsDefaultFbo) {
VkImageLayout trackedLayout = srcOriginalLayout;
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, trackedLayout);
} else {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask,
srcBinding.mipLevel, 1);
MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__);
}
VkBufferImageCopy copyRegion{};
copyRegion.imageSubresource.aspectMask = srcBinding.aspectMask;
copyRegion.imageSubresource.mipLevel = srcBinding.mipLevel;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageOffset = {x, y, 0};
copyRegion.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
readback.GetHandle(), 1, &copyRegion);
VkPipelineStageFlags restoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags restoreAccessMask = 0;
GetImageTransitionDestinationState(srcOriginalLayout, restoreStageMask, restoreAccessMask);
if (readIsDefaultFbo) {
VkImageLayout trackedLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, trackedLayout, srcOriginalLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask,
VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, srcBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain source image layout", __func__);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, trackedLayout);
} else {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask,
VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, srcBinding.aspectMask,
srcBinding.mipLevel, 1);
MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__);
}
if (!SubmitReadbackCommandsAndWait(frame)) {
return;
}
const auto* mapped = static_cast<const Uint8*>(readback.Map());
if (mapped == nullptr) {
MGLOG_E("DirectVulkan::ReadPixels skipped: failed to map readback buffer");
return;
}
const VkFormat srcFormat = readIsDefaultFbo ? m_swapchainObject.GetSurfaceFormat().format : VK_FORMAT_R8G8B8A8_UNORM;
PackReadbackToClientOrPbo(mapped, srcFormat, width, height, format, type, pixels);
}
void VulkanRenderer::GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
GetTextureImage(textureObject, textureUploadTarget, level, format, type, -1, pixels);
}
void VulkanRenderer::GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
TextureUploadTarget textureUploadTarget, GLint level, GLenum format,
GLenum type, GLsizei bufSize, GLvoid* pixels) {
if (textureObject == nullptr || textureObject->GetStorageType() != TextureStorageType::Mipmap) {
return;
}
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
if (level < 0 || static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
MGLOG_E("DirectVulkan::GetTexImage skipped: level %d is out of range", level);
return;
}
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*textureObject);
if (resource == nullptr || resource->image == VK_NULL_HANDLE) {
MGLOG_E("DirectVulkan::GetTexImage skipped: failed to sync textureId=%u",
textureObject->GetExternalIndex());
return;
}
if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) {
MGLOG_E("DirectVulkan::GetTexImage skipped: only color textures are supported right now");
return;
}
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *textureObject);
MOBILEGL_ASSERT(clearReady,
"GetTexImage: failed to materialize pending clear for textureId=%d",
textureObject->GetExternalIndex());
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast<Uint>(level));
const GLsizei width = texelSize.x();
const GLsizei height = texelSize.y();
if (width <= 0 || height <= 0) {
return;
}
if (bufSize >= 0) {
const Int dstChannels = GetReadbackChannelCount(format);
if (type == GL_UNSIGNED_BYTE && dstChannels > 0) {
const SizeT minSize = static_cast<SizeT>(width) * static_cast<SizeT>(height) *
static_cast<SizeT>(dstChannels);
if (static_cast<SizeT>(bufSize) < minSize) {
MGLOG_E("DirectVulkan::GetTextureImage skipped: destination buffer is too small");
return;
}
}
}
const VkDeviceSize readbackSize = static_cast<VkDeviceSize>(width) * static_cast<VkDeviceSize>(height) * 4;
VkBufferObject readback;
if (!readback.Create({
.allocator = m_allocator,
.size = readbackSize,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
})) {
MGLOG_E("DirectVulkan::GetTexImage skipped: failed to create readback buffer");
return;
}
const VkImageLayout originalLayout = resource->layout;
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcAccessMask = 0;
GetImageTransitionSourceState(originalLayout, srcStageMask, srcAccessMask);
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, resource->aspect,
static_cast<Uint32>(level), 1);
MOBILEGL_ASSERT(ok, "%s: failed to transition texture image", __func__);
VkBufferImageCopy copyRegion{};
copyRegion.imageSubresource.aspectMask = resource->aspect;
copyRegion.imageSubresource.mipLevel = static_cast<Uint32>(level);
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
vkCmdCopyImageToBuffer(frame.commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
readback.GetHandle(), 1, &copyRegion);
VkPipelineStageFlags restoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags restoreAccessMask = 0;
GetImageTransitionDestinationState(originalLayout, restoreStageMask, restoreAccessMask);
ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, resource->image, resource->layout, originalLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask,
VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, resource->aspect,
static_cast<Uint32>(level), 1);
MOBILEGL_ASSERT(ok, "%s: failed to restore texture image layout", __func__);
if (!SubmitReadbackCommandsAndWait(frame)) {
return;
}
const auto* mapped = static_cast<const Uint8*>(readback.Map());
if (mapped == nullptr) {
MGLOG_E("DirectVulkan::GetTextureImage skipped: failed to map readback buffer");
return;
}
PackReadbackToClientOrPbo(mapped, resource->format, width, height, format, type, pixels);
}
void VulkanRenderer::GenerateMipmap(GLenum target) {
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
const auto uploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
@@ -3927,6 +4438,108 @@ void main() {
}
}
void VulkanRenderer::MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect,
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) {
auto& frame = m_frameContext.GetCurrent();
if (maxdrawcount <= 0) {
return;
}
if (stride == 0) {
stride = sizeof(DrawIndexedCmdParam);
}
if (stride < static_cast<GLsizei>(sizeof(DrawIndexedCmdParam))) {
MGLOG_E("MultiDrawElementsIndirectCount 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("MultiDrawElementsIndirectCount 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("MultiDrawElementsIndirectCount skipped: no element array buffer is bound");
return;
}
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset +
static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) + sizeof(DrawIndexedCmdParam);
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
return;
}
auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
if (!parameterBuffer || static_cast<SizeT>(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range");
return;
}
DrawCmdParam vertexRange{};
vertexRange.vertexCount = static_cast<Uint32>(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;
}
drawBuffer->MarkPersistentMappedRangeDirty();
parameterBuffer->MarkPersistentMappedRangeDirty();
BufferSlice drawSlice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, drawBuffer, drawSlice)) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync draw indirect buffer");
return;
}
BufferSlice parameterSlice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, parameterBuffer, parameterSlice)) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync parameter buffer");
return;
}
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount) {
MGLOG_D("DirectVulkan: glMultiDrawElementsIndirectCountARB(max=%d stride=%d)", maxdrawcount, stride);
s_vkCmdDrawIndexedIndirectCount(frame.commandBuffer,
drawSlice.buffer,
drawSlice.offset + static_cast<VkDeviceSize>(commandOffset),
parameterSlice.buffer,
parameterSlice.offset + static_cast<VkDeviceSize>(drawcount),
static_cast<Uint32>(maxdrawcount),
static_cast<Uint32>(stride));
return;
}
const auto parameterData = parameterBuffer->GetDataReadOnly();
const auto drawData = drawBuffer->GetDataReadOnly();
if (!parameterData || !drawData) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read buffers");
return;
}
Uint32 actualDrawCount = 0;
std::memcpy(&actualDrawCount, parameterData->data() + drawcount, sizeof(actualDrawCount));
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
for (Uint32 idraw = 0; idraw < actualDrawCount; ++idraw) {
DrawIndexedCmdParam cmd{};
std::memcpy(&cmd, drawData->data() + commandOffset + static_cast<SizeT>(idraw) * stride, sizeof(cmd));
vkCmdDrawIndexed(frame.commandBuffer, cmd.indexCount, cmd.instanceCount, cmd.firstIndex,
cmd.vertexOffset, cmd.firstInstance);
}
}
void VulkanRenderer::Present() {
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
"Present, acquired image index out of range");
@@ -4354,6 +4967,8 @@ void main() {
deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend;
deviceFeatures.shaderClipDistance = supportedDeviceFeatures.shaderClipDistance;
deviceFeatures.shaderCullDistance = supportedDeviceFeatures.shaderCullDistance;
deviceFeatures.shaderInt64 = supportedDeviceFeatures.shaderInt64;
deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance;
VkDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
@@ -4418,16 +5033,20 @@ void main() {
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
MGLOG_I("Device feature support: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s",
MGLOG_I("Device feature support: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s shaderInt64=%s drawIndirectFirstInstance=%s",
supportedDeviceFeatures.geometryShader ? "true" : "false",
supportedDeviceFeatures.independentBlend ? "true" : "false",
supportedDeviceFeatures.shaderClipDistance ? "true" : "false",
supportedDeviceFeatures.shaderCullDistance ? "true" : "false");
MGLOG_I("Device feature enabled: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s",
supportedDeviceFeatures.shaderCullDistance ? "true" : "false",
supportedDeviceFeatures.shaderInt64 ? "true" : "false",
supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false");
MGLOG_I("Device feature enabled: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s shaderInt64=%s drawIndirectFirstInstance=%s",
deviceFeatures.geometryShader ? "true" : "false",
deviceFeatures.independentBlend ? "true" : "false",
deviceFeatures.shaderClipDistance ? "true" : "false",
deviceFeatures.shaderCullDistance ? "true" : "false");
deviceFeatures.shaderCullDistance ? "true" : "false",
deviceFeatures.shaderInt64 ? "true" : "false",
deviceFeatures.drawIndirectFirstInstance ? "true" : "false");
VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
@@ -4634,6 +5253,8 @@ void main() {
Vector<const char*>& inOutEnabledExtensions) {
m_drawIndirectCountExtensionEnabled = EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME);
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);
@@ -120,17 +120,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
GLenum buffer, GLint drawbuffer, const GLfloat* value);
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height);
void GenerateMipmap(GLenum target);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
GLsizei bufSize, GLvoid* pixels);
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
void DispatchComputeIndirect(GLintptr indirect);
void MemoryBarrier(GLbitfield barriers);
void DrawArrays(const DrawCmd& payload);
void DrawElements(const DrawIndexedCmd& payload);
void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void Present();
const PhysicalDevice& GetPhysicalDevice() const;
@@ -173,6 +190,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload);
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload);
NativeWindowType m_window = 0;
void* m_platformDisplay = nullptr;
@@ -279,6 +299,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const IntVec3& storageBaseTexelSize,
VkImageLayout originalLayout,
VkImageLayout finalLayout);
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
void ShutdownSwapchain();