Compare commits

...
Author SHA1 Message Date
BZLZHH 4bf9c1cae6 [Perf] (MG_Backend/DirectVulkanTMP): Optimize buffer uploads with staging ring + batched copies. 2026-02-21 14:04:05 +08:00
BZLZHH c121328c98 [Feat] (MG_Backend/DirectVulkanTMP): Migrate to Vulkan 1.3 and use dynamic rendering. 2026-02-21 13:17:05 +08:00
BZLZHH 5fe5aae138 [Perf] (MG_Backend/DirectVulkan): Batch layout transitions and Reuse staging buffers. 2026-02-20 22:04:50 +08:00
BZLZHH d2485a3fbe [Improvement] (MG_Backend/DirectVulkanTMP): Rename current DirectVulkan to DirectVulkanTMP. 2026-02-20 16:33:26 +08:00
BZLZHH 3fe0999550 [Misc] (MG_Backend/DirectVulkan): Remove "VulkanRenerer". 2026-02-20 16:27:02 +08:00
BZLZHH 39eb6a9026 [Feat] (MG_Backend/DirectVulkan|MG_Util): Get Vulkan device info. 2026-02-20 16:21:28 +08:00
BZLZHH 99c1697fba Merge branch 'dev' into Feat/Backend-Direct-Vulkan-TMP 2026-02-20 15:27:21 +08:00
BZLZHH 5db5545b33 Merge branch 'dev' into Feat/Backend-Direct-Vulkan-TMP 2026-02-19 02:04:57 +08:00
BZLZHH e63eebf425 [Fix] (MG_Backend/DirectVulkan): Correct viewport setting. 2026-02-16 18:29:03 +08:00
BZLZHH 03d3fcdc66 [Feat|Fix] (MG_Backend): Implement BlitFramebuffer & DrawElementsBaseVertex. 2026-02-16 17:12:33 +08:00
BZLZHH 40fc317f69 [Fix] (MG_Backend/DirectVulkan): Add shader position transforms & correct namespace name. 2026-02-16 14:06:34 +08:00
BZLZHH 3bafdee45d [Feat] (MG_Backend/DirectVulkan): Implement DV::TmpImpl and use. 2026-02-11 02:01:08 +08:00
BZLZHH 188275c004 Merge branch 'Feat/Backend-Direct-Vulkan' of github.com:MobileGL-Dev/MobileGL into Feat/Backend-Direct-Vulkan 2026-02-09 21:03:10 +08:00
swung0x48 cd3ff1f881 [Feat] (MG_Backend/DirectVulkan/ProgramManager): impelement CreatePipelineShaderStages/GetPipelineShaderStages 2026-02-09 21:02:27 +08:00
swung0x48 6c10876532 [Fix] (MG_Backend/DirectVulkan): fix file header 2026-02-09 20:51:36 +08:00
swung0x48 ce8a2356d7 [Fix] (MG_Test/Program): fix hardcoded stage-index relationship 2026-02-09 20:30:22 +08:00
BZLZHH 37f26d1bb1 [Misc] (gitignore): Ignore /build_* 2026-02-09 18:22:20 +08:00
swung0x48 22bc4fbbc9 [Feat] (MG_Backend/DirectVulkan): ProgramManager - hash program object 2026-02-09 16:18:50 +08:00
swung0x48 18941f9e83 [Feat] (MobileGL/Config): add CacheVersion 2026-02-09 14:43:29 +08:00
swung0x48 0b332bdc58 [Chore] (3rdparty): add xxHash 2026-02-09 13:55:05 +08:00
BZLZHH 2abdebcd96 [Fix] (MG_Backend/DirectVulkan): Acquire next frame image only at the start of frame. 2026-02-08 18:56:25 +08:00
BZLZHH e39af3b940 [Fix|Refactor] (MG_Backend/DirectVulkan): Fix incorrect vkAcquireNextImageKHR call. MOBILEGL_ASSERT_VK -> VK_VERIFY. 2026-02-08 16:28:12 +08:00
BZLZHH 3b2c2028b9 [Feat] (MG_Backend/DirectVulkan): Present frame in eglSwapBuffers. 2026-02-08 14:58:42 +08:00
BZLZHH 55020a1e86 [Improvement] (MG_Backend/DirectVulkan): Unify error handling. 2026-02-08 14:48:04 +08:00
swung0x48 6d4ddc47f6 [Fix] (MG_Backend/DirectVulkan): request Vulkan 1.1 (to make renderdoc happy) 2026-02-08 13:47:27 +08:00
swung0x48 2aff5dc650 [Fix] (MG_Impl/EGL/EGLForVulkan): use GL semantics for demo shader 2026-02-08 13:31:51 +08:00
swung0x48 7aa7b82628 [Fix] (buildsystem): fix build on Android 2026-02-08 11:51:14 +08:00
swung0x48 fd530156a6 [Fix] (buildsystem): fix build on Windows 2026-02-08 10:00:08 +08:00
BZLZHH 56c98041e3 [Misc] (MG_Backend/DirectVulkan): Prioritize MAILBOX/IMMEDIATE present modes. 2026-02-08 01:09:12 +08:00
BZLZHH 6eeb67f08b Merge branch 'dev' into Feat/Backend-Direct-Vulkan 2026-02-07 12:32:16 +08:00
BZLZHH 9a6cf7fc7f Merge branch 'dev' into Feat/Backend-Direct-Vulkan 2026-02-07 11:41:11 +08:00
BZLZHH 938b92277f [Fix] (MG_State/ProgramState): Correct compiling flags in ShaderObject::Compile 2026-02-01 09:23:25 +08:00
BZLZHH 419ffb3b5e [Fix] (MG_Backend/DirectVulkan): Use VK_KHR_ANDROID_SURFACE only on Android. 2026-02-01 00:17:03 +08:00
BZLZHH ad7d145ece [Fix] (MG_Backend/DirectVulkan): Avoid directly using ANativeWindow in abstracted layers. 2026-02-01 00:05:50 +08:00
BZLZHH 516566321d [Fix] (MG_Backend/DirectVulkan): Fix compiling error on linux. 2026-01-31 23:06:58 +08:00
BZLZHH 7918300848 [Chore] (...): Run scripts. 2026-01-31 22:58:22 +08:00
BZLZHH 00679642d6 [Feat] (MG_Backend/DirectVulkan): Basic Vulkan renderer and EGL impl for Vulkan. 2026-01-31 22:51:24 +08:00
BZLZHH 4769a9cf7b [Chore] (MG_Config): Use DirectVulkan backend. 2026-01-31 16:49:37 +08:00
BZLZHH 9f9e955b92 [Chore] (CI): Run workflows for Feat/Backend-Direct-Vulkan 2026-01-31 16:45:39 +08:00
BZLZHH 85f7c8f4a6 [Feat] (MG_Backend/DirectVulkan): Add DirectVulkan backend. 2026-01-31 16:44:08 +08:00
37 changed files with 5639 additions and 17 deletions
+2 -1
View File
@@ -5,6 +5,7 @@ on:
branches:
- dev
- Feat/Backend-Direct-GLES
- Feat/Backend-Direct-Vulkan
jobs:
benchmark:
@@ -34,7 +35,7 @@ jobs:
- name: Install clang-20
run: |
sudo apt-get update
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev
- name: Show installed toolchain
run: |
+2 -1
View File
@@ -5,6 +5,7 @@ on:
branches:
- dev
- Feat/Backend-Direct-GLES
- Feat/Backend-Direct-Vulkan
jobs:
test:
@@ -34,7 +35,7 @@ jobs:
- name: Install clang-20
run: |
sudo apt-get update
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev
- name: Show installed toolchain
run: |
+1
View File
@@ -14,3 +14,4 @@ MobileGLCodeManager
.vscode
.clangd
MobileGL/MG_Test/build
/build_*
+3
View File
@@ -13,3 +13,6 @@
[submodule "3rdparty/tracy"]
path = 3rdparty/tracy
url = https://github.com/wolfpld/tracy.git
[submodule "3rdparty/xxHash"]
path = 3rdparty/xxHash
url = https://github.com/Cyan4973/xxHash.git
Vendored Submodule
+1
Submodule 3rdparty/xxHash added at 1d7b2a9d21
+31 -5
View File
@@ -108,6 +108,10 @@ set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE)
add_subdirectory(3rdparty/glslang)
add_subdirectory(3rdparty/SPIRV-Cross)
set(XXHASH_BUILD_XXHSUM OFF)
option(BUILD_SHARED_LIBS OFF)
add_subdirectory(3rdparty/xxHash/build/cmake xxhash_build EXCLUDE_FROM_ALL)
set(TRACY_ENABLE ${MOBILEGL_ENABLE_TRACY} CACHE BOOL "Enable Tracy, this is an internal variable" FORCE)
if (TRACY_ENABLE)
@@ -164,6 +168,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp
MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp
@@ -204,6 +209,15 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectGLES/Utils.cpp
MobileGL/MG_Backend/DirectGLES/Managers.cpp
MobileGL/MG_Backend/DirectVulkanTMP/DirectVulkanTMP.cpp
MobileGL/MG_Backend/DirectVulkanTMP/TmpImpl.cpp
MobileGL/MG_Backend/DirectVulkanTMP/BackendObject_DirectVulkanTMP.cpp
MobileGL/MG_Backend/DirectVulkanTMP/Renderer/VulkanContext.cpp
MobileGL/MG_Backend/DirectVulkanTMP/Renderer/SwapchainManager.cpp
MobileGL/MG_Backend/DirectVulkanTMP/Renderer/PipelineManager.cpp
MobileGL/MG_Backend/DirectVulkanTMP/Renderer/FrameContext.cpp
MobileGL/MG_Backend/DirectVulkanTMP/Managers/ProgramManager.cpp
MobileGL/MG_State/GLState/Core.cpp
MobileGL/MG_State/GLState/ErrorState/Error.cpp
MobileGL/MG_State/GLState/BufferState/BufferState.cpp
@@ -236,6 +250,7 @@ set(MOBILEGL_LINK_LIBRARIES
spirv-cross-c
SPIRV-Tools-opt
SPIRV-Tools
xxHash::xxhash
)
set(MOBILEGL_INCLUDE_DIR
@@ -314,13 +329,24 @@ if (ANDROID)
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
android
log
vulkan
)
endif()
if (MOBILEGL_BUILD_TEST)
add_subdirectory(MobileGL/MG_Test)
endif()
if (NOT ANDROID)
find_package(Vulkan)
if (Vulkan_FOUND)
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC Vulkan::Vulkan Vulkan::Headers)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC ${Vulkan_INCLUDE_DIR})
endif ()
if (MOBILEGL_BUILD_BENCHMARK)
add_subdirectory(MobileGL/MG_Benchmark)
if (MOBILEGL_BUILD_TEST)
add_subdirectory(MobileGL/MG_Test)
endif()
if (MOBILEGL_BUILD_BENCHMARK)
add_subdirectory(MobileGL/MG_Benchmark)
endif()
endif()
+1 -1
View File
@@ -10,7 +10,7 @@
namespace MobileGL {
namespace MG_Config {
BackendType ActiveBackendType;
BackendType ActiveBackendType = BackendType::DirectVulkanTMP;
} // namespace MG_Config
namespace MG_Backend {
+6 -4
View File
@@ -17,9 +17,11 @@
#include <map>
#include <array>
#include <ctime>
#include <mutex>
#include <queue>
#include <regex>
#include <atomic>
#include <bitset>
#include <cctype>
#include <chrono>
#include <cstdio>
@@ -29,19 +31,19 @@
#include <string>
#include <thread>
#include <vector>
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <numeric>
#include <expected>
#include <iostream>
#include <optional>
#include <xxhash.h>
#include <algorithm>
#include <stdexcept>
#include <functional>
#include <string_view>
#include <unordered_map>
#include <mutex>
#include <bitset>
#if __cplusplus >= 202302L && MOBILEGL_LOG_ENABLE_STACKTRACE
#include <stacktrace>
#endif
@@ -90,13 +92,13 @@
#endif
#ifdef __ANDROID__
#define VK_USE_PLATFORM_ANDROID_KHR
#include <unistd.h>
#include <pthread.h>
#include <android/log.h>
#include <vulkan/vulkan.h>
#include <android/native_window.h>
#include <vulkan/vulkan_android.h>
#endif
#include <vulkan/vulkan.h>
#ifdef TRACY_ENABLE
#include <tracy/Tracy.hpp>
+1
View File
@@ -12,6 +12,7 @@
namespace MobileGL {
enum class BackendType {
DirectGLES,
DirectVulkanTMP,
BackendTypeCount,
Unknown = -1
};
+1
View File
@@ -10,6 +10,7 @@
#include <Includes.h>
#include "BackendObject.h"
#include "DirectGLES/BackendObject_DirectGLES.h"
#include "DirectVulkanTMP/BackendObject_DirectVulkanTMP.h"
namespace MobileGL::MG_Backend {
extern UniquePtr<BackendObject> pActiveBackendObject;
@@ -0,0 +1,119 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/BackendObject_DirectVulkanTMP.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 "BackendObject_DirectVulkanTMP.h"
#include "MG_Backend/BackendObject.h"
#include "DirectVulkanTMP.h"
#include "TmpImpl.h"
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP {
BackendObject_DirectVulkanTMP::~BackendObject_DirectVulkanTMP() = default;
void BackendObject_DirectVulkanTMP::InitWindowSurface() {
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
if (!DirectVulkanTMP::InitWindowSurface(nativeWindow)) {
MGLOG_E("Failed to initialize window surface for DirectVulkanTMP backend");
}
}
void BackendObject_DirectVulkanTMP::Initialize() {
m_initialized = true;
}
void BackendObject_DirectVulkanTMP::InitCapabilities() {
if (!m_initialized) {
MGLOG_E("Cannot initialize capabilities before backend is initialized");
return;
}
MG_Util::BackendLoader::QueryVulkanCapabilities(m_vulkanCaps,
DirectVulkanTMP::GetVulkanState().ctx->GetPhysicalDevice());
UpdateDynamicBackendParameters();
}
const RendererInfo& BackendObject_DirectVulkanTMP::GetRendererInfo() const {
static RendererInfo RendererInfo = {
.RendererName = "Magma-TMP", // Renderer Name
.BackendName = "Direct (Vulkan) TMP", // Backend Name
.ExtraVendor = Nullopt, // Extra vendor
.RendererGLInfo =
{
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
.Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions
V_OpenGL33},
.IsCompatibilityProfile = false // Is Compatibility Profile
},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
};
return RendererInfo;
}
String BackendObject_DirectVulkanTMP::GetBackendAPIVersionString() const {
if (!m_initialized) {
return "<uninitialized DirectVulkanTMP backend>";
}
// Format:
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
// TODO
String str = m_vulkanCaps.DeviceName + ", Vulkan " + m_vulkanCaps.VulkanAPIVersion.toString() + ", Driver " +
m_vulkanCaps.DriverVersionString;
return str;
}
BackendType BackendObject_DirectVulkanTMP::GetBackendType() const {
return BackendType::DirectVulkanTMP;
}
const GlobalBackendFunctionsTable& BackendObject_DirectVulkanTMP::GetBackendFunctions() const {
static GlobalBackendFunctionsTable funcsTable;
static Bool funcsTableInitialized = false;
if (!funcsTableInitialized) {
funcsTable.Present = DirectVulkanTMP::Present;
funcsTable.GL.DrawArrays = DrawArrays;
funcsTable.GL.DrawElements = DrawElements;
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
funcsTable.GL.MultiDrawElements = MultiDrawElements;
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
funcsTable.GL.DrawRangeElements = DrawRangeElements;
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
funcsTable.GL.DrawElementsInstancedBaseVertex = DrawElementsInstancedBaseVertex;
funcsTable.GL.DrawElementsInstancedBaseInstance = DrawElementsInstancedBaseInstance;
funcsTable.GL.DrawElementsInstanced = DrawElementsInstanced;
funcsTable.GL.DrawArraysInstancedBaseInstance = DrawArraysInstancedBaseInstance;
funcsTable.GL.DrawArraysInstanced = DrawArraysInstanced;
funcsTable.GL.DrawElementsIndirect = DrawElementsIndirect;
funcsTable.GL.DrawArraysIndirect = DrawArraysIndirect;
funcsTable.GL.Clear = Clear;
funcsTable.GL.ClearBufferfi = ClearBufferfi;
funcsTable.GL.ClearBufferfv = ClearBufferfv;
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
funcsTable.GL.ClearBufferiv = ClearBufferiv;
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
funcsTable.GL.GenerateMipmap = GenerateMipmap;
funcsTable.GL.ReadPixels = ReadPixels;
funcsTable.GL.GetTexImage = GetTexImage;
funcsTableInitialized = true;
}
return funcsTable;
}
const DynamicBackendParameters& BackendObject_DirectVulkanTMP::GetDynamicParameters() const {
return m_dynamicParameters;
}
void BackendObject_DirectVulkanTMP::UpdateDynamicBackendParameters() {
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP
@@ -0,0 +1,36 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/BackendObject_DirectVulkanTMP.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 <Includes.h>
#include "../BackendObject.h"
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP {
class BackendObject_DirectVulkanTMP : public BackendObject {
public:
~BackendObject_DirectVulkanTMP() override;
void Initialize() override;
void InitWindowSurface() override;
void InitCapabilities() override;
const RendererInfo& GetRendererInfo() const override;
String GetBackendAPIVersionString() const override;
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
const DynamicBackendParameters& GetDynamicParameters() const override;
BackendType GetBackendType() const override;
private:
void UpdateDynamicBackendParameters();
Bool m_initialized = false;
DynamicBackendParameters m_dynamicParameters;
MG_External::VulkanCapabilities m_vulkanCaps;
};
} // namespace MobileGL::MG_Backend::DirectVulkanTMP
@@ -0,0 +1,74 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/DirectVulkanTMP.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 "DirectVulkanTMP.h"
#include "TmpImpl.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP {
void Clear(GLbitfield mask) {
MobileGL::MG_Backend::DirectVulkanTMP::TmpImpl::Clear(mask);
}
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
MobileGL::MG_Backend::DirectVulkanTMP::TmpImpl::DrawElements(mode, count, type, indices);
}
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {}
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 DrawArrays(GLenum mode, GLint first, GLsizei count) {}
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {}
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount) {}
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) {}
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex) {}
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {}
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {}
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex) {}
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLuint baseinstance) {}
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {}
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {}
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
GLuint baseinstance) {}
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {}
void DrawArraysIndirect(GLenum mode, const void* indirect) {}
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter) {}
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border) {}
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height) {}
void GenerateMipmap(GLenum target) {}
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {}
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {}
Bool InitWindowSurface(NativeWindowType window) {
if (!window) {
MGLOG_E("Cannot initialize Vulkan window surface: invalid window handle");
return false;
}
TmpImpl::InitVulkan(window);
return true;
}
void Present() {
TmpImpl::Present();
}
const TmpImpl::VulkanState& GetVulkanState() {
return TmpImpl::GetVulkanState();
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP
@@ -0,0 +1,58 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/DirectVulkanTMP.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 <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP {
namespace TmpImpl {
class VulkanState;
} // namespace TmpImpl
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
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 Clear(GLbitfield mask);
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
void DrawArrays(GLenum mode, GLint first, GLsizei count);
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount);
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);
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex);
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex);
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLuint baseinstance);
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
GLuint baseinstance);
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
void DrawArraysIndirect(GLenum mode, const void* indirect);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter);
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height);
void GenerateMipmap(GLenum target);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
Bool InitWindowSurface(NativeWindowType window);
void Present();
const TmpImpl::VulkanState& GetVulkanState();
} // namespace MobileGL::MG_Backend::DirectVulkanTMP
@@ -0,0 +1,447 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Managers/ProgramManager.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 "ProgramManager.h"
#include "MG_Util/Debug/Log.h"
#include "spirv-tools/libspirv.h"
#include "spirv-tools/optimizer.hpp"
#include "source/opt/constants.h"
#include "source/opt/instruction.h"
#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
#include "source/opt/type_manager.h"
#include <bit>
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
namespace {
using ProgramObject = MG_State::GLState::ProgramObject;
using ShaderObject = MG_State::GLState::ShaderObject;
struct PositionTargetInfo {
uint32_t variableId = 0;
uint32_t vectorTypeId = 0;
uint32_t floatTypeId = 0;
uint32_t vectorPtrTypeId = 0;
uint32_t memberIndex = 0;
bool isMember = false;
};
bool IsVec4Float32(spvtools::opt::IRContext* context, uint32_t typeId, uint32_t* outFloatTypeId) {
auto* vecInst = context->get_def_use_mgr()->GetDef(typeId);
if (!vecInst || vecInst->opcode() != spv::Op::OpTypeVector) return false;
if (vecInst->GetSingleWordInOperand(1) != 4) return false;
const uint32_t floatTypeId = vecInst->GetSingleWordInOperand(0);
auto* floatInst = context->get_def_use_mgr()->GetDef(floatTypeId);
if (!floatInst || floatInst->opcode() != spv::Op::OpTypeFloat) return false;
if (floatInst->GetSingleWordInOperand(0) != 32) return false;
if (outFloatTypeId) *outFloatTypeId = floatTypeId;
return true;
}
bool ResolveDirectPositionTarget(spvtools::opt::IRContext* context, uint32_t variableId,
PositionTargetInfo* outTarget) {
auto* varInst = context->get_def_use_mgr()->GetDef(variableId);
if (!varInst || varInst->opcode() != spv::Op::OpVariable) return false;
if (varInst->GetSingleWordInOperand(0) != static_cast<uint32_t>(spv::StorageClass::Output)) return false;
auto* ptrTypeInst = context->get_def_use_mgr()->GetDef(varInst->type_id());
if (!ptrTypeInst || ptrTypeInst->opcode() != spv::Op::OpTypePointer) return false;
if (ptrTypeInst->GetSingleWordInOperand(0) != static_cast<uint32_t>(spv::StorageClass::Output))
return false;
PositionTargetInfo target{};
target.variableId = variableId;
target.vectorTypeId = ptrTypeInst->GetSingleWordInOperand(1);
if (!IsVec4Float32(context, target.vectorTypeId, &target.floatTypeId)) return false;
target.vectorPtrTypeId = varInst->type_id();
target.isMember = false;
*outTarget = target;
return true;
}
uint32_t FindOutputVectorPointerTypeId(spvtools::opt::IRContext* context, uint32_t vectorTypeId) {
auto* vectorType = context->get_type_mgr()->GetType(vectorTypeId);
if (!vectorType) return 0;
spvtools::opt::analysis::Pointer ptrType(vectorType, spv::StorageClass::Output);
return context->get_type_mgr()->GetTypeInstruction(&ptrType);
}
bool ResolveMemberPositionTarget(spvtools::opt::IRContext* context, uint32_t structTypeId, uint32_t memberIndex,
PositionTargetInfo* outTarget) {
auto* structInst = context->get_def_use_mgr()->GetDef(structTypeId);
if (!structInst || structInst->opcode() != spv::Op::OpTypeStruct) return false;
if (memberIndex >= structInst->NumInOperands()) return false;
const uint32_t vectorTypeId = structInst->GetSingleWordInOperand(memberIndex);
uint32_t floatTypeId = 0;
if (!IsVec4Float32(context, vectorTypeId, &floatTypeId)) return false;
const uint32_t vectorPtrTypeId = FindOutputVectorPointerTypeId(context, vectorTypeId);
if (vectorPtrTypeId == 0) return false;
for (auto& inst : context->module()->types_values()) {
if (inst.opcode() != spv::Op::OpVariable) continue;
if (inst.GetSingleWordInOperand(0) != static_cast<uint32_t>(spv::StorageClass::Output)) continue;
auto* ptrTypeInst = context->get_def_use_mgr()->GetDef(inst.type_id());
if (!ptrTypeInst || ptrTypeInst->opcode() != spv::Op::OpTypePointer) continue;
if (ptrTypeInst->GetSingleWordInOperand(0) != static_cast<uint32_t>(spv::StorageClass::Output))
continue;
if (ptrTypeInst->GetSingleWordInOperand(1) != structTypeId) continue;
PositionTargetInfo target{};
target.variableId = inst.result_id();
target.vectorTypeId = vectorTypeId;
target.floatTypeId = floatTypeId;
target.vectorPtrTypeId = vectorPtrTypeId;
target.memberIndex = memberIndex;
target.isMember = true;
*outTarget = target;
return true;
}
return false;
}
bool FindPositionTarget(spvtools::opt::IRContext* context, PositionTargetInfo* outTarget) {
Vector<Pair<uint32_t, uint32_t>> memberCandidates;
constexpr uint32_t kDecorationBuiltIn = static_cast<uint32_t>(spv::Decoration::BuiltIn);
constexpr uint32_t kBuiltInPosition = static_cast<uint32_t>(spv::BuiltIn::Position);
for (auto& inst : context->module()->annotations()) {
if (inst.opcode() == spv::Op::OpDecorate) {
if (inst.NumInOperands() < 3) continue;
if (inst.GetSingleWordInOperand(1) != kDecorationBuiltIn) continue;
if (inst.GetSingleWordInOperand(2) != kBuiltInPosition) continue;
if (ResolveDirectPositionTarget(context, inst.GetSingleWordInOperand(0), outTarget)) return true;
} else if (inst.opcode() == spv::Op::OpMemberDecorate) {
if (inst.NumInOperands() < 4) continue;
if (inst.GetSingleWordInOperand(2) != kDecorationBuiltIn) continue;
if (inst.GetSingleWordInOperand(3) != kBuiltInPosition) continue;
memberCandidates.emplace_back(inst.GetSingleWordInOperand(0), inst.GetSingleWordInOperand(1));
}
}
for (const auto& [structTypeId, memberIndex] : memberCandidates) {
if (ResolveMemberPositionTarget(context, structTypeId, memberIndex, outTarget)) return true;
}
return false;
}
bool InsertPositionFixup(spvtools::opt::IRContext* context, spvtools::opt::Instruction* insertBefore,
const PositionTargetInfo& target, uint32_t halfConstId, bool doYFlip, bool doZRemap) {
using namespace spvtools::opt;
InstructionBuilder builder(context, insertBefore,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t positionPtrId = target.variableId;
if (target.isMember) {
const uint32_t memberIndexId = builder.GetUintConstantId(target.memberIndex);
if (memberIndexId == 0) return false;
auto* access = builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId});
if (!access) return false;
positionPtrId = access->result_id();
}
auto* position = builder.AddLoad(target.vectorTypeId, positionPtrId);
if (!position) return false;
auto* x = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {0});
auto* y = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {1});
auto* z = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {2});
auto* w = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {3});
if (!x || !y || !z || !w) return false;
if (!doYFlip && !doZRemap) return false;
uint32_t yValueId = y->result_id();
if (doYFlip) {
auto* negY = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, y->result_id());
if (!negY) return false;
yValueId = negY->result_id();
}
uint32_t zValueId = z->result_id();
if (doZRemap) {
auto* zPlusW = builder.AddBinaryOp(target.floatTypeId, spv::Op::OpFAdd, z->result_id(), w->result_id());
if (!zPlusW) return false;
auto* mappedZ =
builder.AddBinaryOp(target.floatTypeId, spv::Op::OpFMul, zPlusW->result_id(), halfConstId);
if (!mappedZ) return false;
zValueId = mappedZ->result_id();
}
auto* fixedPosition = builder.AddCompositeConstruct(target.vectorTypeId,
{x->result_id(), yValueId, zValueId, w->result_id()});
if (!fixedPosition) return false;
return builder.AddStore(positionPtrId, fixedPosition->result_id()) != nullptr;
}
class GlToVulkanPositionFixPass final : public spvtools::opt::Pass {
public:
const char* name() const override { return "gl-to-vulkan-position-fix"; }
explicit GlToVulkanPositionFixPass(ShaderTransformFlags transformFlags)
: m_transformFlags(transformFlags) {}
Status Process() override {
if (!m_transformFlags) return Status::SuccessWithoutChange;
PositionTargetInfo target{};
if (!FindPositionTarget(context(), &target)) return Status::SuccessWithoutChange;
auto* floatType = context()->get_type_mgr()->GetType(target.floatTypeId);
if (!floatType) return Status::SuccessWithoutChange;
const uint32_t halfBits = std::bit_cast<uint32_t>(0.5f);
const auto* halfConst = context()->get_constant_mgr()->GetConstant(floatType, {halfBits});
auto* halfInst = context()->get_constant_mgr()->GetDefiningInstruction(halfConst);
if (!halfInst) return Status::SuccessWithoutChange;
const uint32_t halfConstId = halfInst->result_id();
const bool doYFlip = (m_transformFlags & ShaderTransformBit::PositionYFlip);
const bool doZRemap = (m_transformFlags & ShaderTransformBit::PositionZRemap);
bool modified = false;
for (auto& entryPoint : get_module()->entry_points()) {
if (entryPoint.opcode() != spv::Op::OpEntryPoint) continue;
if (entryPoint.NumInOperands() < 2) continue;
const auto model = static_cast<spv::ExecutionModel>(entryPoint.GetSingleWordInOperand(0));
if (model != spv::ExecutionModel::Vertex && model != spv::ExecutionModel::TessellationEvaluation &&
model != spv::ExecutionModel::Geometry) {
continue;
}
auto* function = context()->GetFunction(entryPoint.GetSingleWordInOperand(1));
if (!function) continue;
for (auto& bb : *function) {
for (auto instIter = bb.begin(); instIter != bb.end(); ++instIter) {
auto* inst = &*instIter;
const bool needsFixup =
(model == spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpEmitVertex) ||
(model != spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpReturn);
if (!needsFixup) continue;
modified |= InsertPositionFixup(context(), inst, target, halfConstId, doYFlip, doZRemap);
}
}
}
if (!modified) return Status::SuccessWithoutChange;
context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisDefUse |
spvtools::opt::IRContext::kAnalysisInstrToBlockMapping);
return Status::SuccessWithChange;
}
private:
ShaderTransformFlags m_transformFlags;
};
spvtools::Optimizer::PassToken CreateGlToVulkanPositionFixPass(ShaderTransformFlags transformFlags) {
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
}
bool TransformSpirvForVulkanPositionFix(const Vector<Uint32>& input, Vector<Uint32>& output,
ShaderTransformFlags transformFlags) {
if (input.empty()) {
output.clear();
return true;
}
if (!transformFlags) {
output = input;
return true;
}
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
spvtools::OptimizerOptions options;
options.set_run_validator(false);
optimizer.RegisterPass(CreateGlToVulkanPositionFixPass(transformFlags));
const bool success = optimizer.Run(input.data(), input.size(), &output, options);
if (!success) {
MGLOG_E("Vulkan: failed to run GL->Vulkan position fix pass");
output = input;
}
return success;
}
ShaderStage PickClipFixupStage(const Vector<SharedPtr<ShaderObject>>& shaders) {
bool hasGeometry = false;
bool hasTessEval = false;
bool hasVertex = false;
for (const auto& shader : shaders) {
if (!shader) continue;
const auto stage = shader->GetShaderStage();
hasGeometry |= (stage == ShaderStage::Geometry);
hasTessEval |= (stage == ShaderStage::TessEval);
hasVertex |= (stage == ShaderStage::Vertex);
}
if (hasGeometry) return ShaderStage::Geometry;
if (hasTessEval) return ShaderStage::TessEval;
if (hasVertex) return ShaderStage::Vertex;
return ShaderStage::Unknown;
}
} // namespace
ProgramManager::~ProgramManager() {
for (auto& [_, stages] : m_cache) {
DestroyStages(stages);
}
m_cache.clear();
}
ProgramManager::HashType ProgramManager::ComputeSourceSpvHash(MG_State::GLState::ProgramObject* program) const {
if (!program) return 0;
XXH64_state_t* state = XXH64_createState();
XXH64_reset(state, 0xC0FFEEu);
auto& spirvs = program->GetGeneratedSpirv();
for (const auto& spv : spirvs) {
if (spv.empty()) continue;
XXH64_update(state, spv.data(), spv.size() * sizeof(Uint));
}
HashType hash = XXH64_digest(state);
XXH64_freeState(state);
return hash;
}
ProgramManager::HashType ProgramManager::ComputeSpvHash(const Vector<Vector<Uint32>>& spirvs) const {
XXH64_state_t* state = XXH64_createState();
XXH64_reset(state, 0xC0FFEEu);
for (const auto& spv : spirvs) {
if (spv.empty()) continue;
XXH64_update(state, spv.data(), spv.size() * sizeof(Uint32));
}
HashType hash = XXH64_digest(state);
XXH64_freeState(state);
return hash;
}
void ProgramManager::BuildPipelineSpirvModules(MG_State::GLState::ProgramObject* program,
Vector<Vector<Uint32>>& outSpirvs,
ShaderTransformFlags transformFlags) const {
outSpirvs.clear();
if (!program) return;
auto& spirvs = program->GetGeneratedSpirv();
auto& shaders = program->GetAttachedShaders();
const ShaderStage fixupStage = PickClipFixupStage(shaders);
outSpirvs.reserve(spirvs.size());
for (SizeT i = 0; i < spirvs.size(); ++i) {
Vector<Uint32> module = spirvs[i];
ShaderStage stage = ShaderStage::Unknown;
if (i < shaders.size() && shaders[i]) stage = shaders[i]->GetShaderStage();
if (!module.empty() && fixupStage != ShaderStage::Unknown && stage == fixupStage) {
Vector<Uint32> transformed;
TransformSpirvForVulkanPositionFix(module, transformed, transformFlags);
module = Move(transformed);
}
outSpirvs.push_back(Move(module));
}
}
ProgramManager::HashType ProgramManager::ComputeSpvHash(MG_State::GLState::ProgramObject* program,
ShaderTransformFlags transformFlags) const {
Vector<Vector<Uint32>> spirvs;
BuildPipelineSpirvModules(program, spirvs, transformFlags);
return ComputeSpvHash(spirvs);
}
ProgramManager::HashType ProgramManager::ComputeProgramHash(MG_State::GLState::ProgramObject* program,
ShaderTransformFlags transformFlags) const {
if (!program) return 0;
const HashType sourceHash = ComputeSourceSpvHash(program);
auto it = m_cache.find(program);
if (it != m_cache.end() && it->second.sourceHash == sourceHash) return it->second.hash;
return ComputeSpvHash(program, transformFlags);
}
VkShaderStageFlagBits ProgramManager::ToVkStage(ShaderStage stage) const {
switch (stage) {
case ShaderStage::Vertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case ShaderStage::Fragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case ShaderStage::Geometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case ShaderStage::TessControl:
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
case ShaderStage::TessEval:
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
case ShaderStage::Compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
default:
return VK_SHADER_STAGE_ALL_GRAPHICS;
}
}
void ProgramManager::DestroyStages(ProgramStages& stages) {
for (auto module : stages.modules) {
if (module != VK_NULL_HANDLE) {
vkDestroyShaderModule(m_ctx.GetDevice(), module, nullptr);
}
}
stages.modules.clear();
stages.stages.clear();
stages.sourceHash = 0;
stages.hash = 0;
}
Vector<VkPipelineShaderStageCreateInfo>& ProgramManager::CreatePipelineShaderStages(
MG_State::GLState::ProgramObject* program, ShaderTransformFlags transformFlags) {
auto& entry = m_cache[program];
HashType sourceHash = ComputeSourceSpvHash(program);
if (!entry.stages.empty() && entry.sourceHash == sourceHash) return entry.stages;
DestroyStages(entry);
entry.sourceHash = sourceHash;
if (!program) return entry.stages;
Vector<Vector<Uint32>> spirvs;
BuildPipelineSpirvModules(program, spirvs, transformFlags);
entry.hash = ComputeSpvHash(spirvs);
auto& shaders = program->GetAttachedShaders();
for (SizeT i = 0; i < spirvs.size(); ++i) {
auto& spv = spirvs[i];
if (spv.empty()) continue;
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
smci.codeSize = spv.size() * sizeof(Uint);
smci.pCode = spv.data();
VkShaderModule module = VK_NULL_HANDLE;
VK_VERIFY(vkCreateShaderModule(m_ctx.GetDevice(), &smci, nullptr, &module), "vkCreateShaderModule");
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
ShaderStage shaderStage = ShaderStage::Unknown;
if (i < shaders.size() && shaders[i]) shaderStage = shaders[i]->GetShaderStage();
stage.stage = ToVkStage(shaderStage);
stage.module = module;
stage.pName = "main";
entry.modules.push_back(module);
entry.stages.push_back(stage);
}
return entry.stages;
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,58 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Managers/ProgramManager.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 <Includes.h>
#include "../Renderer/VulkanContext.h"
#include "../Renderer/VkCommon.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
enum class ShaderTransformBit : Uint {
None = 0,
PositionYFlip = 1 << 0,
PositionZRemap = 1 << 1,
};
using ShaderTransformFlags = Flags<ShaderTransformBit>;
class ProgramManager {
public:
using HashType = Uint64;
explicit ProgramManager(VulkanContext& ctx) : m_ctx(ctx) {}
~ProgramManager();
ProgramManager(const ProgramManager&) = delete;
ProgramManager& operator=(const ProgramManager&) = delete;
Vector<VkPipelineShaderStageCreateInfo>& CreatePipelineShaderStages(
MG_State::GLState::ProgramObject* program,
ShaderTransformFlags transformFlags = ShaderTransformBit::PositionZRemap);
HashType ComputeProgramHash(MG_State::GLState::ProgramObject* program,
ShaderTransformFlags transformFlags = ShaderTransformBit::PositionZRemap) const;
private:
struct ProgramStages {
HashType sourceHash = 0;
HashType hash = 0;
Vector<VkPipelineShaderStageCreateInfo> stages;
Vector<VkShaderModule> modules;
};
void DestroyStages(ProgramStages& stages);
HashType ComputeSourceSpvHash(MG_State::GLState::ProgramObject* program) const;
HashType ComputeSpvHash(MG_State::GLState::ProgramObject* program, ShaderTransformFlags transformFlags) const;
HashType ComputeSpvHash(const Vector<Vector<Uint32>>& spirvs) const;
void BuildPipelineSpirvModules(MG_State::GLState::ProgramObject* program, Vector<Vector<Uint32>>& outSpirvs,
ShaderTransformFlags transformFlags) const;
VkShaderStageFlagBits ToVkStage(ShaderStage stage) const;
VulkanContext& m_ctx;
UnorderedMap<const MG_State::GLState::ProgramObject*, ProgramStages> m_cache;
};
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,49 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/FrameContext.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 "FrameContext.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
void FrameContext::Initialize(VulkanContext& ctx, VkCommandPool pool) {
CommandPool = pool;
VkCommandBufferAllocateInfo abci{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
abci.commandPool = pool;
abci.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
abci.commandBufferCount = 1;
VK_VERIFY(vkAllocateCommandBuffers(ctx.GetDevice(), &abci, &CommandBuffer), "vkAllocateCommandBuffers");
VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &ImageAvailable), "vkCreateSemaphore");
VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &RenderFinished), "vkCreateSemaphore");
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
fci.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VK_VERIFY(vkCreateFence(ctx.GetDevice(), &fci, nullptr, &InFlightFence), "vkCreateFence");
}
void FrameContext::Cleanup(VulkanContext& ctx) {
auto device = ctx.GetDevice();
if (InFlightFence != VK_NULL_HANDLE) {
vkDestroyFence(device, InFlightFence, nullptr);
InFlightFence = VK_NULL_HANDLE;
}
if (ImageAvailable != VK_NULL_HANDLE) {
vkDestroySemaphore(device, ImageAvailable, nullptr);
ImageAvailable = VK_NULL_HANDLE;
}
if (RenderFinished != VK_NULL_HANDLE) {
vkDestroySemaphore(device, RenderFinished, nullptr);
RenderFinished = VK_NULL_HANDLE;
}
if (CommandBuffer != VK_NULL_HANDLE) {
vkFreeCommandBuffers(device, CommandPool, 1, &CommandBuffer);
CommandBuffer = VK_NULL_HANDLE;
}
CommandPool = VK_NULL_HANDLE;
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,42 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/FrameContext.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 <Includes.h>
#include "VulkanContext.h"
#include "VkCommon.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP {
struct TrashBuffer {
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
Bool mapped = false;
};
struct TrashImage {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
Vector<VkImageView> mipViews;
};
namespace VkManager {
struct FrameContext {
VkCommandBuffer CommandBuffer = VK_NULL_HANDLE;
VkSemaphore ImageAvailable = VK_NULL_HANDLE;
VkSemaphore RenderFinished = VK_NULL_HANDLE;
VkFence InFlightFence = VK_NULL_HANDLE;
Uint32 CurrentImageIndex = 0;
VkCommandPool CommandPool = VK_NULL_HANDLE;
Vector<TrashBuffer> TrashBuffers;
Vector<TrashImage> TrashImages;
// VkCommandPool CommandPool = VK_NULL_HANDLE;
void Initialize(VulkanContext& ctx, VkCommandPool pool);
void Cleanup(VulkanContext& ctx);
};
} // namespace VkManager
} // namespace MobileGL::MG_Backend::DirectVulkanTMP
@@ -0,0 +1,11 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/PipelineManager.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 <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {}
@@ -0,0 +1,166 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/SwapchainManager.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 "SwapchainManager.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
namespace {
VkSurfaceFormatKHR ChooseSurfaceFormat(const Vector<VkSurfaceFormatKHR>& formats) {
for (const auto& f : formats) {
if ((f.format == VK_FORMAT_R8G8B8A8_UNORM || f.format == VK_FORMAT_B8G8R8A8_UNORM) &&
f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return f;
}
}
return formats.empty() ? VkSurfaceFormatKHR{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}
: formats[0];
}
VkPresentModeKHR ChoosePresentMode(const Vector<VkPresentModeKHR>& modes) {
for (const auto& m : modes) {
if (m == VK_PRESENT_MODE_MAILBOX_KHR) return m;
}
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D ChooseExtent(const VkSurfaceCapabilitiesKHR& caps, ANativeWindow* window) {
if (caps.currentExtent.width != UINT32_MAX) return caps.currentExtent;
// try to get frontend viewport size
VkExtent2D extent{640, 480};
#ifdef VK_USE_PLATFORM_ANDROID_KHR
if (window) {
extent.width = static_cast<Uint32>(ANativeWindow_getWidth(window));
extent.height = static_cast<Uint32>(ANativeWindow_getHeight(window));
}
#else
(void)window;
#endif
extent.width = std::max(caps.minImageExtent.width, std::min(caps.maxImageExtent.width, extent.width));
extent.height = std::max(caps.minImageExtent.height, std::min(caps.maxImageExtent.height, extent.height));
return extent;
}
} // namespace
SwapchainManager::~SwapchainManager() {
DestroySwapchain();
}
void SwapchainManager::Initialize() {
CreateSwapchain(VK_NULL_HANDLE);
}
void SwapchainManager::Recreate() {
DestroySwapchain();
CreateSwapchain(VK_NULL_HANDLE);
}
void SwapchainManager::SetFramebuffers(Vector<VkFramebuffer>&& framebuffers) {
m_framebuffers = Move(framebuffers);
}
void SwapchainManager::DestroySwapchain() {
auto device = m_ctx.GetDevice();
if (device == VK_NULL_HANDLE) return;
for (auto fb : m_framebuffers) {
if (fb != VK_NULL_HANDLE) vkDestroyFramebuffer(device, fb, nullptr);
}
m_framebuffers.clear();
for (auto view : m_imageViews) {
if (view != VK_NULL_HANDLE) vkDestroyImageView(device, view, nullptr);
}
m_imageViews.clear();
m_images.clear();
m_imagesInFlight.clear();
if (m_swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, m_swapchain, nullptr);
m_swapchain = VK_NULL_HANDLE;
}
m_format = VK_FORMAT_UNDEFINED;
m_extent = {0, 0};
}
void SwapchainManager::CreateSwapchain(VkSwapchainKHR oldSwapchain) {
VkSurfaceCapabilitiesKHR caps{};
VK_VERIFY(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &caps),
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
Uint32 fmtCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &fmtCount, nullptr);
Vector<VkSurfaceFormatKHR> formats(fmtCount);
if (fmtCount > 0) {
vkGetPhysicalDeviceSurfaceFormatsKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &fmtCount,
formats.data());
}
Uint32 modeCount = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &modeCount, nullptr);
Vector<VkPresentModeKHR> modes(modeCount);
if (modeCount > 0) {
vkGetPhysicalDeviceSurfacePresentModesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &modeCount,
modes.data());
}
VkSurfaceFormatKHR surfaceFormat = ChooseSurfaceFormat(formats);
VkPresentModeKHR presentMode = ChoosePresentMode(modes);
VkExtent2D extent;
if (m_viewportSize.x() == 0 || m_viewportSize.y() == 0) {
extent = ChooseExtent(caps, m_ctx.GetWindow());
} else {
extent = {m_viewportSize.x(), m_viewportSize.y()};
}
Uint32 imageCount = caps.minImageCount + 1;
if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) imageCount = caps.maxImageCount;
VkSwapchainCreateInfoKHR sci{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
sci.surface = m_ctx.GetSurface();
sci.minImageCount = imageCount;
sci.imageFormat = surfaceFormat.format;
sci.imageColorSpace = surfaceFormat.colorSpace;
sci.imageExtent = extent;
sci.imageArrayLayers = 1;
sci.imageUsage =
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
sci.preTransform = caps.currentTransform;
sci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
sci.presentMode = presentMode;
sci.clipped = VK_TRUE;
sci.oldSwapchain = oldSwapchain;
VK_VERIFY(vkCreateSwapchainKHR(m_ctx.GetDevice(), &sci, nullptr, &m_swapchain), "vkCreateSwapchainKHR");
Uint32 actualCount = 0;
vkGetSwapchainImagesKHR(m_ctx.GetDevice(), m_swapchain, &actualCount, nullptr);
m_images.resize(actualCount);
vkGetSwapchainImagesKHR(m_ctx.GetDevice(), m_swapchain, &actualCount, m_images.data());
m_imageViews.clear();
m_imageViews.reserve(actualCount);
for (auto image : m_images) {
VkImageViewCreateInfo ivci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
ivci.image = image;
ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
ivci.format = surfaceFormat.format;
ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ivci.subresourceRange.baseMipLevel = 0;
ivci.subresourceRange.levelCount = 1;
ivci.subresourceRange.baseArrayLayer = 0;
ivci.subresourceRange.layerCount = 1;
VkImageView view = VK_NULL_HANDLE;
VK_VERIFY(vkCreateImageView(m_ctx.GetDevice(), &ivci, nullptr, &view), "vkCreateImageView swapchain");
m_imageViews.push_back(view);
}
m_imagesInFlight.assign(actualCount, VK_NULL_HANDLE);
m_format = surfaceFormat.format;
m_extent = extent;
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,52 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/SwapchainManager.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 <Includes.h>
#include "MG_Util/Math/VectorTypes.h"
#include "VulkanContext.h"
#include "VkCommon.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
class SwapchainManager {
public:
explicit SwapchainManager(VulkanContext& ctx) : m_ctx(ctx) {}
~SwapchainManager();
SwapchainManager(const SwapchainManager&) = delete;
SwapchainManager& operator=(const SwapchainManager&) = delete;
void Initialize();
void Recreate();
void SetViewportSize(const UintVec2& size) { m_viewportSize = size; }
VkSwapchainKHR GetSwapchain() const { return m_swapchain; }
VkFormat GetFormat() const { return m_format; }
VkExtent2D GetExtent() const { return m_extent; }
const Vector<VkImage>& GetImages() const { return m_images; }
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
const Vector<VkFramebuffer>& GetFramebuffers() const { return m_framebuffers; }
void SetFramebuffers(Vector<VkFramebuffer>&& framebuffers);
Vector<VkFence>& GetImagesInFlight() { return m_imagesInFlight; }
const UintVec2& GetViewportSize() const { return m_viewportSize; }
private:
void CreateSwapchain(VkSwapchainKHR oldSwapchain);
void DestroySwapchain();
VulkanContext& m_ctx;
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
VkFormat m_format = VK_FORMAT_UNDEFINED;
VkExtent2D m_extent{0, 0};
Vector<VkImage> m_images;
Vector<VkImageView> m_imageViews;
Vector<VkFramebuffer> m_framebuffers;
Vector<VkFence> m_imagesInFlight;
UintVec2 m_viewportSize{0, 0};
};
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,22 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/VkCommon.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 <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
inline void VkCheck(VkResult result, const char* msg) {
if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) return;
MGLOG_E("Vulkan error %d at %s", static_cast<Int>(result), msg ? msg : "(unknown)");
throw RuntimeError(msg ? msg : "Vulkan error");
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
#ifndef VK_VERIFY
#define VK_VERIFY(res, msg) ::MobileGL::MG_Backend::DirectVulkanTMP::VkManager::VkCheck((res), (msg))
#endif
@@ -0,0 +1,191 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/VulkanContext.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 "VulkanContext.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
namespace {
Bool CheckDeviceExtensionSupport(VkPhysicalDevice device, const Vector<const char*>& required) {
Uint32 count = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr);
Vector<VkExtensionProperties> props(count);
if (count > 0) vkEnumerateDeviceExtensionProperties(device, nullptr, &count, props.data());
for (auto* ext : required) {
Bool found = false;
for (const auto& p : props) {
if (std::strcmp(p.extensionName, ext) == 0) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
Bool FindGraphicsQueueFamily(VkPhysicalDevice device, VkSurfaceKHR surface, Uint32& outFamily) {
Uint32 count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr);
if (count == 0) return false;
Vector<VkQueueFamilyProperties> props(count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, props.data());
for (Uint32 i = 0; i < count; ++i) {
if (!(props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) continue;
if (surface != VK_NULL_HANDLE) {
VkBool32 presentSupport = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (!presentSupport) continue;
}
outFamily = i;
return true;
}
return false;
}
Bool SupportsDynamicRendering(VkPhysicalDevice device) {
VkPhysicalDeviceVulkan13Features vk13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES};
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
features2.pNext = &vk13;
vkGetPhysicalDeviceFeatures2(device, &features2);
return vk13.dynamicRendering == VK_TRUE;
}
Bool SupportsTimelineSemaphore(VkPhysicalDevice device) {
VkPhysicalDeviceVulkan12Features vk12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
features2.pNext = &vk12;
vkGetPhysicalDeviceFeatures2(device, &features2);
return vk12.timelineSemaphore == VK_TRUE;
}
} // namespace
VulkanContext::~VulkanContext() {
Cleanup();
}
void VulkanContext::Initialize(ANativeWindow* window, const char* appName) {
if (m_instance != VK_NULL_HANDLE) return;
m_window = window;
CreateInstance(appName ? appName : "MobileGL-Vulkan");
CreateSurface(window);
PickPhysicalDevice();
CreateDevice();
}
void VulkanContext::Cleanup() {
if (m_device != VK_NULL_HANDLE) {
vkDeviceWaitIdle(m_device);
vkDestroyDevice(m_device, nullptr);
m_device = VK_NULL_HANDLE;
}
if (m_surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
m_surface = VK_NULL_HANDLE;
}
if (m_instance != VK_NULL_HANDLE) {
vkDestroyInstance(m_instance, nullptr);
m_instance = VK_NULL_HANDLE;
}
m_physicalDevice = VK_NULL_HANDLE;
m_graphicsQueue = VK_NULL_HANDLE;
m_graphicsQueueFamily = ~0u;
m_window = nullptr;
}
void VulkanContext::CreateInstance(const char* appName) {
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
app.pApplicationName = appName;
app.applicationVersion = VK_MAKE_VERSION(1, 3, 0);
app.pEngineName = "MobileGL";
app.engineVersion = VK_MAKE_VERSION(1, 3, 0);
app.apiVersion = VK_API_VERSION_1_3;
Vector<const char*> extensions;
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
#ifdef VK_USE_PLATFORM_ANDROID_KHR
extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#endif
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
ici.pApplicationInfo = &app;
ici.enabledExtensionCount = static_cast<Uint32>(extensions.size());
ici.ppEnabledExtensionNames = extensions.data();
VK_VERIFY(vkCreateInstance(&ici, nullptr, &m_instance), "vkCreateInstance");
}
void VulkanContext::CreateSurface(ANativeWindow* window) {
if (!window) return;
#ifdef VK_USE_PLATFORM_ANDROID_KHR
VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
sci.window = window;
VK_VERIFY(vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateAndroidSurfaceKHR");
#else
(void)window;
#endif
}
void VulkanContext::PickPhysicalDevice() {
Uint32 count = 0;
VK_VERIFY(vkEnumeratePhysicalDevices(m_instance, &count, nullptr), "vkEnumeratePhysicalDevices");
if (count == 0) throw RuntimeError("No Vulkan physical devices found");
Vector<VkPhysicalDevice> devices(count);
VK_VERIFY(vkEnumeratePhysicalDevices(m_instance, &count, devices.data()), "vkEnumeratePhysicalDevices list");
Vector<const char*> requiredExts = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
for (auto device : devices) {
Uint32 family = ~0u;
if (!FindGraphicsQueueFamily(device, m_surface, family)) continue;
if (!CheckDeviceExtensionSupport(device, requiredExts)) continue;
if (!SupportsDynamicRendering(device)) continue;
m_physicalDevice = device;
m_graphicsQueueFamily = family;
break;
}
if (m_physicalDevice == VK_NULL_HANDLE) {
throw RuntimeError("No suitable Vulkan physical device found");
}
}
void VulkanContext::CreateDevice() {
float priority = 1.0f;
VkDeviceQueueCreateInfo qci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
qci.queueFamilyIndex = m_graphicsQueueFamily;
qci.queueCount = 1;
qci.pQueuePriorities = &priority;
Vector<const char*> deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkPhysicalDeviceFeatures features{};
VkPhysicalDeviceVulkan13Features vk13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES};
vk13.dynamicRendering = VK_TRUE;
VkPhysicalDeviceVulkan12Features vk12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
vk12.timelineSemaphore = SupportsTimelineSemaphore(m_physicalDevice) ? VK_TRUE : VK_FALSE;
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
features2.features = features;
vk13.pNext = &vk12;
features2.pNext = &vk13;
VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
dci.pNext = &features2;
dci.queueCreateInfoCount = 1;
dci.pQueueCreateInfos = &qci;
dci.enabledExtensionCount = static_cast<Uint32>(deviceExtensions.size());
dci.ppEnabledExtensionNames = deviceExtensions.data();
dci.pEnabledFeatures = nullptr;
VK_VERIFY(vkCreateDevice(m_physicalDevice, &dci, nullptr, &m_device), "vkCreateDevice");
vkGetDeviceQueue(m_device, m_graphicsQueueFamily, 0, &m_graphicsQueue);
}
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
@@ -0,0 +1,47 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/Renderer/VulkanContext.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 <Includes.h>
#include "VkCommon.h"
namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager {
class VulkanContext {
public:
VulkanContext() = default;
~VulkanContext();
VulkanContext(const VulkanContext&) = delete;
VulkanContext& operator=(const VulkanContext&) = delete;
void Initialize(ANativeWindow* window, const char* appName);
void Cleanup();
VkInstance GetInstance() const { return m_instance; }
VkPhysicalDevice GetPhysicalDevice() const { return m_physicalDevice; }
VkDevice GetDevice() const { return m_device; }
VkQueue GetGraphicsQueue() const { return m_graphicsQueue; }
Uint32 GetGraphicsQueueFamily() const { return m_graphicsQueueFamily; }
VkSurfaceKHR GetSurface() const { return m_surface; }
ANativeWindow* GetWindow() const { return m_window; }
private:
void CreateInstance(const char* appName);
void CreateSurface(ANativeWindow* window);
void PickPhysicalDevice();
void CreateDevice();
VkInstance m_instance = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
VkDevice m_device = VK_NULL_HANDLE;
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
Uint32 m_graphicsQueueFamily = ~0u;
ANativeWindow* m_window = nullptr;
};
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::VkManager
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,308 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkanTMP/TmpImpl.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 <Includes.h>
#include "Renderer/VkCommon.h"
#include "Renderer/VulkanContext.h"
#include "Renderer/SwapchainManager.h"
#include "Renderer/FrameContext.h"
#include "Managers/ProgramManager.h"
#include <MG_State/GLState/Core.h>
namespace MobileGL::MG_Backend::DirectVulkanTMP::TmpImpl {
namespace DV = MobileGL::MG_Backend::DirectVulkanTMP::VkManager;
using BufferObject = MobileGL::MG_State::GLState::BufferObject;
using ProgramObject = MobileGL::MG_State::GLState::ProgramObject;
using VertexArrayObject = MobileGL::MG_State::GLState::VertexArrayObject;
using FramebufferObject = MobileGL::MG_State::GLState::FramebufferObject;
using FramebufferAttachmentObject = MobileGL::MG_State::GLState::FramebufferAttachmentObject;
using ITextureObject = MobileGL::MG_State::GLState::ITextureObject;
using RenderbufferObject = MobileGL::MG_State::GLState::RenderbufferObject;
using SamplerObject = MobileGL::MG_State::GLState::SamplerObject;
using TextureObjectMipmap = MobileGL::MG_State::GLState::TextureObjectMipmap;
using TrashImage = MobileGL::MG_Backend::DirectVulkanTMP::TrashImage;
struct BufferResource {
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkDeviceSize size = 0;
VkDeviceSize offset = 0;
VkBufferUsageFlags usage = 0;
VkMemoryPropertyFlags props = 0;
void* mapped = nullptr;
Bool fromRing = false;
Uint32 lastUsedFrame = ~0u;
};
struct BufferResourceSet {
Vector<BufferResource> perFrame;
};
struct PendingBufferCopyBatch {
VkBuffer src = VK_NULL_HANDLE;
VkBuffer dst = VK_NULL_HANDLE;
Vector<VkBufferCopy> regions;
};
struct StagingRing {
BufferResource buffer;
VkDeviceSize capacity = 0;
VkDeviceSize head = 0;
};
struct TextureResource {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
UnorderedMap<Uint32, VkImageView> mipViews;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent3D extent{0, 0, 1};
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
Uint32 mipLevels = 1;
Uint16 paramsVersion = 0;
bool valid = false;
};
struct RenderbufferResource {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{0, 0};
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
bool valid = false;
};
struct SamplerResource {
VkSampler sampler = VK_NULL_HANDLE;
Uint16 version = 0;
SamplerParameters params{};
};
struct PendingClearInfo {
GLbitfield mask = 0;
FloatVec4 clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
Float clearDepth = 1.0f;
};
struct UniformBindingInfo {
String name;
Uint32 binding = 0;
Uint32 set = 0;
Uint32 blockIndex = 0xFFFFFFFFu;
VkShaderStageFlags stages = 0;
};
struct SamplerBindingInfo {
String name;
Uint32 binding = 0;
Uint32 set = 0;
VkShaderStageFlags stages = 0;
};
struct ProgramResource {
ProgramObject* program = nullptr;
Uint64 spvHash = 0;
DV::ProgramManager::HashType programHash = 0;
Vector<VkPipelineShaderStageCreateInfo>* shaderStages = nullptr;
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
struct UboPool {
Vector<BufferResource> buffers;
Uint32 cursor = 0;
};
Vector<UboPool> uboPools;
SizeT uboSize = 0;
Int32 uboBinding = -1;
Uint32 uniformDescriptorCount = 0;
Uint32 samplerDescriptorCount = 0;
Vector<UniformBindingInfo> uniformBindings;
Vector<SamplerBindingInfo> samplerBindings;
UnorderedMap<String, Uint32> samplerBindingByName;
UnorderedMap<Uint64, VkPipeline> pipelines;
};
struct DescriptorPoolConfig {
Uint32 maxSets = 0;
Uint32 uniformCount = 0;
Uint32 samplerCount = 0;
};
struct FrameDescriptorPools {
Vector<VkDescriptorPool> pools;
DescriptorPoolConfig config;
Uint32 activePool = 0;
};
struct VertexAttribKey {
Uint8 enabled = 0;
Uint8 size = 0;
Uint8 type = 0;
Uint8 normalized = 0;
Uint8 isInteger = 0;
Uint8 divisor = 0;
Uint16 pad = 0;
Uint32 stride = 0;
Uint64 offset = 0;
};
struct AttachmentInfo {
FramebufferAttachmentType type = FramebufferAttachmentType::None;
VkImageView view = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkImageAspectFlags aspect = 0;
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
ITextureObject* texture = nullptr;
RenderbufferObject* renderbuffer = nullptr;
};
struct BackendFramebufferObject {
VkExtent2D extent{0, 0};
Uint32 colorAttachmentCount = 0;
Bool hasDepth = false;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
VkFormat depthAttachmentFormat = VK_FORMAT_UNDEFINED;
VkFormat stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
Uint64 renderingCompatHash = 0;
Vector<AttachmentInfo> attachments;
Vector<VkFormat> colorAttachmentFormats;
Array<Int32, static_cast<SizeT>(FramebufferAttachmentType::FramebufferAttachmentTypeCount)> attachmentIndex =
{};
FramebufferObject::FramebufferAttachmentVersionArray syncedAttachmentVersions = {0};
FramebufferObject::FramebufferAttachmentArray frontendDrawBuffers = {FramebufferAttachmentType::None};
Array<Int32, FramebufferObject::MAX_DRAW_BUFFERS> drawBufferAttachmentIndices = {};
FramebufferAttachmentType frontendReadBuffer = FramebufferAttachmentType::Color0;
Uint64 configHash = 0;
Uint16 objectVersion = 0;
Bool IsValid() const { return !attachments.empty() && extent.width > 0 && extent.height > 0; }
};
struct VulkanState {
UniquePtr<DV::VulkanContext> ctx;
UniquePtr<DV::SwapchainManager> swapchain;
UniquePtr<DV::ProgramManager> programMgr;
VkCommandPool commandPool = VK_NULL_HANDLE;
Vector<UniquePtr<DV::FrameContext>> frames;
Vector<FrameDescriptorPools> frameDescriptorPools;
Vector<BufferResource> stagingPool;
Vector<Vector<BufferResource>> stagingPending;
Uint32 currentFrame = 0;
Uint32 maxFramesInFlight = 2;
Bool initialized = false;
UintVec2 viewportSize{0, 0};
VkImage depthImage = VK_NULL_HANDLE;
VkDeviceMemory depthMemory = VK_NULL_HANDLE;
VkImageView depthView = VK_NULL_HANDLE;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
Uint64 defaultRenderingCompatHash = 0;
BufferResource nullUbo;
TextureResource defaultTexture;
SamplerResource defaultSampler;
UnorderedMap<const BufferObject*, BufferResourceSet> buffers;
UnorderedMap<const ITextureObject*, TextureResource> textures;
UnorderedMap<const RenderbufferObject*, RenderbufferResource> renderbuffers;
UnorderedMap<const SamplerObject*, SamplerResource> samplers;
UnorderedMap<const ProgramObject*, ProgramResource> programs;
UnorderedMap<SharedPtr<MG_State::GLState::FramebufferObject>, SharedPtr<BackendFramebufferObject>> framebuffers;
Vector<PendingBufferCopyBatch> pendingBufferCopies;
Vector<StagingRing> stagingRings;
VkExtent2D activeExtent{0, 0};
Vector<VkImageView> activeColorViews;
Vector<VkImageLayout> activeColorLayouts;
Vector<VkFormat> activeColorFormats;
Vector<Int32> activeColorAttachmentIndices;
VkImageView activeDepthView = VK_NULL_HANDLE;
VkImageLayout activeDepthLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageView activeStencilView = VK_NULL_HANDLE;
VkImageLayout activeStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Uint32 activeSwapchainImageIndex = ~0u;
Uint32 activeColorAttachmentCount = 1;
Bool activeHasDepth = false;
VkFormat activeDepthFormat = VK_FORMAT_UNDEFINED;
VkFormat activeDepthAttachmentFormat = VK_FORMAT_UNDEFINED;
VkFormat activeStencilAttachmentFormat = VK_FORMAT_UNDEFINED;
Uint64 activeRenderingCompatHash = 0;
Uint64 activeRenderingConfigHash = 0;
SharedPtr<MG_State::GLState::FramebufferObject> activeStateFBO = nullptr;
SharedPtr<BackendFramebufferObject> activeBackendFBO = nullptr;
Bool activeIsDefault = true;
Bool activeDefaultColorWrites = true;
Uint64 recordingRenderingConfigHash = 0;
Bool cmdBufferBegun = false;
Bool recording = false;
Bool frameSubmitted = false;
FloatVec4 clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
Float clearDepth = 1.0f;
UnorderedMap<SharedPtr<FramebufferObject>, PendingClearInfo> pendingClears;
Vector<VkImageLayout> swapchainImageLayouts;
PFN_vkCmdBeginRendering pfnCmdBeginRendering = nullptr;
PFN_vkCmdEndRendering pfnCmdEndRendering = nullptr;
PFN_vkWaitSemaphores pfnWaitSemaphores = nullptr;
VkSemaphore timelineSemaphore = VK_NULL_HANDLE;
Uint64 timelineValue = 0;
};
void Present();
void FrameBegin();
void InitVulkan(ANativeWindow* window);
const VulkanState& GetVulkanState();
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
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 Clear(GLbitfield mask);
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
void DrawArrays(GLenum mode, GLint first, GLsizei count);
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount);
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);
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex);
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex);
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLuint baseinstance);
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
GLuint baseinstance);
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
void DrawArraysIndirect(GLenum mode, const void* indirect);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter);
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height);
void GenerateMipmap(GLenum target);
} // namespace MobileGL::MG_Backend::DirectVulkanTMP::TmpImpl
+3
View File
@@ -52,6 +52,9 @@ namespace MobileGL::MG_Backend {
case BackendType::DirectGLES:
pActiveBackendObject = MakeUnique<DirectGLES::BackendObject_DirectGLES>();
break;
case BackendType::DirectVulkanTMP:
pActiveBackendObject = MakeUnique<DirectVulkanTMP::BackendObject_DirectVulkanTMP>();
break;
case BackendType::Unknown:
default:
MGLOG_W("Unknown backend type, defaulting to unknown backend");
@@ -7,8 +7,6 @@
// End of Source File Header
#include "GL_Getter.h"
#include "GL/gl.h"
#include "MG_Util/Debug/Log.h"
#include <Config.h>
#include <MGGitHash.h>
#include <MG_State/GLState/Core.h>
@@ -110,6 +110,11 @@ namespace MobileGL {
AddDefaultFragmentShaderIfMissing();
}
std::sort(m_shaders.begin(), m_shaders.end(),
[](SharedPtr<ShaderObject>& a, SharedPtr<ShaderObject>& b) {
return a->GetShaderStage() < b->GetShaderStage();
});
for (SizeT i = 0; i < m_shaders.size(); i++) {
shaderTypes[i] = MG_Util::ConvertShaderStageToGLEnum(m_shaders[i]->GetShaderStage());
MGLOG_D("ProgramObject %u: Preparing shader[%zu] stage %s at %p", m_externalIndex, i,
@@ -112,6 +112,13 @@ namespace MobileGL {
Vector<Vector<unsigned>>& GetGeneratedSpirv() { return m_generatedSpirv; }
Int GetShaderIndexByStage(ShaderStage stage) const {
auto it = std::find_if(
m_shaders.begin(), m_shaders.end(),
[stage](const SharedPtr<ShaderObject>& shader) { return shader->GetShaderStage() == stage; });
return it == m_shaders.end() ? -1 : std::distance(m_shaders.begin(), it);
}
Uint GetExternalIndex() const { return m_externalIndex; }
// const UnorderedMap<String, Uint>& GetAttribLocationMap() const { return
@@ -53,4 +53,4 @@ namespace MobileGL {
}
} // namespace GLState
} // namespace MG_State
} // namespace MobileGL
} // namespace MobileGL
@@ -17,6 +17,7 @@ namespace MobileGL {
Geometry,
Fragment,
Compute,
ShaderStageCount,
Unknown = -1
};
+1 -1
View File
@@ -983,7 +983,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) {
auto programObject = MG_State::pGLContext->GetCurrentProgram();
auto& spirvs = programObject->GetGeneratedSpirv();
auto& fragSpirv = spirvs[0]; // 0 - fragment, 1 - vertex
auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)];
char* pSrcfragOut = nullptr;
const char* needle = "layout(location = 7) out vec4 fragColor;";
// for (auto spirv: spirvs) {
@@ -0,0 +1,51 @@
// MobileGL - MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.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 "Loader.h"
namespace MobileGL::MG_Util::BackendLoader {
inline Version DecodeApiVersion(uint32_t version) {
return {(Int)VK_VERSION_MAJOR(version), (Int)VK_VERSION_MINOR(version), (Int)VK_VERSION_PATCH(version)};
}
inline std::string DecodeDriverVersion(uint32_t driverVersion) {
std::ostringstream oss;
oss << VK_VERSION_MAJOR(driverVersion) << "." << VK_VERSION_MINOR(driverVersion) << "."
<< VK_VERSION_PATCH(driverVersion);
return oss.str();
}
Bool QueryVulkanCapabilities(MobileGL::MG_External::VulkanCapabilities& caps, VkPhysicalDevice physicalDevice) {
if (!physicalDevice) {
MGLOG_E("Invalid physical device handle");
return false;
}
VkPhysicalDeviceProperties props{};
vkGetPhysicalDeviceProperties(physicalDevice, &props);
if (props.apiVersion < VK_API_VERSION_1_1) {
MGLOG_E("Vulkan API version %u.%u.%u is not supported, requires at least 1.1",
VK_VERSION_MAJOR(props.apiVersion), VK_VERSION_MINOR(props.apiVersion),
VK_VERSION_PATCH(props.apiVersion));
return false;
}
VkPhysicalDeviceProperties2 props2{};
props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
vkGetPhysicalDeviceProperties2(physicalDevice, &props2);
const VkPhysicalDeviceProperties& p = props2.properties;
caps.VulkanAPIVersion = DecodeApiVersion(p.apiVersion);
caps.DeviceName = p.deviceName;
caps.DriverVersionString = DecodeDriverVersion(p.driverVersion);
caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment);
return true;
}
} // namespace MobileGL::MG_Util::BackendLoader
@@ -0,0 +1,24 @@
// MobileGL - MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.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 <Includes.h>
namespace MobileGL {
namespace MG_External {
struct VulkanCapabilities {
Version VulkanAPIVersion{1, 0, 0};
String DeviceName;
String DriverVersionString;
Int UniformBufferOffsetAlignment = 256;
};
} // namespace MG_External
namespace MG_Util::BackendLoader {
Bool QueryVulkanCapabilities(MobileGL::MG_External::VulkanCapabilities& caps, VkPhysicalDevice physicalDevice);
} // namespace MG_Util::BackendLoader
} // namespace MobileGL
@@ -16,5 +16,6 @@ namespace MobileGL {
TextureInternalFormat ConvertInternalFormatToSized(TextureInternalFormat internalformat,
TextureInputFormat format, TexturePixelDataType type);
TextureInternalFormat ConvertInternalFormatToUnsized(TextureInternalFormat internalformat);
} // namespace MG_Util
} // namespace MobileGL
@@ -14,7 +14,6 @@ namespace MobileGL {
NoNorm16 = 1 << 0,
None = 0,
};
namespace MG_Util::TextureFormatProcessor {
void NormalizePixelFormat(GLenum internalFormat, Flags<PixelFormatNormalizeOptionBit> options,
GLenum* outInternalFormat, GLenum* outFormat, GLenum* outType);