mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08: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"]
|
||||
path = 3rdparty/Vulkan-Headers
|
||||
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_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/glslang)
|
||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||
add_subdirectory(3rdparty/Vulkan-Headers)
|
||||
add_subdirectory(3rdparty/Vulkan-Utility-Libraries)
|
||||
add_subdirectory(3rdparty/SPIRV-Reflect)
|
||||
|
||||
set(XXHASH_BUILD_XXHSUM 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/PipelineFactory.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/VertexInputStateFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
|
||||
@@ -268,6 +278,7 @@ set(MOBILEGL_LINK_LIBRARIES
|
||||
xxHash::xxhash
|
||||
GPUOpen::VulkanMemoryAllocator
|
||||
Vulkan::UtilityHeaders
|
||||
spirv-reflect-static
|
||||
)
|
||||
|
||||
set(MOBILEGL_COMPILE_DEF
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
#define MOBILEGL_EGL_API MOBILEGL_API
|
||||
|
||||
// ====================== 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_FILE 1
|
||||
|
||||
@@ -1075,7 +1075,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String source;
|
||||
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;
|
||||
spvcSession.CreateOptions(&options);
|
||||
|
||||
@@ -17,10 +17,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {}
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {}
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {}
|
||||
void 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 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 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) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context");
|
||||
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) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context");
|
||||
|
||||
DrawArrayCmd payload{};
|
||||
DrawCmd payload{};
|
||||
payload.mode = mode;
|
||||
payload.first = first;
|
||||
payload.count = count;
|
||||
payload.params.firstVertex = first;
|
||||
payload.params.vertexCount = count;
|
||||
|
||||
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,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter) {
|
||||
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 "MG_Util/ShaderTranspiler/SpvcSession.h"
|
||||
#include "MG_Util/ShaderTranspiler/Types.h"
|
||||
#include <cstring>
|
||||
#include <spirv-tools/libspirv.h>
|
||||
#include <spirv-tools/optimizer.hpp>
|
||||
#include <source/opt/constants.h>
|
||||
@@ -21,6 +24,8 @@
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
using ShaderObject = MG_State::GLState::ShaderObject;
|
||||
using SpvcSession = MG_Util::ShaderTranspiler::SpvcSession;
|
||||
using SessionUsageBit = MG_Util::ShaderTranspiler::SessionUsageBit;
|
||||
|
||||
struct PositionTargetInfo {
|
||||
Uint32 variableId = 0;
|
||||
@@ -321,8 +326,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ProgramFactory::~ProgramFactory() = default;
|
||||
|
||||
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
|
||||
switch (stage) {
|
||||
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, &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);
|
||||
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) {
|
||||
auto hash = ComputeHash(program, flags);
|
||||
auto it = m_cache.find(hash);
|
||||
if (it != m_cache.end()) {
|
||||
return it->second.stages;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto& entry = m_cache[hash];
|
||||
@@ -400,6 +568,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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
|
||||
|
||||
@@ -11,11 +11,19 @@
|
||||
#include "../VkIncludes.h"
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class ProgramFactory {
|
||||
public:
|
||||
enum class DescriptorBindingKind : Uint8 {
|
||||
None = 0,
|
||||
UniformBufferDynamic,
|
||||
CombinedImageSampler
|
||||
};
|
||||
|
||||
enum class CompileOptionBit : Uint {
|
||||
None = 0,
|
||||
PositionYFlip = 1 << 0,
|
||||
@@ -26,10 +34,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
};
|
||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||
using HashType = Uint64;
|
||||
|
||||
struct VkProgramObject {
|
||||
HashType hash = 0;
|
||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||
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;
|
||||
|
||||
VkProgramObject() = default;
|
||||
@@ -39,52 +58,85 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
hash = other.hash;
|
||||
stages = std::move(other.stages);
|
||||
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.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
}
|
||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
Destroy();
|
||||
hash = other.hash;
|
||||
stages = std::move(other.stages);
|
||||
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.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~VkProgramObject() {
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
private:
|
||||
void DestroyModules() {
|
||||
for (auto module : modules) {
|
||||
if (module != VK_NULL_HANDLE && s_device != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(s_device, module, nullptr);
|
||||
void Destroy() {
|
||||
if (s_device != VK_NULL_HANDLE) {
|
||||
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||
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();
|
||||
stages.clear();
|
||||
}
|
||||
};
|
||||
|
||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config)
|
||||
: m_device(device), m_config(config) {
|
||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16)
|
||||
: m_device(device), m_config(config), m_maxBindings(maxBindings) {
|
||||
VkProgramObject::s_device = device;
|
||||
}
|
||||
~ProgramFactory();
|
||||
~ProgramFactory() = default;
|
||||
ProgramFactory(const ProgramFactory&) = delete;
|
||||
|
||||
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);
|
||||
|
||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||
|
||||
private:
|
||||
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
||||
void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
Uint32 m_maxBindings = 0;
|
||||
UnorderedMap<HashType, VkProgramObject> m_cache;
|
||||
const VulkanRendererConfig& m_config;
|
||||
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
|
||||
|
||||
#include "VkBufferObject.h"
|
||||
#include "ProgramFactory.h"
|
||||
#include "VkBufferManager.h"
|
||||
#include "VkSamplerManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
@@ -22,35 +22,30 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class UniformDescriptorBinder {
|
||||
class UniformManager {
|
||||
public:
|
||||
enum class BindingKind : Uint8 {
|
||||
None = 0,
|
||||
UniformBufferDynamic,
|
||||
CombinedImageSampler
|
||||
};
|
||||
|
||||
struct SamplerBindingOverride {
|
||||
Uint32 binding = 0;
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||
};
|
||||
|
||||
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment,
|
||||
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
|
||||
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024,
|
||||
Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
|
||||
ProgramFactory* programFactory,
|
||||
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
|
||||
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
|
||||
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program);
|
||||
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures);
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex);
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex,
|
||||
const SamplerBindingOverride* samplerBindingOverride);
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
const SamplerBindingOverride* samplerBindingOverride = nullptr);
|
||||
|
||||
private:
|
||||
struct DescriptorPoolBucket {
|
||||
@@ -60,54 +55,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
};
|
||||
|
||||
struct FrameResources {
|
||||
VkBufferObject uploadBuffer;
|
||||
Vector<DescriptorPoolBucket> descriptorPools;
|
||||
Uint32 activeDescriptorPoolIndex = 0;
|
||||
Uint32 allocatedSetsThisFrame = 0;
|
||||
Uint32 peakAllocatedSetsThisFrame = 0;
|
||||
VkDeviceSize writeCursor = 0;
|
||||
};
|
||||
|
||||
struct ProgramLayout {
|
||||
Uint64 hash = 0;
|
||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||
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 ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const;
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramLayout& layout, Uint32 binding,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||
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,
|
||||
Vector<VkDeviceSize>& outSizes) const;
|
||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||
void DestroyProgramLayouts();
|
||||
VkResult AllocateDescriptorSetsFromActivePool(
|
||||
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkBufferManager* m_bufferManager = nullptr;
|
||||
ProgramFactory* m_programFactory = nullptr;
|
||||
Vector<FrameResources> m_frames;
|
||||
UnorderedMap<Uint64, ProgramLayout> m_programLayouts;
|
||||
|
||||
VkDeviceSize m_minDynamicOffsetAlignment = 1;
|
||||
VkDeviceSize m_perFrameUploadBytes = 0;
|
||||
Uint32 m_frameCount = 0;
|
||||
Uint32 m_maxBindings = 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_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_mappedData = other.m_mappedData;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_mappedData = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
|
||||
@@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_allocator = other.m_allocator;
|
||||
m_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_mappedData = other.m_mappedData;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_mappedData = nullptr;
|
||||
other.m_size = 0;
|
||||
return *this;
|
||||
}
|
||||
@@ -44,6 +48,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) {
|
||||
MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator");
|
||||
@@ -78,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
void VkBufferObject::Destroy() {
|
||||
Unmap();
|
||||
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
|
||||
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
||||
}
|
||||
@@ -87,6 +96,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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) {
|
||||
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer");
|
||||
MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null");
|
||||
@@ -96,15 +132,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
void* mapped = nullptr;
|
||||
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped);
|
||||
if (mapResult != VK_SUCCESS || mapped == nullptr) {
|
||||
MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult);
|
||||
const Bool wasMapped = IsMapped();
|
||||
void* mapped = wasMapped ? m_mappedData : Map();
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("VkBufferObject::Upload failed: unable to map buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
||||
vmaUnmapMemory(m_allocator, m_allocation);
|
||||
if (!wasMapped) {
|
||||
Unmap();
|
||||
}
|
||||
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
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BufferSlice.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
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 {
|
||||
public:
|
||||
VkBufferObject() = default;
|
||||
@@ -23,20 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkBufferObject(VkBufferObject&& other) noexcept;
|
||||
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
|
||||
|
||||
Bool Create(const VkBufferObjectDesc& desc);
|
||||
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0);
|
||||
void Destroy();
|
||||
|
||||
void* Map();
|
||||
void Unmap();
|
||||
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
|
||||
|
||||
VkBuffer GetHandle() const { return m_buffer; }
|
||||
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; }
|
||||
|
||||
private:
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkBuffer m_buffer = VK_NULL_HANDLE;
|
||||
VmaAllocation m_allocation = nullptr;
|
||||
void* m_mappedData = nullptr;
|
||||
VkDeviceSize m_size = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include "VkClearManager.h"
|
||||
|
||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool VkClearManager::Initialize() {
|
||||
return true;
|
||||
@@ -31,6 +34,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.color = clearPayload.color,
|
||||
.attachmentType = drawbuf
|
||||
}, 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,
|
||||
.attachmentType = FramebufferAttachmentType::Depth,
|
||||
}, 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 &&
|
||||
@@ -48,6 +57,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.stencil = clearPayload.stencil,
|
||||
.attachmentType = FramebufferAttachmentType::Stencil,
|
||||
}, 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) {
|
||||
if (m_aliveObjects.find(texture) == m_aliveObjects.end() ||
|
||||
m_pendingClears.find(texture) == m_pendingClears.end()) {
|
||||
MGLOG_D("%s: Failed getting pending clear for texture %d", __func__, texture->GetExternalIndex());
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_pendingClears.erase(texture);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
else if (att.IsRenderbuffer())
|
||||
contentPtr = att.GetRenderbuffer().get();
|
||||
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()) {
|
||||
auto* texture = att.GetTexture().get();
|
||||
@@ -113,10 +117,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||
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
|
||||
auto* activeRenderPass = GetActiveRenderPass();
|
||||
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);
|
||||
MOBILEGL_ASSERT(activeIt != m_renderPasses.end(),
|
||||
"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* texture = att.GetTexture().get();
|
||||
const Uint32 attachmentMipLevel = static_cast<Uint32>(std::max(att.GetTextureLevel(), 0));
|
||||
const auto textureTarget = texture->GetTarget();
|
||||
|
||||
// Color attachment description
|
||||
@@ -190,9 +223,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
});
|
||||
}
|
||||
if (width == 0)
|
||||
width = texture2d->GetBaseSize().x();
|
||||
width = att.GetSize().x();
|
||||
if (height == 0)
|
||||
height = texture2d->GetBaseSize().y();
|
||||
height = att.GetSize().y();
|
||||
|
||||
if (isDefaultFbo) {
|
||||
const auto& swapchainViews = m_swapchainObject.GetImageViews();
|
||||
@@ -215,7 +248,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.texture = texture,
|
||||
.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) {
|
||||
@@ -249,6 +284,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkTextureManager::TextureResource* depthTextureResource = nullptr;
|
||||
if (depthAtt.IsComplete() && depthAtt.IsTexture()) {
|
||||
auto& texture = *depthAtt.GetTexture();
|
||||
const Uint32 attachmentMipLevel = static_cast<Uint32>(std::max(depthAtt.GetTextureLevel(), 0));
|
||||
const Uint32 depthAttachmentIndex = static_cast<Uint32>(attachmentDescriptions.size());
|
||||
ClearAttachmentPayload clearPayload{};
|
||||
Bool hasClear = m_clearManager.GetPendingClear(&texture, clearPayload);
|
||||
@@ -311,11 +347,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.finalLayout = depthAttachmentDescription.finalLayout,
|
||||
});
|
||||
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) {
|
||||
auto texture2d = static_cast<MG_State::GLState::TextureObject2D*>(&texture);
|
||||
width = texture2d->GetBaseSize().x();
|
||||
height = texture2d->GetBaseSize().y();
|
||||
width = depthAtt.GetSize().x();
|
||||
height = depthAtt.GetSize().y();
|
||||
}
|
||||
}
|
||||
attachmentDescriptions.emplace_back(depthAttachmentDescription);
|
||||
|
||||
@@ -74,6 +74,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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) {
|
||||
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null");
|
||||
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,
|
||||
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());
|
||||
return ok;
|
||||
}
|
||||
@@ -136,7 +160,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
||||
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
|
||||
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(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_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.image = image;
|
||||
barrier.subresourceRange.aspectMask = aspectMask;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseMipLevel = baseMipLevel;
|
||||
barrier.subresourceRange.levelCount = levelCount;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
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__);
|
||||
return false;
|
||||
}
|
||||
if (!SyncTextureViews(texture, outResource)) {
|
||||
MGLOG_D("%s: SyncTextureViews failed", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool hasDirtyMipLevel = false;
|
||||
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.mipLevels == mipLevels;
|
||||
if (compatible) {
|
||||
if (resource.perMipViews.size() != mipLevels) {
|
||||
resource.perMipViews.resize(mipLevels, VK_NULL_HANDLE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -283,26 +314,68 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr),
|
||||
"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.extent = {static_cast<Uint32>(texelSize.x()), static_cast<Uint32>(texelSize.y())};
|
||||
resource.mipLevels = mipLevels;
|
||||
resource.perMipViews.assign(mipLevels, VK_NULL_HANDLE);
|
||||
resource.sampledBaseMipLevel = 0;
|
||||
resource.sampledLevelCount = mipLevels;
|
||||
resource.format = format;
|
||||
resource.aspect = viewInfo.subresourceRange.aspectMask;
|
||||
resource.aspect = aspect;
|
||||
resource.syncedTextureParamsVersion = 0;
|
||||
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,
|
||||
TextureUploadTarget uploadTarget,
|
||||
TextureResource &outResource) {
|
||||
@@ -390,7 +463,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
aspectMask);
|
||||
aspectMask, 0, outResource.mipLevels);
|
||||
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed");
|
||||
|
||||
for (const auto& item : uploadItems) {
|
||||
@@ -415,7 +488,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
kGraphicsSampledReadStages,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT,
|
||||
aspectMask);
|
||||
aspectMask, 0, outResource.mipLevels);
|
||||
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed");
|
||||
outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
@@ -472,15 +545,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto level0TexelSize = mipTexture->GetMipmapTexelSize(target, 0);
|
||||
const auto level0ByteSize = mipTexture->GetMipmapByteSize(target, 0);
|
||||
if (level0TexelSize.x() <= 0 || level0TexelSize.y() <= 0 /*|| level0ByteSize == 0*/) {
|
||||
// Backing VkImage allocation still uses storage mip 0 as the physical image extent.
|
||||
// GL_TEXTURE_BASE_LEVEL / MAX_LEVEL are applied later when building the sampled view.
|
||||
const auto storageBaseTexelSize = mipTexture->GetMipmapTexelSize(target, 0);
|
||||
const auto storageBaseByteSize = mipTexture->GetMipmapByteSize(target, 0);
|
||||
if (storageBaseTexelSize.x() <= 0 || storageBaseTexelSize.y() <= 0 /*|| storageBaseByteSize == 0*/) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outTarget = target;
|
||||
outTexelSize = level0TexelSize;
|
||||
outByteSize = level0ByteSize;
|
||||
outTexelSize = storageBaseTexelSize;
|
||||
outByteSize = storageBaseByteSize;
|
||||
outMipLevelCount = mipLevelCount;
|
||||
return true;
|
||||
}
|
||||
@@ -507,6 +582,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
|
||||
@@ -31,41 +31,58 @@ public:
|
||||
struct TextureResource {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageView fullView = VK_NULL_HANDLE;
|
||||
Vector<VkImageView> perMipViews;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
Uint32 mipLevels = 1;
|
||||
Uint32 sampledBaseMipLevel = 0;
|
||||
Uint32 sampledLevelCount = 1;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||
Uint16 syncedTextureParamsVersion = 0;
|
||||
|
||||
TextureResource() = default;
|
||||
TextureResource(const TextureResource&) = delete;
|
||||
TextureResource(TextureResource&& that) noexcept {
|
||||
std::swap(this->image, that.image);
|
||||
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->extent, that.extent);
|
||||
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->aspect, that.aspect);
|
||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, view, nullptr);
|
||||
if (fullView != VK_NULL_HANDLE) {
|
||||
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) {
|
||||
vmaDestroyImage(s_allocator, image, allocation);
|
||||
}
|
||||
view = VK_NULL_HANDLE;
|
||||
fullView = VK_NULL_HANDLE;
|
||||
perMipViews.clear();
|
||||
image = VK_NULL_HANDLE;
|
||||
allocation = nullptr;
|
||||
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
extent = {0, 0};
|
||||
mipLevels = 1;
|
||||
sampledBaseMipLevel = 0;
|
||||
sampledLevelCount = 1;
|
||||
format = VK_FORMAT_UNDEFINED;
|
||||
aspect = VK_IMAGE_ASPECT_NONE;
|
||||
syncedTextureParamsVersion = 0;
|
||||
}
|
||||
|
||||
~TextureResource() {
|
||||
@@ -81,13 +98,15 @@ public:
|
||||
|
||||
TextureResource* SyncTextureAndGetDescriptor(
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
||||
void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout);
|
||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
|
||||
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
|
||||
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask);
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask,
|
||||
Uint32 baseMipLevel = 0, Uint32 levelCount = 1);
|
||||
|
||||
SizeT CollectGarbage();
|
||||
private:
|
||||
@@ -98,6 +117,9 @@ private:
|
||||
TextureUploadTarget uploadTarget,
|
||||
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
||||
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,
|
||||
TextureUploadTarget uploadTarget,
|
||||
TextureResource &outResource);
|
||||
@@ -107,6 +129,8 @@ private:
|
||||
SizeT& outByteSize,
|
||||
Uint32& outMipLevelCount);
|
||||
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);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
|
||||
@@ -19,6 +19,29 @@
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
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) {
|
||||
switch (layout) {
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
@@ -75,6 +98,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
namespace {
|
||||
static constexpr Uint32 kMaxProgramBindings = 16;
|
||||
static constexpr Uint32 kDescriptorSetsPerFrame = 64;
|
||||
static constexpr Uint kHiddenBlitProgramId = 0xFFFFFFF0u;
|
||||
static constexpr Uint kHiddenBlitVertexShaderId = 0xFFFFFFF1u;
|
||||
static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u;
|
||||
@@ -93,6 +118,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout* trackedLayout = nullptr;
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_NONE;
|
||||
IntVec2 extent = {0, 0};
|
||||
Uint32 mipLevel = 0;
|
||||
Uint32 mipLevelCount = 1;
|
||||
const char* label = nullptr;
|
||||
};
|
||||
|
||||
@@ -164,6 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const auto extent = swapchainObject.GetExtent();
|
||||
outBinding.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)};
|
||||
outBinding.mipLevel = 0;
|
||||
outBinding.mipLevelCount = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -182,7 +211,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
outBinding.image = resource->image;
|
||||
outBinding.trackedLayout = &resource->layout;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -269,15 +301,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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() {
|
||||
CreateInstance();
|
||||
CreateSurface();
|
||||
@@ -286,9 +309,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
CreateAllocator();
|
||||
|
||||
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>();
|
||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
|
||||
auto succeeded = false;
|
||||
succeeded = m_textureManager->Initialize(
|
||||
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue});
|
||||
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
|
||||
@@ -306,7 +341,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
m_pipelineFactory = MakeUnique<PipelineFactory>(m_device, m_config);
|
||||
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.");
|
||||
|
||||
m_samplerManager = MakeUnique<VkSamplerManager>();
|
||||
@@ -316,27 +351,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
succeeded = InitializeBlitResources();
|
||||
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
|
||||
|
||||
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
|
||||
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed.");
|
||||
succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator,
|
||||
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment,
|
||||
m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024,
|
||||
m_textureManager.get(), m_samplerManager.get());
|
||||
m_uniformManager = MakeUnique<UniformManager>();
|
||||
MOBILEGL_ASSERT(m_uniformManager != nullptr, "UniformDescriptorBinder creation failed.");
|
||||
succeeded = m_uniformManager->Initialize(
|
||||
m_device, &m_bufferManager, m_programFactory.get(),
|
||||
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, m_config.MaxFramesInFlight,
|
||||
kMaxProgramBindings, kDescriptorSetsPerFrame, m_textureManager.get(), m_samplerManager.get());
|
||||
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
|
||||
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);
|
||||
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.
|
||||
VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired),
|
||||
"Initialize, WaitAndAcquireNextImage");
|
||||
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
m_transientVertexIndexBuffersThisFrame.clear();
|
||||
|
||||
MGLOG_D("VulkanRenderer initialized");
|
||||
}
|
||||
@@ -345,7 +374,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VK_VERIFY(vkDeviceWaitIdle(m_device));
|
||||
|
||||
m_pipelineFactory.reset();
|
||||
m_programFactory.reset();
|
||||
ShutdownBlitResources();
|
||||
if (m_samplerManager) {
|
||||
m_samplerManager->Shutdown();
|
||||
@@ -356,24 +384,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_textureManager.reset();
|
||||
}
|
||||
m_vertexInputStateFactory.reset();
|
||||
for (auto& buffer : m_frameVertexUploadBuffers) {
|
||||
buffer.Destroy();
|
||||
}
|
||||
for (auto& buffer : m_frameIndexUploadBuffers) {
|
||||
buffer.Destroy();
|
||||
}
|
||||
m_frameVertexUploadBuffers.clear();
|
||||
m_frameVertexUploadHeads.clear();
|
||||
m_frameIndexUploadBuffers.clear();
|
||||
m_frameIndexUploadHeads.clear();
|
||||
m_deferredBufferReleases.clear();
|
||||
m_bufferManager.Shutdown();
|
||||
m_transientVertexIndexBuffersThisFrame.clear();
|
||||
|
||||
m_frameContext.Destroy(m_device, m_commandPool);
|
||||
|
||||
if (m_uniformDescriptorBinder) {
|
||||
m_uniformDescriptorBinder->Shutdown();
|
||||
m_uniformDescriptorBinder.reset();
|
||||
if (m_uniformManager) {
|
||||
m_uniformManager->Shutdown();
|
||||
m_uniformManager.reset();
|
||||
}
|
||||
m_programFactory.reset();
|
||||
|
||||
ShutdownSwapchain();
|
||||
m_renderPassManager.reset();
|
||||
@@ -392,7 +412,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
vkDestroyDevice(m_device, nullptr);
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
m_cmdDrawIndexedIndirectCount = nullptr;
|
||||
s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||
|
||||
if (m_surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
@@ -411,59 +431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_I("VulkanRenderer shut down completed");
|
||||
}
|
||||
|
||||
void VulkanRenderer::DeferDestroyBuffer(VkBufferObject& buffer) {
|
||||
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(
|
||||
Bool VulkanRenderer::UploadAndBindVertexBuffers(
|
||||
VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) {
|
||||
auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
|
||||
@@ -471,7 +439,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
|
||||
Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
|
||||
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
|
||||
|
||||
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
|
||||
const auto& attrs = vao.GetAllAttributes();
|
||||
@@ -491,32 +458,102 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
for (SizeT binding = 0; binding < bindingCount; ++binding) {
|
||||
const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding];
|
||||
const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey);
|
||||
|
||||
const auto sourceData = sourceBuffer->GetDataReadOnly();
|
||||
|
||||
const SizeT sourceSize = sourceBuffer->GetSize();
|
||||
VkDeviceSize& frameHead = m_frameVertexUploadHeads[frameIndex];
|
||||
const VkDeviceSize writeOffset = (frameHead + 0x0F) & ~VkDeviceSize(0x0F);
|
||||
const VkDeviceSize writeEnd = writeOffset + static_cast<VkDeviceSize>(sourceSize);
|
||||
if (!EnsureFrameUploadBufferCapacity(frameIndex, false, writeEnd, 4 * 1024 * 1024,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) {
|
||||
return false;
|
||||
MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer");
|
||||
auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex());
|
||||
MOBILEGL_ASSERT(sourceBufferShared != nullptr,
|
||||
"UploadAndBindVertexStreams failed to resolve shared source buffer");
|
||||
BufferSlice slice{};
|
||||
const Bool transientThisFrame =
|
||||
HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, sourceBufferShared.get());
|
||||
const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
|
||||
if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || transientThisFrame || isDirty) {
|
||||
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];
|
||||
if (!frameUploadBuffer.Upload(sourceData->data(), static_cast<VkDeviceSize>(sourceSize), writeOffset)) {
|
||||
MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding);
|
||||
return false;
|
||||
}
|
||||
|
||||
frameHead = writeEnd;
|
||||
vkBuffers[binding] = frameUploadBuffer.GetHandle();
|
||||
vkOffsets[binding] = writeOffset;
|
||||
vkBuffers[binding] = slice.buffer;
|
||||
vkOffsets[binding] = slice.offset;
|
||||
}
|
||||
|
||||
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
|
||||
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() {
|
||||
ShutdownBlitResources();
|
||||
|
||||
@@ -624,17 +661,17 @@ void main() {
|
||||
VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) {
|
||||
MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program 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 {
|
||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||
};
|
||||
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{
|
||||
.programHash = m_programFactory->ComputeHash(*m_blitResources.program, transformFlags),
|
||||
.programHash = programObj.hash,
|
||||
.vertexInputHash = 0,
|
||||
.pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(*m_blitResources.program),
|
||||
.pipelineLayout = programObj.pipelineLayout,
|
||||
.renderPass = renderPassEntry.renderPass,
|
||||
.subpass = 0,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
@@ -650,7 +687,7 @@ void main() {
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
.stages = &stages,
|
||||
.stages = &programObj.stages,
|
||||
.vertexInputState = &kEmptyVertexInputState
|
||||
};
|
||||
return m_pipelineFactory->GetOrCreatePipeline(payload);
|
||||
@@ -663,16 +700,14 @@ void main() {
|
||||
const RenderPassEntry& renderPassEntry) {
|
||||
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
|
||||
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(program, transformFlags);
|
||||
if (stages.empty()) {
|
||||
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
if (programObj.stages.empty()) {
|
||||
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags);
|
||||
|
||||
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
|
||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
auto pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(program);
|
||||
auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace);
|
||||
auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest);
|
||||
BlendFactor srcRGB = BlendFactor::One;
|
||||
@@ -683,9 +718,9 @@ void main() {
|
||||
auto mask = MG_State::pGLContext->GetColorMask();
|
||||
|
||||
PipelineFactory::PipelineCreatePayload payload {
|
||||
.programHash = programHash,
|
||||
.programHash = programObj.hash,
|
||||
.vertexInputHash = vertexInputHash,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
.pipelineLayout = programObj.pipelineLayout,
|
||||
.renderPass = renderPassEntry.renderPass,
|
||||
.subpass = 0,
|
||||
.topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode),
|
||||
@@ -706,28 +741,36 @@ void main() {
|
||||
(mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
|
||||
(mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) |
|
||||
(mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ),
|
||||
.stages = &stages,
|
||||
.stages = &programObj.stages,
|
||||
.vertexInputState = &vis.state
|
||||
};
|
||||
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();
|
||||
const auto& drawFbo =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
||||
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
|
||||
if (!frame.isCommandRecording) {
|
||||
m_frameContext.BeginCommandRecording();
|
||||
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
}
|
||||
|
||||
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;
|
||||
Bool hasSampledTextures = m_uniformDescriptorBinder->CollectSampledTextures(program, sampledTextures);
|
||||
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
|
||||
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
||||
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
||||
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
|
||||
@@ -800,7 +843,6 @@ void main() {
|
||||
activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||
|
||||
// Begin render pass, and handle clear
|
||||
|
||||
if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) {
|
||||
ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry);
|
||||
} else {
|
||||
@@ -814,10 +856,16 @@ void main() {
|
||||
|
||||
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());
|
||||
|
||||
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{};
|
||||
viewport.x = 0.0f;
|
||||
@@ -839,6 +887,7 @@ void main() {
|
||||
scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() };
|
||||
}
|
||||
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::Clear(GLbitfield mask) {
|
||||
@@ -875,7 +924,7 @@ void main() {
|
||||
Bool ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
resource->aspect);
|
||||
resource->aspect, 0, resource->mipLevels);
|
||||
MOBILEGL_ASSERT(ok,
|
||||
"MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST",
|
||||
texture.GetExternalIndex());
|
||||
@@ -908,7 +957,7 @@ void main() {
|
||||
ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, resource->image, resource->layout, sampledLayout,
|
||||
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,
|
||||
"MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout",
|
||||
texture.GetExternalIndex());
|
||||
@@ -1031,14 +1080,16 @@ void main() {
|
||||
writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform,
|
||||
sizeof(blitUniformData.surfaceTransform));
|
||||
|
||||
const auto samplerBindingOverride = UniformDescriptorBinder::SamplerBindingOverride{
|
||||
const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{
|
||||
.binding = m_blitResources.samplerBinding,
|
||||
.texture = sourceTexture.get(),
|
||||
.sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get()
|
||||
: m_blitResources.nearestSampler.get()),
|
||||
};
|
||||
const Bool bound = m_uniformDescriptorBinder->BindProgramUniformBuffers(
|
||||
frame.commandBuffer, *m_blitResources.program, m_frameContext.GetCurrentFrameIndex(),
|
||||
ProgramFactory::CompileOptionFlags blitTransformFlags = 0;
|
||||
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);
|
||||
MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed");
|
||||
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
|
||||
@@ -1069,7 +1120,7 @@ void main() {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
m_frameContext.BeginCommandRecording();
|
||||
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
}
|
||||
|
||||
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||
@@ -1138,7 +1189,7 @@ void main() {
|
||||
Bool ok = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask);
|
||||
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, 0, srcBinding.mipLevelCount);
|
||||
MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__);
|
||||
}
|
||||
|
||||
@@ -1156,19 +1207,19 @@ void main() {
|
||||
Bool ok = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
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__);
|
||||
}
|
||||
|
||||
VkImageBlit blitRegion{};
|
||||
blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
|
||||
blitRegion.srcSubresource.mipLevel = 0;
|
||||
blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel;
|
||||
blitRegion.srcSubresource.baseArrayLayer = 0;
|
||||
blitRegion.srcSubresource.layerCount = 1;
|
||||
blitRegion.srcOffsets[0] = {srcX0, srcY0, 0};
|
||||
blitRegion.srcOffsets[1] = {srcX1, srcY1, 1};
|
||||
blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask;
|
||||
blitRegion.dstSubresource.mipLevel = 0;
|
||||
blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel;
|
||||
blitRegion.dstSubresource.baseArrayLayer = 0;
|
||||
blitRegion.dstSubresource.layerCount = 1;
|
||||
blitRegion.dstOffsets[0] = {dstX0, dstY0, 0};
|
||||
@@ -1180,7 +1231,7 @@ void main() {
|
||||
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();
|
||||
|
||||
SetupDraw(frame, payload.mode, 0);
|
||||
@@ -1189,68 +1240,49 @@ void main() {
|
||||
|
||||
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();
|
||||
|
||||
SetupDraw(frame, payload.mode, 0);
|
||||
SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer,
|
||||
&payload.indexBufferView);
|
||||
|
||||
if (!frame.isCommandRecording) {
|
||||
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;
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
|
||||
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
vkCmdBindIndexBuffer(commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType);
|
||||
vkCmdDrawIndexed(commandBuffer, static_cast<Uint32>(payload.count), 1, 0,
|
||||
static_cast<Int32>(payload.baseVertex), 0);
|
||||
vkCmdDrawIndexed(commandBuffer,
|
||||
payload.params.indexCount,
|
||||
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() {
|
||||
@@ -1298,13 +1330,8 @@ void main() {
|
||||
result = VK_SUCCESS;
|
||||
}
|
||||
VK_VERIFY(result, "Present, vkAcquireNextImageKHR");
|
||||
CollectDeferredBufferReleases(m_frameContext.GetCurrentFrameIndex());
|
||||
if (m_frameContext.GetCurrentFrameIndex() < m_frameVertexUploadHeads.size()) {
|
||||
m_frameVertexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
|
||||
}
|
||||
if (m_frameContext.GetCurrentFrameIndex() < m_frameIndexUploadHeads.size()) {
|
||||
m_frameIndexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
|
||||
}
|
||||
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
m_transientVertexIndexBuffersThisFrame.clear();
|
||||
}
|
||||
|
||||
void VulkanRenderer::CreateInstance() {
|
||||
@@ -1601,19 +1628,60 @@ void main() {
|
||||
ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions);
|
||||
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.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||
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"));
|
||||
if (m_cmdDrawIndexedIndirectCount == nullptr) {
|
||||
m_cmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
||||
if (s_vkCmdDrawIndexedIndirectCount == nullptr) {
|
||||
s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
||||
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount"));
|
||||
}
|
||||
if (m_drawIndirectCountExtensionEnabled && m_cmdDrawIndexedIndirectCount == nullptr) {
|
||||
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing");
|
||||
if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount == nullptr) {
|
||||
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.");
|
||||
|
||||
// Queues
|
||||
@@ -1658,11 +1726,6 @@ void main() {
|
||||
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() {
|
||||
VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
@@ -1844,12 +1907,12 @@ void main() {
|
||||
m_frameContext.GetCurrent().isCommandRecording = false;
|
||||
m_frameContext.GetCurrent().hasCommandBufferRecorded = false;
|
||||
}
|
||||
m_deferredBufferReleases.clear();
|
||||
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
|
||||
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);
|
||||
const Bool okArena = m_bufferManager.RecreateTransientArenas(m_frameContext.GetFrameCount());
|
||||
MOBILEGL_ASSERT(okArena, "RecreateSwapchain: buffer manager transient arena initialization failed");
|
||||
if (m_frameContext.GetFrameCount() > 0) {
|
||||
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
m_transientVertexIndexBuffersThisFrame.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const {
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
#include "PipelineFactory.h"
|
||||
#include "ProgramFactory.h"
|
||||
#include "SwapchainObject.h"
|
||||
#include "UniformDescriptorBinder.h"
|
||||
#include "UniformManager.h"
|
||||
#include "VertexInputStateFactory.h"
|
||||
#include "VkBufferObject.h"
|
||||
#include "VkBufferManager.h"
|
||||
#include "VkClearManager.h"
|
||||
#include "VkRenderPassManager.h"
|
||||
#include "VkSamplerManager.h"
|
||||
@@ -34,25 +35,55 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class DrawSetupAspect: Uint8 {
|
||||
FramebufferObject = 1 << 0,
|
||||
VertexArrayObject = 1 << 1,
|
||||
UniformBuffer = 1 << 2,
|
||||
VertexBuffer = 1 << 3,
|
||||
IndexBuffer = 1 << 4,
|
||||
Viewport = 1 << 5,
|
||||
Scissor = 1 << 6,
|
||||
FramebufferObject = 1 << 0,
|
||||
VertexArrayObject = 1 << 1,
|
||||
UniformBuffer = 1 << 2,
|
||||
VertexBuffer = 1 << 3,
|
||||
IndexBuffer = 1 << 4,
|
||||
IndirectDrawBuffer = 1 << 5,
|
||||
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;
|
||||
GLint first = 0;
|
||||
GLsizei count = 0;
|
||||
DrawCmdParam params;
|
||||
};
|
||||
|
||||
struct DrawElementCmd: public DrawArrayCmd {
|
||||
struct IndexBufferView {
|
||||
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||
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 {
|
||||
@@ -78,7 +109,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void Initialize();
|
||||
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,
|
||||
const RenderPassEntry& compatibleRenderPassEntry);
|
||||
|
||||
@@ -86,9 +118,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void DrawArrays(const DrawArrayCmd& payload);
|
||||
void DrawElements(const DrawElementCmd& payload);
|
||||
void MultiDrawElements(const Vector<DrawElementCmd>& payloads);
|
||||
void DrawArrays(const DrawCmd& payload);
|
||||
void DrawElements(const DrawIndexedCmd& payload);
|
||||
void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
|
||||
void Present();
|
||||
|
||||
const PhysicalDevice& GetPhysicalDevice() const;
|
||||
@@ -131,26 +163,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
VkQueue m_presentQueue = VK_NULL_HANDLE;
|
||||
Bool m_drawIndirectCountExtensionEnabled = false;
|
||||
Bool m_indexTypeUint8ExtensionEnabled = false;
|
||||
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
|
||||
VkDeviceSize offset, VkBuffer countBuffer,
|
||||
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
|
||||
Uint32 stride);
|
||||
PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr;
|
||||
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
|
||||
Vector<VkBufferObject> m_frameVertexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameVertexUploadHeads;
|
||||
Vector<VkBufferObject> m_frameIndexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameIndexUploadHeads;
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
VkBufferManager m_bufferManager;
|
||||
Vector<const MG_State::GLState::BufferObject*> m_transientVertexIndexBuffersThisFrame;
|
||||
|
||||
Uint m_imageIndexAcquired = 0;
|
||||
FrameContext m_frameContext;
|
||||
|
||||
UniquePtr<PipelineFactory> m_pipelineFactory;
|
||||
UniquePtr<ProgramFactory> m_programFactory;
|
||||
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
|
||||
UniquePtr<UniformManager> m_uniformManager;
|
||||
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
||||
UniquePtr<VkClearManager> m_clearManager;
|
||||
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
||||
@@ -169,7 +199,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void DestroyAllocator();
|
||||
void CreateSwapchain();
|
||||
void CreateCommandPool();
|
||||
void CreateFrameContexts();
|
||||
|
||||
VkPipeline GetOrCreatePipeline(
|
||||
GLenum mode,
|
||||
@@ -177,11 +206,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
const RenderPassEntry& renderPassEntry);
|
||||
|
||||
void DeferDestroyBuffer(VkBufferObject& buffer);
|
||||
void CollectDeferredBufferReleases(Uint32 frameIndex);
|
||||
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset,
|
||||
VkDeviceSize minCapacity, VkBufferUsageFlags usage);
|
||||
Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
|
||||
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
|
||||
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
const IndexBufferView* pIndexBufferView = nullptr);
|
||||
Bool InitializeBlitResources();
|
||||
void ShutdownBlitResources();
|
||||
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
|
||||
@@ -216,8 +244,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
||||
const PhysicalDevice& compareWithDevice,
|
||||
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_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
static Bool CheckValidationLayerSupport();
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct VulkanRendererConfig {
|
||||
Uint32 MaxFramesInFlight = 2;
|
||||
String AppName = "MobileGL-VulkanRenderer";
|
||||
Version Version = MG_Config::CoreVersion;
|
||||
MobileGL::Version Version = MG_Config::CoreVersion;
|
||||
Uint64 CacheVersion = MG_Config::CacheVersion;
|
||||
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) {
|
||||
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
|
||||
auto& textureObject =
|
||||
TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget);
|
||||
TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
|
||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject;
|
||||
return textureObject;
|
||||
} else {
|
||||
@@ -554,9 +554,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
textureObject->SetInternalFormat(textureInternalFormat);
|
||||
|
||||
// if isProxy, no more pixel transfer needed below
|
||||
if (isProxy) return;
|
||||
|
||||
const void* originalPixels = pixels;
|
||||
|
||||
// PBO
|
||||
@@ -671,9 +668,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str());
|
||||
textureObject->SetInternalFormat(textureInternalFormat);
|
||||
|
||||
// if isProxy, no more pixel transfer needed below
|
||||
if (isProxy) return;
|
||||
|
||||
const void* originalPixels = pixels;
|
||||
|
||||
// PBO
|
||||
@@ -797,7 +791,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
||||
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
||||
auto& textureObject =
|
||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
||||
: bindingSlot.GetBoundObject();
|
||||
|
||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||
@@ -875,7 +869,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
||||
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
||||
auto& textureObject =
|
||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
||||
: bindingSlot.GetBoundObject();
|
||||
|
||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||
@@ -967,7 +961,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
||||
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
||||
auto& textureObject =
|
||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
||||
: bindingSlot.GetBoundObject();
|
||||
|
||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||
@@ -1039,6 +1033,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
"pname is not a valid texture level parameter."));
|
||||
return;
|
||||
}
|
||||
MGLOG_D("returned %u",*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);
|
||||
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
||||
auto& textureObject =
|
||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
||||
: bindingSlot.GetBoundObject();
|
||||
|
||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||
@@ -1384,7 +1379,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
||||
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
||||
auto& textureObject =
|
||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
||||
: bindingSlot.GetBoundObject();
|
||||
|
||||
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 "
|
||||
"(shaderType=%u, wordCount=%zu)",
|
||||
m_externalIndex, i, shaderType, spv.size());
|
||||
SpvcSession session(spv);
|
||||
SpvcSession session(spv, SessionUsageBit::Reflection);
|
||||
auto result = session.ParseMetaData();
|
||||
if (result < 0) {
|
||||
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?" : ""));
|
||||
m_uniformSizesInBytes.clear();
|
||||
m_uniformOffsets.clear();
|
||||
m_uboScratch.clear();
|
||||
m_globalUboScratch.clear();
|
||||
continue;
|
||||
} else {
|
||||
auto& meta = session.GetMetadata();
|
||||
auto size = meta.uboSize;
|
||||
auto size = meta.globalUboSize;
|
||||
MGLOG_D("ProgramObject %u: GenerateBinary - SPIR-V meta: uboSize=%zu plainUniformCount=%zu "
|
||||
"plainUniformOffsets=%zu",
|
||||
m_externalIndex, meta.uboSize, meta.plainUniformMemberSizesInBytes.size(),
|
||||
m_externalIndex, meta.globalUboSize, meta.plainUniformMemberSizesInBytes.size(),
|
||||
meta.plainUniformOffsetsInUBO.size());
|
||||
m_uboScratch.resize(size);
|
||||
m_globalUboScratch.resize(size);
|
||||
m_uniformOffsets.resize(m_maxUniformLocation + 1);
|
||||
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
|
||||
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]; }
|
||||
const String& GetAttribName(Uint index) const { return m_attribs[index]; }
|
||||
void* MapUBO() { return m_uboScratch.data(); }
|
||||
const void* GetUBOData() const { return m_uboScratch.data(); }
|
||||
Uint GetUBOSize() const { return static_cast<Uint>(m_uboScratch.size()); }
|
||||
void* MapUBO() { return m_globalUboScratch.data(); }
|
||||
const void* GetUBOData() const { return m_globalUboScratch.data(); }
|
||||
Uint GetUBOSize() const { return static_cast<Uint>(m_globalUboScratch.size()); }
|
||||
|
||||
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
||||
m_uniformSamplerOrImageUnitIndex[location] = unit;
|
||||
@@ -121,9 +121,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
|
||||
// const UnorderedMap<String, Uint>& GetAttribLocationMap() const { return
|
||||
// m_attribLocation; }
|
||||
|
||||
private:
|
||||
void DoReflection();
|
||||
void GenerateBinary();
|
||||
@@ -142,8 +139,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
UnorderedMap<String, Uint> m_explicitAttribLocations;
|
||||
Vector<String> m_attribs;
|
||||
Vector<GLenum> m_attribTypes;
|
||||
// For SpvcSession::SetVertexAttribLocation()
|
||||
// UnorderedMap<String, Uint> m_attribLocation;
|
||||
|
||||
// FragData (Frag out)
|
||||
UnorderedMap<String, Uint> m_explicitFragDataLocation;
|
||||
@@ -162,13 +157,15 @@ namespace MobileGL::MG_State::GLState {
|
||||
// Let's define UniformBlockIndex == the order at glslang getUniformBlock()
|
||||
// aka `i = glGetUniformBlockIndex(prog, "BlockName")` implies:
|
||||
// `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;
|
||||
Vector<Int> m_uniformBlockBinding;
|
||||
|
||||
// Need to be reflected after linking of SPIR-V binary
|
||||
Vector<Uint> m_uniformOffsets;
|
||||
Vector<Uint> m_uniformSizesInBytes;
|
||||
Vector<Uint8> m_uboScratch;
|
||||
Vector<Uint8> m_globalUboScratch;
|
||||
|
||||
Uint m_activeUniformCount = 0;
|
||||
Uint m_maxUniformLocation = 0;
|
||||
|
||||
@@ -85,6 +85,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
return m_indexBufferBindingSlot;
|
||||
}
|
||||
|
||||
const BindingSlot<BufferObject>& VertexArrayObject::GetIndexBufferBindingSlot() const {
|
||||
return m_indexBufferBindingSlot;
|
||||
}
|
||||
|
||||
|
||||
const VertexAttribute& VertexArrayObject::GetAttribute(Uint index) const {
|
||||
static VertexAttribute emptyAttr;
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return emptyAttr;
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace MobileGL {
|
||||
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
|
||||
|
||||
BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
|
||||
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
|
||||
|
||||
const VertexAttribute& GetAttribute(Uint index) const;
|
||||
const Array<VertexAttribute, MAX_VERTEX_ATTRIBS>& GetAllAttributes() const;
|
||||
|
||||
@@ -25,7 +25,8 @@ protected:
|
||||
};
|
||||
|
||||
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& 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& 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]);
|
||||
|
||||
writeSlot.Bind(bufObj);
|
||||
@@ -80,7 +82,9 @@ TEST_F(BufferTest, PingPong) {
|
||||
|
||||
TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
|
||||
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
|
||||
for (SizeT idx : indices) {
|
||||
@@ -104,7 +108,8 @@ TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
|
||||
|
||||
TEST_F(BufferTest, AcquireMemory) {
|
||||
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]);
|
||||
slot.Bind(bufObj);
|
||||
Vector<Int> initData{10, 20, 30, 40, 50};
|
||||
@@ -132,7 +137,8 @@ TEST_F(BufferTest, AcquireMemory) {
|
||||
|
||||
TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
|
||||
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]);
|
||||
slot.Bind(bufObj);
|
||||
Vector<Int> initData{10, 20, 30, 40, 50};
|
||||
@@ -160,7 +166,8 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
|
||||
|
||||
TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
|
||||
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]);
|
||||
slot.Bind(bufObj);
|
||||
|
||||
@@ -208,7 +215,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
|
||||
auto& srcSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead);
|
||||
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]);
|
||||
srcSlot.Bind(srcObj);
|
||||
|
||||
@@ -218,7 +226,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
|
||||
DataPtr srcPtr{.data = srcData.data(), .size = srcSize};
|
||||
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]);
|
||||
dstSlot.Bind(dstObj);
|
||||
|
||||
@@ -249,7 +258,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
|
||||
|
||||
TEST_F(BufferTest, WriteWhileMapped) {
|
||||
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]);
|
||||
slot.Bind(bufObj);
|
||||
|
||||
@@ -280,7 +290,8 @@ TEST_F(BufferTest, WriteWhileMapped) {
|
||||
|
||||
TEST_F(BufferTest, PartialUpdate) {
|
||||
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]);
|
||||
slot.Bind(bufObj);
|
||||
|
||||
@@ -308,7 +319,8 @@ TEST_F(BufferTest, PartialUpdate) {
|
||||
}
|
||||
|
||||
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 bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]);
|
||||
slot.Bind(bufObj);
|
||||
@@ -322,7 +334,7 @@ using namespace MobileGL::MG_Impl::GLImpl;
|
||||
|
||||
class GeneralBufferTest : public ::testing::Test {
|
||||
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 buffer;
|
||||
|
||||
@@ -191,7 +191,7 @@ TEST_F(ProgramTest, CompileAndLink) {
|
||||
String source;
|
||||
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;
|
||||
spvcSession.CreateOptions(&options);
|
||||
@@ -926,7 +926,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) {
|
||||
char* pSrcVertIn = nullptr;
|
||||
const char* needle = "layout(location = 2) in vec2 UV0;";
|
||||
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;
|
||||
spvcSession.CreateOptions(&options);
|
||||
|
||||
@@ -987,7 +987,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) {
|
||||
char* pSrcfragOut = nullptr;
|
||||
const char* needle = "layout(location = 7) out vec4 fragColor;";
|
||||
// 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;
|
||||
spvcSession.CreateOptions(&options);
|
||||
|
||||
@@ -1063,6 +1063,7 @@ float fog_cylindrical_distance(vec3 pos) {
|
||||
return max(distXZ, distY);
|
||||
}
|
||||
|
||||
uniform float fTime;
|
||||
|
||||
layout(std140) uniform Globals {
|
||||
ivec3 CameraBlockPos;
|
||||
@@ -1165,7 +1166,7 @@ vec4 sampleRGSS(sampler2D source, vec2 uv, vec2 pixelSize) {
|
||||
|
||||
void main() {
|
||||
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
|
||||
if (color.a < ALPHA_CUTOUT) {
|
||||
discard;
|
||||
@@ -1207,9 +1208,8 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) {
|
||||
|
||||
auto programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
auto& spirvs = programObject->GetGeneratedSpirv();
|
||||
auto& fragSpirv = spirvs[0]; // 0 - fragment, 1 - vertex
|
||||
char* pSrcfragOut = nullptr;
|
||||
MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv);
|
||||
auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)];
|
||||
MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
||||
spvc_compiler_options options;
|
||||
spvcSession.CreateOptions(&options);
|
||||
|
||||
@@ -1221,5 +1221,5 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) {
|
||||
|
||||
const char* result = nullptr;
|
||||
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());
|
||||
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) {
|
||||
@@ -278,7 +278,7 @@ TEST_F(ProgramUtilTest, DecompProgram) {
|
||||
|
||||
Vector<SpvcSession> sessions(spirvs.size());
|
||||
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) {
|
||||
@@ -439,7 +439,7 @@ TEST_F(ProgramUtilTest, CompileAndLinkBlitProgram) {
|
||||
auto spirvs = bin_res.value();
|
||||
Vector<SpvcSession> sessions(spirvs.size());
|
||||
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) {
|
||||
|
||||
@@ -21,7 +21,8 @@ using namespace MobileGL;
|
||||
class VertexArrayTest : public ::testing::Test {
|
||||
protected:
|
||||
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]);
|
||||
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex).Bind(vbo);
|
||||
|
||||
@@ -39,7 +40,8 @@ protected:
|
||||
};
|
||||
|
||||
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 vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]);
|
||||
|
||||
@@ -55,7 +57,8 @@ TEST_F(VertexArrayTest, GenerateAndBindVAO) {
|
||||
}
|
||||
|
||||
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]);
|
||||
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
|
||||
|
||||
@@ -86,11 +89,13 @@ TEST_F(VertexArrayTest, VertexAttributeSetup) {
|
||||
}
|
||||
|
||||
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]);
|
||||
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]);
|
||||
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);
|
||||
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]);
|
||||
MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(newEbo);
|
||||
ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), newEbo);
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
|
||||
@@ -125,7 +132,8 @@ TEST_F(VertexArrayTest, DeleteVAO) {
|
||||
|
||||
TEST_F(VertexArrayTest, ValidateNamesAndObjects) {
|
||||
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++) {
|
||||
ASSERT_TRUE(MobileGL::MG_State::pGLContext->ValidateVertexArrayName(vaoNames[i]));
|
||||
@@ -144,12 +152,14 @@ TEST_F(VertexArrayTest, ValidateNamesAndObjects) {
|
||||
}
|
||||
|
||||
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]);
|
||||
MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]);
|
||||
|
||||
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]);
|
||||
|
||||
Vector<float> normals(12, 0.5f);
|
||||
@@ -187,7 +197,8 @@ TEST_F(VertexArrayTest, MultipleAttributes) {
|
||||
}
|
||||
|
||||
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 vao2 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]);
|
||||
|
||||
@@ -216,11 +227,9 @@ using namespace MobileGL::MG_Impl::GLImpl;
|
||||
|
||||
class GeneralVertexArrayTest : public ::testing::Test {
|
||||
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 {
|
||||
delete MG_State::pGLContext;
|
||||
MG_State::pGLContext = nullptr;
|
||||
}
|
||||
|
||||
GLuint CreateVAO() {
|
||||
|
||||
@@ -11,65 +11,201 @@
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
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);
|
||||
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);
|
||||
static spvc_basetype MapReflectToSpvcBasetype(const SpvReflectBlockVariable& member) {
|
||||
if (!member.type_description) return SPVC_BASETYPE_UNKNOWN;
|
||||
auto flags = member.type_description->type_flags;
|
||||
auto width = member.numeric.scalar.width;
|
||||
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) {
|
||||
std::swap(this->usage, that.usage);
|
||||
std::swap(this->context, that.context);
|
||||
std::swap(this->compiler, that.compiler);
|
||||
std::swap(this->ir, that.ir);
|
||||
std::swap(this->compiler_options, that.compiler_options);
|
||||
std::swap(this->resources, that.resources);
|
||||
std::swap(this->reflectModule, that.reflectModule);
|
||||
std::swap(this->reflectModuleValid, that.reflectModuleValid);
|
||||
}
|
||||
|
||||
SpvcSession& SpvcSession::operator=(SpvcSession&& that) {
|
||||
std::swap(this->usage, that.usage);
|
||||
std::swap(this->context, that.context);
|
||||
std::swap(this->compiler, that.compiler);
|
||||
std::swap(this->ir, that.ir);
|
||||
std::swap(this->compiler_options, that.compiler_options);
|
||||
std::swap(this->resources, that.resources);
|
||||
std::swap(this->reflectModule, that.reflectModule);
|
||||
std::swap(this->reflectModuleValid, that.reflectModuleValid);
|
||||
return *this;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
spvc_result SpvcSession::SetOptions(spvc_compiler_options options) {
|
||||
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
|
||||
compiler_options = options;
|
||||
return spvc_compiler_install_compiler_options(compiler, options);
|
||||
}
|
||||
|
||||
Vector<InterfaceVariable> SpvcSession::GetShaderInterface(spvc_resource_type resource_type) const {
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
size_t count = 0;
|
||||
spvc_resources_get_resource_list_for_type(resources, resource_type, &list, &count);
|
||||
if (usage & SessionUsageBit::Transpile) {
|
||||
// SPIRV-Cross path
|
||||
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;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) {
|
||||
continue;
|
||||
switch (resource_type) {
|
||||
case SPVC_RESOURCE_TYPE_STAGE_INPUT: {
|
||||
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);
|
||||
}
|
||||
|
||||
InterfaceVariable var;
|
||||
var.name = list[i].name;
|
||||
var.location = spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationLocation);
|
||||
variables.push_back(var);
|
||||
break;
|
||||
}
|
||||
case SPVC_RESOURCE_TYPE_STAGE_OUTPUT: {
|
||||
uint32_t count = 0;
|
||||
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());
|
||||
return variables;
|
||||
}
|
||||
|
||||
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
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
@@ -80,8 +216,6 @@ namespace MobileGL {
|
||||
auto& resource = list[i];
|
||||
auto it = location.find(resource.name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -89,59 +223,99 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
spvc_result SpvcSession::Compile(const char** result) {
|
||||
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
|
||||
SPVC_CHK_INIT
|
||||
SPVC_CHK_RESULT(spvc_compiler_compile(compiler, result));
|
||||
// SPVC_CHK_RESULT(ParseMetaData());
|
||||
SPVC_CHK_RETURN
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
size_t count = 0;
|
||||
uint32_t bindingCount = 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,
|
||||
&list, &count);)
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < bindingCount; ++i) {
|
||||
auto* binding = bindings[i];
|
||||
if (binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) continue;
|
||||
if (strcmp(binding->type_description->type_name, GLOBAL_UBO_NAME) != 0) 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.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);
|
||||
auto& block = binding->block;
|
||||
metadata.globalUboSize = block.size;
|
||||
|
||||
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;
|
||||
for (uint32_t j = 0; j < block.member_count; ++j) {
|
||||
auto& member = block.members[j];
|
||||
metadata.plainUniformOffsetsInUBO[member.name] = member.offset;
|
||||
metadata.plainUniformMemberSizesInBytes[member.name] = member.size;
|
||||
|
||||
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);
|
||||
// auto dim = spvc_type_get_num_array_dimensions(type);
|
||||
metadata.plainUniformMemberTypes[memberName] = {
|
||||
.basetype = basetype,
|
||||
.vectorSize = vectorSize,
|
||||
.matCol = matCol,
|
||||
};
|
||||
}
|
||||
SPVC_CHK_RETURN
|
||||
Uint32 vectorSize = member.numeric.vector.component_count;
|
||||
if (vectorSize == 0) vectorSize = 1;
|
||||
Uint32 matCol = member.numeric.matrix.column_count;
|
||||
if (matCol == 0) matCol = 1;
|
||||
|
||||
metadata.plainUniformMemberTypes[member.name] = {
|
||||
.basetype = MapReflectToSpvcBasetype(member),
|
||||
.vectorSize = vectorSize,
|
||||
.matCol = matCol,
|
||||
};
|
||||
}
|
||||
return SPVC_SUCCESS;
|
||||
}
|
||||
// This means this spv binary does not have
|
||||
// auto-generated UBO in it
|
||||
return SPVC_ERROR_INVALID_SPIRV;
|
||||
}
|
||||
|
||||
@@ -150,11 +324,17 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
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() {
|
||||
spvc_context_destroy(context);
|
||||
if (reflectModuleValid) {
|
||||
spvReflectDestroyShaderModule(&reflectModule);
|
||||
}
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <spirv_reflect.h>
|
||||
#include "Types.h"
|
||||
|
||||
#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 {
|
||||
UnorderedMap<String, unsigned> plainUniformOffsetsInUBO;
|
||||
UnorderedMap<String, SizeT> plainUniformMemberSizesInBytes;
|
||||
UnorderedMap<String, SpvcType> plainUniformMemberTypes;
|
||||
SizeT uboSize = 0;
|
||||
SizeT globalUboSize = 0;
|
||||
};
|
||||
|
||||
class SpvcSession {
|
||||
public:
|
||||
SpvcSession() {}
|
||||
|
||||
explicit SpvcSession(const Vector<unsigned int>& spirv);
|
||||
explicit SpvcSession(const Vector<unsigned int>& spirv,
|
||||
Flags<SessionUsageBit> usage);
|
||||
|
||||
SpvcSession(SpvcSession&) = delete;
|
||||
|
||||
@@ -90,12 +97,19 @@ namespace MobileGL {
|
||||
spvc_result ParseMetaData();
|
||||
|
||||
private:
|
||||
Flags<SessionUsageBit> usage;
|
||||
|
||||
// SPIRV-Cross state (used when Transpile flag is set)
|
||||
spvc_context context = nullptr;
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler = nullptr;
|
||||
spvc_compiler_options compiler_options = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
|
||||
// SPIRV-Reflect state (used when only Reflection flag is set)
|
||||
SpvReflectShaderModule reflectModule = {};
|
||||
bool reflectModuleValid = false;
|
||||
|
||||
SpvcMetadata metadata;
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
Reference in New Issue
Block a user