From fb1d4911c2e2b72c96d5fd48b97359a9b7eb62dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 00:35:23 +0000 Subject: [PATCH 1/4] Initial plan From 527cf1e73ca7da4e155664c9c301580627468147 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 00:49:04 +0000 Subject: [PATCH 2/4] Add ARB fragment program fallback for combine3/combine4 extensions Implement fragment program fallback paths for specular reflection, per-pixel lighting, and lightmap rendering when GL_ATI_texture_env_combine3 and GL_NV_texture_env_combine4 extensions are not available but GL_ARB_fragment_program is (e.g., Intel HD graphics). This fixes armor reflection being disabled on Intel HD graphics. Co-authored-by: kaetemi <1581053+kaetemi@users.noreply.github.com> --- nel/src/3d/driver/opengl/driver_opengl.cpp | 212 +++++++++++++++++- nel/src/3d/driver/opengl/driver_opengl.h | 6 + .../driver/opengl/driver_opengl_material.cpp | 122 +++++++++- 3 files changed, 328 insertions(+), 12 deletions(-) diff --git a/nel/src/3d/driver/opengl/driver_opengl.cpp b/nel/src/3d/driver/opengl/driver_opengl.cpp index 42133d6203..293854a7aa 100644 --- a/nel/src/3d/driver/opengl/driver_opengl.cpp +++ b/nel/src/3d/driver/opengl/driver_opengl.cpp @@ -333,6 +333,12 @@ CDriverGL::CDriverGL() std::fill(ARBWaterShader, ARBWaterShader + 4, 0); + ARBSpecularShader = 0; + ARBSpecularShaderNoTex = 0; + ARBPPLStage0Shader = 0; + ARBPPLStage2Shader = 0; + std::fill(ARBLightmapShader, ARBLightmapShader + 3, 0); + /// buildCausticCubeMapTex(); _SpecularBatchOn= false; @@ -1710,15 +1716,16 @@ void CDriverGL::checkForPerPixelLightingSupport() H_AUTO_OGL(CDriverGL_checkForPerPixelLightingSupport) // we need at least 3 texture stages and cube map support + EnvCombine4 or 3 support - // TODO : support for EnvCombine3 - // TODO : support for less than 3 stages + // ARBFragmentProgram can be used as fallback for EnvCombine4/3 + + bool hasCombine = _Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3 || _Extensions.ARBFragmentProgram; - _SupportPerPixelShaderNoSpec = (_Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3) + _SupportPerPixelShaderNoSpec = hasCombine && _Extensions.ARBTextureCubeMap && _Extensions.NbTextureStages >= 3 && (_Extensions.NVVertexProgram || _Extensions.ARBVertexProgram || _Extensions.EXTVertexShader); - _SupportPerPixelShader = (_Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3) + _SupportPerPixelShader = hasCombine && _Extensions.ARBTextureCubeMap && _Extensions.NbTextureStages >= 2 && (_Extensions.NVVertexProgram || _Extensions.ARBVertexProgram || _Extensions.EXTVertexShader); @@ -2021,7 +2028,127 @@ LRP oCol, tmpFog.x, diffuse, fogColor; \n\ END "; // *************************************************************************** -/** Load a ARB_fragment_program_code, and ensure it is loaded natively +// Fragment programs to emulate GL_ATI_texture_env_combine3 / GL_NV_texture_env_combine4 +// for hardware that has GL_ARB_fragment_program but not the texture env combine extensions. + +// Specular pass with base texture in stage 0: +// Stage 0 equivalent: prev = tex0 * primary_color (modulate) +// Stage 1 equivalent: result.rgb = tex1 * prev.a + prev (MAD), result.a = primary.a +static const char *SpecularTexEnvCombineARBFragmentProgram = +"!!ARBfp1.0\n\ +TEMP base;\n\ +TEMP cube;\n\ +TEMP prev;\n\ +TEX base, fragment.texcoord[0], texture[0], 2D;\n\ +TEX cube, fragment.texcoord[1], texture[1], CUBE;\n\ +MUL prev, base, fragment.color.primary;\n\ +MAD result.color.rgb, cube, prev.a, prev;\n\ +MOV result.color.a, fragment.color.primary.a;\n\ +END\n"; + +// Specular pass without base texture in stage 0: +// Stage 0 equivalent: prev = primary_color +// Stage 1 equivalent: result.rgb = tex1 + prev (since mul factor = 1) +static const char *SpecularTexEnvCombineNoTexARBFragmentProgram = +"!!ARBfp1.0\n\ +TEMP cube;\n\ +TEX cube, fragment.texcoord[1], texture[1], CUBE;\n\ +ADD result.color.rgb, cube, fragment.color.primary;\n\ +MOV result.color.a, fragment.color.primary.a;\n\ +END\n"; + +// PPL stage 0: DiffuseCubeMap * LightColor + DiffuseGouraud +// constant0 = PPLightDiffuseColor +// stage 1 keeps default: modulate with texture +static const char *PPLStage0ARBFragmentProgram = +"!!ARBfp1.0\n\ +PARAM lightColor = program.env[0];\n\ +TEMP diffCube;\n\ +TEMP prev;\n\ +TEMP tex1;\n\ +TEX diffCube, fragment.texcoord[0], texture[0], CUBE;\n\ +MAD prev.rgb, diffCube, lightColor, fragment.color.primary;\n\ +MOV prev.a, fragment.color.primary.a;\n\ +TEX tex1, fragment.texcoord[1], texture[1], 2D;\n\ +MUL result.color.rgb, tex1, prev;\n\ +MOV result.color.a, prev.a;\n\ +END\n"; + +// PPL stage 2: SpecularCubeMap * SpecularLightColor + Previous +// constant2 = PPLightSpecularColor +// stage 0 = cubemap * constant + primary (PPL stage 0) +// stage 1 = modulate with texture (default) +// stage 2 = cubemap * constant + previous +static const char *PPLStage2ARBFragmentProgram = +"!!ARBfp1.0\n\ +PARAM diffuseColor = program.env[0];\n\ +PARAM specularColor = program.env[1];\n\ +TEMP diffCube;\n\ +TEMP prev;\n\ +TEMP tex1;\n\ +TEMP specCube;\n\ +TEX diffCube, fragment.texcoord[0], texture[0], CUBE;\n\ +MAD prev.rgb, diffCube, diffuseColor, fragment.color.primary;\n\ +MOV prev.a, fragment.color.primary.a;\n\ +TEX tex1, fragment.texcoord[1], texture[1], 2D;\n\ +MUL prev.rgb, tex1, prev;\n\ +TEX specCube, fragment.texcoord[2], texture[2], CUBE;\n\ +MAD result.color.rgb, specCube, specularColor, prev;\n\ +MOV result.color.a, prev.a;\n\ +END\n"; + +// Lightmap fragment programs: N lightmaps * constants + base texture +// Stage layout: lightmap stages first, then base texture last +// 1 lightmap stage (2 texture units: lm at 0, base at 1) +static const char *LightmapShader1ARBFragmentProgram = +"!!ARBfp1.0\n\ +PARAM lmColor0 = program.env[0];\n\ +TEMP lm;\n\ +TEMP prev;\n\ +TEMP base;\n\ +TEX lm, fragment.texcoord[0], texture[0], 2D;\n\ +MAD prev, lm, lmColor0, fragment.color.primary;\n\ +TEX base, fragment.texcoord[1], texture[1], 2D;\n\ +MUL result.color.rgb, base, prev;\n\ +MOV result.color.a, base.a;\n\ +END\n"; + +// 2 lightmap stages (3 texture units: lm0 at 0, lm1 at 1, base at 2) +static const char *LightmapShader2ARBFragmentProgram = +"!!ARBfp1.0\n\ +PARAM lmColor0 = program.env[0];\n\ +PARAM lmColor1 = program.env[1];\n\ +TEMP lm;\n\ +TEMP prev;\n\ +TEMP base;\n\ +TEX lm, fragment.texcoord[0], texture[0], 2D;\n\ +MAD prev, lm, lmColor0, fragment.color.primary;\n\ +TEX lm, fragment.texcoord[1], texture[1], 2D;\n\ +MAD prev, lm, lmColor1, prev;\n\ +TEX base, fragment.texcoord[2], texture[2], 2D;\n\ +MUL result.color.rgb, base, prev;\n\ +MOV result.color.a, base.a;\n\ +END\n"; + +// 3 lightmap stages (4 texture units: lm0 at 0, lm1 at 1, lm2 at 2, base at 3) +static const char *LightmapShader3ARBFragmentProgram = +"!!ARBfp1.0\n\ +PARAM lmColor0 = program.env[0];\n\ +PARAM lmColor1 = program.env[1];\n\ +PARAM lmColor2 = program.env[2];\n\ +TEMP lm;\n\ +TEMP prev;\n\ +TEMP base;\n\ +TEX lm, fragment.texcoord[0], texture[0], 2D;\n\ +MAD prev, lm, lmColor0, fragment.color.primary;\n\ +TEX lm, fragment.texcoord[1], texture[1], 2D;\n\ +MAD prev, lm, lmColor1, prev;\n\ +TEX lm, fragment.texcoord[2], texture[2], 2D;\n\ +MAD prev, lm, lmColor2, prev;\n\ +TEX base, fragment.texcoord[3], texture[3], 2D;\n\ +MUL result.color.rgb, base, prev;\n\ +MOV result.color.a, base.a;\n\ +END\n"; */ uint loadARBFragmentProgramStringNative(const char *prog, bool forceNativePrograms) { @@ -2206,6 +2333,48 @@ void CDriverGL::initFragmentShaders() } // if none of the previous programs worked, fallback on NV_texture_shader, or (todo) simpler shader + + /////////////////////////////////////////// + // COMBINE4/COMBINE3 FALLBACK SHADERS // + /////////////////////////////////////////// + + // When NVTextureEnvCombine4 and ATITextureEnvCombine3 are not available, + // use ARB_fragment_program to emulate the MAD (multiply-add) operation + // needed for specular reflection, per-pixel lighting, and lightmaps. + if (_Extensions.ARBFragmentProgram + && !_Extensions.NVTextureEnvCombine4 + && !_Extensions.ATITextureEnvCombine3) + { + nlinfo("COMBINE: Using ARB_fragment_program fallback for combine3/combine4"); + + ARBSpecularShader = loadARBFragmentProgramStringNative(SpecularTexEnvCombineARBFragmentProgram, false); + if (!ARBSpecularShader) + nlwarning("COMBINE: Failed to load specular shader"); + + ARBSpecularShaderNoTex = loadARBFragmentProgramStringNative(SpecularTexEnvCombineNoTexARBFragmentProgram, false); + if (!ARBSpecularShaderNoTex) + nlwarning("COMBINE: Failed to load specular shader (no tex)"); + + ARBPPLStage0Shader = loadARBFragmentProgramStringNative(PPLStage0ARBFragmentProgram, false); + if (!ARBPPLStage0Shader) + nlwarning("COMBINE: Failed to load PPL stage 0 shader"); + + ARBPPLStage2Shader = loadARBFragmentProgramStringNative(PPLStage2ARBFragmentProgram, false); + if (!ARBPPLStage2Shader) + nlwarning("COMBINE: Failed to load PPL stage 2 shader"); + + ARBLightmapShader[0] = loadARBFragmentProgramStringNative(LightmapShader1ARBFragmentProgram, false); + if (!ARBLightmapShader[0]) + nlwarning("COMBINE: Failed to load lightmap shader (1 stage)"); + + ARBLightmapShader[1] = loadARBFragmentProgramStringNative(LightmapShader2ARBFragmentProgram, false); + if (!ARBLightmapShader[1]) + nlwarning("COMBINE: Failed to load lightmap shader (2 stages)"); + + ARBLightmapShader[2] = loadARBFragmentProgramStringNative(LightmapShader3ARBFragmentProgram, false); + if (!ARBLightmapShader[2]) + nlwarning("COMBINE: Failed to load lightmap shader (3 stages)"); + } #endif } @@ -2224,6 +2393,39 @@ void CDriverGL::deleteARBFragmentPrograms() ARBWaterShader[k] = 0; } } + if (ARBSpecularShader) + { + GLuint progId = (GLuint) ARBSpecularShader; + nglDeleteProgramsARB(1, &progId); + ARBSpecularShader = 0; + } + if (ARBSpecularShaderNoTex) + { + GLuint progId = (GLuint) ARBSpecularShaderNoTex; + nglDeleteProgramsARB(1, &progId); + ARBSpecularShaderNoTex = 0; + } + if (ARBPPLStage0Shader) + { + GLuint progId = (GLuint) ARBPPLStage0Shader; + nglDeleteProgramsARB(1, &progId); + ARBPPLStage0Shader = 0; + } + if (ARBPPLStage2Shader) + { + GLuint progId = (GLuint) ARBPPLStage2Shader; + nglDeleteProgramsARB(1, &progId); + ARBPPLStage2Shader = 0; + } + for(uint k = 0; k < 3; ++k) + { + if (ARBLightmapShader[k]) + { + GLuint progId = (GLuint) ARBLightmapShader[k]; + nglDeleteProgramsARB(1, &progId); + ARBLightmapShader[k] = 0; + } + } #endif } diff --git a/nel/src/3d/driver/opengl/driver_opengl.h b/nel/src/3d/driver/opengl/driver_opengl.h index 474e92fde7..03aa6e8ccc 100644 --- a/nel/src/3d/driver/opengl/driver_opengl.h +++ b/nel/src/3d/driver/opengl/driver_opengl.h @@ -1584,6 +1584,12 @@ class CDriverGL : public IDriver GLuint ARBWaterShader[4]; // water support on R300, NV30 & the like + // Fragment program fallback for combine4/combine3 on hardware lacking those extensions + GLuint ARBSpecularShader; // specular pass with base texture: tex1 * prev.a + prev + GLuint ARBSpecularShaderNoTex; // specular pass without base texture: tex1 + primary + GLuint ARBPPLStage0Shader; // PPL stage 0: cubemap * constant + primary + GLuint ARBPPLStage2Shader; // PPL stage 2: cubemap * constant + previous + GLuint ARBLightmapShader[3]; // lightmap: base + N lightmap stages (N=1,2,3) void initFragmentShaders(); void deleteFragmentShaders(); diff --git a/nel/src/3d/driver/opengl/driver_opengl_material.cpp b/nel/src/3d/driver/opengl/driver_opengl_material.cpp index f29cd5c2e6..60035308d1 100644 --- a/nel/src/3d/driver/opengl/driver_opengl_material.cpp +++ b/nel/src/3d/driver/opengl/driver_opengl_material.cpp @@ -709,11 +709,20 @@ void CDriverGL::computeLightMapInfos (const CMaterial &mat) // Compute how many pass, according to driver caps. _NLightMapPerPass = inlGetNumTextStages()-1; - // Can do more than 2 texture stages only if NVTextureEnvCombine4 or ATITextureEnvCombine3 + // Can do more than 2 texture stages only if NVTextureEnvCombine4 or ATITextureEnvCombine3 or ARBFragmentProgram fallback if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3) { - _NLightMapPerPass = 1; - _LightMapNoMulAddFallBack= true; + if (ARBLightmapShader[0]) + { + // With ARB fragment program fallback, limit to 3 lightmaps per pass (max supported by our shaders) + _NLightMapPerPass = std::min(_NLightMapPerPass, (uint)3); + _LightMapNoMulAddFallBack= false; + } + else + { + _NLightMapPerPass = 1; + _LightMapNoMulAddFallBack= true; + } } else { @@ -887,8 +896,8 @@ void CDriverGL::setupLightMapPass(uint pass) } else { - // Here, we are sure that texEnvCombine4 or texEnvCombine3 is OK. - nlassert(_Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3); + // Here, we are sure that texEnvCombine4 or texEnvCombine3 or ARBFragmentProgram is OK. + nlassert(_Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3 || ARBLightmapShader[0]); // setup constant color with Lightmap factor. stdEnv.ConstantColor=lmapFactor; @@ -1016,6 +1025,31 @@ void CDriverGL::setupLightMapPass(uint pass) } } +#ifndef USE_OPENGLES + // ARB fragment program fallback for lightmap combine + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 && !_LightMapNoMulAddFallBack) + { + uint nlm = nstages - 1; // number of lightmap stages (nstages includes base texture) + if (nlm >= 1 && nlm <= 3 && ARBLightmapShader[nlm - 1]) + { + nglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ARBLightmapShader[nlm - 1]); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + // Set lightmap factor colors as program.env parameters + uint lmId = pass * _NLightMapPerPass; + for (uint li = 0; li < nlm; ++li) + { + uint wlm = _LightMapLUT[lmId + li]; + CRGBA lmapFactor = mat._LightMaps[wlm].Factor; + CRGBA lmcDiff = mat._LightMaps[wlm].LMCDiffuse; + float r = ((float)lmapFactor.R * ((float)lmcDiff.R + (lmcDiff.R >> 7))) / (255.f * 255.f); + float g = ((float)lmapFactor.G * ((float)lmcDiff.G + (lmcDiff.G >> 7))) / (255.f * 255.f); + float b = ((float)lmapFactor.B * ((float)lmcDiff.B + (lmcDiff.B >> 7))) / (255.f * 255.f); + nglProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, li, r, g, b, 1.f); + } + } + } +#endif + // setup blend / lighting. //========================= @@ -1092,6 +1126,14 @@ void CDriverGL::setupLightMapPass(uint pass) void CDriverGL::endLightMapMultiPass() { H_AUTO_OGL(CDriverGL_endLightMapMultiPass) +#ifndef USE_OPENGLES + // Disable ARB fragment program if it was used for lightmap fallback + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 + && !_LightMapNoMulAddFallBack && ARBLightmapShader[0]) + { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } +#endif // Flag the fact that VertexSetup is dirty (special lightmap). reseted in activeVertexBuffer(), and setupMaterial() // NB: if no lightmaps, no setupUVPtr() has been called => don't need to flag // (important else crash if graphist error while exporting a Lightmap material, with a MeshVertexProgram (WindTree) ) @@ -1240,6 +1282,8 @@ sint CDriverGL::beginSpecularMultiPass() if( _Extensions.NVTextureEnvCombine4 || _Extensions.ATITextureEnvCombine3) // NVidia or ATI optimization return 1; + else if (ARBSpecularShader || ARBSpecularShaderNoTex) // ARB fragment program fallback + return 1; else return 2; } @@ -1412,6 +1456,16 @@ void CDriverGL::setupSpecularPass(uint pass) glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA ); } } + else if (ARBSpecularShader || ARBSpecularShaderNoTex) + { + // Use ARB fragment program fallback for single pass specular + GLuint shader = (mat.getTexture(0) != NULL && ARBSpecularShader) ? ARBSpecularShader : ARBSpecularShaderNoTex; + if (shader) + { + nglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, shader); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + } + } else #endif { @@ -1472,6 +1526,14 @@ void CDriverGL::setupSpecularPass(uint pass) void CDriverGL::endSpecularMultiPass() { H_AUTO_OGL(CDriverGL_endSpecularMultiPass) +#ifndef USE_OPENGLES + // Disable ARB fragment program if it was used for specular fallback + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 + && (ARBSpecularShader || ARBSpecularShaderNoTex)) + { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } +#endif // End specular , only if not Batching mode. if(!_SpecularBatchOn) setupSpecularEnd(); @@ -1651,6 +1713,24 @@ void CDriverGL::setupPPLPass(uint pass) // setup the tex envs +#ifndef USE_OPENGLES + // ARB fragment program fallback: handles all 3 stages in one program + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 && ARBPPLStage2Shader) + { + nglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ARBPPLStage2Shader); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + // Set diffuse light color as env param 0 + nglProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, + _PPLightDiffuseColor.R / 255.f, _PPLightDiffuseColor.G / 255.f, + _PPLightDiffuseColor.B / 255.f, _PPLightDiffuseColor.A / 255.f); + // Set specular light color as env param 1 + nglProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, + _PPLightSpecularColor.R / 255.f, _PPLightSpecularColor.G / 255.f, + _PPLightSpecularColor.B / 255.f, _PPLightSpecularColor.A / 255.f); + return; + } +#endif + // Stage 0 is rgb = DiffuseCubeMap * LightColor + DiffuseGouraud * 1 if(_CurrentTexEnvSpecial[0] != TexEnvSpecialPPLStage0) { @@ -1813,7 +1893,14 @@ void CDriverGL::setupPPLPass(uint pass) void CDriverGL::endPPLMultiPass() { H_AUTO_OGL(CDriverGL_endPPLMultiPass) - // nothing to do there ... +#ifndef USE_OPENGLES + // Disable ARB fragment program if it was used for PPL fallback + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 + && ARBPPLStage2Shader) + { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } +#endif } // ******PER PIXEL LIGHTING, NO SPECULAR************************************** @@ -1852,6 +1939,20 @@ void CDriverGL::setupPPLNoSpecPass(uint pass) // setup the tex envs +#ifndef USE_OPENGLES + // ARB fragment program fallback: handles stages 0 and 1 in one program + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 && ARBPPLStage0Shader) + { + nglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ARBPPLStage0Shader); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + // Set diffuse light color as env param 0 + nglProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, + _PPLightDiffuseColor.R / 255.f, _PPLightDiffuseColor.G / 255.f, + _PPLightDiffuseColor.B / 255.f, _PPLightDiffuseColor.A / 255.f); + return; + } +#endif + // Stage 0 is rgb = DiffuseCubeMap * LightColor + DiffuseGouraud * 1 (TODO : EnvCombine3) if(_CurrentTexEnvSpecial[0] != TexEnvSpecialPPLStage0) { @@ -1921,7 +2022,14 @@ void CDriverGL::setupPPLNoSpecPass(uint pass) void CDriverGL::endPPLNoSpecMultiPass() { H_AUTO_OGL(CDriverGL_endPPLNoSpecMultiPass) - // nothing to do there ... +#ifndef USE_OPENGLES + // Disable ARB fragment program if it was used for PPL NoSpec fallback + if (!_Extensions.NVTextureEnvCombine4 && !_Extensions.ATITextureEnvCombine3 + && ARBPPLStage0Shader) + { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } +#endif } // *************************************************************************** From aeec87eb1ba0f102971b79c6194434b90f01082f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 00:55:17 +0000 Subject: [PATCH 3/4] Fix stray comment closing in fragment program strings Co-authored-by: kaetemi <1581053+kaetemi@users.noreply.github.com> --- nel/src/3d/driver/opengl/driver_opengl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nel/src/3d/driver/opengl/driver_opengl.cpp b/nel/src/3d/driver/opengl/driver_opengl.cpp index 293854a7aa..f9cdbab1ae 100644 --- a/nel/src/3d/driver/opengl/driver_opengl.cpp +++ b/nel/src/3d/driver/opengl/driver_opengl.cpp @@ -2149,6 +2149,9 @@ TEX base, fragment.texcoord[3], texture[3], 2D;\n\ MUL result.color.rgb, base, prev;\n\ MOV result.color.a, base.a;\n\ END\n"; + +// *************************************************************************** +/** Load a ARB_fragment_program_code, and ensure it is loaded natively */ uint loadARBFragmentProgramStringNative(const char *prog, bool forceNativePrograms) { From a07c0e7b636c5bb8710864e987c417a7a674d50f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 00:56:27 +0000 Subject: [PATCH 4/4] Address code review: improve specular shader selection and add comment Co-authored-by: kaetemi <1581053+kaetemi@users.noreply.github.com> --- nel/src/3d/driver/opengl/driver_opengl_material.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nel/src/3d/driver/opengl/driver_opengl_material.cpp b/nel/src/3d/driver/opengl/driver_opengl_material.cpp index 60035308d1..a53c4374f2 100644 --- a/nel/src/3d/driver/opengl/driver_opengl_material.cpp +++ b/nel/src/3d/driver/opengl/driver_opengl_material.cpp @@ -1041,6 +1041,7 @@ void CDriverGL::setupLightMapPass(uint pass) uint wlm = _LightMapLUT[lmId + li]; CRGBA lmapFactor = mat._LightMaps[wlm].Factor; CRGBA lmcDiff = mat._LightMaps[wlm].LMCDiffuse; + // Compute factor * diffuse as float, using (x + x>>7) to approximate x * (256/255) for proper [0,255]->[0,1] mapping float r = ((float)lmapFactor.R * ((float)lmcDiff.R + (lmcDiff.R >> 7))) / (255.f * 255.f); float g = ((float)lmapFactor.G * ((float)lmcDiff.G + (lmcDiff.G >> 7))) / (255.f * 255.f); float b = ((float)lmapFactor.B * ((float)lmcDiff.B + (lmcDiff.B >> 7))) / (255.f * 255.f); @@ -1459,7 +1460,11 @@ void CDriverGL::setupSpecularPass(uint pass) else if (ARBSpecularShader || ARBSpecularShaderNoTex) { // Use ARB fragment program fallback for single pass specular - GLuint shader = (mat.getTexture(0) != NULL && ARBSpecularShader) ? ARBSpecularShader : ARBSpecularShaderNoTex; + GLuint shader = 0; + if (mat.getTexture(0) != NULL) + shader = ARBSpecularShader; // use texture variant if base texture exists + if (!shader) + shader = ARBSpecularShaderNoTex; // fallback to no-texture variant if (shader) { nglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, shader);