From 3823fe528c9ba2a05dd2a5d8ef9ad365ff86a3da Mon Sep 17 00:00:00 2001 From: HH45137 Date: Mon, 12 Oct 2026 00:20:53 +0800 Subject: [PATCH] [Feat, Test]: Add NV mesh shader support Advertise GL_NV_mesh_shader in the DirectVulkan backend and expose the new task/mesh shader stages in the GL-to-MG and glslang conversion layers. This also wires the mesh task drawing entry points and adds a focused MeshShader test covering extension discovery and task shader compilation. --- .../BackendObject_DirectVulkan.cpp | 8 ++- .../DirectVulkan/BackendObject_DirectVulkan.h | 3 +- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 8 +++ .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 9 +++ MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 5 ++ .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 8 +-- .../GLState/ProgramState/ShaderStage.h | 2 + MobileGL/MG_Test/CMakeLists.txt | 1 + MobileGL/MG_Test/MeshShader/CMakeLists.txt | 20 ++++++ .../MG_Test/MeshShader/MeshShaderTest.cpp | 69 +++++++++++++++++++ .../GLToGlslang/ProgramEnumConverter.cpp | 4 ++ .../GLToMG/ProgramEnumConverter.cpp | 4 ++ .../Converters/GLToStr/GLEnumConverter.cpp | 2 + 13 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 MobileGL/MG_Test/MeshShader/CMakeLists.txt create mode 100644 MobileGL/MG_Test/MeshShader/MeshShaderTest.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 6966b05e..8ff0e343 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -521,7 +521,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Bool anisotropicFilteringSupported, Bool nonZeroIndirectBaseInstanceSupported, - Bool cubeMapArraySupported) { + Bool cubeMapArraySupported, + Bool nvMeshShader) { Vector extensions = { // The version tokens have to reach the version the backend actually claims: // TargetGLVersion is {4,6,0}, and a list that stopped at OpenGL40 told an @@ -685,6 +686,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (cubeMapArraySupported) { extensions.push_back(E_GL_ARB_texture_cube_map_array); } + + if (nvMeshShader) { + extensions.push_back(E_GL_NV_mesh_shader); + } + return extensions; } diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 30a14d59..0ffbd3eb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -77,7 +77,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Bool anisotropicFilteringSupported, Bool nonZeroIndirectBaseInstanceSupported, - Bool cubeMapArraySupported); + Bool cubeMapArraySupported, + Bool nvMeshShader = true); // Format: , Vulkan , Driver — the exact // string an initialized backend returns from GetBackendAPIVersionString (and that diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 0e439b31..ba152278 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -687,6 +687,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetIntegeri_v called with null VulkanRenderer"); switch (target) { case GL_MAX_COMPUTE_WORK_GROUP_COUNT: + if (!pVulkanRenderer) { + *data = 0; + return; + } if (index >= 3) { *data = 0; return; @@ -695,6 +699,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->GetPhysicalDevice().properties.limits.maxComputeWorkGroupCount[index]); return; case GL_MAX_COMPUTE_WORK_GROUP_SIZE: + if (!pVulkanRenderer) { + *data = 0; + return; + } if (index >= 3) { *data = 0; return; diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index d76e8019..337a4002 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -1196,6 +1196,15 @@ namespace MobileGL::MG_Impl::GLImpl { DrawElements_Backend(mode, count, type, indices); } + void DrawMeshTasksNV(GLuint first, GLuint count) {} + + void DrawMeshTasksIndirectNV(GLintptr indirect) {} + + void MultiDrawMeshTasksIndirectNV(GLintptr indirect, GLsizei drawcount, GLsizei stride) {} + + void MultiDrawMeshTasksIndirectCountNV(GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, + GLsizei stride) {} + void BeginTransformFeedback(GLenum primitiveMode) { if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) { MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 91ddf633..48fae44a 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -66,4 +66,9 @@ namespace MobileGL::MG_Impl::GLImpl { GLsizei drawcount, const GLint* basevertex); void Clear(GLbitfield mask); void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); + + void DrawMeshTasksNV(GLuint first, GLuint count); + void DrawMeshTasksIndirectNV(GLintptr indirect); + void MultiDrawMeshTasksIndirectNV(GLintptr indirect, GLsizei drawcount, GLsizei stride); + void MultiDrawMeshTasksIndirectCountNV(GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index ed94936a..573092ef 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -2412,10 +2412,10 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, BufferPageCommitmentMemNV, GLenum target, GL DECLARE_GL_FUNCTION_STUB_HEAD(void, TexPageCommitmentMemNV, GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexPageCommitmentMemNV, target, layer, level, xoffset, yoffset, zoffset, width, height, depth, memory, offset, commit) DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedBufferPageCommitmentMemNV, GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedBufferPageCommitmentMemNV, buffer, offset, size, memory, memOffset, commit) DECLARE_GL_FUNCTION_STUB_HEAD(void, TexturePageCommitmentMemNV, GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexturePageCommitmentMemNV, texture, layer, level, xoffset, yoffset, zoffset, width, height, depth, memory, offset, commit) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawMeshTasksNV, GLuint first, GLuint count) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawMeshTasksNV, first, count) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawMeshTasksIndirectNV, GLintptr indirect) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawMeshTasksIndirectNV, indirect) -DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawMeshTasksIndirectNV, GLintptr indirect, GLsizei drawcount, GLsizei stride) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawMeshTasksIndirectNV, indirect, drawcount, stride) -DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawMeshTasksIndirectCountNV, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawMeshTasksIndirectCountNV, indirect, drawcount, maxdrawcount, stride) +DECLARE_GL_FUNCTION_HEAD(void, DrawMeshTasksNV, GLuint first, GLuint count) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawMeshTasksNV, first, count) +DECLARE_GL_FUNCTION_HEAD(void, DrawMeshTasksIndirectNV, GLintptr indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawMeshTasksIndirectNV, indirect) +DECLARE_GL_FUNCTION_HEAD(void, MultiDrawMeshTasksIndirectNV, GLintptr indirect, GLsizei drawcount, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawMeshTasksIndirectNV, indirect, drawcount, stride) +DECLARE_GL_FUNCTION_HEAD(void, MultiDrawMeshTasksIndirectCountNV, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawMeshTasksIndirectCountNV, indirect, drawcount, maxdrawcount, stride) DECLARE_GL_FUNCTION_STUB_HEAD(void, GenOcclusionQueriesNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenOcclusionQueriesNV, n, ids) DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteOcclusionQueriesNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteOcclusionQueriesNV, n, ids) DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsOcclusionQueryNV, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsOcclusionQueryNV, id) diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderStage.h b/MobileGL/MG_State/GLState/ProgramState/ShaderStage.h index f9d17d2c..a5f8aea7 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderStage.h +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderStage.h @@ -18,6 +18,8 @@ namespace MobileGL { TessEval, Geometry, Fragment, + Task, + Mesh, Compute, ShaderStageCount, Unknown = -1 diff --git a/MobileGL/MG_Test/CMakeLists.txt b/MobileGL/MG_Test/CMakeLists.txt index 394095de..22d73cda 100644 --- a/MobileGL/MG_Test/CMakeLists.txt +++ b/MobileGL/MG_Test/CMakeLists.txt @@ -89,6 +89,7 @@ add_subdirectory(Swapchain) add_subdirectory(ShaderTranspiler) add_subdirectory(Util) add_subdirectory(SelfTest) +add_subdirectory(MeshShader) # The DirectGLES post-transpile ESSL passes are pure String -> String, so unlike the # DirectVulkan suite below this one needs no device and always builds. add_subdirectory(Backend/DirectGLES) diff --git a/MobileGL/MG_Test/MeshShader/CMakeLists.txt b/MobileGL/MG_Test/MeshShader/CMakeLists.txt new file mode 100644 index 00000000..e28ab4a6 --- /dev/null +++ b/MobileGL/MG_Test/MeshShader/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.14) + +add_executable( + MeshShaderTest + MeshShaderTest.cpp +) + +target_include_directories(MeshShaderTest PRIVATE + ${MGL_ROOT}/include + ${MGL_ROOT}/MobileGL +) + +target_link_libraries( + MeshShaderTest PRIVATE + GTest::gtest_main + ${LINK_LIBRARIES} +) + +include(GoogleTest) +gtest_discover_tests(MeshShaderTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) diff --git a/MobileGL/MG_Test/MeshShader/MeshShaderTest.cpp b/MobileGL/MG_Test/MeshShader/MeshShaderTest.cpp new file mode 100644 index 00000000..53296eb3 --- /dev/null +++ b/MobileGL/MG_Test/MeshShader/MeshShaderTest.cpp @@ -0,0 +1,69 @@ +// MobileGL - MobileGL/MG_Test/MeshShader/MeshShader.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include + +#include + +#include "Includes.h" +#include "Init.h" +#include "MG_Impl/GLImpl/Program/GL_Program.h" + +#include +#include +#include +#include +#include + +using namespace MobileGL; + +class MeshShaderTest : public ::testing::Test { +protected: + void SetUp() override { MobileGL::Initialize(); } + + void TearDown() override { + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind"; + } +}; + +TEST_F(MeshShaderTest, IsHaveExt) { + const auto* extensionsString = reinterpret_cast(MG_Impl::GLImpl::GetString(GL_EXTENSIONS)); + ASSERT_NE(extensionsString, nullptr); + // std::cout << extensionsString << std::endl; + const Bool isHaveExt = String(extensionsString).find("GL_NV_mesh_shader") != String::npos; + ASSERT_EQ(isHaveExt, true); +} + +TEST_F(MeshShaderTest, TaskShaderCompile) { + const GLuint shader = MG_Impl::GLImpl::CreateShader(GL_TASK_SHADER_NV); + const char* src = R"GLSL( + #version 460 + #extension GL_NV_mesh_shader : require + + layout(local_size_x = 32) in; + + layout(location = 0) uniform vec4 uParams; // x = grid, y = aspect, z = time + + void main() + { + if (gl_LocalInvocationID.x != 0u) + return; + + uint grid = uint(uParams.x); + + gl_TaskCountNV = grid * grid; + } + )GLSL"; + MG_Impl::GLImpl::ShaderSource(shader, 1, &src, nullptr); + MG_Impl::GLImpl::CompileShader(shader); + + GLint ok = GL_FALSE; + MG_Impl::GLImpl::GetShaderiv(shader, GL_COMPILE_STATUS, &ok); + + ASSERT_EQ(ok, GL_TRUE); +} diff --git a/MobileGL/MG_Util/Converters/GLToGlslang/ProgramEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToGlslang/ProgramEnumConverter.cpp index 87f4d747..deea574b 100644 --- a/MobileGL/MG_Util/Converters/GLToGlslang/ProgramEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToGlslang/ProgramEnumConverter.cpp @@ -18,6 +18,10 @@ namespace MobileGL { return EShLanguage::EShLangFragment; case GL_COMPUTE_SHADER: return EShLanguage::EShLangCompute; + case GL_TASK_SHADER_NV: + return EShLanguage::EShLangTaskNV; + case GL_MESH_SHADER_NV: + return EShLanguage::EShLangMeshNV; case GL_TESS_CONTROL_SHADER: return EShLanguage::EShLangTessControl; case GL_TESS_EVALUATION_SHADER: diff --git a/MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp index cf50a93e..1984cf3b 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp @@ -22,6 +22,10 @@ namespace MobileGL { return ShaderStage::Geometry; case GL_FRAGMENT_SHADER: return ShaderStage::Fragment; + case GL_TASK_SHADER_NV: + return ShaderStage::Task; + case GL_MESH_SHADER_NV: + return ShaderStage::Mesh; case GL_COMPUTE_SHADER: return ShaderStage::Compute; default: diff --git a/MobileGL/MG_Util/Converters/GLToStr/GLEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToStr/GLEnumConverter.cpp index a9bb6334..d309e15d 100644 --- a/MobileGL/MG_Util/Converters/GLToStr/GLEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToStr/GLEnumConverter.cpp @@ -409,6 +409,8 @@ namespace MobileGL { CASE(GL_MAX_TEXTURE_IMAGE_UNITS) CASE(GL_FRAGMENT_SHADER) CASE(GL_VERTEX_SHADER) + CASE(GL_TASK_SHADER_NV) + CASE(GL_MESH_SHADER_NV) CASE(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS) CASE(GL_MAX_VERTEX_UNIFORM_COMPONENTS) CASE(GL_MAX_VARYING_FLOATS)