From 19ada4b8f99e95e760a9f6c45e6431a9f7c49d7b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 7 Jun 2026 11:08:38 +0800 Subject: [PATCH] [Fix]: fix glmark2 crash - deal with legacy GLSL syntax (attribute/varying/gl_FragColor/texture2D/etc.) - implement glGet GL_SHADER_SOURCE_LENGTH, and make sure returns original shader source - expose proper extensions (GL_ARB_depth_texture) - support env var MOBILEGL_LOG_FILE_PATH - unit tests to test against those changes --- .../DirectGLES/BackendObject_DirectGLES.cpp | 2 +- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 21 +++- MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp | 42 ++++--- .../MG_Impl/GLImpl/Program/GL_Program.cpp | 31 ++--- .../GLState/ProgramState/ProgramObject.cpp | 10 +- .../GLState/ProgramState/ShaderObject.cpp | 7 +- MobileGL/MG_Test/CMakeLists.txt | 8 +- MobileGL/MG_Test/Program/ProgramTest.cpp | 90 +++++++++++++++ MobileGL/MG_Test/Program/ProgramUtilTest.cpp | 52 +++++++++ MobileGL/MG_Test/SanityTest.cpp | 58 +++++++++- .../MG_Test/VertexArray/VertexArrayTest.cpp | 45 ++++++++ MobileGL/MG_Util/Debug/Log.cpp | 12 +- .../ShaderSourceProcessor.cpp | 109 +++++++++++++++++- 13 files changed, 439 insertions(+), 48 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index b8aad546..69583192 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -126,7 +126,7 @@ namespace MobileGL::MG_Backend::DirectGLES { V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, - E_GL_EXT_framebuffer_object}, + E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture}, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 5fc6d168..2c6aa0b9 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -214,21 +214,22 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glBindVertexArray(m_backendVAOId); } - inline void BindAttributeBuffer(const MG_State::GLState::VertexAttribute& attrib) { + inline Bool BindAttributeBuffer(const MG_State::GLState::VertexAttribute& attrib) { const auto& bufferObject = attrib.Buffer; if (!bufferObject) { MGLOG_W("Attribute has no bound buffer, skipping."); - return; + return false; } const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject.get()); if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute."); - return; + return false; } const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_ARRAY_BUFFER); + return true; } void BackendVertexArrayObject::SyncToBackend( @@ -266,7 +267,9 @@ namespace MobileGL::MG_Backend::DirectGLES { m_syncedAttributeVersions[attribIndex].BufferVersion; if (!needsSyncFormat && !needsSyncBuffer) continue; - BindAttributeBuffer(attrib); + if (!BindAttributeBuffer(attrib)) { + continue; + } if (!attrib.IsInteger) { g_GLESFuncs.glVertexAttribPointer( @@ -286,16 +289,24 @@ namespace MobileGL::MG_Backend::DirectGLES { Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion(); if (currentIndexBufferVersion != m_syncedIndexBufferVersion) { const auto& indexBufferBinding = stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject(); + Bool indexBufferSynced = false; if (indexBufferBinding) { const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(indexBufferBinding.get()); if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_ELEMENT_ARRAY_BUFFER); + indexBufferSynced = true; } else { MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer."); } + } else { + g_GLESFuncs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + indexBufferSynced = true; + } + + if (indexBufferSynced) { + m_syncedIndexBufferVersion = currentIndexBufferVersion; } - m_syncedIndexBufferVersion = currentIndexBufferVersion; } m_syncedAttributeVersions = allAttributeVersions; diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index 36ce6759..7f4fd073 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -15,6 +15,15 @@ #include namespace MobileGL::MG_Impl::GLImpl { + namespace { + auto& GetBufferBindingSlot(BufferTarget target) { + if (target == BufferTarget::Index) { + return MG_State::pGLContext->GetBoundVertexArray()->GetIndexBufferBindingSlot(); + } + return MG_State::pGLContext->GetBufferBindingSlot(target); + } + } // namespace + void GetBufferParameteriv_State(GLenum target, GLenum pname, GLint* params) { if (!params) { MG_State::pGLContext->RecordError( @@ -26,7 +35,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -105,7 +114,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -148,7 +157,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return GL_FALSE; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -189,7 +198,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return nullptr; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { MG_State::pGLContext->RecordError( @@ -291,7 +300,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return nullptr; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -335,8 +344,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!BufferImpl::ValidateBufferTarget(readBufferTarget) || !BufferImpl::ValidateBufferTarget(writeBufferTarget)) return; - auto& readBindingSlot = MG_State::pGLContext->GetBufferBindingSlot(readBufferTarget); - auto& writeBindingSlot = MG_State::pGLContext->GetBufferBindingSlot(writeBufferTarget); + auto& readBindingSlot = GetBufferBindingSlot(readBufferTarget); + auto& writeBindingSlot = GetBufferBindingSlot(writeBufferTarget); auto& readBufferObject = readBindingSlot.GetBoundObject(); auto& writeBufferObject = writeBindingSlot.GetBoundObject(); @@ -401,7 +410,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -454,7 +463,7 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); auto& bufferObject = bindingSlot.GetBoundObject(); if (!bufferObject) { @@ -477,15 +486,18 @@ namespace MobileGL::MG_Impl::GLImpl { BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; - Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer); - if (!doesBufferObjectCreated) { - MG_State::pGLContext->CreateBufferObject(buffer); + SharedPtr bufferObject; + if (buffer != 0) { + Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer); + if (!doesBufferObjectCreated) { + MG_State::pGLContext->CreateBufferObject(buffer); + } + bufferObject = MG_State::pGLContext->GetBufferObject(buffer); } - auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer); - auto& bindingSlot = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget); + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); bindingSlot.Bind(bufferObject); - MGLOG_D("%s: bind buffer object %d -> %s", __func__, bufferObject->GetExternalIndex(), + MGLOG_D("%s: bind buffer object %d -> %s", __func__, bufferObject ? bufferObject->GetExternalIndex() : 0, MG_Util::ConvertGLEnumToString(target).c_str()); } diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 279044cd..6458f8fa 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -69,13 +69,15 @@ namespace MobileGL::MG_Impl::GLImpl { } void CopyStr(GLsizei bufSize, GLsizei* length, GLchar* dst, const char* src, GLsizei srcLength) { + if (bufSize <= 0) { + if (length) *length = 0; + return; + } + auto sz = std::min(bufSize - 1, srcLength); - if (length) *length = sz; - - if (bufSize == 0) return; - Memcpy(dst, src, sz); dst[sz] = '\0'; + if (length) *length = sz; } void AttachShader_State(GLuint program, GLuint shader) { @@ -317,8 +319,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } getProgramiv(program, pname, params); - MGLOG_D("%s: %s = (%d, %d, %d)", __func__, - MG_Util::ConvertGLEnumToString(pname).c_str(), params[0], params[1], params[2]); + MGLOG_D("%s: %s = (%d, %d, %d)", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), params[0], + params[1], params[2]); break; } @@ -363,10 +365,10 @@ namespace MobileGL::MG_Impl::GLImpl { *params = shaderObject->GetCompileStatus(); break; case GL_INFO_LOG_LENGTH: - *params = (GLint)shaderObject->GetInfoLog().length(); + *params = shaderObject->GetInfoLog().empty() ? 0 : (GLint)shaderObject->GetInfoLog().length() + 1; break; case GL_SHADER_SOURCE_LENGTH: - *params = (GLint)shaderObject->GetShaderSource().length(); + *params = shaderObject->GetShaderSource().empty() ? 0 : (GLint)shaderObject->GetShaderSource().length() + 1; break; default: MG_State::pGLContext->RecordError( @@ -525,7 +527,10 @@ namespace MobileGL::MG_Impl::GLImpl { std::string src; for (GLsizei i = 0; i < count; i++) { - src += (length == nullptr || length[i] <= 0) ? string[i] : std::string(string[i], length[i]); + if (!string[i]) { + continue; + } + src += (length && length[i] >= 0) ? std::string(string[i], length[i]) : std::string(string[i]); } shaderObject->SetShaderSource(Move(src)); } @@ -1475,8 +1480,8 @@ namespace MobileGL::MG_Impl::GLImpl { return getProgramResourceIndex(program, programInterface, name); } - void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, - GLsizei* length, GLchar* name) { + void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, + GLchar* name) { auto& programObject = TryToGetProgramObject(program); if (!programObject || !programObject->GetLinkStatus()) return; if (bufSize < 0) { @@ -1502,8 +1507,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!programObject || !programObject->GetLinkStatus()) return; if (propCount < 0 || bufSize < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, "propCount and bufSize must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, + "propCount and bufSize must be non-negative.")); return; } auto getProgramResourceiv = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceiv; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 93831d19..0bf21d36 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -389,10 +390,13 @@ namespace MobileGL::MG_State::GLState { // 1. Compile shaders for (SizeT i = 0; i < m_shaders.size(); i++) { - auto shaderType = MG_Util::ConvertShaderStageToGLEnum(m_shaders[i]->GetShaderStage()); + auto shaderStage = m_shaders[i]->GetShaderStage(); + auto shaderType = MG_Util::ConvertShaderStageToGLEnum(shaderStage); + String compileSource = m_shaders[i]->GetShaderSource(); + PreprocessShaderSource(shaderStage, compileSource); shaderTypes[i] = shaderType; ShaderAttrib attrib{.shaderType = shaderType, - .sourceStr = m_shaders[i]->GetShaderSource(), + .sourceStr = compileSource, .flags = 0}; // Will need patched glslang to work MGLOG_D("ProgramObject %u: GenerateBinary - compiling shader[%zu] type %u", m_externalIndex, i, shaderType); auto res = ShaderCompiler::CompileShader(attrib); @@ -403,7 +407,7 @@ namespace MobileGL::MG_State::GLState { MGLOG_E("ProgramObject %u: GenerateBinary - CompileShader return code %d, log:\n%s", m_externalIndex, res.error().errc, res.error().log.c_str()); MGLOG_E("ProgramObject %u: GenerateBinary - last compiled shader src: \n%s", m_externalIndex, - m_shaders[i]->GetShaderSource().c_str()); + compileSource.c_str()); } MOBILEGL_ASSERT(res, "CompileShader failed during binary generation"); shaders[i] = res.value(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp index d855799f..ec5219f3 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp @@ -24,13 +24,14 @@ namespace MobileGL::MG_State::GLState { void ShaderObject::Compile() { using namespace MG_Util::ShaderTranspiler; - MG_Util::ShaderTranspiler::PreprocessShaderSource(m_stage, m_source); + String compileSource = m_source; + MG_Util::ShaderTranspiler::PreprocessShaderSource(m_stage, compileSource); // Compile for OpenGL here, so that we can do validation and link // like a real OpenGL driver at linking stage // Will compile for other backends later. ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage), - .sourceStr = m_source, + .sourceStr = compileSource, .flags = ShaderCompileBits::CompileForOpenGL}; auto result = ShaderCompiler::CompileShader(attrib); @@ -42,7 +43,7 @@ namespace MobileGL::MG_State::GLState { m_infoLog = result.error().log; MGLOG_D("ShaderObject::Compile: Shader %d compilation failed.\nSource:\n%s\nInfoLog:\n%s\nSetting " "m_compileStatus = false as a result.", - m_externalIndex, m_source.c_str(), m_infoLog.c_str()); + m_externalIndex, compileSource.c_str(), m_infoLog.c_str()); } } diff --git a/MobileGL/MG_Test/CMakeLists.txt b/MobileGL/MG_Test/CMakeLists.txt index 023d7570..1d0212fd 100644 --- a/MobileGL/MG_Test/CMakeLists.txt +++ b/MobileGL/MG_Test/CMakeLists.txt @@ -35,6 +35,12 @@ add_executable( target_link_libraries( SanityTest GTest::gtest_main + MobileGL_s +) + +target_include_directories(SanityTest PRIVATE + ${MGL_ROOT}/include + ${MGL_ROOT}/MobileGL ) # message(STATUS "glslang_LIBRARIES: ${glslang_LIBRARIES}") @@ -63,4 +69,4 @@ add_subdirectory(VertexArray) add_subdirectory(Program) if (ENABLE_INTEGRATION_TESTS) add_subdirectory(Backend/DirectVulkan) -endif() \ No newline at end of file +endif() diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index 7f9f631e..314d138e 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -7,6 +7,9 @@ // End of Source File Header #include +#include +#include + #include "Includes.h" #include "Init.h" #include "MG_Impl/GLImpl/Program/GL_Program.h" @@ -113,6 +116,93 @@ TEST_F(ProgramTest, CompileFragment) { CompileShader(fs); } +TEST_F(ProgramTest, ShaderSourceKeepsOriginalTextAfterCompile) { + const char* part0 = R"(#define HIGHP_OR_DEFAULT highp +attribute vec4 Position; +varying vec2 uv; +)"; + const char* ignored = "this segment should be ignored"; + const char* part2 = R"(void main() { + uv = Position.xy; + gl_Position = Position; +} +)"; + const GLchar* parts[] = {part0, ignored, part2}; + const GLint lengths[] = {static_cast(std::strlen(part0)), 0, static_cast(std::strlen(part2))}; + const String expectedSource = String(part0) + part2; + + GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 3, parts, lengths); + + GLint sourceLength = 0; + GetShaderiv(vs, GL_SHADER_SOURCE_LENGTH, &sourceLength); + ASSERT_EQ(sourceLength, static_cast(expectedSource.size() + 1)); + + std::vector sourceBuffer(static_cast(sourceLength)); + GLsizei written = 0; + GetShaderSource(vs, sourceLength, &written, sourceBuffer.data()); + EXPECT_EQ(written, static_cast(expectedSource.size())); + EXPECT_EQ(String(sourceBuffer.data(), static_cast(written)), expectedSource); + + CompileShader(vs); + GLint compileStatus = GL_FALSE; + GetShaderiv(vs, GL_COMPILE_STATUS, &compileStatus); + ASSERT_EQ(compileStatus, GL_TRUE); + + std::fill(sourceBuffer.begin(), sourceBuffer.end(), '\0'); + written = 0; + GetShaderSource(vs, sourceLength, &written, sourceBuffer.data()); + EXPECT_EQ(written, static_cast(expectedSource.size())); + EXPECT_EQ(String(sourceBuffer.data(), static_cast(written)), expectedSource); +} + +TEST_F(ProgramTest, LinkProgramWithLegacyGlmarkStyleShaders) { + char infoLog[1024] = ""; + + const char* legacyVs = R"(attribute vec4 Position; +attribute vec2 TexCoord; +varying vec2 vTexCoord; + +void main() { + vTexCoord = TexCoord; + gl_Position = Position; +} +)"; + const char* legacyFs = R"(varying vec2 vTexCoord; +uniform sampler2D Texture; + +void main() { + gl_FragColor = texture2D(Texture, vTexCoord); +} +)"; + + GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &legacyVs, NULL); + CompileShader(vs); + GLint vsStatus = GL_FALSE; + GetShaderiv(vs, GL_COMPILE_STATUS, &vsStatus); + GetShaderInfoLog(vs, sizeof(infoLog), nullptr, infoLog); + ASSERT_EQ(vsStatus, GL_TRUE) << infoLog; + + GLuint fs = CreateShader(GL_FRAGMENT_SHADER); + ShaderSource(fs, 1, &legacyFs, NULL); + CompileShader(fs); + GLint fsStatus = GL_FALSE; + GetShaderiv(fs, GL_COMPILE_STATUS, &fsStatus); + GetShaderInfoLog(fs, sizeof(infoLog), nullptr, infoLog); + ASSERT_EQ(fsStatus, GL_TRUE) << infoLog; + + GLuint program = CreateProgram(); + AttachShader(program, vs); + AttachShader(program, fs); + LinkProgram(program); + + GLint linkStatus = GL_FALSE; + GetProgramiv(program, GL_LINK_STATUS, &linkStatus); + GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog); + ASSERT_EQ(linkStatus, GL_TRUE) << infoLog; +} + TEST_F(ProgramTest, CompileAndLink) { char infoLog[1024] = ""; diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 0fe3beb4..bfc619a1 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -8,6 +8,8 @@ #include +#include + #include "Includes.h" #include "Init.h" #include @@ -29,6 +31,56 @@ TEST_F(ProgramUtilTest, Sanity) { ASSERT_TRUE(true); } +TEST_F(ProgramUtilTest, PreprocessLegacyVertexShaderModernizesGlmarkStyleSource) { + using namespace MG_Util::ShaderTranspiler; + + String source = R"(#define HIGHP_OR_DEFAULT highp +attribute vec3 position; +varying vec2 uv; +uniform HIGHP_OR_DEFAULT mat4 modelViewProjection; + +void main() { + uv = position.xy; + gl_Position = modelViewProjection * vec4(position, 1.0); +})"; + + PreprocessShaderSource(ShaderStage::Vertex, source); + + EXPECT_EQ(source.find("#version 460 core\n"), 0); + EXPECT_NE(source.find("in vec3 position;"), String::npos); + EXPECT_NE(source.find("out vec2 uv;"), String::npos); + EXPECT_EQ(source.find("attribute"), String::npos); + EXPECT_EQ(source.find("varying"), String::npos); + EXPECT_EQ(source.find("HIGHP_OR_DEFAULT"), String::npos); + EXPECT_EQ(source.find("#define"), String::npos); +} + +TEST_F(ProgramUtilTest, PreprocessLegacyFragmentShaderModernizesGlmarkStyleSource) { + using namespace MG_Util::ShaderTranspiler; + + String source = R"(#define MEDIUMP_OR_DEFAULT mediump +varying vec2 uv; +uniform sampler2D texture0; + +void main() { + MEDIUMP_OR_DEFAULT vec4 color = texture2D(texture0, uv); + gl_FragColor = color; +})"; + + PreprocessShaderSource(ShaderStage::Fragment, source); + + EXPECT_EQ(source.find("#version 460 core\n"), 0); + EXPECT_NE(source.find("out vec4 mg_FragColor;\n"), String::npos); + EXPECT_NE(source.find("in vec2 uv;"), String::npos); + EXPECT_NE(source.find("texture(texture0, uv)"), String::npos); + EXPECT_NE(source.find("mg_FragColor = color;"), String::npos); + EXPECT_EQ(source.find("gl_FragColor"), String::npos); + EXPECT_EQ(source.find("texture2D"), String::npos); + EXPECT_EQ(source.find("MEDIUMP_OR_DEFAULT"), String::npos); + EXPECT_EQ(source.find("mediump"), String::npos); + EXPECT_EQ(source.find("#define"), String::npos); +} + const char* vs = R"(#version 150 in vec4 Position; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index fa6d32bc..fe0e8e3b 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -7,10 +7,66 @@ // End of Source File Header #include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + void SetEnvVar(const char* name, const char* value) { +#if defined(_WIN32) + _putenv_s(name, value); +#else + setenv(name, value, 1); +#endif + } + + void UnsetEnvVar(const char* name) { +#if defined(_WIN32) + _putenv_s(name, ""); +#else + unsetenv(name); +#endif + } +} // namespace TEST(Sanity, BasicAssertions) { // Expect two strings not to be equal. EXPECT_STRNE("hello", "world"); // Expect equality. EXPECT_EQ(7 * 6, 42); -} \ No newline at end of file +} + +TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) { + MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; + const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions; + + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end()); +} + +TEST(LogSanity, UsesEnvOverrideForFilePath) { + namespace fs = std::filesystem; + + MobileGL::MG_Util::Debug::Close(); + + const fs::path logPath = fs::temp_directory_path() / "mobilegl-log-env-override-test.log"; + const std::string message = "mobilegl-log-env-override-regression"; + fs::remove(logPath); + + SetEnvVar("MOBILEGL_LOG_FILE_PATH", logPath.string().c_str()); + MobileGL::MG_Util::Debug::Log("INFO", ANDROID_LOG_INFO, "%s", message.c_str()); + MobileGL::MG_Util::Debug::Close(); + UnsetEnvVar("MOBILEGL_LOG_FILE_PATH"); + + std::ifstream logFile(logPath); + ASSERT_TRUE(logFile.good()); + + const std::string contents((std::istreambuf_iterator(logFile)), std::istreambuf_iterator()); + EXPECT_NE(contents.find(message), std::string::npos); + + fs::remove(logPath); +} diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index 26c2f425..78fd245a 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -318,6 +318,51 @@ TEST_F(GeneralVertexArrayTest, General_IndexBufferBinding) { EXPECT_EQ(GetError(), GL_NO_ERROR); } +TEST_F(GeneralVertexArrayTest, General_ElementArrayBufferBindingIsVaoLocalAndZeroUnbinds) { + GLuint vao1 = CreateVAO(); + + GLuint ebo1; + GenBuffers(1, &ebo1); + BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo1); + + GLint binding = -1; + GetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &binding); + EXPECT_EQ(binding, static_cast(ebo1)); + + auto vaoObj1 = MG_State::pGLContext->GetVertexArrayObject(vao1); + ASSERT_NE(vaoObj1, nullptr); + EXPECT_EQ(vaoObj1->GetIndexBufferBindingSlot().GetBoundObject(), MG_State::pGLContext->GetBufferObject(ebo1)); + + GLuint vao2 = CreateVAO(); + auto vaoObj2 = MG_State::pGLContext->GetVertexArrayObject(vao2); + ASSERT_NE(vaoObj2, nullptr); + GetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &binding); + EXPECT_EQ(binding, 0); + EXPECT_EQ(vaoObj2->GetIndexBufferBindingSlot().GetBoundObject(), nullptr); + + BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + EXPECT_EQ(GetError(), GL_NO_ERROR); + EXPECT_EQ(MG_State::pGLContext->GetBufferObject(0), nullptr); + EXPECT_EQ(IsBuffer(0), GL_FALSE); + + BindVertexArray(vao1); + GetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &binding); + EXPECT_EQ(binding, static_cast(ebo1)); + + BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + GetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &binding); + EXPECT_EQ(binding, 0); + EXPECT_EQ(vaoObj1->GetIndexBufferBindingSlot().GetBoundObject(), nullptr); + EXPECT_EQ(MG_State::pGLContext->GetBufferObject(0), nullptr); + EXPECT_EQ(IsBuffer(0), GL_FALSE); + + DeleteVertexArrays(1, &vao1); + DeleteVertexArrays(1, &vao2); + DeleteBuffers(1, &ebo1); + + EXPECT_EQ(GetError(), GL_NO_ERROR); +} + TEST_F(GeneralVertexArrayTest, General_IntegerAttributes) { GLuint vao = CreateVAO(); GLuint vbo = CreateVBO(GL_ARRAY_BUFFER, 128); diff --git a/MobileGL/MG_Util/Debug/Log.cpp b/MobileGL/MG_Util/Debug/Log.cpp index 115a2a9c..8c1b5e05 100644 --- a/MobileGL/MG_Util/Debug/Log.cpp +++ b/MobileGL/MG_Util/Debug/Log.cpp @@ -58,8 +58,14 @@ namespace MobileGL { void InitFile() { #if MOBILEGL_LOG_ENABLE_FILE - if (!s_logFile && MOBILEGL_LOG_FILE_PATH && *MOBILEGL_LOG_FILE_PATH) { - s_logFile = std::fopen(MOBILEGL_LOG_FILE_PATH, "w"); + if (!s_logFile) { + const char* logPath = std::getenv("MOBILEGL_LOG_FILE_PATH"); + if (!logPath || !*logPath) { + logPath = MOBILEGL_LOG_FILE_PATH; + } + if (logPath && *logPath) { + s_logFile = std::fopen(logPath, "w"); + } } #endif } @@ -118,4 +124,4 @@ namespace MobileGL { #endif } } // namespace MG_Util::Debug -} // namespace MobileGL \ No newline at end of file +} // namespace MobileGL diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index e764b618..e0893638 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -29,8 +29,7 @@ namespace { while (functionPos != MobileGL::String::npos && functionPos < lineEnd) { const bool hasLeftBoundary = functionPos == 0 || !IsIdentifierChar(source[functionPos - 1]); const SizeT functionEnd = functionPos + functionName.size(); - const bool hasRightBoundary = - functionEnd >= source.size() || !IsIdentifierChar(source[functionEnd]); + const bool hasRightBoundary = functionEnd >= source.size() || !IsIdentifierChar(source[functionEnd]); if (hasLeftBoundary && hasRightBoundary) { SizeT probe = functionEnd; while (probe < lineEnd && std::isspace(static_cast(source[probe]))) { @@ -89,6 +88,108 @@ namespace { RenameFunctionInvocations(source, fromName, to); } + + void ReplaceIdentifier(MobileGL::String& source, const MobileGL::String& from, const MobileGL::String& to) { + SizeT pos = 0; + while ((pos = source.find(from, pos)) != MobileGL::String::npos) { + const bool hasLeftBoundary = pos == 0 || !IsIdentifierChar(source[pos - 1]); + const SizeT end = pos + from.size(); + const bool hasRightBoundary = end >= source.size() || !IsIdentifierChar(source[end]); + if (hasLeftBoundary && hasRightBoundary) { + source.replace(pos, from.size(), to); + pos += to.size(); + } else { + pos = end; + } + } + } + + void RemoveDefineForIdentifier(MobileGL::String& source, const MobileGL::String& identifier) { + SizeT lineStart = 0; + while (lineStart < source.size()) { + SizeT lineEnd = source.find('\n', lineStart); + const bool hasLineBreak = lineEnd != MobileGL::String::npos; + if (!hasLineBreak) { + lineEnd = source.size(); + } + + SizeT probe = lineStart; + while (probe < lineEnd && std::isspace(static_cast(source[probe]))) { + probe++; + } + if (probe < lineEnd && source[probe] == '#') { + probe++; + while (probe < lineEnd && std::isspace(static_cast(source[probe]))) { + probe++; + } + + constexpr const char* defineToken = "define"; + constexpr SizeT defineLen = 6; + const bool hasDefine = probe + defineLen <= lineEnd && + source.compare(probe, defineLen, defineToken) == 0 && + (probe + defineLen == lineEnd || + !IsIdentifierChar(source[probe + defineLen])); + if (hasDefine) { + probe += defineLen; + while (probe < lineEnd && std::isspace(static_cast(source[probe]))) { + probe++; + } + + const bool hasIdentifier = probe + identifier.size() <= lineEnd && + source.compare(probe, identifier.size(), identifier) == 0 && + (probe + identifier.size() == lineEnd || + !IsIdentifierChar(source[probe + identifier.size()])); + if (hasIdentifier) { + source.erase(lineStart, lineEnd - lineStart + (hasLineBreak ? 1 : 0)); + continue; + } + } + } + + lineStart = lineEnd + (hasLineBreak ? 1 : 0); + } + } + + SizeT FindAfterVersionDirective(const MobileGL::String& source) { + const SizeT versionPos = source.find("#version"); + if (versionPos == MobileGL::String::npos) { + return 0; + } + const SizeT lineEnd = source.find('\n', versionPos); + return lineEnd == MobileGL::String::npos ? source.size() : lineEnd + 1; + } + + void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) { + RemoveDefineForIdentifier(source, "HIGHP_OR_DEFAULT"); + RemoveDefineForIdentifier(source, "MEDIUMP_OR_DEFAULT"); + RemoveDefineForIdentifier(source, "LOWP_OR_DEFAULT"); + ReplaceIdentifier(source, "HIGHP_OR_DEFAULT", ""); + ReplaceIdentifier(source, "MEDIUMP_OR_DEFAULT", ""); + ReplaceIdentifier(source, "LOWP_OR_DEFAULT", ""); + ReplaceIdentifier(source, "highp", ""); + ReplaceIdentifier(source, "mediump", ""); + ReplaceIdentifier(source, "lowp", ""); + + ReplaceIdentifier(source, "texture2D", "texture"); + ReplaceIdentifier(source, "texture2DProj", "textureProj"); + ReplaceIdentifier(source, "textureCube", "texture"); + ReplaceIdentifier(source, "texture3D", "texture"); + + if (stage == MobileGL::ShaderStage::Vertex) { + ReplaceIdentifier(source, "attribute", "in"); + ReplaceIdentifier(source, "varying", "out"); + return; + } + + if (stage == MobileGL::ShaderStage::Fragment) { + ReplaceIdentifier(source, "varying", "in"); + const bool usesFragColor = source.find("gl_FragColor") != MobileGL::String::npos; + if (usesFragColor) { + ReplaceIdentifier(source, "gl_FragColor", "mg_FragColor"); + source.insert(FindAfterVersionDirective(source), "out vec4 mg_FragColor;\n"); + } + } + } } // namespace namespace MobileGL { @@ -145,7 +246,8 @@ namespace MobileGL { } else { profile = ShaderProfile::Core; source.insert(0, "#version 460 core\n"); - return; + versionPos = 0; + lineEnd = source.find('\n', versionPos); } SizeT firstLineEnd = lineEnd; @@ -169,6 +271,7 @@ namespace MobileGL { RenameBuiltinShadowingFunction(source, "round", "mg_round"); RenameBuiltinShadowingFunction(source, "tanh", "mg_tanh"); RenameBuiltinShadowingFunction(source, "fma", "mg_fma"); + ModernizeLegacyGLSL(stage, source); } } // namespace ShaderTranspiler