[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.
This commit is contained in:
HH45137
2026-10-12 00:20:53 +08:00
parent 08124c99f1
commit 3823fe528c
13 changed files with 137 additions and 6 deletions
@@ -521,7 +521,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported) {
Bool cubeMapArraySupported,
Bool nvMeshShader) {
Vector<GLExtension> 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;
}
@@ -77,7 +77,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported);
Bool cubeMapArraySupported,
Bool nvMeshShader = true);
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -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;
@@ -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(
@@ -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
@@ -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)
@@ -18,6 +18,8 @@ namespace MobileGL {
TessEval,
Geometry,
Fragment,
Task,
Mesh,
Compute,
ShaderStageCount,
Unknown = -1
+1
View File
@@ -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)
@@ -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)
@@ -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 <gtest/gtest.h>
#include <limits>
#include "Includes.h"
#include "Init.h"
#include "MG_Impl/GLImpl/Program/GL_Program.h"
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/VertexArray/GL_VertexArray.h>
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
#include <MG_State/GLState/Core.h>
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<const char*>(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);
}
@@ -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:
@@ -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:
@@ -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)