mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 22:58:30 +09:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24dfbb41f9 | ||
|
|
ca7878bf3a | ||
|
|
e418063b08 | ||
|
|
b43ec25bd7 | ||
|
|
525607bad6 | ||
|
|
b045024b6c | ||
|
|
14dfbeeed9 | ||
|
|
d7e79409b3 | ||
|
|
ca3b524396 | ||
|
|
071c8eb673 | ||
|
|
51a43518ac | ||
|
|
34ff95f6f5 | ||
|
|
b9d1504cc5 | ||
|
|
a223499143 | ||
|
|
5dca617f01 | ||
|
|
eb8ef893be | ||
|
|
20567fba6d | ||
|
|
e3f44e8da1 | ||
|
|
9f79a88af5 | ||
|
|
6bf32acdef | ||
|
|
23b53eacce | ||
|
|
e829e70d8b | ||
|
|
7e765e1535 | ||
|
|
bf6061811f | ||
|
|
87750c3b21 | ||
|
|
45b309db37 | ||
|
|
403c82ac4a | ||
|
|
827d46cad3 | ||
|
|
1748da0443 | ||
|
|
7efee8e3e6 | ||
|
|
08ca897a07 | ||
|
|
98f2a55214 | ||
|
|
cedc257566 | ||
|
|
821c0e0d4e | ||
|
|
bd9680ad67 | ||
|
|
6375e07030 | ||
|
|
53cac39d4e | ||
|
|
f6b1ea635b | ||
|
|
8b2711e32a | ||
|
|
9776cc8047 | ||
|
|
3b0591e0ba | ||
|
|
e62f158c22 | ||
|
|
7769156cfc | ||
|
|
0ecfdff4e7 | ||
|
|
6df5a6137f |
@@ -209,7 +209,7 @@ jobs:
|
|||||||
- name: Load trace cases
|
- name: Load trace cases
|
||||||
id: trace-cases
|
id: trace-cases
|
||||||
run: |
|
run: |
|
||||||
echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk)" >> "$GITHUB_OUTPUT"
|
echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk-matrix)" >> "$GITHUB_OUTPUT"
|
||||||
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
trace-fixtures:
|
trace-fixtures:
|
||||||
@@ -337,13 +337,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
matrix:
|
matrix: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||||
backend:
|
|
||||||
- name: DirectGLES
|
|
||||||
gpu: software
|
|
||||||
- name: DirectVulkan
|
|
||||||
gpu: lavapipe
|
|
||||||
case: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set Swap Space
|
- name: Set Swap Space
|
||||||
uses: pierotofy/set-swap-space@v1.0
|
uses: pierotofy/set-swap-space@v1.0
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ jobs:
|
|||||||
- benchmark
|
- benchmark
|
||||||
- integration
|
- integration
|
||||||
outputs:
|
outputs:
|
||||||
|
matrix: ${{ steps.trace-cases.outputs.matrix }}
|
||||||
names: ${{ steps.trace-cases.outputs.names }}
|
names: ${{ steps.trace-cases.outputs.names }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -498,7 +499,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Load trace cases
|
- name: Load trace cases
|
||||||
id: trace-cases
|
id: trace-cases
|
||||||
run: echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
run: |
|
||||||
|
echo "matrix=$(python3 tools/trace_replay/trace_cases.py --ci --format github-test-matrix)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
trace-fixtures:
|
trace-fixtures:
|
||||||
name: trace fixture (${{ matrix.case }})
|
name: trace fixture (${{ matrix.case }})
|
||||||
@@ -577,11 +580,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
matrix:
|
matrix: ${{ fromJSON(needs.trace-cases.outputs.matrix) }}
|
||||||
backend:
|
|
||||||
- DirectGLES
|
|
||||||
- DirectVulkan
|
|
||||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set Swap Space
|
- name: Set Swap Space
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
@@ -16,6 +16,9 @@ MobileGLCodeManager
|
|||||||
MobileGL/MG_Test/build
|
MobileGL/MG_Test/build
|
||||||
/build_*
|
/build_*
|
||||||
/cmake-build*
|
/cmake-build*
|
||||||
|
/build-*/
|
||||||
|
/local.properties
|
||||||
|
/.jspace/
|
||||||
.idea
|
.idea
|
||||||
MobileGL/MG*/build*
|
MobileGL/MG*/build*
|
||||||
MobileGL/MG*/cmake-build*
|
MobileGL/MG*/cmake-build*
|
||||||
|
|||||||
+42
-1
@@ -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_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
|
||||||
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
|
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
|
||||||
|
|
||||||
# add_subdirectory(3rdparty/DiligentCore)
|
|
||||||
add_subdirectory(3rdparty/glslang)
|
add_subdirectory(3rdparty/glslang)
|
||||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||||
@@ -211,6 +210,23 @@ set(XXHASH_BUILD_XXHSUM OFF)
|
|||||||
option(BUILD_SHARED_LIBS OFF)
|
option(BUILD_SHARED_LIBS OFF)
|
||||||
add_subdirectory(3rdparty/xxHash/build/cmake xxhash_build EXCLUDE_FROM_ALL)
|
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)
|
set(TRACY_ENABLE ${MOBILEGL_ENABLE_TRACY} CACHE BOOL "Enable Tracy, this is an internal variable" FORCE)
|
||||||
|
|
||||||
if (TRACY_ENABLE)
|
if (TRACY_ENABLE)
|
||||||
@@ -396,6 +412,14 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
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)
|
if (APPLE AND NOT MOBILEGL_IOS)
|
||||||
list(APPEND SOURCE_FILES
|
list(APPEND SOURCE_FILES
|
||||||
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||||
@@ -436,6 +460,21 @@ set(MOBILEGL_LINK_LIBRARIES
|
|||||||
Threads::Threads
|
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
|
set(MOBILEGL_COMPILE_DEF
|
||||||
-DVMA_STATIC_VULKAN_FUNCTIONS=0
|
-DVMA_STATIC_VULKAN_FUNCTIONS=0
|
||||||
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
||||||
@@ -501,6 +540,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME}
|
|||||||
${MOBILEGL_COMPILE_DEF}
|
${MOBILEGL_COMPILE_DEF}
|
||||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||||
$<$<BOOL:${MOBILEGL_TRACE_ANGLE_VARIANTS}>:MOBILEGL_TRACE_ANGLE_VARIANTS=1>
|
$<$<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)
|
if(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||||
@@ -559,6 +599,7 @@ if(NOT ANDROID)
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
${MOBILEGL_COMPILE_DEF}
|
${MOBILEGL_COMPILE_DEF}
|
||||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||||
|
$<$<BOOL:${MOBILEGL_ENABLE_DILIGENT}>:MOBILEGL_ENABLE_DILIGENT=1>
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -128,10 +128,6 @@ namespace MobileGL::MG_Config {
|
|||||||
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
|
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
|
||||||
// version request.
|
// version request.
|
||||||
Bool RelaxedSemantics = false;
|
Bool RelaxedSemantics = false;
|
||||||
// MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN: overrides the shader-source quirk that
|
|
||||||
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
|
||||||
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
|
||||||
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
|
||||||
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
|
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
|
||||||
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
||||||
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
features.EsprytForceDepthStencilReadbackEmulation =
|
features.EsprytForceDepthStencilReadbackEmulation =
|
||||||
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
||||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
|
||||||
features.MagmaDisableBlendedDepthWriteQuirk =
|
features.MagmaDisableBlendedDepthWriteQuirk =
|
||||||
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
||||||
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||||
@@ -203,6 +202,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
}
|
}
|
||||||
ENTRY(DirectGLES)
|
ENTRY(DirectGLES)
|
||||||
ENTRY(DirectVulkan)
|
ENTRY(DirectVulkan)
|
||||||
|
ENTRY(DiligentVulkan)
|
||||||
ENTRY(Unknown)
|
ENTRY(Unknown)
|
||||||
MG_Config::ActiveBackendType = BackendType::Unknown;
|
MG_Config::ActiveBackendType = BackendType::Unknown;
|
||||||
#undef ENTRY
|
#undef ENTRY
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace MobileGL {
|
|||||||
enum class BackendType {
|
enum class BackendType {
|
||||||
DirectGLES,
|
DirectGLES,
|
||||||
DirectVulkan,
|
DirectVulkan,
|
||||||
|
DiligentVulkan,
|
||||||
BackendTypeCount,
|
BackendTypeCount,
|
||||||
Unknown = -1
|
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
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "MG_Backend/BackendObject.h"
|
#include "MG_Backend/BackendObject.h"
|
||||||
#include "DirectVulkan.h"
|
#include "DirectVulkan.h"
|
||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
#include "MG_State/GLState/Core.h"
|
||||||
#include "MG_State/GLState/TextureState/TextureState.h"
|
#include "MG_State/GLState/TextureState/TextureState.h"
|
||||||
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
||||||
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
||||||
@@ -383,6 +384,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
UpdateAdvertisedExtensions();
|
UpdateAdvertisedExtensions();
|
||||||
|
if (MG_State::pGLContext) {
|
||||||
|
MG_State::pGLContext->InvalidateCompileEnv();
|
||||||
|
}
|
||||||
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||||
MutableFormatCapabilities());
|
MutableFormatCapabilities());
|
||||||
PrintFormatCapabilities(GetFormatCapabilities());
|
PrintFormatCapabilities(GetFormatCapabilities());
|
||||||
@@ -687,6 +691,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_vulkanCaps = capabilities;
|
m_vulkanCaps = capabilities;
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
UpdateAdvertisedExtensions();
|
UpdateAdvertisedExtensions();
|
||||||
|
if (MG_State::pGLContext) {
|
||||||
|
MG_State::pGLContext->InvalidateCompileEnv();
|
||||||
|
}
|
||||||
MutableFormatCapabilities().Clear();
|
MutableFormatCapabilities().Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.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 {
|
namespace MobileGL::MG_Backend {
|
||||||
void LogBackendInfo() {
|
void LogBackendInfo() {
|
||||||
@@ -55,6 +58,11 @@ namespace MobileGL::MG_Backend {
|
|||||||
case BackendType::DirectVulkan:
|
case BackendType::DirectVulkan:
|
||||||
pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
|
pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
|
||||||
break;
|
break;
|
||||||
|
#if defined(MOBILEGL_ENABLE_DILIGENT)
|
||||||
|
case BackendType::DiligentVulkan:
|
||||||
|
pActiveBackendObject = MakeUnique<DiligentBackend::BackendObject_Diligent>();
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case BackendType::Unknown:
|
case BackendType::Unknown:
|
||||||
default:
|
default:
|
||||||
MGLOG_W("Unknown backend type, defaulting to unknown backend");
|
MGLOG_W("Unknown backend type, defaulting to unknown backend");
|
||||||
|
|||||||
@@ -175,14 +175,14 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
|
|
||||||
struct ContextObject {
|
struct ContextObject {
|
||||||
Display* XDisplay = nullptr;
|
Display* XDisplay = nullptr;
|
||||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
EGLDisplay Dpy = EGL_NO_DISPLAY;
|
||||||
EGLConfig Config = nullptr;
|
EGLConfig Config = nullptr;
|
||||||
EGLContext Context = EGL_NO_CONTEXT;
|
EGLContext Context = EGL_NO_CONTEXT;
|
||||||
const FBConfigInfo* FBConfig = nullptr;
|
const FBConfigInfo* FBConfig = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawableSurface {
|
struct DrawableSurface {
|
||||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
EGLDisplay Dpy = EGL_NO_DISPLAY;
|
||||||
EGLSurface Surface = EGL_NO_SURFACE;
|
EGLSurface Surface = EGL_NO_SURFACE;
|
||||||
Uint32 Width = 0;
|
Uint32 Width = 0;
|
||||||
Uint32 Height = 0;
|
Uint32 Height = 0;
|
||||||
@@ -294,7 +294,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
if (width == surface.Width && height == surface.Height) {
|
if (width == surface.Width && height == surface.Height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface,
|
if (EGLImpl::ResizePlatformWindowSurface(surface.Dpy, surface.Surface,
|
||||||
static_cast<EGLint>(width),
|
static_cast<EGLint>(width),
|
||||||
static_cast<EGLint>(height))) {
|
static_cast<EGLint>(height))) {
|
||||||
surface.Width = width;
|
surface.Width = width;
|
||||||
@@ -324,7 +324,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
EGL_NONE,
|
EGL_NONE,
|
||||||
};
|
};
|
||||||
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(
|
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) {
|
if (surface == EGL_NO_SURFACE) {
|
||||||
MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable,
|
MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable,
|
||||||
width, height);
|
width, height);
|
||||||
@@ -332,7 +332,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DrawableSurface record;
|
DrawableSurface record;
|
||||||
record.Display = context.Display;
|
record.Dpy = context.Dpy;
|
||||||
record.Surface = surface;
|
record.Surface = surface;
|
||||||
record.Width = width;
|
record.Width = width;
|
||||||
record.Height = height;
|
record.Height = height;
|
||||||
@@ -388,7 +388,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
|
|
||||||
ContextObject object;
|
ContextObject object;
|
||||||
object.XDisplay = dpy;
|
object.XDisplay = dpy;
|
||||||
object.Display = display;
|
object.Dpy = display;
|
||||||
object.Config = config;
|
object.Config = config;
|
||||||
object.Context = eglContext;
|
object.Context = eglContext;
|
||||||
object.FBConfig = fbconfig;
|
object.FBConfig = fbconfig;
|
||||||
@@ -895,7 +895,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (object->Context != EGL_NO_CONTEXT) {
|
if (object->Context != EGL_NO_CONTEXT) {
|
||||||
EGLImpl::DestroyContext(object->Display, object->Context);
|
EGLImpl::DestroyContext(object->Dpy, object->Context);
|
||||||
}
|
}
|
||||||
Contexts().erase(context);
|
Contexts().erase(context);
|
||||||
}
|
}
|
||||||
@@ -929,7 +929,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface,
|
if (!EGLImpl::MakeCurrent(object->Dpy, surface->Surface, surface->Surface,
|
||||||
object->Context)) {
|
object->Context)) {
|
||||||
MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context);
|
MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -962,7 +962,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SyncSurfaceSize(dpy, drawable, it->second);
|
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,
|
GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window,
|
||||||
@@ -988,7 +988,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
|||||||
if (it == surfaces.end()) {
|
if (it == surfaces.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EGLImpl::DestroySurface(it->second.Display, it->second.Surface);
|
EGLImpl::DestroySurface(it->second.Dpy, it->second.Surface);
|
||||||
surfaces.erase(it);
|
surfaces.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ namespace MobileGL::MG_State {
|
|||||||
return m_compileEnv;
|
return m_compileEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLContext::InvalidateCompileEnv() {
|
||||||
|
m_compileEnv.reset();
|
||||||
|
m_compileEnvBackend = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Error
|
// Error
|
||||||
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
|
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
|
||||||
// Invariant I1, mechanically enforced: the GL error state is GL-thread-owned.
|
// Invariant I1, mechanically enforced: the GL error state is GL-thread-owned.
|
||||||
|
|||||||
@@ -413,9 +413,12 @@ namespace MobileGL {
|
|||||||
// cannot be captured in MG_State::Init() - that runs BEFORE MG_Backend::Init(),
|
// cannot be captured in MG_State::Init() - that runs BEFORE MG_Backend::Init(),
|
||||||
// so there is no backend to query yet. Re-captured whenever the active backend
|
// so there is no backend to query yet. Re-captured whenever the active backend
|
||||||
// object changes, which also rolls the fingerprint and therefore invalidates
|
// object changes, which also rolls the fingerprint and therefore invalidates
|
||||||
// every P0b preprocess memo keyed against the old one.
|
// every P0b preprocess memo keyed against the old one. A backend whose dynamic
|
||||||
|
// capabilities become available without changing object identity must call
|
||||||
|
// InvalidateCompileEnv() after publishing them.
|
||||||
// GL thread only.
|
// GL thread only.
|
||||||
const SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv>& GetCompileEnv();
|
const SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv>& GetCompileEnv();
|
||||||
|
void InvalidateCompileEnv();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// State Components
|
// State Components
|
||||||
|
|||||||
@@ -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
@@ -84,3 +84,6 @@ add_subdirectory(Backend/DirectGLES)
|
|||||||
if (ENABLE_INTEGRATION_TESTS)
|
if (ENABLE_INTEGRATION_TESTS)
|
||||||
add_subdirectory(Backend/DirectVulkan)
|
add_subdirectory(Backend/DirectVulkan)
|
||||||
endif()
|
endif()
|
||||||
|
if (MOBILEGL_ENABLE_DILIGENT)
|
||||||
|
add_subdirectory(Backend/Diligent)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -2073,153 +2073,6 @@ void main() {
|
|||||||
EXPECT_NE(source.find("layout(std140) uniform Blk"), String::npos);
|
EXPECT_NE(source.find("layout(std140) uniform Blk"), String::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
String MakeLinearSubgroupPrefixScanShader() {
|
|
||||||
return R"(#version 460 core
|
|
||||||
#extension GL_KHR_shader_subgroup_arithmetic : enable
|
|
||||||
layout(local_size_x = 1024) in;
|
|
||||||
shared float prefixSumCache[64];
|
|
||||||
|
|
||||||
layout(std430, binding = 0) writeonly buffer OutputBuffer {
|
|
||||||
float outputValues[];
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float importance = 1.0f;
|
|
||||||
float prefixSum = subgroupInclusiveAdd(importance);
|
|
||||||
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum;
|
|
||||||
barrier();
|
|
||||||
uint loopLength = uint(findMSB(gl_NumSubgroups));
|
|
||||||
loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u);
|
|
||||||
for (uint i = 0; i < loopLength; i++) {
|
|
||||||
if ((gl_SubgroupID & (1u << i)) > 0u) {
|
|
||||||
prefixSum += prefixSumCache[(gl_SubgroupID >> i << i) - 1u];
|
|
||||||
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum;
|
|
||||||
}
|
|
||||||
barrier();
|
|
||||||
}
|
|
||||||
if (gl_LocalInvocationID.x == uint(1024 - 1)) prefixSumCache[0] = prefixSum;
|
|
||||||
barrier();
|
|
||||||
float sum = prefixSumCache[0];
|
|
||||||
float warp = (prefixSum - importance) / sum - float(gl_LocalInvocationID.x + 1u) / float(1024);
|
|
||||||
outputValues[gl_GlobalInvocationID.x] = warp;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanUsesSharedMemoryAndProducesValidSpirv) {
|
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
|
||||||
|
|
||||||
String source = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
ASSERT_TRUE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
|
|
||||||
|
|
||||||
EXPECT_NE(source.find("shared float prefixSumCache[1024]"), String::npos) << source;
|
|
||||||
EXPECT_NE(source.find("mglVirtualSubgroupInvocation"), String::npos) << source;
|
|
||||||
EXPECT_NE(source.find("for (uint mglPrefixLane"), String::npos) << source;
|
|
||||||
EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source;
|
|
||||||
EXPECT_EQ(source.find("gl_Subgroup"), String::npos) << source;
|
|
||||||
|
|
||||||
const String onceRewritten = source;
|
|
||||||
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
|
|
||||||
EXPECT_EQ(source, onceRewritten);
|
|
||||||
|
|
||||||
ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source};
|
|
||||||
auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib);
|
|
||||||
ASSERT_TRUE(shaderResult) << shaderResult.error().log << "\nsource:\n" << source;
|
|
||||||
|
|
||||||
ProgramAttrib programAttrib{.shaders = {shaderResult.value()}};
|
|
||||||
auto programResult = ShaderCompiler::LinkProgram(programAttrib);
|
|
||||||
ASSERT_TRUE(programResult) << programResult.error().log;
|
|
||||||
|
|
||||||
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()};
|
|
||||||
auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
|
||||||
ASSERT_TRUE(binaryResult) << binaryResult.error().log;
|
|
||||||
ASSERT_EQ(binaryResult->size(), 1u);
|
|
||||||
|
|
||||||
String validationDiagnostics;
|
|
||||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
|
||||||
tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) {
|
|
||||||
validationDiagnostics += message;
|
|
||||||
validationDiagnostics += '\n';
|
|
||||||
});
|
|
||||||
EXPECT_TRUE(tools.Validate(binaryResult->front())) << validationDiagnostics;
|
|
||||||
|
|
||||||
String spirvText;
|
|
||||||
ASSERT_TRUE(tools.Disassemble(binaryResult->front(), &spirvText));
|
|
||||||
EXPECT_EQ(spirvText.find("OpGroupNonUniform"), String::npos) << spirvText;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsOtherStagesAndSubgroupWidths) {
|
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
|
||||||
|
|
||||||
const String original = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
for (const auto& [stage, subgroupSize] :
|
|
||||||
{std::pair{ShaderStage::Compute, Uint32{32}}, std::pair{ShaderStage::Fragment, Uint32{64}},
|
|
||||||
std::pair{ShaderStage::Compute, Uint32{96}}}) {
|
|
||||||
String source = original;
|
|
||||||
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(stage, subgroupSize, source));
|
|
||||||
EXPECT_EQ(source, original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsPartialOrUnsafeTemplateMatches) {
|
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
|
||||||
|
|
||||||
const auto expectUnchanged = [](String source) {
|
|
||||||
const String original = source;
|
|
||||||
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
|
|
||||||
EXPECT_EQ(source, original);
|
|
||||||
};
|
|
||||||
|
|
||||||
String wrongLocalSize = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
wrongLocalSize.replace(wrongLocalSize.find("local_size_x = 1024"), std::strlen("local_size_x = 1024"),
|
|
||||||
"local_size_x = 512");
|
|
||||||
expectUnchanged(std::move(wrongLocalSize));
|
|
||||||
|
|
||||||
String cacheHasAnotherUse = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
cacheHasAnotherUse.insert(cacheHasAnotherUse.find("float importance"), "prefixSumCache[0] = 0.0f;\n ");
|
|
||||||
expectUnchanged(std::move(cacheHasAnotherUse));
|
|
||||||
|
|
||||||
String extraSubgroupBuiltin = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
extraSubgroupBuiltin.insert(extraSubgroupBuiltin.find("float importance"),
|
|
||||||
"uvec4 extraMask = gl_SubgroupEqMask;\n ");
|
|
||||||
expectUnchanged(std::move(extraSubgroupBuiltin));
|
|
||||||
|
|
||||||
String alteredBarrier = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
alteredBarrier.replace(alteredBarrier.find("barrier();"), std::strlen("barrier();"), "memoryBarrierShared();");
|
|
||||||
expectUnchanged(std::move(alteredBarrier));
|
|
||||||
|
|
||||||
String nestedScan = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
nestedScan.insert(nestedScan.find("float prefixSum ="), "if (importance > 0.0f) {\n ");
|
|
||||||
const SizeT consumerEnd = nestedScan.find(';', nestedScan.find("float warp ="));
|
|
||||||
ASSERT_NE(consumerEnd, String::npos);
|
|
||||||
nestedScan.insert(consumerEnd + 1, "\n }");
|
|
||||||
expectUnchanged(std::move(nestedScan));
|
|
||||||
|
|
||||||
// ARB/NV spellings of lane-width-sensitive builtins must block the rewrite exactly
|
|
||||||
// like their KHR counterparts.
|
|
||||||
String arbSubgroupBuiltin = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
arbSubgroupBuiltin.insert(arbSubgroupBuiltin.find("float importance"),
|
|
||||||
"uint arbLane = gl_SubGroupInvocationARB;\n ");
|
|
||||||
expectUnchanged(std::move(arbSubgroupBuiltin));
|
|
||||||
|
|
||||||
String arbBallotCall = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
arbBallotCall.insert(arbBallotCall.find("float importance"),
|
|
||||||
"uint64_t arbMask = ballotARB(true);\n ");
|
|
||||||
expectUnchanged(std::move(arbBallotCall));
|
|
||||||
|
|
||||||
String nvWarpBuiltin = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
nvWarpBuiltin.insert(nvWarpBuiltin.find("float importance"),
|
|
||||||
"uint warpSize = gl_WarpSizeNV;\n ");
|
|
||||||
expectUnchanged(std::move(nvWarpBuiltin));
|
|
||||||
|
|
||||||
String nvShuffleCall = MakeLinearSubgroupPrefixScanShader();
|
|
||||||
nvShuffleCall.insert(nvShuffleCall.find("float importance"),
|
|
||||||
"float other = shuffleNV(1.0f, 0u, 32u);\n ");
|
|
||||||
expectUnchanged(std::move(nvShuffleCall));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The LEXICAL half must fire at the source level (before the parse) for the
|
// The LEXICAL half must fire at the source level (before the parse) for the
|
||||||
// preempt-list names - the end-to-end ESSL tests cannot tell which half did the
|
// preempt-list names - the end-to-end ESSL tests cannot tell which half did the
|
||||||
// rename, and for these names the parse would fail without the source rewrite.
|
// rename, and for these names the parse would fail without the source rewrite.
|
||||||
@@ -2448,9 +2301,6 @@ TEST_F(ProgramUtilTest, CompileEnvFingerprintTracksEveryInput) {
|
|||||||
otherExtensions.advertisedExtensions.push_back(MobileGL::E_GL_ARB_gpu_shader_int64);
|
otherExtensions.advertisedExtensions.push_back(MobileGL::E_GL_ARB_gpu_shader_int64);
|
||||||
EXPECT_NE(ComputeCompileEnvFingerprint(otherExtensions), baseline);
|
EXPECT_NE(ComputeCompileEnvFingerprint(otherExtensions), baseline);
|
||||||
|
|
||||||
CompileEnv otherQuirk = base;
|
|
||||||
otherQuirk.subgroupPrefixScanQuirk = MobileGL::MG_Config::QuirkOverride::ForceOn;
|
|
||||||
EXPECT_NE(ComputeCompileEnvFingerprint(otherQuirk), baseline);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The no-backend fallback must stay exactly what the pipeline used to do inline:
|
// The no-backend fallback must stay exactly what the pipeline used to do inline:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
|
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
|
||||||
#include <MG_Util/Math/HalfFloat.h>
|
#include <MG_Util/Math/HalfFloat.h>
|
||||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||||
|
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||||
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
|
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
|
||||||
#include <MG_Util/Debug/Log.h>
|
#include <MG_Util/Debug/Log.h>
|
||||||
#include <MG_Util/Types.h>
|
#include <MG_Util/Types.h>
|
||||||
@@ -710,6 +711,37 @@ TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) {
|
|||||||
EXPECT_TRUE(backend.GetDynamicParameters().SubgroupQuadOperationsInAllStages);
|
EXPECT_TRUE(backend.GetDynamicParameters().SubgroupQuadOperationsInAllStages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DirectVulkanSanity, CapabilityRefreshInvalidatesTheCachedCompileEnvironment) {
|
||||||
|
using namespace MobileGL;
|
||||||
|
|
||||||
|
auto previousContext = Move(MG_State::pGLContext);
|
||||||
|
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
|
||||||
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||||
|
|
||||||
|
auto backend = MakeUnique<MG_Backend::DirectVulkan::BackendObject_DirectVulkan>();
|
||||||
|
auto* backendPtr = backend.get();
|
||||||
|
MG_Backend::pActiveBackendObject = Move(backend);
|
||||||
|
|
||||||
|
const auto before = MG_State::pGLContext->GetCompileEnv();
|
||||||
|
EXPECT_EQ(before->params.SubgroupSize, 0u);
|
||||||
|
|
||||||
|
MG_External::VulkanCapabilities caps;
|
||||||
|
caps.SupportsShaderSubgroup = true;
|
||||||
|
caps.SubgroupSize = 8;
|
||||||
|
caps.SubgroupSupportedStages = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||||
|
caps.SubgroupSupportedOperations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT;
|
||||||
|
backendPtr->ApplyVulkanCapabilitiesForTesting(caps);
|
||||||
|
|
||||||
|
const auto after = MG_State::pGLContext->GetCompileEnv();
|
||||||
|
EXPECT_NE(after.get(), before.get());
|
||||||
|
EXPECT_NE(after->fingerprint, before->fingerprint);
|
||||||
|
EXPECT_EQ(after->backend, BackendType::DirectVulkan);
|
||||||
|
EXPECT_EQ(after->params.SubgroupSize, 8u);
|
||||||
|
|
||||||
|
MG_Backend::pActiveBackendObject = Move(previousBackend);
|
||||||
|
MG_State::pGLContext = Move(previousContext);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DirectVulkanSanity, KeepsOptionalGpuShaderInt64BranchForVoxyQuadDecode) {
|
TEST(DirectVulkanSanity, KeepsOptionalGpuShaderInt64BranchForVoxyQuadDecode) {
|
||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ namespace MobileGL {
|
|||||||
|
|
||||||
std::string GetThreadName() {
|
std::string GetThreadName() {
|
||||||
char buffer[64] = {0};
|
char buffer[64] = {0};
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
#if defined(_WIN32)
|
||||||
PWSTR desc = nullptr;
|
PWSTR desc = nullptr;
|
||||||
if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &desc))) {
|
if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &desc))) {
|
||||||
WideCharToMultiByte(CP_UTF8, 0, desc, -1, buffer, sizeof(buffer), nullptr, nullptr);
|
WideCharToMultiByte(CP_UTF8, 0, desc, -1, buffer, sizeof(buffer), nullptr, nullptr);
|
||||||
LocalFree(desc);
|
LocalFree(desc);
|
||||||
}
|
}
|
||||||
#elif defined(__ANDROID__) || defined(__linux__) || defined(__APPLE__) || defined(__MINGW32__)
|
#elif defined(__ANDROID__) || defined(__linux__) || defined(__APPLE__)
|
||||||
pthread_getname_np(pthread_self(), buffer, sizeof(buffer));
|
pthread_getname_np(pthread_self(), buffer, sizeof(buffer));
|
||||||
#endif
|
#endif
|
||||||
return buffer[0] ? buffer : "UnknownThread";
|
return buffer[0] ? buffer : "UnknownThread";
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
HashBytes(state, env.advertisedExtensions.data(),
|
HashBytes(state, env.advertisedExtensions.data(),
|
||||||
env.advertisedExtensions.size() * sizeof(GLExtension));
|
env.advertisedExtensions.size() * sizeof(GLExtension));
|
||||||
}
|
}
|
||||||
HashValue(state, env.subgroupPrefixScanQuirk);
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +72,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
kFrontendMaxComputeWorkGroupInvocations)
|
kFrontendMaxComputeWorkGroupInvocations)
|
||||||
: kFrontendMaxComputeWorkGroupInvocations;
|
: kFrontendMaxComputeWorkGroupInvocations;
|
||||||
|
|
||||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
|
||||||
|
|
||||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
@@ -84,7 +81,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
// computed, and this must not run before MG_Config is loaded.
|
// computed, and this must not run before MG_Config is loaded.
|
||||||
static const SharedPtr<const CompileEnv> kDefault = [] {
|
static const SharedPtr<const CompileEnv> kDefault = [] {
|
||||||
auto env = MakeShared<CompileEnv>();
|
auto env = MakeShared<CompileEnv>();
|
||||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
|
||||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||||
return SharedPtr<const CompileEnv>(Move(env));
|
return SharedPtr<const CompileEnv>(Move(env));
|
||||||
}();
|
}();
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
#include <MG_Backend/BackendObject.h>
|
#include <MG_Backend/BackendObject.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Util::ShaderTranspiler {
|
namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||||
// Everything the shader compile/link pipeline reads from OUTSIDE its own (stage, source)
|
// everything outside (stage, source) this reads - advertised extensions and backend limits -
|
||||||
// inputs: backend identity, backend limits, the advertised extension list, and the one
|
// so the transformation is a pure function of its three arguments and can run on a worker
|
||||||
// config quirk the source rewriter branches on.
|
// thread.
|
||||||
//
|
//
|
||||||
// Why it exists (P1): every one of those reads is a reach-back into
|
// Why it exists (P1): every one of those reads is a reach-back into
|
||||||
// MG_Backend::pActiveBackendObject / gBackendFunctionsTable, and one of them
|
// MG_Backend::pActiveBackendObject / gBackendFunctionsTable, and one of them
|
||||||
@@ -46,9 +46,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
MG_Backend::DynamicBackendParameters params{}; // by value, never by reference
|
MG_Backend::DynamicBackendParameters params{}; // by value, never by reference
|
||||||
Vector<GLExtension> advertisedExtensions;
|
Vector<GLExtension> advertisedExtensions;
|
||||||
|
|
||||||
// --- config the source rewriter branches on ---
|
|
||||||
MG_Config::QuirkOverride subgroupPrefixScanQuirk = MG_Config::QuirkOverride::Auto;
|
|
||||||
|
|
||||||
Uint64 fingerprint = 0; // set by CaptureCompileEnv()
|
Uint64 fingerprint = 0; // set by CaptureCompileEnv()
|
||||||
|
|
||||||
Bool HasBackend() const { return backend != BackendType::Unknown; }
|
Bool HasBackend() const { return backend != BackendType::Unknown; }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
using MobileGL::SizeT;
|
using MobileGL::SizeT;
|
||||||
using MobileGL::String;
|
using MobileGL::String;
|
||||||
|
using MobileGL::Uint32;
|
||||||
using MobileGL::Vector;
|
using MobileGL::Vector;
|
||||||
|
|
||||||
bool IsIdentifierChar(char ch) {
|
bool IsIdentifierChar(char ch) {
|
||||||
@@ -181,331 +182,6 @@ namespace {
|
|||||||
return std::all_of(token.text.begin() + 1, token.text.end(), IsIdentifierChar);
|
return std::all_of(token.text.begin() + 1, token.text.end(), IsIdentifierChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TokenCursor {
|
|
||||||
public:
|
|
||||||
TokenCursor(const Vector<CodeToken>& tokens, SizeT position) : m_tokens(tokens), m_position(position) {}
|
|
||||||
|
|
||||||
bool Consume(const char* expected) {
|
|
||||||
if (m_position >= m_tokens.size() || m_tokens[m_position].text != expected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
++m_position;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsumeAnyIdentifier(String& identifier) {
|
|
||||||
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
identifier = m_tokens[m_position++].text;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsumeAnyIdentifier() {
|
|
||||||
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
++m_position;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsumeIdentifier(const String& expected) {
|
|
||||||
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position]) ||
|
|
||||||
m_tokens[m_position].text != expected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
++m_position;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT Position() const { return m_position; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const Vector<CodeToken>& m_tokens;
|
|
||||||
SizeT m_position;
|
|
||||||
};
|
|
||||||
|
|
||||||
SizeT CountToken(const Vector<CodeToken>& tokens, const String& tokenText) {
|
|
||||||
return static_cast<SizeT>(std::count_if(tokens.begin(), tokens.end(),
|
|
||||||
[&](const CodeToken& token) { return token.text == tokenText; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasIdentifierWithPrefixOutsideAllowed(const Vector<CodeToken>& tokens, const String& prefix,
|
|
||||||
std::initializer_list<const char*> allowedIdentifiers) {
|
|
||||||
return std::any_of(tokens.begin(), tokens.end(), [&](const CodeToken& token) {
|
|
||||||
if (!IsIdentifierToken(token) || !token.text.starts_with(prefix)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return std::none_of(allowedIdentifiers.begin(), allowedIdentifiers.end(),
|
|
||||||
[&](const char* allowed) { return token.text == allowed; });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MatchTokenSequence(const Vector<CodeToken>& tokens, SizeT position,
|
|
||||||
std::initializer_list<const char*> expected) {
|
|
||||||
if (position + expected.size() > tokens.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const char* token : expected) {
|
|
||||||
if (tokens[position++].text != token) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LinearPrefixScanMatch {
|
|
||||||
SizeT sharedArraySizeBegin = 0;
|
|
||||||
SizeT sharedArraySizeEnd = 0;
|
|
||||||
SizeT scanBegin = 0;
|
|
||||||
SizeT scanEnd = 0;
|
|
||||||
String cache;
|
|
||||||
String importance;
|
|
||||||
String prefixSum;
|
|
||||||
String loopLength;
|
|
||||||
String loopIndex;
|
|
||||||
String sum;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ParseLinearPrefixScanTemplate(const Vector<CodeToken>& tokens, LinearPrefixScanMatch& match) {
|
|
||||||
// The workaround deliberately recognizes one complete algorithm, not merely the
|
|
||||||
// subgroupInclusiveAdd token. Changing scratch storage is only safe when that storage is
|
|
||||||
// private to this scan and the workgroup has exactly 1024 X invocations.
|
|
||||||
SizeT localSizeDeclarationCount = 0;
|
|
||||||
for (SizeT i = 0; i < tokens.size(); ++i) {
|
|
||||||
if (MatchTokenSequence(tokens, i, {"layout", "(", "local_size_x", "=", "1024", ")", "in", ";"})) {
|
|
||||||
++localSizeDeclarationCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (localSizeDeclarationCount != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT sharedDeclarationIndex = String::npos;
|
|
||||||
SizeT sharedDeclarationCount = 0;
|
|
||||||
String cacheName;
|
|
||||||
for (SizeT i = 0; i + 6 < tokens.size(); ++i) {
|
|
||||||
if (tokens[i].text != "shared" || tokens[i + 1].text != "float" || !IsIdentifierToken(tokens[i + 2]) ||
|
|
||||||
tokens[i + 3].text != "[" || tokens[i + 4].text != "64" || tokens[i + 5].text != "]" ||
|
|
||||||
tokens[i + 6].text != ";") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++sharedDeclarationCount;
|
|
||||||
sharedDeclarationIndex = i;
|
|
||||||
cacheName = tokens[i + 2].text;
|
|
||||||
}
|
|
||||||
if (sharedDeclarationCount != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT scanTokenIndex = String::npos;
|
|
||||||
SizeT scanCount = 0;
|
|
||||||
for (SizeT i = 0; i + 7 < tokens.size(); ++i) {
|
|
||||||
if (tokens[i].text == "float" && IsIdentifierToken(tokens[i + 1]) && tokens[i + 2].text == "=" &&
|
|
||||||
tokens[i + 3].text == "subgroupInclusiveAdd" && tokens[i + 4].text == "(" &&
|
|
||||||
IsIdentifierToken(tokens[i + 5]) && tokens[i + 6].text == ")" && tokens[i + 7].text == ";") {
|
|
||||||
++scanCount;
|
|
||||||
scanTokenIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (scanCount != 1 || sharedDeclarationIndex >= scanTokenIndex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenCursor cursor(tokens, scanTokenIndex);
|
|
||||||
String prefixSum;
|
|
||||||
String importance;
|
|
||||||
String loopLength;
|
|
||||||
String loopIndex;
|
|
||||||
String sum;
|
|
||||||
if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier(prefixSum) || !cursor.Consume("=") ||
|
|
||||||
!cursor.Consume("subgroupInclusiveAdd") || !cursor.Consume("(") ||
|
|
||||||
!cursor.ConsumeAnyIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume(";") ||
|
|
||||||
!cursor.Consume("if") || !cursor.Consume("(") || !cursor.Consume("gl_SubgroupInvocationID") ||
|
|
||||||
!cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") || !cursor.Consume("-") ||
|
|
||||||
!cursor.Consume("1u") || !cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) ||
|
|
||||||
!cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("]") || !cursor.Consume("=") ||
|
|
||||||
!cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("uint") ||
|
|
||||||
!cursor.ConsumeAnyIdentifier(loopLength) || !cursor.Consume("=") || !cursor.Consume("uint") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("findMSB") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume("gl_NumSubgroups") || !cursor.Consume(")") || !cursor.Consume(")") ||
|
|
||||||
!cursor.Consume(";") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("+=") ||
|
|
||||||
!cursor.Consume("uint") || !cursor.Consume("(") || !cursor.Consume("gl_NumSubgroups") ||
|
|
||||||
!cursor.Consume("-") || !cursor.Consume("(") || !cursor.Consume("1u") || !cursor.Consume("<<") ||
|
|
||||||
!cursor.Consume("(") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("-") ||
|
|
||||||
!cursor.Consume("1u") || !cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") ||
|
|
||||||
!cursor.Consume("0u") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("for") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("uint") || !cursor.ConsumeAnyIdentifier(loopIndex) ||
|
|
||||||
!cursor.Consume("=") || !cursor.Consume("0") || !cursor.Consume(";") ||
|
|
||||||
!cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<") || !cursor.ConsumeIdentifier(loopLength) ||
|
|
||||||
!cursor.Consume(";") || !cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("++") ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume("{") || !cursor.Consume("if") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("&") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume("1u") || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") || !cursor.Consume("0u") ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume("{") || !cursor.ConsumeIdentifier(prefixSum) ||
|
|
||||||
!cursor.Consume("+=") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume(">>") ||
|
|
||||||
!cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume("]") ||
|
|
||||||
!cursor.Consume(";") || !cursor.Consume("if") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume("gl_SubgroupInvocationID") || !cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") ||
|
|
||||||
!cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume(")") ||
|
|
||||||
!cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") ||
|
|
||||||
!cursor.Consume("]") || !cursor.Consume("=") || !cursor.ConsumeIdentifier(prefixSum) ||
|
|
||||||
!cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("barrier") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("if") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") ||
|
|
||||||
!cursor.Consume("x") || !cursor.Consume("==") || !cursor.Consume("uint") || !cursor.Consume("(") ||
|
|
||||||
!cursor.Consume("1024") || !cursor.Consume("-") || !cursor.Consume("1") || !cursor.Consume(")") ||
|
|
||||||
!cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") ||
|
|
||||||
!cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume("=") ||
|
|
||||||
!cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("float") ||
|
|
||||||
!cursor.ConsumeAnyIdentifier(sum) || !cursor.Consume("=") || !cursor.ConsumeIdentifier(cacheName) ||
|
|
||||||
!cursor.Consume("[") || !cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume(";")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const SizeT scanEndToken = cursor.Position() - 1;
|
|
||||||
|
|
||||||
// Require the scan's immediate consumer as well. This makes the match specific to a
|
|
||||||
// linear distribution warp, and avoids changing unrelated prefix scans which may rely on
|
|
||||||
// the implementation's native subgroup partitioning.
|
|
||||||
if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier() || !cursor.Consume("=") ||
|
|
||||||
!cursor.Consume("(") || !cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume("-") ||
|
|
||||||
!cursor.ConsumeIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume("/") ||
|
|
||||||
!cursor.ConsumeIdentifier(sum) || !cursor.Consume("-") || !cursor.Consume("float") ||
|
|
||||||
!cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") ||
|
|
||||||
!cursor.Consume("x") || !cursor.Consume("+") || !cursor.Consume("1u") || !cursor.Consume(")") ||
|
|
||||||
!cursor.Consume("/") || !cursor.Consume("float") || !cursor.Consume("(") || !cursor.Consume("1024") ||
|
|
||||||
!cursor.Consume(")") || !cursor.Consume(";")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No other use may share the scratch array, and no additional subgroup operation or
|
|
||||||
// builtin may silently retain native-64 semantics after this module becomes virtual-32.
|
|
||||||
if (CountToken(tokens, cacheName) != 6 || CountToken(tokens, "subgroupInclusiveAdd") != 1 ||
|
|
||||||
CountToken(tokens, "gl_SubgroupInvocationID") != 2 || CountToken(tokens, "gl_SubgroupSize") != 2 ||
|
|
||||||
CountToken(tokens, "gl_SubgroupID") != 4 || CountToken(tokens, "gl_NumSubgroups") != 2 ||
|
|
||||||
CountToken(tokens, "gl_LocalInvocationID") != 2 || CountToken(tokens, "barrier") != 3 ||
|
|
||||||
CountToken(tokens, "findMSB") != 1 ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "subgroup", {"subgroupInclusiveAdd"}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(
|
|
||||||
tokens, "gl_Subgroup",
|
|
||||||
{"gl_SubgroupInvocationID", "gl_SubgroupSize", "gl_SubgroupID", "gl_NumSubgroups"}) ||
|
|
||||||
// ARB/NV spellings of lane-width-sensitive builtins and functions
|
|
||||||
// (gl_SubGroupSizeARB, ballotARB, gl_WarpSizeNV, shuffleNV, ...) must block the
|
|
||||||
// rewrite just like their KHR counterparts: they would silently keep native-width
|
|
||||||
// semantics in a module rewritten to the virtual 32-lane model.
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SubGroup", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Warp", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Thread", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SMID", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "ballot", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "shuffle", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "readInvocation", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "readFirstInvocation", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "anyInvocation", {}) ||
|
|
||||||
HasIdentifierWithPrefixOutsideAllowed(tokens, "allInvocations", {})) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The scan must be at the top level of the sole main() body. Its existing barriers already
|
|
||||||
// require uniform control flow; this check prevents us from introducing extra barriers in
|
|
||||||
// a nested branch or loop.
|
|
||||||
SizeT mainOpenBrace = String::npos;
|
|
||||||
SizeT mainCloseBrace = String::npos;
|
|
||||||
SizeT mainCount = 0;
|
|
||||||
for (SizeT i = 0; i + 4 < tokens.size(); ++i) {
|
|
||||||
if (!MatchTokenSequence(tokens, i, {"void", "main", "(", ")", "{"})) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++mainCount;
|
|
||||||
mainOpenBrace = i + 4;
|
|
||||||
int depth = 1;
|
|
||||||
for (SizeT j = mainOpenBrace + 1; j < tokens.size(); ++j) {
|
|
||||||
if (tokens[j].text == "{")
|
|
||||||
++depth;
|
|
||||||
else if (tokens[j].text == "}" && --depth == 0) {
|
|
||||||
mainCloseBrace = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mainCount != 1 || mainCloseBrace == String::npos || scanTokenIndex <= mainOpenBrace ||
|
|
||||||
scanEndToken >= mainCloseBrace) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int depthAtScan = 1;
|
|
||||||
for (SizeT i = mainOpenBrace + 1; i < scanTokenIndex; ++i) {
|
|
||||||
if (tokens[i].text == "{")
|
|
||||||
++depthAtScan;
|
|
||||||
else if (tokens[i].text == "}")
|
|
||||||
--depthAtScan;
|
|
||||||
}
|
|
||||||
if (depthAtScan != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr const char* injectedNames[] = {"mglPrefixScanLane", "mglVirtualSubgroupInvocation",
|
|
||||||
"mglVirtualSubgroup", "mglVirtualSubgroupBase",
|
|
||||||
"mglPrefixLane", "mglVirtualSubgroupCount"};
|
|
||||||
for (const char* injectedName : injectedNames) {
|
|
||||||
if (CountToken(tokens, injectedName) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match.sharedArraySizeBegin = tokens[sharedDeclarationIndex + 4].begin;
|
|
||||||
match.sharedArraySizeEnd = tokens[sharedDeclarationIndex + 4].end;
|
|
||||||
match.scanBegin = tokens[scanTokenIndex].begin;
|
|
||||||
match.scanEnd = tokens[scanEndToken].end;
|
|
||||||
match.cache = std::move(cacheName);
|
|
||||||
match.importance = std::move(importance);
|
|
||||||
match.prefixSum = std::move(prefixSum);
|
|
||||||
match.loopLength = std::move(loopLength);
|
|
||||||
match.loopIndex = std::move(loopIndex);
|
|
||||||
match.sum = std::move(sum);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String BuildLinearPrefixScanReplacement(const LinearPrefixScanMatch& match) {
|
|
||||||
String replacement;
|
|
||||||
replacement.reserve(1800);
|
|
||||||
replacement += "uint mglPrefixScanLane = gl_LocalInvocationID.x;\n";
|
|
||||||
replacement += "uint mglVirtualSubgroupInvocation = mglPrefixScanLane & 31u;\n";
|
|
||||||
replacement += "uint mglVirtualSubgroup = mglPrefixScanLane >> 5u;\n";
|
|
||||||
replacement += "const uint mglVirtualSubgroupCount = 32u;\n";
|
|
||||||
replacement += match.cache + "[mglPrefixScanLane] = " + match.importance + ";\n";
|
|
||||||
replacement += "barrier();\n";
|
|
||||||
replacement += "float " + match.prefixSum + " = 0.0f;\n";
|
|
||||||
replacement += "uint mglVirtualSubgroupBase = mglVirtualSubgroup << 5u;\n";
|
|
||||||
replacement += "for (uint mglPrefixLane = mglVirtualSubgroupBase; "
|
|
||||||
"mglPrefixLane <= mglPrefixScanLane; ++mglPrefixLane) {\n";
|
|
||||||
replacement += match.prefixSum + " += " + match.cache + "[mglPrefixLane];\n";
|
|
||||||
replacement += "}\n";
|
|
||||||
replacement += "barrier();\n";
|
|
||||||
replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache +
|
|
||||||
"[mglVirtualSubgroup] = " + match.prefixSum + ";\n";
|
|
||||||
replacement += "barrier();\n";
|
|
||||||
replacement += "uint " + match.loopLength + " = uint(findMSB(mglVirtualSubgroupCount));\n";
|
|
||||||
replacement +=
|
|
||||||
match.loopLength + " += uint(mglVirtualSubgroupCount - (1u << (" + match.loopLength + " - 1u)) > 0u);\n";
|
|
||||||
replacement += "for (uint " + match.loopIndex + " = 0u; " + match.loopIndex + " < " + match.loopLength +
|
|
||||||
"; ++" + match.loopIndex + ") {\n";
|
|
||||||
replacement += "if ((mglVirtualSubgroup & (1u << " + match.loopIndex + ")) > 0u) {\n";
|
|
||||||
replacement += match.prefixSum + " += " + match.cache + "[(mglVirtualSubgroup >> " + match.loopIndex + " << " +
|
|
||||||
match.loopIndex + ") - 1u];\n";
|
|
||||||
replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache +
|
|
||||||
"[mglVirtualSubgroup] = " + match.prefixSum + ";\n";
|
|
||||||
replacement += "}\nbarrier();\n}\n";
|
|
||||||
replacement += "if (mglPrefixScanLane == 1023u) " + match.cache + "[0] = " + match.prefixSum + ";\n";
|
|
||||||
replacement += "barrier();\n";
|
|
||||||
replacement += "float " + match.sum + " = " + match.cache + "[0];";
|
|
||||||
return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
|
void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
|
||||||
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
|
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
|
||||||
pos++;
|
pos++;
|
||||||
@@ -1253,117 +929,6 @@ namespace {
|
|||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
namespace MG_Util {
|
namespace MG_Util {
|
||||||
namespace ShaderTranspiler {
|
namespace ShaderTranspiler {
|
||||||
Bool RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize,
|
|
||||||
String& source) {
|
|
||||||
constexpr Uint32 capturedSubgroupSize = 32;
|
|
||||||
if (stage != ShaderStage::Compute || nativeSubgroupSize <= capturedSubgroupSize ||
|
|
||||||
nativeSubgroupSize % capturedSubgroupSize != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vulkan subgroup widths are powers of two. Keep the workaround restricted to
|
|
||||||
// wider widths which are a power-of-two multiple of the captured 32-lane model.
|
|
||||||
const Uint32 subgroupScale = nativeSubgroupSize / capturedSubgroupSize;
|
|
||||||
if ((subgroupScale & (subgroupScale - 1u)) != 0u) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Vector<CodeToken> tokens = TokenizeCode(source);
|
|
||||||
LinearPrefixScanMatch match;
|
|
||||||
if (!ParseLinearPrefixScanTemplate(tokens, match)) {
|
|
||||||
// Diagnosability: when the trigger op is present but the template no longer
|
|
||||||
// matches (e.g. the pack shipped a new shader revision), the affected device
|
|
||||||
// silently falls back to the driver's miscompiled path. Make that visible.
|
|
||||||
if (CountToken(tokens, "subgroupInclusiveAdd") > 0) {
|
|
||||||
MGLOG_W_ONCE("%s: subgroupInclusiveAdd present but the linear prefix-scan template "
|
|
||||||
"did not match; the wide-subgroup rewrite was NOT applied",
|
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String replacement = BuildLinearPrefixScanReplacement(match);
|
|
||||||
source.replace(match.scanBegin, match.scanEnd - match.scanBegin, replacement);
|
|
||||||
// The declaration occurs before the replaced scan, so its original offsets remain
|
|
||||||
// valid after the first replacement.
|
|
||||||
source.replace(match.sharedArraySizeBegin, match.sharedArraySizeEnd - match.sharedArraySizeBegin,
|
|
||||||
"1024");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct ShaderSourceQuirkContext {
|
|
||||||
ShaderStage stage = ShaderStage::Unknown;
|
|
||||||
BackendType backend = BackendType::Unknown;
|
|
||||||
MG_Backend::GpuVendorKind vendor = MG_Backend::GpuVendorKind::Unknown;
|
|
||||||
Uint32 subgroupSize = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Device-quirk registry. Every entry is a narrowly scoped source rewrite that
|
|
||||||
// works around a specific driver defect. A quirk runs when its env override
|
|
||||||
// forces it on, or when the override is Auto and DeviceApplies matches the
|
|
||||||
// detected device. ForceOn bypasses only the device gate - each Apply keeps
|
|
||||||
// its own structural safety checks. Add new per-device workarounds here
|
|
||||||
// instead of open-coding them in PreprocessShaderSource.
|
|
||||||
struct ShaderSourceQuirk {
|
|
||||||
const char* name;
|
|
||||||
// Reads the override out of the captured env, never out of the live
|
|
||||||
// MG_Config table: a worker must see the same config the GL thread saw.
|
|
||||||
MG_Config::QuirkOverride (*GetOverride)(const CompileEnv&);
|
|
||||||
Bool (*DeviceApplies)(const ShaderSourceQuirkContext&);
|
|
||||||
Bool (*Apply)(const ShaderSourceQuirkContext&, String&);
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr ShaderSourceQuirk kShaderSourceQuirks[] = {
|
|
||||||
{
|
|
||||||
// MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN
|
|
||||||
"subgroup-prefix-scan-rewrite",
|
|
||||||
[](const CompileEnv& env) { return env.subgroupPrefixScanQuirk; },
|
|
||||||
[](const ShaderSourceQuirkContext& ctx) {
|
|
||||||
// Qualcomm's Vulkan driver miscompiles the recognized float
|
|
||||||
// InclusiveScan pattern for native subgroups wider than the
|
|
||||||
// captured 32 lanes; other vendors compile it correctly and
|
|
||||||
// should keep their native scan.
|
|
||||||
return ctx.backend == BackendType::DirectVulkan &&
|
|
||||||
ctx.vendor == MG_Backend::GpuVendorKind::Qualcomm;
|
|
||||||
},
|
|
||||||
[](const ShaderSourceQuirkContext& ctx, String& source) {
|
|
||||||
return RewriteLinearSubgroupPrefixScanForVulkan(ctx.stage, ctx.subgroupSize,
|
|
||||||
source);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
void ApplyShaderSourceQuirks(const CompileEnv& env, ShaderStage stage, String& source) {
|
|
||||||
// No backend at capture time means no device to match a quirk against,
|
|
||||||
// and (as before) no quirk can fire - not even a forced one, because
|
|
||||||
// every Apply reads device parameters that do not exist yet.
|
|
||||||
if (!env.HasBackend()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ShaderSourceQuirkContext quirkContext{
|
|
||||||
stage,
|
|
||||||
env.backend,
|
|
||||||
env.params.GpuVendor,
|
|
||||||
env.params.SubgroupSize,
|
|
||||||
};
|
|
||||||
for (const ShaderSourceQuirk& quirk : kShaderSourceQuirks) {
|
|
||||||
const MG_Config::QuirkOverride quirkOverride = quirk.GetOverride(env);
|
|
||||||
if (quirkOverride == MG_Config::QuirkOverride::ForceOff) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (quirkOverride == MG_Config::QuirkOverride::Auto &&
|
|
||||||
!quirk.DeviceApplies(quirkContext)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (quirk.Apply(quirkContext, source)) {
|
|
||||||
MGLOG_D("ApplyShaderSourceQuirks: applied '%s'%s", quirk.name,
|
|
||||||
quirkOverride == MG_Config::QuirkOverride::ForceOn ? " (forced on)" : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void PreprocessShaderSource(ShaderStage stage, String& source) {
|
void PreprocessShaderSource(ShaderStage stage, String& source) {
|
||||||
PreprocessShaderSource(stage, source, *GetCurrentCompileEnv());
|
PreprocessShaderSource(stage, source, *GetCurrentCompileEnv());
|
||||||
}
|
}
|
||||||
@@ -1403,7 +968,6 @@ namespace MobileGL {
|
|||||||
ModernizeLegacyGLSL(stage, source, afterVersion);
|
ModernizeLegacyGLSL(stage, source, afterVersion);
|
||||||
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
||||||
|
|
||||||
ApplyShaderSourceQuirks(env, stage, source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool RetargetLegacyVersionDirectiveTo460(String& source) {
|
Bool RetargetLegacyVersionDirectiveTo460(String& source) {
|
||||||
|
|||||||
@@ -30,18 +30,6 @@ namespace MobileGL {
|
|||||||
// tests and diagnostics that drive the preprocessor standalone.
|
// tests and diagnostics that drive the preprocessor standalone.
|
||||||
void PreprocessShaderSource(ShaderStage stage, String& source);
|
void PreprocessShaderSource(ShaderStage stage, String& source);
|
||||||
|
|
||||||
// Some desktop-captured compute shaders build a workgroup-wide linear prefix scan
|
|
||||||
// from subgroupInclusiveAdd plus a shared array of subgroup totals. Qualcomm's
|
|
||||||
// Vulkan driver miscompiles that exact float InclusiveScan path for native subgroups
|
|
||||||
// wider than the capture's 32 lanes. For the narrowly recognized, uniform-control-
|
|
||||||
// flow template, replace the subgroup-local scan with a shared-memory, strict
|
|
||||||
// left-fold over virtual 32-lane segments. Returns true only when the complete safe
|
|
||||||
// template was recognized and rewritten. PreprocessShaderSource reaches this through
|
|
||||||
// its device-quirk registry: by default only on detected Qualcomm Vulkan devices,
|
|
||||||
// overridable either way with MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN=1/0. The explicit
|
|
||||||
// entry point exists for deterministic tests.
|
|
||||||
Bool RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize, String& source);
|
|
||||||
|
|
||||||
// Rewrites a "#version 330 core" directive that PreprocessShaderSource normalized down
|
// Rewrites a "#version 330 core" directive that PreprocessShaderSource normalized down
|
||||||
// from a legacy desktop version back up to "#version 460 core". Returns false (leaving
|
// from a legacy desktop version back up to "#version 460 core". Returns false (leaving
|
||||||
// the source untouched) for anything else: ES, compatibility, or an already-modern
|
// the source untouched) for anything else: ES, compatibility, or an already-modern
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ val pluginRendererConfig = buildJsonValue {
|
|||||||
selectable(
|
selectable(
|
||||||
key = "MOBILEGL_BACKEND_TYPE",
|
key = "MOBILEGL_BACKEND_TYPE",
|
||||||
title = RendererConfig.MetaString("mobilegl_backend_type_title"),
|
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_TIMERQUERY", "1", false, RendererConfig.MetaString("mobilegl_disable_timerquery_title"))
|
||||||
toggleable("MOBILEGL_DISABLE_SUBGROUP", "1", false, RendererConfig.MetaString("mobilegl_disable_subgroup_title"))
|
toggleable("MOBILEGL_DISABLE_SUBGROUP", "1", false, RendererConfig.MetaString("mobilegl_disable_subgroup_title"))
|
||||||
|
|||||||
@@ -221,10 +221,13 @@ typedef signed char khronos_int8_t;
|
|||||||
typedef unsigned char khronos_uint8_t;
|
typedef unsigned char khronos_uint8_t;
|
||||||
typedef signed short int khronos_int16_t;
|
typedef signed short int khronos_int16_t;
|
||||||
typedef unsigned short int khronos_uint16_t;
|
typedef unsigned short int khronos_uint16_t;
|
||||||
typedef signed long int khronos_intptr_t;
|
/* `long` is 32-bit on LLP64 Windows, including 64-bit MinGW. Use the
|
||||||
typedef unsigned long int khronos_uintptr_t;
|
* standard pointer-sized integer types so these remain pointer-width there. */
|
||||||
typedef signed long int khronos_ssize_t;
|
#include <stdint.h>
|
||||||
typedef unsigned long int khronos_usize_t;
|
typedef intptr_t khronos_intptr_t;
|
||||||
|
typedef uintptr_t khronos_uintptr_t;
|
||||||
|
typedef intptr_t khronos_ssize_t;
|
||||||
|
typedef uintptr_t khronos_usize_t;
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_FLOAT
|
#if KHRONOS_SUPPORT_FLOAT
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -277,11 +277,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "minecraft-1.21.4-fabric-iris-iterationrp-in-world",
|
"name": "minecraft-1.21.4-fabric-iris-iterationrp-in-world",
|
||||||
"ci": false,
|
"ci_backends": [
|
||||||
|
"DirectVulkan"
|
||||||
|
],
|
||||||
"trace_archive": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.tgz",
|
"trace_archive": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.tgz",
|
||||||
"golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png",
|
"golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png",
|
||||||
"target_call": 202020,
|
"target_call": 202020,
|
||||||
"timeout_seconds": 1800
|
"timeout_seconds": 1800,
|
||||||
|
"ssim_threshold": 0.98
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "minecraft-1.21.4-fabric-iris-bsl-esc-menu-854",
|
"name": "minecraft-1.21.4-fabric-iris-bsl-esc-menu-854",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
TRACE_CASES_JSON = Path(__file__).with_name("trace_cases.json")
|
TRACE_CASES_JSON = Path(__file__).with_name("trace_cases.json")
|
||||||
|
CI_BACKENDS = ("DirectGLES", "DirectVulkan")
|
||||||
|
|
||||||
|
|
||||||
def load_trace_case_manifest(path=TRACE_CASES_JSON):
|
def load_trace_case_manifest(path=TRACE_CASES_JSON):
|
||||||
@@ -71,6 +72,46 @@ def ci_trace_cases(cases):
|
|||||||
return [case for case in cases if case.get("ci", True)]
|
return [case for case in cases if case.get("ci", True)]
|
||||||
|
|
||||||
|
|
||||||
|
def ci_backends(case):
|
||||||
|
backends = case.get("ci_backends")
|
||||||
|
if backends is None:
|
||||||
|
return CI_BACKENDS
|
||||||
|
if not isinstance(backends, list) or not backends:
|
||||||
|
raise ValueError(f"ci_backends must be a non-empty list for {case['name']}")
|
||||||
|
unknown = [backend for backend in backends if backend not in CI_BACKENDS]
|
||||||
|
if unknown:
|
||||||
|
raise ValueError(
|
||||||
|
f"unknown ci_backends for {case['name']}: {', '.join(unknown)}"
|
||||||
|
)
|
||||||
|
if len(set(backends)) != len(backends):
|
||||||
|
raise ValueError(f"ci_backends contains duplicates for {case['name']}")
|
||||||
|
return backends
|
||||||
|
|
||||||
|
|
||||||
|
def github_test_matrix(cases):
|
||||||
|
return {
|
||||||
|
"include": [
|
||||||
|
{"backend": backend, "case": case["name"]}
|
||||||
|
for case in cases
|
||||||
|
for backend in ci_backends(case)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def github_apk_matrix(cases):
|
||||||
|
backends = {
|
||||||
|
"DirectGLES": {"name": "DirectGLES", "gpu": "software"},
|
||||||
|
"DirectVulkan": {"name": "DirectVulkan", "gpu": "lavapipe"},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"include": [
|
||||||
|
{"backend": backends[backend], "case": github_apk_case(case)}
|
||||||
|
for case in cases
|
||||||
|
for backend in ci_backends(case)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def cmake_quote(value):
|
def cmake_quote(value):
|
||||||
return '"' + str(value).replace("\\", "/").replace('"', '\\"') + '"'
|
return '"' + str(value).replace("\\", "/").replace('"', '\\"') + '"'
|
||||||
|
|
||||||
@@ -114,7 +155,14 @@ def parse_args():
|
|||||||
parser.add_argument("--fixture-root", default="tools/trace_replay/fixtures")
|
parser.add_argument("--fixture-root", default="tools/trace_replay/fixtures")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--format",
|
"--format",
|
||||||
choices=("names", "github-apk", "fixture-files", "cmake"),
|
choices=(
|
||||||
|
"names",
|
||||||
|
"github-test-matrix",
|
||||||
|
"github-apk",
|
||||||
|
"github-apk-matrix",
|
||||||
|
"fixture-files",
|
||||||
|
"cmake",
|
||||||
|
),
|
||||||
default="names",
|
default="names",
|
||||||
)
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
@@ -127,8 +175,12 @@ def main():
|
|||||||
cases = ci_trace_cases(cases)
|
cases = ci_trace_cases(cases)
|
||||||
if args.format == "names":
|
if args.format == "names":
|
||||||
print(json.dumps([case["name"] for case in cases], separators=(",", ":")))
|
print(json.dumps([case["name"] for case in cases], separators=(",", ":")))
|
||||||
|
elif args.format == "github-test-matrix":
|
||||||
|
print(json.dumps(github_test_matrix(cases), separators=(",", ":")))
|
||||||
elif args.format == "github-apk":
|
elif args.format == "github-apk":
|
||||||
print(json.dumps([github_apk_case(case) for case in cases], separators=(",", ":")))
|
print(json.dumps([github_apk_case(case) for case in cases], separators=(",", ":")))
|
||||||
|
elif args.format == "github-apk-matrix":
|
||||||
|
print(json.dumps(github_apk_matrix(cases), separators=(",", ":")))
|
||||||
elif args.format == "fixture-files":
|
elif args.format == "fixture-files":
|
||||||
if not args.case_name:
|
if not args.case_name:
|
||||||
print("--case is required for --format fixture-files", file=sys.stderr)
|
print("--case is required for --format fixture-files", file=sys.stderr)
|
||||||
|
|||||||
Reference in New Issue
Block a user