Compare commits

..
Author SHA1 Message Date
BZLZHH 30c6f55c2d [Fix] (MG_Backend/DirectVulkan): Add namespace qualifier to Version type. 2026-05-09 08:28:46 +08:00
BZLZHH b373535d6e [Fix] (MG_Impl/Texture): Fix proxy texture object handling. 2026-04-06 02:35:04 +08:00
swung0x48 4a3a44924a [Refactor] (MG_Backend/DirectVulkan/ProgramFactory): refactor uniform reflection 2026-04-03 16:45:11 +08:00
swung0x48 1b2a7989af [Fix] (MG_Util/ShaderTranspiler/SpvcSession): use proper usage bit to select the right code path. Fix bugs along the way 2026-04-01 14:57:00 +08:00
swung0x48 83d1ce177e [Optimization] (MG_Util/ShaderTranspiler/SpvcSession): use SPIRV-Reflect to avoid full AST parse, speeding up reflection 2026-04-01 10:28:37 +08:00
swung0x48 4321c4a827 [Submodule] (3rdparty/SPIRV-Reflect): add SPIRV-Reflect as dependency 2026-03-29 00:03:57 +08:00
swung0x48 a039ce0987 [Chore] (MG_State/ProgramState): some renames 2026-03-26 09:49:05 +08:00
swung0x48 accfaab720 [Refactor] (MG_Backend/DirectVulkan): get rid of junk, and rename some symbols 2026-03-24 14:20:21 +08:00
swung0x48 35658eb998 [Refactor] (MG_Backend/DirectVulkan): move uniform reflection from UniformDescriptorBinder to ProgramFactory 2026-03-23 16:36:17 +08:00
swung0x48 ccbde0196e [Fix] (MG_Test/ProgramTest): fix wrong decomp source output 2026-03-21 23:07:27 +08:00
swung0x48 0209c5461f [Feat] (MG_Backend/DirectVulkan): implement naive transient/resident buffer, and heuristics to downgrade resident buffer to transient 2026-03-20 17:39:10 +08:00
swung0x48 810b4886c7 [Feat] (MG_Backend/DirectVulkan): supports uint8 index buffer by VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 2026-03-20 15:27:57 +08:00
swung0x48 7dfc2149d8 [Feat] (MG_Backend/DirectVulkan): unified transient buffer arena 2026-03-20 13:56:12 +08:00
swung0x48 9d1e8bd7cd [Feat] (MG_Backend/DirectVulkan): VkBufferManager transient upload now includes uniform 2026-03-20 13:31:51 +08:00
swung0x48 bfa4049ac1 [Chore] (MG_Backend/DirectVulkan): unwrap frame context initialization 2026-03-20 09:55:22 +08:00
swung0x48 87c56ce3d5 [Fix] (MG_Backend/DirectVulkan): Fix VkBufferManager initialization assertion failure 2026-03-20 09:36:37 +08:00
swung0x48 f2ab50b84e [Feat] (MG_Backend/DirectVulkan): VkBufferManager 2026-03-19 17:44:35 +08:00
swung0x48 b6ac6c182e [Feat] (MG_Backend/DirectVulkan): Buffer arena, buffer slice 2026-03-19 17:23:52 +08:00
swung0x48 5e9f0f0c70 [Chore] (MG_Backend/DirectVulkan): remove some unused stuff 2026-03-19 08:59:34 +08:00
swung0x48 e4455aed9a [Feat] (MG_Backend/DirectVulkan): add draw cmd MultiDrawIndexedCmd 2026-03-18 16:08:00 +08:00
swung0x48 bc018c9513 [Chore] (MG_Backend/DirectVulkan): add indexBufferView field for draw cmds 2026-03-18 13:25:10 +08:00
swung0x48 2ecba4d70c [Fix] (MG_Test/VertexArrayTest): fix VertexArrayTest compilation error 2026-03-18 13:07:57 +08:00
swung0x48 1e11a5950e [Fix] (MG_Test/BufferTest): fix BufferTest compilation error 2026-03-18 11:07:56 +08:00
swung0x48 b324363db0 [Chore] (MG_Backend/DirectVulkan): hard assert SetupDraw failure 2026-03-18 11:01:00 +08:00
swung0x48 a654b15190 [Feat] (MG_Backend/DirectVulkan): naively implement DrawElementsBaseVertex and MultiDrawElementsBaseVertex 2026-03-18 10:43:30 +08:00
swung0x48 75ea7f8c0c [Chore] (MG_Backend/DirectVulkan): include index buffer upload into SetupDraw 2026-03-17 16:09:02 +08:00
swung0x48 09aeee4c03 [Chore] (MG_State/VertexArrayState): add const getter to index buffer binding slot 2026-03-17 15:55:16 +08:00
swung0x48 87122973be [Chore] (MG_Backend/DirectVulkan): refactor draw cmds 2026-03-17 15:10:17 +08:00
swung0x48 8b9adc5121 [Fix] (MG_Backend/DirectVulkan/VkRenderPassManager): avoid reusing active render pass when draw FBO attachments still have pending clears 2026-03-17 14:34:50 +08:00
swung0x48 9e42e40705 [Chore] (MG_Backend/DirectVulkan): notes for MaterializePendingClearForTexture 2026-03-09 17:13:56 +08:00
swung0x48 8a91225eb1 [Chore] (MG_Backend/DirectVulkan): debug log for clear manager 2026-03-09 16:22:51 +08:00
swung0x48 e82815802e [Feat] (MG_Backend/DirectVulkan): implement texture mipmap 2026-03-09 15:09:05 +08:00
36 changed files with 2430 additions and 1520 deletions
+3
View File
@@ -25,3 +25,6 @@
[submodule "3rdparty/Vulkan-Headers"] [submodule "3rdparty/Vulkan-Headers"]
path = 3rdparty/Vulkan-Headers path = 3rdparty/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git url = https://github.com/KhronosGroup/Vulkan-Headers.git
[submodule "3rdparty/SPIRV-Reflect"]
path = 3rdparty/SPIRV-Reflect
url = https://github.com/KhronosGroup/SPIRV-Reflect.git
Vendored Submodule
+1
Submodule 3rdparty/SPIRV-Reflect added at 10b4f09a24
+12 -1
View File
@@ -104,12 +104,20 @@ set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "Disable C++ API target" FORCE)
set(SPIRV_CROSS_CLI OFF CACHE BOOL "Disable CLI binary" FORCE) set(SPIRV_CROSS_CLI OFF CACHE BOOL "Disable CLI binary" FORCE)
set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE) set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE)
set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "Build spirv-reflect executable" FORCE)
set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "Build a SPIRV-Reflect static library" FORCE)
set(SPIRV_REFLECT_BUILD_TESTS OFF CACHE BOOL "Build the SPIRV-Reflect test suite" FORCE)
set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging" FORCE)
set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
# add_subdirectory(3rdparty/DiligentCore) # add_subdirectory(3rdparty/DiligentCore)
add_subdirectory(3rdparty/glslang) add_subdirectory(3rdparty/glslang)
add_subdirectory(3rdparty/SPIRV-Cross) add_subdirectory(3rdparty/SPIRV-Cross)
add_subdirectory(3rdparty/VulkanMemoryAllocator) add_subdirectory(3rdparty/VulkanMemoryAllocator)
add_subdirectory(3rdparty/Vulkan-Headers) add_subdirectory(3rdparty/Vulkan-Headers)
add_subdirectory(3rdparty/Vulkan-Utility-Libraries) add_subdirectory(3rdparty/Vulkan-Utility-Libraries)
add_subdirectory(3rdparty/SPIRV-Reflect)
set(XXHASH_BUILD_XXHSUM OFF) set(XXHASH_BUILD_XXHSUM OFF)
option(BUILD_SHARED_LIBS OFF) option(BUILD_SHARED_LIBS OFF)
@@ -223,7 +231,9 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
@@ -268,6 +278,7 @@ set(MOBILEGL_LINK_LIBRARIES
xxHash::xxhash xxHash::xxhash
GPUOpen::VulkanMemoryAllocator GPUOpen::VulkanMemoryAllocator
Vulkan::UtilityHeaders Vulkan::UtilityHeaders
spirv-reflect-static
) )
set(MOBILEGL_COMPILE_DEF set(MOBILEGL_COMPILE_DEF
+1 -1
View File
@@ -34,7 +34,7 @@
#define MOBILEGL_EGL_API MOBILEGL_API #define MOBILEGL_EGL_API MOBILEGL_API
// ====================== MobileGL configurations ======================= // // ====================== MobileGL configurations ======================= //
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO #define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG
#define MOBILEGL_LOG_ENABLE_CONSOLE 0 #define MOBILEGL_LOG_ENABLE_CONSOLE 0
#define MOBILEGL_LOG_ENABLE_FILE 1 #define MOBILEGL_LOG_ENABLE_FILE 1
+2 -1
View File
@@ -1075,7 +1075,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
String source; String source;
auto& spirvCode = shaderSpirvs[index]; auto& spirvCode = shaderSpirvs[index];
MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode); MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode,
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options; spvc_compiler_options options;
spvcSession.CreateOptions(&options); spvcSession.CreateOptions(&options);
@@ -17,10 +17,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {} void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {}
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {} void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {}
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {} void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {}
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {}
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount, const GLint* basevertex) {}
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {} 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 MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {}
@@ -47,65 +43,120 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {} 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 GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {}
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context");
Vector<DrawElementCmd> cmds;
cmds.reserve(static_cast<SizeT>(drawcount));
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] == 0) {
continue;
}
DrawElementCmd payload{};
payload.mode = mode;
payload.first = 0;
payload.count = count[i];
payload.indexType = type;
payload.indexByteOffset = reinterpret_cast<SizeT>(indices[i]);
cmds.push_back(payload);
}
if (cmds.empty()) {
return;
}
pVulkanRenderer->MultiDrawElements(cmds);
}
void Clear(GLbitfield mask) { void Clear(GLbitfield mask) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer"); MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context");
pVulkanRenderer->Clear(mask); pVulkanRenderer->Clear(mask);
} }
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context");
DrawElementCmd payload{};
payload.mode = mode;
payload.first = 0;
payload.count = count;
payload.indexType = type;
payload.indexByteOffset = reinterpret_cast<SizeT>(indices);
pVulkanRenderer->DrawElements(payload);
}
void DrawArrays(GLenum mode, GLint first, GLsizei count) { void DrawArrays(GLenum mode, GLint first, GLsizei count) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer"); MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context");
DrawArrayCmd payload{}; DrawCmd payload{};
payload.mode = mode; payload.mode = mode;
payload.first = first; payload.params.firstVertex = first;
payload.count = count; payload.params.vertexCount = count;
pVulkanRenderer->DrawArrays(payload); pVulkanRenderer->DrawArrays(payload);
} }
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context");
DrawIndexedCmd payload{};
payload.mode = mode;
payload.indexBufferView.indexType = type;
payload.indexBufferView.indexByteOffset = reinterpret_cast<SizeT>(indices);
payload.indexBufferView.indexByteSize = count * MG_Util::GetGLTypeSize(type);
payload.params.indexCount = count;
payload.params.instanceCount = 1;
pVulkanRenderer->DrawElements(payload);
}
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context");
// Vector<DrawElementCmd> cmds;
// cmds.reserve(static_cast<SizeT>(drawcount));
// for (GLsizei i = 0; i < drawcount; ++i) {
// if (count[i] == 0) {
// continue;
// }
//
// DrawElementCmd payload{};
// payload.mode = mode;
// payload.firstVertex = 0;
// payload.indexCount = count[i];
// payload.indexType = type;
// payload.indexByteOffset = reinterpret_cast<SizeT>(indices[i]);
// cmds.push_back(payload);
// }
//
// if (cmds.empty()) {
// return;
// }
// pVulkanRenderer->MultiDrawElements(cmds);
}
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context");
DrawIndexedCmd payload{};
payload.mode = mode;
payload.indexBufferView.indexType = type;
payload.indexBufferView.indexByteOffset = reinterpret_cast<SizeT>(indices);
payload.indexBufferView.indexByteSize = count * MG_Util::GetGLTypeSize(type);
payload.params.indexCount = count;
payload.params.instanceCount = 1;
payload.params.firstIndex = 0;
payload.params.vertexOffset = basevertex;
payload.params.firstInstance = 0;
pVulkanRenderer->DrawElements(payload);
}
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount, const GLint* basevertex) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context");
MultiDrawIndexedCmd payload{};
payload.mode = mode;
payload.indexBufferView.indexType = type;
// TODO: allocate draw cmd buf elsewhere
static Vector<DrawIndexedCmdParam> params;
params.clear();
params.resize(drawcount);
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] == 0) {
continue;
}
// TODO: this index view needs a redesign, now there's a lotta redundant uploads
payload.indexBufferView.indexByteOffset = 0;
payload.indexBufferView.indexByteSize =
std::max(reinterpret_cast<SizeT>(indices[i]) + count[i] * MG_Util::GetGLTypeSize(type),
payload.indexBufferView.indexByteSize);
auto& param = params[i];
param.indexCount = count[i];
param.instanceCount = 1;
param.firstIndex = reinterpret_cast<SizeT>(indices[i]) / MG_Util::GetGLTypeSize(type);
param.vertexOffset = basevertex[i];
param.firstInstance = 0;
}
payload.drawCount = drawcount;
payload.pParams = params.data();
pVulkanRenderer->MultiDrawElements(payload);
}
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter) { GLint dstY1, GLbitfield mask, GLenum filter) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer"); MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer");
@@ -0,0 +1,138 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "BufferArena.h"
namespace MobileGL::MG_Backend::DirectVulkan {
Bool BufferArena::Initialize(const BufferArenaDesc& desc) {
Shutdown();
MOBILEGL_ASSERT(desc.allocator != nullptr, "BufferArena::Initialize requires valid allocator");
MOBILEGL_ASSERT(desc.frameCount > 0, "BufferArena::Initialize requires non-zero frame count");
MOBILEGL_ASSERT(desc.usage != 0, "BufferArena::Initialize requires non-zero buffer usage");
m_desc = desc;
m_frames.clear();
m_frames.resize(desc.frameCount);
m_deferredReleases.resize(desc.frameCount);
return true;
}
void BufferArena::Shutdown() {
for (auto& frame : m_frames) {
frame.buffer.Destroy();
frame.writeCursor = 0;
}
m_frames.clear();
m_deferredReleases.clear();
m_desc = {};
}
void BufferArena::BeginFrame(Uint32 frameIndex) {
CollectDeferredReleases(frameIndex);
ResetFrame(frameIndex);
}
void BufferArena::ResetFrame(Uint32 frameIndex) {
AssertValidFrameIndex(frameIndex);
m_frames[frameIndex].writeCursor = 0;
}
void BufferArena::CollectDeferredReleases(Uint32 frameIndex) {
AssertValidFrameIndex(frameIndex);
m_deferredReleases[frameIndex].clear();
}
Bool BufferArena::Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
AssertValidFrameIndex(frameIndex);
MOBILEGL_ASSERT(size > 0, "BufferArena::Allocate requires non-zero size");
auto& frame = m_frames[frameIndex];
const VkDeviceSize resolvedAlignment = alignment > 0 ? alignment : 1;
const VkDeviceSize offset = (frame.writeCursor + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
const VkDeviceSize endOffset = offset + size;
if (!EnsureCapacity(frameIndex, endOffset)) {
return false;
}
frame.writeCursor = endOffset;
outSlice = frame.buffer.GetSlice(offset, size);
return outSlice.IsValid();
}
Bool BufferArena::Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment,
BufferSlice& outSlice) {
MOBILEGL_ASSERT(data != nullptr || size == 0, "BufferArena::Upload data pointer is null");
if (!Allocate(frameIndex, size, alignment, outSlice)) {
return false;
}
if (outSlice.mapped != nullptr) {
Memcpy(outSlice.mapped, data, static_cast<SizeT>(size));
return true;
}
return m_frames[frameIndex].buffer.Upload(data, size, outSlice.offset);
}
VkDeviceSize BufferArena::GetWriteCursor(Uint32 frameIndex) const {
AssertValidFrameIndex(frameIndex);
return m_frames[frameIndex].writeCursor;
}
Uint32 BufferArena::GetFrameCount() const {
return static_cast<Uint32>(m_frames.size());
}
Bool BufferArena::EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset) {
AssertValidFrameIndex(frameIndex);
auto& frame = m_frames[frameIndex];
auto& buffer = frame.buffer;
if (buffer.IsValid() && buffer.GetSize() >= requiredEndOffset) {
return true;
}
VkDeviceSize newCapacity = buffer.IsValid() ? buffer.GetSize() : 0;
if (newCapacity < m_desc.minBufferSize) {
newCapacity = m_desc.minBufferSize;
}
if (newCapacity == 0) {
newCapacity = requiredEndOffset;
}
while (newCapacity < requiredEndOffset) {
newCapacity *= 2;
}
if (buffer.IsValid()) {
m_deferredReleases[frameIndex].push_back(std::move(buffer));
}
VkBufferObjectDesc bufferDesc{};
bufferDesc.allocator = m_desc.allocator;
bufferDesc.size = newCapacity;
bufferDesc.usage = m_desc.usage;
bufferDesc.memoryUsage = m_desc.memoryUsage;
bufferDesc.allocationFlags = m_desc.allocationFlags;
if (!buffer.Create(bufferDesc)) {
return false;
}
if (m_desc.persistentlyMapped && buffer.Map() == nullptr) {
buffer.Destroy();
return false;
}
frame.writeCursor = 0;
return true;
}
void BufferArena::AssertValidFrameIndex(Uint32 frameIndex) const {
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "BufferArena frame index out of range");
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,56 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include "BufferSlice.h"
#include "VkBufferObject.h"
#include "../VkIncludes.h"
#include <Includes.h>
#include <vk_mem_alloc.h>
namespace MobileGL::MG_Backend::DirectVulkan {
struct BufferArenaDesc {
VmaAllocator allocator = nullptr;
Uint32 frameCount = 0;
VkBufferUsageFlags usage = 0;
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
VmaAllocationCreateFlags allocationFlags = 0;
VkDeviceSize minBufferSize = 0;
Bool persistentlyMapped = false;
};
class BufferArena {
public:
Bool Initialize(const BufferArenaDesc& desc);
void Shutdown();
void BeginFrame(Uint32 frameIndex);
void ResetFrame(Uint32 frameIndex);
void CollectDeferredReleases(Uint32 frameIndex);
Bool Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
VkDeviceSize GetWriteCursor(Uint32 frameIndex) const;
Uint32 GetFrameCount() const;
private:
struct FrameResources {
VkBufferObject buffer;
VkDeviceSize writeCursor = 0;
};
Bool EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset);
void AssertValidFrameIndex(Uint32 frameIndex) const;
BufferArenaDesc m_desc{};
Vector<FrameResources> m_frames;
Vector<Vector<VkBufferObject>> m_deferredReleases;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,23 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include "../VkIncludes.h"
#include <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
struct BufferSlice {
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceSize offset = 0;
VkDeviceSize size = 0;
void* mapped = nullptr;
Bool IsValid() const { return buffer != VK_NULL_HANDLE; }
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -8,6 +8,9 @@
#include "ProgramFactory.h" #include "ProgramFactory.h"
#include "MG_Util/ShaderTranspiler/SpvcSession.h"
#include "MG_Util/ShaderTranspiler/Types.h"
#include <cstring>
#include <spirv-tools/libspirv.h> #include <spirv-tools/libspirv.h>
#include <spirv-tools/optimizer.hpp> #include <spirv-tools/optimizer.hpp>
#include <source/opt/constants.h> #include <source/opt/constants.h>
@@ -21,6 +24,8 @@
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
namespace { namespace {
using ShaderObject = MG_State::GLState::ShaderObject; using ShaderObject = MG_State::GLState::ShaderObject;
using SpvcSession = MG_Util::ShaderTranspiler::SpvcSession;
using SessionUsageBit = MG_Util::ShaderTranspiler::SessionUsageBit;
struct PositionTargetInfo { struct PositionTargetInfo {
Uint32 variableId = 0; Uint32 variableId = 0;
@@ -321,8 +326,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} // namespace } // namespace
ProgramFactory::~ProgramFactory() = default;
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
switch (stage) { switch (stage) {
case ShaderStage::Vertex: case ShaderStage::Vertex:
@@ -351,16 +354,181 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint))); XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
} }
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags))); XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
// Include UBO block bindings in hash so different binding configurations produce different entries
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount)));
for (Uint32 i = 0; i < blockCount; ++i) {
const Uint32 binding = program.GetUniformBlockBinding(i);
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
}
HashType hash = XXH64_digest(m_hashState); HashType hash = XXH64_digest(m_hashState);
return hash; return hash;
} }
Vector<VkPipelineShaderStageCreateInfo>& ProgramFactory::GetOrCreatePipelineShaderStages( TextureTarget ProgramFactory::UniformTypeToTextureTarget(GLenum glType) {
switch (glType) {
case GL_SAMPLER_1D:
case GL_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_1D:
return TextureTarget::Texture1D;
case GL_SAMPLER_3D:
case GL_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_3D:
return TextureTarget::Texture3D;
case GL_SAMPLER_CUBE:
case GL_SAMPLER_CUBE_SHADOW:
case GL_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
return TextureTarget::TextureCubeMap;
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
return TextureTarget::Texture2DMultisample;
case GL_SAMPLER_BUFFER:
case GL_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
return TextureTarget::TextureBuffer;
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
return TextureTarget::Texture1DArray;
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
return TextureTarget::Texture2DArray;
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
return TextureTarget::Texture2DMultisampleArray;
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_2D_RECT_SHADOW:
case GL_INT_SAMPLER_2D_RECT:
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
return TextureTarget::TextureRectangle;
case GL_SAMPLER_2D:
case GL_SAMPLER_2D_SHADOW:
case GL_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_2D:
default:
return TextureTarget::Texture2D;
}
}
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
VkProgramObject& entry) const {
// Initialize layout vectors
entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None);
entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1);
entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D);
entry.globalUboBinding = -1;
entry.dynamicBindings.clear();
// Use SpvcSession (Reflection mode) to reflect all SPIR-V modules in a single pass per module
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
continue;
}
SpvcSession session(module, SessionUsageBit::Reflection);
// Reflect uniform buffers
auto ubos = session.GetShaderInterface(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER);
for (const auto& ubo : ubos) {
const Uint32 binding = ubo.location; // GetShaderInterface stores binding in location field
if (binding >= m_maxBindings) {
continue;
}
if (entry.bindingKinds[binding] == DescriptorBindingKind::None) {
entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic;
}
// Check for global UBO
if (entry.globalUboBinding < 0 &&
std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) {
entry.globalUboBinding = static_cast<Int>(binding);
}
}
// Reflect sampled images
auto samplers = session.GetShaderInterface(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE);
for (const auto& sampler : samplers) {
const Uint32 binding = sampler.location; // GetShaderInterface stores binding in location field
if (binding >= m_maxBindings) {
continue;
}
// Sampler always wins over UBO for a binding slot
entry.bindingKinds[binding] = DescriptorBindingKind::CombinedImageSampler;
// Resolve uniform location for this sampler
String uniformName = sampler.name;
Int location = program.GetUniformLocation(uniformName);
if (location < 0) {
const auto arraySuffix = uniformName.find("[0]");
if (arraySuffix != String::npos) {
uniformName = uniformName.substr(0, arraySuffix);
location = program.GetUniformLocation(uniformName);
}
}
if (location < 0) {
continue;
}
entry.samplerUniformLocationByBinding[binding] = location;
entry.samplerTextureTargetByBinding[binding] =
UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
}
}
// Build Vulkan descriptor set layout and pipeline layout from reflected binding kinds
Vector<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(m_maxBindings);
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const auto kind = entry.bindingKinds[binding];
if (kind == DescriptorBindingKind::None) {
continue;
}
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = binding;
layoutBinding.descriptorCount = 1;
layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
layoutBinding.pImmutableSamplers = nullptr;
if (kind == DescriptorBindingKind::UniformBufferDynamic) {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
entry.dynamicBindings.push_back(binding);
} else {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
bindings.push_back(layoutBinding);
}
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
setLayoutInfo.pBindings = bindings.data();
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout),
"ProgramFactory::ReflectLayout, vkCreateDescriptorSetLayout");
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &entry.descriptorSetLayout;
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &entry.pipelineLayout),
"ProgramFactory::ReflectLayout, vkCreatePipelineLayout");
}
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) { const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
auto hash = ComputeHash(program, flags); auto hash = ComputeHash(program, flags);
auto it = m_cache.find(hash); auto it = m_cache.find(hash);
if (it != m_cache.end()) { if (it != m_cache.end()) {
return it->second.stages; return it->second;
} }
auto& entry = m_cache[hash]; auto& entry = m_cache[hash];
@@ -400,6 +568,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.stages.push_back(stage); entry.stages.push_back(stage);
} }
return entry.stages; // Reflect and create layout as part of the program object
ReflectLayout(program, entry);
return entry;
} }
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -11,11 +11,19 @@
#include "../VkIncludes.h" #include "../VkIncludes.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_State/GLState/ProgramState/ShaderObject.h" #include "MG_State/GLState/ProgramState/ShaderObject.h"
#include "MG_State/GLState/TextureState/TextureEnum.h"
#include <Includes.h> #include <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
class ProgramFactory { class ProgramFactory {
public: public:
enum class DescriptorBindingKind : Uint8 {
None = 0,
UniformBufferDynamic,
CombinedImageSampler
};
enum class CompileOptionBit : Uint { enum class CompileOptionBit : Uint {
None = 0, None = 0,
PositionYFlip = 1 << 0, PositionYFlip = 1 << 0,
@@ -26,10 +34,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
using CompileOptionFlags = Flags<CompileOptionBit>; using CompileOptionFlags = Flags<CompileOptionBit>;
using HashType = Uint64; using HashType = Uint64;
struct VkProgramObject { struct VkProgramObject {
HashType hash = 0; HashType hash = 0;
Vector<VkPipelineShaderStageCreateInfo> stages; Vector<VkPipelineShaderStageCreateInfo> stages;
Vector<VkShaderModule> modules; Vector<VkShaderModule> modules;
// Layout data (previously in separate VkProgramLayout)
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<DescriptorBindingKind> bindingKinds;
Vector<Uint32> dynamicBindings;
Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> samplerTextureTargetByBinding;
Int globalUboBinding = -1;
static inline VkDevice s_device = VK_NULL_HANDLE; static inline VkDevice s_device = VK_NULL_HANDLE;
VkProgramObject() = default; VkProgramObject() = default;
@@ -39,52 +58,85 @@ namespace MobileGL::MG_Backend::DirectVulkan {
hash = other.hash; hash = other.hash;
stages = std::move(other.stages); stages = std::move(other.stages);
modules = std::move(other.modules); modules = std::move(other.modules);
descriptorSetLayout = other.descriptorSetLayout;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
dynamicBindings = std::move(other.dynamicBindings);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
globalUboBinding = other.globalUboBinding;
other.hash = 0; other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.pipelineLayout = VK_NULL_HANDLE;
other.globalUboBinding = -1;
} }
VkProgramObject& operator=(VkProgramObject&& other) noexcept { VkProgramObject& operator=(VkProgramObject&& other) noexcept {
if (this == &other) { if (this == &other) {
return *this; return *this;
} }
DestroyModules(); Destroy();
stages.clear();
hash = other.hash; hash = other.hash;
stages = std::move(other.stages); stages = std::move(other.stages);
modules = std::move(other.modules); modules = std::move(other.modules);
descriptorSetLayout = other.descriptorSetLayout;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
dynamicBindings = std::move(other.dynamicBindings);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
globalUboBinding = other.globalUboBinding;
other.hash = 0; other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.pipelineLayout = VK_NULL_HANDLE;
other.globalUboBinding = -1;
return *this; return *this;
} }
~VkProgramObject() { ~VkProgramObject() {
DestroyModules(); Destroy();
stages.clear();
} }
private: private:
void DestroyModules() { void Destroy() {
for (auto module : modules) { if (s_device != VK_NULL_HANDLE) {
if (module != VK_NULL_HANDLE && s_device != VK_NULL_HANDLE) { if (pipelineLayout != VK_NULL_HANDLE) {
vkDestroyShaderModule(s_device, module, nullptr); vkDestroyPipelineLayout(s_device, pipelineLayout, nullptr);
pipelineLayout = VK_NULL_HANDLE;
}
if (descriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(s_device, descriptorSetLayout, nullptr);
descriptorSetLayout = VK_NULL_HANDLE;
}
for (auto module : modules) {
if (module != VK_NULL_HANDLE) {
vkDestroyShaderModule(s_device, module, nullptr);
}
} }
} }
modules.clear(); modules.clear();
stages.clear();
} }
}; };
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config) explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16)
: m_device(device), m_config(config) { : m_device(device), m_config(config), m_maxBindings(maxBindings) {
VkProgramObject::s_device = device; VkProgramObject::s_device = device;
} }
~ProgramFactory(); ~ProgramFactory() = default;
ProgramFactory(const ProgramFactory&) = delete; ProgramFactory(const ProgramFactory&) = delete;
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const; HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
Vector<VkPipelineShaderStageCreateInfo>& GetOrCreatePipelineShaderStages( const VkProgramObject& GetOrCreateProgram(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
static VkShaderStageFlagBits ToVkStage(ShaderStage stage); static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
private: private:
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const;
VkDevice m_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE;
Uint32 m_maxBindings = 0;
UnorderedMap<HashType, VkProgramObject> m_cache; UnorderedMap<HashType, VkProgramObject> m_cache;
const VulkanRendererConfig& m_config; const VulkanRendererConfig& m_config;
static inline XXH64_state_t* m_hashState = XXH64_createState(); static inline XXH64_state_t* m_hashState = XXH64_createState();
@@ -1,985 +0,0 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "UniformDescriptorBinder.h"
#include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#include "MG_Util/ShaderTranspiler/Types.h"
#include <limits>
namespace MobileGL::MG_Backend::DirectVulkan {
static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer,
const MG_State::GLState::ITextureObject& texture,
FramebufferAttachmentType& outAttachment, Int& outLevel) {
const auto& attachments = framebuffer.GetAllAttachmentObjects();
for (SizeT i = 0; i < attachments.size(); ++i) {
const auto attachmentType = static_cast<FramebufferAttachmentType>(i);
if (attachmentType == FramebufferAttachmentType::None) {
continue;
}
const auto& attachment = attachments[i];
if (!attachment.IsTexture()) {
continue;
}
auto attachedTexture = attachment.GetTexture();
if (attachedTexture && attachedTexture.get() == &texture) {
outAttachment = attachmentType;
outLevel = attachment.GetTextureLevel();
return true;
}
}
return false;
}
static Bool IsValidSampledImageLayout(VkImageLayout layout) {
switch (layout) {
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_GENERAL:
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
return true;
default:
return false;
}
}
VkDeviceSize UniformDescriptorBinder::AlignUp(VkDeviceSize value, VkDeviceSize alignment) {
if (alignment == 0) {
return value;
}
return (value + alignment - 1) / alignment * alignment;
}
Uint64 UniformDescriptorBinder::ComputeProgramHash(const MG_State::GLState::ProgramObject& program) {
XXH64_state_t* state = XXH64_createState();
XXHASH_VERIFY(XXH64_reset(state, 0xC0D3A11ULL));
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
XXHASH_VERIFY(XXH64_update(state, module.data(), module.size() * sizeof(Uint)));
}
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
XXHASH_VERIFY(XXH64_update(state, &blockCount, sizeof(blockCount)));
for (Uint32 i = 0; i < blockCount; ++i) {
const Uint32 binding = program.GetUniformBlockBinding(i);
XXHASH_VERIFY(XXH64_update(state, &binding, sizeof(binding)));
}
const Uint64 hash = XXH64_digest(state);
XXH64_freeState(state);
return hash;
}
Bool UniformDescriptorBinder::IsSamplerUniformType(GLenum glType) {
switch (glType) {
case GL_SAMPLER_1D:
case GL_SAMPLER_2D:
case GL_SAMPLER_3D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_1D_SHADOW:
case GL_SAMPLER_2D_SHADOW:
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_SAMPLER_CUBE_SHADOW:
case GL_SAMPLER_BUFFER:
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_2D_RECT_SHADOW:
case GL_INT_SAMPLER_1D:
case GL_INT_SAMPLER_2D:
case GL_INT_SAMPLER_3D:
case GL_INT_SAMPLER_CUBE:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_BUFFER:
case GL_INT_SAMPLER_2D_RECT:
case GL_UNSIGNED_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
return true;
default:
return false;
}
}
TextureTarget UniformDescriptorBinder::UniformTypeToTextureTarget(GLenum glType) {
switch (glType) {
case GL_SAMPLER_1D:
case GL_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_1D:
return TextureTarget::Texture1D;
case GL_SAMPLER_3D:
case GL_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_3D:
return TextureTarget::Texture3D;
case GL_SAMPLER_CUBE:
case GL_SAMPLER_CUBE_SHADOW:
case GL_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
return TextureTarget::TextureCubeMap;
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
return TextureTarget::Texture2DMultisample;
case GL_SAMPLER_BUFFER:
case GL_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
return TextureTarget::TextureBuffer;
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
return TextureTarget::Texture1DArray;
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
return TextureTarget::Texture2DArray;
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
return TextureTarget::Texture2DMultisampleArray;
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_2D_RECT_SHADOW:
case GL_INT_SAMPLER_2D_RECT:
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
return TextureTarget::TextureRectangle;
case GL_SAMPLER_2D:
case GL_SAMPLER_2D_SHADOW:
case GL_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_2D:
default:
return TextureTarget::Texture2D;
}
}
Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes,
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Shutdown();
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
MOBILEGL_ASSERT(allocator != nullptr, "UniformDescriptorBinder::Initialize requires valid VMA allocator");
MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0");
MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0");
MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0");
MOBILEGL_ASSERT(textureManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid texture manager");
MOBILEGL_ASSERT(samplerManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid sampler manager");
m_device = device;
m_allocator = allocator;
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
m_perFrameUploadBytes = perFrameUploadBytes;
m_frameCount = frameCount;
m_maxBindings = maxBindings;
m_setsPerFrame = setsPerFrame;
m_peakDescriptorSetsObserved = 0;
m_textureManager = textureManager;
m_samplerManager = samplerManager;
m_frames.resize(m_frameCount);
for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) {
auto& frame = m_frames[frameIndex];
frame.writeCursor = 0;
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
frame.descriptorPools.clear();
VkDescriptorPool initialPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) {
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
frameIndex);
Shutdown();
return false;
}
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, m_setsPerFrame);
const Bool created = frame.uploadBuffer.Create(
m_allocator, m_perFrameUploadBytes, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT);
if (!created) {
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame upload buffer %u", frameIndex);
Shutdown();
return false;
}
}
return true;
}
void UniformDescriptorBinder::Shutdown() {
for (auto& frame : m_frames) {
frame.uploadBuffer.Destroy();
if (m_device != VK_NULL_HANDLE) {
for (auto& bucket : frame.descriptorPools) {
if (bucket.handle != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(m_device, bucket.handle, nullptr);
bucket.handle = VK_NULL_HANDLE;
}
}
}
frame.descriptorPools.clear();
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
frame.writeCursor = 0;
}
m_frames.clear();
DestroyProgramLayouts();
m_allocator = nullptr;
m_device = VK_NULL_HANDLE;
m_minDynamicOffsetAlignment = 1;
m_perFrameUploadBytes = 0;
m_frameCount = 0;
m_maxBindings = 0;
m_setsPerFrame = 0;
m_peakDescriptorSetsObserved = 0;
m_textureManager = nullptr;
m_samplerManager = nullptr;
}
void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index");
auto& frame = m_frames[frameIndex];
if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) {
m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame;
MGLOG_D(
"UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)",
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
}
frame.writeCursor = 0;
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
for (auto& bucket : frame.descriptorPools) {
bucket.allocatedSets = 0;
if (bucket.handle == VK_NULL_HANDLE) {
continue;
}
VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0),
"UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool");
}
}
Bool UniformDescriptorBinder::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program,
Vector<BindingKind>& outKinds) const {
outKinds.assign(m_maxBindings, BindingKind::None);
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
continue;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
}
const spvc_result parseResult = spvc_context_parse_spirv(context, module.data(), module.size(), &ir);
if (parseResult != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_result compilerResult =
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
if (compilerResult != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const auto applyBindings = [&](spvc_resource_type resourceType, BindingKind kind) {
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, resourceType, &list, &count) != SPVC_SUCCESS) {
return;
}
for (size_t i = 0; i < count; ++i) {
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding >= m_maxBindings) {
continue;
}
if (kind == BindingKind::CombinedImageSampler) {
outKinds[binding] = BindingKind::CombinedImageSampler;
} else if (outKinds[binding] == BindingKind::None) {
outKinds[binding] = kind;
}
}
};
applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, BindingKind::UniformBufferDynamic);
applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, BindingKind::CombinedImageSampler);
spvc_context_destroy(context);
}
return true;
}
Bool UniformDescriptorBinder::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program,
ProgramLayout& layout) const {
layout.samplerUniformLocationByBinding.assign(m_maxBindings, -1);
layout.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D);
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
continue;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
}
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
&compiler) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count) ==
SPVC_SUCCESS) {
for (size_t i = 0; i < count; ++i) {
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding >= m_maxBindings) {
continue;
}
String uniformName = list[i].name ? list[i].name : "";
Int location = program.GetUniformLocation(uniformName);
if (location < 0) {
const auto arraySuffix = uniformName.find("[0]");
if (arraySuffix != String::npos) {
uniformName = uniformName.substr(0, arraySuffix);
location = program.GetUniformLocation(uniformName);
}
}
if (location < 0) {
continue;
}
layout.samplerUniformLocationByBinding[binding] = location;
layout.samplerTextureTargetByBinding[binding] =
UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
}
}
spvc_context_destroy(context);
}
return true;
}
Bool UniformDescriptorBinder::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program,
ProgramLayout& layout) const {
layout.globalUboBinding = -1;
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
continue;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
}
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
&compiler) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count) ==
SPVC_SUCCESS) {
for (size_t i = 0; i < count; ++i) {
const char* name = list[i].name ? list[i].name : "";
if (std::strstr(name, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) {
continue;
}
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding < m_maxBindings) {
layout.globalUboBinding = static_cast<Int>(binding);
}
break;
}
}
spvc_context_destroy(context);
if (layout.globalUboBinding >= 0) {
break;
}
}
return true;
}
Bool UniformDescriptorBinder::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
const ProgramLayout& layout, 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");
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, layout, binding, texture)) {
return false;
}
const Int location = layout.samplerUniformLocationByBinding[binding];
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const auto samplerOverride = textureUnit.GetSamplerObject();
if (!texture) {
return false;
}
const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
if (!samplerToUse) {
return false;
}
VkTextureManager::TextureResource* resource =
m_textureManager->SyncTextureAndGetDescriptor(*texture);
if (resource == nullptr) {
return false;
}
if (!IsValidSampledImageLayout(resource->layout)) {
auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
FramebufferAttachmentType attachmentType = FramebufferAttachmentType::None;
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));
}
MOBILEGL_ASSERT(false,
"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),
.imageView = resource->view,
.imageLayout = resource->layout,
};
return outImageInfo.sampler != VK_NULL_HANDLE;
}
Bool UniformDescriptorBinder::ResolveSamplerDescriptorOverride(
const SamplerBindingOverride& samplerBindingOverride,
VkDescriptorImageInfo& outImageInfo) const {
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null");
if (samplerBindingOverride.texture == nullptr || samplerBindingOverride.sampler == nullptr) {
return false;
}
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture);
if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) {
return false;
}
outImageInfo = {
.sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler),
.imageView = resource->view,
.imageLayout = resource->layout,
};
return outImageInfo.sampler != VK_NULL_HANDLE;
}
Bool UniformDescriptorBinder::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
const ProgramLayout& layout, Uint32 binding,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const {
outTexture.reset();
if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) {
return false;
}
const Int location = layout.samplerUniformLocationByBinding[binding];
if (location < 0) {
return false;
}
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
if (unit < 0) {
return false;
}
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding];
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
return outTexture != nullptr;
}
Bool UniformDescriptorBinder::CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
Vector<MG_State::GLState::ITextureObject*>& outTextures) {
outTextures.clear();
ProgramLayout* layout = GetOrCreateProgramLayout(program);
if (layout == nullptr) {
return false;
}
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
if (layout->bindingKinds[binding] != BindingKind::CombinedImageSampler) {
continue;
}
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, *layout, binding, texture) || !texture) {
continue;
}
auto found = std::find(outTextures.begin(), outTextures.end(), texture.get());
if (found == outTextures.end()) {
outTextures.push_back(texture.get());
}
}
return true;
}
UniformDescriptorBinder::ProgramLayout* UniformDescriptorBinder::GetOrCreateProgramLayout(
const MG_State::GLState::ProgramObject& program) {
const Uint64 hash = ComputeProgramHash(program);
auto it = m_programLayouts.find(hash);
if (it != m_programLayouts.end()) {
return &it->second;
}
ProgramLayout layout{};
layout.hash = hash;
if (!ReflectBindingKinds(program, layout.bindingKinds)) {
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: reflection failed");
return nullptr;
}
if (!ReflectSamplerBindings(program, layout)) {
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: sampler reflection failed");
return nullptr;
}
if (!ReflectGlobalUboBinding(program, layout)) {
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: global UBO reflection failed");
return nullptr;
}
Vector<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(m_maxBindings);
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const auto kind = layout.bindingKinds[binding];
if (kind == BindingKind::None) {
continue;
}
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = binding;
layoutBinding.descriptorCount = 1;
layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
layoutBinding.pImmutableSamplers = nullptr;
if (kind == BindingKind::UniformBufferDynamic) {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
layout.dynamicBindings.push_back(binding);
} else {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
bindings.push_back(layoutBinding);
}
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
setLayoutInfo.pBindings = bindings.data();
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &layout.descriptorSetLayout),
"UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreateDescriptorSetLayout");
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &layout.descriptorSetLayout;
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &layout.pipelineLayout),
"UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreatePipelineLayout");
auto [insertIt, _] = m_programLayouts.emplace(hash, std::move(layout));
return &insertIt->second;
}
VkPipelineLayout UniformDescriptorBinder::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) {
auto* layout = GetOrCreateProgramLayout(program);
return layout ? layout->pipelineLayout : VK_NULL_HANDLE;
}
Bool UniformDescriptorBinder::AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset) {
const VkDeviceSize alignedOffset = AlignUp(frame.writeCursor, m_minDynamicOffsetAlignment);
if (alignedOffset + size > m_perFrameUploadBytes) {
return false;
}
outOffset = alignedOffset;
frame.writeCursor = alignedOffset + size;
return true;
}
Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program,
Vector<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const {
outData.assign(m_maxBindings, nullptr);
outSizes.assign(m_maxBindings, 0);
const Uint32 activeUniformBlockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform));
for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) {
const Uint32 binding = program.GetUniformBlockBinding(blockIndex);
if (binding >= m_maxBindings) {
continue;
}
VkDeviceSize blockSize = static_cast<VkDeviceSize>(program.GetUBOSizeAt(blockIndex));
if (blockSize == 0) {
continue;
}
if (binding >= uniformBindingPointCount) {
continue;
}
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding);
const auto bufferObject = bindingPoint.GetBoundObject();
if (!bufferObject) {
continue;
}
const auto bufferData = bufferObject->GetDataReadOnly();
if (!bufferData || bufferData->empty()) {
continue;
}
const auto range = bindingPoint.GetRange();
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(range.end);
if (rangeStart >= bufferSize) {
continue;
}
if (rangeEnd <= rangeStart || rangeEnd > bufferSize) {
rangeEnd = bufferSize;
}
VkDeviceSize available = rangeEnd - rangeStart;
if (available == 0) {
continue;
}
outData[binding] = bufferData->data() + static_cast<SizeT>(rangeStart);
outSizes[binding] = std::min(blockSize, available);
}
return true;
}
Bool UniformDescriptorBinder::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const {
outPool = VK_NULL_HANDLE;
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
return false;
}
const Uint64 descriptorCount64 = static_cast<Uint64>(maxSets) * static_cast<Uint64>(m_maxBindings);
if (descriptorCount64 > static_cast<Uint64>(std::numeric_limits<Uint32>::max())) {
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow");
return false;
}
const Uint32 descriptorCount = static_cast<Uint32>(descriptorCount64);
VkDescriptorPoolSize poolSizes[2]{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
poolSizes[0].descriptorCount = descriptorCount;
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = descriptorCount;
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.maxSets = maxSets;
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
poolInfo.pPoolSizes = poolSizes;
const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool);
if (result != VK_SUCCESS) {
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", result);
return false;
}
return true;
}
Bool UniformDescriptorBinder::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) {
if (frame.descriptorPools.empty()) {
return false;
}
const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
const Uint32 currentMaxSets = std::max<Uint32>(1, currentBucket.maxSets);
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
: currentMaxSets;
VkDescriptorPool grownPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(grownMaxSets, grownPool)) {
MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
currentMaxSets, grownMaxSets);
return false;
}
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0});
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
MGLOG_D(
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size());
return true;
}
Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
Uint32 frameIndex) {
return BindProgramUniformBuffers(commandBuffer, program, frameIndex, nullptr);
}
Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride) {
ProgramLayout* layout = GetOrCreateProgramLayout(program);
MOBILEGL_ASSERT(layout != nullptr,
"UniformDescriptorBinder::BindProgramUniformBuffers: program layout is null");
auto& frame = m_frames[frameIndex];
if (frame.descriptorPools.empty()) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
return false;
}
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
frame.activeDescriptorPoolIndex = 0;
}
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &layout->descriptorSetLayout;
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
auto allocateFromActivePool = [&](VkResult& outResult) {
auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
allocInfo.descriptorPool = bucket.handle;
outResult = vkAllocateDescriptorSets(m_device, &allocInfo, &descriptorSet);
if (outResult == VK_SUCCESS) {
++bucket.allocatedSets;
++frame.allocatedSetsThisFrame;
frame.peakAllocatedSetsThisFrame = std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
}
};
VkResult allocResult = VK_SUCCESS;
allocateFromActivePool(allocResult);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed");
return false;
}
allocateFromActivePool(allocResult);
}
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d",
allocResult);
return false;
}
Vector<const void*> bindingData;
Vector<VkDeviceSize> bindingSizes;
if (!GatherBindingPayloads(program, bindingData, bindingSizes)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads");
return false;
}
static const Uint8 kFallbackData[16] = {};
MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
Vector<VkWriteDescriptorSet> writes;
writes.reserve(m_maxBindings);
Vector<VkDescriptorBufferInfo> bufferInfos;
Vector<VkDescriptorImageInfo> imageInfos;
Vector<Uint32> dynamicOffsets;
bufferInfos.reserve(m_maxBindings);
imageInfos.reserve(m_maxBindings);
dynamicOffsets.reserve(layout->dynamicBindings.size());
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const auto kind = layout->bindingKinds[binding];
if (kind == BindingKind::None) {
continue;
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = descriptorSet;
write.dstBinding = binding;
write.dstArrayElement = 0;
write.descriptorCount = 1;
if (kind == BindingKind::UniformBufferDynamic) {
const void* payload = bindingData[binding];
VkDeviceSize payloadSize = bindingSizes[binding];
if (payload == nullptr || payloadSize == 0) {
if (layout->globalUboBinding == static_cast<Int>(binding)) {
const void* globalUboData = program.GetUBOData();
const VkDeviceSize globalUboSize = static_cast<VkDeviceSize>(program.GetUBOSize());
if (globalUboData != nullptr && globalUboSize > 0) {
payload = globalUboData;
payloadSize = globalUboSize;
}
}
if (payload == nullptr || payloadSize == 0) {
payload = kFallbackData;
payloadSize = sizeof(kFallbackData);
}
}
VkDeviceSize payloadOffset = 0;
if (!AllocateUploadRegion(frame, payloadSize, payloadOffset)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame upload buffer exhausted");
return false;
}
if (!frame.uploadBuffer.Upload(payload, payloadSize, payloadOffset)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
binding);
return false;
}
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = frame.uploadBuffer.GetHandle();
bufferInfo.offset = 0;
bufferInfo.range = payloadSize;
bufferInfos.push_back(bufferInfo);
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
write.pBufferInfo = &bufferInfos.back();
writes.push_back(write);
dynamicOffsets.push_back(static_cast<Uint32>(payloadOffset));
} else {
VkDescriptorImageInfo imageInfo{};
Bool hasImage = false;
if (samplerBindingOverride != nullptr &&
samplerBindingOverride->binding == binding &&
samplerBindingOverride->texture != nullptr &&
samplerBindingOverride->sampler != nullptr) {
hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo);
} else {
hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo);
}
if (!hasImage) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor",
binding);
return false;
}
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView",
binding);
return false;
}
imageInfos.push_back(imageInfo);
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &imageInfos.back();
writes.push_back(write);
}
}
if (!writes.empty()) {
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
}
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->pipelineLayout, 0, 1, &descriptorSet,
static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
return true;
}
void UniformDescriptorBinder::DestroyProgramLayouts() {
for (auto& [_, layout] : m_programLayouts) {
if (layout.pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(m_device, layout.pipelineLayout, nullptr);
layout.pipelineLayout = VK_NULL_HANDLE;
}
if (layout.descriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(m_device, layout.descriptorSetLayout, nullptr);
layout.descriptorSetLayout = VK_NULL_HANDLE;
}
}
m_programLayouts.clear();
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,556 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "UniformManager.h"
#include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#include <limits>
namespace MobileGL::MG_Backend::DirectVulkan {
static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer,
const MG_State::GLState::ITextureObject& texture,
FramebufferAttachmentType& outAttachment, Int& outLevel) {
const auto& attachments = framebuffer.GetAllAttachmentObjects();
for (SizeT i = 0; i < attachments.size(); ++i) {
const auto attachmentType = static_cast<FramebufferAttachmentType>(i);
if (attachmentType == FramebufferAttachmentType::None) {
continue;
}
const auto& attachment = attachments[i];
if (!attachment.IsTexture()) {
continue;
}
auto attachedTexture = attachment.GetTexture();
if (attachedTexture && attachedTexture.get() == &texture) {
outAttachment = attachmentType;
outLevel = attachment.GetTextureLevel();
return true;
}
}
return false;
}
static Bool IsValidSampledImageLayout(VkImageLayout layout) {
switch (layout) {
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_GENERAL:
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
return true;
default:
return false;
}
}
Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager,
ProgramFactory* programFactory,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings, Uint32 setsPerFrame,
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Shutdown();
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
MOBILEGL_ASSERT(bufferManager != nullptr, "UniformDescriptorBinder::Initialize requires valid buffer manager");
MOBILEGL_ASSERT(programFactory != nullptr,
"UniformDescriptorBinder::Initialize requires valid program factory");
MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0");
MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0");
MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0");
MOBILEGL_ASSERT(textureManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid texture manager");
MOBILEGL_ASSERT(samplerManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid sampler manager");
m_device = device;
m_bufferManager = bufferManager;
m_programFactory = programFactory;
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
m_frameCount = frameCount;
m_maxBindings = maxBindings;
m_setsPerFrame = setsPerFrame;
m_peakDescriptorSetsObserved = 0;
m_textureManager = textureManager;
m_samplerManager = samplerManager;
m_frames.resize(m_frameCount);
for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) {
auto& frame = m_frames[frameIndex];
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
frame.descriptorPools.clear();
VkDescriptorPool initialPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) {
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
frameIndex);
Shutdown();
return false;
}
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex,
m_setsPerFrame);
}
return true;
}
void UniformManager::Shutdown() {
for (auto& frame : m_frames) {
if (m_device != VK_NULL_HANDLE) {
for (auto& bucket : frame.descriptorPools) {
if (bucket.handle != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(m_device, bucket.handle, nullptr);
bucket.handle = VK_NULL_HANDLE;
}
}
}
frame.descriptorPools.clear();
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
}
m_frames.clear();
m_bufferManager = nullptr;
m_programFactory = nullptr;
m_device = VK_NULL_HANDLE;
m_minDynamicOffsetAlignment = 1;
m_frameCount = 0;
m_maxBindings = 0;
m_setsPerFrame = 0;
m_peakDescriptorSetsObserved = 0;
m_textureManager = nullptr;
m_samplerManager = nullptr;
}
void UniformManager::BeginFrame(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index");
auto& frame = m_frames[frameIndex];
if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) {
m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame;
MGLOG_D(
"UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)",
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
}
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
for (auto& bucket : frame.descriptorPools) {
bucket.allocatedSets = 0;
if (bucket.handle == VK_NULL_HANDLE) {
continue;
}
VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0),
"UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool");
}
}
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
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");
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, programObj, binding, texture)) {
return false;
}
const Int location = programObj.samplerUniformLocationByBinding[binding];
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const auto samplerOverride = textureUnit.GetSamplerObject();
if (!texture) {
return false;
}
const MG_State::GLState::SamplerObject* samplerToUse =
samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
if (!samplerToUse) {
return false;
}
VkTextureManager::TextureResource* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
if (resource == nullptr) {
return false;
}
if (!IsValidSampledImageLayout(resource->layout)) {
auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
FramebufferAttachmentType attachmentType = FramebufferAttachmentType::None;
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));
}
MOBILEGL_ASSERT(false,
"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),
.imageView = resource->fullView,
.imageLayout = resource->layout,
};
return outImageInfo.sampler != VK_NULL_HANDLE;
}
Bool UniformManager::ResolveSamplerDescriptorOverride(
const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const {
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null");
if (samplerBindingOverride.texture == nullptr || samplerBindingOverride.sampler == nullptr) {
return false;
}
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture);
if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) {
return false;
}
outImageInfo = {
.sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler),
.imageView = resource->fullView,
.imageLayout = resource->layout,
};
return outImageInfo.sampler != VK_NULL_HANDLE;
}
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const {
outTexture.reset();
if (!MG_State::pGLContext || binding >= programObj.samplerUniformLocationByBinding.size()) {
return false;
}
const Int location = programObj.samplerUniformLocationByBinding[binding];
if (location < 0) {
return false;
}
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
if (unit < 0) {
return false;
}
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
return outTexture != nullptr;
}
Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures) {
outTextures.clear();
const Uint32 bindingCount =
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
continue;
}
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, programObj, binding, texture) || !texture) {
continue;
}
auto found = std::find(outTextures.begin(), outTextures.end(), texture.get());
if (found == outTextures.end()) {
outTextures.push_back(texture.get());
}
}
return true;
}
Bool UniformManager::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program,
Vector<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const {
outData.assign(m_maxBindings, nullptr);
outSizes.assign(m_maxBindings, 0);
const Uint32 activeUniformBlockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform));
for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) {
const Uint32 binding = program.GetUniformBlockBinding(blockIndex);
if (binding >= m_maxBindings) {
continue;
}
VkDeviceSize blockSize = static_cast<VkDeviceSize>(program.GetUBOSizeAt(blockIndex));
if (blockSize == 0) {
continue;
}
if (binding >= uniformBindingPointCount) {
continue;
}
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding);
const auto bufferObject = bindingPoint.GetBoundObject();
if (!bufferObject) {
continue;
}
const auto bufferData = bufferObject->GetDataReadOnly();
if (!bufferData || bufferData->empty()) {
continue;
}
const auto range = bindingPoint.GetRange();
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(range.end);
if (rangeStart >= bufferSize) {
continue;
}
if (rangeEnd <= rangeStart || rangeEnd > bufferSize) {
rangeEnd = bufferSize;
}
VkDeviceSize available = rangeEnd - rangeStart;
if (available == 0) {
continue;
}
outData[binding] = bufferData->data() + static_cast<SizeT>(rangeStart);
outSizes[binding] = std::min(blockSize, available);
}
return true;
}
Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const {
outPool = VK_NULL_HANDLE;
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
return false;
}
const Uint64 descriptorCount64 = static_cast<Uint64>(maxSets) * static_cast<Uint64>(m_maxBindings);
if (descriptorCount64 > static_cast<Uint64>(std::numeric_limits<Uint32>::max())) {
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow");
return false;
}
const Uint32 descriptorCount = static_cast<Uint32>(descriptorCount64);
VkDescriptorPoolSize poolSizes[2]{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
poolSizes[0].descriptorCount = descriptorCount;
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = descriptorCount;
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.maxSets = maxSets;
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
poolInfo.pPoolSizes = poolSizes;
const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool);
if (result != VK_SUCCESS) {
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d",
result);
return false;
}
return true;
}
Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) {
if (frame.descriptorPools.empty()) {
return false;
}
const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
const Uint32 currentMaxSets = std::max<Uint32>(1, currentBucket.maxSets);
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
: currentMaxSets;
VkDescriptorPool grownPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(grownMaxSets, grownPool)) {
MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
currentMaxSets, grownMaxSets);
return false;
}
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0});
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
MGLOG_D(
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size());
return true;
}
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &programObj.descriptorSetLayout;
allocInfo.descriptorPool = bucket.handle;
VkResult result = vkAllocateDescriptorSets(m_device, &allocInfo, &outDescriptorSet);
if (result == VK_SUCCESS) {
++bucket.allocatedSets;
++frame.allocatedSetsThisFrame;
frame.peakAllocatedSetsThisFrame =
std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
}
return result;
}
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride) {
auto& frame = m_frames[frameIndex];
if (frame.descriptorPools.empty()) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
return false;
}
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
frame.activeDescriptorPoolIndex = 0;
}
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed");
return false;
}
allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
}
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d",
allocResult);
return false;
}
Vector<const void*> bindingData;
Vector<VkDeviceSize> bindingSizes;
if (!GatherBindingPayloads(program, bindingData, bindingSizes)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads");
return false;
}
MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null");
Vector<VkWriteDescriptorSet> writes;
writes.reserve(m_maxBindings);
Vector<VkDescriptorBufferInfo> bufferInfos;
Vector<VkDescriptorImageInfo> imageInfos;
Vector<Uint32> dynamicOffsets;
bufferInfos.reserve(m_maxBindings);
imageInfos.reserve(m_maxBindings);
dynamicOffsets.reserve(programObj.dynamicBindings.size());
const Uint32 bindingCount =
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
const auto kind = programObj.bindingKinds[binding];
if (kind == ProgramFactory::DescriptorBindingKind::None) {
continue;
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = descriptorSet;
write.dstBinding = binding;
write.dstArrayElement = 0;
write.descriptorCount = 1;
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
const void* payload = bindingData[binding];
VkDeviceSize payloadSize = bindingSizes[binding];
if (payload == nullptr || payloadSize == 0) {
if (programObj.globalUboBinding == static_cast<Int>(binding)) {
const void* globalUboData = program.GetUBOData();
const VkDeviceSize globalUboSize = static_cast<VkDeviceSize>(program.GetUBOSize());
if (globalUboData != nullptr && globalUboSize > 0) {
payload = globalUboData;
payloadSize = globalUboSize;
}
}
}
BufferSlice slice{};
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize,
m_minDynamicOffsetAlignment, slice)) {
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
binding);
return false;
}
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = slice.buffer;
bufferInfo.offset = 0;
bufferInfo.range = payloadSize;
bufferInfos.push_back(bufferInfo);
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
write.pBufferInfo = &bufferInfos.back();
writes.push_back(write);
dynamicOffsets.push_back(static_cast<Uint32>(slice.offset));
} else {
VkDescriptorImageInfo imageInfo{};
Bool hasImage = false;
if (samplerBindingOverride != nullptr &&
samplerBindingOverride->binding == binding &&
samplerBindingOverride->texture != nullptr &&
samplerBindingOverride->sampler != nullptr) {
hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo);
} else {
hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo);
}
if (!hasImage) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor",
binding);
return false;
}
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView",
binding);
return false;
}
imageInfos.push_back(imageInfo);
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &imageInfos.back();
writes.push_back(write);
}
}
if (!writes.empty()) {
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
}
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, programObj.pipelineLayout, 0, 1,
&descriptorSet, static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
return true;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -8,12 +8,12 @@
#pragma once #pragma once
#include "VkBufferObject.h" #include "ProgramFactory.h"
#include "VkBufferManager.h"
#include "VkSamplerManager.h" #include "VkSamplerManager.h"
#include "VkTextureManager.h" #include "VkTextureManager.h"
#include "../VkIncludes.h" #include "../VkIncludes.h"
#include <Includes.h> #include <Includes.h>
#include <vk_mem_alloc.h>
namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_State::GLState {
class ITextureObject; class ITextureObject;
@@ -22,35 +22,30 @@ namespace MobileGL::MG_State::GLState {
} }
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
class UniformDescriptorBinder { class UniformManager {
public: public:
enum class BindingKind : Uint8 {
None = 0,
UniformBufferDynamic,
CombinedImageSampler
};
struct SamplerBindingOverride { struct SamplerBindingOverride {
Uint32 binding = 0; Uint32 binding = 0;
MG_State::GLState::ITextureObject* texture = nullptr; MG_State::GLState::ITextureObject* texture = nullptr;
const MG_State::GLState::SamplerObject* sampler = nullptr; const MG_State::GLState::SamplerObject* sampler = nullptr;
}; };
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment, Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, ProgramFactory* programFactory,
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr); VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
void Shutdown(); void Shutdown();
void BeginFrame(Uint32 frameIndex); void BeginFrame(Uint32 frameIndex);
VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program);
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program, Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures); Vector<MG_State::GLState::ITextureObject*>& outTextures);
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex); const MG_State::GLState::ProgramObject& program,
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const ProgramFactory::VkProgramObject& programObj,
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex, Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride); const SamplerBindingOverride* samplerBindingOverride = nullptr);
private: private:
struct DescriptorPoolBucket { struct DescriptorPoolBucket {
@@ -60,54 +55,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
struct FrameResources { struct FrameResources {
VkBufferObject uploadBuffer;
Vector<DescriptorPoolBucket> descriptorPools; Vector<DescriptorPoolBucket> descriptorPools;
Uint32 activeDescriptorPoolIndex = 0; Uint32 activeDescriptorPoolIndex = 0;
Uint32 allocatedSetsThisFrame = 0; Uint32 allocatedSetsThisFrame = 0;
Uint32 peakAllocatedSetsThisFrame = 0; Uint32 peakAllocatedSetsThisFrame = 0;
VkDeviceSize writeCursor = 0;
}; };
struct ProgramLayout { Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
Uint64 hash = 0; const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<BindingKind> bindingKinds;
Vector<Uint32> dynamicBindings;
Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> samplerTextureTargetByBinding;
Int globalUboBinding = -1;
};
static VkDeviceSize AlignUp(VkDeviceSize value, VkDeviceSize alignment);
static Uint64 ComputeProgramHash(const MG_State::GLState::ProgramObject& program);
static Bool IsSamplerUniformType(GLenum glType);
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const;
Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const;
Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, const ProgramLayout& layout,
Uint32 binding, SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const;
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
const ProgramLayout& layout, Uint32 binding, const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
VkDescriptorImageInfo& outImageInfo) const; VkDescriptorImageInfo& outImageInfo) const;
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
VkDescriptorImageInfo& outImageInfo) const; VkDescriptorImageInfo& outImageInfo) const;
Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, Vector<BindingKind>& outKinds) const;
ProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program);
Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset);
Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector<const void*>& outData, Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const; Vector<VkDeviceSize>& outSizes) const;
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const; Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex); Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
void DestroyProgramLayouts(); VkResult AllocateDescriptorSetsFromActivePool(
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
VkDevice m_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE;
VmaAllocator m_allocator = nullptr; VkBufferManager* m_bufferManager = nullptr;
ProgramFactory* m_programFactory = nullptr;
Vector<FrameResources> m_frames; Vector<FrameResources> m_frames;
UnorderedMap<Uint64, ProgramLayout> m_programLayouts;
VkDeviceSize m_minDynamicOffsetAlignment = 1; VkDeviceSize m_minDynamicOffsetAlignment = 1;
VkDeviceSize m_perFrameUploadBytes = 0;
Uint32 m_frameCount = 0; Uint32 m_frameCount = 0;
Uint32 m_maxBindings = 0; Uint32 m_maxBindings = 0;
Uint32 m_setsPerFrame = 0; Uint32 m_setsPerFrame = 0;
@@ -0,0 +1,246 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "VkBufferManager.h"
namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
constexpr Uint32 kResidentBufferGCInterval = 60;
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
} // namespace
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
Shutdown();
MOBILEGL_ASSERT(initInfo.allocator != nullptr, "VkBufferManager::Initialize requires valid allocator");
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count");
m_initInfo = initInfo;
m_deferredResidentReleases.resize(initInfo.frameCount);
m_currentFrameIndex = 0;
return InitializeTransientArenas();
}
void VkBufferManager::Shutdown() {
m_transientUploadArena.Shutdown();
DestroyResidentBuffers();
DestroyDeferredResidentReleases();
m_initInfo = {};
m_currentFrameIndex = 0;
m_residentGcTick = 0;
}
Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) {
MOBILEGL_ASSERT(m_initInfo.allocator != nullptr, "VkBufferManager::RecreateTransientArenas requires initialized manager");
MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count");
m_transientUploadArena.Shutdown();
m_initInfo.frameCount = frameCount;
DestroyDeferredResidentReleases();
m_deferredResidentReleases.resize(frameCount);
m_currentFrameIndex = 0;
return InitializeTransientArenas();
}
void VkBufferManager::BeginFrame(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(),
"VkBufferManager::BeginFrame frame index out of range");
m_currentFrameIndex = frameIndex;
CollectDeferredResidentReleases(frameIndex);
m_transientUploadArena.BeginFrame(frameIndex);
}
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data,
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
(void)kind;
return m_transientUploadArena.Upload(frameIndex, data, size, alignment, outSlice);
}
Bool VkBufferManager::InitializeTransientArenas() {
return m_transientUploadArena.Initialize({
.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,
.memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags,
.minBufferSize = m_initInfo.minUploadBytes,
.persistentlyMapped = m_initInfo.transientPersistentMapping,
});
}
Bool VkBufferManager::SyncResidentBuffer(BufferKind kind,
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice) {
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
MOBILEGL_ASSERT(requiredUsage != 0,
"VkBufferManager::SyncResidentBuffer only supports resident vertex/index buffers");
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::SyncResidentBuffer requires valid buffer object");
CollectResidentGarbageIfNeeded();
const auto* bufferData = bufferObject->GetDataReadOnly().get();
MOBILEGL_ASSERT(bufferData != nullptr, "VkBufferManager::SyncResidentBuffer requires frontend buffer data");
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
if (bufferSize == 0) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: buffer size is zero");
return false;
}
auto& entry = m_residentBuffers[bufferObject.get()];
entry.aliveRef = bufferObject;
const auto changeBits = bufferObject->GetChangeBits();
const Bool needsRecreate = !entry.buffer.IsValid() || entry.size != bufferSize ||
((entry.usage & requiredUsage) != requiredUsage) ||
(changeBits & BufferChangeBits::PreferReallocationBit);
if (needsRecreate) {
const VkBufferUsageFlags recreatedUsage = entry.usage | requiredUsage;
DeferResidentRelease(std::move(entry.buffer));
const Bool created = entry.buffer.Create({
.allocator = m_initInfo.allocator,
.size = bufferSize,
.usage = recreatedUsage,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = kResidentBufferAllocationFlags,
});
if (!created || entry.buffer.Map() == nullptr) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: unable to create resident buffer");
entry.buffer.Destroy();
entry.size = 0;
entry.usage = 0;
return false;
}
if (!entry.buffer.Upload(bufferData->data(), bufferSize, 0)) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: initial upload failed");
entry.buffer.Destroy();
entry.size = 0;
entry.usage = 0;
return false;
}
entry.size = bufferSize;
entry.usage = recreatedUsage;
bufferObject->ClearDirty();
outSlice = entry.buffer.GetSlice(0, bufferSize);
return true;
}
if (changeBits & BufferChangeBits::DirtyBit) {
const auto& dirtyRanges = bufferObject->GetDirtyRanges();
for (const auto& range : dirtyRanges) {
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(range.start);
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(range.end - range.start);
if (rangeSize == 0) {
continue;
}
if (!entry.buffer.Upload(bufferData->data() + range.start, rangeSize, rangeOffset)) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: dirty range upload failed");
return false;
}
}
bufferObject->ClearDirty();
}
outSlice = entry.buffer.GetSlice(0, bufferSize);
return true;
}
void VkBufferManager::DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
if (bufferObject == nullptr) {
return;
}
auto it = m_residentBuffers.find(bufferObject.get());
if (it == m_residentBuffers.end()) {
return;
}
DeferResidentRelease(std::move(it->second.buffer));
m_residentBuffers.erase(it);
}
void VkBufferManager::DeferResidentRelease(VkBufferObject&& buffer) {
if (!buffer.IsValid()) {
return;
}
if (m_deferredResidentReleases.empty()) {
buffer.Destroy();
return;
}
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResidentReleases.size(),
"VkBufferManager::DeferResidentRelease current frame index out of range");
m_deferredResidentReleases[m_currentFrameIndex].push_back(std::move(buffer));
}
void VkBufferManager::CollectDeferredResidentReleases(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(),
"VkBufferManager::CollectDeferredResidentReleases frame index out of range");
m_deferredResidentReleases[frameIndex].clear();
}
VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) {
switch (kind) {
case BufferKind::Vertex:
case BufferKind::Index:
// A GL buffer can be rebound between ARRAY_BUFFER and ELEMENT_ARRAY_BUFFER,
// and may even be used as both within the same draw setup. Keep resident
// vertex/index buffers compatible with both roles from the start so we
// never need to recreate a buffer after it has already been bound.
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
case BufferKind::Uniform:
default:
return 0;
}
}
void VkBufferManager::CollectResidentGarbageIfNeeded() {
++m_residentGcTick;
if (m_residentGcTick < kResidentBufferGCInterval) {
return;
}
CollectResidentGarbageNow();
m_residentGcTick = 0;
}
void VkBufferManager::CollectResidentGarbageNow() {
Vector<MG_State::GLState::BufferObject*> staleBuffers;
staleBuffers.reserve(m_residentBuffers.size());
for (const auto& [rawBuffer, entry] : m_residentBuffers) {
if (entry.aliveRef.expired()) {
staleBuffers.push_back(rawBuffer);
}
}
for (const auto* rawBuffer : staleBuffers) {
auto it = m_residentBuffers.find(const_cast<MG_State::GLState::BufferObject*>(rawBuffer));
if (it == m_residentBuffers.end()) {
continue;
}
DeferResidentRelease(std::move(it->second.buffer));
m_residentBuffers.erase(it);
}
}
void VkBufferManager::DestroyDeferredResidentReleases() {
for (auto& deferredReleases : m_deferredResidentReleases) {
deferredReleases.clear();
}
m_deferredResidentReleases.clear();
}
void VkBufferManager::DestroyResidentBuffers() {
for (auto& [_, entry] : m_residentBuffers) {
entry.buffer.Destroy();
}
m_residentBuffers.clear();
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,72 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include "BufferArena.h"
#include "MG_State/GLState/BufferState/BufferObject.h"
#include "../VkIncludes.h"
#include <Includes.h>
#include <vk_mem_alloc.h>
namespace MobileGL::MG_Backend::DirectVulkan {
enum class BufferKind : Uint8 {
Vertex,
Index,
Uniform,
};
struct VkBufferManagerInitInfo {
VmaAllocator allocator = nullptr;
Uint32 frameCount = 0;
VkDeviceSize minUploadBytes = 4 * 1024 * 1024;
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
Bool transientPersistentMapping = false;
};
class VkBufferManager {
public:
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
void Shutdown();
// Recreate all per-frame transient arenas
Bool RecreateTransientArenas(Uint32 frameCount);
void BeginFrame(Uint32 frameIndex);
Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
VkDeviceSize alignment, BufferSlice& outSlice);
Bool SyncResidentBuffer(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice);
void DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
private:
struct ResidentBufferEntry {
WeakPtr<MG_State::GLState::BufferObject> aliveRef;
VkBufferObject buffer;
VkDeviceSize size = 0;
VkBufferUsageFlags usage = 0;
};
Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
void DeferResidentRelease(VkBufferObject&& buffer);
void CollectDeferredResidentReleases(Uint32 frameIndex);
void CollectResidentGarbageIfNeeded();
void CollectResidentGarbageNow();
void DestroyDeferredResidentReleases();
void DestroyResidentBuffers();
VkBufferManagerInitInfo m_initInfo{};
BufferArena m_transientUploadArena;
UnorderedMap<MG_State::GLState::BufferObject*, ResidentBufferEntry> m_residentBuffers;
Vector<Vector<VkBufferObject>> m_deferredResidentReleases;
Uint32 m_currentFrameIndex = 0;
Uint32 m_residentGcTick = 0;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -13,11 +13,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_allocator = other.m_allocator; m_allocator = other.m_allocator;
m_buffer = other.m_buffer; m_buffer = other.m_buffer;
m_allocation = other.m_allocation; m_allocation = other.m_allocation;
m_mappedData = other.m_mappedData;
m_size = other.m_size; m_size = other.m_size;
other.m_allocator = nullptr; other.m_allocator = nullptr;
other.m_buffer = VK_NULL_HANDLE; other.m_buffer = VK_NULL_HANDLE;
other.m_allocation = nullptr; other.m_allocation = nullptr;
other.m_mappedData = nullptr;
other.m_size = 0; other.m_size = 0;
} }
@@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_allocator = other.m_allocator; m_allocator = other.m_allocator;
m_buffer = other.m_buffer; m_buffer = other.m_buffer;
m_allocation = other.m_allocation; m_allocation = other.m_allocation;
m_mappedData = other.m_mappedData;
m_size = other.m_size; m_size = other.m_size;
other.m_allocator = nullptr; other.m_allocator = nullptr;
other.m_buffer = VK_NULL_HANDLE; other.m_buffer = VK_NULL_HANDLE;
other.m_allocation = nullptr; other.m_allocation = nullptr;
other.m_mappedData = nullptr;
other.m_size = 0; other.m_size = 0;
return *this; return *this;
} }
@@ -44,6 +48,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Destroy(); Destroy();
} }
Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) {
return Create(desc.allocator, desc.size, desc.usage, desc.memoryUsage, desc.allocationFlags);
}
Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) { VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) {
MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator"); MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator");
@@ -78,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
void VkBufferObject::Destroy() { void VkBufferObject::Destroy() {
Unmap();
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) { if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation); vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
} }
@@ -87,6 +96,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_size = 0; m_size = 0;
} }
void* VkBufferObject::Map() {
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Map called on invalid buffer");
if (m_mappedData != nullptr) {
return m_mappedData;
}
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &m_mappedData);
if (mapResult != VK_SUCCESS || m_mappedData == nullptr) {
MGLOG_E("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult);
m_mappedData = nullptr;
return nullptr;
}
return m_mappedData;
}
void VkBufferObject::Unmap() {
if (!IsValid() || m_mappedData == nullptr) {
m_mappedData = nullptr;
return;
}
vmaUnmapMemory(m_allocator, m_allocation);
m_mappedData = nullptr;
}
Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) { Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) {
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer"); MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer");
MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null"); MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null");
@@ -96,15 +132,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true; return true;
} }
void* mapped = nullptr; const Bool wasMapped = IsMapped();
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped); void* mapped = wasMapped ? m_mappedData : Map();
if (mapResult != VK_SUCCESS || mapped == nullptr) { if (mapped == nullptr) {
MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult); MGLOG_E("VkBufferObject::Upload failed: unable to map buffer");
return false; return false;
} }
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size)); Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
vmaUnmapMemory(m_allocator, m_allocation); if (!wasMapped) {
Unmap();
}
return true; return true;
} }
BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const {
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range");
const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size;
MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::GetSlice range out of bounds");
BufferSlice slice{};
slice.buffer = m_buffer;
slice.offset = offset;
slice.size = resolvedSize;
slice.mapped = (m_mappedData != nullptr) ? static_cast<Uint8*>(m_mappedData) + offset : nullptr;
return slice;
}
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -8,11 +8,20 @@
#pragma once #pragma once
#include "BufferSlice.h"
#include "../VkIncludes.h" #include "../VkIncludes.h"
#include <Includes.h> #include <Includes.h>
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
struct VkBufferObjectDesc {
VmaAllocator allocator = nullptr;
VkDeviceSize size = 0;
VkBufferUsageFlags usage = 0;
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
VmaAllocationCreateFlags allocationFlags = 0;
};
class VkBufferObject { class VkBufferObject {
public: public:
VkBufferObject() = default; VkBufferObject() = default;
@@ -23,20 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferObject(VkBufferObject&& other) noexcept; VkBufferObject(VkBufferObject&& other) noexcept;
VkBufferObject& operator=(VkBufferObject&& other) noexcept; VkBufferObject& operator=(VkBufferObject&& other) noexcept;
Bool Create(const VkBufferObjectDesc& desc);
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0); VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0);
void Destroy(); void Destroy();
void* Map();
void Unmap();
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0); Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
VkBuffer GetHandle() const { return m_buffer; } VkBuffer GetHandle() const { return m_buffer; }
VkDeviceSize GetSize() const { return m_size; } VkDeviceSize GetSize() const { return m_size; }
BufferSlice GetSlice(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) const;
void* GetMappedData() const { return m_mappedData; }
Bool IsMapped() const { return m_mappedData != nullptr; }
Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; } Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; }
private: private:
VmaAllocator m_allocator = nullptr; VmaAllocator m_allocator = nullptr;
VkBuffer m_buffer = VK_NULL_HANDLE; VkBuffer m_buffer = VK_NULL_HANDLE;
VmaAllocation m_allocation = nullptr; VmaAllocation m_allocation = nullptr;
void* m_mappedData = nullptr;
VkDeviceSize m_size = 0; VkDeviceSize m_size = 0;
}; };
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -8,6 +8,9 @@
#include "VkClearManager.h" #include "VkClearManager.h"
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkClearManager::Initialize() { Bool VkClearManager::Initialize() {
return true; return true;
@@ -31,6 +34,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.color = clearPayload.color, .color = clearPayload.color,
.attachmentType = drawbuf .attachmentType = drawbuf
}, drawFbo.GetAttachment(drawbuf).GetTexture()); }, drawFbo.GetAttachment(drawbuf).GetTexture());
MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__,
MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(),
drawFbo.GetAttachment(drawbuf).GetTexture()->GetExternalIndex(),
clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]);
} }
} }
@@ -40,6 +47,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.depth = clearPayload.depth, .depth = clearPayload.depth,
.attachmentType = FramebufferAttachmentType::Depth, .attachmentType = FramebufferAttachmentType::Depth,
}, drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture()); }, drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture());
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture()->GetExternalIndex(), clearPayload.depth);
} }
if (mask & GL_STENCIL_BUFFER_BIT && if (mask & GL_STENCIL_BUFFER_BIT &&
@@ -48,6 +57,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.stencil = clearPayload.stencil, .stencil = clearPayload.stencil,
.attachmentType = FramebufferAttachmentType::Stencil, .attachmentType = FramebufferAttachmentType::Stencil,
}, drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture()); }, drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture());
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture()->GetExternalIndex(), clearPayload.stencil);
} }
} }
@@ -68,14 +79,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkClearManager::GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) { Bool VkClearManager::GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) {
if (m_aliveObjects.find(texture) == m_aliveObjects.end() || if (m_aliveObjects.find(texture) == m_aliveObjects.end() ||
m_pendingClears.find(texture) == m_pendingClears.end()) { m_pendingClears.find(texture) == m_pendingClears.end()) {
MGLOG_D("%s: Failed getting pending clear for texture %d", __func__, texture->GetExternalIndex());
return false; return false;
} }
outPayload = m_pendingClears[texture]; outPayload = m_pendingClears[texture];
MGLOG_D("%s: Got pending clear for texture %d (%s), clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
texture->GetExternalIndex(),
MG_Util::ConvertTextureInternalFormatToString(texture->GetFormat()).c_str(),
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
outPayload.depth,
outPayload.stencil);
return true; return true;
} }
void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) { void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) {
MGLOG_D("%s: Pop pending clear for texture %d", __func__, texture->GetExternalIndex());
m_aliveObjects.erase(texture); m_aliveObjects.erase(texture);
m_pendingClears.erase(texture); m_pendingClears.erase(texture);
} }
@@ -66,6 +66,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
else if (att.IsRenderbuffer()) else if (att.IsRenderbuffer())
contentPtr = att.GetRenderbuffer().get(); contentPtr = att.GetRenderbuffer().get();
XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr)));
if (att.IsTexture()) {
const Int textureLevel = att.GetTextureLevel();
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel)));
}
if (includePendingClear && att.IsTexture()) { if (includePendingClear && att.IsTexture()) {
auto* texture = att.GetTexture().get(); auto* texture = att.GetTexture().get();
@@ -113,10 +117,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
Uint32 swapchainImageIndex) { Uint32 swapchainImageIndex) {
auto hasPendingClearOnFramebuffer = [&]() -> Bool {
const auto& drawBuffers = fbo.GetDrawBuffers();
for (auto attachment : drawBuffers) {
if (attachment == FramebufferAttachmentType::None) {
continue;
}
const auto& att = fbo.GetAttachment(attachment);
if (att.IsTexture() && m_clearManager.HasPendingClear(att.GetTexture().get())) {
return true;
}
}
const auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth);
if (depthAtt.IsTexture() && m_clearManager.HasPendingClear(depthAtt.GetTexture().get())) {
return true;
}
const auto& stencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil);
if (stencilAtt.IsTexture() && m_clearManager.HasPendingClear(stencilAtt.GetTexture().get())) {
return true;
}
return false;
};
// retrieve from cache first // retrieve from cache first
auto* activeRenderPass = GetActiveRenderPass(); auto* activeRenderPass = GetActiveRenderPass();
auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false); auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false);
if (activeRenderPass != nullptr && activeRenderPass->CompatibleWith(compatibilityHash)) { if (activeRenderPass != nullptr &&
activeRenderPass->CompatibleWith(compatibilityHash) &&
!hasPendingClearOnFramebuffer()) {
auto activeIt = m_renderPasses.find(activeRenderPass->hash); auto activeIt = m_renderPasses.find(activeRenderPass->hash);
MOBILEGL_ASSERT(activeIt != m_renderPasses.end(), MOBILEGL_ASSERT(activeIt != m_renderPasses.end(),
"GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache", "GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache",
@@ -158,6 +190,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& att = fbo.GetAttachment(drawbuf); auto& att = fbo.GetAttachment(drawbuf);
auto* texture = att.GetTexture().get(); auto* texture = att.GetTexture().get();
const Uint32 attachmentMipLevel = static_cast<Uint32>(std::max(att.GetTextureLevel(), 0));
const auto textureTarget = texture->GetTarget(); const auto textureTarget = texture->GetTarget();
// Color attachment description // Color attachment description
@@ -190,9 +223,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}); });
} }
if (width == 0) if (width == 0)
width = texture2d->GetBaseSize().x(); width = att.GetSize().x();
if (height == 0) if (height == 0)
height = texture2d->GetBaseSize().y(); height = att.GetSize().y();
if (isDefaultFbo) { if (isDefaultFbo) {
const auto& swapchainViews = m_swapchainObject.GetImageViews(); const auto& swapchainViews = m_swapchainObject.GetImageViews();
@@ -215,7 +248,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.texture = texture, .texture = texture,
.finalLayout = desc.finalLayout, .finalLayout = desc.finalLayout,
}); });
attachmentViews[i] = textureResources[i]->view; attachmentViews[i] = m_textureManager.GetOrCreateViewAtMipLevel(*texture, attachmentMipLevel);
MOBILEGL_ASSERT(attachmentViews[i] != VK_NULL_HANDLE,
"GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i);
} }
if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
@@ -249,6 +284,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkTextureManager::TextureResource* depthTextureResource = nullptr; VkTextureManager::TextureResource* depthTextureResource = nullptr;
if (depthAtt.IsComplete() && depthAtt.IsTexture()) { if (depthAtt.IsComplete() && depthAtt.IsTexture()) {
auto& texture = *depthAtt.GetTexture(); auto& texture = *depthAtt.GetTexture();
const Uint32 attachmentMipLevel = static_cast<Uint32>(std::max(depthAtt.GetTextureLevel(), 0));
const Uint32 depthAttachmentIndex = static_cast<Uint32>(attachmentDescriptions.size()); const Uint32 depthAttachmentIndex = static_cast<Uint32>(attachmentDescriptions.size());
ClearAttachmentPayload clearPayload{}; ClearAttachmentPayload clearPayload{};
Bool hasClear = m_clearManager.GetPendingClear(&texture, clearPayload); Bool hasClear = m_clearManager.GetPendingClear(&texture, clearPayload);
@@ -311,11 +347,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.finalLayout = depthAttachmentDescription.finalLayout, .finalLayout = depthAttachmentDescription.finalLayout,
}); });
textureResources.emplace_back(depthTextureResource); textureResources.emplace_back(depthTextureResource);
attachmentViews.emplace_back(depthTextureResource->view); attachmentViews.emplace_back(m_textureManager.GetOrCreateViewAtMipLevel(texture, attachmentMipLevel));
MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE,
"GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment");
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
auto texture2d = static_cast<MG_State::GLState::TextureObject2D*>(&texture); width = depthAtt.GetSize().x();
width = texture2d->GetBaseSize().x(); height = depthAtt.GetSize().y();
height = texture2d->GetBaseSize().y();
} }
} }
attachmentDescriptions.emplace_back(depthAttachmentDescription); attachmentDescriptions.emplace_back(depthAttachmentDescription);
@@ -74,6 +74,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return &(it->second); return &(it->second);
} }
VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) {
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) {
return VK_NULL_HANDLE;
}
if (resource->perMipViews.size() != resource->mipLevels) {
resource->perMipViews.resize(resource->mipLevels, VK_NULL_HANDLE);
}
VkImageView& perMipView = resource->perMipViews[mipLevel];
if (perMipView != VK_NULL_HANDLE) {
return perMipView;
}
perMipView = CreateImageView(resource->image, resource->format, resource->aspect, mipLevel, 1);
if (perMipView == VK_NULL_HANDLE) {
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel);
return VK_NULL_HANDLE;
}
return perMipView;
}
void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) { void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) {
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null"); MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null");
auto it = m_textureResources.find(texture); auto it = m_textureResources.find(texture);
@@ -127,7 +151,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask,
kGraphicsSampledReadStages, srcAccessMask, kGraphicsSampledReadStages, srcAccessMask,
VK_ACCESS_SHADER_READ_BIT, resource->aspect); VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels);
MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex());
return ok; return ok;
} }
@@ -136,7 +160,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout& trackedLayout, VkImageLayout newLayout, VkImageLayout& trackedLayout, VkImageLayout newLayout,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask,
VkImageAspectFlags aspectMask) { VkImageAspectFlags aspectMask, Uint32 baseMipLevel, Uint32 levelCount) {
MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE"); MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE");
MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 && MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 &&
(dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0), (dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0),
@@ -158,8 +182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image; barrier.image = image;
barrier.subresourceRange.aspectMask = aspectMask; barrier.subresourceRange.aspectMask = aspectMask;
barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.baseMipLevel = baseMipLevel;
barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.levelCount = levelCount;
barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier);
@@ -205,6 +229,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_D("%s: SyncTextureResource failed", __func__); MGLOG_D("%s: SyncTextureResource failed", __func__);
return false; return false;
} }
if (!SyncTextureViews(texture, outResource)) {
MGLOG_D("%s: SyncTextureViews failed", __func__);
return false;
}
Bool hasDirtyMipLevel = false; Bool hasDirtyMipLevel = false;
for (Uint32 level = 0; level < mipLevelCount; ++level) { for (Uint32 level = 0; level < mipLevelCount; ++level) {
@@ -251,6 +279,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.extent.height == static_cast<Uint32>(texelSize.y()) && resource.extent.height == static_cast<Uint32>(texelSize.y()) &&
resource.mipLevels == mipLevels; resource.mipLevels == mipLevels;
if (compatible) { if (compatible) {
if (resource.perMipViews.size() != mipLevels) {
resource.perMipViews.resize(mipLevels, VK_NULL_HANDLE);
}
return true; return true;
} }
@@ -283,26 +314,68 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr),
"vmaCreateImage(texture)"); "vmaCreateImage(texture)");
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = resource.image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = aspect;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = mipLevels;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view), "vkCreateImageView(texture)");
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
resource.extent = {static_cast<Uint32>(texelSize.x()), static_cast<Uint32>(texelSize.y())}; resource.extent = {static_cast<Uint32>(texelSize.x()), static_cast<Uint32>(texelSize.y())};
resource.mipLevels = mipLevels; resource.mipLevels = mipLevels;
resource.perMipViews.assign(mipLevels, VK_NULL_HANDLE);
resource.sampledBaseMipLevel = 0;
resource.sampledLevelCount = mipLevels;
resource.format = format; resource.format = format;
resource.aspect = viewInfo.subresourceRange.aspectMask; resource.aspect = aspect;
resource.syncedTextureParamsVersion = 0;
return true; return true;
} }
Bool VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource) {
MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "SyncTextureViews: image == VK_NULL_HANDLE");
Uint32 baseMipLevel = 0;
Uint32 levelCount = 1;
ResolveViewMipRange(texture, resource.mipLevels, baseMipLevel, levelCount);
const Bool needsRecreate =
resource.fullView == VK_NULL_HANDLE ||
resource.sampledBaseMipLevel != baseMipLevel ||
resource.sampledLevelCount != levelCount ||
resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion();
if (!needsRecreate) {
return true;
}
if (resource.fullView != VK_NULL_HANDLE) {
vkDestroyImageView(m_device, resource.fullView, nullptr);
resource.fullView = VK_NULL_HANDLE;
}
resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, baseMipLevel, levelCount);
if (resource.fullView == VK_NULL_HANDLE) {
return false;
}
resource.sampledBaseMipLevel = baseMipLevel;
resource.sampledLevelCount = levelCount;
resource.syncedTextureParamsVersion = texture.GetTextureParamsVersion();
return true;
}
VkImageView VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect,
Uint32 baseMipLevel, Uint32 levelCount) const {
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = aspect;
viewInfo.subresourceRange.baseMipLevel = baseMipLevel;
viewInfo.subresourceRange.levelCount = levelCount;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
VkImageView view = VK_NULL_HANDLE;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)");
return view;
}
Bool VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, Bool VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
TextureUploadTarget uploadTarget, TextureUploadTarget uploadTarget,
TextureResource &outResource) { TextureResource &outResource) {
@@ -390,7 +463,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
aspectMask); aspectMask, 0, outResource.mipLevels);
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed");
for (const auto& item : uploadItems) { for (const auto& item : uploadItems) {
@@ -415,7 +488,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
kGraphicsSampledReadStages, kGraphicsSampledReadStages,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_READ_BIT,
aspectMask); aspectMask, 0, outResource.mipLevels);
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed"); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed");
outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
@@ -472,15 +545,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
const auto level0TexelSize = mipTexture->GetMipmapTexelSize(target, 0); // Backing VkImage allocation still uses storage mip 0 as the physical image extent.
const auto level0ByteSize = mipTexture->GetMipmapByteSize(target, 0); // GL_TEXTURE_BASE_LEVEL / MAX_LEVEL are applied later when building the sampled view.
if (level0TexelSize.x() <= 0 || level0TexelSize.y() <= 0 /*|| level0ByteSize == 0*/) { const auto storageBaseTexelSize = mipTexture->GetMipmapTexelSize(target, 0);
const auto storageBaseByteSize = mipTexture->GetMipmapByteSize(target, 0);
if (storageBaseTexelSize.x() <= 0 || storageBaseTexelSize.y() <= 0 /*|| storageBaseByteSize == 0*/) {
continue; continue;
} }
outTarget = target; outTarget = target;
outTexelSize = level0TexelSize; outTexelSize = storageBaseTexelSize;
outByteSize = level0ByteSize; outByteSize = storageBaseByteSize;
outMipLevelCount = mipLevelCount; outMipLevelCount = mipLevelCount;
return true; return true;
} }
@@ -507,6 +582,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return validLevelCount; return validLevelCount;
} }
void VkTextureManager::ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels,
Uint32& outBaseMipLevel, Uint32& outLevelCount) {
MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0");
const auto& levelRange = texture.GetLevelRange();
const Uint32 maxAvailableMipLevel = mipLevels - 1;
const Uint32 requestedBaseMipLevel = std::min(static_cast<Uint32>(levelRange.x()), maxAvailableMipLevel);
Uint32 requestedMaxMipLevel = std::min(static_cast<Uint32>(levelRange.y()), maxAvailableMipLevel);
if (requestedMaxMipLevel < requestedBaseMipLevel) {
requestedMaxMipLevel = requestedBaseMipLevel;
}
outBaseMipLevel = requestedBaseMipLevel;
outLevelCount = requestedMaxMipLevel - requestedBaseMipLevel + 1;
}
VkImageAspectFlags VkTextureManager::GetAspectMaskForFormat(VkFormat format) { VkImageAspectFlags VkTextureManager::GetAspectMaskForFormat(VkFormat format) {
switch (format) { switch (format) {
case VK_FORMAT_D16_UNORM: case VK_FORMAT_D16_UNORM:
@@ -31,41 +31,58 @@ public:
struct TextureResource { struct TextureResource {
VkImage image = VK_NULL_HANDLE; VkImage image = VK_NULL_HANDLE;
VmaAllocation allocation = nullptr; VmaAllocation allocation = nullptr;
VkImageView view = VK_NULL_HANDLE; VkImageView fullView = VK_NULL_HANDLE;
Vector<VkImageView> perMipViews;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkExtent2D extent = {0, 0}; VkExtent2D extent = {0, 0};
Uint32 mipLevels = 1; Uint32 mipLevels = 1;
Uint32 sampledBaseMipLevel = 0;
Uint32 sampledLevelCount = 1;
VkFormat format = VK_FORMAT_UNDEFINED; VkFormat format = VK_FORMAT_UNDEFINED;
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
Uint16 syncedTextureParamsVersion = 0;
TextureResource() = default; TextureResource() = default;
TextureResource(const TextureResource&) = delete; TextureResource(const TextureResource&) = delete;
TextureResource(TextureResource&& that) noexcept { TextureResource(TextureResource&& that) noexcept {
std::swap(this->image, that.image); std::swap(this->image, that.image);
std::swap(this->allocation, that.allocation); std::swap(this->allocation, that.allocation);
std::swap(this->view, that.view); std::swap(this->fullView, that.fullView);
std::swap(this->perMipViews, that.perMipViews);
std::swap(this->layout, that.layout); std::swap(this->layout, that.layout);
std::swap(this->extent, that.extent); std::swap(this->extent, that.extent);
std::swap(this->mipLevels, that.mipLevels); std::swap(this->mipLevels, that.mipLevels);
std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel);
std::swap(this->sampledLevelCount, that.sampledLevelCount);
std::swap(this->format, that.format); std::swap(this->format, that.format);
std::swap(this->aspect, that.aspect); std::swap(this->aspect, that.aspect);
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
} }
void Reset() { void Reset() {
if (view != VK_NULL_HANDLE) { if (fullView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, view, nullptr); vkDestroyImageView(s_device, fullView, nullptr);
}
for (const auto attachmentView : perMipViews) {
if (attachmentView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, attachmentView, nullptr);
}
} }
if (image != VK_NULL_HANDLE && allocation != nullptr) { if (image != VK_NULL_HANDLE && allocation != nullptr) {
vmaDestroyImage(s_allocator, image, allocation); vmaDestroyImage(s_allocator, image, allocation);
} }
view = VK_NULL_HANDLE; fullView = VK_NULL_HANDLE;
perMipViews.clear();
image = VK_NULL_HANDLE; image = VK_NULL_HANDLE;
allocation = nullptr; allocation = nullptr;
layout = VK_IMAGE_LAYOUT_UNDEFINED; layout = VK_IMAGE_LAYOUT_UNDEFINED;
extent = {0, 0}; extent = {0, 0};
mipLevels = 1; mipLevels = 1;
sampledBaseMipLevel = 0;
sampledLevelCount = 1;
format = VK_FORMAT_UNDEFINED; format = VK_FORMAT_UNDEFINED;
aspect = VK_IMAGE_ASPECT_NONE; aspect = VK_IMAGE_ASPECT_NONE;
syncedTextureParamsVersion = 0;
} }
~TextureResource() { ~TextureResource() {
@@ -81,13 +98,15 @@ public:
TextureResource* SyncTextureAndGetDescriptor( TextureResource* SyncTextureAndGetDescriptor(
MG_State::GLState::ITextureObject& texture); MG_State::GLState::ITextureObject& texture);
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout); void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout);
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask); VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask,
Uint32 baseMipLevel = 0, Uint32 levelCount = 1);
SizeT CollectGarbage(); SizeT CollectGarbage();
private: private:
@@ -98,6 +117,9 @@ private:
TextureUploadTarget uploadTarget, TextureUploadTarget uploadTarget,
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
TextureResource &resource); TextureResource &resource);
Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource);
VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect,
Uint32 baseMipLevel, Uint32 levelCount) const;
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
TextureUploadTarget uploadTarget, TextureUploadTarget uploadTarget,
TextureResource &outResource); TextureResource &outResource);
@@ -107,6 +129,8 @@ private:
SizeT& outByteSize, SizeT& outByteSize,
Uint32& outMipLevelCount); Uint32& outMipLevelCount);
static Uint32 GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target); static Uint32 GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target);
static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels,
Uint32& outBaseMipLevel, Uint32& outLevelCount);
static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format); static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format);
VkDevice m_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE;
@@ -19,6 +19,29 @@
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
static Bool ShouldUseTransientVertexIndexBuffer(const MG_State::GLState::BufferObject& bufferObject) {
switch (bufferObject.GetUsage()) {
case BufferUsage::StreamDraw:
case BufferUsage::StreamRead:
case BufferUsage::StreamCopy:
case BufferUsage::DynamicDraw:
case BufferUsage::DynamicRead:
case BufferUsage::DynamicCopy:
return true;
case BufferUsage::StaticDraw:
case BufferUsage::StaticRead:
case BufferUsage::StaticCopy:
default:
return false;
}
}
static Bool HasTransientVertexIndexBufferThisFrame(
const Vector<const MG_State::GLState::BufferObject*>& buffers,
const MG_State::GLState::BufferObject* buffer) {
return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end();
}
static const char* VkImageLayoutToString(VkImageLayout layout) { static const char* VkImageLayoutToString(VkImageLayout layout) {
switch (layout) { switch (layout) {
case VK_IMAGE_LAYOUT_UNDEFINED: case VK_IMAGE_LAYOUT_UNDEFINED:
@@ -75,6 +98,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
namespace { namespace {
static constexpr Uint32 kMaxProgramBindings = 16;
static constexpr Uint32 kDescriptorSetsPerFrame = 64;
static constexpr Uint kHiddenBlitProgramId = 0xFFFFFFF0u; static constexpr Uint kHiddenBlitProgramId = 0xFFFFFFF0u;
static constexpr Uint kHiddenBlitVertexShaderId = 0xFFFFFFF1u; static constexpr Uint kHiddenBlitVertexShaderId = 0xFFFFFFF1u;
static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u; static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u;
@@ -93,6 +118,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout* trackedLayout = nullptr; VkImageLayout* trackedLayout = nullptr;
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_NONE; VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_NONE;
IntVec2 extent = {0, 0}; IntVec2 extent = {0, 0};
Uint32 mipLevel = 0;
Uint32 mipLevelCount = 1;
const char* label = nullptr; const char* label = nullptr;
}; };
@@ -164,6 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
const auto extent = swapchainObject.GetExtent(); const auto extent = swapchainObject.GetExtent();
outBinding.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)}; outBinding.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)};
outBinding.mipLevel = 0;
outBinding.mipLevelCount = 1;
return true; return true;
} }
@@ -182,7 +211,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outBinding.image = resource->image; outBinding.image = resource->image;
outBinding.trackedLayout = &resource->layout; outBinding.trackedLayout = &resource->layout;
outBinding.aspectMask = resource->aspect; outBinding.aspectMask = resource->aspect;
outBinding.extent = {static_cast<Int>(resource->extent.width), static_cast<Int>(resource->extent.height)}; const auto attachmentExtent = attachment.GetSize();
outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()};
outBinding.mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
outBinding.mipLevelCount = resource->mipLevels;
return true; return true;
} }
@@ -269,15 +301,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return flags; return flags;
} }
void VulkanRenderer::CreateFrameContexts() {
VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight),
"CreateFrameContexts");
VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(m_device,
static_cast<Uint32>(m_swapchainObject.GetImageCount())),
"CreateFrameContexts, InitializeSwapchainSemaphores");
MGLOG_I("CreateFrameContexts completed");
}
void VulkanRenderer::Initialize() { void VulkanRenderer::Initialize() {
CreateInstance(); CreateInstance();
CreateSurface(); CreateSurface();
@@ -286,9 +309,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
CreateAllocator(); CreateAllocator();
CreateCommandPool(); CreateCommandPool();
VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight),
"CreateFrameContexts");
MGLOG_I("CreateFrameContexts completed");
auto succeeded = false;
succeeded = m_bufferManager.Initialize({
.allocator = m_allocator,
.frameCount = m_frameContext.GetFrameCount(),
.minUploadBytes = 4 * 1024 * 1024,
.transientMemoryUsage = VMA_MEMORY_USAGE_AUTO,
.transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
.transientPersistentMapping = false,
});
MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed.");
m_textureManager = MakeUnique<VkTextureManager>(); m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed."); MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
auto succeeded = false;
succeeded = m_textureManager->Initialize( succeeded = m_textureManager->Initialize(
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue}); {m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue});
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed."); MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
@@ -306,7 +341,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_pipelineFactory = MakeUnique<PipelineFactory>(m_device, m_config); m_pipelineFactory = MakeUnique<PipelineFactory>(m_device, m_config);
MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed."); MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed.");
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config); m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config, kMaxProgramBindings);
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed."); MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
m_samplerManager = MakeUnique<VkSamplerManager>(); m_samplerManager = MakeUnique<VkSamplerManager>();
@@ -316,27 +351,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
succeeded = InitializeBlitResources(); succeeded = InitializeBlitResources();
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed."); MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>(); m_uniformManager = MakeUnique<UniformManager>();
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed."); MOBILEGL_ASSERT(m_uniformManager != nullptr, "UniformDescriptorBinder creation failed.");
succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator, succeeded = m_uniformManager->Initialize(
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, m_device, &m_bufferManager, m_programFactory.get(),
m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024, m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, m_config.MaxFramesInFlight,
m_textureManager.get(), m_samplerManager.get()); kMaxProgramBindings, kDescriptorSetsPerFrame, m_textureManager.get(), m_samplerManager.get());
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed."); MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config); m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);
MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed."); MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed.");
CreateFrameContexts();
m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
m_deferredBufferReleases.clear();
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
// Prime the first frame so Render() always targets an acquired swapchain image. // Prime the first frame so Render() always targets an acquired swapchain image.
VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired), VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired),
"Initialize, WaitAndAcquireNextImage"); "Initialize, WaitAndAcquireNextImage");
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_transientVertexIndexBuffersThisFrame.clear();
MGLOG_D("VulkanRenderer initialized"); MGLOG_D("VulkanRenderer initialized");
} }
@@ -345,7 +374,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(vkDeviceWaitIdle(m_device)); VK_VERIFY(vkDeviceWaitIdle(m_device));
m_pipelineFactory.reset(); m_pipelineFactory.reset();
m_programFactory.reset();
ShutdownBlitResources(); ShutdownBlitResources();
if (m_samplerManager) { if (m_samplerManager) {
m_samplerManager->Shutdown(); m_samplerManager->Shutdown();
@@ -356,24 +384,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_textureManager.reset(); m_textureManager.reset();
} }
m_vertexInputStateFactory.reset(); m_vertexInputStateFactory.reset();
for (auto& buffer : m_frameVertexUploadBuffers) { m_bufferManager.Shutdown();
buffer.Destroy(); m_transientVertexIndexBuffersThisFrame.clear();
}
for (auto& buffer : m_frameIndexUploadBuffers) {
buffer.Destroy();
}
m_frameVertexUploadBuffers.clear();
m_frameVertexUploadHeads.clear();
m_frameIndexUploadBuffers.clear();
m_frameIndexUploadHeads.clear();
m_deferredBufferReleases.clear();
m_frameContext.Destroy(m_device, m_commandPool); m_frameContext.Destroy(m_device, m_commandPool);
if (m_uniformDescriptorBinder) { if (m_uniformManager) {
m_uniformDescriptorBinder->Shutdown(); m_uniformManager->Shutdown();
m_uniformDescriptorBinder.reset(); m_uniformManager.reset();
} }
m_programFactory.reset();
ShutdownSwapchain(); ShutdownSwapchain();
m_renderPassManager.reset(); m_renderPassManager.reset();
@@ -392,7 +412,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkDestroyDevice(m_device, nullptr); vkDestroyDevice(m_device, nullptr);
m_device = VK_NULL_HANDLE; m_device = VK_NULL_HANDLE;
} }
m_cmdDrawIndexedIndirectCount = nullptr; s_vkCmdDrawIndexedIndirectCount = nullptr;
if (m_surface != VK_NULL_HANDLE) { if (m_surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(m_instance, m_surface, nullptr); vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
@@ -411,59 +431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("VulkanRenderer shut down completed"); MGLOG_I("VulkanRenderer shut down completed");
} }
void VulkanRenderer::DeferDestroyBuffer(VkBufferObject& buffer) { Bool VulkanRenderer::UploadAndBindVertexBuffers(
if (!buffer.IsValid()) {
return;
}
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
if (m_deferredBufferReleases.size() < m_frameContext.GetFrameCount()) {
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
}
m_deferredBufferReleases[frameIndex].push_back(std::move(buffer));
}
void VulkanRenderer::CollectDeferredBufferReleases(Uint32 frameIndex) {
if (frameIndex >= m_deferredBufferReleases.size()) {
return;
}
m_deferredBufferReleases[frameIndex].clear();
}
Bool VulkanRenderer::EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer,
VkDeviceSize requiredEndOffset, VkDeviceSize minCapacity,
VkBufferUsageFlags usage) {
auto& buffers = isIndexBuffer ? m_frameIndexUploadBuffers : m_frameVertexUploadBuffers;
auto& heads = isIndexBuffer ? m_frameIndexUploadHeads : m_frameVertexUploadHeads;
if (frameIndex >= buffers.size() || frameIndex >= heads.size()) {
return false;
}
auto& uploadBuffer = buffers[frameIndex];
if (uploadBuffer.IsValid() && uploadBuffer.GetSize() >= requiredEndOffset) {
return true;
}
VkDeviceSize newCapacity = uploadBuffer.IsValid() ? uploadBuffer.GetSize() : 0;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
while (newCapacity < requiredEndOffset) {
newCapacity *= 2;
}
DeferDestroyBuffer(uploadBuffer);
if (!uploadBuffer.Create(m_allocator, newCapacity, usage, VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT)) {
MGLOG_E("EnsureFrameUploadBufferCapacity failed: create upload buffer (index=%d, capacity=%zu)",
isIndexBuffer, static_cast<SizeT>(newCapacity));
return false;
}
heads[frameIndex] = 0;
return true;
}
Bool VulkanRenderer::UploadAndBindVertexStreams(
VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) {
auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
@@ -471,7 +439,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE); Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
Vector<VkDeviceSize> vkOffsets(bindingCount, 0); Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* { auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
const auto& attrs = vao.GetAllAttributes(); const auto& attrs = vao.GetAllAttributes();
@@ -491,32 +458,102 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (SizeT binding = 0; binding < bindingCount; ++binding) { for (SizeT binding = 0; binding < bindingCount; ++binding) {
const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding];
const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey);
MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer");
const auto sourceData = sourceBuffer->GetDataReadOnly(); auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex());
MOBILEGL_ASSERT(sourceBufferShared != nullptr,
const SizeT sourceSize = sourceBuffer->GetSize(); "UploadAndBindVertexStreams failed to resolve shared source buffer");
VkDeviceSize& frameHead = m_frameVertexUploadHeads[frameIndex]; BufferSlice slice{};
const VkDeviceSize writeOffset = (frameHead + 0x0F) & ~VkDeviceSize(0x0F); const Bool transientThisFrame =
const VkDeviceSize writeEnd = writeOffset + static_cast<VkDeviceSize>(sourceSize); HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, sourceBufferShared.get());
if (!EnsureFrameUploadBufferCapacity(frameIndex, false, writeEnd, 4 * 1024 * 1024, const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) { if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || transientThisFrame || isDirty) {
return false; const auto sourceData = sourceBufferShared->GetDataReadOnly();
const SizeT sourceSize = sourceBufferShared->GetSize();
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
sourceData->data(), static_cast<VkDeviceSize>(sourceSize), 16,
slice)) {
MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding);
return false;
}
if (!transientThisFrame) {
m_transientVertexIndexBuffersThisFrame.push_back(sourceBufferShared.get());
}
m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared);
sourceBufferShared->ClearDirty();
} else {
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Vertex, sourceBufferShared, slice)) {
MGLOG_E("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding);
return false;
}
} }
auto& frameUploadBuffer = m_frameVertexUploadBuffers[frameIndex]; vkBuffers[binding] = slice.buffer;
if (!frameUploadBuffer.Upload(sourceData->data(), static_cast<VkDeviceSize>(sourceSize), writeOffset)) { vkOffsets[binding] = slice.offset;
MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding);
return false;
}
frameHead = writeEnd;
vkBuffers[binding] = frameUploadBuffer.GetHandle();
vkOffsets[binding] = writeOffset;
} }
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data()); vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
return true; return true;
} }
Bool VulkanRenderer::UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
const MG_State::GLState::VertexArrayObject& vao,
const IndexBufferView* pIndexBufferView) {
VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM;
switch (pIndexBufferView->indexType) {
case GL_UNSIGNED_BYTE:
MOBILEGL_ASSERT(m_indexTypeUint8ExtensionEnabled,
"DrawElements with GL_UNSIGNED_BYTE requires VK_KHR_index_type_uint8 or VK_EXT_index_type_uint8");
vkIndexType = VK_INDEX_TYPE_UINT8;
break;
case GL_UNSIGNED_SHORT:
vkIndexType = VK_INDEX_TYPE_UINT16;
break;
case GL_UNSIGNED_INT:
vkIndexType = VK_INDEX_TYPE_UINT32;
break;
default:
MGLOG_D("DrawElements skipped: index type %u is not supported yet", pIndexBufferView->indexType);
return false;
}
const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get();
MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO");
const SizeT indexSize = MG_Util::GetGLTypeSize(pIndexBufferView->indexType);
const SizeT indexDataSizeBytes = pIndexBufferView->indexByteSize;
MOBILEGL_ASSERT(pIndexBufferView->indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(),
"DrawElements index range out of bounds");
BufferSlice slice{};
auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex());
MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO");
const Bool transientThisFrame =
HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, indexBufferShared.get());
const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared) || transientThisFrame || isDirty) {
const auto indexData = indexBufferShared->GetDataReadOnly();
MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data");
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
indexData->data() + pIndexBufferView->indexByteOffset,
static_cast<VkDeviceSize>(indexDataSizeBytes), indexSize, slice)) {
MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer");
return false;
}
if (!transientThisFrame) {
m_transientVertexIndexBuffersThisFrame.push_back(indexBufferShared.get());
}
m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared);
indexBufferShared->ClearDirty();
vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType);
return true;
}
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Index, indexBufferShared, slice)) {
MGLOG_E("DrawElements skipped: failed to sync resident index buffer");
return false;
}
vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer,
slice.offset + static_cast<VkDeviceSize>(pIndexBufferView->indexByteOffset), vkIndexType);
return true;
}
Bool VulkanRenderer::InitializeBlitResources() { Bool VulkanRenderer::InitializeBlitResources() {
ShutdownBlitResources(); ShutdownBlitResources();
@@ -624,17 +661,17 @@ void main() {
VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) { VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) {
MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null"); MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null");
MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null"); MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null");
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "GetOrCreateBlitPipeline: descriptor binder is null"); MOBILEGL_ASSERT(m_uniformManager != nullptr, "GetOrCreateBlitPipeline: descriptor binder is null");
static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState { static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
}; };
ProgramFactory::CompileOptionFlags transformFlags = 0; ProgramFactory::CompileOptionFlags transformFlags = 0;
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(*m_blitResources.program, transformFlags); const auto& programObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, transformFlags);
PipelineFactory::PipelineCreatePayload payload{ PipelineFactory::PipelineCreatePayload payload{
.programHash = m_programFactory->ComputeHash(*m_blitResources.program, transformFlags), .programHash = programObj.hash,
.vertexInputHash = 0, .vertexInputHash = 0,
.pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(*m_blitResources.program), .pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass, .renderPass = renderPassEntry.renderPass,
.subpass = 0, .subpass = 0,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
@@ -650,7 +687,7 @@ void main() {
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
.stages = &stages, .stages = &programObj.stages,
.vertexInputState = &kEmptyVertexInputState .vertexInputState = &kEmptyVertexInputState
}; };
return m_pipelineFactory->GetOrCreatePipeline(payload); return m_pipelineFactory->GetOrCreatePipeline(payload);
@@ -663,16 +700,14 @@ void main() {
const RenderPassEntry& renderPassEntry) { const RenderPassEntry& renderPassEntry) {
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip; Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(program, transformFlags); const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
if (stages.empty()) { if (programObj.stages.empty()) {
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages"); MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
return VK_NULL_HANDLE; return VK_NULL_HANDLE;
} }
const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags);
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao); auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
auto pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(program);
auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace); auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace);
auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest); auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest);
BlendFactor srcRGB = BlendFactor::One; BlendFactor srcRGB = BlendFactor::One;
@@ -683,9 +718,9 @@ void main() {
auto mask = MG_State::pGLContext->GetColorMask(); auto mask = MG_State::pGLContext->GetColorMask();
PipelineFactory::PipelineCreatePayload payload { PipelineFactory::PipelineCreatePayload payload {
.programHash = programHash, .programHash = programObj.hash,
.vertexInputHash = vertexInputHash, .vertexInputHash = vertexInputHash,
.pipelineLayout = pipelineLayout, .pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass, .renderPass = renderPassEntry.renderPass,
.subpass = 0, .subpass = 0,
.topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode), .topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode),
@@ -706,28 +741,36 @@ void main() {
(mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) | (mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
(mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) | (mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) |
(mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ), (mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ),
.stages = &stages, .stages = &programObj.stages,
.vertexInputState = &vis.state .vertexInputState = &vis.state
}; };
return m_pipelineFactory->GetOrCreatePipeline(payload); return m_pipelineFactory->GetOrCreatePipeline(payload);
} }
void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects) { Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const IndexBufferView* pIndexBufferView) {
m_textureManager->CollectGarbage(); m_textureManager->CollectGarbage();
const auto& drawFbo = const auto& drawFbo =
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
const auto& program = *MG_State::pGLContext->GetCurrentProgram(); const auto& program = *MG_State::pGLContext->GetCurrentProgram();
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
// Begin command recording if not yet // Begin command recording if not yet
if (!frame.isCommandRecording) { if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording(); m_frameContext.BeginCommandRecording();
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
} }
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
// Check if any of the textures to sample have pending clears,
// which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and
// without draws in between to give it a chance to materialize such clear.
// Deal with this situation here.
Vector<MG_State::GLState::ITextureObject*> sampledTextures; Vector<MG_State::GLState::ITextureObject*> sampledTextures;
Bool hasSampledTextures = m_uniformDescriptorBinder->CollectSampledTextures(program, sampledTextures); Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__); MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s", MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(), program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
@@ -800,7 +843,6 @@ void main() {
activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
// Begin render pass, and handle clear // Begin render pass, and handle clear
if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) { if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) {
ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry); ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry);
} else { } else {
@@ -814,10 +856,16 @@ void main() {
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program, m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
m_frameContext.GetCurrentFrameIndex()); m_frameContext.GetCurrentFrameIndex());
UploadAndBindVertexStreams(frame.commandBuffer, vao); auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao);
MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex buffers");
if (aspects & DrawSetupAspect::IndexBuffer) {
auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, pIndexBufferView);
MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer");
}
VkViewport viewport{}; VkViewport viewport{};
viewport.x = 0.0f; viewport.x = 0.0f;
@@ -839,6 +887,7 @@ void main() {
scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() }; scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() };
} }
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
return true;
} }
void VulkanRenderer::Clear(GLbitfield mask) { void VulkanRenderer::Clear(GLbitfield mask) {
@@ -875,7 +924,7 @@ void main() {
Bool ok = VkTextureManager::TransitionImageLayout( Bool ok = VkTextureManager::TransitionImageLayout(
commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
resource->aspect); resource->aspect, 0, resource->mipLevels);
MOBILEGL_ASSERT(ok, MOBILEGL_ASSERT(ok,
"MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST", "MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST",
texture.GetExternalIndex()); texture.GetExternalIndex());
@@ -908,7 +957,7 @@ void main() {
ok = VkTextureManager::TransitionImageLayout( ok = VkTextureManager::TransitionImageLayout(
commandBuffer, resource->image, resource->layout, sampledLayout, commandBuffer, resource->image, resource->layout, sampledLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect); VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels);
MOBILEGL_ASSERT(ok, MOBILEGL_ASSERT(ok,
"MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout", "MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout",
texture.GetExternalIndex()); texture.GetExternalIndex());
@@ -1031,14 +1080,16 @@ void main() {
writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform, writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform,
sizeof(blitUniformData.surfaceTransform)); sizeof(blitUniformData.surfaceTransform));
const auto samplerBindingOverride = UniformDescriptorBinder::SamplerBindingOverride{ const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{
.binding = m_blitResources.samplerBinding, .binding = m_blitResources.samplerBinding,
.texture = sourceTexture.get(), .texture = sourceTexture.get(),
.sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get() .sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get()
: m_blitResources.nearestSampler.get()), : m_blitResources.nearestSampler.get()),
}; };
const Bool bound = m_uniformDescriptorBinder->BindProgramUniformBuffers( ProgramFactory::CompileOptionFlags blitTransformFlags = 0;
frame.commandBuffer, *m_blitResources.program, m_frameContext.GetCurrentFrameIndex(), const auto& blitProgramObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, blitTransformFlags);
const Bool bound = m_uniformManager->BindProgramUniformBuffers(
frame.commandBuffer, *m_blitResources.program, blitProgramObj, m_frameContext.GetCurrentFrameIndex(),
&samplerBindingOverride); &samplerBindingOverride);
MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed"); MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed");
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
@@ -1069,7 +1120,7 @@ void main() {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) { if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording(); m_frameContext.BeginCommandRecording();
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
} }
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
@@ -1138,7 +1189,7 @@ void main() {
Bool ok = VkTextureManager::TransitionImageLayout( Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask); srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, 0, srcBinding.mipLevelCount);
MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__); MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__);
} }
@@ -1156,19 +1207,19 @@ void main() {
Bool ok = VkTextureManager::TransitionImageLayout( Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask); dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, 0, dstBinding.mipLevelCount);
MOBILEGL_ASSERT(ok, "%s: failed to transition destination image", __func__); MOBILEGL_ASSERT(ok, "%s: failed to transition destination image", __func__);
} }
VkImageBlit blitRegion{}; VkImageBlit blitRegion{};
blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask; blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
blitRegion.srcSubresource.mipLevel = 0; blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel;
blitRegion.srcSubresource.baseArrayLayer = 0; blitRegion.srcSubresource.baseArrayLayer = 0;
blitRegion.srcSubresource.layerCount = 1; blitRegion.srcSubresource.layerCount = 1;
blitRegion.srcOffsets[0] = {srcX0, srcY0, 0}; blitRegion.srcOffsets[0] = {srcX0, srcY0, 0};
blitRegion.srcOffsets[1] = {srcX1, srcY1, 1}; blitRegion.srcOffsets[1] = {srcX1, srcY1, 1};
blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask; blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask;
blitRegion.dstSubresource.mipLevel = 0; blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel;
blitRegion.dstSubresource.baseArrayLayer = 0; blitRegion.dstSubresource.baseArrayLayer = 0;
blitRegion.dstSubresource.layerCount = 1; blitRegion.dstSubresource.layerCount = 1;
blitRegion.dstOffsets[0] = {dstX0, dstY0, 0}; blitRegion.dstOffsets[0] = {dstX0, dstY0, 0};
@@ -1180,7 +1231,7 @@ void main() {
1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); 1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
} }
void VulkanRenderer::DrawArrays(const DrawArrayCmd& payload) { void VulkanRenderer::DrawArrays(const DrawCmd& payload) {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
SetupDraw(frame, payload.mode, 0); SetupDraw(frame, payload.mode, 0);
@@ -1189,68 +1240,49 @@ void main() {
VkCommandBuffer& commandBuffer = frame.commandBuffer; VkCommandBuffer& commandBuffer = frame.commandBuffer;
vkCmdDraw(commandBuffer, static_cast<Uint32>(payload.count), 1, static_cast<Uint32>(payload.first), 0); vkCmdDraw(commandBuffer,
payload.params.vertexCount,
payload.params.instanceCount,
payload.params.firstVertex,
payload.params.firstInstance);
} }
void VulkanRenderer::DrawElements(const DrawElementCmd& payload) { void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
SetupDraw(frame, payload.mode, 0); SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer,
&payload.indexBufferView);
if (!frame.isCommandRecording) { MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
MGLOG_D("DrawElements skipped: frame recording was not started");
return;
}
VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM;
switch (payload.indexType) {
case GL_UNSIGNED_SHORT:
vkIndexType = VK_INDEX_TYPE_UINT16;
break;
case GL_UNSIGNED_INT:
vkIndexType = VK_INDEX_TYPE_UINT32;
break;
default:
MGLOG_D("DrawElements skipped: index type %u is not supported yet", payload.indexType);
return;
}
auto* vao = MG_State::pGLContext->GetBoundVertexArray().get();
const auto* indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject().get();
const auto indexData = indexBuffer->GetDataReadOnly();
MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data");
const SizeT indexSize = (payload.indexType == GL_UNSIGNED_SHORT) ? sizeof(Uint16) : sizeof(Uint32);
const SizeT indexDataSizeBytes = static_cast<SizeT>(payload.count) * indexSize;
MOBILEGL_ASSERT(payload.indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(),
"DrawElements index range out of bounds");
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex];
const VkDeviceSize alignment = static_cast<VkDeviceSize>(indexSize);
const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1);
const VkDeviceSize writeEnd = writeOffset + static_cast<VkDeviceSize>(indexDataSizeBytes);
if (!EnsureFrameUploadBufferCapacity(frameIndex, true, writeEnd, 1 * 1024 * 1024,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) {
MGLOG_E("DrawElements skipped: failed to prepare index upload buffer");
return;
}
auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex];
if (!frameIndexUploadBuffer.Upload(indexData->data() + payload.indexByteOffset,
static_cast<VkDeviceSize>(indexDataSizeBytes), writeOffset)) {
MGLOG_E("DrawElements skipped: failed to upload index data");
return;
}
frameIndexHead = writeEnd;
VkCommandBuffer& commandBuffer = frame.commandBuffer; VkCommandBuffer& commandBuffer = frame.commandBuffer;
vkCmdBindIndexBuffer(commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType); vkCmdDrawIndexed(commandBuffer,
vkCmdDrawIndexed(commandBuffer, static_cast<Uint32>(payload.count), 1, 0, payload.params.indexCount,
static_cast<Int32>(payload.baseVertex), 0); payload.params.instanceCount,
payload.params.firstIndex,
payload.params.vertexOffset,
payload.params.firstInstance);
} }
void VulkanRenderer::MultiDrawElements(const Vector<DrawElementCmd>& payloads) { void VulkanRenderer::MultiDrawElements(const MultiDrawIndexedCmd& payload) {
auto& frame = m_frameContext.GetCurrent();
SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer,
&payload.indexBufferView);
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
VkCommandBuffer& commandBuffer = frame.commandBuffer;
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
vkCmdDrawIndexed(commandBuffer,
payload.pParams[idraw].indexCount,
payload.pParams[idraw].instanceCount,
payload.pParams[idraw].firstIndex,
payload.pParams[idraw].vertexOffset,
payload.pParams[idraw].firstInstance);
}
} }
void VulkanRenderer::Present() { void VulkanRenderer::Present() {
@@ -1298,13 +1330,8 @@ void main() {
result = VK_SUCCESS; result = VK_SUCCESS;
} }
VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); VK_VERIFY(result, "Present, vkAcquireNextImageKHR");
CollectDeferredBufferReleases(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
if (m_frameContext.GetCurrentFrameIndex() < m_frameVertexUploadHeads.size()) { m_transientVertexIndexBuffersThisFrame.clear();
m_frameVertexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
}
if (m_frameContext.GetCurrentFrameIndex() < m_frameIndexUploadHeads.size()) {
m_frameIndexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
}
} }
void VulkanRenderer::CreateInstance() { void VulkanRenderer::CreateInstance() {
@@ -1601,19 +1628,60 @@ void main() {
ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions); ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions);
MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false"); MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false");
m_indexTypeUint8ExtensionEnabled = false;
const char* indexTypeUint8ExtensionName = nullptr;
if (IsExtensionSupported(availableExtensions, VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME)) {
indexTypeUint8ExtensionName = VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME;
} else if (IsExtensionSupported(availableExtensions, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME)) {
indexTypeUint8ExtensionName = VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME;
}
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<PFN_vkGetPhysicalDeviceFeatures2>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2"));
if (getPhysicalDeviceFeatures2 == nullptr) {
getPhysicalDeviceFeatures2 = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR"));
}
MOBILEGL_ASSERT(getPhysicalDeviceFeatures2 != nullptr,
"CreateLogicalDeviceAndQueues: vkGetPhysicalDeviceFeatures2 is unavailable");
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
if (indexTypeUint8Features.indexTypeUint8 == VK_TRUE) {
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, indexTypeUint8ExtensionName)) {
enabledDeviceExtensions.push_back(indexTypeUint8ExtensionName);
}
m_indexTypeUint8ExtensionEnabled = true;
indexTypeUint8Features.pNext = const_cast<void*>(deviceCreateInfo.pNext);
deviceCreateInfo.pNext = &indexTypeUint8Features;
MGLOG_I("Enabled optional device extension: %s", indexTypeUint8ExtensionName);
} else {
MGLOG_W("%s is advertised, but indexTypeUint8 feature is unavailable; uint8 index buffers will stay disabled",
indexTypeUint8ExtensionName);
}
} else {
MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay disabled");
}
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size()); deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice"); VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
m_cmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>( s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR")); vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR"));
if (m_cmdDrawIndexedIndirectCount == nullptr) { if (s_vkCmdDrawIndexedIndirectCount == nullptr) {
m_cmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>( s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount")); vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount"));
} }
if (m_drawIndirectCountExtensionEnabled && m_cmdDrawIndexedIndirectCount == nullptr) { if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount == nullptr) {
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing"); MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will continue as if VK_KHR_draw_indirect_count is not supported!");
m_drawIndirectCountExtensionEnabled = false;
} }
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
MGLOG_I("Logical device created."); MGLOG_I("Logical device created.");
// Queues // Queues
@@ -1658,11 +1726,6 @@ void main() {
m_config.MaxFramesInFlight); m_config.MaxFramesInFlight);
} }
Uint64 VulkanRenderer::BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer) {
return (static_cast<Uint64>(targetsDefaultFramebuffer ? 1 : 0) << 63) |
static_cast<Uint64>(drawFboExternalIndex);
}
void VulkanRenderer::CreateCommandPool() { void VulkanRenderer::CreateCommandPool() {
VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
@@ -1844,12 +1907,12 @@ void main() {
m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().isCommandRecording = false;
m_frameContext.GetCurrent().hasCommandBufferRecorded = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false;
} }
m_deferredBufferReleases.clear(); const Bool okArena = m_bufferManager.RecreateTransientArenas(m_frameContext.GetFrameCount());
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); MOBILEGL_ASSERT(okArena, "RecreateSwapchain: buffer manager transient arena initialization failed");
m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount()); if (m_frameContext.GetFrameCount() > 0) {
m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount()); m_transientVertexIndexBuffersThisFrame.clear();
m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); }
} }
const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const { const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const {
@@ -12,9 +12,10 @@
#include "PipelineFactory.h" #include "PipelineFactory.h"
#include "ProgramFactory.h" #include "ProgramFactory.h"
#include "SwapchainObject.h" #include "SwapchainObject.h"
#include "UniformDescriptorBinder.h" #include "UniformManager.h"
#include "VertexInputStateFactory.h" #include "VertexInputStateFactory.h"
#include "VkBufferObject.h" #include "VkBufferObject.h"
#include "VkBufferManager.h"
#include "VkClearManager.h" #include "VkClearManager.h"
#include "VkRenderPassManager.h" #include "VkRenderPassManager.h"
#include "VkSamplerManager.h" #include "VkSamplerManager.h"
@@ -34,25 +35,55 @@ namespace MobileGL::MG_State::GLState {
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
enum class DrawSetupAspect: Uint8 { enum class DrawSetupAspect: Uint8 {
FramebufferObject = 1 << 0, FramebufferObject = 1 << 0,
VertexArrayObject = 1 << 1, VertexArrayObject = 1 << 1,
UniformBuffer = 1 << 2, UniformBuffer = 1 << 2,
VertexBuffer = 1 << 3, VertexBuffer = 1 << 3,
IndexBuffer = 1 << 4, IndexBuffer = 1 << 4,
Viewport = 1 << 5, IndirectDrawBuffer = 1 << 5,
Scissor = 1 << 6, Viewport = 1 << 6,
Scissor = 1 << 7,
}; };
struct DrawArrayCmd { struct DrawCmdParam {
Uint32 vertexCount = 0;
Uint32 instanceCount = 1;
Uint32 firstVertex = 0;
Uint32 firstInstance = 0;
};
struct DrawIndexedCmdParam {
Uint32 indexCount = 0;
Uint32 instanceCount = 1;
Uint32 firstIndex = 0;
Int32 vertexOffset = 0;
Int32 firstInstance = 0;
};
struct DrawCmd {
GLenum mode = GL_TRIANGLES; GLenum mode = GL_TRIANGLES;
GLint first = 0; DrawCmdParam params;
GLsizei count = 0;
}; };
struct DrawElementCmd: public DrawArrayCmd { struct IndexBufferView {
GLenum indexType = GL_UNSIGNED_SHORT; GLenum indexType = GL_UNSIGNED_SHORT;
SizeT indexByteOffset = 0; SizeT indexByteOffset = 0;
GLint baseVertex = 0; SizeT indexByteSize = 0;
};
struct DrawIndexedCmd {
GLenum mode = GL_TRIANGLES;
IndexBufferView indexBufferView;
DrawIndexedCmdParam params;
};
struct MultiDrawIndexedCmd {
GLenum mode = GL_TRIANGLES;
IndexBufferView indexBufferView;
Uint32 drawCount = 0;
DrawIndexedCmdParam* pParams = nullptr;
}; };
struct QueueFamilyIndices { struct QueueFamilyIndices {
@@ -78,7 +109,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
void SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects); Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const IndexBufferView* pIndexBufferView = nullptr);
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
const RenderPassEntry& compatibleRenderPassEntry); const RenderPassEntry& compatibleRenderPassEntry);
@@ -86,9 +118,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter); GLbitfield mask, GLenum filter);
void DrawArrays(const DrawArrayCmd& payload); void DrawArrays(const DrawCmd& payload);
void DrawElements(const DrawElementCmd& payload); void DrawElements(const DrawIndexedCmd& payload);
void MultiDrawElements(const Vector<DrawElementCmd>& payloads); void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
void Present(); void Present();
const PhysicalDevice& GetPhysicalDevice() const; const PhysicalDevice& GetPhysicalDevice() const;
@@ -131,26 +163,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkQueue m_graphicsQueue = VK_NULL_HANDLE; VkQueue m_graphicsQueue = VK_NULL_HANDLE;
VkQueue m_presentQueue = VK_NULL_HANDLE; VkQueue m_presentQueue = VK_NULL_HANDLE;
Bool m_drawIndirectCountExtensionEnabled = false; Bool m_drawIndirectCountExtensionEnabled = false;
Bool m_indexTypeUint8ExtensionEnabled = false;
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer, using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize offset, VkBuffer countBuffer,
VkDeviceSize countBufferOffset, Uint32 maxDrawCount, VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
Uint32 stride); Uint32 stride);
PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr; static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
VkCommandPool m_commandPool = VK_NULL_HANDLE; VkCommandPool m_commandPool = VK_NULL_HANDLE;
Vector<VkBufferObject> m_frameVertexUploadBuffers; VkBufferManager m_bufferManager;
Vector<VkDeviceSize> m_frameVertexUploadHeads; Vector<const MG_State::GLState::BufferObject*> m_transientVertexIndexBuffersThisFrame;
Vector<VkBufferObject> m_frameIndexUploadBuffers;
Vector<VkDeviceSize> m_frameIndexUploadHeads;
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
Uint m_imageIndexAcquired = 0; Uint m_imageIndexAcquired = 0;
FrameContext m_frameContext; FrameContext m_frameContext;
UniquePtr<PipelineFactory> m_pipelineFactory; UniquePtr<PipelineFactory> m_pipelineFactory;
UniquePtr<ProgramFactory> m_programFactory; UniquePtr<ProgramFactory> m_programFactory;
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder; UniquePtr<UniformManager> m_uniformManager;
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory; UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
UniquePtr<VkClearManager> m_clearManager; UniquePtr<VkClearManager> m_clearManager;
UniquePtr<VkRenderPassManager> m_renderPassManager; UniquePtr<VkRenderPassManager> m_renderPassManager;
@@ -169,7 +199,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DestroyAllocator(); void DestroyAllocator();
void CreateSwapchain(); void CreateSwapchain();
void CreateCommandPool(); void CreateCommandPool();
void CreateFrameContexts();
VkPipeline GetOrCreatePipeline( VkPipeline GetOrCreatePipeline(
GLenum mode, GLenum mode,
@@ -177,11 +206,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::VertexArrayObject& vao, const MG_State::GLState::VertexArrayObject& vao,
const RenderPassEntry& renderPassEntry); const RenderPassEntry& renderPassEntry);
void DeferDestroyBuffer(VkBufferObject& buffer); Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
void CollectDeferredBufferReleases(Uint32 frameIndex); Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset, const MG_State::GLState::VertexArrayObject& vao,
VkDeviceSize minCapacity, VkBufferUsageFlags usage); const IndexBufferView* pIndexBufferView = nullptr);
Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
Bool InitializeBlitResources(); Bool InitializeBlitResources();
void ShutdownBlitResources(); void ShutdownBlitResources();
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
@@ -216,8 +244,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface, static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
const PhysicalDevice& compareWithDevice, const PhysicalDevice& compareWithDevice,
PhysicalDevice& outBetterDevice); PhysicalDevice& outBetterDevice);
static Uint64 BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer);
static constexpr VkDynamicState s_dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"}; static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
static Bool CheckValidationLayerSupport(); static Bool CheckValidationLayerSupport();
@@ -13,8 +13,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct VulkanRendererConfig { struct VulkanRendererConfig {
Uint32 MaxFramesInFlight = 2; Uint32 MaxFramesInFlight = 2;
String AppName = "MobileGL-VulkanRenderer"; String AppName = "MobileGL-VulkanRenderer";
Version Version = MG_Config::CoreVersion; MobileGL::Version Version = MG_Config::CoreVersion;
Uint64 CacheVersion = MG_Config::CacheVersion; Uint64 CacheVersion = MG_Config::CacheVersion;
Bool EnableValidationLayers = true; Bool EnableValidationLayers = true;
}; };
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
+7 -12
View File
@@ -34,7 +34,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) { TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) {
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
auto& textureObject = auto& textureObject =
TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget); TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject; if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject;
return textureObject; return textureObject;
} else { } else {
@@ -554,9 +554,6 @@ namespace MobileGL::MG_Impl::GLImpl {
textureObject->SetInternalFormat(textureInternalFormat); textureObject->SetInternalFormat(textureInternalFormat);
// if isProxy, no more pixel transfer needed below
if (isProxy) return;
const void* originalPixels = pixels; const void* originalPixels = pixels;
// PBO // PBO
@@ -671,9 +668,6 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str()); MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str());
textureObject->SetInternalFormat(textureInternalFormat); textureObject->SetInternalFormat(textureInternalFormat);
// if isProxy, no more pixel transfer needed below
if (isProxy) return;
const void* originalPixels = pixels; const void* originalPixels = pixels;
// PBO // PBO
@@ -797,7 +791,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
@@ -875,7 +869,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
@@ -967,7 +961,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
@@ -1039,6 +1033,7 @@ namespace MobileGL::MG_Impl::GLImpl {
"pname is not a valid texture level parameter.")); "pname is not a valid texture level parameter."));
return; return;
} }
MGLOG_D("returned %u",*params);
} }
void GetTexLevelParameterfv_State(GLenum target, GLint level, GLenum pname, GLfloat* params) { void GetTexLevelParameterfv_State(GLenum target, GLint level, GLenum pname, GLfloat* params) {
@@ -1056,7 +1051,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
@@ -1384,7 +1379,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) { if (!TextureImpl::ValidateTextureObject(textureObject)) {
@@ -464,7 +464,7 @@ namespace MobileGL::MG_State::GLState {
MGLOG_D("ProgramObject %u: GenerateBinary - parsing SPIR-V meta data for module %zu " MGLOG_D("ProgramObject %u: GenerateBinary - parsing SPIR-V meta data for module %zu "
"(shaderType=%u, wordCount=%zu)", "(shaderType=%u, wordCount=%zu)",
m_externalIndex, i, shaderType, spv.size()); m_externalIndex, i, shaderType, spv.size());
SpvcSession session(spv); SpvcSession session(spv, SessionUsageBit::Reflection);
auto result = session.ParseMetaData(); auto result = session.ParseMetaData();
if (result < 0) { if (result < 0) {
MGLOG_D("ProgramObject %u: GenerateBinary - SpvcSession::ParseMetaData failed for module %zu, " MGLOG_D("ProgramObject %u: GenerateBinary - SpvcSession::ParseMetaData failed for module %zu, "
@@ -473,16 +473,16 @@ namespace MobileGL::MG_State::GLState {
(result == SPVC_ERROR_INVALID_SPIRV ? ". Probably no global UBO?" : "")); (result == SPVC_ERROR_INVALID_SPIRV ? ". Probably no global UBO?" : ""));
m_uniformSizesInBytes.clear(); m_uniformSizesInBytes.clear();
m_uniformOffsets.clear(); m_uniformOffsets.clear();
m_uboScratch.clear(); m_globalUboScratch.clear();
continue; continue;
} else { } else {
auto& meta = session.GetMetadata(); auto& meta = session.GetMetadata();
auto size = meta.uboSize; auto size = meta.globalUboSize;
MGLOG_D("ProgramObject %u: GenerateBinary - SPIR-V meta: uboSize=%zu plainUniformCount=%zu " MGLOG_D("ProgramObject %u: GenerateBinary - SPIR-V meta: uboSize=%zu plainUniformCount=%zu "
"plainUniformOffsets=%zu", "plainUniformOffsets=%zu",
m_externalIndex, meta.uboSize, meta.plainUniformMemberSizesInBytes.size(), m_externalIndex, meta.globalUboSize, meta.plainUniformMemberSizesInBytes.size(),
meta.plainUniformOffsetsInUBO.size()); meta.plainUniformOffsetsInUBO.size());
m_uboScratch.resize(size); m_globalUboScratch.resize(size);
m_uniformOffsets.resize(m_maxUniformLocation + 1); m_uniformOffsets.resize(m_maxUniformLocation + 1);
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) { for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
if (m_uniformLocations.find(name) != m_uniformLocations.end()) { if (m_uniformLocations.find(name) != m_uniformLocations.end()) {
@@ -65,9 +65,9 @@ namespace MobileGL::MG_State::GLState {
} }
GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; } GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; }
const String& GetAttribName(Uint index) const { return m_attribs[index]; } const String& GetAttribName(Uint index) const { return m_attribs[index]; }
void* MapUBO() { return m_uboScratch.data(); } void* MapUBO() { return m_globalUboScratch.data(); }
const void* GetUBOData() const { return m_uboScratch.data(); } const void* GetUBOData() const { return m_globalUboScratch.data(); }
Uint GetUBOSize() const { return static_cast<Uint>(m_uboScratch.size()); } Uint GetUBOSize() const { return static_cast<Uint>(m_globalUboScratch.size()); }
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) { void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
m_uniformSamplerOrImageUnitIndex[location] = unit; m_uniformSamplerOrImageUnitIndex[location] = unit;
@@ -121,9 +121,6 @@ namespace MobileGL::MG_State::GLState {
Uint GetExternalIndex() const { return m_externalIndex; } Uint GetExternalIndex() const { return m_externalIndex; }
// const UnorderedMap<String, Uint>& GetAttribLocationMap() const { return
// m_attribLocation; }
private: private:
void DoReflection(); void DoReflection();
void GenerateBinary(); void GenerateBinary();
@@ -142,8 +139,6 @@ namespace MobileGL::MG_State::GLState {
UnorderedMap<String, Uint> m_explicitAttribLocations; UnorderedMap<String, Uint> m_explicitAttribLocations;
Vector<String> m_attribs; Vector<String> m_attribs;
Vector<GLenum> m_attribTypes; Vector<GLenum> m_attribTypes;
// For SpvcSession::SetVertexAttribLocation()
// UnorderedMap<String, Uint> m_attribLocation;
// FragData (Frag out) // FragData (Frag out)
UnorderedMap<String, Uint> m_explicitFragDataLocation; UnorderedMap<String, Uint> m_explicitFragDataLocation;
@@ -162,13 +157,15 @@ namespace MobileGL::MG_State::GLState {
// Let's define UniformBlockIndex == the order at glslang getUniformBlock() // Let's define UniformBlockIndex == the order at glslang getUniformBlock()
// aka `i = glGetUniformBlockIndex(prog, "BlockName")` implies: // aka `i = glGetUniformBlockIndex(prog, "BlockName")` implies:
// `prog->getUniformBlock(i) == "BlockName"` // `prog->getUniformBlock(i) == "BlockName"`
// These stuff are present for GL semantics, not for backend inspection
// These may change after-link (because GL spec decided to have `glUniformBlockBinding`)
UnorderedMap<String, Uint> m_uniformBlockIndexByName; UnorderedMap<String, Uint> m_uniformBlockIndexByName;
Vector<Int> m_uniformBlockBinding; Vector<Int> m_uniformBlockBinding;
// Need to be reflected after linking of SPIR-V binary // Need to be reflected after linking of SPIR-V binary
Vector<Uint> m_uniformOffsets; Vector<Uint> m_uniformOffsets;
Vector<Uint> m_uniformSizesInBytes; Vector<Uint> m_uniformSizesInBytes;
Vector<Uint8> m_uboScratch; Vector<Uint8> m_globalUboScratch;
Uint m_activeUniformCount = 0; Uint m_activeUniformCount = 0;
Uint m_maxUniformLocation = 0; Uint m_maxUniformLocation = 0;
@@ -85,6 +85,11 @@ namespace MobileGL::MG_State::GLState {
return m_indexBufferBindingSlot; return m_indexBufferBindingSlot;
} }
const BindingSlot<BufferObject>& VertexArrayObject::GetIndexBufferBindingSlot() const {
return m_indexBufferBindingSlot;
}
const VertexAttribute& VertexArrayObject::GetAttribute(Uint index) const { const VertexAttribute& VertexArrayObject::GetAttribute(Uint index) const {
static VertexAttribute emptyAttr; static VertexAttribute emptyAttr;
if (index >= MAX_VERTEX_ATTRIBS) return emptyAttr; if (index >= MAX_VERTEX_ATTRIBS) return emptyAttr;
@@ -48,6 +48,7 @@ namespace MobileGL {
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer); void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
BindingSlot<BufferObject>& GetIndexBufferBindingSlot(); BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
const VertexAttribute& GetAttribute(Uint index) const; const VertexAttribute& GetAttribute(Uint index) const;
const Array<VertexAttribute, MAX_VERTEX_ATTRIBS>& GetAllAttributes() const; const Array<VertexAttribute, MAX_VERTEX_ATTRIBS>& GetAllAttributes() const;
+24 -12
View File
@@ -25,7 +25,8 @@ protected:
}; };
TEST_F(BufferTest, Binding) { TEST_F(BufferTest, Binding) {
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(3); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(3, bufferNames);
auto& arraySlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& arraySlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto& indexSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); auto& indexSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
@@ -49,7 +50,8 @@ TEST_F(BufferTest, PingPong) {
auto& readSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead); auto& readSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead);
auto& writeSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite); auto& writeSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite);
{ {
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
writeSlot.Bind(bufObj); writeSlot.Bind(bufObj);
@@ -80,7 +82,9 @@ TEST_F(BufferTest, PingPong) {
TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) { TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
const SizeT largeCount = 100000; // generate tons of buffer names const SizeT largeCount = 100000; // generate tons of buffer names
auto names = MobileGL::MG_State::pGLContext->GenBufferNames(largeCount);
Vector<Uint> names;
MobileGL::MG_State::pGLContext->GenBufferNames(largeCount, names);
std::vector<SizeT> indices = {0, 600, 5000, 32768, 99999}; // only create a few buffer objects std::vector<SizeT> indices = {0, 600, 5000, 32768, 99999}; // only create a few buffer objects
for (SizeT idx : indices) { for (SizeT idx : indices) {
@@ -104,7 +108,8 @@ TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
TEST_F(BufferTest, AcquireMemory) { TEST_F(BufferTest, AcquireMemory) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
Vector<Int> initData{10, 20, 30, 40, 50}; Vector<Int> initData{10, 20, 30, 40, 50};
@@ -132,7 +137,8 @@ TEST_F(BufferTest, AcquireMemory) {
TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) { TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
Vector<Int> initData{10, 20, 30, 40, 50}; Vector<Int> initData{10, 20, 30, 40, 50};
@@ -160,7 +166,8 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) { TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
@@ -208,7 +215,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
auto& srcSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead); auto& srcSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead);
auto& dstSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite); auto& dstSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite);
auto srcNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> srcNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, srcNames);
auto srcObj = MobileGL::MG_State::pGLContext->CreateBufferObject(srcNames[0]); auto srcObj = MobileGL::MG_State::pGLContext->CreateBufferObject(srcNames[0]);
srcSlot.Bind(srcObj); srcSlot.Bind(srcObj);
@@ -218,7 +226,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
DataPtr srcPtr{.data = srcData.data(), .size = srcSize}; DataPtr srcPtr{.data = srcData.data(), .size = srcSize};
srcObj->UploadData(srcPtr, 0); srcObj->UploadData(srcPtr, 0);
auto dstNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> dstNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, dstNames);
auto dstObj = MobileGL::MG_State::pGLContext->CreateBufferObject(dstNames[0]); auto dstObj = MobileGL::MG_State::pGLContext->CreateBufferObject(dstNames[0]);
dstSlot.Bind(dstObj); dstSlot.Bind(dstObj);
@@ -249,7 +258,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
TEST_F(BufferTest, WriteWhileMapped) { TEST_F(BufferTest, WriteWhileMapped) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::ShaderStorage); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::ShaderStorage);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
@@ -280,7 +290,8 @@ TEST_F(BufferTest, WriteWhileMapped) {
TEST_F(BufferTest, PartialUpdate) { TEST_F(BufferTest, PartialUpdate) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
@@ -308,7 +319,8 @@ TEST_F(BufferTest, PartialUpdate) {
} }
TEST_F(BufferTest, DeleteBufferObject) { TEST_F(BufferTest, DeleteBufferObject) {
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
slot.Bind(bufObj); slot.Bind(bufObj);
@@ -322,7 +334,7 @@ using namespace MobileGL::MG_Impl::GLImpl;
class GeneralBufferTest : public ::testing::Test { class GeneralBufferTest : public ::testing::Test {
protected: protected:
void SetUp() override { MG_State::pGLContext = new MG_State::GLState::GLContext(); } void SetUp() override { MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>(); }
GLuint CreateBoundBuffer(GLenum target, GLsizeiptr size, GLenum usage) { GLuint CreateBoundBuffer(GLenum target, GLsizeiptr size, GLenum usage) {
GLuint buffer; GLuint buffer;
+8 -8
View File
@@ -191,7 +191,7 @@ TEST_F(ProgramTest, CompileAndLink) {
String source; String source;
auto& spirvCode = shaderSpirvs[index]; auto& spirvCode = shaderSpirvs[index];
MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode); MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options; spvc_compiler_options options;
spvcSession.CreateOptions(&options); spvcSession.CreateOptions(&options);
@@ -926,7 +926,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) {
char* pSrcVertIn = nullptr; char* pSrcVertIn = nullptr;
const char* needle = "layout(location = 2) in vec2 UV0;"; const char* needle = "layout(location = 2) in vec2 UV0;";
for (auto spirv : spirvs) { for (auto spirv : spirvs) {
MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv); MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options; spvc_compiler_options options;
spvcSession.CreateOptions(&options); spvcSession.CreateOptions(&options);
@@ -987,7 +987,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) {
char* pSrcfragOut = nullptr; char* pSrcfragOut = nullptr;
const char* needle = "layout(location = 7) out vec4 fragColor;"; const char* needle = "layout(location = 7) out vec4 fragColor;";
// for (auto spirv: spirvs) { // for (auto spirv: spirvs) {
MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv); MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options; spvc_compiler_options options;
spvcSession.CreateOptions(&options); spvcSession.CreateOptions(&options);
@@ -1063,6 +1063,7 @@ float fog_cylindrical_distance(vec3 pos) {
return max(distXZ, distY); return max(distXZ, distY);
} }
uniform float fTime;
layout(std140) uniform Globals { layout(std140) uniform Globals {
ivec3 CameraBlockPos; ivec3 CameraBlockPos;
@@ -1165,7 +1166,7 @@ vec4 sampleRGSS(sampler2D source, vec2 uv, vec2 pixelSize) {
void main() { void main() {
vec4 color = (UseRgss == 1 ? sampleRGSS(Sampler0, texCoord0, 1.0f / TextureSize) : sampleNearest(Sampler0, texCoord0, 1.0f / TextureSize)) * vertexColor; vec4 color = (UseRgss == 1 ? sampleRGSS(Sampler0, texCoord0, 1.0f / TextureSize) : sampleNearest(Sampler0, texCoord0, 1.0f / TextureSize)) * vertexColor;
color = mix(FogColor * vec4(1, 1, 1, color.a), color, ChunkVisibility); color = mix(FogColor * vec4(1, 1, 1, color.a * fTime), color, ChunkVisibility);
#ifdef ALPHA_CUTOUT #ifdef ALPHA_CUTOUT
if (color.a < ALPHA_CUTOUT) { if (color.a < ALPHA_CUTOUT) {
discard; discard;
@@ -1207,9 +1208,8 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) {
auto programObject = MG_State::pGLContext->GetCurrentProgram(); auto programObject = MG_State::pGLContext->GetCurrentProgram();
auto& spirvs = programObject->GetGeneratedSpirv(); auto& spirvs = programObject->GetGeneratedSpirv();
auto& fragSpirv = spirvs[0]; // 0 - fragment, 1 - vertex auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)];
char* pSrcfragOut = nullptr; MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv);
spvc_compiler_options options; spvc_compiler_options options;
spvcSession.CreateOptions(&options); spvcSession.CreateOptions(&options);
@@ -1221,5 +1221,5 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) {
const char* result = nullptr; const char* result = nullptr;
spvcSession.Compile(&result); spvcSession.Compile(&result);
printf("%s\n\n", result); printf("decomp from fragSpirv:\n%s\n\n", result);
} }
+3 -3
View File
@@ -153,7 +153,7 @@ TEST_F(ProgramUtilTest, CompileFragmentShaderWithDiscard) {
Vector<SpvcSession> sessions(spirvs.size()); Vector<SpvcSession> sessions(spirvs.size());
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
sessions[i] = SpvcSession(spirvs[i]); sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile);
} }
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
@@ -278,7 +278,7 @@ TEST_F(ProgramUtilTest, DecompProgram) {
Vector<SpvcSession> sessions(spirvs.size()); Vector<SpvcSession> sessions(spirvs.size());
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
sessions[i] = SpvcSession(spirvs[i]); sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile);
} }
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
@@ -439,7 +439,7 @@ TEST_F(ProgramUtilTest, CompileAndLinkBlitProgram) {
auto spirvs = bin_res.value(); auto spirvs = bin_res.value();
Vector<SpvcSession> sessions(spirvs.size()); Vector<SpvcSession> sessions(spirvs.size());
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
sessions[i] = SpvcSession(spirvs[i]); sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile);
} }
for (SizeT i = 0; i < spirvs.size(); ++i) { for (SizeT i = 0; i < spirvs.size(); ++i) {
@@ -21,7 +21,8 @@ using namespace MobileGL;
class VertexArrayTest : public ::testing::Test { class VertexArrayTest : public ::testing::Test {
protected: protected:
SharedPtr<MG_State::GLState::BufferObject> CreateTestVBO() { SharedPtr<MG_State::GLState::BufferObject> CreateTestVBO() {
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto vbo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto vbo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex).Bind(vbo); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex).Bind(vbo);
@@ -39,7 +40,8 @@ protected:
}; };
TEST_F(VertexArrayTest, GenerateAndBindVAO) { TEST_F(VertexArrayTest, GenerateAndBindVAO) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(2); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(2, vaoNames);
auto vao0 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao0 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]); auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]);
@@ -55,7 +57,8 @@ TEST_F(VertexArrayTest, GenerateAndBindVAO) {
} }
TEST_F(VertexArrayTest, VertexAttributeSetup) { TEST_F(VertexArrayTest, VertexAttributeSetup) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames);
auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
@@ -86,11 +89,13 @@ TEST_F(VertexArrayTest, VertexAttributeSetup) {
} }
TEST_F(VertexArrayTest, IndexBufferBinding) { TEST_F(VertexArrayTest, IndexBufferBinding) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames);
auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> bufferNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
auto ebo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); auto ebo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo);
@@ -103,14 +108,16 @@ TEST_F(VertexArrayTest, IndexBufferBinding) {
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo);
ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), ebo); ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), ebo);
auto newEboNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> newEboNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, newEboNames);
auto newEbo = MobileGL::MG_State::pGLContext->CreateBufferObject(newEboNames[0]); auto newEbo = MobileGL::MG_State::pGLContext->CreateBufferObject(newEboNames[0]);
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(newEbo); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(newEbo);
ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), newEbo); ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), newEbo);
} }
TEST_F(VertexArrayTest, DeleteVAO) { TEST_F(VertexArrayTest, DeleteVAO) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames);
auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
@@ -125,7 +132,8 @@ TEST_F(VertexArrayTest, DeleteVAO) {
TEST_F(VertexArrayTest, ValidateNamesAndObjects) { TEST_F(VertexArrayTest, ValidateNamesAndObjects) {
const Uint count = 5; const Uint count = 5;
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(count); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(count, vaoNames);
for (Uint i = 0; i < count; i++) { for (Uint i = 0; i < count; i++) {
ASSERT_TRUE(MobileGL::MG_State::pGLContext->ValidateVertexArrayName(vaoNames[i])); ASSERT_TRUE(MobileGL::MG_State::pGLContext->ValidateVertexArrayName(vaoNames[i]));
@@ -144,12 +152,14 @@ TEST_F(VertexArrayTest, ValidateNamesAndObjects) {
} }
TEST_F(VertexArrayTest, MultipleAttributes) { TEST_F(VertexArrayTest, MultipleAttributes) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames);
auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
auto vboPos = CreateTestVBO(); auto vboPos = CreateTestVBO();
auto vboNormalNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); Vector<Uint> vboNormalNames;
MobileGL::MG_State::pGLContext->GenBufferNames(1, vboNormalNames);
auto vboNormal = MobileGL::MG_State::pGLContext->CreateBufferObject(vboNormalNames[0]); auto vboNormal = MobileGL::MG_State::pGLContext->CreateBufferObject(vboNormalNames[0]);
Vector<float> normals(12, 0.5f); Vector<float> normals(12, 0.5f);
@@ -187,7 +197,8 @@ TEST_F(VertexArrayTest, MultipleAttributes) {
} }
TEST_F(VertexArrayTest, BoundVAOPreservesState) { TEST_F(VertexArrayTest, BoundVAOPreservesState) {
auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(2); Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(2, vaoNames);
auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]);
auto vao2 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]); auto vao2 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]);
@@ -216,11 +227,9 @@ using namespace MobileGL::MG_Impl::GLImpl;
class GeneralVertexArrayTest : public ::testing::Test { class GeneralVertexArrayTest : public ::testing::Test {
protected: protected:
void SetUp() override { MG_State::pGLContext = new MG_State::GLState::GLContext(); } void SetUp() override { MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>(); }
void TearDown() override { void TearDown() override {
delete MG_State::pGLContext;
MG_State::pGLContext = nullptr;
} }
GLuint CreateVAO() { GLuint CreateVAO() {
+241 -61
View File
@@ -11,65 +11,201 @@
namespace MobileGL { namespace MobileGL {
namespace MG_Util { namespace MG_Util {
namespace ShaderTranspiler { namespace ShaderTranspiler {
SpvcSession::SpvcSession(const Vector<unsigned int>& spirv) {
const SpvId* p_spirv = spirv.data();
size_t word_count = spirv.size();
spvc_context_create(&context); static spvc_basetype MapReflectToSpvcBasetype(const SpvReflectBlockVariable& member) {
spvc_context_parse_spirv(context, p_spirv, word_count, &ir); if (!member.type_description) return SPVC_BASETYPE_UNKNOWN;
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, auto flags = member.type_description->type_flags;
&compiler); auto width = member.numeric.scalar.width;
spvc_compiler_create_shader_resources(compiler, &resources); auto signedness = member.numeric.scalar.signedness;
if (flags & SPV_REFLECT_TYPE_FLAG_FLOAT) {
switch (width) {
case 16: return SPVC_BASETYPE_FP16;
case 32: return SPVC_BASETYPE_FP32;
case 64: return SPVC_BASETYPE_FP64;
default: return SPVC_BASETYPE_UNKNOWN;
}
} else if (flags & SPV_REFLECT_TYPE_FLAG_INT) {
if (signedness) {
switch (width) {
case 8: return SPVC_BASETYPE_INT8;
case 16: return SPVC_BASETYPE_INT16;
case 32: return SPVC_BASETYPE_INT32;
case 64: return SPVC_BASETYPE_INT64;
default: return SPVC_BASETYPE_UNKNOWN;
}
} else {
switch (width) {
case 8: return SPVC_BASETYPE_UINT8;
case 16: return SPVC_BASETYPE_UINT16;
case 32: return SPVC_BASETYPE_UINT32;
case 64: return SPVC_BASETYPE_UINT64;
default: return SPVC_BASETYPE_UNKNOWN;
}
}
} else if (flags & SPV_REFLECT_TYPE_FLAG_BOOL) {
return SPVC_BASETYPE_BOOLEAN;
}
return SPVC_BASETYPE_UNKNOWN;
}
SpvcSession::SpvcSession(const Vector<unsigned int>& spirv, Flags<SessionUsageBit> usage)
: usage(usage) {
if (usage & SessionUsageBit::Transpile) {
const SpvId* p_spirv = spirv.data();
size_t word_count = spirv.size();
spvc_context_create(&context);
spvc_context_parse_spirv(context, p_spirv, word_count, &ir);
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
&compiler);
spvc_compiler_create_shader_resources(compiler, &resources);
} else if (usage & SessionUsageBit::Reflection) {
SpvReflectResult result = spvReflectCreateShaderModule(
spirv.size() * sizeof(uint32_t), spirv.data(), &reflectModule);
reflectModuleValid = (result == SPV_REFLECT_RESULT_SUCCESS);
}
} }
SpvcSession::SpvcSession(SpvcSession&& that) { SpvcSession::SpvcSession(SpvcSession&& that) {
std::swap(this->usage, that.usage);
std::swap(this->context, that.context); std::swap(this->context, that.context);
std::swap(this->compiler, that.compiler); std::swap(this->compiler, that.compiler);
std::swap(this->ir, that.ir); std::swap(this->ir, that.ir);
std::swap(this->compiler_options, that.compiler_options); std::swap(this->compiler_options, that.compiler_options);
std::swap(this->resources, that.resources); std::swap(this->resources, that.resources);
std::swap(this->reflectModule, that.reflectModule);
std::swap(this->reflectModuleValid, that.reflectModuleValid);
} }
SpvcSession& SpvcSession::operator=(SpvcSession&& that) { SpvcSession& SpvcSession::operator=(SpvcSession&& that) {
std::swap(this->usage, that.usage);
std::swap(this->context, that.context); std::swap(this->context, that.context);
std::swap(this->compiler, that.compiler); std::swap(this->compiler, that.compiler);
std::swap(this->ir, that.ir); std::swap(this->ir, that.ir);
std::swap(this->compiler_options, that.compiler_options); std::swap(this->compiler_options, that.compiler_options);
std::swap(this->resources, that.resources); std::swap(this->resources, that.resources);
std::swap(this->reflectModule, that.reflectModule);
std::swap(this->reflectModuleValid, that.reflectModuleValid);
return *this; return *this;
} }
spvc_result SpvcSession::CreateOptions(spvc_compiler_options* options) { spvc_result SpvcSession::CreateOptions(spvc_compiler_options* options) {
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
return spvc_compiler_create_compiler_options(compiler, options); return spvc_compiler_create_compiler_options(compiler, options);
} }
spvc_result SpvcSession::SetOptions(spvc_compiler_options options) { spvc_result SpvcSession::SetOptions(spvc_compiler_options options) {
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
compiler_options = options; compiler_options = options;
return spvc_compiler_install_compiler_options(compiler, options); return spvc_compiler_install_compiler_options(compiler, options);
} }
Vector<InterfaceVariable> SpvcSession::GetShaderInterface(spvc_resource_type resource_type) const { Vector<InterfaceVariable> SpvcSession::GetShaderInterface(spvc_resource_type resource_type) const {
const spvc_reflected_resource* list = nullptr; if (usage & SessionUsageBit::Transpile) {
size_t count = 0; // SPIRV-Cross path
spvc_resources_get_resource_list_for_type(resources, resource_type, &list, &count); const spvc_reflected_resource* list = nullptr;
size_t count = 0;
spvc_resources_get_resource_list_for_type(resources, resource_type, &list, &count);
Vector<InterfaceVariable> variables;
for (size_t i = 0; i < count; ++i) {
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) {
continue;
}
InterfaceVariable var;
var.name = list[i].name;
var.location = spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationLocation);
variables.push_back(var);
}
std::sort(variables.begin(), variables.end());
return variables;
}
// SPIRV-Reflect path (Reflection only, no Transpile)
if (!reflectModuleValid) return {};
Vector<InterfaceVariable> variables; Vector<InterfaceVariable> variables;
for (size_t i = 0; i < count; ++i) { switch (resource_type) {
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { case SPVC_RESOURCE_TYPE_STAGE_INPUT: {
continue; uint32_t count = 0;
spvReflectEnumerateInputVariables(&reflectModule, &count, nullptr);
Vector<SpvReflectInterfaceVariable*> vars(count);
spvReflectEnumerateInputVariables(&reflectModule, &count, vars.data());
for (uint32_t i = 0; i < count; ++i) {
if (vars[i]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) continue;
InterfaceVariable var;
var.name = vars[i]->name;
var.location = vars[i]->location;
variables.push_back(var);
} }
break;
InterfaceVariable var; }
var.name = list[i].name; case SPVC_RESOURCE_TYPE_STAGE_OUTPUT: {
var.location = spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationLocation); uint32_t count = 0;
variables.push_back(var); spvReflectEnumerateOutputVariables(&reflectModule, &count, nullptr);
Vector<SpvReflectInterfaceVariable*> vars(count);
spvReflectEnumerateOutputVariables(&reflectModule, &count, vars.data());
for (uint32_t i = 0; i < count; ++i) {
if (vars[i]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) continue;
InterfaceVariable var;
var.name = vars[i]->name;
var.location = vars[i]->location;
variables.push_back(var);
}
break;
}
case SPVC_RESOURCE_TYPE_SAMPLED_IMAGE: {
uint32_t count = 0;
spvReflectEnumerateDescriptorBindings(&reflectModule, &count, nullptr);
Vector<SpvReflectDescriptorBinding*> bindings(count);
spvReflectEnumerateDescriptorBindings(&reflectModule, &count, bindings.data());
for (uint32_t i = 0; i < count; ++i) {
if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE) {
InterfaceVariable var;
var.name = bindings[i]->name;
var.location = bindings[i]->binding;
variables.push_back(var);
}
}
break;
}
case SPVC_RESOURCE_TYPE_UNIFORM_BUFFER: {
uint32_t count = 0;
spvReflectEnumerateDescriptorBindings(&reflectModule, &count, nullptr);
Vector<SpvReflectDescriptorBinding*> bindings(count);
spvReflectEnumerateDescriptorBindings(&reflectModule, &count, bindings.data());
for (uint32_t i = 0; i < count; ++i) {
if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) {
InterfaceVariable var;
// Use the block/type name (e.g. "MGL_GLOBAL_UBO") rather than
// the variable name, which may be empty or meaningless for UBOs.
// This is consistent with ParseMetaData() which uses type_description->type_name.
if (bindings[i]->type_description && bindings[i]->type_description->type_name) {
var.name = bindings[i]->type_description->type_name;
} else {
var.name = bindings[i]->name;
}
var.location = bindings[i]->binding;
variables.push_back(var);
}
}
break;
}
case SPVC_RESOURCE_TYPE_GL_PLAIN_UNIFORM:
// GL plain uniforms are a SPIRV-Cross-specific concept.
// In reflection-only mode, not available.
break;
default:
break;
} }
std::sort(variables.begin(), variables.end()); std::sort(variables.begin(), variables.end());
return variables; return variables;
} }
spvc_result SpvcSession::SetVertexAttribLocation(const UnorderedMap<String, Uint>& location) { spvc_result SpvcSession::SetVertexAttribLocation(const UnorderedMap<String, Uint>& location) {
// TODO: We should assert we're really dealing with vertex shader here if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
SPVC_CHK_INIT SPVC_CHK_INIT
const spvc_reflected_resource* list = nullptr; const spvc_reflected_resource* list = nullptr;
@@ -80,8 +216,6 @@ namespace MobileGL {
auto& resource = list[i]; auto& resource = list[i];
auto it = location.find(resource.name); auto it = location.find(resource.name);
if (it != location.end()) { if (it != location.end()) {
// realize glBindVertexAttribLocation here
// it->second should be the location explicitly requested
spvc_compiler_set_decoration(compiler, resource.id, SpvDecorationLocation, it->second); spvc_compiler_set_decoration(compiler, resource.id, SpvDecorationLocation, it->second);
} }
} }
@@ -89,59 +223,99 @@ namespace MobileGL {
} }
spvc_result SpvcSession::Compile(const char** result) { spvc_result SpvcSession::Compile(const char** result) {
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
SPVC_CHK_INIT SPVC_CHK_INIT
SPVC_CHK_RESULT(spvc_compiler_compile(compiler, result)); SPVC_CHK_RESULT(spvc_compiler_compile(compiler, result));
// SPVC_CHK_RESULT(ParseMetaData());
SPVC_CHK_RETURN SPVC_CHK_RETURN
} }
spvc_result SpvcSession::ParseMetaData() { spvc_result SpvcSession::ParseMetaData() {
SPVC_CHK_INIT if (usage & SessionUsageBit::Transpile) {
// SPIRV-Cross path
SPVC_CHK_INIT
metadata = SpvcMetadata();
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(
resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);)
for (size_t i = 0; i < count; ++i) {
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) {
continue;
}
if (strcmp(list[i].name, GLOBAL_UBO_NAME) == 0) {
spvc_type type = spvc_compiler_get_type_handle(compiler, list[i].base_type_id);
spvc_compiler_get_declared_struct_size(compiler, type, &metadata.globalUboSize);
size_t num_members = spvc_type_get_num_member_types(type);
for (size_t j = 0; j < num_members; ++j) {
const char* memberName =
spvc_compiler_get_member_name(compiler, list[i].base_type_id, j);
unsigned memberOffset = 0;
SPVC_CHK_RESULT(
spvc_compiler_type_struct_member_offset(compiler, type, j, &memberOffset);)
metadata.plainUniformOffsetsInUBO[memberName] = memberOffset;
SizeT memberSize = 0;
SPVC_CHK_RESULT(
spvc_compiler_get_declared_struct_member_size(compiler, type, j, &memberSize);)
metadata.plainUniformMemberSizesInBytes[memberName] = memberSize;
auto memberTypeId = spvc_type_get_member_type(type, j);
spvc_type memberType = spvc_compiler_get_type_handle(compiler, memberTypeId);
spvc_basetype basetype = spvc_type_get_basetype(memberType);
auto vectorSize = spvc_type_get_vector_size(memberType);
auto matCol = spvc_type_get_columns(memberType);
metadata.plainUniformMemberTypes[memberName] = {
.basetype = basetype,
.vectorSize = vectorSize,
.matCol = matCol,
};
}
SPVC_CHK_RETURN
}
}
return SPVC_ERROR_INVALID_SPIRV;
}
// SPIRV-Reflect path (Reflection only)
if (!reflectModuleValid) return SPVC_ERROR_INVALID_SPIRV;
metadata = SpvcMetadata(); metadata = SpvcMetadata();
const spvc_reflected_resource* list = nullptr; uint32_t bindingCount = 0;
size_t count = 0; spvReflectEnumerateDescriptorBindings(&reflectModule, &bindingCount, nullptr);
Vector<SpvReflectDescriptorBinding*> bindings(bindingCount);
spvReflectEnumerateDescriptorBindings(&reflectModule, &bindingCount, bindings.data());
SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, for (uint32_t i = 0; i < bindingCount; ++i) {
&list, &count);) auto* binding = bindings[i];
for (size_t i = 0; i < count; ++i) { if (binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) continue;
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { if (strcmp(binding->type_description->type_name, GLOBAL_UBO_NAME) != 0) continue;
continue;
}
if (strcmp(list[i].name, GLOBAL_UBO_NAME) == 0) { auto& block = binding->block;
spvc_type type = spvc_compiler_get_type_handle(compiler, list[i].base_type_id); metadata.globalUboSize = block.size;
spvc_compiler_get_declared_struct_size(compiler, type, &metadata.uboSize);
size_t num_members = spvc_type_get_num_member_types(type);
for (size_t j = 0; j < num_members; ++j) {
const char* memberName = spvc_compiler_get_member_name(compiler, list[i].base_type_id, j);
unsigned memberOffset = 0; for (uint32_t j = 0; j < block.member_count; ++j) {
SPVC_CHK_RESULT(spvc_compiler_type_struct_member_offset(compiler, type, j, &memberOffset);) auto& member = block.members[j];
metadata.plainUniformOffsetsInUBO[memberName] = memberOffset; metadata.plainUniformOffsetsInUBO[member.name] = member.offset;
SizeT memberSize = 0; metadata.plainUniformMemberSizesInBytes[member.name] = member.size;
SPVC_CHK_RESULT(
spvc_compiler_get_declared_struct_member_size(compiler, type, j, &memberSize);)
metadata.plainUniformMemberSizesInBytes[memberName] = memberSize;
auto memberTypeId = spvc_type_get_member_type(type, j); Uint32 vectorSize = member.numeric.vector.component_count;
spvc_type memberType = spvc_compiler_get_type_handle(compiler, memberTypeId); if (vectorSize == 0) vectorSize = 1;
spvc_basetype basetype = spvc_type_get_basetype(memberType); Uint32 matCol = member.numeric.matrix.column_count;
auto vectorSize = spvc_type_get_vector_size(memberType); if (matCol == 0) matCol = 1;
auto matCol = spvc_type_get_columns(memberType);
// auto dim = spvc_type_get_num_array_dimensions(type); metadata.plainUniformMemberTypes[member.name] = {
metadata.plainUniformMemberTypes[memberName] = { .basetype = MapReflectToSpvcBasetype(member),
.basetype = basetype, .vectorSize = vectorSize,
.vectorSize = vectorSize, .matCol = matCol,
.matCol = matCol, };
};
}
SPVC_CHK_RETURN
} }
return SPVC_SUCCESS;
} }
// This means this spv binary does not have
// auto-generated UBO in it
return SPVC_ERROR_INVALID_SPIRV; return SPVC_ERROR_INVALID_SPIRV;
} }
@@ -150,11 +324,17 @@ namespace MobileGL {
} }
const char* SpvcSession::GetLastErrorString() const { const char* SpvcSession::GetLastErrorString() const {
return spvc_context_get_last_error_string(context); if (context) {
return spvc_context_get_last_error_string(context);
}
return "";
} }
SpvcSession::~SpvcSession() { SpvcSession::~SpvcSession() {
spvc_context_destroy(context); spvc_context_destroy(context);
if (reflectModuleValid) {
spvReflectDestroyShaderModule(&reflectModule);
}
} }
} // namespace ShaderTranspiler } // namespace ShaderTranspiler
} // namespace MG_Util } // namespace MG_Util
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include <Includes.h> #include <Includes.h>
#include <spirv_reflect.h>
#include "Types.h" #include "Types.h"
#define SPVC_CHK_INIT auto __r = SPVC_SUCCESS; #define SPVC_CHK_INIT auto __r = SPVC_SUCCESS;
@@ -55,18 +56,24 @@ namespace MobileGL {
} }
}; };
enum class SessionUsageBit {
Reflection = 1 << 0,
Transpile = 1 << 1,
};
struct SpvcMetadata { struct SpvcMetadata {
UnorderedMap<String, unsigned> plainUniformOffsetsInUBO; UnorderedMap<String, unsigned> plainUniformOffsetsInUBO;
UnorderedMap<String, SizeT> plainUniformMemberSizesInBytes; UnorderedMap<String, SizeT> plainUniformMemberSizesInBytes;
UnorderedMap<String, SpvcType> plainUniformMemberTypes; UnorderedMap<String, SpvcType> plainUniformMemberTypes;
SizeT uboSize = 0; SizeT globalUboSize = 0;
}; };
class SpvcSession { class SpvcSession {
public: public:
SpvcSession() {} SpvcSession() {}
explicit SpvcSession(const Vector<unsigned int>& spirv); explicit SpvcSession(const Vector<unsigned int>& spirv,
Flags<SessionUsageBit> usage);
SpvcSession(SpvcSession&) = delete; SpvcSession(SpvcSession&) = delete;
@@ -90,12 +97,19 @@ namespace MobileGL {
spvc_result ParseMetaData(); spvc_result ParseMetaData();
private: private:
Flags<SessionUsageBit> usage;
// SPIRV-Cross state (used when Transpile flag is set)
spvc_context context = nullptr; spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr; spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr; spvc_compiler compiler = nullptr;
spvc_compiler_options compiler_options = nullptr; spvc_compiler_options compiler_options = nullptr;
spvc_resources resources = nullptr; spvc_resources resources = nullptr;
// SPIRV-Reflect state (used when only Reflection flag is set)
SpvReflectShaderModule reflectModule = {};
bool reflectModuleValid = false;
SpvcMetadata metadata; SpvcMetadata metadata;
}; };
} // namespace ShaderTranspiler } // namespace ShaderTranspiler