Compare commits

...
Author SHA1 Message Date
BZLZHH 24dfbb41f9 [Fix] (Diligent): fix y inversion and front-face winding in viewport mapping
- Translate OpenGL bottom-left viewport/scissor rects to Diligent top-left origin
- Invert FrontCounterClockwise to compensate for the Y conversion
- Add DrawsTopHalfTriangleFromMobileGLState y-orientation test
2026-08-23 12:25:41 +08:00
BZLZHH ca7878bf3a [Feat] (AndroidPlugin): expose DiligentVulkan backend in plugin selector 2026-08-23 11:11:20 +08:00
BZLZHH e418063b08 [Feat] (Diligent, EGL): forward eglSwapInterval to swap chain Present
- Renderer stores the requested swap interval and passes it to ISwapChain::Present
- SetSwapInterval now updates the active renderer instead of being a no-op
2026-08-23 11:01:40 +08:00
BZLZHH b43ec25bd7 [Feat] (Diligent, EGL): validate and defer EGL surface activation
- CreateEGLWindowSurface/CreateEGLPbufferSurface register surfaces only; activation happens on eglMakeCurrent
- Reject unsupported native window backends
2026-08-23 10:57:09 +08:00
BZLZHH 525607bad6 [Feat] (Diligent, EGL): release swap chain on EGL surface release 2026-08-23 10:55:40 +08:00
BZLZHH b045024b6c [Feat] (Diligent, EGL): route EGL window resize to Diligent swap chain
- Add renderer->ResizeSwapChain and override BackendObject_Diligent::ResizeEGLWindowSurface
- Update handoff; 16 Diligent tests pass
2026-08-23 10:53:19 +08:00
BZLZHH 14dfbeeed9 [Feat] (Diligent, EGL): wire EGL window swapchain creation via Diligent ISwapChain
- Add CreateSwapChain to renderer using IEngineFactoryVk::CreateSwapChainVk
- InitWindowSurface creates the swapchain for native window surfaces
- Present now presents the active swapchain; ReleaseEGLResources releases it
- InitPbufferSurface keeps offscreen target for pbuffer EGL surfaces
- Update handoff; 16 Diligent tests pass
2026-08-23 10:46:06 +08:00
BZLZHH d7e79409b3 [Feat] (Diligent): wire SetSwapInterval no-op
- GlobalBackendFunctionsTable.SetSwapInterval is now present; offscreen renderer ignores it
- Update handoff; 16 Diligent tests pass
2026-08-23 10:16:56 +08:00
BZLZHH ca3b524396 [Feat] (Diligent): wire CPU timer query fallback
- Add steady_clock based BeginTimeElapsedQuery/EndTimeElapsedQuery/QueryCounterTimestamp
- Wire IsTimerQuerySupported/IsQueryResultAvailable/GetQueryResult64/DeleteBackendQuery
- Update handoff; 16 Diligent tests pass
2026-08-23 10:15:19 +08:00
BZLZHH 071c8eb673 [Feat] (Diligent): wire fence sync CPU fallback
- Provide always-signaled FenceSync/ClientWaitSync/WaitSync/DeleteSync/GetSyncStatus
- Update handoff; 16 Diligent tests pass
2026-08-23 10:12:58 +08:00
BZLZHH 51a43518ac [Feat] (Diligent, MG_Impl): wire BlitNamedFramebuffer color copy
- Explicit read/draw FBO color attachments resolve to Diligent textures/renderbuffers
- CopyTexture between them for same-size color blits
- Update handoff; 16 Diligent tests pass
2026-08-23 10:11:17 +08:00
BZLZHH 34ff95f6f5 [Feat] (Diligent, MG_Impl): wire GenerateMipmap via Diligent GPU mip generation
- Create state textures with MISC_TEXTURE_FLAG_GENERATE_MIPS
- GenerateMipmap resolves active GL_TEXTURE_2D and calls IDeviceContext::GenerateMips
- Update handoff; 16 Diligent tests pass
2026-08-23 10:08:54 +08:00
BZLZHH b9d1504cc5 [Feat] (Diligent, MG_Impl): wire CopyImageSubData whole-texture copy
- CopyImageSubData syncs both texture objects and issues a Diligent CopyTexture
- Update handoff; 16 Diligent tests pass
2026-08-23 10:05:50 +08:00
BZLZHH a223499143 [Feat] (Diligent, MG_Impl): wire ClearBufferfi and stencil clears through ClearBufferiv/uiv
- ClearBufferfi clears depth+stencil on the current draw framebuffer
- ClearBufferiv/uiv now support GL_STENCIL via ClearStencil
- Update handoff; 16 Diligent tests pass
2026-08-23 10:04:14 +08:00
BZLZHH 5dca617f01 [Feat] (Diligent, MG_Impl): honor DrawElementsBaseVertex baseVertex in CPU vertex packing
- Add baseVertex parameter through DrawFromState/UploadVertexDataFromState
- Apply baseVertex when resolving indexed vertex indices
- Pass baseVertex through DrawElementsBaseVertex, instanced, and indirect indexed draws
- Add DrawsIndexedBaseVertexFromMobileGLState; 16 Diligent tests pass
2026-08-23 10:02:44 +08:00
BZLZHH eb8ef893be [Feat] (Diligent): support multiple simultaneous color attachments
- PSO RTV count/formats now derive from the bound draw FBO color attachments
- Include RT layout in the last-PSO cache key
- Add DrawsToMultipleColorAttachmentsFromMobileGLState; 15 Diligent tests pass
2026-08-23 09:56:58 +08:00
BZLZHH 20567fba6d [Feat] (Diligent, MG_State): cache renderbuffer resources and support color readback
- SyncRenderbuffer now reuses the cached Diligent texture so Clear/Draw/ReadPixels target the same resource
- ReadPixels resolves renderbuffer color attachments from the read FBO
- Add DrawsToRenderbufferFramebufferFromMobileGLState; 14 Diligent tests pass
2026-08-23 09:54:08 +08:00
BZLZHH e3f44e8da1 [Feat] (Diligent, MG_Impl): add indirect draw CPU fallbacks
- Wire DrawArraysIndirect/DrawElementsIndirect
- Wire MultiDraw*Indirect and *IndirectCount using client memory or GL_DRAW_INDIRECT_BUFFER/GL_PARAMETER_BUFFER CPU reads
- Update handoff; 13 Diligent tests pass
2026-08-23 09:45:34 +08:00
BZLZHH 9f79a88af5 [Feat] (Diligent, MG_Impl): wire GetTexImage/GetTextureImage RGBA8 readback
- Copy texture to staging and map rows for GL_RGBA/GL_UNSIGNED_BYTE
- Wire both GLFunctionsTable entries; 13 Diligent tests pass
2026-08-23 09:41:17 +08:00
BZLZHH 6bf32acdef [Feat] (Diligent, MG_Impl): wire CopyTexImage2D/CopyTexSubImage2D readback copy
- Copy current read-FBO color attachment into the bound GL_TEXTURE_2D
- Uses whole-color CopyTexture fallback for now; 13 Diligent tests pass
2026-08-23 09:39:40 +08:00
BZLZHH 23b53eacce [Feat, Test] (Diligent, MG_Test): add stencil clear and stencil state test
- Use D24S8 for the default offscreen depth/stencil target
- Wire GL_STENCIL_BUFFER_BIT Clear through renderer->ClearStencil
- Add DrawsWithStencilTestFromMobileGLState; 13 Diligent tests pass
2026-08-23 09:37:49 +08:00
BZLZHH e829e70d8b [Feat] (Diligent, MG_State): bind named application uniform blocks from frontend buffers
- Resolve named UBOs through SPIRV-Reflect type names when block names are empty
- Read the bound GL buffer range at the frontend uniform-block binding point and upload it as a Diligent uniform buffer
- Add DrawsNamedUniformBlockFromMobileGLState test; 12 Diligent tests pass
2026-08-23 09:35:32 +08:00
BZLZHH 7e765e1535 [Test] (Diligent, MG_Test): add depth test and ensure default framebuffer isolation
- Verify nearer depth draw occludes farther draw
- Bind default framebuffer at test start so previous FBO state cannot leak
- Update handoff to 11 passing Diligent tests
2026-08-23 09:23:06 +08:00
BZLZHH bf6061811f [Test] (Diligent, MG_Test): add scissor and blend state tests
- Verify scissor clipping leaves outside pixels untouched
- Verify alpha blend combines source/destination colors
- Update handoff to 10 passing Diligent tests
2026-08-23 09:17:41 +08:00
BZLZHH 87750c3b21 [Feat] (Diligent): add instanced/clear-buffer/blit GL entry points
- Wire DrawElementsBaseVertex, instanced draw family, MultiDrawElementsBaseVertex
- Wire ClearBufferfv/iv/uiv to the Diligent clear path
- Add same-size color BlitFramebuffer between current read/draw framebuffers
- Update handoff with newly implemented GL 3.2 entry points
2026-08-23 09:15:21 +08:00
BZLZHH 45b309db37 [Feat] (Diligent, MG_Impl): wire GL ReadPixels entry to offscreen/user-FBO readback 2026-08-23 09:11:56 +08:00
BZLZHH 403c82ac4a [Feat, Docs] (Diligent): cache last PSO and document framebuffer/UBO progress
- Reuse the last state PSO when program/render-state/topology/VAO layout is unchanged
- Update handoff with completed texture/sampler, UBO, framebuffer, and multi-draw work
2026-08-23 09:10:53 +08:00
BZLZHH 827d46cad3 [Feat] (Diligent, MG_State): wire textures, samplers, global UBO, and user framebuffers
- Auto-sync ITextureObject to Diligent textures with dirty-level uploads
- Translate SamplerObject/unit sampler state into Diligent samplers
- Bind the synthesized MGL_GLOBAL_UBO for default-block glUniform data
- Resolve bound draw/read framebuffers to Diligent RTV/DSV for draws/clears/readback
- Wire DrawRangeElements, DrawRangeElementsBaseVertex, MultiDrawArrays, MultiDrawElements
- Add real-texture, uniform, and user-framebuffer tests; 8 Diligent tests pass
2026-08-23 09:09:03 +08:00
BZLZHH 1748da0443 [Docs] (Diligent): add Diligent GL3.2 backend handoff document 2026-08-23 08:36:57 +08:00
BZLZHH 7efee8e3e6 [Test] (Diligent, MG_Test): verify indexed DrawElements path from real frontend state
Add DrawsIndexedFromMobileGLState: creates a GL program, VBO, EBO and VAO
through the frontend, then draws via DrawFromState(GL_TRIANGLES, ...,
GL_UNSIGNED_INT, nullptr) and verifies the offscreen center is red.

