From 4a738b381263bf9b6da5f0bc9084503447997891 Mon Sep 17 00:00:00 2001 From: dsafdsaf132 Date: Wed, 10 Jun 2026 14:10:42 +0900 Subject: [PATCH] Expose S3TC compressed texture extensions --- README.md | 2 +- binding/binding.cc | 2 + binding/webgl_extensions.cc | 175 +++++++++++++++++++++++++++++ binding/webgl_extensions.h | 20 ++++ binding/webgl_rendering_context.cc | 8 ++ src/binding.ts | 18 +++ test/webgl2-smoke.js | 88 +++++++++++++++ 7 files changed, 312 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9bd4ed..0af99db 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ backend. | `WEBGL_draw_buffers` | Exposed | | `WEBGL_lose_context` | Exposed | | `EXT_disjoint_timer_query` / `EXT_disjoint_timer_query_webgl2` | Not exposed | -| `WEBGL_compressed_texture_s3tc` / `WEBGL_compressed_texture_s3tc_srgb` | Not exposed | +| `WEBGL_compressed_texture_s3tc` / `WEBGL_compressed_texture_s3tc_srgb` | Exposed | ## Install diff --git a/binding/binding.cc b/binding/binding.cc index ba127a7..c4386e1 100644 --- a/binding/binding.cc +++ b/binding/binding.cc @@ -52,6 +52,8 @@ static napi_value InitBinding(napi_env env, napi_value exports) { OESTextureHalfFloatLinearExtension::Register(env, exports); WebGLDebugRendererInfoExtension::Register(env, exports); WebGLDepthTextureExtension::Register(env, exports); + WebGLCompressedTextureS3TCExtension::Register(env, exports); + WebGLCompressedTextureS3TCSRGBExtension::Register(env, exports); WebGLLoseContextExtension::Register(env, exports); WebGLRenderingContext::Register(env, exports); diff --git a/binding/webgl_extensions.cc b/binding/webgl_extensions.cc index a921823..26cc5cd 100644 --- a/binding/webgl_extensions.cc +++ b/binding/webgl_extensions.cc @@ -50,6 +50,59 @@ static void RequestExtensionIfAvailable(EGLContextWrapper* egl_context_wrapper, } } +static bool IsExtensionAvailable(EGLContextWrapper* egl_context_wrapper, + const char* ext_name) { + return egl_context_wrapper->angle_requestable_extensions->HasExtension( + ext_name) || + egl_context_wrapper->gl_extensions->HasExtension(ext_name); +} + +static bool IsAnyExtensionAvailable(EGLContextWrapper* egl_context_wrapper, + const char* const* ext_names, + size_t ext_count) { + for (size_t i = 0; i < ext_count; ++i) { + if (IsExtensionAvailable(egl_context_wrapper, ext_names[i])) { + return true; + } + } + return false; +} + +static bool IsS3TCTextureCompressionSupported( + EGLContextWrapper* egl_context_wrapper) { + static const char* kDxt1Extensions[] = { + "GL_EXT_texture_compression_s3tc", + "GL_EXT_texture_compression_dxt1", + }; + static const char* kDxt3Extensions[] = { + "GL_EXT_texture_compression_s3tc", + "GL_ANGLE_texture_compression_dxt3", + }; + static const char* kDxt5Extensions[] = { + "GL_EXT_texture_compression_s3tc", + "GL_ANGLE_texture_compression_dxt5", + }; + + return IsAnyExtensionAvailable(egl_context_wrapper, kDxt1Extensions, + ARRAY_SIZE(kDxt1Extensions)) && + IsAnyExtensionAvailable(egl_context_wrapper, kDxt3Extensions, + ARRAY_SIZE(kDxt3Extensions)) && + IsAnyExtensionAvailable(egl_context_wrapper, kDxt5Extensions, + ARRAY_SIZE(kDxt5Extensions)); +} + +static void RequestS3TCTextureCompressionExtensions( + EGLContextWrapper* egl_context_wrapper) { + RequestExtensionIfAvailable(egl_context_wrapper, + "GL_EXT_texture_compression_s3tc"); + RequestExtensionIfAvailable(egl_context_wrapper, + "GL_EXT_texture_compression_dxt1"); + RequestExtensionIfAvailable(egl_context_wrapper, + "GL_ANGLE_texture_compression_dxt3"); + RequestExtensionIfAvailable(egl_context_wrapper, + "GL_ANGLE_texture_compression_dxt5"); +} + //============================================================================== // GLExtensionBase @@ -959,6 +1012,128 @@ napi_status WebGLDepthTextureExtension::NewInstance( return napi_ok; } +//============================================================================== +// WebGLCompressedTextureS3TCExtension + +napi_ref WebGLCompressedTextureS3TCExtension::constructor_ref_; + +WebGLCompressedTextureS3TCExtension::WebGLCompressedTextureS3TCExtension( + napi_env env) + : GLExtensionBase(env) {} + +/* static */ +bool WebGLCompressedTextureS3TCExtension::IsSupported( + EGLContextWrapper* egl_context_wrapper) { + return IsS3TCTextureCompressionSupported(egl_context_wrapper); +} + +/* static */ +napi_status WebGLCompressedTextureS3TCExtension::Register(napi_env env, + napi_value exports) { + napi_status nstatus; + + napi_property_descriptor properties[] = { + NapiDefineIntProperty(env, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, + "COMPRESSED_RGB_S3TC_DXT1_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, + "COMPRESSED_RGBA_S3TC_DXT1_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, + "COMPRESSED_RGBA_S3TC_DXT3_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, + "COMPRESSED_RGBA_S3TC_DXT5_EXT"), + }; + + napi_value ctor_value; + nstatus = + napi_define_class(env, "WEBGL_compressed_texture_s3tc", NAPI_AUTO_LENGTH, + GLExtensionBase::InitStubClass, nullptr, + ARRAY_SIZE(properties), properties, &ctor_value); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + nstatus = napi_create_reference(env, ctor_value, 1, &constructor_ref_); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + return napi_ok; +} + +/* static */ +napi_status WebGLCompressedTextureS3TCExtension::NewInstance( + napi_env env, napi_value* instance, + EGLContextWrapper* egl_context_wrapper) { + ENSURE_EXTENSION_IS_SUPPORTED + + napi_status nstatus = NewInstanceBase(env, constructor_ref_, instance); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + RequestS3TCTextureCompressionExtensions(egl_context_wrapper); + egl_context_wrapper->RefreshGLExtensions(); + + return napi_ok; +} + +//============================================================================== +// WebGLCompressedTextureS3TCSRGBExtension + +napi_ref WebGLCompressedTextureS3TCSRGBExtension::constructor_ref_; + +WebGLCompressedTextureS3TCSRGBExtension:: + WebGLCompressedTextureS3TCSRGBExtension(napi_env env) + : GLExtensionBase(env) {} + +/* static */ +bool WebGLCompressedTextureS3TCSRGBExtension::IsSupported( + EGLContextWrapper* egl_context_wrapper) { + return IsS3TCTextureCompressionSupported(egl_context_wrapper) && + IsExtensionAvailable(egl_context_wrapper, + "GL_EXT_texture_compression_s3tc_srgb"); +} + +/* static */ +napi_status WebGLCompressedTextureS3TCSRGBExtension::Register( + napi_env env, napi_value exports) { + napi_status nstatus; + + napi_property_descriptor properties[] = { + NapiDefineIntProperty(env, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT, + "COMPRESSED_SRGB_S3TC_DXT1_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, + "COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, + "COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT"), + NapiDefineIntProperty(env, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, + "COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT"), + }; + + napi_value ctor_value; + nstatus = napi_define_class(env, "WEBGL_compressed_texture_s3tc_srgb", + NAPI_AUTO_LENGTH, GLExtensionBase::InitStubClass, + nullptr, ARRAY_SIZE(properties), properties, + &ctor_value); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + nstatus = napi_create_reference(env, ctor_value, 1, &constructor_ref_); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + return napi_ok; +} + +/* static */ +napi_status WebGLCompressedTextureS3TCSRGBExtension::NewInstance( + napi_env env, napi_value* instance, + EGLContextWrapper* egl_context_wrapper) { + ENSURE_EXTENSION_IS_SUPPORTED + + napi_status nstatus = NewInstanceBase(env, constructor_ref_, instance); + ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); + + RequestS3TCTextureCompressionExtensions(egl_context_wrapper); + RequestExtensionIfAvailable(egl_context_wrapper, + "GL_EXT_texture_compression_s3tc_srgb"); + egl_context_wrapper->RefreshGLExtensions(); + + return napi_ok; +} + //============================================================================== // WebGLLoseContextExtension diff --git a/binding/webgl_extensions.h b/binding/webgl_extensions.h index c79a8fa..30dfa8f 100644 --- a/binding/webgl_extensions.h +++ b/binding/webgl_extensions.h @@ -241,6 +241,26 @@ class WebGLDepthTextureExtension : public GLExtensionBase { virtual ~WebGLDepthTextureExtension() {} }; +// Provides 'WEBGL_compressed_texture_s3tc': +// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc/ +class WebGLCompressedTextureS3TCExtension : public GLExtensionBase { + NAPI_BOOTSTRAP_METHODS + + protected: + WebGLCompressedTextureS3TCExtension(napi_env env); + virtual ~WebGLCompressedTextureS3TCExtension() {} +}; + +// Provides 'WEBGL_compressed_texture_s3tc_srgb': +// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ +class WebGLCompressedTextureS3TCSRGBExtension : public GLExtensionBase { + NAPI_BOOTSTRAP_METHODS + + protected: + WebGLCompressedTextureS3TCSRGBExtension(napi_env env); + virtual ~WebGLCompressedTextureS3TCSRGBExtension() {} +}; + // Provides the 'WEBGL_lose_context' extension: // https://www.khronos.org/registry/webgl/extensions/WEBGL_lose_context/ class WebGLLoseContextExtension : public GLExtensionBase { diff --git a/binding/webgl_rendering_context.cc b/binding/webgl_rendering_context.cc index c500218..784e710 100644 --- a/binding/webgl_rendering_context.cc +++ b/binding/webgl_rendering_context.cc @@ -6320,6 +6320,10 @@ DEFINE_EXTENSION_DESCRIPTOR_HELPERS(WebGLDebugRendererInfo, WebGLDebugRendererInfoExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(WebGLDepthTexture, WebGLDepthTextureExtension) +DEFINE_EXTENSION_DESCRIPTOR_HELPERS(WebGLCompressedTextureS3TC, + WebGLCompressedTextureS3TCExtension) +DEFINE_EXTENSION_DESCRIPTOR_HELPERS(WebGLCompressedTextureS3TCSRGB, + WebGLCompressedTextureS3TCSRGBExtension) #undef DEFINE_EXTENSION_DESCRIPTOR_HELPERS @@ -6371,6 +6375,10 @@ static const WebGLExtensionDescriptor kKnownWebGLExtensions[] = { {"WEBGL_debug_renderer_info", SupportsWebGLDebugRendererInfo, NewWebGLDebugRendererInfo}, {"WEBGL_depth_texture", SupportsWebGLDepthTexture, NewWebGLDepthTexture}, + {"WEBGL_compressed_texture_s3tc", SupportsWebGLCompressedTextureS3TC, + NewWebGLCompressedTextureS3TC}, + {"WEBGL_compressed_texture_s3tc_srgb", + SupportsWebGLCompressedTextureS3TCSRGB, NewWebGLCompressedTextureS3TCSRGB}, {"WEBGL_draw_buffers", SupportsWEBGLDrawBuffers, NewWEBGLDrawBuffers}, {"WEBGL_lose_context", WebGLLoseContextExtension::IsSupported, NewWebGLLoseContext}, diff --git a/src/binding.ts b/src/binding.ts index f1f8cf1..36a10fa 100644 --- a/src/binding.ts +++ b/src/binding.ts @@ -133,6 +133,20 @@ export type NodeGlesWEBGLDepthTexture = { readonly UNSIGNED_INT_24_8_WEBGL: number; }; +export type NodeGlesWEBGLCompressedTextureS3TC = { + readonly COMPRESSED_RGB_S3TC_DXT1_EXT: number; + readonly COMPRESSED_RGBA_S3TC_DXT1_EXT: number; + readonly COMPRESSED_RGBA_S3TC_DXT3_EXT: number; + readonly COMPRESSED_RGBA_S3TC_DXT5_EXT: number; +}; + +export type NodeGlesWEBGLCompressedTextureS3TCSRGB = { + readonly COMPRESSED_SRGB_S3TC_DXT1_EXT: number; + readonly COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: number; + readonly COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: number; + readonly COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: number; +}; + export type NodeGlesWEBGLLoseContext = { loseContext(): void; restoreContext(): void; @@ -282,6 +296,10 @@ export type NodeGlesWebGL2RenderingContext = WebGL2RenderingContext & { NodeGlesWEBGLDebugRendererInfo | null; getExtension(extensionName: "WEBGL_depth_texture"): NodeGlesWEBGLDepthTexture | null; + getExtension(extensionName: "WEBGL_compressed_texture_s3tc"): + NodeGlesWEBGLCompressedTextureS3TC | null; + getExtension(extensionName: "WEBGL_compressed_texture_s3tc_srgb"): + NodeGlesWEBGLCompressedTextureS3TCSRGB | null; getExtension(extensionName: "WEBGL_draw_buffers"): NodeGlesWEBGLDrawBuffers | null; getExtension(extensionName: "WEBGL_lose_context"): diff --git a/test/webgl2-smoke.js b/test/webgl2-smoke.js index f98815b..f1079a5 100644 --- a/test/webgl2-smoke.js +++ b/test/webgl2-smoke.js @@ -304,6 +304,8 @@ function testSupportedExtensionsReflectGetExtension(gl) { "OES_vertex_array_object", "WEBGL_debug_renderer_info", "WEBGL_depth_texture", + "WEBGL_compressed_texture_s3tc", + "WEBGL_compressed_texture_s3tc_srgb", "WEBGL_draw_buffers", "WEBGL_lose_context" ]; @@ -359,6 +361,91 @@ function testSupportedExtensionsReflectGetExtension(gl) { assertNoError(gl, "getExtension browser-compatible names"); } +function testCompressedTextureS3TC(gl) { + const supported = gl.getSupportedExtensions(); + const s3tc = gl.getExtension("WEBGL_compressed_texture_s3tc"); + const s3tcSRGB = gl.getExtension("WEBGL_compressed_texture_s3tc_srgb"); + assert.strictEqual( + supported.includes("WEBGL_compressed_texture_s3tc"), s3tc !== null, + "WEBGL_compressed_texture_s3tc support should match getExtension"); + assert.strictEqual( + supported.includes("WEBGL_compressed_texture_s3tc_srgb"), + s3tcSRGB !== null, + "WEBGL_compressed_texture_s3tc_srgb support should match getExtension"); + + if (!s3tc) { + assert.strictEqual( + s3tcSRGB, null, + "WEBGL_compressed_texture_s3tc_srgb requires base S3TC support"); + assertNoError(gl, "WEBGL_compressed_texture_s3tc unsupported path"); + return; + } + + const s3tcFormats = [ + ["COMPRESSED_RGB_S3TC_DXT1_EXT", 0x83f0, 8], + ["COMPRESSED_RGBA_S3TC_DXT1_EXT", 0x83f1, 8], + ["COMPRESSED_RGBA_S3TC_DXT3_EXT", 0x83f2, 16], + ["COMPRESSED_RGBA_S3TC_DXT5_EXT", 0x83f3, 16] + ]; + const compressedFormats = + new Set(gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)); + for (const [name, value] of s3tcFormats) { + assert.strictEqual(s3tc[name], value, `${name} constant`); + assert( + compressedFormats.has(value), + `${name} should be listed in COMPRESSED_TEXTURE_FORMATS`); + } + + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + for (const [name, , byteLength] of s3tcFormats) { + gl.compressedTexImage2D( + gl.TEXTURE_2D, 0, s3tc[name], 4, 4, 0, + new Uint8Array(byteLength)); + assertNoError(gl, `${name} compressedTexImage2D`); + } + gl.deleteTexture(texture); + + if (!s3tcSRGB) { + assertNoError(gl, "WEBGL_compressed_texture_s3tc smoke"); + return; + } + + const s3tcSRGBFormats = [ + ["COMPRESSED_SRGB_S3TC_DXT1_EXT", 0x8c4c, 8], + ["COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT", 0x8c4d, 8], + ["COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT", 0x8c4e, 16], + ["COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT", 0x8c4f, 16] + ]; + const srgbCompressedFormats = + new Set(gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)); + for (const [name, value] of s3tcSRGBFormats) { + assert.strictEqual(s3tcSRGB[name], value, `${name} constant`); + assert( + srgbCompressedFormats.has(value), + `${name} should be listed in COMPRESSED_TEXTURE_FORMATS`); + } + + const srgbTexture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, srgbTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + for (const [name, , byteLength] of s3tcSRGBFormats) { + gl.compressedTexImage2D( + gl.TEXTURE_2D, 0, s3tcSRGB[name], 4, 4, 0, + new Uint8Array(byteLength)); + assertNoError(gl, `${name} compressedTexImage2D`); + } + gl.deleteTexture(srgbTexture); + assertNoError(gl, "WEBGL_compressed_texture_s3tc_srgb smoke"); +} + function testExtensionCreationOptions() { const defaultGl = createContext(); const defaultExtensions = defaultGl.getSupportedExtensions(); @@ -2735,6 +2822,7 @@ const gl = createContext(); console.log(gl.getParameter(gl.VERSION)); testRequiredWebGL2Methods(gl); testSupportedExtensionsReflectGetExtension(gl); +testCompressedTextureS3TC(gl); testExtensionCreationOptions(); testUnsupportedExtensionDoesNotWriteStderr(); testInfoLogEmptyStrings(gl);