From 7205d6daf3741a1b7f1be2e372a98dd94743fe19 Mon Sep 17 00:00:00 2001 From: Sahinur Date: Thu, 8 Oct 2026 17:54:39 +0600 Subject: [PATCH 1/3] fix(OpenGLRenderWindow): request WEBGL_debug_renderer_info lazily Firefox deprecated WEBGL_debug_renderer_info and logs a warning each time the extension is requested. getGLInformations() is called whenever a texture is created (useTexStorage), so every vtk.js app showed the warning. Only request the extension when the UNMASKED_RENDERER or UNMASKED_VENDOR values are actually read. fix #3506 --- .../Rendering/OpenGL/RenderWindow/index.js | 39 +++++++++++-- .../RenderWindow/test/testGLInformations.js | 56 +++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 Sources/Rendering/OpenGL/RenderWindow/test/testGLInformations.js diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js index 13e511c9e8c..1eb6718f37a 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.js +++ b/Sources/Rendering/OpenGL/RenderWindow/index.js @@ -663,7 +663,17 @@ function vtkOpenGLRenderWindow(publicAPI, model) { // Float/halfFloat textures and multiple render targets are core in // WebGL2; only float color renderability still hangs off an extension. const glColorBufferFloat = gl.getExtension('EXT_color_buffer_float'); - const glDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info'); + // Firefox deprecated WEBGL_debug_renderer_info (RENDERER/VENDOR already + // report the unmasked strings there) and logs a warning every time the + // extension is requested, so only request it when the unmasked values + // are actually read. + let glDebugRendererInfo; + const getUnmaskedParameter = (name) => () => { + if (glDebugRendererInfo === undefined) { + glDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info'); + } + return glDebugRendererInfo && gl.getParameter(glDebugRendererInfo[name]); + }; const glAnisotropic = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); @@ -998,14 +1008,12 @@ function vtkOpenGLRenderWindow(publicAPI, model) { [ 'Unmasked Renderer', 'UNMASKED_RENDERER', - glDebugRendererInfo && - gl.getParameter(glDebugRendererInfo.UNMASKED_RENDERER_WEBGL), + getUnmaskedParameter('UNMASKED_RENDERER_WEBGL'), ], [ 'Unmasked Vendor', 'UNMASKED_VENDOR', - glDebugRendererInfo && - gl.getParameter(glDebugRendererInfo.UNMASKED_VENDOR_WEBGL), + getUnmaskedParameter('UNMASKED_VENDOR_WEBGL'), ], ['WebGL Version', 'WEBGL_VERSION', 2], ]; @@ -1014,7 +1022,26 @@ function vtkOpenGLRenderWindow(publicAPI, model) { while (params.length) { const [label, key, value] = params.pop(); if (key) { - result[key] = { label, value }; + if (typeof value === 'function') { + // lazily evaluated (and cached) on first access + const entry = { label }; + Object.defineProperty(entry, 'value', { + get() { + const resolved = value(); + Object.defineProperty(entry, 'value', { + value: resolved, + enumerable: true, + writable: true, + }); + return resolved; + }, + enumerable: true, + configurable: true, + }); + result[key] = entry; + } else { + result[key] = { label, value }; + } } } model._glInformation = result; diff --git a/Sources/Rendering/OpenGL/RenderWindow/test/testGLInformations.js b/Sources/Rendering/OpenGL/RenderWindow/test/testGLInformations.js new file mode 100644 index 00000000000..6d89d277187 --- /dev/null +++ b/Sources/Rendering/OpenGL/RenderWindow/test/testGLInformations.js @@ -0,0 +1,56 @@ +import { it, expect, vi } from 'vitest'; + +import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow'; + +it.skipIf(__VTK_TEST_NO_WEBGL__)( + 'only requests WEBGL_debug_renderer_info when unmasked values are read', + () => { + const getExtension = vi.spyOn( + WebGL2RenderingContext.prototype, + 'getExtension' + ); + const glWindow = vtkOpenGLRenderWindow.newInstance(); + + try { + const info = glWindow.getGLInformations(); + expect(info.RENDERER.value).toBeTruthy(); + expect(info.UNMASKED_RENDERER.label).toBe('Unmasked Renderer'); + // Firefox logs a deprecation warning whenever this extension is + // requested, so getting the regular GL informations must not do it. + expect(getExtension).not.toHaveBeenCalledWith( + 'WEBGL_debug_renderer_info' + ); + + const unmaskedRenderer = info.UNMASKED_RENDERER.value; + const unmaskedVendor = info.UNMASKED_VENDOR.value; + expect(getExtension).toHaveBeenCalledWith('WEBGL_debug_renderer_info'); + const calls = getExtension.mock.calls.filter( + ([name]) => name === 'WEBGL_debug_renderer_info' + ).length; + expect(calls).toBe(1); + + const debugInfo = glWindow + .get3DContext() + .getExtension('WEBGL_debug_renderer_info'); + if (debugInfo) { + const gl = glWindow.get3DContext(); + expect(unmaskedRenderer).toBe( + gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) + ); + expect(unmaskedVendor).toBe( + gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) + ); + } else { + expect(unmaskedRenderer).toBeFalsy(); + expect(unmaskedVendor).toBeFalsy(); + } + + // values are cached and enumerable like the other entries + expect(info.UNMASKED_RENDERER.value).toBe(unmaskedRenderer); + expect(Object.keys(info.UNMASKED_RENDERER)).toEqual(['label', 'value']); + } finally { + getExtension.mockRestore(); + glWindow.delete(); + } + } +); From a1c1bb5d2240c3ee4c211e0d7647a7e99e80a5a6 Mon Sep 17 00:00:00 2001 From: Sahinur Date: Thu, 8 Oct 2026 19:40:06 +0600 Subject: [PATCH 2/3] refactor(OpenGLRenderWindow): move GL informations helpers to module scope Address review feedback: - rename getUnmaskedParameter to the generic createLazyExtensionParameterGetter(gl, extensionName) and move it to the top of the file - move the params-to-result while loop into buildGLInformations(), with the lazy value definition split into defineLazyProperty() No behavior change. --- .../Rendering/OpenGL/RenderWindow/index.js | 99 ++++++++++++------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js index 1eb6718f37a..c6b3c0ba61b 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.js +++ b/Sources/Rendering/OpenGL/RenderWindow/index.js @@ -86,6 +86,63 @@ function checkRenderTargetSupport(gl, internalFormat, format, type) { return status === gl.FRAMEBUFFER_COMPLETE; } +// ---------------------------------------------------------------------------- +// GL informations helpers +// ---------------------------------------------------------------------------- + +// Returns a factory of lazy getters for the parameters of a WebGL extension. +// The extension is only requested (once) when one of the getters is first +// called. This matters for extensions that are costly or noisy to request, +// e.g. Firefox logs a deprecation warning whenever WEBGL_debug_renderer_info +// is requested. +function createLazyExtensionParameterGetter(gl, extensionName) { + let extension; + return (parameterName) => () => { + if (extension === undefined) { + extension = gl.getExtension(extensionName); + } + return extension && gl.getParameter(extension[parameterName]); + }; +} + +// Defines an enumerable `object[name]` property that is only evaluated with +// `evaluate()` on first access, then cached as a regular writable value. +function defineLazyProperty(object, name, evaluate) { + Object.defineProperty(object, name, { + get() { + const value = evaluate(); + Object.defineProperty(object, name, { + value, + enumerable: true, + writable: true, + }); + return value; + }, + enumerable: true, + configurable: true, + }); +} + +// Converts a list of [label, key, value] entries into a { [key]: { label, +// value } } map. A function value is evaluated lazily, on first read of the +// entry value. Entries without a key are skipped. +function buildGLInformations(params) { + const result = {}; + while (params.length) { + const [label, key, value] = params.pop(); + if (key) { + if (typeof value === 'function') { + const entry = { label }; + defineLazyProperty(entry, 'value', value); + result[key] = entry; + } else { + result[key] = { label, value }; + } + } + } + return result; +} + // ---------------------------------------------------------------------------- // Monitor the usage of GL context across vtkOpenGLRenderWindow instances // ---------------------------------------------------------------------------- @@ -667,13 +724,10 @@ function vtkOpenGLRenderWindow(publicAPI, model) { // report the unmasked strings there) and logs a warning every time the // extension is requested, so only request it when the unmasked values // are actually read. - let glDebugRendererInfo; - const getUnmaskedParameter = (name) => () => { - if (glDebugRendererInfo === undefined) { - glDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info'); - } - return glDebugRendererInfo && gl.getParameter(glDebugRendererInfo[name]); - }; + const getDebugRendererInfoParameter = createLazyExtensionParameterGetter( + gl, + 'WEBGL_debug_renderer_info' + ); const glAnisotropic = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); @@ -1008,42 +1062,17 @@ function vtkOpenGLRenderWindow(publicAPI, model) { [ 'Unmasked Renderer', 'UNMASKED_RENDERER', - getUnmaskedParameter('UNMASKED_RENDERER_WEBGL'), + getDebugRendererInfoParameter('UNMASKED_RENDERER_WEBGL'), ], [ 'Unmasked Vendor', 'UNMASKED_VENDOR', - getUnmaskedParameter('UNMASKED_VENDOR_WEBGL'), + getDebugRendererInfoParameter('UNMASKED_VENDOR_WEBGL'), ], ['WebGL Version', 'WEBGL_VERSION', 2], ]; - const result = {}; - while (params.length) { - const [label, key, value] = params.pop(); - if (key) { - if (typeof value === 'function') { - // lazily evaluated (and cached) on first access - const entry = { label }; - Object.defineProperty(entry, 'value', { - get() { - const resolved = value(); - Object.defineProperty(entry, 'value', { - value: resolved, - enumerable: true, - writable: true, - }); - return resolved; - }, - enumerable: true, - configurable: true, - }); - result[key] = entry; - } else { - result[key] = { label, value }; - } - } - } + const result = buildGLInformations(params); model._glInformation = result; return result; }; From 40aedf6f3e762642b48474b62d1f29b4a8cc59b4 Mon Sep 17 00:00:00 2001 From: Sahinur Date: Mon, 12 Oct 2026 14:40:48 +0600 Subject: [PATCH 3/3] refactor(OpenGLRenderWindow): drop temporary variable in getGLInformations --- Sources/Rendering/OpenGL/RenderWindow/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js index c6b3c0ba61b..f141603a6e8 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.js +++ b/Sources/Rendering/OpenGL/RenderWindow/index.js @@ -1072,9 +1072,8 @@ function vtkOpenGLRenderWindow(publicAPI, model) { ['WebGL Version', 'WEBGL_VERSION', 2], ]; - const result = buildGLInformations(params); - model._glInformation = result; - return result; + model._glInformation = buildGLInformations(params); + return model._glInformation; }; publicAPI.traverseAllPasses = () => {