mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c6f55c2d | ||
|
|
b373535d6e | ||
|
|
4a3a44924a | ||
|
|
1b2a7989af | ||
|
|
83d1ce177e | ||
|
|
4321c4a827 | ||
|
|
a039ce0987 | ||
|
|
accfaab720 | ||
|
|
35658eb998 | ||
|
|
ccbde0196e | ||
|
|
0209c5461f | ||
|
|
810b4886c7 | ||
|
|
7dfc2149d8 | ||
|
|
9d1e8bd7cd | ||
|
|
bfa4049ac1 | ||
|
|
87c56ce3d5 | ||
|
|
f2ab50b84e | ||
|
|
b6ac6c182e | ||
|
|
5e9f0f0c70 | ||
|
|
e4455aed9a | ||
|
|
bc018c9513 | ||
|
|
2ecba4d70c | ||
|
|
1e11a5950e | ||
|
|
b324363db0 | ||
|
|
a654b15190 | ||
|
|
75ea7f8c0c | ||
|
|
09aeee4c03 | ||
|
|
87122973be | ||
|
|
8b9adc5121 | ||
|
|
9e42e40705 | ||
|
|
8a91225eb1 | ||
|
|
e82815802e |
@@ -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
|
||||||
|
|||||||
+1
Submodule 3rdparty/SPIRV-Reflect added at 10b4f09a24
+12
-1
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
+20
-46
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user