All 5 Diligent local tests pass.
2026-08-18 14:22:20 +08:00
BZLZHH 08ca897a07 [Feat, Test] (Diligent, MG_Test): add basic texture binding and textured state-draw test
- Add CreateTestTexture(): creates an RGBA8 texture, SRV and default sampler,
  and attaches the sampler to the SRV.
- State PSOs now bind a static pixel-shader variable 'g_Texture' to the test
  texture and commit shader resources before drawing.
- Add DrawsTexturedFromMobileGLState test using a real GL program with
  sampler2D and interleaved position+UV attributes.
- All 4 Diligent local tests pass on Turnip Adreno 750.
2026-08-18 14:20:02 +08:00
BZLZHH 98f2a55214 [Feat] (Diligent, MG_Impl): clear depth in GL Clear when GL_DEPTH_BUFFER_BIT set 2026-08-18 13:38:44 +08:00
BZLZHH cedc257566 [Feat] (Diligent): add offscreen depth target and depth clear
The renderer now creates a D32_FLOAT depth target and binds it as DSV for all
render passes, so depth-test state wired earlier can actually work. Add
ClearDepth for depth clears. All Diligent local tests still pass.
2026-08-18 13:34:37 +08:00
BZLZHH 821c0e0d4e [Feat] (Diligent): wire stencil and color-mask state into state PSO
CreatePipelineFromState now applies GL_STENCIL_TEST state (front/back funcs,
ops, read/write masks) and GL color write mask. DrawFromState sets the stencil
reference before drawing. All Diligent local tests still pass.
2026-08-18 13:31:13 +08:00
BZLZHH bd9680ad67 [Feat] (Diligent): wire viewport/scissor state into state draws
DrawFromState now uses MG_State viewport (with full-target fallback when the
viewport is uninitialized) and applies the scissor test rect when enabled.
The state-driven test sets an explicit glViewport and passes again.
2026-08-18 13:26:20 +08:00
BZLZHH 6375e07030 [Feat] (Diligent): wire blend/depth/cull render state into state PSO
CreatePipelineFromState now reads MG_State render state:
- GL_BLEND enable, blend factors/equations
- GL_DEPTH_TEST enable, depth func, depth write mask
- GL_CULL_FACE enable, cull face mode, front-face winding

All Diligent local tests still pass.
2026-08-18 13:16:55 +08:00
BZLZHH 53cac39d4e [Test] (Diligent, MG_Test): verify state-driven draw with real MobileGL frontend state
Add DrawsFromMobileGLState test that creates a GL 3.2 program, buffer and VAO
through the real frontend, then draws through DiligentRenderer::DrawFromState
and verifies the offscreen pixels.

Also release PSO/vertex buffer before recreation to avoid Diligent debug
assertions about overwriting references.

All 3 Diligent local tests pass on Turnip Adreno 750.
2026-08-18 13:13:10 +08:00
BZLZHH f6b1ea635b [Feat] (Diligent, MG_State): add state-driven draw path (VAO/buffer/program to Diligent)
DiligentRenderer now has DrawFromState() that:
- reads the current MobileGL program SPIR-V and creates Diligent shaders
- reads the current VAO enabled attributes and packs bound buffer data into
  an interleaved vertex buffer
- creates a PSO with the matching input layout and primitive topology
- supports DrawArrays, DrawElements, triangle-fan and line-loop expansion

This is the first real front-end state wiring; it compiles and is used by
GLFunctionsTable DrawArrays/DrawElements, but is not yet covered by a
runtime state-driven test.
2026-08-18 13:08:23 +08:00
BZLZHH 8b2711e32a [Feat] (Diligent): add dynamic vertex buffer upload path
DiligentRenderer can now upload arbitrary vec2 vertex data into a dynamic
vertex buffer and draw it with the existing triangle PSO. The local sanity
test uses this path instead of the hardcoded triangle, verifying buffer
basics on Turnip Adreno 750.
2026-08-18 13:01:29 +08:00
BZLZHH 9776cc8047 [Feat] (Diligent, MG_Backend): wire Clear/Draw/Present into GLFunctionsTable
BackendObject_Diligent now exposes real function-table entries backed by the
DiligentRenderer: Clear reads the current GL clear color from MG_State, and
DrawArrays/DrawElements currently render the built-in triangle as a
placeholder until buffer/VAO/program state is connected. Present flushes the
immediate context.

Local Diligent tests still pass on Turnip Adreno 750.
2026-08-18 12:57:06 +08:00
BZLZHH 3b0591e0ba [Feat] (Diligent): add real offscreen renderer with clear and triangle draw
- Add DiligentRenderer: creates an offscreen RGBA8 render target, compiles a
  GLSL vertex/pixel shader through Diligent's glslang path, creates a triangle
  vertex buffer and pipeline, and supports clear/draw/readback.
- BackendObject_Diligent now owns a DiligentRenderer after device creation.
- Extend local sanity test to clear green, draw a red triangle, and verify
  center is red and corner stays green.
- All Diligent local tests pass on Turnip Adreno 750.
2026-08-18 12:54:10 +08:00
BZLZHH e62f158c22 [Feat] (Diligent, CMake): enable DiligentCore and add initial Diligent/Vulkan backend skeleton
- Add MOBILEGL_ENABLE_DILIGENT option; build DiligentCore Vulkan-only after
  MobileGL's existing 3rdparty targets so shared glslang/SPIRV-Cross/xxHash
  targets are reused instead of duplicated.
- Add BackendType::DiligentVulkan, config parsing, and backend-object switch.
- Add DiligentBackend::BackendObject_Diligent skeleton: creates a Diligent
  Vulkan device/context when an adapter is available, advertises GL 3.2 core,
  and returns an empty GL function table for now.
- Add local DiligentVulkanSanityTest that compiles/runs on the host (skips
  device creation gracefully when no Vulkan adapter is present).
- Fix GLXImpl EGLDisplay member shadowing the X11 Display typedef, exposed by
  GCC 16 + Diligent header include order.
