mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 06:38:31 +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
|
||||
id: trace-cases
|
||||
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"
|
||||
|
||||
trace-fixtures:
|
||||
@@ -337,13 +337,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- name: DirectGLES
|
||||
gpu: software
|
||||
- name: DirectVulkan
|
||||
gpu: lavapipe
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||
matrix: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
|
||||
@@ -491,6 +491,7 @@ jobs:
|
||||
- benchmark
|
||||
- integration
|
||||
outputs:
|
||||
matrix: ${{ steps.trace-cases.outputs.matrix }}
|
||||
names: ${{ steps.trace-cases.outputs.names }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -498,7 +499,9 @@ jobs:
|
||||
|
||||
- name: Load 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:
|
||||
name: trace fixture (${{ matrix.case }})
|
||||
@@ -577,11 +580,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- DirectGLES
|
||||
- DirectVulkan
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
matrix: ${{ fromJSON(needs.trace-cases.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
################################################################################
|
||||
################################################################################
|
||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
||||
################################################################################
|
||||
|
||||
@@ -16,6 +16,9 @@ MobileGLCodeManager
|
||||
MobileGL/MG_Test/build
|
||||
/build_*
|
||||
/cmake-build*
|
||||
/build-*/
|
||||
/local.properties
|
||||
/.jspace/
|
||||
.idea
|
||||
MobileGL/MG*/build*
|
||||
MobileGL/MG*/cmake-build*
|
||||
|
||||
+42
-1
@@ -199,7 +199,6 @@ set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging"
|
||||
set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
|
||||
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
|
||||
|
||||
# add_subdirectory(3rdparty/DiligentCore)
|
||||
add_subdirectory(3rdparty/glslang)
|
||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||
@@ -211,6 +210,23 @@ set(XXHASH_BUILD_XXHSUM OFF)
|
||||
option(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(3rdparty/xxHash/build/cmake xxhash_build EXCLUDE_FROM_ALL)
|
||||
|
||||
# Diligent-based backend. Enabled by default on local builds; only the Vulkan
|
||||
# engine from DiligentCore is built. Added after the other 3rdparty projects so
|
||||
# DiligentCore reuses the glslang / SPIRV-Cross / SPIRV-Tools / xxHash targets
|
||||
# already defined by MobileGL instead of building its bundled copies.
|
||||
option(MOBILEGL_ENABLE_DILIGENT "Enable the Diligent/Vulkan backend" ON)
|
||||
if(MOBILEGL_ENABLE_DILIGENT)
|
||||
set(DILIGENT_NO_DIRECT3D11 ON CACHE BOOL "Disable Direct3D11 backend" FORCE)
|
||||
set(DILIGENT_NO_DIRECT3D12 ON CACHE BOOL "Disable Direct3D12 backend" FORCE)
|
||||
set(DILIGENT_NO_OPENGL ON CACHE BOOL "Disable OpenGL backend" FORCE)
|
||||
set(DILIGENT_NO_METAL ON CACHE BOOL "Disable Metal backend" FORCE)
|
||||
set(DILIGENT_NO_WEBGPU ON CACHE BOOL "Disable WebGPU backend" FORCE)
|
||||
set(DILIGENT_NO_ARCHIVER ON CACHE BOOL "Disable Archiver" FORCE)
|
||||
set(DILIGENT_BUILD_TESTS OFF CACHE BOOL "Build Diligent tests" FORCE)
|
||||
set(DILIGENT_INSTALL_CORE OFF CACHE BOOL "Install DiligentCore" FORCE)
|
||||
add_subdirectory(3rdparty/DiligentCore)
|
||||
endif()
|
||||
|
||||
set(TRACY_ENABLE ${MOBILEGL_ENABLE_TRACY} CACHE BOOL "Enable Tracy, this is an internal variable" FORCE)
|
||||
|
||||
if (TRACY_ENABLE)
|
||||
@@ -396,6 +412,14 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
||||
)
|
||||
|
||||
if(MOBILEGL_ENABLE_DILIGENT)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp
|
||||
MobileGL/MG_Backend/Diligent/DiligentVulkan.cpp
|
||||
MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (APPLE AND NOT MOBILEGL_IOS)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||
@@ -436,6 +460,21 @@ set(MOBILEGL_LINK_LIBRARIES
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
if(MOBILEGL_ENABLE_DILIGENT)
|
||||
list(APPEND MOBILEGL_LINK_LIBRARIES
|
||||
Diligent-GraphicsEngineVk-static
|
||||
Diligent-GraphicsEngine
|
||||
Diligent-GraphicsEngineNextGenBase
|
||||
Diligent-GraphicsAccessories
|
||||
Diligent-ShaderTools
|
||||
Diligent-GraphicsTools
|
||||
Diligent-Common
|
||||
Diligent-Primitives
|
||||
Diligent-TargetPlatform
|
||||
Vulkan::Headers
|
||||
)
|
||||
endif()
|
||||
|
||||
set(MOBILEGL_COMPILE_DEF
|
||||
-DVMA_STATIC_VULKAN_FUNCTIONS=0
|
||||
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
||||
@@ -501,6 +540,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME}
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
$<$<BOOL:${MOBILEGL_TRACE_ANGLE_VARIANTS}>:MOBILEGL_TRACE_ANGLE_VARIANTS=1>
|
||||
$<$<BOOL:${MOBILEGL_ENABLE_DILIGENT}>:MOBILEGL_ENABLE_DILIGENT=1>
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
@@ -559,6 +599,7 @@ if(NOT ANDROID)
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
$<$<BOOL:${MOBILEGL_ENABLE_DILIGENT}>:MOBILEGL_ENABLE_DILIGENT=1>
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -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
|
||||
// version request.
|
||||
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
|
||||
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
||||
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
||||
|
||||
@@ -180,7 +180,6 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
features.EsprytForceDepthStencilReadbackEmulation =
|
||||
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
||||
features.MagmaDisableBlendedDepthWriteQuirk =
|
||||
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
||||
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||
@@ -203,6 +202,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
}
|
||||
ENTRY(DirectGLES)
|
||||
ENTRY(DirectVulkan)
|
||||
ENTRY(DiligentVulkan)
|
||||
ENTRY(Unknown)
|
||||
MG_Config::ActiveBackendType = BackendType::Unknown;
|
||||
#undef ENTRY
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace MobileGL {
|
||||
enum class BackendType {
|
||||
DirectGLES,
|
||||
DirectVulkan,
|
||||
DiligentVulkan,
|
||||
BackendTypeCount,
|
||||
Unknown = -1
|
||||
};
|
||||
|
||||
@@ -0,0 +1,906 @@
|
||||
// MobileGL - MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include "BackendObject_Diligent.h"
|
||||
#include "DiligentVulkan.h"
|
||||
#include "Renderer/DiligentRenderer.h"
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
#include <EngineFactoryVk.h>
|
||||
#include <RenderDevice.h>
|
||||
#include <DeviceContext.h>
|
||||
|
||||
#include <exception>
|
||||
#include <chrono>
|
||||
|
||||
namespace MobileGL::MG_Backend::DiligentBackend {
|
||||
namespace {
|
||||
const RendererInfo BuildInitialRendererInfo() {
|
||||
RendererInfo info;
|
||||
info.RendererName = "MobileGL (Diligent/Vulkan)";
|
||||
info.BackendName = "Diligent Vulkan";
|
||||
info.RendererGLInfo.TargetGLVersion = {3, 2, 0};
|
||||
info.RendererGLInfo.TargetGLSLVersion = {1, 50, 0};
|
||||
info.RendererGLInfo.IsCompatibilityProfile = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
DiligentRenderer* GetActiveRenderer() {
|
||||
auto* backend = dynamic_cast<BackendObject_Diligent*>(pActiveBackendObject.get());
|
||||
return backend != nullptr ? backend->GetRenderer() : nullptr;
|
||||
}
|
||||
|
||||
struct DrawArraysIndirectCommand {
|
||||
Uint32 Count = 0;
|
||||
Uint32 InstanceCount = 0;
|
||||
Uint32 First = 0;
|
||||
Uint32 BaseInstance = 0;
|
||||
};
|
||||
|
||||
struct DrawElementsIndirectCommand {
|
||||
Uint32 Count = 0;
|
||||
Uint32 InstanceCount = 0;
|
||||
Uint32 FirstIndex = 0;
|
||||
Int32 BaseVertex = 0;
|
||||
Uint32 BaseInstance = 0;
|
||||
};
|
||||
|
||||
struct CpuTimerQuery {
|
||||
std::chrono::steady_clock::time_point Start;
|
||||
Uint64 TimestampNs = 0;
|
||||
Bool Available = false;
|
||||
};
|
||||
|
||||
const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) {
|
||||
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (drawBuffer) {
|
||||
drawBuffer->SyncPersistentMappedRange();
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
if (drawBuffer->MappedData() == nullptr || commandOffset + requiredBytes > drawBuffer->GetSize()) {
|
||||
MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label);
|
||||
return nullptr;
|
||||
}
|
||||
return drawBuffer->MappedData() + commandOffset;
|
||||
}
|
||||
if (indirect == nullptr) {
|
||||
MGLOG_E_ONCE("%s skipped: indirect pointer is null", label);
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<const Uint8*>(indirect);
|
||||
}
|
||||
|
||||
void Clear(GLbitfield mask) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
|
||||
return;
|
||||
}
|
||||
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
const auto& color = MG_State::pGLContext->GetClearColor();
|
||||
renderer->Clear(color.x(), color.y(), color.z(), color.w());
|
||||
}
|
||||
if ((mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
renderer->ClearDepth(MG_State::pGLContext->GetClearDepth());
|
||||
}
|
||||
if ((mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
renderer->ClearStencil(MG_State::pGLContext->GetClearStencil());
|
||||
}
|
||||
}
|
||||
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->DrawFromState(mode, first, count, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices) {
|
||||
// The CPU-side UploadVertexDataFromState path already honors the selected index
|
||||
// range. start/end only restrict which indices may be referenced; they do not
|
||||
// change the vertex buffer layout for this backend.
|
||||
(void)start;
|
||||
(void)end;
|
||||
DrawElements(mode, count, type, indices);
|
||||
}
|
||||
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex) {
|
||||
(void)start;
|
||||
(void)end;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] > 0) {
|
||||
renderer->DrawFromState(mode, first[i], count[i], 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] > 0) {
|
||||
renderer->DrawFromState(mode, 0, count[i], type, indices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLint basevertex) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type,
|
||||
const GLvoid* const* indices, GLsizei drawcount,
|
||||
const GLint* basevertex) {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] > 0) {
|
||||
DrawElementsBaseVertex(mode, count[i], type, indices[i],
|
||||
basevertex != nullptr ? basevertex[i] : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto* bytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawArraysIndirectCommand),
|
||||
"DrawArraysIndirect");
|
||||
if (bytes == nullptr) {
|
||||
return;
|
||||
}
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, bytes, sizeof(cmd));
|
||||
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
|
||||
return;
|
||||
}
|
||||
for (Uint32 i = 0; i < cmd.InstanceCount; ++i) {
|
||||
renderer->DrawFromState(mode, static_cast<GLint>(cmd.First), static_cast<GLsizei>(cmd.Count),
|
||||
0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr) {
|
||||
return;
|
||||
}
|
||||
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
|
||||
if (indexSize == 0) {
|
||||
return;
|
||||
}
|
||||
const auto* bytes = ResolveIndirectCommandBytes(indirect, sizeof(DrawElementsIndirectCommand),
|
||||
"DrawElementsIndirect");
|
||||
if (bytes == nullptr) {
|
||||
return;
|
||||
}
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, bytes, sizeof(cmd));
|
||||
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
|
||||
return;
|
||||
}
|
||||
const void* indices = reinterpret_cast<const void*>(static_cast<SizeT>(cmd.FirstIndex) * indexSize);
|
||||
for (Uint32 i = 0; i < cmd.InstanceCount; ++i) {
|
||||
renderer->DrawFromState(mode, 0, static_cast<GLsizei>(cmd.Count), type, indices,
|
||||
static_cast<GLint>(cmd.BaseVertex));
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || drawcount <= 0) {
|
||||
return;
|
||||
}
|
||||
const GLsizei realStride = stride == 0 ? static_cast<GLsizei>(sizeof(DrawArraysIndirectCommand)) : stride;
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
const auto* bytes = ResolveIndirectCommandBytes(
|
||||
static_cast<const Uint8*>(indirect) + static_cast<SizeT>(i) * static_cast<SizeT>(realStride),
|
||||
sizeof(DrawArraysIndirectCommand), "MultiDrawArraysIndirect");
|
||||
if (bytes == nullptr) {
|
||||
continue;
|
||||
}
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, bytes, sizeof(cmd));
|
||||
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
for (Uint32 instance = 0; instance < cmd.InstanceCount; ++instance) {
|
||||
renderer->DrawFromState(mode, static_cast<GLint>(cmd.First),
|
||||
static_cast<GLsizei>(cmd.Count), 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || drawcount <= 0) {
|
||||
return;
|
||||
}
|
||||
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
|
||||
if (indexSize == 0) {
|
||||
return;
|
||||
}
|
||||
const GLsizei realStride = stride == 0 ? static_cast<GLsizei>(sizeof(DrawElementsIndirectCommand)) : stride;
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
const auto* bytes = ResolveIndirectCommandBytes(
|
||||
static_cast<const Uint8*>(indirect) + static_cast<SizeT>(i) * static_cast<SizeT>(realStride),
|
||||
sizeof(DrawElementsIndirectCommand), "MultiDrawElementsIndirect");
|
||||
if (bytes == nullptr) {
|
||||
continue;
|
||||
}
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, bytes, sizeof(cmd));
|
||||
if (cmd.Count == 0 || cmd.InstanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
const void* indices = reinterpret_cast<const void*>(static_cast<SizeT>(cmd.FirstIndex) * indexSize);
|
||||
for (Uint32 instance = 0; instance < cmd.InstanceCount; ++instance) {
|
||||
renderer->DrawFromState(mode, 0, static_cast<GLsizei>(cmd.Count), type, indices,
|
||||
static_cast<GLint>(cmd.BaseVertex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
if (MG_State::pGLContext == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto paramBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
if (!paramBuffer) {
|
||||
return;
|
||||
}
|
||||
paramBuffer->SyncPersistentMappedRange();
|
||||
const Uint8* paramData = paramBuffer->MappedData();
|
||||
if (paramData == nullptr) {
|
||||
return;
|
||||
}
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, paramData + static_cast<SizeT>(drawcount), sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
MultiDrawArraysIndirect(mode, indirect, static_cast<GLsizei>(actualDrawCount), stride);
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect,
|
||||
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) {
|
||||
if (MG_State::pGLContext == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto paramBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
if (!paramBuffer) {
|
||||
return;
|
||||
}
|
||||
paramBuffer->SyncPersistentMappedRange();
|
||||
const Uint8* paramData = paramBuffer->MappedData();
|
||||
if (paramData == nullptr) {
|
||||
return;
|
||||
}
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, paramData + static_cast<SizeT>(drawcount), sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
MultiDrawElementsIndirect(mode, type, indirect, static_cast<GLsizei>(actualDrawCount), stride);
|
||||
}
|
||||
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || instancecount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < instancecount; ++i) {
|
||||
renderer->DrawFromState(mode, first, count, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance) {
|
||||
(void)baseinstance;
|
||||
DrawArraysInstanced(mode, first, count, instancecount);
|
||||
}
|
||||
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || instancecount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < instancecount; ++i) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || instancecount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < instancecount; ++i) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance) {
|
||||
(void)baseinstance;
|
||||
DrawElementsInstanced(mode, count, type, indices, instancecount);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type,
|
||||
const void* indices, GLsizei instancecount,
|
||||
GLint basevertex, GLuint baseinstance) {
|
||||
(void)baseinstance;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || instancecount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (GLsizei i = 0; i < instancecount; ++i) {
|
||||
renderer->DrawFromState(mode, 0, count, type, indices, basevertex);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || value == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (buffer == GL_COLOR && drawbuffer == 0) {
|
||||
renderer->Clear(value[0], value[1], value[2], value[3]);
|
||||
} else if (buffer == GL_DEPTH && drawbuffer == 0) {
|
||||
renderer->ClearDepth(value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {
|
||||
if (value == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (buffer == GL_STENCIL) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->ClearStencil(static_cast<Uint32>(value[0]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Float color[4] = {
|
||||
static_cast<Float>(value[0]) / 255.0f,
|
||||
static_cast<Float>(value[1]) / 255.0f,
|
||||
static_cast<Float>(value[2]) / 255.0f,
|
||||
static_cast<Float>(value[3]) / 255.0f,
|
||||
};
|
||||
ClearBufferfv(buffer, drawbuffer, color);
|
||||
}
|
||||
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {
|
||||
if (value == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (buffer == GL_STENCIL) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->ClearStencil(value[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Float color[4] = {
|
||||
static_cast<Float>(value[0]) / 255.0f,
|
||||
static_cast<Float>(value[1]) / 255.0f,
|
||||
static_cast<Float>(value[2]) / 255.0f,
|
||||
static_cast<Float>(value[3]) / 255.0f,
|
||||
};
|
||||
ClearBufferfv(buffer, drawbuffer, color);
|
||||
}
|
||||
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || buffer != GL_DEPTH_STENCIL) {
|
||||
return;
|
||||
}
|
||||
(void)drawbuffer;
|
||||
renderer->ClearDepth(depth);
|
||||
renderer->ClearStencil(static_cast<Uint32>(stencil));
|
||||
}
|
||||
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || pixels == nullptr) {
|
||||
return;
|
||||
}
|
||||
// The Diligent backend's offscreen targets are RGBA8; the frontend currently
|
||||
// uses this entry for the common GL_RGBA/GL_UNSIGNED_BYTE readback path.
|
||||
if (format != GL_RGBA || type != GL_UNSIGNED_BYTE) {
|
||||
return;
|
||||
}
|
||||
renderer->ReadPixels(static_cast<Uint32>(x), static_cast<Uint32>(y),
|
||||
static_cast<Uint32>(width), static_cast<Uint32>(height), pixels);
|
||||
}
|
||||
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1,
|
||||
mask, filter);
|
||||
}
|
||||
}
|
||||
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter) {
|
||||
(void)srcX0;
|
||||
(void)srcY0;
|
||||
(void)srcX1;
|
||||
(void)srcY1;
|
||||
(void)dstX0;
|
||||
(void)dstY0;
|
||||
(void)dstX1;
|
||||
(void)dstY1;
|
||||
(void)filter;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->BlitNamedFramebuffer(readFramebuffer, drawFramebuffer, mask);
|
||||
}
|
||||
}
|
||||
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height, GLint border) {
|
||||
(void)level;
|
||||
(void)internalformat;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)border;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
|
||||
return;
|
||||
}
|
||||
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
|
||||
if (texture) {
|
||||
renderer->CopyReadFramebufferToTexture(*texture);
|
||||
}
|
||||
}
|
||||
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height) {
|
||||
(void)level;
|
||||
(void)xoffset;
|
||||
(void)yoffset;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)width;
|
||||
(void)height;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
|
||||
return;
|
||||
}
|
||||
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
|
||||
if (texture) {
|
||||
renderer->CopyReadFramebufferToTexture(*texture);
|
||||
}
|
||||
}
|
||||
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D ||
|
||||
format != GL_RGBA || type != GL_UNSIGNED_BYTE || pixels == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
|
||||
if (texture) {
|
||||
renderer->ReadTextureImage(*texture, static_cast<Uint32>(level), pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||
GLsizei bufSize, GLvoid* pixels) {
|
||||
(void)uploadTarget;
|
||||
(void)bufSize;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || !texture || format != GL_RGBA || type != GL_UNSIGNED_BYTE ||
|
||||
pixels == nullptr) {
|
||||
return;
|
||||
}
|
||||
renderer->ReadTextureImage(*texture, static_cast<Uint32>(level), pixels);
|
||||
}
|
||||
|
||||
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
(void)srcTarget;
|
||||
(void)srcLevel;
|
||||
(void)srcX;
|
||||
(void)srcY;
|
||||
(void)srcZ;
|
||||
(void)dstTarget;
|
||||
(void)dstLevel;
|
||||
(void)dstX;
|
||||
(void)dstY;
|
||||
(void)dstZ;
|
||||
(void)srcWidth;
|
||||
(void)srcHeight;
|
||||
(void)srcDepth;
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr && srcTexture && dstTexture) {
|
||||
renderer->CopyTextureSubData(*srcTexture, *dstTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateMipmap(GLenum target) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer == nullptr || MG_State::pGLContext == nullptr || target != GL_TEXTURE_2D) {
|
||||
return;
|
||||
}
|
||||
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = unit.GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
|
||||
if (texture) {
|
||||
renderer->GenerateMipmap(*texture);
|
||||
}
|
||||
}
|
||||
|
||||
Bool IsTimerQuerySupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
BackendQueryHandle BeginTimeElapsedQuery() {
|
||||
auto* query = new CpuTimerQuery;
|
||||
query->Start = std::chrono::steady_clock::now();
|
||||
query->Available = false;
|
||||
return query;
|
||||
}
|
||||
|
||||
void EndTimeElapsedQuery(BackendQueryHandle query) {
|
||||
if (query == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto* cpuQuery = static_cast<CpuTimerQuery*>(query);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
cpuQuery->TimestampNs = static_cast<Uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(now - cpuQuery->Start).count());
|
||||
cpuQuery->Available = true;
|
||||
}
|
||||
|
||||
BackendQueryHandle QueryCounterTimestamp() {
|
||||
auto* query = new CpuTimerQuery;
|
||||
query->TimestampNs = static_cast<Uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||
query->Available = true;
|
||||
return query;
|
||||
}
|
||||
|
||||
Bool IsQueryResultAvailable(BackendQueryHandle query) {
|
||||
return query != nullptr && static_cast<CpuTimerQuery*>(query)->Available;
|
||||
}
|
||||
|
||||
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds) {
|
||||
if (query == nullptr || outNanoseconds == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto* cpuQuery = static_cast<CpuTimerQuery*>(query);
|
||||
if (!cpuQuery->Available && !wait) {
|
||||
return false;
|
||||
}
|
||||
*outNanoseconds = cpuQuery->TimestampNs;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeleteBackendQuery(BackendQueryHandle query) {
|
||||
delete static_cast<CpuTimerQuery*>(query);
|
||||
}
|
||||
|
||||
BackendSyncHandle FenceSync() {
|
||||
// CPU fallback fence: always signaled is a valid implementation for a
|
||||
// backend without native sync primitives. The handle still round-trips
|
||||
// through ClientWaitSync/DeleteSync so frontend state stays balanced.
|
||||
return new int(0);
|
||||
}
|
||||
|
||||
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout) {
|
||||
(void)sync;
|
||||
(void)flags;
|
||||
(void)timeout;
|
||||
return GL_ALREADY_SIGNALED;
|
||||
}
|
||||
|
||||
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout) {
|
||||
(void)sync;
|
||||
(void)flags;
|
||||
(void)timeout;
|
||||
}
|
||||
|
||||
void DeleteSync(BackendSyncHandle sync) {
|
||||
delete static_cast<int*>(sync);
|
||||
}
|
||||
|
||||
Bool GetSyncStatus(BackendSyncHandle sync) {
|
||||
(void)sync;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetSwapInterval(Int interval) {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->SetSwapInterval(interval > 0 ? static_cast<Uint32>(interval) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Present() {
|
||||
auto* renderer = GetActiveRenderer();
|
||||
if (renderer != nullptr) {
|
||||
renderer->Present();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BackendObject_Diligent::BackendObject_Diligent()
|
||||
: m_rendererInfo(BuildInitialRendererInfo()) {}
|
||||
|
||||
BackendObject_Diligent::~BackendObject_Diligent() {
|
||||
m_pRenderer.reset();
|
||||
m_pContext.Release();
|
||||
m_pDevice.Release();
|
||||
m_pFactoryVk = nullptr;
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::CreateDiligentDevice() {
|
||||
if (m_pDevice && m_pContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (m_pFactoryVk == nullptr) {
|
||||
m_pFactoryVk = ::Diligent::GetEngineFactoryVk();
|
||||
if (m_pFactoryVk == nullptr) {
|
||||
MGLOG_E("Diligent: failed to load Vulkan engine factory");
|
||||
return false;
|
||||
}
|
||||
m_pFactoryVk->SetBreakOnError(false);
|
||||
}
|
||||
|
||||
::Diligent::Uint32 numAdapters = 0;
|
||||
m_pFactoryVk->EnumerateAdapters(::Diligent::Version{}, numAdapters, nullptr);
|
||||
if (numAdapters == 0) {
|
||||
MGLOG_W("Diligent: no Vulkan adapters available; skipping device creation");
|
||||
return false;
|
||||
}
|
||||
|
||||
::Diligent::EngineVkCreateInfo engineCI;
|
||||
::Diligent::ImmediateContextCreateInfo ctxCI;
|
||||
ctxCI.Name = "MobileGL Diligent Main Context";
|
||||
ctxCI.QueueId = 0;
|
||||
ctxCI.Priority = ::Diligent::QUEUE_PRIORITY_MEDIUM;
|
||||
engineCI.NumImmediateContexts = 1;
|
||||
engineCI.pImmediateContextInfo = &ctxCI;
|
||||
|
||||
::Diligent::IRenderDevice* pDevice = nullptr;
|
||||
::Diligent::IDeviceContext* pContext = nullptr;
|
||||
m_pFactoryVk->CreateDeviceAndContextsVk(engineCI, &pDevice, &pContext);
|
||||
if (pDevice == nullptr || pContext == nullptr) {
|
||||
MGLOG_E("Diligent: failed to create Vulkan device/context");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pDevice.Attach(pDevice);
|
||||
m_pContext.Attach(pContext);
|
||||
MGLOG_I("Diligent: Vulkan device created");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
MGLOG_W("Diligent: Vulkan device creation failed: %s", e.what());
|
||||
return false;
|
||||
} catch (...) {
|
||||
MGLOG_W("Diligent: Vulkan device creation failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject_Diligent::Initialize() {
|
||||
if (m_initialized) {
|
||||
return;
|
||||
}
|
||||
if (!CreateDiligentDevice()) {
|
||||
MGLOG_W("Diligent: backend initialization failed");
|
||||
return;
|
||||
}
|
||||
|
||||
m_pRenderer = std::make_unique<DiligentRenderer>(m_pDevice, m_pContext);
|
||||
if (!m_pRenderer->Initialize(256, 256)) {
|
||||
MGLOG_W("Diligent: renderer initialization failed");
|
||||
m_pRenderer.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
m_functions.GL.Clear = Clear;
|
||||
m_functions.GL.DrawArrays = DrawArrays;
|
||||
m_functions.GL.DrawElements = DrawElements;
|
||||
m_functions.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||
m_functions.GL.DrawRangeElements = DrawRangeElements;
|
||||
m_functions.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||
m_functions.GL.MultiDrawArrays = MultiDrawArrays;
|
||||
m_functions.GL.MultiDrawElements = MultiDrawElements;
|
||||
m_functions.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||
m_functions.GL.DrawArraysInstanced = DrawArraysInstanced;
|
||||
m_functions.GL.DrawArraysInstancedBaseInstance = DrawArraysInstancedBaseInstance;
|
||||
m_functions.GL.DrawElementsInstanced = DrawElementsInstanced;
|
||||
m_functions.GL.DrawElementsInstancedBaseVertex = DrawElementsInstancedBaseVertex;
|
||||
m_functions.GL.DrawElementsInstancedBaseInstance = DrawElementsInstancedBaseInstance;
|
||||
m_functions.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||
m_functions.GL.DrawArraysIndirect = DrawArraysIndirect;
|
||||
m_functions.GL.DrawElementsIndirect = DrawElementsIndirect;
|
||||
m_functions.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||
m_functions.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
m_functions.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||
m_functions.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||
m_functions.GL.ClearBufferfv = ClearBufferfv;
|
||||
m_functions.GL.ClearBufferfi = ClearBufferfi;
|
||||
m_functions.GL.ClearBufferiv = ClearBufferiv;
|
||||
m_functions.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
m_functions.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
m_functions.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
m_functions.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
m_functions.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||
m_functions.GL.CopyImageSubData = CopyImageSubData;
|
||||
m_functions.GL.GenerateMipmap = GenerateMipmap;
|
||||
m_functions.GL.GetTexImage = GetTexImage;
|
||||
m_functions.GL.GetTextureImage = GetTextureImage;
|
||||
m_functions.GL.ReadPixels = ReadPixels;
|
||||
m_functions.GL.FenceSync = FenceSync;
|
||||
m_functions.GL.ClientWaitSync = ClientWaitSync;
|
||||
m_functions.GL.WaitSync = WaitSync;
|
||||
m_functions.GL.DeleteSync = DeleteSync;
|
||||
m_functions.GL.GetSyncStatus = GetSyncStatus;
|
||||
m_functions.GL.IsTimerQuerySupported = IsTimerQuerySupported;
|
||||
m_functions.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
||||
m_functions.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
||||
m_functions.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
||||
m_functions.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||
m_functions.GL.GetQueryResult64 = GetQueryResult64;
|
||||
m_functions.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
m_functions.Present = Present;
|
||||
m_functions.SetSwapInterval = SetSwapInterval;
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
DiligentRenderer* BackendObject_Diligent::GetRenderer() {
|
||||
return m_pRenderer.get();
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::InitCapabilities() {
|
||||
// Skeleton: no format probing yet. The backend advertises GL 3.2 core
|
||||
// capability, and the capability tables will be filled as resource
|
||||
// creation paths are ported.
|
||||
m_backendCapabilitiesInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::InitWindowSurface() {
|
||||
if (!m_windowHandle.Handle) {
|
||||
MGLOG_E("BackendObject_Diligent::InitWindowSurface failed: native window handle is null");
|
||||
return false;
|
||||
}
|
||||
if (m_pRenderer == nullptr || m_pFactoryVk == nullptr) {
|
||||
MGLOG_E("BackendObject_Diligent::InitWindowSurface failed: renderer/factory is not ready");
|
||||
return false;
|
||||
}
|
||||
return m_pRenderer->CreateSwapChain(m_pFactoryVk, m_windowHandle,
|
||||
m_windowHandle.Width, m_windowHandle.Height);
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::InitPbufferSurface(EGLint width, EGLint height) {
|
||||
// The Diligent backend keeps its offscreen target for pbuffer EGL surfaces.
|
||||
// A future enhancement can resize/recreate the offscreen target to match the
|
||||
// pbuffer dimensions.
|
||||
(void)width;
|
||||
(void)height;
|
||||
return m_pRenderer != nullptr;
|
||||
}
|
||||
|
||||
void BackendObject_Diligent::ReleaseEGLResources() {
|
||||
if (m_pRenderer != nullptr) {
|
||||
m_pRenderer->ReleaseSwapChain();
|
||||
}
|
||||
BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
void BackendObject_Diligent::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
if (m_pRenderer != nullptr) {
|
||||
m_pRenderer->ReleaseSwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("BackendObject_Diligent::CreateEGLWindowSurface failed: backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32)) {
|
||||
MGLOG_E("BackendObject_Diligent::CreateEGLWindowSurface failed: unsupported native window backend");
|
||||
return false;
|
||||
}
|
||||
return RegisterEGLWindowSurface(surface, handle);
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("BackendObject_Diligent::CreateEGLPbufferSurface failed: backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return RegisterEGLPbufferSurface(surface, width, height);
|
||||
}
|
||||
|
||||
Bool BackendObject_Diligent::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!BackendObject::ResizeEGLWindowSurface(surface, width, height)) {
|
||||
return false;
|
||||
}
|
||||
if (m_eglSurface == surface && m_pRenderer != nullptr) {
|
||||
return m_pRenderer->ResizeSwapChain(width, height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const RendererInfo& BackendObject_Diligent::GetRendererInfo() const {
|
||||
return m_rendererInfo;
|
||||
}
|
||||
|
||||
String BackendObject_Diligent::GetBackendAPIVersionString() const {
|
||||
return "Diligent Vulkan 0.1 (GL 3.2 skeleton)";
|
||||
}
|
||||
|
||||
const GlobalBackendFunctionsTable& BackendObject_Diligent::GetBackendFunctions() const {
|
||||
return m_functions;
|
||||
}
|
||||
|
||||
const DynamicBackendParameters& BackendObject_Diligent::GetDynamicParameters() const {
|
||||
return m_dynamicParameters;
|
||||
}
|
||||
|
||||
BackendType BackendObject_Diligent::GetBackendType() const {
|
||||
return BackendType::DiligentVulkan;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DiligentBackend
|
||||
@@ -0,0 +1,72 @@
|
||||
// MobileGL - MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Includes.h>
|
||||
#include "../BackendObject.h"
|
||||
|
||||
// X11 (pulled in by Includes.h through Vulkan-Headers) defines True/False as
|
||||
// macros, which collide with Diligent's Bool constants in BasicTypes.h.
|
||||
#if defined(True)
|
||||
#undef True
|
||||
#endif
|
||||
#if defined(False)
|
||||
#undef False
|
||||
#endif
|
||||
|
||||
#include <RefCntAutoPtr.hpp>
|
||||
|
||||
namespace Diligent {
|
||||
struct IEngineFactoryVk;
|
||||
struct IRenderDevice;
|
||||
struct IDeviceContext;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DiligentBackend {
|
||||
class DiligentRenderer;
|
||||
|
||||
// New Diligent/Vulkan backend, implemented from scratch on top of
|
||||
// DiligentCore. The backend object owns the Diligent device/context and
|
||||
// currently advertises OpenGL 3.2 core capability; the GL function table
|
||||
// is intentionally empty until drawing/resource paths are ported.
|
||||
class BackendObject_Diligent : public BackendObject {
|
||||
public:
|
||||
BackendObject_Diligent();
|
||||
~BackendObject_Diligent() override;
|
||||
|
||||
void Initialize() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||
BackendType GetBackendType() const override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
DiligentRenderer* GetRenderer();
|
||||
|
||||
private:
|
||||
Bool CreateDiligentDevice();
|
||||
|
||||
RendererInfo m_rendererInfo;
|
||||
DynamicBackendParameters m_dynamicParameters;
|
||||
GlobalBackendFunctionsTable m_functions{};
|
||||
::Diligent::IEngineFactoryVk* m_pFactoryVk = nullptr;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IRenderDevice> m_pDevice;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IDeviceContext> m_pContext;
|
||||
std::unique_ptr<DiligentRenderer> m_pRenderer;
|
||||
Bool m_initialized = false;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DiligentBackend
|
||||
@@ -0,0 +1,8 @@
|
||||
// MobileGL - MobileGL/MG_Backend/Diligent/DiligentVulkan.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include "DiligentVulkan.h"
|
||||
@@ -0,0 +1,17 @@
|
||||
// MobileGL - MobileGL/MG_Backend/Diligent/DiligentVulkan.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DiligentBackend {
|
||||
// Backend identity string used by the backend object and local smoke tests.
|
||||
inline String GetDiligentVulkanBackendName() {
|
||||
return "DiligentVulkan";
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DiligentBackend
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
// MobileGL - MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
// X11 (pulled in by Includes.h through Vulkan-Headers) defines True/False as
|
||||
// macros, which collide with Diligent's Bool constants in BasicTypes.h.
|
||||
#if defined(True)
|
||||
#undef True
|
||||
#endif
|
||||
#if defined(False)
|
||||
#undef False
|
||||
#endif
|
||||
|
||||
#include <RefCntAutoPtr.hpp>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
struct WindowHandle;
|
||||
}
|
||||
|
||||
namespace Diligent {
|
||||
struct IRenderDevice;
|
||||
struct IDeviceContext;
|
||||
struct ITexture;
|
||||
struct ITextureView;
|
||||
struct IPipelineState;
|
||||
struct IBuffer;
|
||||
struct ISampler;
|
||||
struct IShaderResourceBinding;
|
||||
struct ISwapChain;
|
||||
struct IEngineFactoryVk;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
class SamplerObject;
|
||||
class ProgramObject;
|
||||
class RenderbufferObject;
|
||||
class FramebufferObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DiligentBackend {
|
||||
// Minimal real Diligent renderer used to prove the GL 3.2 basic path:
|
||||
// clear an offscreen color target, draw a hardcoded triangle, and read
|
||||
// pixels back. This is the first concrete rendering layer on top of the
|
||||
// Diligent device; it will be expanded into the full MobileGL backend.
|
||||
class DiligentRenderer {
|
||||
public:
|
||||
DiligentRenderer(::Diligent::IRenderDevice* device, ::Diligent::IDeviceContext* context);
|
||||
~DiligentRenderer();
|
||||
|
||||
Bool Initialize(Uint32 width, Uint32 height);
|
||||
void Clear(Float r, Float g, Float b, Float a);
|
||||
void ClearDepth(Float depth);
|
||||
void ClearStencil(Uint32 stencil);
|
||||
void DrawTriangle();
|
||||
void DrawVertices(const Float* vertices, Uint32 vertexCount);
|
||||
// Creates a real Diligent swap chain for a native EGL window surface.
|
||||
Bool CreateSwapChain(::Diligent::IEngineFactoryVk* factory, const WindowHandle& handle,
|
||||
Uint32 width, Uint32 height);
|
||||
Bool ResizeSwapChain(Uint32 width, Uint32 height);
|
||||
void SetSwapInterval(Uint32 interval);
|
||||
// Creates a simple 2D RGBA8 texture from CPU data and makes it available
|
||||
// to state PSOs under the shader variable name "g_Texture".
|
||||
Bool CreateTestTexture(const void* data, Uint32 width, Uint32 height);
|
||||
// Draws using the live MG_State GL context: current program, VAO and
|
||||
// bound buffers. This is the front-end emulation entry point.
|
||||
void DrawFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices,
|
||||
GLint baseVertex = 0);
|
||||
void ReadPixels(Uint32 x, Uint32 y, Uint32 width, Uint32 height, void* pixels);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
|
||||
GLbitfield mask);
|
||||
void CopyReadFramebufferToTexture(MG_State::GLState::ITextureObject& dst);
|
||||
void CopyTextureSubData(MG_State::GLState::ITextureObject& src, MG_State::GLState::ITextureObject& dst);
|
||||
void GenerateMipmap(MG_State::GLState::ITextureObject& texture);
|
||||
Bool ReadTextureImage(MG_State::GLState::ITextureObject& texture, Uint32 level, void* pixels);
|
||||
void ReleaseSwapChain();
|
||||
void Present();
|
||||
|
||||
::Diligent::IRenderDevice* GetDevice() const { return m_pDevice; }
|
||||
::Diligent::IDeviceContext* GetContext() const { return m_pContext; }
|
||||
|
||||
private:
|
||||
struct TextureResource {
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITexture> Texture;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> SRV;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> RTV;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> DSV;
|
||||
Uint64 ContentVersion = 0;
|
||||
Uint16 ParamsVersion = 0;
|
||||
Bool IsDepth = false;
|
||||
};
|
||||
|
||||
struct SamplerResource {
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ISampler> Sampler;
|
||||
Uint16 Version = 0;
|
||||
};
|
||||
|
||||
Bool CreateOffscreenTargets();
|
||||
Bool CreatePipeline();
|
||||
Bool CreateVertexBuffer();
|
||||
Bool CreatePipelineFromState(GLenum mode);
|
||||
Bool UploadVertexDataFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices,
|
||||
GLint baseVertex = 0);
|
||||
::Diligent::ITextureView* SyncTexture(MG_State::GLState::ITextureObject& texture);
|
||||
::Diligent::ITextureView* SyncTextureForAttachment(MG_State::GLState::ITextureObject& texture, Bool depth);
|
||||
::Diligent::ITextureView* SyncRenderbuffer(MG_State::GLState::RenderbufferObject& renderbuffer);
|
||||
::Diligent::ISampler* SyncSampler(const MG_State::GLState::SamplerObject& sampler);
|
||||
Bool BindShaderResourcesFromState(const MG_State::GLState::ProgramObject& program);
|
||||
Bool UploadUBOFromState(const MG_State::GLState::ProgramObject& program);
|
||||
Bool ResolveCurrentRenderTargets(Vector<::Diligent::ITextureView*>& rtvs,
|
||||
::Diligent::ITextureView*& dsv);
|
||||
|
||||
::Diligent::IRenderDevice* m_pDevice = nullptr;
|
||||
::Diligent::IDeviceContext* m_pContext = nullptr;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pColorTarget;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pColorRTV;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pDepthTarget;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pDepthDSV;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ISwapChain> m_pSwapChain;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pTestTexture;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pTestSRV;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::ISampler> m_pTestSampler;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IShaderResourceBinding> m_pStateSRB;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IPipelineState> m_pPSO;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pVertexBuffer;
|
||||
::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pUBO;
|
||||
Uint32 m_uboSize = 0;
|
||||
Uint32 m_uboContentVersion = 0;
|
||||
Uint64 m_uboProgramLifetimeId = 0;
|
||||
UnorderedMap<Uint64, TextureResource> m_textureCache;
|
||||
UnorderedMap<Uint64, SamplerResource> m_samplerCache;
|
||||
UnorderedMap<Uint32, TextureResource> m_renderbufferCache;
|
||||
UnorderedMap<Uint64, ::Diligent::RefCntAutoPtr<::Diligent::IBuffer>> m_namedUboCache;
|
||||
Uint32 m_width = 256;
|
||||
Uint32 m_height = 256;
|
||||
Uint32 m_swapInterval = 0;
|
||||
Uint32 m_lastDrawVertexCount = 0;
|
||||
Uint64 m_lastPSOKey = 0;
|
||||
Bool m_hasCachedPSO = false;
|
||||
Bool m_initialized = false;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DiligentBackend
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "MG_Backend/BackendObject.h"
|
||||
#include "DirectVulkan.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_State/GLState/TextureState/TextureState.h"
|
||||
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
||||
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
||||
@@ -383,6 +384,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
if (MG_State::pGLContext) {
|
||||
MG_State::pGLContext->InvalidateCompileEnv();
|
||||
}
|
||||
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||
MutableFormatCapabilities());
|
||||
PrintFormatCapabilities(GetFormatCapabilities());
|
||||
@@ -687,6 +691,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_vulkanCaps = capabilities;
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
if (MG_State::pGLContext) {
|
||||
MG_State::pGLContext->InvalidateCompileEnv();
|
||||
}
|
||||
MutableFormatCapabilities().Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
|
||||
#if defined(MOBILEGL_ENABLE_DILIGENT)
|
||||
#include <MG_Backend/Diligent/BackendObject_Diligent.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
void LogBackendInfo() {
|
||||
@@ -55,6 +58,11 @@ namespace MobileGL::MG_Backend {
|
||||
case BackendType::DirectVulkan:
|
||||
pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
|
||||
break;
|
||||
#if defined(MOBILEGL_ENABLE_DILIGENT)
|
||||
case BackendType::DiligentVulkan:
|
||||
pActiveBackendObject = MakeUnique<DiligentBackend::BackendObject_Diligent>();
|
||||
break;
|
||||
#endif
|
||||
case BackendType::Unknown:
|
||||
default:
|
||||
MGLOG_W("Unknown backend type, defaulting to unknown backend");
|
||||
|
||||
@@ -175,14 +175,14 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
|
||||
struct ContextObject {
|
||||
Display* XDisplay = nullptr;
|
||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||
EGLDisplay Dpy = EGL_NO_DISPLAY;
|
||||
EGLConfig Config = nullptr;
|
||||
EGLContext Context = EGL_NO_CONTEXT;
|
||||
const FBConfigInfo* FBConfig = nullptr;
|
||||
};
|
||||
|
||||
struct DrawableSurface {
|
||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||
EGLDisplay Dpy = EGL_NO_DISPLAY;
|
||||
EGLSurface Surface = EGL_NO_SURFACE;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
@@ -294,7 +294,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
if (width == surface.Width && height == surface.Height) {
|
||||
return;
|
||||
}
|
||||
if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface,
|
||||
if (EGLImpl::ResizePlatformWindowSurface(surface.Dpy, surface.Surface,
|
||||
static_cast<EGLint>(width),
|
||||
static_cast<EGLint>(height))) {
|
||||
surface.Width = width;
|
||||
@@ -324,7 +324,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(
|
||||
context.Display, context.Config, reinterpret_cast<void*>(drawable), attribs);
|
||||
context.Dpy, context.Config, reinterpret_cast<void*>(drawable), attribs);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable,
|
||||
width, height);
|
||||
@@ -332,7 +332,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
}
|
||||
|
||||
DrawableSurface record;
|
||||
record.Display = context.Display;
|
||||
record.Dpy = context.Dpy;
|
||||
record.Surface = surface;
|
||||
record.Width = width;
|
||||
record.Height = height;
|
||||
@@ -388,7 +388,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
|
||||
ContextObject object;
|
||||
object.XDisplay = dpy;
|
||||
object.Display = display;
|
||||
object.Dpy = display;
|
||||
object.Config = config;
|
||||
object.Context = eglContext;
|
||||
object.FBConfig = fbconfig;
|
||||
@@ -895,7 +895,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
return;
|
||||
}
|
||||
if (object->Context != EGL_NO_CONTEXT) {
|
||||
EGLImpl::DestroyContext(object->Display, object->Context);
|
||||
EGLImpl::DestroyContext(object->Dpy, object->Context);
|
||||
}
|
||||
Contexts().erase(context);
|
||||
}
|
||||
@@ -929,7 +929,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface,
|
||||
if (!EGLImpl::MakeCurrent(object->Dpy, surface->Surface, surface->Surface,
|
||||
object->Context)) {
|
||||
MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context);
|
||||
return 0;
|
||||
@@ -962,7 +962,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
return;
|
||||
}
|
||||
SyncSurfaceSize(dpy, drawable, it->second);
|
||||
EGLImpl::SwapBuffers(it->second.Display, it->second.Surface);
|
||||
EGLImpl::SwapBuffers(it->second.Dpy, it->second.Surface);
|
||||
}
|
||||
|
||||
GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window,
|
||||
@@ -988,7 +988,7 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
if (it == surfaces.end()) {
|
||||
return;
|
||||
}
|
||||
EGLImpl::DestroySurface(it->second.Display, it->second.Surface);
|
||||
EGLImpl::DestroySurface(it->second.Dpy, it->second.Surface);
|
||||
surfaces.erase(it);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace MobileGL::MG_State {
|
||||
return m_compileEnv;
|
||||
}
|
||||
|
||||
void GLContext::InvalidateCompileEnv() {
|
||||
m_compileEnv.reset();
|
||||
m_compileEnvBackend = nullptr;
|
||||
}
|
||||
|
||||
// Error
|
||||
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
|
||||
// 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(),
|
||||
// so there is no backend to query yet. Re-captured whenever the active backend
|
||||
// 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.
|
||||
const SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv>& GetCompileEnv();
|
||||
void InvalidateCompileEnv();
|
||||
|
||||
private:
|
||||
// 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)
|
||||
add_subdirectory(Backend/DirectVulkan)
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
@@ -2448,9 +2301,6 @@ TEST_F(ProgramUtilTest, CompileEnvFingerprintTracksEveryInput) {
|
||||
otherExtensions.advertisedExtensions.push_back(MobileGL::E_GL_ARB_gpu_shader_int64);
|
||||
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:
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
|
||||
#include <MG_Util/Math/HalfFloat.h>
|
||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <MG_Util/Types.h>
|
||||
@@ -710,6 +711,37 @@ TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) {
|
||||
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) {
|
||||
using namespace MobileGL;
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace MobileGL {
|
||||
|
||||
std::string GetThreadName() {
|
||||
char buffer[64] = {0};
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
#if defined(_WIN32)
|
||||
PWSTR desc = nullptr;
|
||||
if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &desc))) {
|
||||
WideCharToMultiByte(CP_UTF8, 0, desc, -1, buffer, sizeof(buffer), nullptr, nullptr);
|
||||
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));
|
||||
#endif
|
||||
return buffer[0] ? buffer : "UnknownThread";
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
HashBytes(state, env.advertisedExtensions.data(),
|
||||
env.advertisedExtensions.size() * sizeof(GLExtension));
|
||||
}
|
||||
HashValue(state, env.subgroupPrefixScanQuirk);
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -73,8 +72,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
kFrontendMaxComputeWorkGroupInvocations)
|
||||
: kFrontendMaxComputeWorkGroupInvocations;
|
||||
|
||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
||||
|
||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||
return env;
|
||||
}
|
||||
@@ -84,7 +81,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
// computed, and this must not run before MG_Config is loaded.
|
||||
static const SharedPtr<const CompileEnv> kDefault = [] {
|
||||
auto env = MakeShared<CompileEnv>();
|
||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||
return SharedPtr<const CompileEnv>(Move(env));
|
||||
}();
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
|
||||
namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
// Everything the shader compile/link pipeline reads from OUTSIDE its own (stage, source)
|
||||
// inputs: backend identity, backend limits, the advertised extension list, and the one
|
||||
// config quirk the source rewriter branches on.
|
||||
// everything outside (stage, source) this reads - advertised extensions and backend limits -
|
||||
// so the transformation is a pure function of its three arguments and can run on a worker
|
||||
// thread.
|
||||
//
|
||||
// Why it exists (P1): every one of those reads is a reach-back into
|
||||
// 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
|
||||
Vector<GLExtension> advertisedExtensions;
|
||||
|
||||
// --- config the source rewriter branches on ---
|
||||
MG_Config::QuirkOverride subgroupPrefixScanQuirk = MG_Config::QuirkOverride::Auto;
|
||||
|
||||
Uint64 fingerprint = 0; // set by CaptureCompileEnv()
|
||||
|
||||
Bool HasBackend() const { return backend != BackendType::Unknown; }
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace {
|
||||
using MobileGL::SizeT;
|
||||
using MobileGL::String;
|
||||
using MobileGL::Uint32;
|
||||
using MobileGL::Vector;
|
||||
|
||||
bool IsIdentifierChar(char ch) {
|
||||
@@ -181,331 +182,6 @@ namespace {
|
||||
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) {
|
||||
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
|
||||
pos++;
|
||||
@@ -1253,117 +929,6 @@ namespace {
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
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) {
|
||||
PreprocessShaderSource(stage, source, *GetCurrentCompileEnv());
|
||||
}
|
||||
@@ -1403,7 +968,6 @@ namespace MobileGL {
|
||||
ModernizeLegacyGLSL(stage, source, afterVersion);
|
||||
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
||||
|
||||
ApplyShaderSourceQuirks(env, stage, source);
|
||||
}
|
||||
|
||||
Bool RetargetLegacyVersionDirectiveTo460(String& source) {
|
||||
|
||||
@@ -30,18 +30,6 @@ namespace MobileGL {
|
||||
// tests and diagnostics that drive the preprocessor standalone.
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -86,7 +86,7 @@ val pluginRendererConfig = buildJsonValue {
|
||||
selectable(
|
||||
key = "MOBILEGL_BACKEND_TYPE",
|
||||
title = RendererConfig.MetaString("mobilegl_backend_type_title"),
|
||||
items = RendererConfig.EnvItems("DirectGLES", listOf("DirectVulkan")),
|
||||
items = RendererConfig.EnvItems("DirectGLES", listOf("DirectVulkan", "DiligentVulkan")),
|
||||
)
|
||||
toggleable("MOBILEGL_DISABLE_TIMERQUERY", "1", false, RendererConfig.MetaString("mobilegl_disable_timerquery_title"))
|
||||
toggleable("MOBILEGL_DISABLE_SUBGROUP", "1", false, RendererConfig.MetaString("mobilegl_disable_subgroup_title"))
|
||||
|
||||
@@ -221,10 +221,13 @@ typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
/* `long` is 32-bit on LLP64 Windows, including 64-bit MinGW. Use the
|
||||
* standard pointer-sized integer types so these remain pointer-width there. */
|
||||
#include <stdint.h>
|
||||
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
|
||||
/*
|
||||
|
||||
@@ -277,11 +277,14 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png",
|
||||
"target_call": 202020,
|
||||
"timeout_seconds": 1800
|
||||
"timeout_seconds": 1800,
|
||||
"ssim_threshold": 0.98
|
||||
},
|
||||
{
|
||||
"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")
|
||||
CI_BACKENDS = ("DirectGLES", "DirectVulkan")
|
||||
|
||||
|
||||
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)]
|
||||
|
||||
|
||||
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):
|
||||
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(
|
||||
"--format",
|
||||
choices=("names", "github-apk", "fixture-files", "cmake"),
|
||||
choices=(
|
||||
"names",
|
||||
"github-test-matrix",
|
||||
"github-apk",
|
||||
"github-apk-matrix",
|
||||
"fixture-files",
|
||||
"cmake",
|
||||
),
|
||||
default="names",
|
||||
)
|
||||
return parser.parse_args()
|
||||
@@ -127,8 +175,12 @@ def main():
|
||||
cases = ci_trace_cases(cases)
|
||||
if args.format == "names":
|
||||
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":
|
||||
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":
|
||||
if not args.case_name:
|
||||
print("--case is required for --format fixture-files", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user