diff --git a/README.md b/README.md index 63859db..97c6477 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,11 @@ backend. | `EXT_color_buffer_float` / `WEBGL_color_buffer_float` | Exposed | | `EXT_color_buffer_half_float` | Exposed | | `EXT_frag_depth` | Exposed | +| `EXT_float_blend` | Exposed | | `EXT_sRGB` | Exposed | | `EXT_shader_texture_lod` | Exposed | | `EXT_texture_filter_anisotropic` | Exposed | +| `EXT_texture_mirror_clamp_to_edge` | Exposed | | `OES_element_index_uint` | Exposed | | `OES_standard_derivatives` | Exposed | | `OES_texture_float` / `OES_texture_float_linear` | Exposed | diff --git a/binding/binding.cc b/binding/binding.cc index d17564d..ba127a7 100644 --- a/binding/binding.cc +++ b/binding/binding.cc @@ -39,9 +39,11 @@ static napi_value InitBinding(napi_env env, napi_value exports) { EXTColorBufferFloatExtension::Register(env, exports); EXTColorBufferHalfFloatExtension::Register(env, exports); EXTFragDepthExtension::Register(env, exports); + EXTFloatBlendExtension::Register(env, exports); EXTShaderTextureLodExtension::Register(env, exports); EXTSRGBExtension::Register(env, exports); EXTTextureFilterAnisotropicExtension::Register(env, exports); + EXTTextureMirrorClampToEdgeExtension::Register(env, exports); OESElementIndexUintExtension::Register(env, exports); OESStandardDerivativesExtension::Register(env, exports); OESTextureFloatExtension::Register(env, exports); diff --git a/binding/webgl_extensions.cc b/binding/webgl_extensions.cc index fe5252b..a921823 100644 --- a/binding/webgl_extensions.cc +++ b/binding/webgl_extensions.cc @@ -41,6 +41,15 @@ namespace nodejsgl { egl_context_wrapper->gl_extensions->HasExtension(ext_name); #endif +static void RequestExtensionIfAvailable(EGLContextWrapper* egl_context_wrapper, + const char* ext_name) { + if (egl_context_wrapper->angle_requestable_extensions->HasExtension( + ext_name) || + egl_context_wrapper->gl_extensions->HasExtension(ext_name)) { + egl_context_wrapper->glRequestExtensionANGLE(ext_name); + } +} + //============================================================================== // GLExtensionBase @@ -210,6 +219,7 @@ napi_status EXTColorBufferFloatExtension::NewInstance( ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus); egl_context_wrapper->glRequestExtensionANGLE("GL_EXT_color_buffer_float"); + RequestExtensionIfAvailable(egl_context_wrapper, "GL_EXT_float_blend"); egl_context_wrapper->RefreshGLExtensions(); return napi_ok; @@ -307,6 +317,51 @@ napi_status EXTFragDepthExtension::NewInstance( return napi_ok; } +//============================================================================== +// EXTFloatBlendExtension + +napi_ref EXTFloatBlendExtension::constructor_ref_; + +EXTFloatBlendExtension::EXTFloatBlendExtension(napi_env env) + : GLExtensionBase(env) {} + +/* static */ +bool EXTFloatBlendExtension::IsSupported( + EGLContextWrapper* egl_context_wrapper) { + IS_EXTENSION_NAME_AVAILABLE("GL_EXT_float_blend"); +} + +/* static */ +napi_status EXTFloatBlendExtension::Register(napi_env env, napi_value exports) { + napi_status nstatus; + + napi_value ctor_value; + nstatus = napi_define_class(env, "EXT_float_blend", NAPI_AUTO_LENGTH, + GLExtensionBase::InitStubClass, nullptr, 0, + nullptr, &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 EXTFloatBlendExtension::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); + + egl_context_wrapper->glRequestExtensionANGLE("GL_EXT_float_blend"); + egl_context_wrapper->RefreshGLExtensions(); + + return napi_ok; +} + //============================================================================== // EXTShaderTextureLodExtension @@ -460,6 +515,58 @@ napi_status EXTTextureFilterAnisotropicExtension::NewInstance( return napi_ok; } +//============================================================================== +// EXTTextureMirrorClampToEdgeExtension + +napi_ref EXTTextureMirrorClampToEdgeExtension::constructor_ref_; + +EXTTextureMirrorClampToEdgeExtension::EXTTextureMirrorClampToEdgeExtension( + napi_env env) + : GLExtensionBase(env) {} + +/* static */ +bool EXTTextureMirrorClampToEdgeExtension::IsSupported( + EGLContextWrapper* egl_context_wrapper) { + IS_EXTENSION_NAME_AVAILABLE("GL_EXT_texture_mirror_clamp_to_edge"); +} + +/* static */ +napi_status EXTTextureMirrorClampToEdgeExtension::Register(napi_env env, + napi_value exports) { + napi_status nstatus; + + napi_property_descriptor properties[] = {NapiDefineIntProperty( + env, GL_MIRROR_CLAMP_TO_EDGE_EXT, "MIRROR_CLAMP_TO_EDGE_EXT")}; + + napi_value ctor_value; + nstatus = napi_define_class(env, "EXT_texture_mirror_clamp_to_edge", + 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 EXTTextureMirrorClampToEdgeExtension::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); + + egl_context_wrapper->glRequestExtensionANGLE( + "GL_EXT_texture_mirror_clamp_to_edge"); + egl_context_wrapper->RefreshGLExtensions(); + + return napi_ok; +} + //============================================================================== // OESElementIndexUintExtension @@ -601,6 +708,7 @@ napi_status OESTextureFloatExtension::NewInstance( "GL_CHROMIUM_color_buffer_float_rgba"); egl_context_wrapper->glRequestExtensionANGLE( "GL_CHROMIUM_color_buffer_float_rgb"); + RequestExtensionIfAvailable(egl_context_wrapper, "GL_EXT_float_blend"); egl_context_wrapper->RefreshGLExtensions(); return napi_ok; diff --git a/binding/webgl_extensions.h b/binding/webgl_extensions.h index e67475f..c79a8fa 100644 --- a/binding/webgl_extensions.h +++ b/binding/webgl_extensions.h @@ -111,6 +111,16 @@ class EXTFragDepthExtension : public GLExtensionBase { virtual ~EXTFragDepthExtension() {} }; +// Provides 'EXT_float_blend': +// https://registry.khronos.org/webgl/extensions/EXT_float_blend/ +class EXTFloatBlendExtension : public GLExtensionBase { + NAPI_BOOTSTRAP_METHODS + + protected: + EXTFloatBlendExtension(napi_env env); + virtual ~EXTFloatBlendExtension() {} +}; + // Provides 'EXT_shader_texture_lod': // https://www.khronos.org/registry/webgl/extensions/EXT_shader_texture_lod/ class EXTShaderTextureLodExtension : public GLExtensionBase { @@ -141,6 +151,16 @@ class EXTTextureFilterAnisotropicExtension : public GLExtensionBase { virtual ~EXTTextureFilterAnisotropicExtension() {} }; +// Provides 'EXT_texture_mirror_clamp_to_edge': +// https://registry.khronos.org/webgl/extensions/EXT_texture_mirror_clamp_to_edge/ +class EXTTextureMirrorClampToEdgeExtension : public GLExtensionBase { + NAPI_BOOTSTRAP_METHODS + + protected: + EXTTextureMirrorClampToEdgeExtension(napi_env env); + virtual ~EXTTextureMirrorClampToEdgeExtension() {} +}; + // Provides 'OES_element_index_uint': // https://www.khronos.org/registry/webgl/extensions/OES_element_index_uint/ class OESElementIndexUintExtension : public GLExtensionBase { diff --git a/binding/webgl_rendering_context.cc b/binding/webgl_rendering_context.cc index f95b2ad..d520d5f 100644 --- a/binding/webgl_rendering_context.cc +++ b/binding/webgl_rendering_context.cc @@ -6229,11 +6229,14 @@ DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTColorBufferFloat, DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTColorBufferHalfFloat, EXTColorBufferHalfFloatExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTFragDepth, EXTFragDepthExtension) +DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTFloatBlend, EXTFloatBlendExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTSRGB, EXTSRGBExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTShaderTextureLod, EXTShaderTextureLodExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTTextureFilterAnisotropic, EXTTextureFilterAnisotropicExtension) +DEFINE_EXTENSION_DESCRIPTOR_HELPERS(EXTTextureMirrorClampToEdge, + EXTTextureMirrorClampToEdgeExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(OESElementIndexUint, OESElementIndexUintExtension) DEFINE_EXTENSION_DESCRIPTOR_HELPERS(OESStandardDerivatives, @@ -6276,11 +6279,14 @@ static const WebGLExtensionDescriptor kKnownWebGLExtensions[] = { {"EXT_color_buffer_half_float", SupportsEXTColorBufferHalfFloat, NewEXTColorBufferHalfFloat}, {"EXT_frag_depth", SupportsEXTFragDepth, NewEXTFragDepth}, + {"EXT_float_blend", SupportsEXTFloatBlend, NewEXTFloatBlend}, {"EXT_sRGB", SupportsEXTSRGB, NewEXTSRGB}, {"EXT_shader_texture_lod", SupportsEXTShaderTextureLod, NewEXTShaderTextureLod}, {"EXT_texture_filter_anisotropic", SupportsEXTTextureFilterAnisotropic, NewEXTTextureFilterAnisotropic}, + {"EXT_texture_mirror_clamp_to_edge", SupportsEXTTextureMirrorClampToEdge, + NewEXTTextureMirrorClampToEdge}, {"OES_element_index_uint", SupportsOESElementIndexUint, NewOESElementIndexUint}, {"OES_standard_derivatives", SupportsOESStandardDerivatives, diff --git a/src/binding.ts b/src/binding.ts index 1ff0314..83d9a0b 100644 --- a/src/binding.ts +++ b/src/binding.ts @@ -77,6 +77,12 @@ export type NodeGlesWEBGLDrawBuffers = { drawBuffersWEBGL(buffers: number[] | Uint32Array): void; }; +export type NodeGlesEXTFloatBlend = {}; + +export type NodeGlesEXTTextureMirrorClampToEdge = { + readonly MIRROR_CLAMP_TO_EDGE_EXT: number; +}; + export type NodeGlesWebGL2RenderingContext = WebGL2RenderingContext & { drawingBufferColorSpace: "srgb" | "display-p3"; readonly drawingBufferFormat: number; @@ -183,6 +189,10 @@ export type NodeGlesWebGL2RenderingContext = WebGL2RenderingContext & { srcOffsetOrOffset?: number, srcLengthOverride?: number): void; getExtension(extensionName: "ANGLE_instanced_arrays"): NodeGlesANGLEInstancedArrays | null; + getExtension(extensionName: "EXT_float_blend"): + NodeGlesEXTFloatBlend | null; + getExtension(extensionName: "EXT_texture_mirror_clamp_to_edge"): + NodeGlesEXTTextureMirrorClampToEdge | null; getExtension(extensionName: "OES_vertex_array_object"): NodeGlesOESVertexArrayObject | null; getExtension(extensionName: "WEBGL_draw_buffers"): diff --git a/test/webgl2-smoke.js b/test/webgl2-smoke.js index a8484cb..3fd7a26 100644 --- a/test/webgl2-smoke.js +++ b/test/webgl2-smoke.js @@ -290,9 +290,11 @@ function testSupportedExtensionsReflectGetExtension(gl) { "WEBGL_color_buffer_float", "EXT_color_buffer_half_float", "EXT_frag_depth", + "EXT_float_blend", "EXT_sRGB", "EXT_shader_texture_lod", "EXT_texture_filter_anisotropic", + "EXT_texture_mirror_clamp_to_edge", "OES_element_index_uint", "OES_standard_derivatives", "OES_texture_float", @@ -499,6 +501,18 @@ function testGetParameterSemantics(gl) { assert(Number.isInteger( gl.getParameter(derivatives.FRAGMENT_SHADER_DERIVATIVE_HINT_OES))); } + const mirrorClamp = + gl.getExtension("EXT_texture_mirror_clamp_to_edge"); + if (mirrorClamp) { + assert.strictEqual(mirrorClamp.MIRROR_CLAMP_TO_EDGE_EXT, 0x8743); + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri( + gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, + mirrorClamp.MIRROR_CLAMP_TO_EDGE_EXT); + gl.deleteTexture(texture); + assertNoError(gl, "EXT_texture_mirror_clamp_to_edge wrap mode"); + } assert.strictEqual(gl.getError(), gl.NO_ERROR); assert.strictEqual(gl.getParameter(0xdeadbeef), null); @@ -506,6 +520,108 @@ function testGetParameterSemantics(gl) { assert.strictEqual(gl.getError(), gl.NO_ERROR); } +function assertEXTFloatBlendDraw(gl, label) { + const previousViewport = gl.getParameter(gl.VIEWPORT); + const wasBlendEnabled = gl.isEnabled(gl.BLEND); + const previousBlendSrcRgb = gl.getParameter(gl.BLEND_SRC_RGB); + const previousBlendDstRgb = gl.getParameter(gl.BLEND_DST_RGB); + const previousBlendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA); + const previousBlendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA); + const previousClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE); + + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + configureTexture(gl, gl.TEXTURE_2D); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA32F, 1, 1, 0, gl.RGBA, gl.FLOAT, null); + + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + assert.strictEqual( + gl.checkFramebufferStatus(gl.FRAMEBUFFER), gl.FRAMEBUFFER_COMPLETE); + gl.drawBuffers([gl.COLOR_ATTACHMENT0]); + + const program = createProgramFromSources(gl, `#version 300 es +in vec2 a_position; +void main() { + gl_Position = vec4(a_position, 0.0, 1.0); +} +`, `#version 300 es +precision highp float; +out vec4 out_color; +void main() { + out_color = vec4(0.25, 0.5, 0.75, 0.5); +} +`); + const vao = gl.createVertexArray(); + const positionBuffer = gl.createBuffer(); + gl.bindVertexArray(vao); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), + gl.STATIC_DRAW); + const positionLocation = gl.getAttribLocation(program, "a_position"); + assert(positionLocation >= 0, "EXT_float_blend position attribute not found"); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(positionLocation); + + gl.viewport(0, 0, 1, 1); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE); + gl.useProgram(program); + gl.drawArrays(gl.TRIANGLES, 0, 3); + gl.drawArrays(gl.TRIANGLES, 0, 3); + + const readback = new Float32Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, readback); + assertArrayNear( + Array.from(readback), [0.5, 1, 1.5, 1], + `${label} implicit EXT_float_blend additive blending`, 1e-6); + + if (!wasBlendEnabled) { + gl.disable(gl.BLEND); + } + gl.blendFuncSeparate( + previousBlendSrcRgb, previousBlendDstRgb, + previousBlendSrcAlpha, previousBlendDstAlpha); + gl.viewport( + previousViewport[0], previousViewport[1], + previousViewport[2], previousViewport[3]); + gl.clearColor( + previousClearColor[0], previousClearColor[1], + previousClearColor[2], previousClearColor[3]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindVertexArray(null); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.deleteBuffer(positionBuffer); + gl.deleteVertexArray(vao); + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + gl.deleteProgram(program); + assertNoError(gl, `${label} implicit EXT_float_blend additive blending`); +} + +function testEXTFloatBlendImplicitEnable() { + for (const triggerName of [ + "EXT_color_buffer_float", + "WEBGL_color_buffer_float", + "OES_texture_float" + ]) { + const blendGl = createContext({width: 1, height: 1}); + if (!blendGl.getExtension(triggerName)) { + blendGl.destroy(); + continue; + } + assertEXTFloatBlendDraw(blendGl, triggerName); + blendGl.destroy(); + } +} + function testBufferCopyAndReadback(gl) { const source = gl.createBuffer(); const destination = gl.createBuffer(); @@ -2547,6 +2663,7 @@ testSupportedExtensionsReflectGetExtension(gl); testUnsupportedExtensionDoesNotWriteStderr(); testInfoLogEmptyStrings(gl); testGetParameterSemantics(gl); +testEXTFloatBlendImplicitEnable(); testBufferCopyAndReadback(gl); testSamplerObjects(gl); testSyncObjects(gl);