2026-08-18 12:42:17 +08:00
17 changed files with 4783 additions and 13 deletions
+4 -1
View File
@@ -1,4 +1,4 @@
################################################################################
################################################################################
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
################################################################################
@@ -16,6 +16,9 @@ MobileGLCodeManager
MobileGL/MG_Test/build
/build_*
/cmake-build*
/build-*/
/local.properties
/.jspace/
.idea
MobileGL/MG*/build*
MobileGL/MG*/cmake-build*
+42 -1
View File
@@ -199,7 +199,6 @@ set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging"
set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
# add_subdirectory(3rdparty/DiligentCore)
add_subdirectory(3rdparty/glslang)
add_subdirectory(3rdparty/SPIRV-Cross)
add_subdirectory(3rdparty/VulkanMemoryAllocator)
@@ -211,6 +210,23 @@ set(XXHASH_BUILD_XXHSUM OFF)
option(BUILD_SHARED_LIBS OFF)
add_subdirectory(3rdparty/xxHash/build/cmake xxhash_build EXCLUDE_FROM_ALL)
# Diligent-based backend. Enabled by default on local builds; only the Vulkan
# engine from DiligentCore is built. Added after the other 3rdparty projects so
# DiligentCore reuses the glslang / SPIRV-Cross / SPIRV-Tools / xxHash targets
# already defined by MobileGL instead of building its bundled copies.
option(MOBILEGL_ENABLE_DILIGENT "Enable the Diligent/Vulkan backend" ON)
if(MOBILEGL_ENABLE_DILIGENT)
set(DILIGENT_NO_DIRECT3D11 ON CACHE BOOL "Disable Direct3D11 backend" FORCE)
set(DILIGENT_NO_DIRECT3D12 ON CACHE BOOL "Disable Direct3D12 backend" FORCE)
set(DILIGENT_NO_OPENGL ON CACHE BOOL "Disable OpenGL backend" FORCE)
set(DILIGENT_NO_METAL ON CACHE BOOL "Disable Metal backend" FORCE)
set(DILIGENT_NO_WEBGPU ON CACHE BOOL "Disable WebGPU backend" FORCE)
set(DILIGENT_NO_ARCHIVER ON CACHE BOOL "Disable Archiver" FORCE)
set(DILIGENT_BUILD_TESTS OFF CACHE BOOL "Build Diligent tests" FORCE)
set(DILIGENT_INSTALL_CORE OFF CACHE BOOL "Install DiligentCore" FORCE)
add_subdirectory(3rdparty/DiligentCore)
endif()
set(TRACY_ENABLE ${MOBILEGL_ENABLE_TRACY} CACHE BOOL "Enable Tracy, this is an internal variable" FORCE)
if (TRACY_ENABLE)
@@ -396,6 +412,14 @@ set(SOURCE_FILES
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
)
if(MOBILEGL_ENABLE_DILIGENT)
list(APPEND SOURCE_FILES
MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp
MobileGL/MG_Backend/Diligent/DiligentVulkan.cpp
MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp
)
endif()
if (APPLE AND NOT MOBILEGL_IOS)
list(APPEND SOURCE_FILES
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
@@ -436,6 +460,21 @@ set(MOBILEGL_LINK_LIBRARIES
Threads::Threads
)
if(MOBILEGL_ENABLE_DILIGENT)
list(APPEND MOBILEGL_LINK_LIBRARIES
Diligent-GraphicsEngineVk-static
Diligent-GraphicsEngine
Diligent-GraphicsEngineNextGenBase
Diligent-GraphicsAccessories
Diligent-ShaderTools
Diligent-GraphicsTools
Diligent-Common
Diligent-Primitives
Diligent-TargetPlatform
Vulkan::Headers
)
endif()
set(MOBILEGL_COMPILE_DEF
-DVMA_STATIC_VULKAN_FUNCTIONS=0
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
@@ -501,6 +540,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME}
${MOBILEGL_COMPILE_DEF}
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
$<$<BOOL:${MOBILEGL_TRACE_ANGLE_VARIANTS}>:MOBILEGL_TRACE_ANGLE_VARIANTS=1>
$<$<BOOL:${MOBILEGL_ENABLE_DILIGENT}>:MOBILEGL_ENABLE_DILIGENT=1>
)
if(UNIX AND NOT APPLE AND NOT ANDROID)
@@ -559,6 +599,7 @@ if(NOT ANDROID)
PUBLIC
${MOBILEGL_COMPILE_DEF}
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
$<$<BOOL:${MOBILEGL_ENABLE_DILIGENT}>:MOBILEGL_ENABLE_DILIGENT=1>
)
endif()
+278
View File
@@ -0,0 +1,278 @@
# Handoff: Diligent/Vulkan GL3.2 Backend for MobileGL
Date: 2026-08-18
Branch: `feat/diligent-vulkan-backend`
Repo: `~/MobileGL-dev`
Status: **Active work-in-progress. Do not mark complete yet.**
---
## 1. Goal
Implement a complete OpenGL 3.2 front-end emulation on a new Diligent/Vulkan backend inside MobileGL, instead of the DirectVulkan / DirectGLES backends.
Target state:
- Fully wire MobileGL front-end `MG_State` (buffers, VAO, program, texture, sampler, framebuffer, render-state) into Diligent.
- Implement all GL 3.2 core entry points through the Diligent backend.
- Pass local non-Android GL3.2 tests on the Turnip Adreno 750 GPU.
---
## 2. Current Branch / Commits
Latest 12 commits on `feat/diligent-vulkan-backend`:
```
2f5abf83 test(diligent): verify indexed DrawElements path from real frontend state
c31b7381 feat(diligent): add basic texture binding and textured state-draw test
8945c507 feat(diligent): clear depth in GL Clear when GL_DEPTH_BUFFER_BIT set
558d3aea feat(diligent): add offscreen depth target and depth clear
7e2f0bc8 feat(diligent): wire stencil and color-mask state into state PSO
f99786f6 feat(diligent): wire viewport/scissor state into state draws
be4cc3ce feat(diligent): wire blend/depth/cull render state into state PSO
c855e6cf feat(diligent): verify state-driven draw with real MobileGL frontend state
02e60bfa feat(diligent): add state-driven draw path (VAO/buffer/program to Diligent)
a9515c92 feat(diligent): add dynamic vertex buffer upload path
2f57582a feat(diligent): wire Clear/Draw/Present into GLFunctionsTable
beb21123 feat(diligent): add real offscreen renderer with clear and triangle draw
```
Working tree is clean.
---
## 3. Key Files
### Backend core
- `MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h/.cpp`
- `BackendObject_Diligent`
- Creates Diligent Vulkan device/context
- Owns `DiligentRenderer`
- Wires `GLFunctionsTable`:
- `Clear` (color + depth)
- `DrawArrays`
- `DrawElements`
- `Present`
- `MobileGL/MG_Backend/Diligent/DiligentVulkan.h/.cpp`
- Backend identity helper / translation unit
- `MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h/.cpp`
- Offscreen RGBA8 + D32F targets
- Clear / ClearDepth / DrawTriangle / DrawVertices
- `CreateTestTexture` (RGBA8 texture + SRV + sampler)
- `DrawFromState` (main front-end emulation draw path)
- `CreatePipelineFromState`:
- SPIR-V → Diligent shaders via SPIRV-Reflect
- VAO attributes → input layout
- primitive topology from GL mode
- blend / depth / cull / stencil / color-mask state
- `UploadVertexDataFromState`:
- packs enabled VAO attributes from `BufferObject` into interleaved vertex buffer
- supports `DrawArrays`, `DrawElements`, triangle-fan and line-loop expansion
- Static texture binding to `g_Texture` through PSO static variables + SRB
### Integration changes
- `CMakeLists.txt`
- New option `MOBILEGL_ENABLE_DILIGENT` (default ON for local)
- DiligentCore added **after** glslang/SPIRV-Cross/xxHash/Vulkan-Headers so it reuses existing CMake targets
- Diligent static libraries linked into `MobileGL` / `MobileGL_s`
- New Diligent backend sources added
- `MobileGL/MG_Backend/BackendObject.h`
- New `BackendType::DiligentVulkan`
- `MobileGL/MG_Backend/Init.cpp`
- New backend switch case
- `MobileGL/ConfigLoader.cpp`
- `MOBILEGL_BACKEND_TYPE=DiligentVulkan` accepted
- `MobileGL/MG_Test/CMakeLists.txt`
- New `MobileGL/MG_Test/Backend/Diligent` subdirectory
- `MobileGL/MG_Test/Backend/Diligent/`
- `CMakeLists.txt`
- `SanityTest.cpp`
### Local test files
- `MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp`
- `CreatesDiligentDeviceAndAdvertisesGL32`
- `ClearsAndDrawsTriangleOffscreen`
- `DrawsFromMobileGLState`
- `DrawsTexturedFromMobileGLState`
- `DrawsIndexedFromMobileGLState`
- `DrawsRealTexturedFromMobileGLState`
- `DrawsUniformFromMobileGLState`
- `DrawsToOffscreenFramebufferFromMobileGLState`
- `DrawsWithScissorFromMobileGLState`
- `DrawsWithBlendFromMobileGLState`
- `DrawsWithDepthTestFromMobileGLState`
- `DrawsNamedUniformBlockFromMobileGLState`
- `DrawsWithStencilTestFromMobileGLState`
- `DrawsToRenderbufferFramebufferFromMobileGLState`
- `DrawsToMultipleColorAttachmentsFromMobileGLState`
- `DrawsIndexedBaseVertexFromMobileGLState`
---
## 4. What Works Today
Verified locally on Turnip Adreno 750:
- Diligent device/context creation
- EGL window-surface swapchain creation path through Diligent `ISwapChain` (offscreen tests still use the offscreen target)
- GL 3.2 / GLSL 1.50 capability advertisement
- Offscreen color + depth rendering
- Clear color and depth
- Real mobilegl front-end state-driven drawing:
- Program SPIR-V → Diligent shaders
- VAO attributes + bound GL buffer → interleaved vertex buffer
- `DrawArrays` path
- `DrawElements` path (index buffer)
- Texture basics:
- Offscreen texture creation
- CPU → Diligent texture (`CreateTestTexture`)
- Static sampler2D binding to `g_Texture`
- Textured draw test passes
- Render state:
- Blend enable/factors/equations
- Stencil clear + test enabled on a D24S8 default depth/stencil target
- Depth test enable/func/write mask
- Cull face enable/mode/front-face winding
- Stencil test enable/masks/ops/func/ref
- Color write mask
- Viewport
- Scissor rect
- Texture/sampler full integration:
- `ITextureObject` → Diligent `ITexture` + SRV with automatic dirty upload
- `SamplerObject` / texture-object sampler → Diligent `ISampler`
- Real front-end `glTexImage2D` path (not only `CreateTestTexture`) verified
- Global UBO upload:
- Front-end `glUniform*` shadow → Diligent uniform buffer bound as `MGL_GLOBAL_UBO`
- User framebuffer mapping:
- Current draw/read FBO resolves texture attachments to Diligent RTV/DSV
- `ReadPixels` can read back from a user FBO color attachment
- More GL entry points wired:
- `DrawRangeElements` / `DrawRangeElementsBaseVertex`
- `DrawElementsBaseVertex` with real baseVertex selection
- `MultiDrawArrays` / `MultiDrawElements` / `MultiDrawElementsBaseVertex`
- `DrawArraysInstanced` / `DrawElementsInstanced` family
- Indirect draw CPU fallback: `DrawArraysIndirect`, `DrawElementsIndirect`, `MultiDraw*Indirect`, `*IndirectCount`
- `ClearBufferfv` / `ClearBufferfi` / `ClearBufferiv` / `ClearBufferuiv` (incl. stencil clear)
- `BlitFramebuffer` / `BlitNamedFramebuffer` (same-size color copy between read/draw FBOs)
- `CopyTexImage2D` / `CopyTexSubImage2D` (whole-color copy fallback)
- `CopyImageSubData` (whole-texture copy between two texture objects)
- `GenerateMipmap` (Diligent GPU mip generation on state textures)
- `GetTexImage` / `GetTextureImage` (RGBA8 readback)
- Fence sync entries (`FenceSync` / `ClientWaitSync` / `WaitSync` / `DeleteSync` / `GetSyncStatus`) as CPU always-signaled fallback
- Timer query entries (`BeginTimeElapsedQuery` / `EndTimeElapsedQuery` / `QueryCounterTimestamp` / `GetQueryResult64` etc.) as CPU `steady_clock` fallback
- `ReadPixels` from default and user color attachments
- Primitive expansion:
- `GL_TRIANGLE_FAN` expanded to triangle list
- `GL_LINE_LOOP` expanded to line strip
- Local test result:
```
[ PASSED ] 16 tests
```
---
## 5. How to Build and Run Locally
From repo root `~/MobileGL-dev`:
```bash
cmake -S . -B build-diligent -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DMOBILEGL_ENABLE_DILIGENT=ON \
-DMOBILEGL_BUILD_TEST=ON \
-DMOBILEGL_BUILD_BENCHMARK=OFF \
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST="$PWD/3rdparty/DiligentCore/ThirdParty/googletest"
cmake --build build-diligent --target DiligentVulkanSanityTest -j 4
./build-diligent/MobileGL/MG_Test/Backend/Diligent/DiligentVulkanSanityTest --gtest_color=no
```
Notes:
- `MOBILEGL_BUILD_BENCHMARK=OFF` avoids network fetch of google/benchmark in this environment.
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST` pins googletest to DiligentCore's bundled copy, avoiding flaky network clone.
- Max 4 cores is intentional: use `-j 4`.
---
## 6. Environment Notes
- Host: Linux `aarch64`, glibc 2.43 (Fedora container on Android/Droidspaces)
- GPU: Turnip Adreno 750, Vulkan API 1.4.354
- GPU nodes available:
- `/dev/dri/renderD128`
- `/dev/kgsl-3d0`
- Android SDK/NDK: `~/android-sdk` (aarch64 glibc)
- NDK `27.3.13750724`
- CMake `3.22.1`
- JDK/Gradle for APK builds:
- `~/android-build-tools/jdk17`
- `~/android-build-tools/gradle/gradle-8.10.2`
---
## 7. Known Limitations / Not Yet Implemented
- User framebuffers now support texture color attachments, renderbuffer color readback, multiple simultaneous color targets, and depth/stencil texture or renderbuffer attachments.
- Textures auto-sync `ITextureObject` → Diligent resources, including mip levels and sampler state; compressed textures and integer/3-channel formats that Diligent lacks are still skipped.
- Global UBO (default-block `glUniform*`) and named application UBO blocks (through `glBindBufferBase`/`glUniformBlockBinding`) now upload and bind; SSBOs are still not fed from frontend buffer bindings.
- Swapchain creation and resize are wired for native EGL window surfaces via `Diligent::ISwapChain`; `Present()` presents the active swap chain when present and otherwise flushes the offscreen target. Actual on-screen EGL presentation is still untested in this headless environment, and the X11 display/connection fields are not yet plumbed through `WindowHandle`. `SetSwapInterval` now forwards the requested sync interval to `ISwapChain::Present()`.
- No transform feedback / GPU-accelerated queries / non-color readback; fence sync and timer queries use CPU fallbacks.
- Draw range, multi-draw, instanced-draw wrappers, clear-buffer, blit, read-pixels, CopyTexImage*, CopyImageSubData, GenerateMipmap, GetTexImage/GetTextureImage and indirect draws are now wired; buffer subdata paths still remain.
- A last-PSO cache now avoids recreating the pipeline when program/render-state/topology/VAO layout is unchanged; texture/UBO resources are still rebound dynamically per draw.
- The `GLFunctionsTable` is only partially populated.
---
## 8. Recommended Next Steps
1. **Framebuffer / Renderbuffer mapping**
- [x] Map `MG_State::GLState::FramebufferObject` attachments to Diligent `ITextureView` / `ITexture`.
- [x] Support default framebuffer as current offscreen target.
- [x] Support `glBindFramebuffer`, `glFramebufferTexture2D`, renderbuffer color/depth attachments and renderbuffer color readback.
- [x] Multiple simultaneous color attachments.
2. **Texture / Sampler full integration**
- [x] Translate MobileGL `ITextureObject` to Diligent `ITexture` and cache by `GetLifetimeId()`.
- [x] Propagate texture unit bindings into the PSO SRB.
- [x] Translate `SamplerObject` state into Diligent `SamplerDesc`.
3. **Uniform / UBO support**
- [x] Create Diligent buffer for `ProgramObject::GetUBOData()` / `GetUBOSize()`.
- [x] Bind the global UBO as a dynamic shader resource.
- [x] Handle per-program uniform block bindings / named UBO blocks.
4. **PSO / resource caching**
- [~] Cache PSOs by program + VAO config + render state + topology (single last-PSO fast path).
- [~] Cache textures and samplers; buffers/SRBs can still be re-bound per draw.
5. **More GL 3.2 entry points**
- [x] `DrawRangeElements`
- [x] `MultiDraw*`
- [x] `BlitFramebuffer` (same-size color copy)
- [x] `ReadPixels` from non-default framebuffer
- [x] `CopyTexImage*` / `CopyImageSubData` wired as whole-resource copies
- [x] `GetTexImage` / `GetTextureImage` (RGBA8)
- [x] Indirect draws (CPU fallback)
6. **Expand local test suite**
- [x] Scissor test
- [x] Blend test
- [x] Texture filtering / sampler state test
- [x] framebuffer offscreen render-to-texture test
- [x] Depth test visual test
- [x] Stencil test
---
## 9. Handoff Notes for Next Agent
- Do **not** reference `origin/Deprecated/Feat/Diligent`; that old implementation is intentionally ignored.
- Work from this branch, keep tests green.
- The command `./build-diligent/.../DiligentVulkanSanityTest` runs all 5 Diligent tests.
- If a new test crashes during shader resource binding, remember Diligent texture SRVs need a sampler attached via `ITextureView::SetSampler()` before `InitializeStaticSRBResources()`.
- When re-creating a PSO or buffer, call `Release()` (or assign `nullptr`) before the create call to avoid Diligent debug “Overwriting reference” assertions.
+1
View File
@@ -202,6 +202,7 @@ namespace MobileGL::MG_ConfigLoader {
}
ENTRY(DirectGLES)
ENTRY(DirectVulkan)
ENTRY(DiligentVulkan)
ENTRY(Unknown)
MG_Config::ActiveBackendType = BackendType::Unknown;
#undef ENTRY
+1
View File
@@ -19,6 +19,7 @@ namespace MobileGL {
enum class BackendType {
DirectGLES,
DirectVulkan,
DiligentVulkan,
BackendTypeCount,
Unknown = -1
};
@@ -0,0 +1,906 @@
// MobileGL - MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp
// Copyright (c) 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
#include "BackendObject_Diligent.h"
#include "DiligentVulkan.h"
#include "Renderer/DiligentRenderer.h"
#include <MG_Backend/BackendObject.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/Core.h>
#include <EngineFactoryVk.h>
#include <RenderDevice.h>
#include <DeviceContext.h>
#include <exception>
#include <chrono>
namespace MobileGL::MG_Backend::DiligentBackend {
namespace {
const RendererInfo BuildInitialRendererInfo() {
RendererInfo info;
info.RendererName = "MobileGL (Diligent/Vulkan)";
info.BackendName = "Diligent Vulkan";
info.RendererGLInfo.TargetGLVersion = {3, 2, 0};
info.RendererGLInfo.TargetGLSLVersion = {1, 50, 0};
info.RendererGLInfo.IsCompatibilityProfile = false;
return info;
}
DiligentRenderer* GetActiveRenderer() {
auto* backend = dynamic_cast<BackendObject_Diligent*>(pActiveBackendObject.get());
return backend != nullptr ? backend->GetRenderer() : nullptr;
}
struct DrawArraysIndirectCommand {
Uint32 Count = 0;
Uint32 InstanceCount = 0;
Uint32 First = 0;
Uint32 BaseInstance = 0;
};
struct DrawElementsIndirectCommand {
Uint32 Count = 0;
Uint32 InstanceCount = 0;
Uint32 FirstIndex = 0;
Int32 BaseVertex = 0;
Uint32 BaseInstance = 0;
};
struct CpuTimerQuery {
std::chrono::steady_clock::time_point Start;
Uint64 TimestampNs = 0;
Bool Available = false;
};
const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) {
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (drawBuffer) {
drawBuffer->SyncPersistentMappedRange();
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
if (drawBuffer->MappedData() == nullptr || commandOffset + requiredBytes > drawBuffer->GetSize()) {
MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label);
return nullptr;
}
return drawBuffer->MappedData() + commandOffset;
}
if (indirect == nullptr) {
MGLOG_E_ONCE("%s skipped: indirect pointer is null", label);
return nullptr;
}
return reinterpret_cast<const Uint8*>(indirect);
}
void Clear(GLbitfield mask) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
return;
}
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
const auto& color = MG_State::pGLContext->GetClearColor();
renderer->Clear(color.x(), color.y(), color.z(), color.w());
}
if ((mask & GL_DEPTH_BUFFER_BIT) != 0) {
renderer->ClearDepth(MG_State::pGLContext->GetClearDepth());
}
if ((mask & GL_STENCIL_BUFFER_BIT) != 0) {
renderer->ClearStencil(MG_State::pGLContext->GetClearStencil());
}
}
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->DrawFromState(mode, first, count, 0, nullptr);
}
}
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->DrawFromState(mode, 0, count, type, indices);
}
}
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices) {
// The CPU-side UploadVertexDataFromState path already honors the selected index
// range. start/end only restrict which indices may be referenced; they do not
// change the vertex buffer layout for this backend.
(void)start;
(void)end;
DrawElements(mode, count, type, indices);
}
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex) {
(void)start;
(void)end;
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
}
}
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr) {
return;
}
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] > 0) {
renderer->DrawFromState(mode, first[i], count[i], 0, nullptr);
}
}
}
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr) {
return;
}
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] > 0) {
renderer->DrawFromState(mode, 0, count[i], type, indices[i]);
}
}
}
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLint basevertex) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
}
}
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type,
const GLvoid* const* indices, GLsizei drawcount,
const GLint* basevertex) {
for (GLsizei i = 0; i < drawcount; ++i) {
if (count[i] > 0) {
DrawElementsBaseVertex(mode, count[i], type, indices[i],
basevertex != nullptr ? basevertex[i] : 0);
}
}
}
void DrawArraysIndirect(GLenum mode, const void* indirect) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
return;
}
const auto* bytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawArraysIndirectCommand),
"DrawArraysIndirect");
if (bytes == nullptr) {
return;
}
DrawArraysIndirectCommand cmd{};
std::memcpy(&cmd, bytes, sizeof(cmd));
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
return;
}
for (Uint32 i = 0; i < cmd.InstanceCount; ++i) {
renderer->DrawFromState(mode, static_cast<GLint>(cmd.First), static_cast<GLsizei>(cmd.Count),
0, nullptr);
}
}
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
return;
}
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
if (indexSize == 0) {
return;
}
const auto* bytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawElementsIndirectCommand),
"DrawElementsIndirect");
if (bytes == nullptr) {
return;
}
DrawElementsIndirectCommand cmd{};
std::memcpy(&cmd, bytes, sizeof(cmd));
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
return;
}
const void* indices = reinterpret_cast<const void*>(static_cast<SizeT>(cmd.FirstIndex) * indexSize);
for (Uint32 i = 0; i < cmd.InstanceCount; ++i) {
renderer->DrawFromState(mode, 0, static_cast<GLsizei>(cmd.Count), type, indices,
static_cast<GLint>(cmd.BaseVertex));
}
}
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || drawcount <= 0) {
return;
}
const GLsizei realStride = stride == 0 ? static_cast<GLsizei>(sizeof(DrawArraysIndirectCommand)) : stride;
for (GLsizei i = 0; i < drawcount; ++i) {
const auto* bytes = ResolveIndirectCommandBytes(
static_cast<const Uint8*>(indirect) + static_cast<SizeT>(i) * static_cast<SizeT>(realStride),
sizeof(DrawArraysIndirectCommand), "MultiDrawArraysIndirect");
if (bytes == nullptr) {
continue;
}
DrawArraysIndirectCommand cmd{};
std::memcpy(&cmd, bytes, sizeof(cmd));
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
continue;
}
for (Uint32 instance = 0; instance < cmd.InstanceCount; ++instance) {
renderer->DrawFromState(mode, static_cast<GLint>(cmd.First),
static_cast<GLsizei>(cmd.Count), 0, nullptr);
}
}
}
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
GLsizei stride) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || drawcount <= 0) {
return;
}
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
if (indexSize == 0) {
return;
}
const GLsizei realStride = stride == 0 ? static_cast<GLsizei>(sizeof(DrawElementsIndirectCommand)) : stride;
for (GLsizei i = 0; i < drawcount; ++i) {
const auto* bytes = ResolveIndirectCommandBytes(
static_cast<const Uint8*>(indirect) + static_cast<SizeT>(i) * static_cast<SizeT>(realStride),
sizeof(DrawElementsIndirectCommand), "MultiDrawElementsIndirect");
if (bytes == nullptr) {
continue;
}
DrawElementsIndirectCommand cmd{};
std::memcpy(&cmd, bytes, sizeof(cmd));
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
continue;
}
const void* indices = reinterpret_cast<const void*>(static_cast<SizeT>(cmd.FirstIndex) * indexSize);
for (Uint32 instance = 0; instance < cmd.InstanceCount; ++instance) {
renderer->DrawFromState(mode, 0, static_cast<GLsizei>(cmd.Count), type, indices,
static_cast<GLint>(cmd.BaseVertex));
}
}
}
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
if (MG_State::pGLContext == nullptr) {
return;
}
auto paramBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
if (!paramBuffer) {
return;
}
paramBuffer->SyncPersistentMappedRange();
const Uint8* paramData = paramBuffer->MappedData();
if (paramData == nullptr) {
return;
}
Uint32 actualDrawCount = 0;
std::memcpy(&actualDrawCount, paramData + static_cast<SizeT>(drawcount), sizeof(actualDrawCount));
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
MultiDrawArraysIndirect(mode, indirect, static_cast<GLsizei>(actualDrawCount), stride);
}
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect,
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) {
if (MG_State::pGLContext == nullptr) {
return;
}
auto paramBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
if (!paramBuffer) {
return;
}
paramBuffer->SyncPersistentMappedRange();
const Uint8* paramData = paramBuffer->MappedData();
if (paramData == nullptr) {
return;
}
Uint32 actualDrawCount = 0;
std::memcpy(&actualDrawCount, paramData + static_cast<SizeT>(drawcount), sizeof(actualDrawCount));
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
MultiDrawElementsIndirect(mode, type, indirect, static_cast<GLsizei>(actualDrawCount), stride);
}
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || instancecount <= 0) {
return;
}
for (GLsizei i = 0; i < instancecount; ++i) {
renderer->DrawFromState(mode, first, count, 0, nullptr);
}
}
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
GLuint baseinstance) {
(void)baseinstance;
DrawArraysInstanced(mode, first, count, instancecount);
}
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || instancecount <= 0) {
return;
}
for (GLsizei i = 0; i < instancecount; ++i) {
renderer->DrawFromState(mode, 0, count, type, indices);
}
}
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || instancecount <= 0) {
return;
}
for (GLsizei i = 0; i < instancecount; ++i) {
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
}
}
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLuint baseinstance) {
(void)baseinstance;
DrawElementsInstanced(mode, count, type, indices, instancecount);
}
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type,
const void* indices, GLsizei instancecount,
GLint basevertex, GLuint baseinstance) {
(void)baseinstance;
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || instancecount <= 0) {
return;
}
for (GLsizei i = 0; i < instancecount; ++i) {
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
}
}
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || value == nullptr) {
return;
}
if (buffer == GL_COLOR && drawbuffer == 0) {
renderer->Clear(value[0], value[1], value[2], value[3]);
} else if (buffer == GL_DEPTH && drawbuffer == 0) {
renderer->ClearDepth(value[0]);
}
}
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {
if (value == nullptr) {
return;
}
if (buffer == GL_STENCIL) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->ClearStencil(static_cast<Uint32>(value[0]));
}
return;
}
Float color[4] = {
static_cast<Float>(value[0]) / 255.0f,
static_cast<Float>(value[1]) / 255.0f,
static_cast<Float>(value[2]) / 255.0f,
static_cast<Float>(value[3]) / 255.0f,
};
ClearBufferfv(buffer, drawbuffer, color);
}
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {
if (value == nullptr) {
return;
}
if (buffer == GL_STENCIL) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->ClearStencil(value[0]);
}
return;
}
Float color[4] = {
static_cast<Float>(value[0]) / 255.0f,
static_cast<Float>(value[1]) / 255.0f,
static_cast<Float>(value[2]) / 255.0f,
static_cast<Float>(value[3]) / 255.0f,
};
ClearBufferfv(buffer, drawbuffer, color);
}
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || buffer != GL_DEPTH_STENCIL) {
return;
}
(void)drawbuffer;
renderer->ClearDepth(depth);
renderer->ClearStencil(static_cast<Uint32>(stencil));
}
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || pixels == nullptr) {
return;
}
// The Diligent backend's offscreen targets are RGBA8; the frontend currently
// uses this entry for the common GL_RGBA/GL_UNSIGNED_BYTE readback path.
if (format != GL_RGBA || type != GL_UNSIGNED_BYTE) {
return;
}
renderer->ReadPixels(static_cast<Uint32>(x), static_cast<Uint32>(y),
static_cast<Uint32>(width), static_cast<Uint32>(height), pixels);
}
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1,
mask, filter);
}
}
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
(void)srcX0;
(void)srcY0;
(void)srcX1;
(void)srcY1;
(void)dstX0;
(void)dstY0;
(void)dstX1;
(void)dstY1;
(void)filter;
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->BlitNamedFramebuffer(readFramebuffer, drawFramebuffer, mask);
}
}
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y,
GLsizei width, GLsizei height, GLint border) {
(void)level;
(void)internalformat;
(void)x;
(void)y;
(void)width;
(void)height;
(void)border;
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
return;
}
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
if (texture) {
renderer->CopyReadFramebufferToTexture(*texture);
}
}
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height) {
(void)level;
(void)xoffset;
(void)yoffset;
(void)x;
(void)y;
(void)width;
(void)height;
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
return;
}
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
if (texture) {
renderer->CopyReadFramebufferToTexture(*texture);
}
}
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D ||
format != GL_RGBA || type != GL_UNSIGNED_BYTE || pixels == nullptr) {
return;
}
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
if (texture) {
renderer->ReadTextureImage(*texture, static_cast<Uint32>(level), pixels);
}
}
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
GLsizei bufSize, GLvoid* pixels) {
(void)uploadTarget;
(void)bufSize;
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || !texture || format != GL_RGBA || type != GL_UNSIGNED_BYTE ||
pixels == nullptr) {
return;
}
renderer->ReadTextureImage(*texture, static_cast<Uint32>(level), pixels);
}
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
(void)srcTarget;
(void)srcLevel;
(void)srcX;
(void)srcY;
(void)srcZ;
(void)dstTarget;
(void)dstLevel;
(void)dstX;
(void)dstY;
(void)dstZ;
(void)srcWidth;
(void)srcHeight;
(void)srcDepth;
auto* renderer = GetActiveRenderer();
if (renderer != nullptr && srcTexture && dstTexture) {
renderer->CopyTextureSubData(*srcTexture, *dstTexture);
}
}
void GenerateMipmap(GLenum target) {
auto* renderer = GetActiveRenderer();
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
return;
}
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
if (texture) {
renderer->GenerateMipmap(*texture);
}
}
Bool IsTimerQuerySupported() {
return true;
}
BackendQueryHandle BeginTimeElapsedQuery() {
auto* query = new CpuTimerQuery;
query->Start = std::chrono::steady_clock::now();
query->Available = false;
return query;
}
void EndTimeElapsedQuery(BackendQueryHandle query) {
if (query == nullptr) {
return;
}
auto* cpuQuery = static_cast<CpuTimerQuery*>(query);
const auto now = std::chrono::steady_clock::now();
cpuQuery->TimestampNs = static_cast<Uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(now - cpuQuery->Start).count());
cpuQuery->Available = true;
}
BackendQueryHandle QueryCounterTimestamp() {
auto* query = new CpuTimerQuery;
query->TimestampNs = static_cast<Uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
query->Available = true;
return query;
}
Bool IsQueryResultAvailable(BackendQueryHandle query) {
return query != nullptr && static_cast<CpuTimerQuery*>(query)->Available;
}
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds) {
if (query == nullptr || outNanoseconds == nullptr) {
return false;
}
auto* cpuQuery = static_cast<CpuTimerQuery*>(query);
if (!cpuQuery->Available && !wait) {
return false;
}
*outNanoseconds = cpuQuery->TimestampNs;
return true;
}
void DeleteBackendQuery(BackendQueryHandle query) {
delete static_cast<CpuTimerQuery*>(query);
}
BackendSyncHandle FenceSync() {
// CPU fallback fence: always signaled is a valid implementation for a
// backend without native sync primitives. The handle still round-trips
// through ClientWaitSync/DeleteSync so frontend state stays balanced.
return new int(0);
}
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout) {
(void)sync;
(void)flags;
(void)timeout;
return GL_ALREADY_SIGNALED;
}
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout) {
(void)sync;
(void)flags;
(void)timeout;
}
void DeleteSync(BackendSyncHandle sync) {
delete static_cast<int*>(sync);
}
Bool GetSyncStatus(BackendSyncHandle sync) {
(void)sync;
return true;
}
void SetSwapInterval(Int interval) {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->SetSwapInterval(interval > 0 ? static_cast<Uint32>(interval) : 0);
}
}
void Present() {
auto* renderer = GetActiveRenderer();
if (renderer != nullptr) {
renderer->Present();
}
}
} // namespace
BackendObject_Diligent::BackendObject_Diligent()
: m_rendererInfo(BuildInitialRendererInfo()) {}
BackendObject_Diligent::~BackendObject_Diligent() {
m_pRenderer.reset();
m_pContext.Release();
m_pDevice.Release();
m_pFactoryVk = nullptr;
}
Bool BackendObject_Diligent::CreateDiligentDevice() {
if (m_pDevice && m_pContext) {
return true;
}
try {
if (m_pFactoryVk == nullptr) {
m_pFactoryVk = ::Diligent::GetEngineFactoryVk();
if (m_pFactoryVk == nullptr) {
MGLOG_E("Diligent: failed to load Vulkan engine factory");
return false;
}
m_pFactoryVk->SetBreakOnError(false);
}
::Diligent::Uint32 numAdapters = 0;
m_pFactoryVk->EnumerateAdapters(::Diligent::Version{}, numAdapters, nullptr);
if (numAdapters == 0) {
MGLOG_W("Diligent: no Vulkan adapters available; skipping device creation");
return false;
}
::Diligent::EngineVkCreateInfo engineCI;
::Diligent::ImmediateContextCreateInfo ctxCI;
ctxCI.Name = "MobileGL Diligent Main Context";
ctxCI.QueueId = 0;
ctxCI.Priority = ::Diligent::QUEUE_PRIORITY_MEDIUM;
engineCI.NumImmediateContexts = 1;
engineCI.pImmediateContextInfo = &ctxCI;
::Diligent::IRenderDevice* pDevice = nullptr;
::Diligent::IDeviceContext* pContext = nullptr;
m_pFactoryVk->CreateDeviceAndContextsVk(engineCI, &pDevice, &pContext);
if (pDevice == nullptr || pContext == nullptr) {
MGLOG_E("Diligent: failed to create Vulkan device/context");
return false;
}
m_pDevice.Attach(pDevice);
m_pContext.Attach(pContext);
MGLOG_I("Diligent: Vulkan device created");
return true;
} catch (const std::exception& e) {
MGLOG_W("Diligent: Vulkan device creation failed: %s", e.what());
return false;
} catch (...) {
MGLOG_W("Diligent: Vulkan device creation failed");
return false;
}
}
void BackendObject_Diligent::Initialize() {
if (m_initialized) {
return;
}
if (!CreateDiligentDevice()) {
MGLOG_W("Diligent: backend initialization failed");
return;
}
m_pRenderer = std::make_unique<DiligentRenderer>(m_pDevice, m_pContext);
if (!m_pRenderer->Initialize(256, 256)) {
MGLOG_W("Diligent: renderer initialization failed");
m_pRenderer.reset();
return;
}
m_functions.GL.Clear = Clear;
m_functions.GL.DrawArrays = DrawArrays;
m_functions.GL.DrawElements = DrawElements;
m_functions.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
m_functions.GL.DrawRangeElements = DrawRangeElements;
m_functions.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
m_functions.GL.MultiDrawArrays = MultiDrawArrays;
m_functions.GL.MultiDrawElements = MultiDrawElements;
m_functions.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
m_functions.GL.DrawArraysInstanced = DrawArraysInstanced;
m_functions.GL.DrawArraysInstancedBaseInstance = DrawArraysInstancedBaseInstance;
m_functions.GL.DrawElementsInstanced = DrawElementsInstanced;
m_functions.GL.DrawElementsInstancedBaseVertex = DrawElementsInstancedBaseVertex;
m_functions.GL.DrawElementsInstancedBaseInstance = DrawElementsInstancedBaseInstance;
m_functions.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
m_functions.GL.DrawArraysIndirect = DrawArraysIndirect;
m_functions.GL.DrawElementsIndirect = DrawElementsIndirect;
m_functions.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
m_functions.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
m_functions.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
m_functions.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
m_functions.GL.ClearBufferfv = ClearBufferfv;
m_functions.GL.ClearBufferfi = ClearBufferfi;
m_functions.GL.ClearBufferiv = ClearBufferiv;
m_functions.GL.ClearBufferuiv = ClearBufferuiv;
m_functions.GL.BlitFramebuffer = BlitFramebuffer;
m_functions.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
m_functions.GL.CopyTexImage2D = CopyTexImage2D;
m_functions.GL.CopyTexSubImage2D = CopyTexSubImage2D;
m_functions.GL.CopyImageSubData = CopyImageSubData;
m_functions.GL.GenerateMipmap = GenerateMipmap;
m_functions.GL.GetTexImage = GetTexImage;
m_functions.GL.GetTextureImage = GetTextureImage;
m_functions.GL.ReadPixels = ReadPixels;
m_functions.GL.FenceSync = FenceSync;
m_functions.GL.ClientWaitSync = ClientWaitSync;
m_functions.GL.WaitSync = WaitSync;
m_functions.GL.DeleteSync = DeleteSync;
m_functions.GL.GetSyncStatus = GetSyncStatus;
m_functions.GL.IsTimerQuerySupported = IsTimerQuerySupported;
m_functions.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
m_functions.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
m_functions.GL.QueryCounterTimestamp = QueryCounterTimestamp;
m_functions.GL.IsQueryResultAvailable = IsQueryResultAvailable;
m_functions.GL.GetQueryResult64 = GetQueryResult64;
m_functions.GL.DeleteBackendQuery = DeleteBackendQuery;
m_functions.Present = Present;
m_functions.SetSwapInterval = SetSwapInterval;
m_initialized = true;
}
DiligentRenderer* BackendObject_Diligent::GetRenderer() {
return m_pRenderer.get();
}
Bool BackendObject_Diligent::InitCapabilities() {
// Skeleton: no format probing yet. The backend advertises GL 3.2 core
// capability, and the capability tables will be filled as resource
// creation paths are ported.
m_backendCapabilitiesInitialized = true;
return true;
}
Bool BackendObject_Diligent::InitWindowSurface() {
if (!m_windowHandle.Handle) {
MGLOG_E("BackendObject_Diligent::InitWindowSurface failed: native window handle is null");
return false;
}
if (m_pRenderer == nullptr || m_pFactoryVk == nullptr) {
MGLOG_E("BackendObject_Diligent::InitWindowSurface failed: renderer/factory is not ready");
return false;
}
return m_pRenderer->CreateSwapChain(m_pFactoryVk, m_windowHandle,
m_windowHandle.Width, m_windowHandle.Height);
}
Bool BackendObject_Diligent::InitPbufferSurface(EGLint width, EGLint height) {
// The Diligent backend keeps its offscreen target for pbuffer EGL surfaces.
// A future enhancement can resize/recreate the offscreen target to match the
// pbuffer dimensions.
(void)width;
(void)height;
return m_pRenderer != nullptr;
}
void BackendObject_Diligent::ReleaseEGLResources() {
if (m_pRenderer != nullptr) {
m_pRenderer->ReleaseSwapChain();
}
BackendObject::ReleaseEGLResources();
}
void BackendObject_Diligent::OnEGLSurfaceReleased(EGLSurface surface) {
(void)surface;
if (m_pRenderer != nullptr) {
m_pRenderer->ReleaseSwapChain();
}
}
Bool BackendObject_Diligent::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (!m_initialized) {
MGLOG_E("BackendObject_Diligent::CreateEGLWindowSurface failed: backend not initialized");
return false;
}
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32)) {
MGLOG_E("BackendObject_Diligent::CreateEGLWindowSurface failed: unsupported native window backend");
return false;
}
return RegisterEGLWindowSurface(surface, handle);
}
Bool BackendObject_Diligent::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (!m_initialized) {
MGLOG_E("BackendObject_Diligent::CreateEGLPbufferSurface failed: backend not initialized");
return false;
}
return RegisterEGLPbufferSurface(surface, width, height);
}
Bool BackendObject_Diligent::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (!BackendObject::ResizeEGLWindowSurface(surface, width, height)) {
return false;
}
if (m_eglSurface == surface && m_pRenderer != nullptr) {
return m_pRenderer->ResizeSwapChain(width, height);
}
return true;
}
const RendererInfo& BackendObject_Diligent::GetRendererInfo() const {
return m_rendererInfo;
}
String BackendObject_Diligent::GetBackendAPIVersionString() const {
return "Diligent Vulkan 0.1 (GL 3.2 skeleton)";
}
const GlobalBackendFunctionsTable& BackendObject_Diligent::GetBackendFunctions() const {
return m_functions;
}
const DynamicBackendParameters& BackendObject_Diligent::GetDynamicParameters() const {
return m_dynamicParameters;
}
BackendType BackendObject_Diligent::GetBackendType() const {
return BackendType::DiligentVulkan;
}
} // namespace MobileGL::MG_Backend::DiligentBackend
@@ -0,0 +1,72 @@
// MobileGL - MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h
// Copyright (c) 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
#pragma once
#include <Includes.h>
#include "../BackendObject.h"
// X11 (pulled in by Includes.h through Vulkan-Headers) defines True/False as
// macros, which collide with Diligent's Bool constants in BasicTypes.h.
#if defined(True)
#undef True
#endif
#if defined(False)
#undef False
#endif
#include <RefCntAutoPtr.hpp>
namespace Diligent {
struct IEngineFactoryVk;
struct IRenderDevice;
struct IDeviceContext;
}
namespace MobileGL::MG_Backend::DiligentBackend {
class DiligentRenderer;
// New Diligent/Vulkan backend, implemented from scratch on top of
// DiligentCore. The backend object owns the Diligent device/context and
// currently advertises OpenGL 3.2 core capability; the GL function table
// is intentionally empty until drawing/resource paths are ported.
class BackendObject_Diligent : public BackendObject {
public:
BackendObject_Diligent();
~BackendObject_Diligent() override;
void Initialize() override;
Bool InitCapabilities() override;
Bool InitWindowSurface() override;
Bool InitPbufferSurface(EGLint width, EGLint height) override;
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
void OnEGLSurfaceReleased(EGLSurface surface) override;
const RendererInfo& GetRendererInfo() const override;
String GetBackendAPIVersionString() const override;
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
const DynamicBackendParameters& GetDynamicParameters() const override;
BackendType GetBackendType() const override;
void ReleaseEGLResources() override;
DiligentRenderer* GetRenderer();
private:
Bool CreateDiligentDevice();
RendererInfo m_rendererInfo;
DynamicBackendParameters m_dynamicParameters;
GlobalBackendFunctionsTable m_functions{};
::Diligent::IEngineFactoryVk* m_pFactoryVk = nullptr;
::Diligent::RefCntAutoPtr<::Diligent::IRenderDevice> m_pDevice;
::Diligent::RefCntAutoPtr<::Diligent::IDeviceContext> m_pContext;
std::unique_ptr<DiligentRenderer> m_pRenderer;
Bool m_initialized = false;
};
} // namespace MobileGL::MG_Backend::DiligentBackend
@@ -0,0 +1,8 @@
// MobileGL - MobileGL/MG_Backend/Diligent/DiligentVulkan.cpp
// Copyright (c) 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
#include "DiligentVulkan.h"
@@ -0,0 +1,17 @@
// MobileGL - MobileGL/MG_Backend/Diligent/DiligentVulkan.h
// Copyright (c) 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
#pragma once
#include <Includes.h>
namespace MobileGL::MG_Backend::DiligentBackend {
// Backend identity string used by the backend object and local smoke tests.
inline String GetDiligentVulkanBackendName() {
return "DiligentVulkan";
}
} // namespace MobileGL::MG_Backend::DiligentBackend
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
// MobileGL - MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h
// Copyright (c) 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
#pragma once
#include <Includes.h>
// X11 (pulled in by Includes.h through Vulkan-Headers) defines True/False as
// macros, which collide with Diligent's Bool constants in BasicTypes.h.
#if defined(True)
#undef True
#endif
#if defined(False)
#undef False
#endif
#include <RefCntAutoPtr.hpp>
namespace MobileGL::MG_Backend {
struct WindowHandle;
}
namespace Diligent {
struct IRenderDevice;
struct IDeviceContext;
struct ITexture;
struct ITextureView;
struct IPipelineState;
struct IBuffer;
struct ISampler;
struct IShaderResourceBinding;
struct ISwapChain;
struct IEngineFactoryVk;
}
namespace MobileGL::MG_State::GLState {
class ITextureObject;
class SamplerObject;
class ProgramObject;
class RenderbufferObject;
class FramebufferObject;
}
namespace MobileGL::MG_Backend::DiligentBackend {
// Minimal real Diligent renderer used to prove the GL 3.2 basic path:
// clear an offscreen color target, draw a hardcoded triangle, and read
// pixels back. This is the first concrete rendering layer on top of the
// Diligent device; it will be expanded into the full MobileGL backend.
class DiligentRenderer {
public:
DiligentRenderer(::Diligent::IRenderDevice* device, ::Diligent::IDeviceContext* context);
~DiligentRenderer();
Bool Initialize(Uint32 width, Uint32 height);
void Clear(Float r, Float g, Float b, Float a);
void ClearDepth(Float depth);
void ClearStencil(Uint32 stencil);
void DrawTriangle();
void DrawVertices(const Float* vertices, Uint32 vertexCount);
// Creates a real Diligent swap chain for a native EGL window surface.
Bool CreateSwapChain(::Diligent::IEngineFactoryVk* factory, const WindowHandle& handle,
Uint32 width, Uint32 height);
Bool ResizeSwapChain(Uint32 width, Uint32 height);
void SetSwapInterval(Uint32 interval);
// Creates a simple 2D RGBA8 texture from CPU data and makes it available
// to state PSOs under the shader variable name "g_Texture".
Bool CreateTestTexture(const void* data, Uint32 width, Uint32 height);
// Draws using the live MG_State GL context: current program, VAO and
// bound buffers. This is the front-end emulation entry point.
void DrawFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices,
GLint baseVertex = 0);
void ReadPixels(Uint32 x, Uint32 y, Uint32 width, Uint32 height, void* pixels);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
GLbitfield mask);
void CopyReadFramebufferToTexture(MG_State::GLState::ITextureObject& dst);
void CopyTextureSubData(MG_State::GLState::ITextureObject& src, MG_State::GLState::ITextureObject& dst);
void GenerateMipmap(MG_State::GLState::ITextureObject& texture);
Bool ReadTextureImage(MG_State::GLState::ITextureObject& texture, Uint32 level, void* pixels);
void ReleaseSwapChain();
void Present();
::Diligent::IRenderDevice* GetDevice() const { return m_pDevice; }
::Diligent::IDeviceContext* GetContext() const { return m_pContext; }
private:
struct TextureResource {
::Diligent::RefCntAutoPtr<::Diligent::ITexture> Texture;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> SRV;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> RTV;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> DSV;
Uint64 ContentVersion = 0;
Uint16 ParamsVersion = 0;
Bool IsDepth = false;
};
struct SamplerResource {
::Diligent::RefCntAutoPtr<::Diligent::ISampler> Sampler;
Uint16 Version = 0;
};
Bool CreateOffscreenTargets();
Bool CreatePipeline();
Bool CreateVertexBuffer();
Bool CreatePipelineFromState(GLenum mode);
Bool UploadVertexDataFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices,
GLint baseVertex = 0);
::Diligent::ITextureView* SyncTexture(MG_State::GLState::ITextureObject& texture);
::Diligent::ITextureView* SyncTextureForAttachment(MG_State::GLState::ITextureObject& texture, Bool depth);
::Diligent::ITextureView* SyncRenderbuffer(MG_State::GLState::RenderbufferObject& renderbuffer);
::Diligent::ISampler* SyncSampler(const MG_State::GLState::SamplerObject& sampler);
Bool BindShaderResourcesFromState(const MG_State::GLState::ProgramObject& program);
Bool UploadUBOFromState(const MG_State::GLState::ProgramObject& program);
Bool ResolveCurrentRenderTargets(Vector<::Diligent::ITextureView*>& rtvs,
::Diligent::ITextureView*& dsv);
::Diligent::IRenderDevice* m_pDevice = nullptr;
::Diligent::IDeviceContext* m_pContext = nullptr;
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pColorTarget;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pColorRTV;
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pDepthTarget;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pDepthDSV;
::Diligent::RefCntAutoPtr<::Diligent::ISwapChain> m_pSwapChain;
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pTestTexture;
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pTestSRV;
::Diligent::RefCntAutoPtr<::Diligent::ISampler> m_pTestSampler;
::Diligent::RefCntAutoPtr<::Diligent::IShaderResourceBinding> m_pStateSRB;
::Diligent::RefCntAutoPtr<::Diligent::IPipelineState> m_pPSO;
::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pVertexBuffer;
::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pUBO;
Uint32 m_uboSize = 0;
Uint32 m_uboContentVersion = 0;
Uint64 m_uboProgramLifetimeId = 0;
UnorderedMap<Uint64, TextureResource> m_textureCache;
UnorderedMap<Uint64, SamplerResource> m_samplerCache;
UnorderedMap<Uint32, TextureResource> m_renderbufferCache;
UnorderedMap<Uint64, ::Diligent::RefCntAutoPtr<::Diligent::IBuffer>> m_namedUboCache;
Uint32 m_width = 256;
Uint32 m_height = 256;
Uint32 m_swapInterval = 0;
Uint32 m_lastDrawVertexCount = 0;
Uint64 m_lastPSOKey = 0;
Bool m_hasCachedPSO = false;
Bool m_initialized = false;
};
} // namespace MobileGL::MG_Backend::DiligentBackend
+8
View File
@@ -10,6 +10,9 @@
#include <Config.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
#if defined(MOBILEGL_ENABLE_DILIGENT)
#include <MG_Backend/Diligent/BackendObject_Diligent.h>
#endif
namespace MobileGL::MG_Backend {
void LogBackendInfo() {
@@ -55,6 +58,11 @@ namespace MobileGL::MG_Backend {
case BackendType::DirectVulkan:
pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
break;
#if defined(MOBILEGL_ENABLE_DILIGENT)
case BackendType::DiligentVulkan:
pActiveBackendObject = MakeUnique<DiligentBackend::BackendObject_Diligent>();
break;
#endif
case BackendType::Unknown:
default:
MGLOG_W("Unknown backend type, defaulting to unknown backend");
+10 -10
View File
@@ -175,14 +175,14 @@ namespace MobileGL::MG_Impl::GLXImpl {
struct ContextObject {
Display* XDisplay = nullptr;
EGLDisplay Display = EGL_NO_DISPLAY;
EGLDisplay Dpy = EGL_NO_DISPLAY;
EGLConfig Config = nullptr;
EGLContext Context = EGL_NO_CONTEXT;
const FBConfigInfo* FBConfig = nullptr;
};
struct DrawableSurface {
EGLDisplay Display = EGL_NO_DISPLAY;
EGLDisplay Dpy = EGL_NO_DISPLAY;
EGLSurface Surface = EGL_NO_SURFACE;
Uint32 Width = 0;
Uint32 Height = 0;
@@ -294,7 +294,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
if (width == surface.Width && height == surface.Height) {
return;
}
if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface,
if (EGLImpl::ResizePlatformWindowSurface(surface.Dpy, surface.Surface,
static_cast<EGLint>(width),
static_cast<EGLint>(height))) {
surface.Width = width;
@@ -324,7 +324,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
EGL_NONE,
};
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(
context.Display, context.Config, reinterpret_cast<void*>(drawable), attribs);
context.Dpy, context.Config, reinterpret_cast<void*>(drawable), attribs);
if (surface == EGL_NO_SURFACE) {
MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable,
width, height);
@@ -332,7 +332,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
}
DrawableSurface record;
record.Display = context.Display;
record.Dpy = context.Dpy;
record.Surface = surface;
record.Width = width;
record.Height = height;
@@ -388,7 +388,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
ContextObject object;
object.XDisplay = dpy;
object.Display = display;
object.Dpy = display;
object.Config = config;
object.Context = eglContext;
object.FBConfig = fbconfig;
@@ -895,7 +895,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
return;
}
if (object->Context != EGL_NO_CONTEXT) {
EGLImpl::DestroyContext(object->Display, object->Context);
EGLImpl::DestroyContext(object->Dpy, object->Context);
}
Contexts().erase(context);
}
@@ -929,7 +929,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
return 0;
}
if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface,
if (!EGLImpl::MakeCurrent(object->Dpy, surface->Surface, surface->Surface,
object->Context)) {
MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context);
return 0;
@@ -962,7 +962,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
return;
}
SyncSurfaceSize(dpy, drawable, it->second);
EGLImpl::SwapBuffers(it->second.Display, it->second.Surface);
EGLImpl::SwapBuffers(it->second.Dpy, it->second.Surface);
}
GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window,
@@ -988,7 +988,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
if (it == surfaces.end()) {
return;
}
EGLImpl::DestroySurface(it->second.Display, it->second.Surface);
EGLImpl::DestroySurface(it->second.Dpy, it->second.Surface);
surfaces.erase(it);
}
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.14)
message(STATUS "Generating build files for MobileGL Diligent Backend Test...")
add_executable(
DiligentVulkanSanityTest
SanityTest.cpp
)
target_include_directories(DiligentVulkanSanityTest PRIVATE
${MGL_ROOT}/include
${MGL_ROOT}/MobileGL
)
target_link_libraries(
DiligentVulkanSanityTest PRIVATE
GTest::gtest_main
${LINK_LIBRARIES}
)
include(GoogleTest)
gtest_discover_tests(DiligentVulkanSanityTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS integration)
File diff suppressed because it is too large Load Diff
+3
View File
@@ -84,3 +84,6 @@ add_subdirectory(Backend/DirectGLES)
if (ENABLE_INTEGRATION_TESTS)
add_subdirectory(Backend/DirectVulkan)
endif()
if (MOBILEGL_ENABLE_DILIGENT)
add_subdirectory(Backend/Diligent)
endif()
+1 -1
View File
@@ -86,7 +86,7 @@ val pluginRendererConfig = buildJsonValue {
selectable(
key = "MOBILEGL_BACKEND_TYPE",
title = RendererConfig.MetaString("mobilegl_backend_type_title"),
items = RendererConfig.EnvItems("DirectGLES", listOf("DirectVulkan")),
items = RendererConfig.EnvItems("DirectGLES", listOf("DirectVulkan", "DiligentVulkan")),
)
toggleable("MOBILEGL_DISABLE_TIMERQUERY", "1", false, RendererConfig.MetaString("mobilegl_disable_timerquery_title"))
toggleable("MOBILEGL_DISABLE_SUBGROUP", "1", false, RendererConfig.MetaString("mobilegl_disable_subgroup_title"))