mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
Compare commits
145
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22d2d64f2 | ||
|
|
bd8bc1e251 | ||
|
|
22416cf2f0 | ||
|
|
8bdf990810 | ||
|
|
9e90d9508b | ||
|
|
7ccf858181 | ||
|
|
3c66016489 | ||
|
|
b88042bdff | ||
|
|
6540c2eec1 | ||
|
|
d5940f40be | ||
|
|
033ba16aff | ||
|
|
47467ea1ab | ||
|
|
d84b4da766 | ||
|
|
07b7f7eba5 | ||
|
|
7f9953fdee | ||
|
|
ccd51bf2d3 | ||
|
|
f79b7a91d7 | ||
|
|
ea71b3d96f | ||
|
|
f96f4a0a3b | ||
|
|
9f062fbd69 | ||
|
|
56428df5e5 | ||
|
|
25a5fe0757 | ||
|
|
2104d9865d | ||
|
|
dcb8bb1871 | ||
|
|
6b656d3f96 | ||
|
|
c2a0db3d12 | ||
|
|
c810b27a83 | ||
|
|
25f6c67859 | ||
|
|
5f78ff2b51 | ||
|
|
9226ea092c | ||
|
|
12d514229c | ||
|
|
b69b6b2987 | ||
|
|
377b967996 | ||
|
|
e132e9edc1 | ||
|
|
49a18f68e1 | ||
|
|
fe6e7902d3 | ||
|
|
f7ea6cd1d9 | ||
|
|
cf229c4a5b | ||
|
|
058ec6d26e | ||
|
|
098f8d0738 | ||
|
|
5f6a0b5969 | ||
|
|
49b0743422 | ||
|
|
13aa88b5ef | ||
|
|
e055b9a644 | ||
|
|
324498ccac | ||
|
|
206d8ab36a | ||
|
|
53989d1711 | ||
|
|
7b7e00dbb3 | ||
|
|
7d4ebfc305 | ||
|
|
dfe91e96a5 | ||
|
|
4d61f11bb8 | ||
|
|
5412d1c165 | ||
|
|
3ce7a2fd46 | ||
|
|
f8f3d6e657 | ||
|
|
781bda2573 | ||
|
|
d06720f534 | ||
|
|
a3b440e7c5 | ||
|
|
ebdadcecff | ||
|
|
876d5e13af | ||
|
|
2ad1207a31 | ||
|
|
91326025f3 | ||
|
|
3324dbfbf6 | ||
|
|
e1b59bcb46 | ||
|
|
8f57e837b8 | ||
|
|
6b7de70bf3 | ||
|
|
0ac4d8658d | ||
|
|
1a4fde893f | ||
|
|
e01ebc8fc7 | ||
|
|
533e6bd57c | ||
|
|
ad822e1095 | ||
|
|
129091a430 | ||
|
|
1a91e48219 | ||
|
|
4bcedc84b9 | ||
|
|
1357435e94 | ||
|
|
7c67c99717 | ||
|
|
a58016f49d | ||
|
|
8a66b0a542 | ||
|
|
92cdcd952b | ||
|
|
d7156631d1 | ||
|
|
81125dd06d | ||
|
|
7bd4d74b2c | ||
|
|
b25562711a | ||
|
|
927d95fb41 | ||
|
|
843fdcb47a | ||
|
|
ad6ffbe42e | ||
|
|
5f94f72be5 | ||
|
|
0ce95894a3 | ||
|
|
beb9301282 | ||
|
|
0822cb871d | ||
|
|
bfea5dc8ba | ||
|
|
9fb5a38172 | ||
|
|
74ff24e4a4 | ||
|
|
178dbd33ba | ||
|
|
f5e59b1c84 | ||
|
|
ba7bf87ee0 | ||
|
|
99bc2e121b | ||
|
|
341749706d | ||
|
|
a25bfcb0d0 | ||
|
|
b2ea29b91d | ||
|
|
a28f5eeee6 | ||
|
|
2febb10319 | ||
|
|
ae3a27bf06 | ||
|
|
5a751a6745 | ||
|
|
0a7061f375 | ||
|
|
c7db89a200 | ||
|
|
a6784e7714 | ||
|
|
a9b92a756b | ||
|
|
52eb6b3f2a | ||
|
|
43e59d8fce | ||
|
|
ceaa6ff5f8 | ||
|
|
a77088eeb3 | ||
|
|
162dbf8750 | ||
|
|
1c2924eef9 | ||
|
|
acc826ee9f | ||
|
|
8e58875b8f | ||
|
|
51451e8de3 | ||
|
|
b806397ae3 | ||
|
|
358efe4d1b | ||
|
|
cd3ff1f881 | ||
|
|
6c10876532 | ||
|
|
ce8a2356d7 | ||
|
|
37f26d1bb1 | ||
|
|
22bc4fbbc9 | ||
|
|
18941f9e83 | ||
|
|
0b332bdc58 | ||
|
|
2abdebcd96 | ||
|
|
e39af3b940 | ||
|
|
3b2c2028b9 | ||
|
|
55020a1e86 | ||
|
|
6d4ddc47f6 | ||
|
|
2aff5dc650 | ||
|
|
7aa7b82628 | ||
|
|
fd530156a6 | ||
|
|
56c98041e3 | ||
|
|
6eeb67f08b | ||
|
|
9a6cf7fc7f | ||
|
|
938b92277f | ||
|
|
419ffb3b5e | ||
|
|
ad7d145ece | ||
|
|
516566321d | ||
|
|
7918300848 | ||
|
|
00679642d6 | ||
|
|
4769a9cf7b | ||
|
|
9f9e955b92 | ||
|
|
85f7c8f4a6 |
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
@@ -27,6 +28,13 @@ jobs:
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: ${{env.BENCH_ROOT}}/3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
@@ -34,7 +42,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: |
|
||||
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -27,6 +28,13 @@ jobs:
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: ${{env.TEST_ROOT}}/3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
@@ -34,7 +42,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: |
|
||||
|
||||
@@ -14,3 +14,8 @@ MobileGLCodeManager
|
||||
.vscode
|
||||
.clangd
|
||||
MobileGL/MG_Test/build
|
||||
/build_*
|
||||
/cmake-build*
|
||||
.idea
|
||||
MobileGL/MG*/build*
|
||||
MobileGL/MG*/cmake-build*
|
||||
@@ -13,3 +13,9 @@
|
||||
[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
|
||||
[submodule "3rdparty/VulkanMemoryAllocator"]
|
||||
path = 3rdparty/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
|
||||
+1
Submodule 3rdparty/VulkanMemoryAllocator added at e722e57c89
+1
Submodule 3rdparty/xxHash added at 1d7b2a9d21
+62
-7
@@ -107,6 +107,11 @@ set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE)
|
||||
# add_subdirectory(3rdparty/DiligentCore)
|
||||
add_subdirectory(3rdparty/glslang)
|
||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||
|
||||
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)
|
||||
|
||||
@@ -123,6 +128,7 @@ endif ()
|
||||
set(SOURCE_FILES
|
||||
MobileGL/Init.cpp
|
||||
MobileGL/GlobalObjects.cpp
|
||||
MobileGL/ConfigLoader.cpp
|
||||
|
||||
MobileGL/MG_Util/Debug/Log.cpp
|
||||
|
||||
@@ -164,6 +170,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,7 +211,24 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Backend/DirectGLES/Utils.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/Managers.cpp
|
||||
|
||||
MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/VmaImpl.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp
|
||||
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/VertexInputStateBuilder.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp
|
||||
|
||||
MobileGL/MG_State/GLState/Core.cpp
|
||||
MobileGL/MG_State/EGLState/Core.cpp
|
||||
MobileGL/MG_State/GLState/ErrorState/Error.cpp
|
||||
MobileGL/MG_State/GLState/BufferState/BufferState.cpp
|
||||
MobileGL/MG_State/GLState/BufferState/BufferObject.cpp
|
||||
@@ -236,8 +260,18 @@ set(MOBILEGL_LINK_LIBRARIES
|
||||
spirv-cross-c
|
||||
SPIRV-Tools-opt
|
||||
SPIRV-Tools
|
||||
xxHash::xxhash
|
||||
GPUOpen::VulkanMemoryAllocator
|
||||
)
|
||||
|
||||
set(MOBILEGL_COMPILE_DEF
|
||||
-DVMA_STATIC_VULKAN_FUNCTIONS=0
|
||||
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
||||
-DVMA_VULKAN_VERSION=1001000
|
||||
)
|
||||
|
||||
message(STATUS "MOBILEGL_COMPILE_DEF=${MOBILEGL_COMPILE_DEF}")
|
||||
|
||||
set(MOBILEGL_INCLUDE_DIR
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/MobileGL
|
||||
@@ -270,10 +304,15 @@ target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
${MOBILEGL_LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
)
|
||||
|
||||
if(NOT ANDROID)
|
||||
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
||||
${SOURCE_FILES}
|
||||
@@ -298,9 +337,14 @@ if(NOT ANDROID)
|
||||
)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
${MOBILEGL_LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (TRACY_ENABLE)
|
||||
@@ -314,13 +358,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()
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace MobileGL::MG_Config {
|
||||
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
||||
inline const Version CoreVersion = {26, 2, 0, "-dev", VersionType::Development};
|
||||
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||
inline const Uint64 CacheVersion = 0;
|
||||
|
||||
extern BackendType ActiveBackendType;
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// MobileGL - MobileGL/ConfigLoader.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 "Config.h"
|
||||
|
||||
namespace MobileGL::MG_ConfigLoader {
|
||||
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
||||
|
||||
static Bool IsAcceptedPrefix(const String& key) {
|
||||
return (key.compare(0, 6, "LIBGL_") == 0 || key.compare(0, 9, "MOBILEGL_") == 0);
|
||||
}
|
||||
|
||||
inline void InitializeAcceptedEnvVariables() {
|
||||
if (!acceptedEnvVariablesMap) {
|
||||
acceptedEnvVariablesMap = MakeUnique<UnorderedMap<String, String>>();
|
||||
} else {
|
||||
acceptedEnvVariablesMap->clear();
|
||||
}
|
||||
|
||||
char** envPtr = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
envPtr = _environ;
|
||||
#else // POSIX
|
||||
envPtr = ::environ;
|
||||
#endif
|
||||
|
||||
if (envPtr == nullptr) return;
|
||||
|
||||
for (char** env = envPtr; *env != nullptr; ++env) {
|
||||
String entry(*env);
|
||||
SizeT pos = entry.find('=');
|
||||
if (pos != String::npos) {
|
||||
String key = entry.substr(0, pos);
|
||||
String value = entry.substr(pos + 1);
|
||||
|
||||
if (IsAcceptedPrefix(key)) {
|
||||
(*acceptedEnvVariablesMap)[key] = value;
|
||||
MGLOG_D("Config: Accepted env variable: %s=%s", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void QueryEnvVariable(const String& key, String& outValue, const String& defaultValue) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
if (it != acceptedEnvVariablesMap->end()) {
|
||||
outValue = it->second;
|
||||
} else {
|
||||
outValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
inline void InitBackendType() {
|
||||
String backendTypeStr;
|
||||
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
||||
#define ENTRY(backendType) \
|
||||
if (backendTypeStr == #backendType) { \
|
||||
MG_Config::ActiveBackendType = BackendType::backendType; \
|
||||
MGLOG_I("Config: Active backend type set to " #backendType); \
|
||||
return; \
|
||||
}
|
||||
ENTRY(DirectGLES)
|
||||
ENTRY(DirectVulkan)
|
||||
ENTRY(Unknown)
|
||||
MG_Config::ActiveBackendType = BackendType::Unknown;
|
||||
#undef ENTRY
|
||||
}
|
||||
|
||||
void Init() {
|
||||
MGLOG_D("Loading configuration from environment variables...");
|
||||
InitializeAcceptedEnvVariables();
|
||||
|
||||
InitBackendType();
|
||||
|
||||
// Destroy the map since we won't need it anymore
|
||||
acceptedEnvVariablesMap.reset();
|
||||
}
|
||||
} // namespace MobileGL::MG_ConfigLoader
|
||||
+19
-4
@@ -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,6 +31,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
@@ -40,8 +43,6 @@
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <bitset>
|
||||
#if __cplusplus >= 202302L && MOBILEGL_LOG_ENABLE_STACKTRACE
|
||||
#include <stacktrace>
|
||||
#endif
|
||||
@@ -49,6 +50,9 @@
|
||||
// Include FastSTL
|
||||
#include <FastSTL/UnorderedMap.h>
|
||||
|
||||
// Include xxHash
|
||||
#include <xxhash.h>
|
||||
|
||||
// Include spirv_cross
|
||||
#include <spirv_cross/spirv_cross_c.h>
|
||||
|
||||
@@ -58,7 +62,9 @@
|
||||
#endif
|
||||
#include <EGL/egl.h>
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#ifndef NO_GL_H
|
||||
#include "GL/gl.h"
|
||||
#endif
|
||||
#include <GL/glcorearb.h>
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
@@ -93,11 +99,20 @@
|
||||
#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
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define VK_USE_PLATFORM_ANDROID_KHR
|
||||
#elif _WIN32
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#elif defined(__APPLE__)
|
||||
#define VK_USE_PLATFORM_METAL_EXT
|
||||
#else
|
||||
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#include <tracy/Tracy.hpp>
|
||||
#define TRACY_ZONECOLOR_ENTRY 0xFF0000
|
||||
|
||||
+13
-10
@@ -8,31 +8,34 @@
|
||||
|
||||
#include "Init.h"
|
||||
#include "Config.h"
|
||||
#include <MG_Impl/Init.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
|
||||
namespace MobileGL {
|
||||
void MG_Initialize() {
|
||||
void Initialize() {
|
||||
MG_Util::Debug::InitFile();
|
||||
MGLOG_I("Initializing MobileGL...");
|
||||
MG_ConfigLoader::Init();
|
||||
MGLOG_I("Config loaded");
|
||||
MG_State::Init();
|
||||
MGLOG_D("MobileGL State initialized");
|
||||
MGLOG_D("MG_State initialized");
|
||||
MG_Backend::Init();
|
||||
MGLOG_D("MobileGL Backend initialized");
|
||||
MGLOG_D("MG_Backend initialized");
|
||||
MG_Impl::Init();
|
||||
MGLOG_D("MobileGL Implementation initialized");
|
||||
MGLOG_D("MG_Impl initialized");
|
||||
glslang::InitializeProcess();
|
||||
MGLOG_D("glslang initialized");
|
||||
MGLOG_I("MobileGL initialized");
|
||||
}
|
||||
|
||||
void MG_Destroy() {
|
||||
void Destroy() {
|
||||
MGLOG_I("MobileGL closing...");
|
||||
glslang::FinalizeProcess();
|
||||
delete MG_State::pGLContext;
|
||||
delete MG_State::pEGLContext;
|
||||
delete MG_Impl::GLImpl::TextureImpl::pProxyTextureManager;
|
||||
delete MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
MG_Util::Debug::Close();
|
||||
@@ -42,11 +45,11 @@ namespace MobileGL {
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
__attribute__((constructor)) static void AutoInit() {
|
||||
MG_Initialize();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
__attribute__((destructor)) static void AutoDestroy() {
|
||||
MG_Destroy();
|
||||
Destroy();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -54,11 +57,11 @@ namespace MobileGL {
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
MG_Initialize();
|
||||
Initialize();
|
||||
break;
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
MG_Destroy();
|
||||
Destroy();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
+19
-3
@@ -10,6 +10,22 @@
|
||||
#include "Includes.h"
|
||||
|
||||
namespace MobileGL {
|
||||
void MG_Initialize();
|
||||
void MG_Destroy();
|
||||
} // namespace MobileGL
|
||||
void Initialize();
|
||||
void Destroy();
|
||||
|
||||
namespace MG_Util::Debug {
|
||||
void InitFile();
|
||||
} // namespace MG_Util::Debug
|
||||
|
||||
namespace MG_ConfigLoader {
|
||||
void Init();
|
||||
} // namespace MG_ConfigLoader
|
||||
|
||||
namespace MG_Backend {
|
||||
void Init();
|
||||
} // namespace MG_Backend
|
||||
|
||||
namespace MG_Impl {
|
||||
void Init();
|
||||
} // namespace MG_Impl
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -9,7 +9,133 @@
|
||||
#include "BackendObject.h"
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
std::thread::id CurrentThreadKey() {
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (dpy == EGL_NO_DISPLAY) {
|
||||
MGLOG_E("InitializeEGLDisplay failed: invalid EGLDisplay");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_eglDisplayInitialized && m_eglDisplay != dpy) {
|
||||
MGLOG_E("InitializeEGLDisplay failed: backend already bound to a different EGLDisplay");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglDisplay = dpy;
|
||||
m_eglDisplayInitialized = true;
|
||||
if (major) {
|
||||
*major = 1;
|
||||
}
|
||||
if (minor) {
|
||||
*minor = 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::CreateEGLWindowSurface(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: EGL display is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: invalid native window handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SetWindowHandle(handle);
|
||||
if (!InitWindowSurface()) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: backend InitWindowSurface failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglWindowSurfaceInitialized = true;
|
||||
m_eglCurrentThreads.clear();
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
const auto threadKey = CurrentThreadKey();
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
m_eglCurrentThreads.erase(threadKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglWindowSurfaceInitialized) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL window surface is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
||||
MGLOG_E("MakeEGLCurrent failed: draw/read/context is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_backendCapabilitiesInitialized) {
|
||||
if (!InitCapabilities()) {
|
||||
MGLOG_E("MakeEGLCurrent failed: InitCapabilities failed");
|
||||
return false;
|
||||
}
|
||||
m_backendCapabilitiesInitialized = true;
|
||||
}
|
||||
|
||||
m_eglCurrentThreads[threadKey] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::ResetEGLRuntimeState() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_eglWindowSurfaceInitialized = false;
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
m_eglCurrentThreads.clear();
|
||||
}
|
||||
|
||||
Bool BackendObject::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
|
||||
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
if (m_eglCurrentThreads.find(CurrentThreadKey()) == m_eglCurrentThreads.end()) {
|
||||
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglWindowSurfaceInitialized || draw == EGL_NO_SURFACE) {
|
||||
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& backendFunctions = GetBackendFunctions();
|
||||
if (!backendFunctions.Present) {
|
||||
MGLOG_E("SwapEGLBuffers failed: backend Present function is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
backendFunctions.Present();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_windowHandle = handle;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace MobileGL {
|
||||
enum class BackendType {
|
||||
DirectGLES,
|
||||
DirectVulkan,
|
||||
BackendTypeCount,
|
||||
Unknown = -1
|
||||
};
|
||||
@@ -90,8 +91,13 @@ namespace MobileGL {
|
||||
virtual ~BackendObject() = default;
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual void InitCapabilities() = 0;
|
||||
virtual void InitWindowSurface() = 0;
|
||||
virtual Bool InitCapabilities() = 0;
|
||||
virtual Bool InitWindowSurface() = 0;
|
||||
|
||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||
virtual Bool CreateEGLWindowSurface(const WindowHandle& handle);
|
||||
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||
|
||||
void SetWindowHandle(const WindowHandle& handle);
|
||||
|
||||
@@ -102,7 +108,15 @@ namespace MobileGL {
|
||||
virtual BackendType GetBackendType() const = 0;
|
||||
|
||||
protected:
|
||||
void ResetEGLRuntimeState();
|
||||
|
||||
mutable std::recursive_mutex m_eglStateMutex;
|
||||
WindowHandle m_windowHandle;
|
||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||
Bool m_eglDisplayInitialized = false;
|
||||
Bool m_eglWindowSurfaceInitialized = false;
|
||||
Bool m_backendCapabilitiesInitialized = false;
|
||||
UnorderedMap<std::thread::id, Bool> m_eglCurrentThreads;
|
||||
};
|
||||
} // namespace MG_Backend
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
#include <Includes.h>
|
||||
#include "BackendObject.h"
|
||||
#include "DirectGLES/BackendObject_DirectGLES.h"
|
||||
#include "DirectVulkan/BackendObject_DirectVulkan.h"
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
extern UniquePtr<BackendObject> pActiveBackendObject;
|
||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||
|
||||
void Init();
|
||||
} // namespace MobileGL::MG_Backend
|
||||
|
||||
@@ -13,16 +13,24 @@
|
||||
#include <format>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace {
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BackendObject_DirectGLES::~BackendObject_DirectGLES() {
|
||||
DestroyEGLContext();
|
||||
}
|
||||
|
||||
void BackendObject_DirectGLES::InitWindowSurface() {
|
||||
Bool BackendObject_DirectGLES::InitWindowSurface() {
|
||||
// Only use EGL for now
|
||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
||||
if (!DirectGLES::InitWindowSurface(nativeWindow)) {
|
||||
MGLOG_E("Failed to initialize window surface for DirectGLES backend");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject_DirectGLES::Initialize() {
|
||||
@@ -40,18 +48,65 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DirectGLES::SetGLESFuncsTable(m_GLESFunctions);
|
||||
}
|
||||
|
||||
void BackendObject_DirectGLES::InitCapabilities() {
|
||||
Bool BackendObject_DirectGLES::InitCapabilities() {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectGLES backend not initialized");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MG_Util::BackendLoader::FillInGLESCapabilities(m_GLESCapabilities, m_GLESFunctions)) {
|
||||
MGLOG_E("Failed to fill in GLES capabilities for DirectGLES backend");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
|
||||
UpdateDynamicBackendParameters();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectGLES::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectGLES backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectGLES::CreateEGLWindowSurface(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectGLES backend not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handle.Backend != WindowBackend::Android || !handle.Handle) {
|
||||
MGLOG_E("DirectGLES backend only supports Android native windows");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool sameHandle =
|
||||
m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
||||
if (sameHandle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_eglWindowSurfaceInitialized) {
|
||||
DestroyEGLContext();
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
|
||||
return BackendObject::CreateEGLWindowSurface(handle);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectGLES::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectGLES::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
||||
}
|
||||
|
||||
const RendererInfo& BackendObject_DirectGLES::GetRendererInfo() const {
|
||||
|
||||
@@ -17,8 +17,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
~BackendObject_DirectGLES() override;
|
||||
|
||||
void Initialize() override;
|
||||
void InitCapabilities() override;
|
||||
void InitWindowSurface() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.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_DirectVulkan.h"
|
||||
#include "MG_Backend/BackendObject.h"
|
||||
#include "DirectVulkan.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||
if (!m_windowHandle.Handle) {
|
||||
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
||||
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(nativeWindow);
|
||||
pVulkanRenderer->Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::Initialize() {
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitCapabilities() {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("Cannot initialize capabilities before backend is initialized");
|
||||
return false;
|
||||
}
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("Cannot initialize capabilities: Vulkan renderer has not been created");
|
||||
return false;
|
||||
}
|
||||
|
||||
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetPhysicalDevice().properties);
|
||||
UpdateDynamicBackendParameters();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (handle.Backend != WindowBackend::Android || !handle.Handle) {
|
||||
MGLOG_E("DirectVulkan backend only supports Android native windows");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool sameHandle =
|
||||
m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
||||
if (sameHandle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_eglWindowSurfaceInitialized || pVulkanRenderer) {
|
||||
pVulkanRenderer.reset();
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
|
||||
return BackendObject::CreateEGLWindowSurface(handle);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("DirectVulkan renderer is not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("DirectVulkan renderer is not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
||||
}
|
||||
|
||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||
static RendererInfo RendererInfo = {
|
||||
.RendererName = "Magma", // Renderer Name
|
||||
.BackendName = "Direct (Vulkan)", // 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_DirectVulkan::GetBackendAPIVersionString() const {
|
||||
if (!m_initialized) {
|
||||
return "<uninitialized DirectVulkan backend>";
|
||||
}
|
||||
// Format:
|
||||
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
||||
String str = m_vulkanCaps.DeviceName + ", Vulkan " + m_vulkanCaps.VulkanAPIVersion.toString() + ", Driver " +
|
||||
m_vulkanCaps.DriverVersionString;
|
||||
return str;
|
||||
}
|
||||
|
||||
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
||||
return BackendType::DirectVulkan;
|
||||
}
|
||||
|
||||
const GlobalBackendFunctionsTable& BackendObject_DirectVulkan::GetBackendFunctions() const {
|
||||
static GlobalBackendFunctionsTable funcsTable;
|
||||
static Bool funcsTableInitialized = false;
|
||||
if (!funcsTableInitialized) {
|
||||
funcsTable.Present = 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_DirectVulkan::GetDynamicParameters() const {
|
||||
return m_dynamicParameters;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,40 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.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::DirectVulkan {
|
||||
class BackendObject_DirectVulkan : public BackendObject {
|
||||
public:
|
||||
~BackendObject_DirectVulkan() override;
|
||||
|
||||
void Initialize() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) 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::DirectVulkan
|
||||
@@ -0,0 +1,460 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkan.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 "DirectVulkan.h"
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
UniquePtr<VulkanRenderer> pVulkanRenderer = nullptr;
|
||||
|
||||
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 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");
|
||||
|
||||
if (count < 0) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: count (%d) must be non-negative", count);
|
||||
return;
|
||||
}
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
if (mode != GL_TRIANGLES) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: primitive mode %u is not supported yet", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
SizeT indexSize = 0;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_SHORT:
|
||||
indexSize = sizeof(Uint16);
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
indexSize = sizeof(Uint32);
|
||||
break;
|
||||
default:
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: index type %u is not supported yet", type);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: no bound VAO");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
if (!indexBuffer) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: no bound ELEMENT_ARRAY_BUFFER");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexData = indexBuffer->GetDataReadOnly();
|
||||
if (!indexData || indexData->empty()) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: ELEMENT_ARRAY_BUFFER has no data");
|
||||
return;
|
||||
}
|
||||
|
||||
const SizeT byteOffset = reinterpret_cast<SizeT>(indices);
|
||||
const SizeT requiredBytes = static_cast<SizeT>(count) * indexSize;
|
||||
if (byteOffset + requiredBytes > indexBuffer->GetSize()) {
|
||||
MGLOG_W("DrawElementsBaseVertex skipped: index range out of bounds (offset=%zu, size=%zu, buffer=%zu)",
|
||||
byteOffset, requiredBytes, indexBuffer->GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
DrawElementPayload payload{};
|
||||
payload.drawArray.mode = mode;
|
||||
payload.drawArray.first = 0;
|
||||
payload.drawArray.count = count;
|
||||
const auto currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
payload.drawArray.program = currentProgram ? currentProgram.get() : nullptr;
|
||||
payload.drawArray.vertexArray = vao.get();
|
||||
payload.indexType = type;
|
||||
payload.indexByteOffset = byteOffset;
|
||||
payload.baseVertex = basevertex;
|
||||
|
||||
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::MultiDrawElementsBaseVertex called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElementsBaseVertex called with null GL context");
|
||||
|
||||
if (drawcount < 0) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: drawcount (%d) must be non-negative", drawcount);
|
||||
return;
|
||||
}
|
||||
if (drawcount == 0) {
|
||||
return;
|
||||
}
|
||||
if (!count || !indices || !basevertex) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: count/indices/basevertex pointer is null");
|
||||
return;
|
||||
}
|
||||
if (mode != GL_TRIANGLES) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: primitive mode %u is not supported yet", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
SizeT indexSize = 0;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_SHORT:
|
||||
indexSize = sizeof(Uint16);
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
indexSize = sizeof(Uint32);
|
||||
break;
|
||||
default:
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: index type %u is not supported yet", type);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: no bound VAO");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
if (!indexBuffer) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: no bound ELEMENT_ARRAY_BUFFER");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexData = indexBuffer->GetDataReadOnly();
|
||||
if (!indexData || indexData->empty()) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: ELEMENT_ARRAY_BUFFER has no data");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
Vector<DrawElementPayload> payloads;
|
||||
payloads.reserve(static_cast<SizeT>(drawcount));
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] < 0) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: count[%d] (%d) must be non-negative", i, count[i]);
|
||||
return;
|
||||
}
|
||||
if (count[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const SizeT byteOffset = reinterpret_cast<SizeT>(indices[i]);
|
||||
const SizeT requiredBytes = static_cast<SizeT>(count[i]) * indexSize;
|
||||
if (byteOffset + requiredBytes > indexBuffer->GetSize()) {
|
||||
MGLOG_W("MultiDrawElementsBaseVertex skipped: draw[%d] index range out of bounds (offset=%zu, "
|
||||
"size=%zu, buffer=%zu)",
|
||||
i, byteOffset, requiredBytes, indexBuffer->GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
DrawElementPayload payload{};
|
||||
payload.drawArray.mode = mode;
|
||||
payload.drawArray.first = 0;
|
||||
payload.drawArray.count = count[i];
|
||||
payload.drawArray.program = currentProgram ? currentProgram.get() : nullptr;
|
||||
payload.drawArray.vertexArray = vao.get();
|
||||
payload.indexType = type;
|
||||
payload.indexByteOffset = byteOffset;
|
||||
payload.baseVertex = basevertex[i];
|
||||
payloads.push_back(payload);
|
||||
}
|
||||
|
||||
if (payloads.empty()) {
|
||||
return;
|
||||
}
|
||||
pVulkanRenderer->MultiDrawElements(payloads);
|
||||
}
|
||||
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 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) {}
|
||||
|
||||
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");
|
||||
|
||||
if (drawcount < 0) {
|
||||
MGLOG_W("MultiDrawElements skipped: drawcount (%d) must be non-negative", drawcount);
|
||||
return;
|
||||
}
|
||||
if (drawcount == 0) {
|
||||
return;
|
||||
}
|
||||
if (!count || !indices) {
|
||||
MGLOG_W("MultiDrawElements skipped: count/indices pointer is null");
|
||||
return;
|
||||
}
|
||||
if (mode != GL_TRIANGLES) {
|
||||
MGLOG_W("MultiDrawElements skipped: primitive mode %u is not supported yet", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
SizeT indexSize = 0;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_SHORT:
|
||||
indexSize = sizeof(Uint16);
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
indexSize = sizeof(Uint32);
|
||||
break;
|
||||
default:
|
||||
MGLOG_W("MultiDrawElements skipped: index type %u is not supported yet", type);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
MGLOG_W("MultiDrawElements skipped: no bound VAO");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
if (!indexBuffer) {
|
||||
MGLOG_W("MultiDrawElements skipped: no bound ELEMENT_ARRAY_BUFFER");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexData = indexBuffer->GetDataReadOnly();
|
||||
if (!indexData || indexData->empty()) {
|
||||
MGLOG_W("MultiDrawElements skipped: ELEMENT_ARRAY_BUFFER has no data");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
Vector<DrawElementPayload> payloads;
|
||||
payloads.reserve(static_cast<SizeT>(drawcount));
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] < 0) {
|
||||
MGLOG_W("MultiDrawElements skipped: count[%d] (%d) must be non-negative", i, count[i]);
|
||||
return;
|
||||
}
|
||||
if (count[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto byteOffset = reinterpret_cast<SizeT>(indices[i]);
|
||||
const SizeT requiredBytes = static_cast<SizeT>(count[i]) * indexSize;
|
||||
if (byteOffset + requiredBytes > indexBuffer->GetSize()) {
|
||||
MGLOG_W("MultiDrawElements skipped: draw[%d] index range out of bounds (offset=%zu, size=%zu, "
|
||||
"buffer=%zu)",
|
||||
i, byteOffset, requiredBytes, indexBuffer->GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
DrawElementPayload payload{};
|
||||
payload.drawArray.mode = mode;
|
||||
payload.drawArray.first = 0;
|
||||
payload.drawArray.count = count[i];
|
||||
payload.drawArray.program = currentProgram ? currentProgram.get() : nullptr;
|
||||
payload.drawArray.vertexArray = vao.get();
|
||||
payload.indexType = type;
|
||||
payload.indexByteOffset = byteOffset;
|
||||
payloads.push_back(payload);
|
||||
}
|
||||
|
||||
if (payloads.empty()) {
|
||||
return;
|
||||
}
|
||||
pVulkanRenderer->MultiDrawElements(payloads);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
const auto& drawFboSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw);
|
||||
const auto drawFbo = drawFboSlot.GetBoundObject();
|
||||
const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr;
|
||||
const Bool isDefaultFboTarget = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr);
|
||||
Uint drawFboExternalIndex = 0;
|
||||
if (drawFbo) {
|
||||
drawFboExternalIndex = drawFbo->GetExternalIndex();
|
||||
} else if (defaultFbo) {
|
||||
drawFboExternalIndex = defaultFbo->GetExternalIndex();
|
||||
}
|
||||
|
||||
const auto& clearColor = MG_State::pGLContext->GetClearColor();
|
||||
const auto clearDepth = MG_State::pGLContext->GetClearDepth();
|
||||
const auto clearStencil = static_cast<Uint32>(MG_State::pGLContext->GetClearStencil());
|
||||
pVulkanRenderer->RequestClear(mask, clearColor, clearDepth, clearStencil, drawFboExternalIndex,
|
||||
isDefaultFboTarget);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if (count < 0) {
|
||||
MGLOG_W("DrawElements skipped: count (%d) must be non-negative", count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode != GL_TRIANGLES) {
|
||||
MGLOG_W("DrawElements skipped: primitive mode %u is not supported yet", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
SizeT indexSize = 0;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_SHORT:
|
||||
indexSize = sizeof(Uint16);
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
indexSize = sizeof(Uint32);
|
||||
break;
|
||||
default:
|
||||
MGLOG_W("DrawElements skipped: index type %u is not supported yet", type);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
MGLOG_W("DrawElements skipped: no bound VAO");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
||||
if (!indexBuffer) {
|
||||
MGLOG_W("DrawElements skipped: no bound ELEMENT_ARRAY_BUFFER");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indexData = indexBuffer->GetDataReadOnly();
|
||||
if (!indexData || indexData->empty()) {
|
||||
MGLOG_W("DrawElements skipped: ELEMENT_ARRAY_BUFFER has no data");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto byteOffset = reinterpret_cast<SizeT>(indices);
|
||||
const SizeT requiredBytes = static_cast<SizeT>(count) * indexSize;
|
||||
if (byteOffset + requiredBytes > indexBuffer->GetSize()) {
|
||||
MGLOG_W("DrawElements skipped: index range out of bounds (offset=%zu, size=%zu, buffer=%zu)", byteOffset,
|
||||
requiredBytes, indexBuffer->GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
DrawElementPayload payload{};
|
||||
payload.drawArray.mode = mode;
|
||||
payload.drawArray.first = 0;
|
||||
payload.drawArray.count = count;
|
||||
const auto currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
payload.drawArray.program = currentProgram ? currentProgram.get() : nullptr;
|
||||
payload.drawArray.vertexArray = vao.get();
|
||||
payload.indexType = type;
|
||||
payload.indexByteOffset = byteOffset;
|
||||
|
||||
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");
|
||||
|
||||
if (first < 0) {
|
||||
MGLOG_W("DrawArrays skipped: first (%d) must be non-negative", first);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count < 0) {
|
||||
MGLOG_W("DrawArrays skipped: count (%d) must be non-negative", count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode != GL_TRIANGLES) {
|
||||
MGLOG_W("DrawArrays skipped: primitive mode %u is not supported yet", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawArrayPayload payload{};
|
||||
payload.mode = mode;
|
||||
payload.first = first;
|
||||
payload.count = count;
|
||||
const auto currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
payload.program = currentProgram ? currentProgram.get() : nullptr;
|
||||
|
||||
const auto vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
payload.vertexArray = vao ? vao.get() : nullptr;
|
||||
|
||||
pVulkanRenderer->DrawArrays(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");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::BlitFramebuffer called with null GL context");
|
||||
|
||||
const auto& readFboSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read);
|
||||
const auto& drawFboSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw);
|
||||
const auto readFbo = readFboSlot.GetBoundObject();
|
||||
const auto drawFbo = drawFboSlot.GetBoundObject();
|
||||
const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr;
|
||||
|
||||
const Bool readIsDefault = (readFbo == defaultFbo) || (readFbo == nullptr && defaultFbo != nullptr);
|
||||
const Bool drawIsDefault = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr);
|
||||
|
||||
Uint readFboExternalIndex = 0;
|
||||
if (readFbo) {
|
||||
readFboExternalIndex = readFbo->GetExternalIndex();
|
||||
} else if (defaultFbo) {
|
||||
readFboExternalIndex = defaultFbo->GetExternalIndex();
|
||||
}
|
||||
|
||||
Uint drawFboExternalIndex = 0;
|
||||
if (drawFbo) {
|
||||
drawFboExternalIndex = drawFbo->GetExternalIndex();
|
||||
} else if (defaultFbo) {
|
||||
drawFboExternalIndex = defaultFbo->GetExternalIndex();
|
||||
}
|
||||
|
||||
pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter,
|
||||
readFboExternalIndex, drawFboExternalIndex, readIsDefault, drawIsDefault);
|
||||
}
|
||||
|
||||
void Present() {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Present called with null VulkanRenderer");
|
||||
pVulkanRenderer->Present();
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,55 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkan.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/VulkanRenderer.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
extern UniquePtr<VulkanRenderer> pVulkanRenderer;
|
||||
|
||||
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);
|
||||
void Present();
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,281 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/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::DirectVulkan {
|
||||
VkResult FrameContext::Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount) {
|
||||
Destroy(device, commandPool);
|
||||
m_frames.assign(frameCount, {});
|
||||
currentFrameIndex = 0;
|
||||
|
||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = frameCount;
|
||||
VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||
m_frames[i].commandBuffer = commandBuffers[i];
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
|
||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||
result = CreateSyncObjectsForFrame(device, i, semaphoreInfo, fenceInfo);
|
||||
if (result != VK_SUCCESS) {
|
||||
Destroy(device, commandPool);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) {
|
||||
const Uint32 frameCount = static_cast<Uint32>(m_frames.size());
|
||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||
commandBuffers[i] = m_frames[i].commandBuffer;
|
||||
}
|
||||
|
||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||
DestroySyncObjectsForFrame(device, i);
|
||||
}
|
||||
DestroySwapchainSemaphores(device);
|
||||
if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !m_frames.empty()) {
|
||||
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
||||
}
|
||||
m_frames.clear();
|
||||
currentFrameIndex = 0;
|
||||
}
|
||||
|
||||
FrameContext::FrameData& FrameContext::GetCurrent() {
|
||||
MOBILEGL_ASSERT(!m_frames.empty(), "FrameContext is not initialized");
|
||||
return m_frames[currentFrameIndex];
|
||||
}
|
||||
|
||||
const FrameContext::FrameData& FrameContext::GetCurrent() const {
|
||||
MOBILEGL_ASSERT(!m_frames.empty(), "FrameContext is not initialized");
|
||||
return m_frames[currentFrameIndex];
|
||||
}
|
||||
|
||||
Bool FrameContext::IsCommandRecording() const {
|
||||
return GetCurrent().isCommandRecording;
|
||||
}
|
||||
|
||||
void FrameContext::AdvanceToNext() {
|
||||
MOBILEGL_ASSERT(!m_frames.empty(), "FrameContext is not initialized");
|
||||
currentFrameIndex = (currentFrameIndex + 1) % static_cast<Uint32>(m_frames.size());
|
||||
GetCurrent().isCommandRecording = false;
|
||||
GetCurrent().hasCommandBufferRecorded = false;
|
||||
}
|
||||
|
||||
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
|
||||
const VkCommandBufferInheritanceInfo* pInheritanceInfo) {
|
||||
auto& frame = GetCurrent();
|
||||
MOBILEGL_ASSERT(!frame.isCommandRecording, "BeginCommandRecording called while command buffer is already recording");
|
||||
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
VK_VERIFY(vkResetCommandBuffer(frame.commandBuffer, 0), "BeginCommandRecording, vkResetCommandBuffer");
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = flags;
|
||||
beginInfo.pInheritanceInfo = pInheritanceInfo;
|
||||
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
||||
|
||||
frame.isCommandRecording = true;
|
||||
return frame.commandBuffer;
|
||||
}
|
||||
|
||||
void FrameContext::EndCommandRecording() {
|
||||
auto& frame = GetCurrent();
|
||||
MOBILEGL_ASSERT(frame.isCommandRecording, "EndCommandRecording called without active command buffer recording");
|
||||
VK_VERIFY(vkEndCommandBuffer(frame.commandBuffer), "EndCommandRecording, vkEndCommandBuffer");
|
||||
frame.isCommandRecording = false;
|
||||
frame.hasCommandBufferRecorded = true;
|
||||
}
|
||||
|
||||
VkResult FrameContext::InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount) {
|
||||
DestroySwapchainSemaphores(device);
|
||||
if (swapchainImageCount == 0) {
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
m_swapchainImageRenderFinishedSemaphores.assign(swapchainImageCount, VK_NULL_HANDLE);
|
||||
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
for (Uint32 imageIndex = 0; imageIndex < swapchainImageCount; ++imageIndex) {
|
||||
VkResult result =
|
||||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &m_swapchainImageRenderFinishedSemaphores[imageIndex]);
|
||||
if (result != VK_SUCCESS) {
|
||||
DestroySwapchainSemaphores(device);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void FrameContext::DestroySwapchainSemaphores(VkDevice device) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
for (auto semaphore : m_swapchainImageRenderFinishedSemaphores) {
|
||||
if (semaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, semaphore, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_swapchainImageRenderFinishedSemaphores.clear();
|
||||
}
|
||||
|
||||
Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) {
|
||||
auto& frame = GetCurrent();
|
||||
if (frame.hasCommandBufferRecorded || frame.isCommandRecording || oldLayout == presentLayout ||
|
||||
oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& commandBuffer = BeginCommandRecording();
|
||||
|
||||
VkImageMemoryBarrier presentBarrier{};
|
||||
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
presentBarrier.srcAccessMask = 0;
|
||||
presentBarrier.dstAccessMask = 0;
|
||||
presentBarrier.oldLayout = oldLayout;
|
||||
presentBarrier.newLayout = presentLayout;
|
||||
presentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
presentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
presentBarrier.image = image;
|
||||
presentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
presentBarrier.subresourceRange.baseMipLevel = 0;
|
||||
presentBarrier.subresourceRange.levelCount = 1;
|
||||
presentBarrier.subresourceRange.baseArrayLayer = 0;
|
||||
presentBarrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &presentBarrier);
|
||||
|
||||
EndCommandRecording();
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameContext::SubmitInfoPacket FrameContext::GetSubmitInfo(Bool shouldSubmitCommandBuffer,
|
||||
Uint32 swapchainImageIndex) const {
|
||||
const auto& frame = GetCurrent();
|
||||
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
|
||||
AssertValidSwapchainImageIndex(swapchainImageIndex);
|
||||
SubmitInfoPacket packet{};
|
||||
packet.waitSemaphore = frame.imageAvailableSemaphore;
|
||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||
packet.commandBuffer = frame.commandBuffer;
|
||||
|
||||
packet.submitInfo.waitSemaphoreCount = 1;
|
||||
packet.submitInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||
packet.submitInfo.pWaitDstStageMask = &packet.waitDstStageMask;
|
||||
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
||||
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
||||
packet.submitInfo.signalSemaphoreCount = 1;
|
||||
packet.submitInfo.pSignalSemaphores = &packet.signalSemaphore;
|
||||
return packet;
|
||||
}
|
||||
|
||||
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const {
|
||||
AssertValidSwapchainImageIndex(imageIndex);
|
||||
PresentInfoPacket packet{};
|
||||
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
||||
packet.swapchain = swapchain;
|
||||
packet.imageIndex = &imageIndex;
|
||||
|
||||
packet.presentInfo.waitSemaphoreCount = 1;
|
||||
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||
packet.presentInfo.swapchainCount = 1;
|
||||
packet.presentInfo.pSwapchains = &packet.swapchain;
|
||||
packet.presentInfo.pImageIndices = packet.imageIndex;
|
||||
packet.presentInfo.pResults = nullptr;
|
||||
return packet;
|
||||
}
|
||||
|
||||
VkResult FrameContext::WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||
Uint64 timeout, VkFence acquireFence) {
|
||||
auto& frame = GetCurrent();
|
||||
VkResult result = vkWaitForFences(device, 1, &frame.imageInFlightFence, VK_TRUE, timeout);
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||
&outImageIndex);
|
||||
}
|
||||
|
||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||
return currentFrameIndex;
|
||||
}
|
||||
|
||||
Uint32 FrameContext::GetFrameCount() const {
|
||||
return static_cast<Uint32>(m_frames.size());
|
||||
}
|
||||
|
||||
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
||||
}
|
||||
|
||||
void FrameContext::AssertValidSwapchainImageIndex(Uint32 imageIndex) const {
|
||||
MOBILEGL_ASSERT(imageIndex < m_swapchainImageRenderFinishedSemaphores.size(),
|
||||
"FrameContext swapchain image index out of range");
|
||||
}
|
||||
|
||||
VkResult FrameContext::CreateSyncObjectsForFrame(VkDevice device, Uint32 frameIndex,
|
||||
const VkSemaphoreCreateInfo& semaphoreInfo,
|
||||
const VkFenceCreateInfo& fenceInfo) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
DestroySyncObjectsForFrame(device, frameIndex);
|
||||
|
||||
auto& frame = m_frames[frameIndex];
|
||||
VkResult result =
|
||||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &frame.imageAvailableSemaphore);
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = vkCreateFence(device, &fenceInfo, nullptr, &frame.imageInFlightFence);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkDestroySemaphore(device, frame.imageAvailableSemaphore, nullptr);
|
||||
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||
return result;
|
||||
}
|
||||
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.isCommandRecording = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void FrameContext::DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
auto& frame = m_frames[frameIndex];
|
||||
if (device != VK_NULL_HANDLE && frame.imageInFlightFence != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(device, frame.imageInFlightFence, nullptr);
|
||||
}
|
||||
frame.imageInFlightFence = VK_NULL_HANDLE;
|
||||
|
||||
if (device != VK_NULL_HANDLE && frame.imageAvailableSemaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, frame.imageAvailableSemaphore, nullptr);
|
||||
}
|
||||
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||
frame.isCommandRecording = false;
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,76 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/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 "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class FrameContext {
|
||||
public:
|
||||
struct SubmitInfoPacket {
|
||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
};
|
||||
|
||||
struct PresentInfoPacket {
|
||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
const Uint32* imageIndex = nullptr;
|
||||
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||
};
|
||||
|
||||
struct FrameData {
|
||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||
Bool isCommandRecording = false;
|
||||
Bool hasCommandBufferRecorded = false;
|
||||
};
|
||||
|
||||
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
||||
void Destroy(VkDevice device, VkCommandPool commandPool);
|
||||
|
||||
// Lifecycle functions
|
||||
FrameData& GetCurrent();
|
||||
const FrameData& GetCurrent() const;
|
||||
Bool IsCommandRecording() const;
|
||||
void AdvanceToNext();
|
||||
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
||||
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
||||
void EndCommandRecording();
|
||||
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
||||
void DestroySwapchainSemaphores(VkDevice device);
|
||||
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
||||
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
||||
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const;
|
||||
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
||||
|
||||
Uint32 GetCurrentFrameIndex() const;
|
||||
Uint32 GetFrameCount() const;
|
||||
|
||||
private:
|
||||
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
||||
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
||||
|
||||
VkResult CreateSyncObjectsForFrame(VkDevice device, Uint32 frameIndex,
|
||||
const VkSemaphoreCreateInfo& semaphoreInfo,
|
||||
const VkFenceCreateInfo& fenceInfo);
|
||||
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
||||
|
||||
Vector<FrameData> m_frames;
|
||||
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
||||
Uint32 currentFrameIndex = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,129 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.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 "PipelineFactory.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
PipelineFactory::~PipelineFactory() {
|
||||
DestroyAll();
|
||||
}
|
||||
|
||||
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.blendEnable, sizeof(payload.blendEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcColorBlendFactor, sizeof(payload.srcColorBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstColorBlendFactor, sizeof(payload.dstColorBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcAlphaBlendFactor, sizeof(payload.srcAlphaBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstAlphaBlendFactor, sizeof(payload.dstAlphaBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorWriteMask, sizeof(payload.colorWriteMask)));
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
VkPipeline PipelineFactory::GetOrCreatePipeline(const PipelineCreatePayload& payload) {
|
||||
const HashType hash = ComputeHash(payload);
|
||||
auto it = m_cache.find(hash);
|
||||
if (it != m_cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
VkPipeline pipeline = CreatePipeline(payload);
|
||||
m_cache.emplace(hash, pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void PipelineFactory::DestroyAll() {
|
||||
for (auto& pair : m_cache) {
|
||||
if (pair.second != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device, pair.second, nullptr);
|
||||
}
|
||||
}
|
||||
m_cache.clear();
|
||||
}
|
||||
|
||||
VkPipeline PipelineFactory::CreatePipeline(const PipelineCreatePayload& payload) const {
|
||||
MOBILEGL_ASSERT(payload.stages != nullptr && !payload.stages->empty(), "PipelineFactory: stages are empty");
|
||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null");
|
||||
MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null");
|
||||
|
||||
static constexpr VkDynamicState kDynamicStates[] = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamicState.dynamicStateCount = static_cast<uint32_t>(std::size(kDynamicStates));
|
||||
dynamicState.pDynamicStates = kDynamicStates;
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
ia.topology = payload.topology;
|
||||
|
||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||
vpci.viewportCount = 1;
|
||||
vpci.scissorCount = 1;
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
raster.cullMode = VK_CULL_MODE_NONE;
|
||||
raster.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
raster.lineWidth = 1.0f;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
||||
depthStencil.depthCompareOp = payload.depthCompareOp;
|
||||
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
||||
depthStencil.stencilTestEnable = VK_FALSE;
|
||||
|
||||
VkPipelineColorBlendAttachmentState colorAttach{};
|
||||
colorAttach.colorWriteMask = payload.colorWriteMask;
|
||||
colorAttach.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE;
|
||||
colorAttach.srcColorBlendFactor = payload.srcColorBlendFactor;
|
||||
colorAttach.dstColorBlendFactor = payload.dstColorBlendFactor;
|
||||
colorAttach.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
colorAttach.srcAlphaBlendFactor = payload.srcAlphaBlendFactor;
|
||||
colorAttach.dstAlphaBlendFactor = payload.dstAlphaBlendFactor;
|
||||
colorAttach.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||
blend.attachmentCount = 1;
|
||||
blend.pAttachments = &colorAttach;
|
||||
|
||||
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
||||
gpi.pStages = payload.stages->data();
|
||||
gpi.pVertexInputState = payload.vertexInputState;
|
||||
gpi.pInputAssemblyState = &ia;
|
||||
gpi.pViewportState = &vpci;
|
||||
gpi.pRasterizationState = &raster;
|
||||
gpi.pMultisampleState = &ms;
|
||||
gpi.pDepthStencilState = &depthStencil;
|
||||
gpi.pColorBlendState = &blend;
|
||||
gpi.pDynamicState = &dynamicState;
|
||||
gpi.layout = payload.pipelineLayout;
|
||||
gpi.renderPass = payload.renderPass;
|
||||
gpi.subpass = payload.subpass;
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline),
|
||||
"vkCreateGraphicsPipelines");
|
||||
return pipeline;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,59 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.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 "Config.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class PipelineFactory {
|
||||
public:
|
||||
using HashType = Uint64;
|
||||
|
||||
struct PipelineCreatePayload {
|
||||
HashType programHash = 0;
|
||||
HashType vertexInputHash = 0;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
Uint32 subpass = 0;
|
||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
Bool depthTestEnable = false;
|
||||
Bool depthWriteEnable = false;
|
||||
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
Bool blendEnable = false;
|
||||
VkBlendFactor srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
VkBlendFactor dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
VkBlendFactor srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
VkBlendFactor dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
VkColorComponentFlags colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
||||
};
|
||||
|
||||
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
||||
m_device(device), m_config(config) {}
|
||||
~PipelineFactory();
|
||||
PipelineFactory(const PipelineFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const PipelineCreatePayload& payload) const;
|
||||
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
||||
void DestroyAll();
|
||||
|
||||
private:
|
||||
VkPipeline CreatePipeline(const PipelineCreatePayload& payload) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
const VulkanRendererConfig& m_config;
|
||||
UnorderedMap<HashType, VkPipeline> m_cache;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,406 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.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 "ProgramFactory.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>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
using ShaderObject = MG_State::GLState::ShaderObject;
|
||||
|
||||
struct PositionTargetInfo {
|
||||
Uint32 variableId = 0;
|
||||
Uint32 vectorTypeId = 0;
|
||||
Uint32 floatTypeId = 0;
|
||||
Uint32 vectorPtrTypeId = 0;
|
||||
Uint32 memberIndex = 0;
|
||||
Bool isMember = false;
|
||||
};
|
||||
|
||||
Bool IsVec4Float32(spvtools::opt::IRContext* context, Uint32 typeId, Uint32* 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 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 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>(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>(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 FindOutputVectorPointerTypeId(spvtools::opt::IRContext* context, Uint32 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 structTypeId, Uint32 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 vectorTypeId = structInst->GetSingleWordInOperand(memberIndex);
|
||||
Uint32 floatTypeId = 0;
|
||||
if (!IsVec4Float32(context, vectorTypeId, &floatTypeId)) return false;
|
||||
|
||||
const Uint32 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>(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>(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, Uint32>> memberCandidates;
|
||||
constexpr auto kDecorationBuiltIn = static_cast<Uint32>(spv::Decoration::BuiltIn);
|
||||
constexpr auto kBuiltInPosition = static_cast<Uint32>(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 halfConstId, Bool doYFlip, Bool doZRemap,
|
||||
Bool doSurfaceRotate90, Bool doSurfaceRotate180, Bool doSurfaceRotate270) {
|
||||
using namespace spvtools::opt;
|
||||
InstructionBuilder builder(context, insertBefore,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
|
||||
Uint32 positionPtrId = target.variableId;
|
||||
if (target.isMember) {
|
||||
const Uint32 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 && !doSurfaceRotate90 && !doSurfaceRotate180 && !doSurfaceRotate270) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 xValueId = x->result_id();
|
||||
Uint32 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();
|
||||
}
|
||||
|
||||
if (doSurfaceRotate90) {
|
||||
auto* negY = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, yValueId);
|
||||
if (!negY) return false;
|
||||
xValueId = negY->result_id();
|
||||
yValueId = x->result_id();
|
||||
} else if (doSurfaceRotate180) {
|
||||
auto* negX = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, xValueId);
|
||||
auto* negY = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, yValueId);
|
||||
if (!negX || !negY) return false;
|
||||
xValueId = negX->result_id();
|
||||
yValueId = negY->result_id();
|
||||
} else if (doSurfaceRotate270) {
|
||||
auto* negX = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, xValueId);
|
||||
if (!negX) return false;
|
||||
xValueId = yValueId;
|
||||
yValueId = negX->result_id();
|
||||
}
|
||||
|
||||
Uint32 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,
|
||||
{xValueId, 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(ProgramFactory::CompileOptionFlags 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 auto halfBits = std::bit_cast<Uint32>(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 halfConstId = halfInst->result_id();
|
||||
|
||||
const Bool doYFlip = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip);
|
||||
const Bool doZRemap = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionZRemap);
|
||||
const Bool doSurfaceRotate90 = (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate90);
|
||||
const Bool doSurfaceRotate180 =
|
||||
(m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate180);
|
||||
const Bool doSurfaceRotate270 =
|
||||
(m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate270);
|
||||
|
||||
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,
|
||||
doSurfaceRotate90, doSurfaceRotate180, doSurfaceRotate270);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!modified) return Status::SuccessWithoutChange;
|
||||
context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisDefUse |
|
||||
spvtools::opt::IRContext::kAnalysisInstrToBlockMapping);
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
private:
|
||||
ProgramFactory::CompileOptionFlags m_transformFlags;
|
||||
};
|
||||
|
||||
spvtools::Optimizer::PassToken CreateGlToVulkanPositionFixPass(
|
||||
ProgramFactory::CompileOptionFlags transformFlags) {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
|
||||
}
|
||||
|
||||
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
|
||||
ProgramFactory::CompileOptionFlags 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
|
||||
|
||||
ProgramFactory::~ProgramFactory() = default;
|
||||
|
||||
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
|
||||
CompileOptionFlags flags) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
// We expect shader stages in program object are sorted
|
||||
const auto& spirvs = program.GetGeneratedSpirv();
|
||||
for (const auto& spv : spirvs) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
|
||||
HashType hash = XXH64_digest(m_hashState);
|
||||
return hash;
|
||||
}
|
||||
|
||||
Vector<VkPipelineShaderStageCreateInfo>& ProgramFactory::GetOrCreatePipelineShaderStages(
|
||||
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;
|
||||
}
|
||||
|
||||
auto& entry = m_cache[hash];
|
||||
entry.device = m_device;
|
||||
entry.hash = hash;
|
||||
auto& shaders = program.GetAttachedShaders();
|
||||
auto& spirv = program.GetGeneratedSpirv();
|
||||
|
||||
const ShaderStage fixupStage = PickClipFixupStage(shaders);
|
||||
|
||||
for (SizeT i = 0; i < shaders.size(); ++i) {
|
||||
auto& spv = spirv[i];
|
||||
if (spv.empty()) continue;
|
||||
|
||||
Vector<Uint> moduleSpv;
|
||||
|
||||
// Apply position fixup if needed
|
||||
if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) {
|
||||
TransformSpirvForVulkanPositionFix(spv, moduleSpv, flags);
|
||||
} else {
|
||||
moduleSpv = spv;
|
||||
}
|
||||
|
||||
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
smci.codeSize = moduleSpv.size() * sizeof(Uint);
|
||||
smci.pCode = moduleSpv.data();
|
||||
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &module), "vkCreateShaderModule");
|
||||
|
||||
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
ShaderStage 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::DirectVulkan
|
||||
@@ -0,0 +1,94 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.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 "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class ProgramFactory {
|
||||
public:
|
||||
enum class CompileOptionBit : Uint {
|
||||
None = 0,
|
||||
PositionYFlip = 1 << 0,
|
||||
PositionZRemap = 1 << 1,
|
||||
SurfaceRotate90 = 1 << 2,
|
||||
SurfaceRotate180 = 1 << 3,
|
||||
SurfaceRotate270 = 1 << 4,
|
||||
};
|
||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||
using HashType = Uint64;
|
||||
struct BackendProgramObject {
|
||||
HashType hash = 0;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||
Vector<VkShaderModule> modules;
|
||||
|
||||
BackendProgramObject() = default;
|
||||
BackendProgramObject(const BackendProgramObject&) = delete;
|
||||
BackendProgramObject& operator=(const BackendProgramObject&) = delete;
|
||||
BackendProgramObject(BackendProgramObject&& other) noexcept {
|
||||
hash = other.hash;
|
||||
device = other.device;
|
||||
stages = std::move(other.stages);
|
||||
modules = std::move(other.modules);
|
||||
other.hash = 0;
|
||||
other.device = VK_NULL_HANDLE;
|
||||
}
|
||||
BackendProgramObject& operator=(BackendProgramObject&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
hash = other.hash;
|
||||
device = other.device;
|
||||
stages = std::move(other.stages);
|
||||
modules = std::move(other.modules);
|
||||
other.hash = 0;
|
||||
other.device = VK_NULL_HANDLE;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~BackendProgramObject() {
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
void DestroyModules() {
|
||||
for (auto module : modules) {
|
||||
if (module != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(device, module, nullptr);
|
||||
}
|
||||
}
|
||||
modules.clear();
|
||||
}
|
||||
};
|
||||
|
||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config)
|
||||
: m_device(device), m_config(config) {}
|
||||
~ProgramFactory();
|
||||
ProgramFactory(const ProgramFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
|
||||
Vector<VkPipelineShaderStageCreateInfo>& GetOrCreatePipelineShaderStages(
|
||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||
|
||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||
|
||||
private:
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
UnorderedMap<HashType, BackendProgramObject> m_cache;
|
||||
const VulkanRendererConfig& m_config;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,247 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.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 "SwapchainObject.h"
|
||||
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<vulkan/vk_enum_string_helper.h>)
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
#define MOBILEGL_HAS_VK_ENUM_STRING_HELPER 1
|
||||
#else
|
||||
#define MOBILEGL_HAS_VK_ENUM_STRING_HELPER 0
|
||||
#endif
|
||||
#else
|
||||
#define MOBILEGL_HAS_VK_ENUM_STRING_HELPER 0
|
||||
#endif
|
||||
|
||||
#if !MOBILEGL_HAS_VK_ENUM_STRING_HELPER
|
||||
static const char* string_VkFormat(VkFormat) {
|
||||
return "VkFormat(unknown)";
|
||||
}
|
||||
|
||||
static const char* string_VkColorSpaceKHR(VkColorSpaceKHR) {
|
||||
return "VkColorSpaceKHR(unknown)";
|
||||
}
|
||||
|
||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR) {
|
||||
return "VkPresentModeKHR(unknown)";
|
||||
}
|
||||
|
||||
static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBitsKHR) {
|
||||
return "VkSurfaceTransformFlagBitsKHR(unknown)";
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
SwapchainObject::SwapchainCapabilities SwapchainObject::GetSwapchainCapabilities(VkPhysicalDevice physicalDevice,
|
||||
VkSurfaceKHR surface) {
|
||||
SwapchainCapabilities swapchainCapabilities{};
|
||||
|
||||
VK_VERIFY(
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &swapchainCapabilities.capabilities));
|
||||
|
||||
Uint32 formatCount = 0;
|
||||
VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr));
|
||||
if (formatCount != 0) {
|
||||
swapchainCapabilities.surfaceFormats.resize(formatCount);
|
||||
VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(
|
||||
physicalDevice, surface, &formatCount, swapchainCapabilities.surfaceFormats.data()));
|
||||
}
|
||||
|
||||
Uint32 presentModeCount = 0;
|
||||
VK_VERIFY(
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr));
|
||||
if (presentModeCount != 0) {
|
||||
swapchainCapabilities.presentModes.resize(presentModeCount);
|
||||
VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(
|
||||
physicalDevice, surface, &presentModeCount, swapchainCapabilities.presentModes.data()));
|
||||
}
|
||||
|
||||
return swapchainCapabilities;
|
||||
}
|
||||
|
||||
VkSurfaceFormatKHR SwapchainObject::ChooseSwapchainSurfaceFormat(
|
||||
const Vector<VkSurfaceFormatKHR>& availableFormats) {
|
||||
for (const auto& availableFormat : availableFormats) {
|
||||
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
|
||||
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||
return availableFormat;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Properly rank other formats
|
||||
return availableFormats[0];
|
||||
}
|
||||
|
||||
VkPresentModeKHR SwapchainObject::ChooseSwapchainPresentMode(
|
||||
const Vector<VkPresentModeKHR>& availablePresentModes) {
|
||||
for (auto desiredPresentMode : s_desiredPresentModes) {
|
||||
for (const auto& presentMode : availablePresentModes) {
|
||||
if (presentMode == desiredPresentMode) {
|
||||
return presentMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Properly rank other modes
|
||||
return availablePresentModes[0];
|
||||
}
|
||||
|
||||
void SwapchainObject::Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
Uint32 graphicsQueueFamily, Uint32 presentQueueFamily, Uint32 minImageCountHint) {
|
||||
const auto swapchainCapabilities = GetSwapchainCapabilities(physicalDevice, surface);
|
||||
MOBILEGL_ASSERT(swapchainCapabilities.IsComplete(),
|
||||
"SwapchainObject::Create failed: incomplete swapchain capabilities");
|
||||
|
||||
MGLOG_I("Got %d surface formats:", swapchainCapabilities.surfaceFormats.size());
|
||||
for (const auto& sf : swapchainCapabilities.surfaceFormats) {
|
||||
MGLOG_I(" [%s, %s]", string_VkFormat(sf.format), string_VkColorSpaceKHR(sf.colorSpace));
|
||||
}
|
||||
|
||||
const auto pickedSurfaceFormat = ChooseSwapchainSurfaceFormat(swapchainCapabilities.surfaceFormats);
|
||||
MGLOG_I("Picked surface format: [%s, %s]", string_VkFormat(pickedSurfaceFormat.format),
|
||||
string_VkColorSpaceKHR(pickedSurfaceFormat.colorSpace));
|
||||
|
||||
MGLOG_I("Got %d present modes:", swapchainCapabilities.presentModes.size());
|
||||
for (const auto& pm : swapchainCapabilities.presentModes) {
|
||||
MGLOG_I(" %s", string_VkPresentModeKHR(pm));
|
||||
}
|
||||
|
||||
const auto presentMode = ChooseSwapchainPresentMode(swapchainCapabilities.presentModes);
|
||||
MGLOG_I("Picked present mode: %s", string_VkPresentModeKHR(presentMode));
|
||||
|
||||
const auto& swapchainCaps = swapchainCapabilities.capabilities;
|
||||
const auto targetImageCount = std::max<Uint32>(minImageCountHint, swapchainCaps.minImageCount);
|
||||
MGLOG_I("Set minImageCount = %u", targetImageCount);
|
||||
MGLOG_I("Swapchain currentTransform = %s",
|
||||
string_VkSurfaceTransformFlagBitsKHR(swapchainCaps.currentTransform));
|
||||
|
||||
VkSwapchainCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
|
||||
createInfo.surface = surface;
|
||||
createInfo.minImageCount = targetImageCount;
|
||||
createInfo.imageFormat = pickedSurfaceFormat.format;
|
||||
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
|
||||
createInfo.imageExtent = swapchainCaps.currentExtent;
|
||||
createInfo.imageArrayLayers = 1;
|
||||
const VkImageUsageFlags requiredImageUsage =
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
MOBILEGL_ASSERT((swapchainCaps.supportedUsageFlags & requiredImageUsage) == requiredImageUsage,
|
||||
"Swapchain does not support required usage flags (COLOR_ATTACHMENT | TRANSFER_DST). "
|
||||
"supportedUsageFlags=0x%x",
|
||||
static_cast<Uint32>(swapchainCaps.supportedUsageFlags));
|
||||
|
||||
VkImageUsageFlags imageUsage = requiredImageUsage;
|
||||
if ((swapchainCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0) {
|
||||
imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
createInfo.imageUsage = imageUsage;
|
||||
MGLOG_I("Swapchain imageUsage = 0x%x (supportedUsageFlags = 0x%x)", static_cast<Uint32>(createInfo.imageUsage),
|
||||
static_cast<Uint32>(swapchainCaps.supportedUsageFlags));
|
||||
Uint32 queueFamilyIndices[] = {graphicsQueueFamily, presentQueueFamily};
|
||||
if (graphicsQueueFamily != presentQueueFamily) {
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||
createInfo.queueFamilyIndexCount = 2;
|
||||
createInfo.pQueueFamilyIndices = queueFamilyIndices;
|
||||
} else {
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
createInfo.queueFamilyIndexCount = 0;
|
||||
createInfo.pQueueFamilyIndices = nullptr;
|
||||
}
|
||||
createInfo.preTransform = swapchainCaps.currentTransform;
|
||||
MGLOG_I("Set swapchain preTransform = %s", string_VkSurfaceTransformFlagBitsKHR(createInfo.preTransform));
|
||||
const VkCompositeAlphaFlagBitsKHR compositeAlphaCandidates[] = {
|
||||
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
|
||||
};
|
||||
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
for (auto candidate : compositeAlphaCandidates) {
|
||||
if ((swapchainCaps.supportedCompositeAlpha & candidate) != 0) {
|
||||
createInfo.compositeAlpha = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
createInfo.presentMode = presentMode;
|
||||
createInfo.clipped = VK_TRUE;
|
||||
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
||||
|
||||
m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace};
|
||||
m_extent = createInfo.imageExtent;
|
||||
m_preTransform = createInfo.preTransform;
|
||||
|
||||
VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain));
|
||||
|
||||
Uint32 imageCount = 0;
|
||||
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, nullptr));
|
||||
|
||||
m_images.resize(imageCount, VK_NULL_HANDLE);
|
||||
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data()));
|
||||
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
|
||||
CreateImageViews(device);
|
||||
|
||||
MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height,
|
||||
imageCount);
|
||||
}
|
||||
|
||||
void SwapchainObject::Shutdown(VkDevice device) {
|
||||
for (auto imageView : m_imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
}
|
||||
m_imageViews.clear();
|
||||
|
||||
if (m_swapchain != VK_NULL_HANDLE) {
|
||||
vkDestroySwapchainKHR(device, m_swapchain, nullptr);
|
||||
m_swapchain = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
m_images.clear();
|
||||
m_imageLayouts.clear();
|
||||
m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
}
|
||||
|
||||
VkImage SwapchainObject::GetImage(Uint32 index) const {
|
||||
MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range");
|
||||
return m_images[index];
|
||||
}
|
||||
|
||||
VkImageLayout SwapchainObject::GetImageLayout(Uint32 index) const {
|
||||
MOBILEGL_ASSERT(index < m_imageLayouts.size(), "Swapchain image layout index out of range");
|
||||
return m_imageLayouts[index];
|
||||
}
|
||||
|
||||
void SwapchainObject::SetImageLayout(Uint32 index, VkImageLayout layout) {
|
||||
MOBILEGL_ASSERT(index < m_imageLayouts.size(), "Swapchain image layout index out of range");
|
||||
m_imageLayouts[index] = layout;
|
||||
}
|
||||
|
||||
void SwapchainObject::CreateImageViews(VkDevice device) {
|
||||
m_imageViews.resize(m_images.size(), VK_NULL_HANDLE);
|
||||
for (SizeT i = 0; i < m_imageViews.size(); i++) {
|
||||
VkImageViewCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
createInfo.image = m_images[i];
|
||||
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
createInfo.format = m_surfaceFormat.format;
|
||||
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
createInfo.subresourceRange.baseMipLevel = 0;
|
||||
createInfo.subresourceRange.levelCount = 1;
|
||||
createInfo.subresourceRange.baseArrayLayer = 0;
|
||||
createInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(device, &createInfo, nullptr, &m_imageViews[i]));
|
||||
}
|
||||
MGLOG_I("Swapchain image views created");
|
||||
}
|
||||
|
||||
#undef VK_VERIFY
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,63 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.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 "../VkIncludes.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class SwapchainObject {
|
||||
public:
|
||||
struct SwapchainCapabilities {
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
Vector<VkSurfaceFormatKHR> surfaceFormats;
|
||||
Vector<VkPresentModeKHR> presentModes;
|
||||
|
||||
Bool IsComplete() const {
|
||||
return !surfaceFormats.empty() && !presentModes.empty();
|
||||
}
|
||||
};
|
||||
|
||||
static SwapchainCapabilities GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface);
|
||||
static VkSurfaceFormatKHR ChooseSwapchainSurfaceFormat(const Vector<VkSurfaceFormatKHR>& availableFormats);
|
||||
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes);
|
||||
|
||||
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
||||
Uint32 presentQueueFamily, Uint32 minImageCountHint);
|
||||
void Shutdown(VkDevice device);
|
||||
|
||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||
const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; }
|
||||
VkExtent2D GetExtent() const { return m_extent; }
|
||||
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
||||
const Vector<VkImage>& GetImages() const { return m_images; }
|
||||
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
|
||||
VkImage GetImage(Uint32 index) const;
|
||||
VkImageLayout GetImageLayout(Uint32 index) const;
|
||||
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
||||
SizeT GetImageCount() const { return m_images.size(); }
|
||||
|
||||
private:
|
||||
void CreateImageViews(VkDevice device);
|
||||
static constexpr VkPresentModeKHR s_desiredPresentModes[] {
|
||||
VK_PRESENT_MODE_MAILBOX_KHR,
|
||||
VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||
VK_PRESENT_MODE_FIFO_RELAXED_KHR,
|
||||
VK_PRESENT_MODE_FIFO_KHR
|
||||
};
|
||||
|
||||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||
VkExtent2D m_extent{};
|
||||
VkSurfaceTransformFlagBitsKHR m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
Vector<VkImage> m_images;
|
||||
Vector<VkImageView> m_imageViews;
|
||||
Vector<VkImageLayout> m_imageLayouts;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,875 @@
|
||||
// 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 "VkFramebufferManager.h"
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_Util/ShaderTranspiler/Types.h"
|
||||
#include <limits>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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,
|
||||
VkTextureSamplerManager* textureSamplerManager,
|
||||
VkFramebufferManager* framebufferManager) {
|
||||
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");
|
||||
|
||||
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_textureSamplerManager = textureSamplerManager;
|
||||
m_framebufferManager = framebufferManager;
|
||||
|
||||
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_textureSamplerManager = nullptr;
|
||||
m_framebufferManager = 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 {
|
||||
if (!m_textureSamplerManager || !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 auto samplerOverride = textureUnit.GetSamplerObject();
|
||||
|
||||
const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding];
|
||||
auto texture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
|
||||
if (!texture) {
|
||||
auto& slots = textureUnit.GetAllBindingSlots();
|
||||
for (auto& slot : slots) {
|
||||
texture = slot.GetBoundObject();
|
||||
if (texture) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_framebufferManager &&
|
||||
m_framebufferManager->TransitionOffscreenColorTextureToShaderRead(commandBuffer, texture->GetExternalIndex())) {
|
||||
VkImageView offscreenView = VK_NULL_HANDLE;
|
||||
if (m_framebufferManager->GetOffscreenColorViewByTexture(texture->GetExternalIndex(), offscreenView) &&
|
||||
offscreenView != VK_NULL_HANDLE) {
|
||||
VkDescriptorImageInfo sampledInfo{};
|
||||
if (!m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), sampledInfo)) {
|
||||
return false;
|
||||
}
|
||||
sampledInfo.imageView = offscreenView;
|
||||
sampledInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
outImageInfo = sampledInfo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), outImageInfo);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (MG_State::pGLContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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, VkPipelineLayout pipelineLayout,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
Uint32 frameIndex) {
|
||||
if (m_frames.empty()) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: binder is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (frameIndex >= m_frames.size()) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: invalid frame index %u", frameIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
ProgramLayout* layout = GetOrCreateProgramLayout(program);
|
||||
if (!layout || layout->pipelineLayout == VK_NULL_HANDLE || layout->descriptorSetLayout == VK_NULL_HANDLE) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot get program layout");
|
||||
return false;
|
||||
}
|
||||
if (layout->pipelineLayout != pipelineLayout) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: pipelineLayout mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
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] = {};
|
||||
VkDescriptorImageInfo fallbackImageInfo{};
|
||||
const Bool hasFallbackImage = m_textureSamplerManager && m_textureSamplerManager->GetFallbackDescriptor(fallbackImageInfo);
|
||||
|
||||
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 = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo);
|
||||
if (!hasImage) {
|
||||
if (!hasFallbackImage) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable");
|
||||
return false;
|
||||
}
|
||||
imageInfo = fallbackImageInfo;
|
||||
}
|
||||
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable");
|
||||
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, 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,104 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.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 "VkBufferObject.h"
|
||||
#include "VkTextureSamplerManager.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ProgramObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkFramebufferManager;
|
||||
|
||||
class UniformDescriptorBinder {
|
||||
public:
|
||||
enum class BindingKind : Uint8 {
|
||||
None = 0,
|
||||
UniformBufferDynamic,
|
||||
CombinedImageSampler
|
||||
};
|
||||
|
||||
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment,
|
||||
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
|
||||
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024,
|
||||
VkTextureSamplerManager* textureSamplerManager = nullptr,
|
||||
VkFramebufferManager* framebufferManager = nullptr);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program);
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout,
|
||||
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex);
|
||||
|
||||
private:
|
||||
struct DescriptorPoolBucket {
|
||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||
Uint32 maxSets = 0;
|
||||
Uint32 allocatedSets = 0;
|
||||
};
|
||||
|
||||
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 ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramLayout& layout, Uint32 binding,
|
||||
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();
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = 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;
|
||||
Uint32 m_peakDescriptorSetsObserved = 0;
|
||||
VkTextureSamplerManager* m_textureSamplerManager = nullptr;
|
||||
VkFramebufferManager* m_framebufferManager = nullptr;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.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 "VertexInputStateBuilder.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VertexInputStateBuilder::VertexInputStateBuilder() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void VertexInputStateBuilder::Reset() {
|
||||
m_bindings.clear();
|
||||
m_attributes.clear();
|
||||
|
||||
m_state = {};
|
||||
m_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
m_state.vertexBindingDescriptionCount = 0;
|
||||
m_state.pVertexBindingDescriptions = nullptr;
|
||||
m_state.vertexAttributeDescriptionCount = 0;
|
||||
m_state.pVertexAttributeDescriptions = nullptr;
|
||||
}
|
||||
|
||||
VertexInputStateBuilder& VertexInputStateBuilder::AddBinding(
|
||||
Uint32 binding, Uint32 stride, VkVertexInputRate inputRate) {
|
||||
VkVertexInputBindingDescription desc{};
|
||||
desc.binding = binding;
|
||||
desc.stride = stride;
|
||||
desc.inputRate = inputRate;
|
||||
m_bindings.push_back(desc);
|
||||
return *this;
|
||||
}
|
||||
|
||||
VertexInputStateBuilder& VertexInputStateBuilder::AddAttribute(
|
||||
Uint32 location, Uint32 binding, VkFormat format, Uint32 offset) {
|
||||
VkVertexInputAttributeDescription desc{};
|
||||
desc.location = location;
|
||||
desc.binding = binding;
|
||||
desc.format = format;
|
||||
desc.offset = offset;
|
||||
m_attributes.push_back(desc);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const VkPipelineVertexInputStateCreateInfo& VertexInputStateBuilder::Build() {
|
||||
m_state.vertexBindingDescriptionCount = static_cast<Uint32>(m_bindings.size());
|
||||
m_state.pVertexBindingDescriptions = m_bindings.empty() ? nullptr : m_bindings.data();
|
||||
m_state.vertexAttributeDescriptionCount = static_cast<Uint32>(m_attributes.size());
|
||||
m_state.pVertexAttributeDescriptions = m_attributes.empty() ? nullptr : m_attributes.data();
|
||||
return m_state;
|
||||
}
|
||||
|
||||
const Vector<VkVertexInputBindingDescription>& VertexInputStateBuilder::GetBindings() const {
|
||||
return m_bindings;
|
||||
}
|
||||
|
||||
const Vector<VkVertexInputAttributeDescription>& VertexInputStateBuilder::GetAttributes() const {
|
||||
return m_attributes;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,31 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.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::DirectVulkan {
|
||||
class VertexInputStateBuilder {
|
||||
public:
|
||||
VertexInputStateBuilder();
|
||||
|
||||
void Reset();
|
||||
VertexInputStateBuilder& AddBinding(
|
||||
Uint32 binding, Uint32 stride, VkVertexInputRate inputRate = VK_VERTEX_INPUT_RATE_VERTEX);
|
||||
VertexInputStateBuilder& AddAttribute(Uint32 location, Uint32 binding, VkFormat format, Uint32 offset);
|
||||
|
||||
const VkPipelineVertexInputStateCreateInfo& Build();
|
||||
const Vector<VkVertexInputBindingDescription>& GetBindings() const;
|
||||
const Vector<VkVertexInputAttributeDescription>& GetAttributes() const;
|
||||
|
||||
private:
|
||||
VkPipelineVertexInputStateCreateInfo m_state{};
|
||||
Vector<VkVertexInputBindingDescription> m_bindings;
|
||||
Vector<VkVertexInputAttributeDescription> m_attributes;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,237 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.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 "VertexInputStateFactory.h"
|
||||
#include "MG_Util/Converters/MGToStr/DataTypeConverter.h"
|
||||
#include <utility>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash(
|
||||
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
|
||||
for (Int i = 0; i < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) {
|
||||
const auto& attr = vao.GetAttribute(i);
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Enabled, sizeof(attr.Enabled)));
|
||||
if (!attr.Enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Size, sizeof(attr.Size)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Type, sizeof(attr.Type)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Normalized, sizeof(attr.Normalized)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||
|
||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
||||
}
|
||||
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao) {
|
||||
const HashType hash = ComputeHash(vao);
|
||||
auto it = m_cache.find(hash);
|
||||
if (it != m_cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
VertexInputStateBuilder builder;
|
||||
UnorderedMap<SizeT, Uint32> bindingByBufferKey;
|
||||
UnorderedMap<SizeT, Uint32> strideByBufferKey;
|
||||
UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
|
||||
Vector<SizeT> bindingBufferKeys;
|
||||
|
||||
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
||||
const auto& attr = vao.GetAttribute(location);
|
||||
if (!attr.Enabled || !attr.Buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger);
|
||||
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_D("Skipping unsupported vertex attribute layout (location=%u, type=%s, size=%d)",
|
||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SizeT componentSize = GetComponentSize(attr.Type);
|
||||
if (componentSize == 0) {
|
||||
MGLOG_D("Skipping vertex attribute with unknown component size (location=%u, type=%s)",
|
||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
const Uint32 stride = attr.Stride > 0
|
||||
? static_cast<Uint32>(attr.Stride)
|
||||
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
|
||||
const VkVertexInputRate inputRate =
|
||||
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||
|
||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||
Uint32 binding = 0;
|
||||
auto itBinding = bindingByBufferKey.find(bufferKey);
|
||||
if (itBinding == bindingByBufferKey.end()) {
|
||||
binding = static_cast<Uint32>(bindingByBufferKey.size());
|
||||
bindingByBufferKey.emplace(bufferKey, binding);
|
||||
strideByBufferKey.emplace(bufferKey, stride);
|
||||
inputRateByBufferKey.emplace(bufferKey, inputRate);
|
||||
bindingBufferKeys.push_back(bufferKey);
|
||||
builder.AddBinding(binding, stride, inputRate);
|
||||
} else {
|
||||
binding = itBinding->second;
|
||||
if (strideByBufferKey[bufferKey] != stride) {
|
||||
MGLOG_D("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) on same buffer",
|
||||
location, stride, strideByBufferKey[bufferKey]);
|
||||
continue;
|
||||
}
|
||||
if (inputRateByBufferKey[bufferKey] != inputRate) {
|
||||
MGLOG_D("Skipping vertex attribute at location %u: input-rate mismatch on same buffer", location);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddAttribute(location, binding, vkFormat, static_cast<Uint32>(attr.Offset));
|
||||
}
|
||||
|
||||
const auto& state = builder.Build();
|
||||
|
||||
auto& entry = m_cache[hash];
|
||||
entry.hash = hash;
|
||||
entry.bindings = builder.GetBindings();
|
||||
entry.attributes = builder.GetAttributes();
|
||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||
entry.state = state;
|
||||
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
||||
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
||||
return entry;
|
||||
}
|
||||
|
||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) {
|
||||
switch (type) {
|
||||
case DataType::Float32:
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||
case 2: return VK_FORMAT_R32G32_SFLOAT;
|
||||
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
|
||||
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Int32:
|
||||
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32_SINT;
|
||||
case 2: return VK_FORMAT_R32G32_SINT;
|
||||
case 3: return VK_FORMAT_R32G32B32_SINT;
|
||||
case 4: return VK_FORMAT_R32G32B32A32_SINT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Uint32:
|
||||
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32_UINT;
|
||||
case 2: return VK_FORMAT_R32G32_UINT;
|
||||
case 3: return VK_FORMAT_R32G32B32_UINT;
|
||||
case 4: return VK_FORMAT_R32G32B32A32_UINT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Int16:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R16_SINT
|
||||
: (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R16G16_SINT
|
||||
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
||||
case 3:
|
||||
return isInteger ? VK_FORMAT_R16G16B16_SINT
|
||||
: (normalized ? VK_FORMAT_R16G16B16_SNORM : VK_FORMAT_R16G16B16_SSCALED);
|
||||
case 4:
|
||||
return isInteger ? VK_FORMAT_R16G16B16A16_SINT
|
||||
: (normalized ? VK_FORMAT_R16G16B16A16_SNORM : VK_FORMAT_R16G16B16A16_SSCALED);
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Uint16:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R16_UINT
|
||||
: (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R16G16_UINT
|
||||
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
||||
case 3:
|
||||
return isInteger ? VK_FORMAT_R16G16B16_UINT
|
||||
: (normalized ? VK_FORMAT_R16G16B16_UNORM : VK_FORMAT_R16G16B16_USCALED);
|
||||
case 4:
|
||||
return isInteger ? VK_FORMAT_R16G16B16A16_UINT
|
||||
: (normalized ? VK_FORMAT_R16G16B16A16_UNORM : VK_FORMAT_R16G16B16A16_USCALED);
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Int8:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R8_SINT
|
||||
: (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R8G8_SINT
|
||||
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
||||
case 3:
|
||||
return isInteger ? VK_FORMAT_R8G8B8_SINT
|
||||
: (normalized ? VK_FORMAT_R8G8B8_SNORM : VK_FORMAT_R8G8B8_SSCALED);
|
||||
case 4:
|
||||
return isInteger ? VK_FORMAT_R8G8B8A8_SINT
|
||||
: (normalized ? VK_FORMAT_R8G8B8A8_SNORM : VK_FORMAT_R8G8B8A8_SSCALED);
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Uint8:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R8_UINT
|
||||
: (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R8G8_UINT
|
||||
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
||||
case 3:
|
||||
return isInteger ? VK_FORMAT_R8G8B8_UINT
|
||||
: (normalized ? VK_FORMAT_R8G8B8_UNORM : VK_FORMAT_R8G8B8_USCALED);
|
||||
case 4:
|
||||
return isInteger ? VK_FORMAT_R8G8B8A8_UINT
|
||||
: (normalized ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_USCALED);
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT VertexInputStateFactory::GetComponentSize(DataType type) {
|
||||
switch (type) {
|
||||
case DataType::Int8:
|
||||
case DataType::Uint8:
|
||||
return 1;
|
||||
case DataType::Int16:
|
||||
case DataType::Uint16:
|
||||
case DataType::Float16:
|
||||
return 2;
|
||||
case DataType::Int32:
|
||||
case DataType::Uint32:
|
||||
case DataType::Float32:
|
||||
case DataType::Fixed32:
|
||||
return 4;
|
||||
case DataType::Float64:
|
||||
return 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,48 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.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 "Config.h"
|
||||
#include "VertexInputStateBuilder.h"
|
||||
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
|
||||
#include <Includes.h>
|
||||
#include "../VkIncludes.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VertexInputStateFactory {
|
||||
public:
|
||||
using HashType = Uint64;
|
||||
|
||||
struct BackendVertexInputState {
|
||||
HashType hash = 0;
|
||||
Vector<VkVertexInputBindingDescription> bindings;
|
||||
Vector<VkVertexInputAttributeDescription> attributes;
|
||||
Vector<SizeT> bindingBufferKeys;
|
||||
VkPipelineVertexInputStateCreateInfo state{
|
||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||
};
|
||||
};
|
||||
|
||||
explicit VertexInputStateFactory(const VulkanRendererConfig& config):
|
||||
m_config(config) {}
|
||||
~VertexInputStateFactory() = default;
|
||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||
|
||||
private:
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger);
|
||||
static SizeT GetComponentSize(DataType type);
|
||||
|
||||
const VulkanRendererConfig& m_config;
|
||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,110 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.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 "VkBufferObject.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkBufferObject::VkBufferObject(VkBufferObject&& other) noexcept {
|
||||
m_allocator = other.m_allocator;
|
||||
m_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
|
||||
VkBufferObject& VkBufferObject::operator=(VkBufferObject&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Destroy();
|
||||
|
||||
m_allocator = other.m_allocator;
|
||||
m_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VkBufferObject::~VkBufferObject() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) {
|
||||
MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator");
|
||||
MOBILEGL_ASSERT(size > 0, "VkBufferObject::Create requires non-zero size");
|
||||
|
||||
Destroy();
|
||||
m_allocator = allocator;
|
||||
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = size;
|
||||
bufferInfo.usage = usage;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VmaAllocationCreateInfo allocationInfo{};
|
||||
allocationInfo.usage = memoryUsage;
|
||||
allocationInfo.flags = allocationFlags;
|
||||
|
||||
const VkResult result =
|
||||
vmaCreateBuffer(m_allocator, &bufferInfo, &allocationInfo, &m_buffer, &m_allocation, nullptr);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_E("VkBufferObject::Create failed: vmaCreateBuffer returned %d", result);
|
||||
m_allocator = nullptr;
|
||||
m_buffer = VK_NULL_HANDLE;
|
||||
m_allocation = nullptr;
|
||||
m_size = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_size = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferObject::Destroy() {
|
||||
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
|
||||
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
||||
}
|
||||
m_buffer = VK_NULL_HANDLE;
|
||||
m_allocation = nullptr;
|
||||
m_allocator = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
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");
|
||||
MOBILEGL_ASSERT(offset + size <= m_size, "VkBufferObject::Upload out of range");
|
||||
|
||||
if (size == 0) {
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
||||
vmaUnmapMemory(m_allocator, m_allocation);
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,42 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.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>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkBufferObject {
|
||||
public:
|
||||
VkBufferObject() = default;
|
||||
~VkBufferObject();
|
||||
|
||||
VkBufferObject(const VkBufferObject&) = delete;
|
||||
VkBufferObject& operator=(const VkBufferObject&) = delete;
|
||||
VkBufferObject(VkBufferObject&& other) noexcept;
|
||||
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
|
||||
|
||||
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0);
|
||||
void Destroy();
|
||||
|
||||
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
|
||||
|
||||
VkBuffer GetHandle() const { return m_buffer; }
|
||||
VkDeviceSize GetSize() const { return m_size; }
|
||||
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;
|
||||
VkDeviceSize m_size = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,547 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.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 "VkFramebufferManager.h"
|
||||
|
||||
#include <MG_State/GLState/RenderbufferState/RenderbufferObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureEnum.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool VkFramebufferManager::Initialize(const InitInfo& initInfo) {
|
||||
m_device = initInfo.device;
|
||||
m_physicalDevice = initInfo.physicalDevice;
|
||||
return m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VkFramebufferManager::Shutdown() {
|
||||
for (auto& [_, target] : m_offscreenColorTargets) {
|
||||
DestroyOffscreenColorTarget(target);
|
||||
}
|
||||
m_offscreenColorTargets.clear();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_physicalDevice = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::EnsureOffscreenColorTarget(Uint glFboExternalIndex,
|
||||
const MG_State::GLState::FramebufferObject& glFbo) {
|
||||
const auto& colorAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Color0);
|
||||
if (!colorAttachment.IsValid() || colorAttachment.IsEmpty()) {
|
||||
MGLOG_W("VkFramebufferManager: FBO %u has no valid COLOR0 attachment", glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectVersion = glFbo.GetObjectVersion();
|
||||
auto& target = m_offscreenColorTargets[glFboExternalIndex];
|
||||
if (target.image != VK_NULL_HANDLE && target.glObjectVersion == objectVersion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return RecreateOffscreenColorTarget(target, glFbo, colorAttachment, objectVersion);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToAttachment skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (!TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
|
||||
return TransitionImageLayout(
|
||||
commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0,
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferSrc skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToTransferDst(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferDst skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToGeneral(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToGeneral skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToTransferSrc(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToTransferSrc skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_READ_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToTransferDst(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToTransferDst skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToGeneral(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToGeneral skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer,
|
||||
Uint textureExternalIndex) {
|
||||
for (auto& [_, target] : m_offscreenColorTargets) {
|
||||
if (target.colorTextureExternalIndex != textureExternalIndex || target.image == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
const Bool fromUndefined = (target.layout == VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
return TransitionImageLayout(
|
||||
commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
fromUndefined ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
|
||||
: (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT),
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
fromUndefined ? 0 : (VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT),
|
||||
VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage,
|
||||
VkExtent2D& outExtent) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.image == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImage = it->second.image;
|
||||
outExtent = it->second.extent;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenDepthStencilImage(Uint glFboExternalIndex, VkImage& outImage,
|
||||
VkExtent2D& outExtent, VkFormat& outFormat) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImage = it->second.depthStencilImage;
|
||||
outExtent = it->second.extent;
|
||||
outFormat = it->second.depthStencilFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenColorViewByTexture(Uint textureExternalIndex,
|
||||
VkImageView& outImageView) const {
|
||||
for (const auto& [_, target] : m_offscreenColorTargets) {
|
||||
if (target.colorTextureExternalIndex != textureExternalIndex || target.imageView == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
outImageView = target.imageView;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView,
|
||||
VkFormat& outColorFormat, VkImageView& outDepthStencilView,
|
||||
VkFormat& outDepthStencilFormat, VkExtent2D& outExtent) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.imageView == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outColorView = it->second.imageView;
|
||||
outColorFormat = it->second.format;
|
||||
outDepthStencilView = it->second.depthStencilImageView;
|
||||
outExtent = it->second.extent;
|
||||
outDepthStencilFormat = it->second.depthStencilFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::RecreateOffscreenColorTarget(
|
||||
OffscreenColorTarget& target, const MG_State::GLState::FramebufferObject& glFbo,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, Uint16 glObjectVersion) {
|
||||
DestroyOffscreenColorTarget(target);
|
||||
|
||||
const auto size = colorAttachment.GetSize();
|
||||
if (size.x() <= 0 || size.y() <= 0) {
|
||||
MGLOG_W("VkFramebufferManager: COLOR0 attachment size is invalid (%d, %d)", size.x(), size.y());
|
||||
return false;
|
||||
}
|
||||
|
||||
const VkFormat format = ResolveColorFormat(colorAttachment);
|
||||
if (format == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_W("VkFramebufferManager: COLOR0 attachment format is unsupported for Vulkan clear");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent.width = static_cast<Uint32>(size.x());
|
||||
imageInfo.extent.height = static_cast<Uint32>(size.y());
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = format;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &target.image), "vkCreateImage(offscreen color)");
|
||||
|
||||
VkMemoryRequirements memoryRequirements{};
|
||||
vkGetImageMemoryRequirements(m_device, target.image, &memoryRequirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memoryRequirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &target.memory), "vkAllocateMemory(offscreen color)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, target.image, target.memory, 0), "vkBindImageMemory(offscreen color)");
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = target.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = format;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &target.imageView),
|
||||
"vkCreateImageView(offscreen color)");
|
||||
|
||||
const auto& depthAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Depth);
|
||||
const auto& stencilAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Stencil);
|
||||
const Bool requestedDepthStencil = (depthAttachment.IsValid() && !depthAttachment.IsEmpty()) ||
|
||||
(stencilAttachment.IsValid() && !stencilAttachment.IsEmpty());
|
||||
VkFormat depthStencilFormat = ResolveDepthStencilFormat(depthAttachment, stencilAttachment);
|
||||
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT) {
|
||||
depthStencilFormat =
|
||||
FindSupportedDepthStencilFormat({VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT});
|
||||
} else if (depthStencilFormat == VK_FORMAT_D32_SFLOAT) {
|
||||
depthStencilFormat = FindSupportedDepthStencilFormat({VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM});
|
||||
}
|
||||
const Bool hasDepthStencil = (depthStencilFormat != VK_FORMAT_UNDEFINED);
|
||||
if (requestedDepthStencil && !hasDepthStencil) {
|
||||
MGLOG_W("VkFramebufferManager: FBO %u depth/stencil attachment exists but format is unsupported",
|
||||
glFbo.GetExternalIndex());
|
||||
}
|
||||
if (hasDepthStencil) {
|
||||
VkImageCreateInfo depthImageInfo{};
|
||||
depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
depthImageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
depthImageInfo.extent.width = static_cast<Uint32>(size.x());
|
||||
depthImageInfo.extent.height = static_cast<Uint32>(size.y());
|
||||
depthImageInfo.extent.depth = 1;
|
||||
depthImageInfo.mipLevels = 1;
|
||||
depthImageInfo.arrayLayers = 1;
|
||||
depthImageInfo.format = depthStencilFormat;
|
||||
depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &depthImageInfo, nullptr, &target.depthStencilImage),
|
||||
"vkCreateImage(offscreen depth/stencil)");
|
||||
|
||||
VkMemoryRequirements depthMemoryRequirements{};
|
||||
vkGetImageMemoryRequirements(m_device, target.depthStencilImage, &depthMemoryRequirements);
|
||||
VkMemoryAllocateInfo depthAllocInfo{};
|
||||
depthAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
depthAllocInfo.allocationSize = depthMemoryRequirements.size;
|
||||
depthAllocInfo.memoryTypeIndex =
|
||||
FindMemoryType(depthMemoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &depthAllocInfo, nullptr, &target.depthStencilMemory),
|
||||
"vkAllocateMemory(offscreen depth/stencil)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, target.depthStencilImage, target.depthStencilMemory, 0),
|
||||
"vkBindImageMemory(offscreen depth/stencil)");
|
||||
|
||||
VkImageAspectFlags depthAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
depthAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
VkImageViewCreateInfo depthViewInfo{};
|
||||
depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
depthViewInfo.image = target.depthStencilImage;
|
||||
depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
depthViewInfo.format = depthStencilFormat;
|
||||
depthViewInfo.subresourceRange.aspectMask = depthAspectMask;
|
||||
depthViewInfo.subresourceRange.baseMipLevel = 0;
|
||||
depthViewInfo.subresourceRange.levelCount = 1;
|
||||
depthViewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
depthViewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &depthViewInfo, nullptr, &target.depthStencilImageView),
|
||||
"vkCreateImageView(offscreen depth/stencil)");
|
||||
}
|
||||
|
||||
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.extent = {static_cast<Uint32>(size.x()), static_cast<Uint32>(size.y())};
|
||||
target.format = format;
|
||||
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.depthStencilFormat = depthStencilFormat;
|
||||
target.glObjectVersion = glObjectVersion;
|
||||
target.colorTextureExternalIndex = (colorAttachment.IsTexture() && colorAttachment.GetTexture())
|
||||
? colorAttachment.GetTexture()->GetExternalIndex()
|
||||
: 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkFramebufferManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) {
|
||||
if (target.imageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, target.imageView, nullptr);
|
||||
target.imageView = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilImageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, target.depthStencilImageView, nullptr);
|
||||
target.depthStencilImageView = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.image != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, target.image, nullptr);
|
||||
target.image = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilImage != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, target.depthStencilImage, nullptr);
|
||||
target.depthStencilImage = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, target.memory, nullptr);
|
||||
target.memory = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, target.depthStencilMemory, nullptr);
|
||||
target.depthStencilMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.extent = {0, 0};
|
||||
target.format = VK_FORMAT_UNDEFINED;
|
||||
target.depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
target.glObjectVersion = 0;
|
||||
target.colorTextureExternalIndex = 0;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
|
||||
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask) {
|
||||
if (image == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
if (trackedLayout == newLayout) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = srcAccessMask;
|
||||
barrier.dstAccessMask = dstAccessMask;
|
||||
barrier.oldLayout = trackedLayout;
|
||||
barrier.newLayout = newLayout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = aspectMask;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
|
||||
trackedLayout = newLayout;
|
||||
return true;
|
||||
}
|
||||
|
||||
Uint32 VkFramebufferManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const {
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
||||
for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) {
|
||||
if ((typeFilter & (1U << i)) &&
|
||||
(memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
MOBILEGL_ASSERT(false, "VkFramebufferManager::FindMemoryType failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::ResolveColorFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment) {
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
if (colorAttachment.IsTexture()) {
|
||||
const auto texture = colorAttachment.GetTexture();
|
||||
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
|
||||
} else if (colorAttachment.IsRenderbuffer()) {
|
||||
const auto renderbuffer = colorAttachment.GetRenderbuffer();
|
||||
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
|
||||
}
|
||||
|
||||
switch (internalFormat) {
|
||||
case TextureInternalFormat::RGBA:
|
||||
case TextureInternalFormat::RGBA8:
|
||||
case TextureInternalFormat::SRGB8Alpha8:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::ResolveDepthStencilFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment) {
|
||||
const auto resolveAttachmentFormat = [](const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
if (attachment.IsTexture()) {
|
||||
const auto texture = attachment.GetTexture();
|
||||
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
|
||||
} else if (attachment.IsRenderbuffer()) {
|
||||
const auto renderbuffer = attachment.GetRenderbuffer();
|
||||
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
|
||||
}
|
||||
return internalFormat;
|
||||
};
|
||||
|
||||
const auto depthFormat = resolveAttachmentFormat(depthAttachment);
|
||||
const auto stencilFormat = resolveAttachmentFormat(stencilAttachment);
|
||||
|
||||
switch (depthFormat) {
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return VK_FORMAT_D24_UNORM_S8_UINT;
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
return VK_FORMAT_D16_UNORM;
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
return VK_FORMAT_D32_SFLOAT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (stencilFormat) {
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return VK_FORMAT_D24_UNORM_S8_UINT;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const {
|
||||
for (auto format : candidates) {
|
||||
VkFormatProperties properties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
|
||||
if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,83 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.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>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkFramebufferManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
VkFramebufferManager() = default;
|
||||
~VkFramebufferManager() = default;
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo);
|
||||
Bool TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToTransferDst(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToGeneral(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToTransferDst(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToGeneral(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer, Uint textureExternalIndex);
|
||||
Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const;
|
||||
Bool GetOffscreenDepthStencilImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent,
|
||||
VkFormat& outFormat) const;
|
||||
Bool GetOffscreenColorViewByTexture(Uint textureExternalIndex, VkImageView& outImageView) const;
|
||||
Bool GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView, VkFormat& outColorFormat,
|
||||
VkImageView& outDepthStencilView, VkFormat& outDepthStencilFormat,
|
||||
VkExtent2D& outExtent) const;
|
||||
|
||||
private:
|
||||
struct OffscreenColorTarget {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImage depthStencilImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory depthStencilMemory = VK_NULL_HANDLE;
|
||||
VkImageView depthStencilImageView = VK_NULL_HANDLE;
|
||||
VkImageLayout depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Uint16 glObjectVersion = 0;
|
||||
Uint colorTextureExternalIndex = 0;
|
||||
};
|
||||
|
||||
Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target,
|
||||
const MG_State::GLState::FramebufferObject& glFbo,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment,
|
||||
Uint16 glObjectVersion);
|
||||
void DestroyOffscreenColorTarget(OffscreenColorTarget& target);
|
||||
Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
|
||||
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
static VkFormat ResolveColorFormat(const MG_State::GLState::FramebufferAttachmentObject& colorAttachment);
|
||||
static VkFormat ResolveDepthStencilFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment);
|
||||
VkFormat FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
UnorderedMap<Uint, OffscreenColorTarget> m_offscreenColorTargets;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,347 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.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 "VkRenderPassManager.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool VkRenderPassManager::Initialize(const InitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
if (initInfo.device == VK_NULL_HANDLE || initInfo.colorFormat == VK_FORMAT_UNDEFINED ||
|
||||
initInfo.depthStencilFormat == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device = initInfo.device;
|
||||
m_colorFormat = initInfo.colorFormat;
|
||||
m_depthStencilFormat = initInfo.depthStencilFormat;
|
||||
m_renderPassLoad = CreateDefaultRenderPass(VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
m_renderPassClear = CreateDefaultRenderPass(VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||
MGLOG_D("VkRenderPassManager: RenderPasses created (LOAD/CLEAR).");
|
||||
return m_renderPassLoad != VK_NULL_HANDLE && m_renderPassClear != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VkRenderPassManager::Shutdown() {
|
||||
DestroyDefaultFramebuffers();
|
||||
for (auto& [_, target] : m_offscreenRenderTargets) {
|
||||
DestroyOffscreenRenderTarget(target);
|
||||
}
|
||||
m_offscreenRenderTargets.clear();
|
||||
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
if (m_renderPassClear != VK_NULL_HANDLE) {
|
||||
vkDestroyRenderPass(m_device, m_renderPassClear, nullptr);
|
||||
}
|
||||
if (m_renderPassLoad != VK_NULL_HANDLE) {
|
||||
vkDestroyRenderPass(m_device, m_renderPassLoad, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
m_renderPassClear = VK_NULL_HANDLE;
|
||||
m_renderPassLoad = VK_NULL_HANDLE;
|
||||
m_colorFormat = VK_FORMAT_UNDEFINED;
|
||||
m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
m_defaultExtent = {0, 0};
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkRenderPassManager::RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
|
||||
const Vector<VkImageView>& depthStencilViews,
|
||||
VkExtent2D extent) {
|
||||
DestroyDefaultFramebuffers();
|
||||
|
||||
if (m_device == VK_NULL_HANDLE || m_renderPassLoad == VK_NULL_HANDLE || extent.width == 0 ||
|
||||
extent.height == 0 || colorViews.empty() || colorViews.size() != depthStencilViews.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_defaultFramebuffers.reserve(colorViews.size());
|
||||
for (SizeT i = 0; i < colorViews.size(); ++i) {
|
||||
if (colorViews[i] == VK_NULL_HANDLE || depthStencilViews[i] == VK_NULL_HANDLE) {
|
||||
DestroyDefaultFramebuffers();
|
||||
return false;
|
||||
}
|
||||
VkImageView attachments[] = {colorViews[i], depthStencilViews[i]};
|
||||
VkFramebufferCreateInfo createInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
createInfo.renderPass = m_renderPassLoad;
|
||||
createInfo.attachmentCount = static_cast<Uint32>(std::size(attachments));
|
||||
createInfo.pAttachments = attachments;
|
||||
createInfo.width = extent.width;
|
||||
createInfo.height = extent.height;
|
||||
createInfo.layers = 1;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
if (vkCreateFramebuffer(m_device, &createInfo, nullptr, &framebuffer) != VK_SUCCESS) {
|
||||
DestroyDefaultFramebuffers();
|
||||
return false;
|
||||
}
|
||||
m_defaultFramebuffers.push_back(framebuffer);
|
||||
}
|
||||
m_defaultExtent = extent;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkRenderPassManager::GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass,
|
||||
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
|
||||
VkFormat& outDepthStencilFormat) const {
|
||||
if (imageIndex >= m_defaultFramebuffers.size() || m_renderPassLoad == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outRenderPass = m_renderPassLoad;
|
||||
outFramebuffer = m_defaultFramebuffers[imageIndex];
|
||||
outExtent = m_defaultExtent;
|
||||
outDepthStencilFormat = m_depthStencilFormat;
|
||||
return outFramebuffer != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkRenderPassManager::EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo) {
|
||||
if (m_device == VK_NULL_HANDLE || targetInfo.colorView == VK_NULL_HANDLE ||
|
||||
targetInfo.colorFormat == VK_FORMAT_UNDEFINED || targetInfo.extent.width == 0 ||
|
||||
targetInfo.extent.height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool hasDepthStencil =
|
||||
targetInfo.depthStencilView != VK_NULL_HANDLE && targetInfo.depthStencilFormat != VK_FORMAT_UNDEFINED;
|
||||
|
||||
auto& target = m_offscreenRenderTargets[targetInfo.targetExternalIndex];
|
||||
if (target.framebuffer != VK_NULL_HANDLE && target.renderPassLoad != VK_NULL_HANDLE &&
|
||||
target.targetVersion == targetInfo.targetVersion && target.colorView == targetInfo.colorView &&
|
||||
target.colorFormat == targetInfo.colorFormat && target.depthStencilView == targetInfo.depthStencilView &&
|
||||
target.depthStencilFormat == targetInfo.depthStencilFormat &&
|
||||
target.extent.width == targetInfo.extent.width && target.extent.height == targetInfo.extent.height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyOffscreenRenderTarget(target);
|
||||
|
||||
const VkFormat depthStencilFormat = hasDepthStencil ? targetInfo.depthStencilFormat : VK_FORMAT_UNDEFINED;
|
||||
target.renderPassLoad = CreateRenderPass(targetInfo.colorFormat, depthStencilFormat, VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
if (target.renderPassLoad == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Array<VkImageView, 2> attachments{};
|
||||
attachments[0] = targetInfo.colorView;
|
||||
Uint32 attachmentCount = 1;
|
||||
if (hasDepthStencil) {
|
||||
attachments[1] = targetInfo.depthStencilView;
|
||||
attachmentCount = 2;
|
||||
}
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebufferInfo.renderPass = target.renderPassLoad;
|
||||
framebufferInfo.attachmentCount = attachmentCount;
|
||||
framebufferInfo.pAttachments = attachments.data();
|
||||
framebufferInfo.width = targetInfo.extent.width;
|
||||
framebufferInfo.height = targetInfo.extent.height;
|
||||
framebufferInfo.layers = 1;
|
||||
VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &target.framebuffer),
|
||||
"vkCreateFramebuffer(offscreen)");
|
||||
|
||||
target.targetVersion = targetInfo.targetVersion;
|
||||
target.colorView = targetInfo.colorView;
|
||||
target.colorFormat = targetInfo.colorFormat;
|
||||
target.depthStencilView = targetInfo.depthStencilView;
|
||||
target.depthStencilFormat = targetInfo.depthStencilFormat;
|
||||
target.extent = targetInfo.extent;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkRenderPassManager::GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
|
||||
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
|
||||
VkFormat& outDepthStencilFormat) const {
|
||||
auto it = m_offscreenRenderTargets.find(targetExternalIndex);
|
||||
if (it == m_offscreenRenderTargets.end() || it->second.renderPassLoad == VK_NULL_HANDLE ||
|
||||
it->second.framebuffer == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRenderPass = it->second.renderPassLoad;
|
||||
outFramebuffer = it->second.framebuffer;
|
||||
outExtent = it->second.extent;
|
||||
outDepthStencilFormat = it->second.depthStencilFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkRenderPassManager::RemoveOffscreenRenderTarget(Uint targetExternalIndex) {
|
||||
auto it = m_offscreenRenderTargets.find(targetExternalIndex);
|
||||
if (it == m_offscreenRenderTargets.end()) {
|
||||
return;
|
||||
}
|
||||
DestroyOffscreenRenderTarget(it->second);
|
||||
m_offscreenRenderTargets.erase(it);
|
||||
}
|
||||
|
||||
void VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass,
|
||||
VkFramebuffer framebuffer, VkExtent2D extent) const {
|
||||
VkRenderPassBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
beginInfo.renderPass = renderPass;
|
||||
beginInfo.framebuffer = framebuffer;
|
||||
beginInfo.renderArea.offset = {0, 0};
|
||||
beginInfo.renderArea.extent = extent;
|
||||
beginInfo.clearValueCount = 0;
|
||||
beginInfo.pClearValues = nullptr;
|
||||
vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
}
|
||||
|
||||
void VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) const {
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
}
|
||||
|
||||
void VkRenderPassManager::RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
|
||||
const VkClearColorValue& clearColor) const {
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = 0;
|
||||
clearAttachment.clearValue.color = clearColor;
|
||||
|
||||
VkClearRect clearRect{};
|
||||
clearRect.rect.offset = {0, 0};
|
||||
clearRect.rect.extent = extent;
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
|
||||
vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
void VkRenderPassManager::RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask,
|
||||
Float depth, Uint32 stencil, VkFormat depthStencilFormat) const {
|
||||
if (depthStencilFormat == VK_FORMAT_UNDEFINED) {
|
||||
return;
|
||||
}
|
||||
|
||||
VkImageAspectFlags aspectMask = 0;
|
||||
if ((mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
if ((mask & GL_STENCIL_BUFFER_BIT) != 0 && HasStencilComponent(depthStencilFormat)) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
if (aspectMask == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = aspectMask;
|
||||
clearAttachment.clearValue.depthStencil.depth = depth;
|
||||
clearAttachment.clearValue.depthStencil.stencil = stencil;
|
||||
|
||||
VkClearRect clearRect{};
|
||||
clearRect.rect.offset = {0, 0};
|
||||
clearRect.rect.extent = extent;
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
|
||||
vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
VkRenderPass VkRenderPassManager::GetLoadRenderPass() const {
|
||||
return m_renderPassLoad;
|
||||
}
|
||||
|
||||
VkRenderPass VkRenderPassManager::GetClearRenderPass() const {
|
||||
return m_renderPassClear;
|
||||
}
|
||||
|
||||
VkRenderPass VkRenderPassManager::CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const {
|
||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "VkRenderPassManager: device is null");
|
||||
MOBILEGL_ASSERT(m_colorFormat != VK_FORMAT_UNDEFINED, "VkRenderPassManager: color format is undefined");
|
||||
MOBILEGL_ASSERT(m_depthStencilFormat != VK_FORMAT_UNDEFINED,
|
||||
"VkRenderPassManager: depth/stencil format is undefined");
|
||||
return CreateRenderPass(m_colorFormat, m_depthStencilFormat, colorLoadOp, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
}
|
||||
|
||||
VkRenderPass VkRenderPassManager::CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat,
|
||||
VkAttachmentLoadOp colorLoadOp,
|
||||
VkImageLayout colorFinalLayout) const {
|
||||
VkAttachmentDescription color{};
|
||||
color.format = colorFormat;
|
||||
color.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
color.loadOp = colorLoadOp;
|
||||
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
color.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
color.finalLayout = colorFinalLayout;
|
||||
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
|
||||
const Bool hasDepthStencil = depthStencilFormat != VK_FORMAT_UNDEFINED;
|
||||
VkAttachmentDescription depthStencil{};
|
||||
if (hasDepthStencil) {
|
||||
depthStencil.format = depthStencilFormat;
|
||||
depthStencil.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depthStencil.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
depthStencil.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
depthStencil.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
depthStencil.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
depthStencil.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
depthStencil.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
|
||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depthStencilRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
if (hasDepthStencil) {
|
||||
subpass.pDepthStencilAttachment = &depthStencilRef;
|
||||
}
|
||||
|
||||
Array<VkAttachmentDescription, 2> attachments{};
|
||||
attachments[0] = color;
|
||||
Uint32 attachmentCount = 1;
|
||||
if (hasDepthStencil) {
|
||||
attachments[1] = depthStencil;
|
||||
attachmentCount = 2;
|
||||
}
|
||||
|
||||
VkRenderPassCreateInfo createInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
createInfo.attachmentCount = attachmentCount;
|
||||
createInfo.pAttachments = attachments.data();
|
||||
createInfo.subpassCount = 1;
|
||||
createInfo.pSubpasses = &subpass;
|
||||
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateRenderPass(m_device, &createInfo, nullptr, &renderPass), "vkCreateRenderPass");
|
||||
return renderPass;
|
||||
}
|
||||
|
||||
void VkRenderPassManager::DestroyDefaultFramebuffers() {
|
||||
for (auto framebuffer : m_defaultFramebuffers) {
|
||||
if (framebuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
|
||||
}
|
||||
}
|
||||
m_defaultFramebuffers.clear();
|
||||
m_defaultExtent = {0, 0};
|
||||
}
|
||||
|
||||
void VkRenderPassManager::DestroyOffscreenRenderTarget(OffscreenRenderTarget& target) {
|
||||
if (target.framebuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyFramebuffer(m_device, target.framebuffer, nullptr);
|
||||
target.framebuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.renderPassLoad != VK_NULL_HANDLE) {
|
||||
vkDestroyRenderPass(m_device, target.renderPassLoad, nullptr);
|
||||
target.renderPassLoad = VK_NULL_HANDLE;
|
||||
}
|
||||
target.targetVersion = 0;
|
||||
target.colorView = VK_NULL_HANDLE;
|
||||
target.colorFormat = VK_FORMAT_UNDEFINED;
|
||||
target.depthStencilView = VK_NULL_HANDLE;
|
||||
target.depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
target.extent = {0, 0};
|
||||
}
|
||||
|
||||
Bool VkRenderPassManager::HasStencilComponent(VkFormat format) {
|
||||
return format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,86 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.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 {
|
||||
class VkRenderPassManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
};
|
||||
|
||||
struct OffscreenRenderTargetInfo {
|
||||
Uint targetExternalIndex = 0;
|
||||
Uint16 targetVersion = 0;
|
||||
VkImageView colorView = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageView depthStencilView = VK_NULL_HANDLE;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
Bool RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
|
||||
const Vector<VkImageView>& depthStencilViews, VkExtent2D extent);
|
||||
Bool GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
||||
|
||||
Bool EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo);
|
||||
Bool GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
|
||||
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
|
||||
VkFormat& outDepthStencilFormat) const;
|
||||
void RemoveOffscreenRenderTarget(Uint targetExternalIndex);
|
||||
|
||||
void BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer,
|
||||
VkExtent2D extent) const;
|
||||
void EndRenderPass(VkCommandBuffer commandBuffer) const;
|
||||
void RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
|
||||
const VkClearColorValue& clearColor) const;
|
||||
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask, Float depth,
|
||||
Uint32 stencil, VkFormat depthStencilFormat) const;
|
||||
|
||||
VkRenderPass GetLoadRenderPass() const;
|
||||
VkRenderPass GetClearRenderPass() const;
|
||||
|
||||
private:
|
||||
struct OffscreenRenderTarget {
|
||||
Uint16 targetVersion = 0;
|
||||
VkImageView colorView = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageView depthStencilView = VK_NULL_HANDLE;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkRenderPass renderPassLoad = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
VkRenderPass CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const;
|
||||
VkRenderPass CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat, VkAttachmentLoadOp colorLoadOp,
|
||||
VkImageLayout colorFinalLayout) const;
|
||||
void DestroyDefaultFramebuffers();
|
||||
void DestroyOffscreenRenderTarget(OffscreenRenderTarget& target);
|
||||
static Bool HasStencilComponent(VkFormat format);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkFormat m_colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkRenderPass m_renderPassLoad = VK_NULL_HANDLE;
|
||||
VkRenderPass m_renderPassClear = VK_NULL_HANDLE;
|
||||
Vector<VkFramebuffer> m_defaultFramebuffers;
|
||||
VkExtent2D m_defaultExtent = {0, 0};
|
||||
UnorderedMap<Uint, OffscreenRenderTarget> m_offscreenRenderTargets;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,644 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.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 "VkTextureSamplerManager.h"
|
||||
|
||||
#include "MG_State/GLState/Core.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
constexpr Uint64 BuildSamplerKey(Uint externalIndex, Uint16 version) {
|
||||
return (static_cast<Uint64>(externalIndex) << 16) | static_cast<Uint64>(version);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool VkTextureSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
m_device = initInfo.device;
|
||||
m_physicalDevice = initInfo.physicalDevice;
|
||||
m_commandPool = initInfo.commandPool;
|
||||
m_graphicsQueue = initInfo.graphicsQueue;
|
||||
|
||||
if (m_device == VK_NULL_HANDLE || m_physicalDevice == VK_NULL_HANDLE || m_commandPool == VK_NULL_HANDLE ||
|
||||
m_graphicsQueue == VK_NULL_HANDLE) {
|
||||
MGLOG_E("VkTextureSamplerManager::Initialize failed: invalid Vulkan handles");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!UploadFallbackTexture()) {
|
||||
MGLOG_E("VkTextureSamplerManager::Initialize failed: fallback texture creation failed");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTextureSamplerManager::Shutdown() {
|
||||
for (auto& [_, resource] : m_textureResources) {
|
||||
DestroyTextureResource(resource);
|
||||
}
|
||||
m_textureResources.clear();
|
||||
|
||||
for (auto& [_, sampler] : m_samplers) {
|
||||
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(m_device, sampler.handle, nullptr);
|
||||
}
|
||||
sampler.handle = VK_NULL_HANDLE;
|
||||
}
|
||||
m_samplers.clear();
|
||||
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackSampler != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(m_device, m_fallbackSampler, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, m_fallbackImageView, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImage != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, m_fallbackImage, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImageMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, m_fallbackImageMemory, nullptr);
|
||||
}
|
||||
m_fallbackSampler = VK_NULL_HANDLE;
|
||||
m_fallbackImageView = VK_NULL_HANDLE;
|
||||
m_fallbackImage = VK_NULL_HANDLE;
|
||||
m_fallbackImageMemory = VK_NULL_HANDLE;
|
||||
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_physicalDevice = VK_NULL_HANDLE;
|
||||
m_commandPool = VK_NULL_HANDLE;
|
||||
m_graphicsQueue = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const {
|
||||
if (m_fallbackSampler == VK_NULL_HANDLE || m_fallbackImageView == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImageInfo.sampler = m_fallbackSampler;
|
||||
outImageInfo.imageView = m_fallbackImageView;
|
||||
outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture,
|
||||
const MG_State::GLState::SamplerObject* samplerOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) {
|
||||
if (m_device == VK_NULL_HANDLE) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
auto it = m_textureResources.find(texture.GetExternalIndex());
|
||||
if (it == m_textureResources.end()) {
|
||||
TextureResource initial{};
|
||||
initial.textureExternalIndex = texture.GetExternalIndex();
|
||||
auto [insertIt, _] = m_textureResources.emplace(texture.GetExternalIndex(), initial);
|
||||
it = insertIt;
|
||||
}
|
||||
|
||||
if (!EnsureTextureSynced(it->second, texture)) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride;
|
||||
if (!samplerToUse) {
|
||||
auto textureSampler = texture.GetSamplerObject();
|
||||
if (textureSampler) {
|
||||
samplerToUse = textureSampler.get();
|
||||
}
|
||||
}
|
||||
|
||||
VkSampler sampler = m_fallbackSampler;
|
||||
if (samplerToUse) {
|
||||
sampler = GetOrCreateSampler(*samplerToUse);
|
||||
}
|
||||
if (sampler == VK_NULL_HANDLE) {
|
||||
sampler = m_fallbackSampler;
|
||||
}
|
||||
|
||||
if (it->second.view == VK_NULL_HANDLE || sampler == VK_NULL_HANDLE) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
outImageInfo.sampler = sampler;
|
||||
outImageInfo.imageView = it->second.view;
|
||||
outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::EnsureTextureSynced(TextureResource& resource,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
TextureUploadTarget level0Target = TextureUploadTarget::Unknown;
|
||||
IntVec3 texelSize{0, 0, 0};
|
||||
SizeT byteSize = 0;
|
||||
if (!ResolveLevel0(texture, level0Target, texelSize, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureTextureResource(resource, texture, level0Target, texelSize, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
if (!mipTexture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mipTexture->IsStorageDirty(level0Target, 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!UploadLevel0(resource, *mipTexture, level0Target, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& mutableTexture = const_cast<MG_State::GLState::TextureObjectMipmap&>(*mipTexture);
|
||||
mutableTexture.MarkStorageDirty(level0Target, 0, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::EnsureTextureResource(TextureResource& resource,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget level0Target, const IntVec3& texelSize,
|
||||
SizeT byteSize) {
|
||||
const VkFormat format = ResolveTextureFormat(texture.GetFormat());
|
||||
if (format == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) {
|
||||
return false;
|
||||
}
|
||||
if (level0Target != TextureUploadTarget::Texture2D) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format &&
|
||||
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
|
||||
resource.extent.height == static_cast<Uint32>(texelSize.y());
|
||||
if (compatible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyTextureResource(resource);
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent.width = static_cast<Uint32>(texelSize.x());
|
||||
imageInfo.extent.height = static_cast<Uint32>(texelSize.y());
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = format;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &resource.image), "vkCreateImage(texture)");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(m_device, resource.image, &requirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = requirements.size;
|
||||
allocInfo.memoryTypeIndex = FindMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &resource.memory), "vkAllocateMemory(texture)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, resource.image, resource.memory, 0), "vkBindImageMemory(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 = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
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.format = format;
|
||||
resource.textureExternalIndex = texture.GetExternalIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::UploadLevel0(TextureResource& resource,
|
||||
const MG_State::GLState::TextureObjectMipmap& mipmapTexture,
|
||||
TextureUploadTarget level0Target, SizeT byteSize) {
|
||||
auto& mutableTexture = const_cast<MG_State::GLState::TextureObjectMipmap&>(mipmapTexture);
|
||||
const void* source = mutableTexture.MapMipmapData(level0Target, 0);
|
||||
if (source == nullptr || byteSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = byteSize;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(staging texture)");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(m_device, stagingBuffer, &requirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = requirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
FindMemoryType(requirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &stagingMemory), "vkAllocateMemory(staging texture)");
|
||||
VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(staging texture)");
|
||||
|
||||
void* mapped = nullptr;
|
||||
VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, byteSize, 0, &mapped), "vkMapMemory(staging texture)");
|
||||
std::memcpy(mapped, source, byteSize);
|
||||
vkUnmapMemory(m_device, stagingMemory);
|
||||
|
||||
const Bool ok = ExecuteImmediate([&](VkCommandBuffer commandBuffer) {
|
||||
VkImageMemoryBarrier toTransferDst{};
|
||||
toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toTransferDst.srcAccessMask = 0;
|
||||
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransferDst.oldLayout = resource.layout;
|
||||
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.image = resource.image;
|
||||
toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toTransferDst.subresourceRange.baseMipLevel = 0;
|
||||
toTransferDst.subresourceRange.levelCount = 1;
|
||||
toTransferDst.subresourceRange.baseArrayLayer = 0;
|
||||
toTransferDst.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &toTransferDst);
|
||||
|
||||
VkBufferImageCopy copy{};
|
||||
copy.bufferOffset = 0;
|
||||
copy.bufferRowLength = 0;
|
||||
copy.bufferImageHeight = 0;
|
||||
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy.imageSubresource.mipLevel = 0;
|
||||
copy.imageSubresource.baseArrayLayer = 0;
|
||||
copy.imageSubresource.layerCount = 1;
|
||||
copy.imageOffset = {0, 0, 0};
|
||||
copy.imageExtent = {resource.extent.width, resource.extent.height, 1};
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, resource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
||||
©);
|
||||
|
||||
VkImageMemoryBarrier toSampled{};
|
||||
toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.image = resource.image;
|
||||
toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toSampled.subresourceRange.baseMipLevel = 0;
|
||||
toSampled.subresourceRange.levelCount = 1;
|
||||
toSampled.subresourceRange.baseArrayLayer = 0;
|
||||
toSampled.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 1, &toSampled);
|
||||
});
|
||||
|
||||
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
|
||||
vkFreeMemory(m_device, stagingMemory, nullptr);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
resource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::ExecuteImmediate(const std::function<void(VkCommandBuffer)>& recorder) const {
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = m_commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer), "vkAllocateCommandBuffers(texture)");
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture)");
|
||||
|
||||
recorder(commandBuffer);
|
||||
|
||||
VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture)");
|
||||
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE), "vkQueueSubmit(texture)");
|
||||
VK_VERIFY(vkQueueWaitIdle(m_graphicsQueue), "vkQueueWaitIdle(texture)");
|
||||
|
||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTextureSamplerManager::DestroyTextureResource(TextureResource& resource) const {
|
||||
if (m_device != VK_NULL_HANDLE && resource.view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, resource.view, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && resource.image != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, resource.image, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && resource.memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, resource.memory, nullptr);
|
||||
}
|
||||
resource.view = VK_NULL_HANDLE;
|
||||
resource.image = VK_NULL_HANDLE;
|
||||
resource.memory = VK_NULL_HANDLE;
|
||||
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
resource.extent = {0, 0};
|
||||
resource.format = VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::ResolveLevel0(const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget& outTarget, IntVec3& outTexelSize,
|
||||
SizeT& outByteSize) {
|
||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
if (!mipTexture) {
|
||||
return false;
|
||||
}
|
||||
const auto& targets = texture.GetUploadTargets();
|
||||
if (targets.empty()) {
|
||||
return false;
|
||||
}
|
||||
outTarget = targets.front();
|
||||
outTexelSize = mipTexture->GetMipmapTexelSize(outTarget, 0);
|
||||
outByteSize = mipTexture->GetMipmapByteSize(outTarget, 0);
|
||||
return outTexelSize.x() > 0 && outTexelSize.y() > 0 && outByteSize > 0;
|
||||
}
|
||||
|
||||
VkFormat VkTextureSamplerManager::ResolveTextureFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::RGBA:
|
||||
case TextureInternalFormat::RGBA8:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case TextureInternalFormat::SRGB8Alpha8:
|
||||
return VK_FORMAT_R8G8B8A8_SRGB;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 VkTextureSamplerManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const {
|
||||
VkPhysicalDeviceMemoryProperties memProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
|
||||
for (Uint32 i = 0; i < memProperties.memoryTypeCount; ++i) {
|
||||
if ((typeFilter & (1u << i)) != 0 &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
MOBILEGL_ASSERT(false, "VkTextureSamplerManager::FindMemoryType failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
VkSampler VkTextureSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler) {
|
||||
const Uint64 key = BuildSamplerKey(sampler.GetExternalIndex(), sampler.GetVersion());
|
||||
auto it = m_samplers.find(key);
|
||||
if (it != m_samplers.end()) {
|
||||
return it->second.handle;
|
||||
}
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = ToVkFilter(sampler.GetMagFilter());
|
||||
samplerInfo.minFilter = ToVkFilter(sampler.GetMinFilter());
|
||||
samplerInfo.mipmapMode = ToVkMipmapMode(sampler.GetMipmapMode());
|
||||
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
|
||||
samplerInfo.minLod = sampler.GetMinLod();
|
||||
samplerInfo.maxLod = sampler.GetMaxLod();
|
||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||
|
||||
VkSampler vkSampler = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &vkSampler), "vkCreateSampler(texture)");
|
||||
|
||||
SamplerCacheEntry entry{};
|
||||
entry.handle = vkSampler;
|
||||
entry.externalIndex = sampler.GetExternalIndex();
|
||||
entry.version = sampler.GetVersion();
|
||||
m_samplers[key] = entry;
|
||||
return vkSampler;
|
||||
}
|
||||
|
||||
VkFilter VkTextureSamplerManager::ToVkFilter(SamplerFilterMode mode) {
|
||||
return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
}
|
||||
|
||||
VkSamplerMipmapMode VkTextureSamplerManager::ToVkMipmapMode(SamplerMipmapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerMipmapMode::Nearest:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
case SamplerMipmapMode::Linear:
|
||||
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
case SamplerMipmapMode::None:
|
||||
default:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerAddressMode VkTextureSamplerManager::ToVkAddressMode(SamplerWrapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerWrapMode::ClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
case SamplerWrapMode::MirroredRepeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
case SamplerWrapMode::Repeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
case SamplerWrapMode::ClampToBorder:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||
case SamplerWrapMode::MirrorClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
|
||||
default:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
}
|
||||
}
|
||||
|
||||
VkCompareOp VkTextureSamplerManager::ToVkCompareOp(SamplerCompareFunc func) {
|
||||
switch (func) {
|
||||
case SamplerCompareFunc::Never:
|
||||
return VK_COMPARE_OP_NEVER;
|
||||
case SamplerCompareFunc::Less:
|
||||
return VK_COMPARE_OP_LESS;
|
||||
case SamplerCompareFunc::Equal:
|
||||
return VK_COMPARE_OP_EQUAL;
|
||||
case SamplerCompareFunc::LessEqual:
|
||||
return VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
case SamplerCompareFunc::Greater:
|
||||
return VK_COMPARE_OP_GREATER;
|
||||
case SamplerCompareFunc::NotEqual:
|
||||
return VK_COMPARE_OP_NOT_EQUAL;
|
||||
case SamplerCompareFunc::GreaterEqual:
|
||||
return VK_COMPARE_OP_GREATER_OR_EQUAL;
|
||||
case SamplerCompareFunc::Always:
|
||||
default:
|
||||
return VK_COMPARE_OP_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::UploadFallbackTexture() {
|
||||
const Uint32 rgba = 0xFFFFFFFFu;
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent = {1, 1, 1};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &m_fallbackImage), "vkCreateImage(fallback)");
|
||||
|
||||
VkMemoryRequirements imageMemReq{};
|
||||
vkGetImageMemoryRequirements(m_device, m_fallbackImage, &imageMemReq);
|
||||
|
||||
VkMemoryAllocateInfo imageAllocInfo{};
|
||||
imageAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
imageAllocInfo.allocationSize = imageMemReq.size;
|
||||
imageAllocInfo.memoryTypeIndex = FindMemoryType(imageMemReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &imageAllocInfo, nullptr, &m_fallbackImageMemory),
|
||||
"vkAllocateMemory(fallback)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, m_fallbackImage, m_fallbackImageMemory, 0), "vkBindImageMemory(fallback)");
|
||||
|
||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = sizeof(rgba);
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(fallback)");
|
||||
|
||||
VkMemoryRequirements stagingMemReq{};
|
||||
vkGetBufferMemoryRequirements(m_device, stagingBuffer, &stagingMemReq);
|
||||
|
||||
VkMemoryAllocateInfo stagingAllocInfo{};
|
||||
stagingAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
stagingAllocInfo.allocationSize = stagingMemReq.size;
|
||||
stagingAllocInfo.memoryTypeIndex =
|
||||
FindMemoryType(stagingMemReq.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &stagingAllocInfo, nullptr, &stagingMemory), "vkAllocateMemory(fallback)");
|
||||
VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(fallback)");
|
||||
|
||||
void* mapped = nullptr;
|
||||
VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, sizeof(rgba), 0, &mapped), "vkMapMemory(fallback)");
|
||||
std::memcpy(mapped, &rgba, sizeof(rgba));
|
||||
vkUnmapMemory(m_device, stagingMemory);
|
||||
|
||||
const Bool uploadOk = ExecuteImmediate([&](VkCommandBuffer commandBuffer) {
|
||||
VkImageMemoryBarrier toTransferDst{};
|
||||
toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toTransferDst.srcAccessMask = 0;
|
||||
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransferDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.image = m_fallbackImage;
|
||||
toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toTransferDst.subresourceRange.baseMipLevel = 0;
|
||||
toTransferDst.subresourceRange.levelCount = 1;
|
||||
toTransferDst.subresourceRange.baseArrayLayer = 0;
|
||||
toTransferDst.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &toTransferDst);
|
||||
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy.imageSubresource.mipLevel = 0;
|
||||
copy.imageSubresource.baseArrayLayer = 0;
|
||||
copy.imageSubresource.layerCount = 1;
|
||||
copy.imageExtent = {1, 1, 1};
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, m_fallbackImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
||||
©);
|
||||
|
||||
VkImageMemoryBarrier toSampled{};
|
||||
toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.image = m_fallbackImage;
|
||||
toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toSampled.subresourceRange.baseMipLevel = 0;
|
||||
toSampled.subresourceRange.levelCount = 1;
|
||||
toSampled.subresourceRange.baseArrayLayer = 0;
|
||||
toSampled.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 1, &toSampled);
|
||||
});
|
||||
|
||||
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
|
||||
vkFreeMemory(m_device, stagingMemory, nullptr);
|
||||
if (!uploadOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = m_fallbackImage;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &m_fallbackImageView), "vkCreateImageView(fallback)");
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.compareEnable = VK_FALSE;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 0.0f;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fallbackSampler), "vkCreateSampler(fallback)");
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,88 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.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>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
class SamplerObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkTextureSamplerManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
Bool GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture,
|
||||
const MG_State::GLState::SamplerObject* samplerOverride,
|
||||
VkDescriptorImageInfo& outImageInfo);
|
||||
|
||||
private:
|
||||
struct TextureResource {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
Uint textureExternalIndex = 0;
|
||||
};
|
||||
|
||||
struct SamplerCacheEntry {
|
||||
VkSampler handle = VK_NULL_HANDLE;
|
||||
Uint externalIndex = 0;
|
||||
Uint16 version = 0;
|
||||
};
|
||||
|
||||
Bool EnsureTextureSynced(TextureResource& resource, const MG_State::GLState::ITextureObject& texture);
|
||||
Bool EnsureTextureResource(TextureResource& resource, const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget level0Target, const IntVec3& texelSize, SizeT byteSize);
|
||||
Bool UploadLevel0(TextureResource& resource, const MG_State::GLState::TextureObjectMipmap& mipmapTexture,
|
||||
TextureUploadTarget level0Target, SizeT byteSize);
|
||||
Bool ExecuteImmediate(const std::function<void(VkCommandBuffer)>& recorder) const;
|
||||
void DestroyTextureResource(TextureResource& resource) const;
|
||||
static Bool ResolveLevel0(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget& outTarget,
|
||||
IntVec3& outTexelSize, SizeT& outByteSize);
|
||||
static VkFormat ResolveTextureFormat(TextureInternalFormat format);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
|
||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler);
|
||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
||||
Bool UploadFallbackTexture();
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
|
||||
UnorderedMap<Uint, TextureResource> m_textureResources;
|
||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||
|
||||
VkImage m_fallbackImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_fallbackImageMemory = VK_NULL_HANDLE;
|
||||
VkImageView m_fallbackImageView = VK_NULL_HANDLE;
|
||||
VkSampler m_fallbackSampler = VK_NULL_HANDLE;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,229 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.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 "Config.h"
|
||||
#include "FrameContext.h"
|
||||
#include "PipelineFactory.h"
|
||||
#include "ProgramFactory.h"
|
||||
#include "SwapchainObject.h"
|
||||
#include "UniformDescriptorBinder.h"
|
||||
#include "VertexInputStateFactory.h"
|
||||
#include "VkBufferObject.h"
|
||||
#include "VkFramebufferManager.h"
|
||||
#include "VkRenderPassManager.h"
|
||||
#include "VkTextureSamplerManager.h"
|
||||
#include "MG_Util/Math/VectorTypes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ProgramObject;
|
||||
class VertexArrayObject;
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct DrawArrayPayload {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
GLint first = 0;
|
||||
GLsizei count = 0;
|
||||
const MG_State::GLState::ProgramObject* program = nullptr;
|
||||
const MG_State::GLState::VertexArrayObject* vertexArray = nullptr;
|
||||
};
|
||||
|
||||
struct DrawElementPayload {
|
||||
DrawArrayPayload drawArray;
|
||||
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||
SizeT indexByteOffset = 0;
|
||||
GLint baseVertex = 0;
|
||||
};
|
||||
|
||||
struct QueueFamilyIndices {
|
||||
Int32 graphicsFamily = -1;
|
||||
Int32 presentFamily = -1;
|
||||
};
|
||||
|
||||
struct PhysicalDevice {
|
||||
QueueFamilyIndices queueFamilies;
|
||||
VkPhysicalDeviceProperties properties;
|
||||
VkPhysicalDevice handle = VK_NULL_HANDLE;
|
||||
|
||||
Bool IsComplete() const {
|
||||
return handle != VK_NULL_HANDLE && queueFamilies.graphicsFamily != -1 && queueFamilies.presentFamily != -1;
|
||||
}
|
||||
};
|
||||
|
||||
class VulkanRenderer {
|
||||
public:
|
||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||
~VulkanRenderer();
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil,
|
||||
Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget);
|
||||
Bool ConsumePendingColorClear(VkClearColorValue& outClearColor);
|
||||
void EnsureFrameRecordingStarted();
|
||||
void DrawArrays(const DrawArrayPayload& payload);
|
||||
void DrawElements(const DrawElementPayload& payload);
|
||||
void MultiDrawElements(const Vector<DrawElementPayload>& payloads);
|
||||
Bool BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter, Uint readFboExternalIndex,
|
||||
Uint drawFboExternalIndex, Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer);
|
||||
void Render();
|
||||
void Present();
|
||||
|
||||
const PhysicalDevice& GetPhysicalDevice() const;
|
||||
Bool IsDrawIndirectCountExtensionEnabled() const;
|
||||
|
||||
void RecreateSwapchain();
|
||||
|
||||
private:
|
||||
struct PendingClearState {
|
||||
GLbitfield mask = 0;
|
||||
VkClearColorValue color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
||||
Float depth = 1.0f;
|
||||
Uint32 stencil = 0;
|
||||
Uint drawFboExternalIndex = 0;
|
||||
Bool targetsDefaultFramebuffer = true;
|
||||
};
|
||||
|
||||
NativeWindowType m_window = 0;
|
||||
VulkanRendererConfig m_config;
|
||||
|
||||
// Vulkan objects
|
||||
Bool m_validationLayersEnabled = false;
|
||||
Vector<VkExtensionProperties> m_extensions;
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||
PhysicalDevice m_physicalDevice;
|
||||
// VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
SwapchainObject m_swapchainObject;
|
||||
|
||||
// Vector<VkQueueFamilyProperties> m_queueFamilies;
|
||||
// QueueFamilyIndices m_queueFamilyIndices;
|
||||
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
VkQueue m_presentQueue = VK_NULL_HANDLE;
|
||||
Bool m_drawIndirectCountExtensionEnabled = false;
|
||||
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
|
||||
VkDeviceSize offset, VkBuffer countBuffer,
|
||||
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
|
||||
Uint32 stride);
|
||||
PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr;
|
||||
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
|
||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Vector<VkImage> m_depthStencilImages;
|
||||
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
||||
Vector<VkImageView> m_depthStencilImageViews;
|
||||
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
||||
|
||||
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
|
||||
Vector<VkBufferObject> m_frameVertexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameVertexUploadHeads;
|
||||
Vector<VkBufferObject> m_frameIndexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameIndexUploadHeads;
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
|
||||
Uint m_imageIndexAcquired = 0;
|
||||
FrameContext m_frameContext;
|
||||
UnorderedMap<Uint64, PendingClearState> m_pendingClears;
|
||||
Bool m_isMainRenderPassActive = false;
|
||||
VkRenderPass m_activeRenderPass = VK_NULL_HANDLE;
|
||||
VkExtent2D m_activeRenderExtent = {0, 0};
|
||||
VkFormat m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Bool m_activeRenderTargetIsDefault = true;
|
||||
Uint m_activeDrawFboExternalIndex = 0;
|
||||
|
||||
UniquePtr<PipelineFactory> m_pipelineFactory;
|
||||
UniquePtr<ProgramFactory> m_programFactory;
|
||||
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
|
||||
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
||||
UniquePtr<VkFramebufferManager> m_framebufferManager;
|
||||
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
||||
UniquePtr<VkTextureSamplerManager> m_textureSamplerManager;
|
||||
|
||||
void CreateInstance();
|
||||
VkResult SetupDebugMessenger();
|
||||
VkResult DestroyDebugMessenger();
|
||||
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
||||
void CreateSurface();
|
||||
void PickPhysicalDevice();
|
||||
void CreateLogicalDeviceAndQueues();
|
||||
void CreateAllocator();
|
||||
void DestroyAllocator();
|
||||
void CreateSwapchain();
|
||||
void CreateCommandPool();
|
||||
void CreateFrameContexts();
|
||||
void CreateDepthStencilResources();
|
||||
void DestroyDepthStencilResources();
|
||||
VkRenderPass GetDefaultLoadRenderPass() const;
|
||||
Bool GetDefaultRenderTargetForCurrentImage(VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
||||
Bool EnsureOffscreenRenderTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo,
|
||||
VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat);
|
||||
void PrepareDemoPipeline();
|
||||
VkPipeline GetOrCreatePipeline(const MG_State::GLState::ProgramObject& program, VkPipelineLayout pipelineLayout,
|
||||
Uint64 vertexInputHash,
|
||||
const VkPipelineVertexInputStateCreateInfo& vertexInputState);
|
||||
void TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
||||
void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
||||
void EndFrameRecordingIfNeeded();
|
||||
void DeferDestroyBuffer(VkBufferObject& buffer);
|
||||
void CollectDeferredBufferReleases(Uint32 frameIndex);
|
||||
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset,
|
||||
VkDeviceSize minCapacity, VkBufferUsageFlags usage);
|
||||
Bool UploadAndBindVertexStreams(const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
|
||||
const DrawArrayPayload& payload, VkCommandBuffer commandBuffer);
|
||||
|
||||
void ShutdownSwapchain();
|
||||
|
||||
static Int GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
|
||||
const Vector<VkQueueFamilyProperties>& queueFamilies,
|
||||
Int preferredFamilyIndex = -1);
|
||||
static Vector<VkQueueFamilyProperties> GetQueueFamilyFromPhysicalDevice(VkPhysicalDevice device);
|
||||
static Int GetQueueFamilyIndex(const Vector<VkQueueFamilyProperties>& queueFamilies, VkQueueFlagBits flag);
|
||||
static Vector<VkExtensionProperties> EnumerateInstanceExtensions();
|
||||
static Vector<VkExtensionProperties> EnumerateDeviceExtensions(VkPhysicalDevice device);
|
||||
static Bool IsExtensionSupported(const Vector<VkExtensionProperties>& availableExtensions,
|
||||
const char* extensionName);
|
||||
static Bool IsExtensionAlreadyEnabled(const Vector<const char*>& enabledExtensions, const char* extensionName);
|
||||
static Bool EnableOptionalDeviceExtension(const Vector<VkExtensionProperties>& availableExtensions,
|
||||
Vector<const char*>& inOutEnabledExtensions,
|
||||
const char* extensionName);
|
||||
void ResolveOptionalDeviceExtensions(const Vector<VkExtensionProperties>& availableExtensions,
|
||||
Vector<const char*>& inOutEnabledExtensions);
|
||||
static Bool IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device);
|
||||
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
||||
const PhysicalDevice& compareWithDevice,
|
||||
PhysicalDevice& outBetterDevice);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
static Uint64 BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer);
|
||||
static Bool HasStencilComponent(VkFormat format);
|
||||
static VkFormat FindSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice);
|
||||
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();
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData);
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,25 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/VkIncludes.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 "VulkanRendererConfig.h"
|
||||
|
||||
#define VK_VERIFY(expr, ...) \
|
||||
do { \
|
||||
VkResult _vk_verify_result = (expr); \
|
||||
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#define ENUM_STR_CASE(c) case c: return #c;
|
||||
|
||||
#define XXHASH_VERIFY(expr, ...) \
|
||||
do { \
|
||||
XXH_errorcode _xxh_verify_result = (expr); \
|
||||
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _xxh_verify_result, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
@@ -1,4 +1,4 @@
|
||||
// MobileGL - MobileGL/MG_Impl/Init.h
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/VmaImpl.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
|
||||
@@ -6,10 +6,5 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Impl {
|
||||
void Init();
|
||||
} // namespace MG_Impl
|
||||
} // namespace MobileGL
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vk_mem_alloc.h>
|
||||
@@ -0,0 +1,20 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererConfig.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 "Config.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct VulkanRendererConfig {
|
||||
Uint32 MaxFramesInFlight = 2;
|
||||
String AppName = "MobileGL-VulkanRenderer";
|
||||
Version Version = MG_Config::CoreVersion;
|
||||
Uint64 CacheVersion = MG_Config::CacheVersion;
|
||||
Bool EnableValidationLayers = true;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -52,6 +52,9 @@ namespace MobileGL::MG_Backend {
|
||||
case BackendType::DirectGLES:
|
||||
pActiveBackendObject = MakeUnique<DirectGLES::BackendObject_DirectGLES>();
|
||||
break;
|
||||
case BackendType::DirectVulkan:
|
||||
pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
|
||||
break;
|
||||
case BackendType::Unknown:
|
||||
default:
|
||||
MGLOG_W("Unknown backend type, defaulting to unknown backend");
|
||||
|
||||
@@ -61,7 +61,7 @@ static void BM_CreateBufferObjectsAndBindBuffer(benchmark::State& state) {
|
||||
BENCHMARK(BM_CreateBufferObjectsAndBindBuffer)->Unit(benchmark::kMillisecond)->UseRealTime();
|
||||
|
||||
static void BM_DeleteBufferObjects(benchmark::State& state) {
|
||||
MG_Initialize();
|
||||
Initialize();
|
||||
std::vector<GLuint> buffers(BUFFER_COUNT);
|
||||
|
||||
for (auto _ : state) {
|
||||
@@ -179,7 +179,7 @@ static void BM_UpdateDataPartially(benchmark::State& state) {
|
||||
BENCHMARK(BM_UpdateDataPartially)->Unit(benchmark::kMillisecond)->UseRealTime();
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
MG_Initialize();
|
||||
Initialize();
|
||||
benchmark ::MaybeReenterWithoutASLR(argc, argv);
|
||||
char arg0_default[] = "benchmark";
|
||||
char* args_default = reinterpret_cast<char*>(arg0_default);
|
||||
|
||||
@@ -43,7 +43,7 @@ void main(){
|
||||
// Use TestMat2 and TestMat3 to prevent optimization
|
||||
vec2 dummy2 = TestMat2[0];
|
||||
vec3 dummy3 = TestMat3[0];
|
||||
|
||||
|
||||
oneTexel = (1.0 * (fIn1 * fIn2 * fIn3 * fIn4 * fIn5 * fIn6)) / InSize;
|
||||
|
||||
texCoord = Position.xy / OutSize;
|
||||
@@ -221,7 +221,7 @@ static void BM_CompileAndLink(benchmark::State& state) {
|
||||
BENCHMARK(BM_CompileAndLink)->Unit(benchmark::kMillisecond);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
MG_Initialize();
|
||||
Initialize();
|
||||
benchmark ::MaybeReenterWithoutASLR(argc, argv);
|
||||
char arg0_default[] = "benchmark";
|
||||
char* args_default = reinterpret_cast<char*>(arg0_default);
|
||||
@@ -234,4 +234,4 @@ int main(int argc, char** argv) {
|
||||
::benchmark ::RunSpecifiedBenchmarks();
|
||||
::benchmark ::Shutdown();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,178 +9,451 @@
|
||||
#include "EGLImpl.h"
|
||||
#include "../GetProcAddress.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace MobileGL::MG_Impl::EGLImpl {
|
||||
namespace {
|
||||
using EGLStateContext = MG_State::EGLState::EGLContext;
|
||||
|
||||
EGLStateContext* GetState() {
|
||||
if (!MG_State::pEGLContext) {
|
||||
MGLOG_E("pEGLContext is null. MG_State may not be initialized.");
|
||||
}
|
||||
return MG_State::pEGLContext;
|
||||
}
|
||||
|
||||
MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) {
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (!backendObject && state) {
|
||||
state->SetError(EGL_NOT_INITIALIZED);
|
||||
}
|
||||
return backendObject;
|
||||
}
|
||||
|
||||
MG_Backend::WindowBackend DetectWindowBackend() {
|
||||
#if defined(ANDROID) || defined(__ANDROID__)
|
||||
return MG_Backend::WindowBackend::Android;
|
||||
#else
|
||||
return MG_Backend::WindowBackend::Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename NativeType>
|
||||
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
||||
if constexpr (std::is_pointer_v<NativeType>) {
|
||||
return nativeHandle == nullptr;
|
||||
} else {
|
||||
return nativeHandle == 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename NativeType>
|
||||
void* ToVoidHandle(NativeType nativeHandle) {
|
||||
if constexpr (std::is_pointer_v<NativeType>) {
|
||||
return reinterpret_cast<void*>(nativeHandle);
|
||||
} else {
|
||||
return reinterpret_cast<void*>(static_cast<SizeT>(nativeHandle));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window,
|
||||
const EGLint* attrib_list) {
|
||||
MGLOG_D("EGLImpl::CreateWindowSurface called with window=%p", window);
|
||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||
if (!activeBackendObject) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!state->IsDisplayInitialized(dpy)) {
|
||||
state->SetError(EGL_NOT_INITIALIZED);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!state->ValidateConfigOnDisplay(dpy, config)) {
|
||||
state->SetError(EGL_BAD_CONFIG);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (IsNullNativeHandle(window)) {
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
activeBackendObject->SetWindowHandle({MG_Backend::WindowBackend::Android, reinterpret_cast<void*>(window)});
|
||||
activeBackendObject->InitWindowSurface();
|
||||
return (EGLSurface)1;
|
||||
|
||||
const MG_Backend::WindowHandle windowHandle = {
|
||||
.Backend = DetectWindowBackend(),
|
||||
.Handle = ToVoidHandle(window),
|
||||
};
|
||||
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
return state->CreateWindowSurface(dpy, config, window, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
MGLOG_D("EGLImpl::SwapBuffers called with dpy=%p", dpy);
|
||||
if (!MG_Backend::gBackendFunctionsTable.Present) {
|
||||
MGLOG_E("MG_Backend::gBackendFunctionsTable.Present not initialized!");
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ValidateSurfaceOnDisplay(dpy, draw)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!backendObject->SwapEGLBuffers(dpy, draw)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
MG_Backend::gBackendFunctionsTable.Present();
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean ChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs, EGLint config_size,
|
||||
EGLint* num_config) {
|
||||
*num_config = 1;
|
||||
return EGL_TRUE;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->ChooseConfig(dpy, attrib_list, configs, config_size, num_config) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLContext CreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx, const EGLint* attrib_list) {
|
||||
return (EGLContext)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
return state->CreateContext(dpy, config, shareCtx, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
if (major) *major = 1;
|
||||
if (minor) *minor = 5;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->InitializeDisplay(dpy, major, minor)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!backendObject->InitializeEGLDisplay(dpy, major, minor)) {
|
||||
state->SetError(EGL_NOT_INITIALIZED);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLDisplay GetDisplay(NativeDisplayType display) {
|
||||
return (EGLDisplay)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
return state->GetDisplay(display);
|
||||
}
|
||||
|
||||
EGLint GetError() {
|
||||
return EGL_SUCCESS;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NOT_INITIALIZED;
|
||||
}
|
||||
return state->ConsumeError();
|
||||
}
|
||||
|
||||
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
MGLOG_D("EGLImpl::CreateWindowSurface called with window=%p", window);
|
||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||
if (!activeBackendObject) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
const auto oldDisplay = state->GetCurrentDisplay();
|
||||
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
||||
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
||||
const auto oldContext = state->GetCurrentContext();
|
||||
|
||||
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
const Bool releaseCurrentRequest =
|
||||
dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
if (releaseCurrentRequest) {
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
(void)backendObject->MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
||||
state->SetError(EGL_BAD_ACCESS);
|
||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
activeBackendObject->InitCapabilities();
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean DestroyContext(EGLDisplay dpy, EGLContext ctx) {
|
||||
return EGL_TRUE;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->DestroyContext(dpy, ctx) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||
return EGL_TRUE;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->DestroySurface(dpy, surface) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean Terminate(EGLDisplay dpy) {
|
||||
return EGL_TRUE;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->TerminateDisplay(dpy) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean ReleaseThread() {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
state->ReleaseThread();
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLContext GetCurrentContext() {
|
||||
return (EGLContext)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
return state->GetCurrentContext();
|
||||
}
|
||||
|
||||
EGLBoolean GetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint* value) {
|
||||
if (attribute == EGL_NATIVE_VISUAL_ID) {
|
||||
#if defined(ANDROID)
|
||||
*value = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->GetConfigAttrib(dpy, config, attribute, value) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean BindAPI(EGLenum api) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
switch (api) {
|
||||
case EGL_OPENGL_API:
|
||||
case EGL_OPENGL_ES_API:
|
||||
case EGL_OPENVG_API:
|
||||
state->SetBoundAPI(api);
|
||||
return EGL_TRUE;
|
||||
#elif defined(__linux__)
|
||||
*value = 0;
|
||||
return EGL_TRUE;
|
||||
#elif defined(_WIN32)
|
||||
*value = 0;
|
||||
return EGL_TRUE;
|
||||
#else
|
||||
*value = 0;
|
||||
default:
|
||||
state->SetError(EGL_BAD_PARAMETER);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
EGLSurface GetCurrentSurface(EGLint readdraw) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->GetCurrentSurface(readdraw);
|
||||
}
|
||||
|
||||
EGLBoolean QuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint* value) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
char const* QueryString(EGLDisplay display, EGLint name) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (display != EGL_NO_DISPLAY && !state->ValidateDisplay(display)) {
|
||||
state->SetError(EGL_BAD_DISPLAY);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case EGL_VENDOR:
|
||||
return "MobileGL";
|
||||
case EGL_VERSION:
|
||||
return "1.5 MobileGL";
|
||||
case EGL_CLIENT_APIS:
|
||||
return "OpenGL OpenGL_ES";
|
||||
case EGL_EXTENSIONS:
|
||||
return "";
|
||||
default:
|
||||
state->SetError(EGL_BAD_PARAMETER);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
EGLBoolean SwapInterval(EGLDisplay dpy, EGLint interval) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->SwapInterval(dpy, interval) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->CreatePbufferSurface(dpy, config, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ValidateSurfaceOnDisplay(dpy, surface)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (buffer != EGL_BACK_BUFFER) {
|
||||
state->SetError(EGL_BAD_PARAMETER);
|
||||
return EGL_FALSE;
|
||||
#endif
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean BindAPI(EGLenum api) {
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface GetCurrentSurface(EGLint readdraw) {
|
||||
return (EGLSurface)1;
|
||||
}
|
||||
|
||||
EGLBoolean QuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint* value) {
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
char const* QueryString(EGLDisplay display, EGLint name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
EGLBoolean SwapInterval(EGLDisplay dpy, EGLint interval) {
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||
return (EGLSurface)1;
|
||||
}
|
||||
|
||||
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ValidateSurfaceOnDisplay(dpy, surface)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (buffer != EGL_BACK_BUFFER) {
|
||||
state->SetError(EGL_BAD_PARAMETER);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ValidateSurfaceOnDisplay(dpy, surface)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (IsNullNativeHandle(target)) {
|
||||
state->SetError(EGL_BAD_NATIVE_PIXMAP);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface CreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config,
|
||||
const EGLint* attrib_list) {
|
||||
return (EGLSurface)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->CreatePbufferFromClientBuffer(dpy, buftype, buffer, config, attrib_list);
|
||||
}
|
||||
|
||||
EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap,
|
||||
const EGLint* attrib_list) {
|
||||
return (EGLSurface)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->CreatePixmapSurface(dpy, config, pixmap, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean GetConfigs(EGLDisplay dpy, EGLConfig* configs, EGLint config_size, EGLint* num_config) {
|
||||
if (num_config) {
|
||||
*num_config = 1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (configs && config_size > 0) {
|
||||
configs[0] = (EGLConfig)1;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
return state->GetConfigs(dpy, configs, config_size, num_config) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLDisplay GetCurrentDisplay() {
|
||||
return (EGLDisplay)1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
return state->GetCurrentDisplay();
|
||||
}
|
||||
|
||||
EGLenum QueryAPI() {
|
||||
return EGL_OPENGL_API;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_OPENGL_API;
|
||||
}
|
||||
return state->GetBoundAPI();
|
||||
}
|
||||
|
||||
EGLBoolean QueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint* value) {
|
||||
if (value) {
|
||||
*value = 1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
return state->QueryContext(dpy, ctx, attribute, value) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean SurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) {
|
||||
return EGL_TRUE;
|
||||
(void)value;
|
||||
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ValidateSurfaceOnDisplay(dpy, surface)) {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
switch (attribute) {
|
||||
case EGL_MIPMAP_LEVEL:
|
||||
case EGL_SWAP_BEHAVIOR:
|
||||
case EGL_TEXTURE_FORMAT:
|
||||
case EGL_TEXTURE_TARGET:
|
||||
case EGL_MIPMAP_TEXTURE:
|
||||
return EGL_TRUE;
|
||||
default:
|
||||
state->SetError(EGL_BAD_ATTRIBUTE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
EGLBoolean WaitClient() {
|
||||
@@ -192,60 +465,132 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
}
|
||||
|
||||
EGLBoolean WaitNative(EGLint engine) {
|
||||
(void)engine;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSync CreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib* attrib_list) {
|
||||
return reinterpret_cast<EGLSync>(0x1);
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SYNC;
|
||||
}
|
||||
return state->CreateSync(dpy, type, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean DestroySync(void* dpy, void* sync) {
|
||||
return EGL_TRUE;
|
||||
EGLBoolean DestroySync(EGLDisplay dpy, EGLSync sync) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->DestroySync(dpy, sync) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLint ClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout) {
|
||||
return EGL_CONDITION_SATISFIED;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->ClientWaitSync(dpy, sync, flags, timeout);
|
||||
}
|
||||
|
||||
EGLBoolean GetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib* value) {
|
||||
if (value) {
|
||||
*value = 1;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
return state->GetSyncAttrib(dpy, sync, attribute, value) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLImage CreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
const EGLAttrib* attrib_list) {
|
||||
return reinterpret_cast<EGLImage>(0x1);
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_IMAGE;
|
||||
}
|
||||
return state->CreateImage(dpy, ctx, target, buffer, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean DestroyImage(EGLDisplay dpy, EGLImage image) {
|
||||
return EGL_TRUE;
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->DestroyImage(dpy, image) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list) {
|
||||
return reinterpret_cast<EGLDisplay>(0x1);
|
||||
(void)attrib_list;
|
||||
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
return state->GetPlatformDisplay(platform, native_display);
|
||||
}
|
||||
|
||||
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||
const EGLAttrib* attrib_list) {
|
||||
return reinterpret_cast<EGLSurface>(0x1);
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (native_window == nullptr) {
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!state->IsDisplayInitialized(dpy)) {
|
||||
state->SetError(EGL_NOT_INITIALIZED);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!state->ValidateConfigOnDisplay(dpy, config)) {
|
||||
state->SetError(EGL_BAD_CONFIG);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
const MG_Backend::WindowHandle windowHandle = {
|
||||
.Backend = DetectWindowBackend(),
|
||||
.Handle = native_window,
|
||||
};
|
||||
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
return state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
|
||||
}
|
||||
|
||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||
const EGLAttrib* attrib_list) {
|
||||
return reinterpret_cast<EGLSurface>(0x1);
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->CreatePlatformPixmapSurface(dpy, config, native_pixmap, attrib_list);
|
||||
}
|
||||
|
||||
EGLBoolean WaitSync(void* dpy, void* sync, int flags) {
|
||||
return EGL_TRUE;
|
||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->WaitSync(dpy, sync, flags) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
|
||||
__eglMustCastToProperFunctionPointerType GetProcAddress(const char* name) {
|
||||
if (!name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MGLOG_D("eglGetProcAddress(%s)", name);
|
||||
void* proc = MG_Impl::GetProcAddress(name);
|
||||
if (!proc) {
|
||||
MGLOG_W("Failed to get function: %s", (const char*)name);
|
||||
MGLOG_W("Failed to get function: %s", name);
|
||||
return nullptr;
|
||||
}
|
||||
return (__eglMustCastToProperFunctionPointerType)proc;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
EGLBoolean WaitGL();
|
||||
EGLBoolean WaitNative(EGLint engine);
|
||||
EGLSync CreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib* attrib_list);
|
||||
EGLBoolean DestroySync(void* dpy, void* sync);
|
||||
EGLBoolean DestroySync(EGLDisplay dpy, EGLSync sync);
|
||||
EGLint ClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
|
||||
EGLBoolean GetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib* value);
|
||||
EGLImage CreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
@@ -59,6 +59,6 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
const EGLAttrib* attrib_list);
|
||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||
const EGLAttrib* attrib_list);
|
||||
EGLBoolean WaitSync(void* dpy, void* sync, int flags);
|
||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
||||
__eglMustCastToProperFunctionPointerType GetProcAddress(const char* name);
|
||||
} // namespace MobileGL::MG_Impl::EGLImpl
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
void ClearStencil_State(GLint s) {
|
||||
// TODO: implement
|
||||
MG_State::pGLContext->SetClearStencil(static_cast<Int>(s));
|
||||
}
|
||||
|
||||
void ClearDepth_State(GLclampd depth) {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "Init.h"
|
||||
#include <Includes.h>
|
||||
|
||||
#include "GLImpl/Texture/ProxyTexture.h"
|
||||
#include "GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,258 @@
|
||||
// MobileGL - MobileGL/MG_State/EGLState/Core.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 <type_traits>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
namespace EGLState {
|
||||
class EGLContext {
|
||||
public:
|
||||
using EGLDisplayHandle = ::EGLDisplay;
|
||||
using EGLConfigHandle = ::EGLConfig;
|
||||
using EGLSurfaceHandle = ::EGLSurface;
|
||||
using EGLContextHandle = ::EGLContext;
|
||||
using EGLSyncHandle = ::EGLSync;
|
||||
using EGLImageHandle = ::EGLImage;
|
||||
|
||||
EGLContext() = default;
|
||||
|
||||
// Error
|
||||
void SetError(EGLint errorCode);
|
||||
EGLint ConsumeError();
|
||||
|
||||
// API
|
||||
void SetBoundAPI(EGLenum api);
|
||||
EGLenum GetBoundAPI() const;
|
||||
|
||||
// Display
|
||||
EGLDisplayHandle GetDisplay(NativeDisplayType nativeDisplay);
|
||||
EGLDisplayHandle GetPlatformDisplay(EGLenum platform, void* nativeDisplay);
|
||||
Bool ValidateDisplay(EGLDisplayHandle display) const;
|
||||
Bool IsDisplayInitialized(EGLDisplayHandle display) const;
|
||||
Bool InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor);
|
||||
Bool TerminateDisplay(EGLDisplayHandle display);
|
||||
|
||||
// Config
|
||||
Bool ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs,
|
||||
EGLint configSize, EGLint* numConfig);
|
||||
Bool GetConfigs(EGLDisplayHandle display, EGLConfigHandle* configs, EGLint configSize,
|
||||
EGLint* numConfig);
|
||||
Bool ValidateConfig(EGLConfigHandle config) const;
|
||||
Bool ValidateConfigOnDisplay(EGLDisplayHandle display, EGLConfigHandle config) const;
|
||||
Bool GetConfigAttrib(EGLDisplayHandle display, EGLConfigHandle config, EGLint attribute,
|
||||
EGLint* value) const;
|
||||
|
||||
// Context
|
||||
EGLContextHandle CreateContext(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
EGLContextHandle shareCtx, const EGLint* attribList);
|
||||
Bool DestroyContext(EGLDisplayHandle display, EGLContextHandle context);
|
||||
Bool QueryContext(EGLDisplayHandle display, EGLContextHandle context, EGLint attribute,
|
||||
EGLint* value) const;
|
||||
Bool ValidateContext(EGLContextHandle context) const;
|
||||
Bool ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const;
|
||||
|
||||
// Surface
|
||||
EGLSurfaceHandle CreateWindowSurface(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
NativeWindowType window, const EGLint* attribList);
|
||||
EGLSurfaceHandle CreatePbufferSurface(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
const EGLint* attribList);
|
||||
EGLSurfaceHandle CreatePixmapSurface(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
EGLNativePixmapType pixmap, const EGLint* attribList);
|
||||
EGLSurfaceHandle CreatePbufferFromClientBuffer(EGLDisplayHandle display, EGLenum bufferType,
|
||||
EGLClientBuffer buffer, EGLConfigHandle config,
|
||||
const EGLint* attribList);
|
||||
EGLSurfaceHandle CreatePlatformWindowSurface(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
void* nativeWindow, const EGLAttrib* attribList);
|
||||
EGLSurfaceHandle CreatePlatformPixmapSurface(EGLDisplayHandle display, EGLConfigHandle config,
|
||||
void* nativePixmap, const EGLAttrib* attribList);
|
||||
Bool DestroySurface(EGLDisplayHandle display, EGLSurfaceHandle surface);
|
||||
Bool QuerySurface(EGLDisplayHandle display, EGLSurfaceHandle surface, EGLint attribute,
|
||||
EGLint* value) const;
|
||||
Bool ValidateSurface(EGLSurfaceHandle surface) const;
|
||||
Bool ValidateSurfaceOnDisplay(EGLDisplayHandle display, EGLSurfaceHandle surface) const;
|
||||
Bool SwapInterval(EGLDisplayHandle display, EGLint interval);
|
||||
|
||||
// Current
|
||||
Bool MakeCurrent(EGLDisplayHandle display, EGLSurfaceHandle draw, EGLSurfaceHandle read,
|
||||
EGLContextHandle context);
|
||||
void ReleaseThread();
|
||||
EGLContextHandle GetCurrentContext() const;
|
||||
EGLDisplayHandle GetCurrentDisplay() const;
|
||||
EGLSurfaceHandle GetCurrentSurface(EGLint readdraw) const;
|
||||
|
||||
// Sync
|
||||
EGLSyncHandle CreateSync(EGLDisplayHandle display, EGLenum type, const EGLAttrib* attribList);
|
||||
Bool DestroySync(EGLDisplayHandle display, EGLSyncHandle sync);
|
||||
EGLint ClientWaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags, EGLTime timeout);
|
||||
Bool GetSyncAttrib(EGLDisplayHandle display, EGLSyncHandle sync, EGLint attribute,
|
||||
EGLAttrib* value) const;
|
||||
Bool WaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags) const;
|
||||
|
||||
// Image
|
||||
EGLImageHandle CreateImage(EGLDisplayHandle display, EGLContextHandle context, EGLenum target,
|
||||
EGLClientBuffer buffer, const EGLAttrib* attribList);
|
||||
Bool DestroyImage(EGLDisplayHandle display, EGLImageHandle image);
|
||||
|
||||
private:
|
||||
enum class SurfaceType {
|
||||
Window,
|
||||
Pbuffer,
|
||||
Pixmap,
|
||||
PbufferFromClientBuffer,
|
||||
PlatformWindow,
|
||||
PlatformPixmap
|
||||
};
|
||||
|
||||
struct DisplayLookupKey {
|
||||
Uint64 NativeDisplayKey = 0;
|
||||
EGLenum Platform = EGL_NONE;
|
||||
|
||||
Bool operator==(const DisplayLookupKey& rhs) const = default;
|
||||
};
|
||||
|
||||
struct DisplayLookupHasher {
|
||||
SizeT operator()(const DisplayLookupKey& key) const;
|
||||
};
|
||||
|
||||
struct DisplayObject {
|
||||
Uint64 NativeDisplayKey = 0;
|
||||
EGLenum Platform = EGL_NONE;
|
||||
Bool Initialized = false;
|
||||
EGLint MajorVersion = 1;
|
||||
EGLint MinorVersion = 5;
|
||||
EGLint SwapInterval = 1;
|
||||
Vector<EGLConfigHandle> Configs;
|
||||
};
|
||||
|
||||
struct ConfigObject {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLint ConfigId = 1;
|
||||
EGLint RedSize = 8;
|
||||
EGLint GreenSize = 8;
|
||||
EGLint BlueSize = 8;
|
||||
EGLint AlphaSize = 8;
|
||||
EGLint DepthSize = 24;
|
||||
EGLint StencilSize = 8;
|
||||
EGLint SurfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT | EGL_PIXMAP_BIT;
|
||||
EGLint RenderableType = EGL_OPENGL_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
|
||||
EGLint MinSwapInterval = 0;
|
||||
EGLint MaxSwapInterval = 4;
|
||||
EGLint NativeVisualId = 0;
|
||||
};
|
||||
|
||||
struct ContextObject {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLConfigHandle Config = nullptr;
|
||||
EGLContextHandle SharedContext = nullptr;
|
||||
EGLenum ClientAPI = EGL_OPENGL_API;
|
||||
EGLint ClientVersion = 1;
|
||||
EGLint MajorVersion = 1;
|
||||
EGLint MinorVersion = 0;
|
||||
};
|
||||
|
||||
struct SurfaceObject {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLConfigHandle Config = nullptr;
|
||||
SurfaceType Type = SurfaceType::Window;
|
||||
Uint64 NativeHandleKey = 0;
|
||||
EGLClientBuffer ClientBuffer = nullptr;
|
||||
EGLenum BufferType = EGL_NONE;
|
||||
EGLint Width = 0;
|
||||
EGLint Height = 0;
|
||||
EGLint TextureFormat = EGL_NO_TEXTURE;
|
||||
EGLint TextureTarget = EGL_NO_TEXTURE;
|
||||
EGLint MipmapLevel = 0;
|
||||
EGLint MipmapTexture = EGL_FALSE;
|
||||
EGLint RenderBuffer = EGL_BACK_BUFFER;
|
||||
EGLint SwapBehavior = EGL_BUFFER_DESTROYED;
|
||||
};
|
||||
|
||||
struct SyncObject {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLenum Type = EGL_SYNC_FENCE;
|
||||
EGLenum Condition = EGL_SYNC_PRIOR_COMMANDS_COMPLETE;
|
||||
EGLenum Status = EGL_SIGNALED;
|
||||
};
|
||||
|
||||
struct ImageObject {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLContextHandle Context = nullptr;
|
||||
EGLenum Target = EGL_NONE;
|
||||
EGLClientBuffer Buffer = nullptr;
|
||||
};
|
||||
|
||||
struct ThreadCurrentState {
|
||||
EGLDisplayHandle Display = EGL_NO_DISPLAY;
|
||||
EGLSurfaceHandle DrawSurface = EGL_NO_SURFACE;
|
||||
EGLSurfaceHandle ReadSurface = EGL_NO_SURFACE;
|
||||
EGLContextHandle Context = nullptr;
|
||||
};
|
||||
|
||||
template <typename HandleType>
|
||||
static HandleType EncodeHandle(Uint64 rawHandle) {
|
||||
return reinterpret_cast<HandleType>(static_cast<SizeT>(rawHandle));
|
||||
}
|
||||
|
||||
template <typename NativeType>
|
||||
static Uint64 ToNativeKey(NativeType nativeHandle) {
|
||||
if constexpr (std::is_pointer_v<NativeType>) {
|
||||
return static_cast<Uint64>(reinterpret_cast<SizeT>(nativeHandle));
|
||||
} else {
|
||||
return static_cast<Uint64>(nativeHandle);
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<EGLint> ParseAttribValue(const EGLint* attribList, EGLint attrib);
|
||||
static Optional<EGLAttrib> ParseAttribValue(const EGLAttrib* attribList, EGLint attrib);
|
||||
static std::thread::id CurrentThreadKey();
|
||||
|
||||
EGLDisplayHandle GetOrCreateDisplay(Uint64 nativeDisplayKey, EGLenum platform);
|
||||
EGLConfigHandle CreateDefaultConfig(EGLDisplayHandle display);
|
||||
|
||||
DisplayObject* TryGetDisplay(EGLDisplayHandle display);
|
||||
const DisplayObject* TryGetDisplay(EGLDisplayHandle display) const;
|
||||
const ConfigObject* TryGetConfig(EGLConfigHandle config) const;
|
||||
const ContextObject* TryGetContext(EGLContextHandle context) const;
|
||||
const SurfaceObject* TryGetSurface(EGLSurfaceHandle surface) const;
|
||||
const SyncObject* TryGetSync(EGLSyncHandle sync) const;
|
||||
const ImageObject* TryGetImage(EGLImageHandle image) const;
|
||||
|
||||
void ReleaseDisplayObjects(EGLDisplayHandle display);
|
||||
void ReleaseThreadUnlocked(const std::thread::id& threadKey);
|
||||
|
||||
private:
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
|
||||
Uint64 m_nextDisplayHandle = 1;
|
||||
Uint64 m_nextConfigHandle = 1;
|
||||
Uint64 m_nextSurfaceHandle = 1;
|
||||
Uint64 m_nextContextHandle = 1;
|
||||
Uint64 m_nextSyncHandle = 1;
|
||||
Uint64 m_nextImageHandle = 1;
|
||||
|
||||
UnorderedMap<DisplayLookupKey, EGLDisplayHandle, DisplayLookupHasher> m_displayLookup;
|
||||
UnorderedMap<EGLDisplayHandle, DisplayObject> m_displays;
|
||||
UnorderedMap<EGLConfigHandle, ConfigObject> m_configs;
|
||||
UnorderedMap<EGLSurfaceHandle, SurfaceObject> m_surfaces;
|
||||
UnorderedMap<EGLContextHandle, ContextObject> m_contexts;
|
||||
UnorderedMap<EGLSyncHandle, SyncObject> m_syncs;
|
||||
UnorderedMap<EGLImageHandle, ImageObject> m_images;
|
||||
|
||||
UnorderedMap<std::thread::id, EGLint> m_threadErrors;
|
||||
UnorderedMap<std::thread::id, EGLenum> m_threadBoundAPI;
|
||||
UnorderedMap<std::thread::id, ThreadCurrentState> m_threadCurrents;
|
||||
UnorderedMap<EGLContextHandle, std::thread::id> m_contextOwners;
|
||||
};
|
||||
} // namespace EGLState
|
||||
|
||||
extern EGLState::EGLContext* pEGLContext;
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "MG_State/GLState/RenderbufferState/RenderbufferObject.h"
|
||||
#include "MG_State/EGLState/Core.h"
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
void Init() {
|
||||
MGLOG_D("Initializing MobileGL State...");
|
||||
pGLContext = new MG_State::GLState::GLContext();
|
||||
pEGLContext = new MG_State::EGLState::EGLContext();
|
||||
}
|
||||
|
||||
namespace GLState {
|
||||
@@ -312,6 +314,14 @@ namespace MobileGL {
|
||||
return m_renderState.GetClearDepth();
|
||||
}
|
||||
|
||||
void GLContext::SetClearStencil(Int stencil) {
|
||||
m_renderState.SetClearStencil(stencil);
|
||||
}
|
||||
|
||||
Int GLContext::GetClearStencil() const {
|
||||
return m_renderState.GetClearStencil();
|
||||
}
|
||||
|
||||
void GLContext::SetPixelStoreParam(PixelStoreParam param, Int value) {
|
||||
m_renderState.SetPixelStoreParam(param, value);
|
||||
}
|
||||
@@ -432,4 +442,4 @@ namespace MobileGL {
|
||||
|
||||
GLState::GLContext* pGLContext;
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace MobileGL {
|
||||
const FloatVec4& GetClearColor() const;
|
||||
void SetClearDepth(Float depth);
|
||||
Float GetClearDepth() const;
|
||||
void SetClearStencil(Int stencil);
|
||||
Int GetClearStencil() const;
|
||||
void SetPixelStoreParam(PixelStoreParam param, Int value);
|
||||
Int GetPixelStoreParam(PixelStoreParam param) const;
|
||||
PixelStoreParameters GetPixelStoreParameters(Bool isUnpack) const;
|
||||
@@ -161,4 +163,4 @@ namespace MobileGL {
|
||||
|
||||
extern GLState::GLContext* pGLContext;
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -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,
|
||||
@@ -174,6 +179,10 @@ namespace MobileGL {
|
||||
return m_shaders;
|
||||
}
|
||||
|
||||
const Vector<SharedPtr<ShaderObject>>& ProgramObject::GetAttachedShaders() const {
|
||||
return m_shaders;
|
||||
}
|
||||
|
||||
void ProgramObject::DoReflection() {
|
||||
if (!m_program) {
|
||||
MGLOG_E("ProgramObject %u: DoReflection called but m_program is null", m_externalIndex);
|
||||
|
||||
@@ -30,11 +30,12 @@ namespace MobileGL {
|
||||
Int GetFragmentDataLocation(const char* name);
|
||||
|
||||
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
||||
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
||||
const String& GetInfoLog() const { return m_infoLog; }
|
||||
Int GetUniformMaxLength() const { return m_uniformNameMaxLength; }
|
||||
Uint GetUniformCount() { return m_activeUniformCount; }
|
||||
Uint GetMaxUniformLocation() const { return m_maxUniformLocation; }
|
||||
Int GetUniformLocation(const String& name) {
|
||||
Int GetUniformLocation(const String& name) const {
|
||||
const auto it = m_uniformLocations.find(name);
|
||||
if (it == m_uniformLocations.end()) return -1;
|
||||
return (Int)it->second;
|
||||
@@ -68,6 +69,7 @@ namespace MobileGL {
|
||||
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 SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
||||
@@ -111,6 +113,13 @@ namespace MobileGL {
|
||||
Uint GetUniformBlockBinding(Uint index) const { return m_uniformBlockBinding[index]; }
|
||||
|
||||
Vector<Vector<unsigned>>& GetGeneratedSpirv() { return m_generatedSpirv; }
|
||||
const Vector<Vector<unsigned>>& GetGeneratedSpirv() const { 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; }
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ namespace MobileGL {
|
||||
// Compile for OpenGL here, so that we can do validation and link
|
||||
// like a real OpenGL driver at linking stage
|
||||
// Will compile for other backends later.
|
||||
ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage),
|
||||
.sourceStr = m_source,
|
||||
.flags = ShaderCompileBits::CompileForOpenGL};
|
||||
ShaderAttrib attrib{
|
||||
.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage), .sourceStr = m_source, .flags = ShaderCompileBits::CompileForOpenGL};
|
||||
|
||||
auto result = ShaderCompiler::CompileShader(attrib);
|
||||
if (result) {
|
||||
@@ -53,4 +52,4 @@ namespace MobileGL {
|
||||
}
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace MobileGL {
|
||||
Geometry,
|
||||
Fragment,
|
||||
Compute,
|
||||
ShaderStageCount,
|
||||
Unknown = -1
|
||||
};
|
||||
|
||||
|
||||
@@ -222,6 +222,17 @@ namespace MobileGL {
|
||||
return m_parameters.ClearDepth;
|
||||
}
|
||||
|
||||
void RenderState::SetClearStencil(Int stencil) {
|
||||
if (m_parameters.ClearStencil == stencil) return;
|
||||
|
||||
m_parameters.ClearStencil = stencil;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
Int RenderState::GetClearStencil() const {
|
||||
return m_parameters.ClearStencil;
|
||||
}
|
||||
|
||||
// -------------------- Pixel Store --------------------
|
||||
void RenderState::SetPixelStoreParam(PixelStoreParam param, Int value) {
|
||||
#define SET_PIXEL_STORE_PARAM(paramNameHead, paramNameTail, val) \
|
||||
|
||||
@@ -154,6 +154,7 @@ namespace MobileGL {
|
||||
// Clear State
|
||||
FloatVec4 ClearColor = FloatVec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
Float ClearDepth = 1.0f;
|
||||
Int ClearStencil = 0;
|
||||
|
||||
// Cull Face
|
||||
Bool CullFaceEnabled = false;
|
||||
@@ -207,6 +208,8 @@ namespace MobileGL {
|
||||
const FloatVec4& GetClearColor() const;
|
||||
void SetClearDepth(Float depth);
|
||||
Float GetClearDepth() const;
|
||||
void SetClearStencil(Int stencil);
|
||||
Int GetClearStencil() const;
|
||||
|
||||
// Pixel Store
|
||||
void SetPixelStoreParam(PixelStoreParam param, Int value);
|
||||
@@ -231,4 +234,4 @@ namespace MobileGL {
|
||||
};
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
message(STATUS "Generating build files for MobileGL DirectVulkan Integration Test...")
|
||||
|
||||
if (APPLE)
|
||||
find_library(QUARTZCORE_FRAMEWORK QuartzCore REQUIRED)
|
||||
endif()
|
||||
|
||||
add_executable(
|
||||
DirectVulkanSanityTest
|
||||
SanityTest.cpp
|
||||
)
|
||||
|
||||
target_include_directories(DirectVulkanSanityTest PRIVATE
|
||||
${MGL_ROOT}/include
|
||||
${MGL_ROOT}/MobileGL
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
# I know this is horrible, but glfw only provides binary but not CMake config for Windows, so we have to manually link against the library and include directories
|
||||
#set(GLFW_ROOT path/to/glfw-3.4.bin.WIN64)
|
||||
target_include_directories(DirectVulkanSanityTest PRIVATE ${GLFW_ROOT}/include)
|
||||
target_link_directories(DirectVulkanSanityTest PRIVATE ${GLFW_ROOT}/lib-vc2022)
|
||||
target_link_libraries(DirectVulkanSanityTest PRIVATE glfw3_mt.lib)
|
||||
else()
|
||||
find_package(glfw3 REQUIRED)
|
||||
target_link_libraries(DirectVulkanSanityTest PRIVATE glfw)
|
||||
endif()
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
target_include_directories(DirectVulkanSanityTest PUBLIC ${Vulkan_INCLUDE_DIRS})
|
||||
target_link_libraries(DirectVulkanSanityTest PRIVATE Vulkan::Vulkan)
|
||||
|
||||
target_link_libraries(
|
||||
DirectVulkanSanityTest PRIVATE
|
||||
GTest::gtest_main
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
if (APPLE)
|
||||
target_link_libraries(DirectVulkanSanityTest PRIVATE objc)
|
||||
target_link_libraries(DirectVulkanSanityTest PRIVATE ${QUARTZCORE_FRAMEWORK})
|
||||
endif()
|
||||
target_compile_definitions(DirectVulkanSanityTest PRIVATE -DNOMINMAX)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(DirectVulkanSanityTest)
|
||||
|
||||
add_executable(
|
||||
DirectVulkanTestExec
|
||||
TestExec.cpp
|
||||
)
|
||||
|
||||
target_include_directories(DirectVulkanTestExec PRIVATE
|
||||
${MGL_ROOT}/include
|
||||
${MGL_ROOT}/MobileGL
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
# I know this is horrible, but glfw only provides binary but not CMake config for Windows, so we have to manually link against the library and include directories
|
||||
#set(GLFW_ROOT path/to/glfw-3.4.bin.WIN64)
|
||||
target_include_directories(DirectVulkanTestExec PRIVATE ${GLFW_ROOT}/include)
|
||||
target_link_directories(DirectVulkanTestExec PRIVATE ${GLFW_ROOT}/lib-vc2022)
|
||||
target_link_libraries(DirectVulkanTestExec PRIVATE glfw3_mt.lib)
|
||||
else()
|
||||
find_package(glfw3 REQUIRED)
|
||||
target_link_libraries(DirectVulkanTestExec PRIVATE glfw)
|
||||
endif()
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
target_include_directories(DirectVulkanTestExec PUBLIC ${Vulkan_INCLUDE_DIRS})
|
||||
target_link_libraries(DirectVulkanTestExec PRIVATE Vulkan::Vulkan)
|
||||
|
||||
target_link_libraries(
|
||||
DirectVulkanTestExec PRIVATE
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
if (APPLE)
|
||||
target_link_libraries(DirectVulkanTestExec PRIVATE objc)
|
||||
target_link_libraries(DirectVulkanTestExec PRIVATE ${QUARTZCORE_FRAMEWORK})
|
||||
endif()
|
||||
target_compile_definitions(DirectVulkanTestExec PRIVATE -DNOMINMAX)
|
||||
@@ -0,0 +1,109 @@
|
||||
// MobileGL - MobileGL/MG_Test/Backend/DirectVulkan/SanityTest.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 <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
TEST(DirectVulkanSanity, ExtensionEnumeration) {
|
||||
uint32_t extensionCount = 0;
|
||||
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
|
||||
|
||||
std::cout << extensionCount << " extensions supported\n";
|
||||
|
||||
std::vector<VkExtensionProperties> extensions(extensionCount);
|
||||
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
|
||||
|
||||
for(const auto& extension : extensions) {
|
||||
std::cout << extension.extensionName
|
||||
<< " (r." << extension.specVersion << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
TEST(DirectVulkanSanity, WindowCreation) {
|
||||
glfwInit();
|
||||
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
GLFWwindow* window = glfwCreateWindow(800, 600, "MobileGL WindowCreation", nullptr, nullptr);
|
||||
|
||||
while(!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
#define EGLAPI
|
||||
#include <EGL/egl.h>
|
||||
#ifdef _WIN32
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#elif defined(__APPLE__)
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#endif
|
||||
#include <GLFW/glfw3native.h>
|
||||
#ifdef __APPLE__
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
#include <objc/runtime.h>
|
||||
#endif
|
||||
TEST(DirectVulkanSanity, ContextCreation) {
|
||||
glfwInit();
|
||||
|
||||
static EGLint const attribute_list[] = {
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
eglInitialize(display, nullptr, nullptr);
|
||||
EGLConfig config;
|
||||
EGLint num_config;
|
||||
eglChooseConfig(display, attribute_list, &config, 1, &num_config);
|
||||
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, nullptr);
|
||||
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
GLFWwindow* window = glfwCreateWindow(800, 600, "MobileGL ContextCreation", nullptr, nullptr);
|
||||
EGLNativeWindowType nativewindow = nullptr;
|
||||
#ifdef _WIN32
|
||||
nativewindow = glfwGetWin32Window(window);
|
||||
#elif defined(__APPLE__)
|
||||
void* cocoaWindow = glfwGetCocoaWindow(window);
|
||||
ASSERT_NE(cocoaWindow, nullptr);
|
||||
auto msgSendObj = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend);
|
||||
auto msgSendVoidObj = reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend);
|
||||
auto msgSendVoidBool = reinterpret_cast<void (*)(id, SEL, bool)>(objc_msgSend);
|
||||
id contentView = msgSendObj(static_cast<id>(cocoaWindow), sel_registerName("contentView"));
|
||||
ASSERT_NE(contentView, nullptr);
|
||||
msgSendVoidBool(contentView, sel_registerName("setWantsLayer:"), true);
|
||||
|
||||
id metalLayerClass = reinterpret_cast<id>(objc_getClass("CAMetalLayer"));
|
||||
ASSERT_NE(metalLayerClass, nullptr);
|
||||
id metalLayer = msgSendObj(metalLayerClass, sel_registerName("layer"));
|
||||
ASSERT_NE(metalLayer, nullptr);
|
||||
|
||||
msgSendVoidObj(contentView, sel_registerName("setLayer:"), metalLayer);
|
||||
nativewindow = reinterpret_cast<EGLNativeWindowType>(metalLayer);
|
||||
#endif
|
||||
EGLSurface surface = eglCreateWindowSurface(display, config, nativewindow, nullptr);
|
||||
eglMakeCurrent(display, surface, surface, context);
|
||||
|
||||
while(!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
|
||||
glfwTerminate();
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// MobileGL - MobileGL/MG_Test/Backend/DirectVulkan/TestExec.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 <iostream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/glcorearb.h>
|
||||
#include <GL/glext.h>
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
#ifndef EGLAPI
|
||||
#define EGLAPI extern
|
||||
#endif
|
||||
#include <EGL/egl.h>
|
||||
#ifdef _WIN32
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#elif defined(__APPLE__)
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#endif
|
||||
|
||||
#include <GLFW/glfw3native.h>
|
||||
#ifdef __APPLE__
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
#include <objc/runtime.h>
|
||||
#endif
|
||||
|
||||
#define NO_GL_H
|
||||
#include <Includes.h>
|
||||
#undef NO_GL_H
|
||||
|
||||
namespace MobileGL {
|
||||
void Initialize();
|
||||
} // namespace MobileGL
|
||||
|
||||
static bool CheckShaderCompile(GLuint shader, const char* label) {
|
||||
GLint status = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status == GL_TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint logLength = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
std::string log;
|
||||
if (logLength > 0) {
|
||||
log.resize(static_cast<size_t>(logLength));
|
||||
GLsizei written = 0;
|
||||
glGetShaderInfoLog(shader, logLength, &written, log.data());
|
||||
if (written >= 0 && static_cast<size_t>(written) < log.size()) {
|
||||
log.resize(static_cast<size_t>(written));
|
||||
}
|
||||
}
|
||||
std::cerr << label << " compile failed: " << log << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool CheckProgramLink(GLuint program) {
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (status == GL_TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint logLength = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
|
||||
std::string log;
|
||||
if (logLength > 0) {
|
||||
log.resize(static_cast<size_t>(logLength));
|
||||
GLsizei written = 0;
|
||||
glGetProgramInfoLog(program, logLength, &written, log.data());
|
||||
if (written >= 0 && static_cast<size_t>(written) < log.size()) {
|
||||
log.resize(static_cast<size_t>(written));
|
||||
}
|
||||
}
|
||||
std::cerr << "Program link failed: " << log << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
glfwInit();
|
||||
|
||||
static EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE};
|
||||
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
eglInitialize(display, nullptr, nullptr);
|
||||
EGLConfig config;
|
||||
EGLint num_config;
|
||||
eglChooseConfig(display, attribute_list, &config, 1, &num_config);
|
||||
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, nullptr);
|
||||
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
GLFWwindow* window = glfwCreateWindow(800, 600, "MobileGL ContextCreation", nullptr, nullptr);
|
||||
EGLNativeWindowType nativewindow = nullptr;
|
||||
#ifdef _WIN32
|
||||
nativewindow = glfwGetWin32Window(window);
|
||||
#elif defined(__APPLE__)
|
||||
void* cocoaWindow = glfwGetCocoaWindow(window);
|
||||
MOBILEGL_ASSERT(cocoaWindow, "glfwGetCocoaWindow returned null");
|
||||
auto msgSendObj = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend);
|
||||
auto msgSendVoidObj = reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend);
|
||||
auto msgSendVoidBool = reinterpret_cast<void (*)(id, SEL, bool)>(objc_msgSend);
|
||||
id contentView = msgSendObj(static_cast<id>(cocoaWindow), sel_registerName("contentView"));
|
||||
MOBILEGL_ASSERT(contentView, "Failed to query NSWindow.contentView");
|
||||
msgSendVoidBool(contentView, sel_registerName("setWantsLayer:"), true);
|
||||
|
||||
id metalLayerClass = reinterpret_cast<id>(objc_getClass("CAMetalLayer"));
|
||||
MOBILEGL_ASSERT(metalLayerClass, "Failed to lookup CAMetalLayer class");
|
||||
id metalLayer = msgSendObj(metalLayerClass, sel_registerName("layer"));
|
||||
MOBILEGL_ASSERT(metalLayer, "Failed to create CAMetalLayer");
|
||||
|
||||
msgSendVoidObj(contentView, sel_registerName("setLayer:"), metalLayer);
|
||||
nativewindow = reinterpret_cast<EGLNativeWindowType>(metalLayer);
|
||||
#endif
|
||||
EGLSurface surface = eglCreateWindowSurface(display, config, nativewindow, nullptr);
|
||||
eglMakeCurrent(display, surface, surface, context);
|
||||
|
||||
MobileGL::Initialize();
|
||||
|
||||
GLuint offscreenTex = 0;
|
||||
GLuint offscreenFbo = 0;
|
||||
glGenTextures(1, &offscreenTex);
|
||||
glBindTexture(GL_TEXTURE_2D, offscreenTex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glGenFramebuffers(1, &offscreenFbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, offscreenTex, 0);
|
||||
const GLenum offscreenFboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
std::cout << "Offscreen FBO status = 0x" << std::hex << offscreenFboStatus << std::dec << std::endl;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
static constexpr GLint kWindowWidth = 800;
|
||||
static constexpr GLint kWindowHeight = 600;
|
||||
glViewport(0, 0, kWindowWidth, kWindowHeight);
|
||||
|
||||
static constexpr int kMaxSegments = 192;
|
||||
static constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
GLuint vao = 0;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
GLuint shapeVbo = 0;
|
||||
glGenBuffers(1, &shapeVbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>((kMaxSegments + 1) * 5 * sizeof(GLfloat)), nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(5 * sizeof(GLfloat)), nullptr);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(5 * sizeof(GLfloat)),
|
||||
reinterpret_cast<void*>(2 * sizeof(GLfloat)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
GLuint shapeIbo = 0;
|
||||
glGenBuffers(1, &shapeIbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shapeIbo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLsizeiptr>(kMaxSegments * 3 * sizeof(GLushort)), nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
static constexpr const char* kVertexShaderSource = R"(#version 330 core
|
||||
layout(location = 0) in vec2 aPos;
|
||||
layout(location = 1) in vec3 aColor;
|
||||
out vec3 vColor;
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
vColor = aColor;
|
||||
})";
|
||||
static constexpr const char* kFragmentShaderSource = R"(#version 330 core
|
||||
in vec3 vColor;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
void main() {
|
||||
outColor = vec4(vColor, 1.0);
|
||||
})";
|
||||
|
||||
const GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vs, 1, &kVertexShaderSource, nullptr);
|
||||
glCompileShader(vs);
|
||||
if (!CheckShaderCompile(vs, "Vertex shader")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fs, 1, &kFragmentShaderSource, nullptr);
|
||||
glCompileShader(fs);
|
||||
if (!CheckShaderCompile(fs, "Fragment shader")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const GLuint program = glCreateProgram();
|
||||
glAttachShader(program, vs);
|
||||
glAttachShader(program, fs);
|
||||
glLinkProgram(program);
|
||||
if (!CheckProgramLink(program)) {
|
||||
return 1;
|
||||
}
|
||||
glUseProgram(program);
|
||||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
|
||||
std::vector<GLfloat> dynamicVertices(static_cast<size_t>((kMaxSegments + 1) * 5));
|
||||
std::vector<GLushort> dynamicIndices(static_cast<size_t>(kMaxSegments * 3));
|
||||
int i = 0;
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
GLint framebufferWidth = kWindowWidth;
|
||||
GLint framebufferHeight = kWindowHeight;
|
||||
glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight);
|
||||
glViewport(0, 0, framebufferWidth, framebufferHeight);
|
||||
|
||||
const bool useOffscreenPath = ((i / 100) % 2) == 0;
|
||||
if (useOffscreenPath) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo);
|
||||
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, offscreenFbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBlitFramebuffer(0, 0, 256, 256, 0, 0, framebufferWidth, framebufferHeight, GL_COLOR_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
} else {
|
||||
const int segmentCount = std::min(kMaxSegments, 3 + (i / 100));
|
||||
const float radius = 0.75f;
|
||||
const float rotation = static_cast<float>(i) * 0.01f;
|
||||
|
||||
const size_t vertexFloatCount = static_cast<size_t>((segmentCount + 1) * 5);
|
||||
const size_t indexCount = static_cast<size_t>(segmentCount * 3);
|
||||
|
||||
dynamicVertices[0] = 0.0f;
|
||||
dynamicVertices[1] = 0.0f;
|
||||
dynamicVertices[2] = 0.95f;
|
||||
dynamicVertices[3] = 0.95f;
|
||||
dynamicVertices[4] = 0.95f;
|
||||
|
||||
for (int v = 0; v < segmentCount; ++v) {
|
||||
const float theta = rotation + (2.0f * kPi * static_cast<float>(v) / static_cast<float>(segmentCount));
|
||||
const float x = radius * std::cos(theta);
|
||||
const float y = radius * std::sin(theta);
|
||||
const size_t base = static_cast<size_t>((v + 1) * 5);
|
||||
dynamicVertices[base + 0] = x;
|
||||
dynamicVertices[base + 1] = y;
|
||||
dynamicVertices[base + 2] = 0.5f + 0.5f * std::cos(theta);
|
||||
dynamicVertices[base + 3] = 0.5f + 0.5f * std::sin(theta);
|
||||
dynamicVertices[base + 4] = 0.9f;
|
||||
|
||||
const size_t ib = static_cast<size_t>(v * 3);
|
||||
dynamicIndices[ib + 0] = 0;
|
||||
dynamicIndices[ib + 1] = static_cast<GLushort>(v + 1);
|
||||
dynamicIndices[ib + 2] = static_cast<GLushort>((v + 1) % segmentCount + 1);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glClearColor(0.05f, 0.08f, 0.12f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glUseProgram(program);
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(vertexFloatCount * sizeof(GLfloat)),
|
||||
dynamicVertices.data());
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shapeIbo);
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(indexCount * sizeof(GLushort)),
|
||||
dynamicIndices.data());
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indexCount), GL_UNSIGNED_SHORT, nullptr);
|
||||
}
|
||||
|
||||
if (i % 100 == 0) {
|
||||
std::cout << "frame=" << i
|
||||
<< " path=" << (useOffscreenPath ? "offscreen-clear+blit" : "dynamic-vbo-vao-ibo-archimedes")
|
||||
<< std::endl;
|
||||
}
|
||||
eglSwapBuffers(display, surface);
|
||||
++i;
|
||||
}
|
||||
|
||||
glDeleteProgram(program);
|
||||
glDeleteBuffers(1, &shapeVbo);
|
||||
glDeleteBuffers(1, &shapeIbo);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
glDeleteFramebuffers(1, &offscreenFbo);
|
||||
glDeleteTextures(1, &offscreenTex);
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
|
||||
glfwTerminate();
|
||||
}
|
||||
@@ -19,7 +19,7 @@ using namespace MobileGL;
|
||||
|
||||
class BufferTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { MobileGL::MG_Initialize(); }
|
||||
void SetUp() override { MobileGL::Initialize(); }
|
||||
|
||||
void TearDown() override {}
|
||||
};
|
||||
@@ -543,4 +543,4 @@ TEST_F(GeneralBufferTest, General_GeneralTest_1) {
|
||||
DeleteBuffers(2, toDelete);
|
||||
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,3 +61,6 @@ gtest_discover_tests(SanityTest)
|
||||
add_subdirectory(Buffer)
|
||||
add_subdirectory(VertexArray)
|
||||
add_subdirectory(Program)
|
||||
if (ENABLE_INTEGRATION_TESTS)
|
||||
add_subdirectory(Backend/DirectVulkan)
|
||||
endif()
|
||||
@@ -18,7 +18,7 @@ using namespace MobileGL::MG_Impl::GLImpl;
|
||||
|
||||
class ProgramTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { MobileGL::MG_Initialize(); }
|
||||
void SetUp() override { MobileGL::Initialize(); }
|
||||
|
||||
void TearDown() override {}
|
||||
};
|
||||
@@ -54,7 +54,7 @@ void main(){
|
||||
// Use TestMat2 and TestMat3 to prevent optimization
|
||||
vec2 dummy2 = TestMat2[0];
|
||||
vec3 dummy3 = TestMat3[0];
|
||||
|
||||
|
||||
oneTexel = (1.0 * (fIn1 * fIn2 * fIn3 * fIn4 * fIn5 * fIn6 * fIn0)) / InSize;
|
||||
|
||||
texCoord = Position.xy / OutSize;
|
||||
@@ -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) {
|
||||
|
||||
@@ -19,7 +19,7 @@ using namespace MobileGL;
|
||||
|
||||
class ProgramUtilTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { MobileGL::MG_Initialize(); }
|
||||
void SetUp() override { MobileGL::Initialize(); }
|
||||
|
||||
void TearDown() override {}
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ protected:
|
||||
|
||||
return vbo;
|
||||
}
|
||||
void SetUp() override { MobileGL::MG_Initialize(); }
|
||||
void SetUp() override { MobileGL::Initialize(); }
|
||||
|
||||
void TearDown() override {}
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
#if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__)
|
||||
return dlsym(lib, name);
|
||||
#else
|
||||
return nullptrptr;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
void FillInVulkanCapabilities(MobileGL::MG_External::VulkanCapabilities& caps,
|
||||
VkPhysicalDeviceProperties properties) {
|
||||
caps.VulkanAPIVersion = DecodeApiVersion(properties.apiVersion);
|
||||
caps.DeviceName = properties.deviceName;
|
||||
caps.DriverVersionString = DecodeDriverVersion(properties.driverVersion);
|
||||
caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment);
|
||||
}
|
||||
} // namespace MobileGL::MG_Util::BackendLoader
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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);
|
||||
void FillInVulkanCapabilities(MobileGL::MG_External::VulkanCapabilities& caps,
|
||||
VkPhysicalDeviceProperties properties);
|
||||
} // 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
|
||||
@@ -59,7 +59,6 @@ namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace Debug {
|
||||
void Log(const char* levelTag, android_LogPriority androidLogLevel, const char* fmt, ...);
|
||||
void InitFile();
|
||||
void WriteToFile(const char* msg);
|
||||
std::string GetCurrentTime();
|
||||
constexpr char* GetOSName();
|
||||
|
||||
@@ -147,14 +147,13 @@ namespace MobileGL {
|
||||
if (attrib.flags & ShaderCompileBits::CompileForOpenGL) {
|
||||
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
|
||||
tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450);
|
||||
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5);
|
||||
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
||||
} else {
|
||||
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
|
||||
tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3);
|
||||
tshader->setEnvTarget(glslang::EShTargetSpv,
|
||||
((attrib.flags & ShaderCompileBits::EmitDiscardAsDemote)
|
||||
? glslang::EShTargetSpv_1_6
|
||||
: glslang::EShTargetSpv_1_5));
|
||||
// MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path,
|
||||
// so generated SPIR-V must not exceed SPIR-V 1.3.
|
||||
tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
|
||||
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
||||
tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and
|
||||
// gl_InstanceID?
|
||||
}
|
||||
@@ -233,7 +232,7 @@ namespace MobileGL {
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
Optimizer optimizer(SPV_ENV_UNIVERSAL_1_5);
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
|
||||
optimizer.RegisterPass(EliminateFloatEqualsZeroPass::CreateEliminateFloatEqualsZeroPass());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -103,6 +103,16 @@ If you want to try the project right now, you’ll need to build it yourself:
|
||||
* `MG_Test` and `MG_Benchmark` can only be built with Clang, not GCC. To enforce Clang, add `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` to your command.
|
||||
* On Android, tests and benchmarks are always disabled.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
MobileGL supports runtime configuration via environment variables.
|
||||
|
||||
### Supported Keys
|
||||
|
||||
| Variable | Description | Allowed Values | Default |
|
||||
|-------------------------|--------------------------------------------------|--------------------------------------|----------------|
|
||||
| `MOBILEGL_BACKEND_TYPE` | Select active backend implementation at startup. | `DirectGLES`, `DirectVulkan` | `DirectGLES` |
|
||||
|
||||
## Notice
|
||||
|
||||
* MobileGL is **not** production-ready currently.
|
||||
|
||||
Reference in New Issue
Block a user