Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ const gl = nodeGles.createWebGLRenderingContext({
console.log(gl.getParameter(gl.VERSION));
```

### Context Options

- `width` / `height`: drawing buffer size, default `1`.
- `majorVersion` / `minorVersion`: requested OpenGL ES version, default `3.0`.
- `webGLCompatibility`: requests ANGLE WebGL compatibility mode. The legacy
misspelled `webGLCompability` option is still accepted as an alias.
- `enabledExtensions`: optional WebGL extension allowlist. When set, only listed
supported extensions are exposed.
- `disabledExtensions`: optional WebGL extension blocklist. This takes
precedence over `enabledExtensions`.

## Context Lifecycle

For batch rendering, call `gl.destroy()` or `gl.dispose()` after the final
Expand Down
3 changes: 3 additions & 0 deletions binding/egl_context_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ struct GLContextOptions {
uint32_t client_minor_es_version = 0;
uint32_t width = 1;
uint32_t height = 1;
bool has_enabled_extensions_filter = false;
std::vector<std::string> enabled_extensions;
std::vector<std::string> disabled_extensions;
};

// Provides lookup of EGL/GL extensions.
Expand Down
124 changes: 116 additions & 8 deletions binding/webgl_rendering_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,44 @@ static napi_status GetStringParam(napi_env env, napi_value string_value,
return napi_ok;
}

static napi_status GetOptionalStringArrayParam(napi_env env, napi_value value,
const char *name,
std::vector<std::string> *out) {
napi_valuetype value_type;
napi_status nstatus = napi_typeof(env, value, &value_type);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);

if (value_type == napi_undefined || value_type == napi_null) {
return napi_ok;
}

bool is_array = false;
nstatus = napi_is_array(env, value, &is_array);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);
if (!is_array) {
std::string message = std::string(name) + " must be an array";
NAPI_THROW_ERROR(env, message.c_str());
return napi_invalid_arg;
}

uint32_t length = 0;
nstatus = napi_get_array_length(env, value, &length);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);

for (uint32_t i = 0; i < length; ++i) {
napi_value item;
nstatus = napi_get_element(env, value, i, &item);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);

std::string extension_name;
nstatus = GetStringParam(env, item, extension_name);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);
out->push_back(extension_name);
}

return napi_ok;
}

static napi_status CreateInfoLogString(napi_env env, const char *log,
GLsizei length, napi_value *result) {
if (log == nullptr || length <= 0) {
Expand Down Expand Up @@ -1976,7 +2014,10 @@ WebGLRenderingContext::WebGLRenderingContext(napi_env env,
ref_(nullptr),
eglContextWrapper_(nullptr),
drawing_buffer_color_space_("srgb"),
unpack_color_space_("srgb") {
unpack_color_space_("srgb"),
has_enabled_extensions_filter_(opts.has_enabled_extensions_filter),
enabled_extensions_(opts.enabled_extensions),
disabled_extensions_(opts.disabled_extensions) {
eglContextWrapper_ = EGLContextWrapper::Create(env, opts);
if (!eglContextWrapper_) {
bool exception_pending = false;
Expand Down Expand Up @@ -3061,13 +3102,13 @@ napi_status WebGLRenderingContext::NewInstance(napi_env env,
nstatus = napi_get_reference_value(env, constructor_ref_, &ctor_value);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);

size_t argc = 5;
napi_value args[5];
size_t argc = 7;
napi_value args[7];
napi_value js_this;
nstatus = napi_get_cb_info(env, info, &argc, args, &js_this, nullptr);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);
ENSURE_ARGC_RETVAL(env, argc, 5, nstatus);
argc = std::min(argc, static_cast<size_t>(5));
argc = std::min(argc, static_cast<size_t>(7));

nstatus = napi_new_instance(env, ctor_value, argc, args, instance);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nstatus);
Expand All @@ -3082,8 +3123,8 @@ napi_value WebGLRenderingContext::InitInternal(napi_env env,

ENSURE_CONSTRUCTOR_CALL_RETVAL(env, info, nullptr);

size_t argc = 5;
napi_value args[5];
size_t argc = 7;
napi_value args[7];
napi_value js_this;
nstatus = napi_get_cb_info(env, info, &argc, args, &js_this, nullptr);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);
Expand All @@ -3105,6 +3146,23 @@ napi_value WebGLRenderingContext::InitInternal(napi_env env,
nstatus = napi_get_value_bool(env, args[4], &opts.webgl_compatibility);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);

if (argc > 5) {
napi_valuetype value_type;
nstatus = napi_typeof(env, args[5], &value_type);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);
opts.has_enabled_extensions_filter =
value_type != napi_undefined && value_type != napi_null;
nstatus = GetOptionalStringArrayParam(env, args[5], "enabledExtensions",
&opts.enabled_extensions);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);
}

if (argc > 6) {
nstatus = GetOptionalStringArrayParam(env, args[6], "disabledExtensions",
&opts.disabled_extensions);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);
}

WebGLRenderingContext *context =
new (std::nothrow) WebGLRenderingContext(env, opts);
if (context == nullptr) {
Expand Down Expand Up @@ -6172,6 +6230,16 @@ static bool ExtensionNameEqualsIgnoringCase(const std::string &input,
return i == input.size() && extension[i] == '\0';
}

static bool ExtensionListContains(const std::vector<std::string> &extensions,
const char *extension) {
for (const std::string &entry : extensions) {
if (ExtensionNameEqualsIgnoringCase(entry, extension)) {
return true;
}
}
return false;
}

static bool SupportsANGLEInstancedArrays(EGLContextWrapper *egl_ctx) {
return (egl_ctx->glDrawArraysInstanced && egl_ctx->glDrawElementsInstanced &&
egl_ctx->glVertexAttribDivisor) ||
Expand Down Expand Up @@ -6318,6 +6386,28 @@ static const WebGLExtensionDescriptor *CanonicalWebGLExtension(
return nullptr;
}

static bool WebGLExtensionDescriptorsAlias(
const WebGLExtensionDescriptor *lhs, const WebGLExtensionDescriptor *rhs) {
return lhs != nullptr && rhs != nullptr &&
lhs->is_supported == rhs->is_supported &&
lhs->new_instance == rhs->new_instance;
}

static bool ExtensionListContainsExtensionOrAlias(
const std::vector<std::string> &extensions, const char *extension) {
const WebGLExtensionDescriptor *target = CanonicalWebGLExtension(extension);
for (const std::string &entry : extensions) {
if (ExtensionNameEqualsIgnoringCase(entry, extension)) {
return true;
}
const WebGLExtensionDescriptor *listed = CanonicalWebGLExtension(entry);
if (WebGLExtensionDescriptorsAlias(listed, target)) {
return true;
}
}
return false;
}

static void AddUniqueExtension(std::vector<std::string> *extensions,
const char *extension) {
if (extension == nullptr || extension[0] == '\0') {
Expand All @@ -6329,6 +6419,22 @@ static void AddUniqueExtension(std::vector<std::string> *extensions,
}
}

bool WebGLRenderingContext::IsExtensionAllowed(
const char *extension_name) const {
if (extension_name == nullptr || extension_name[0] == '\0') {
return false;
}
if (!enabled_extensions_.empty() &&
!ExtensionListContains(enabled_extensions_, extension_name)) {
return false;
}
if (has_enabled_extensions_filter_ && enabled_extensions_.empty()) {
return false;
}
return !ExtensionListContainsExtensionOrAlias(disabled_extensions_,
extension_name);
}

/* static */
napi_value WebGLRenderingContext::GetExtension(napi_env env,
napi_callback_info info) {
Expand Down Expand Up @@ -6356,7 +6462,8 @@ napi_value WebGLRenderingContext::GetExtension(napi_env env,
EGLContextWrapper *egl_ctx = context->eglContextWrapper_;

napi_value webgl_extension = nullptr;
if (extension != nullptr && extension->is_supported(egl_ctx)) {
if (extension != nullptr && context->IsExtensionAllowed(extension->name) &&
extension->is_supported(egl_ctx)) {
nstatus = extension->new_instance(env, js_this, egl_ctx, &webgl_extension);
} else {
nstatus = napi_get_null(env, &webgl_extension);
Expand Down Expand Up @@ -7842,7 +7949,8 @@ napi_value WebGLRenderingContext::GetSupportedExtensions(

std::vector<std::string> supported_extensions;
for (const WebGLExtensionDescriptor &extension : kKnownWebGLExtensions) {
if (extension.is_supported(context->eglContextWrapper_)) {
if (context->IsExtensionAllowed(extension.name) &&
extension.is_supported(context->eglContextWrapper_)) {
AddUniqueExtension(&supported_extensions, extension.name);
}
}
Expand Down
5 changes: 5 additions & 0 deletions binding/webgl_rendering_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <atomic>
#include <deque>
#include <string>
#include <vector>

#include "egl_context_wrapper.h"

Expand Down Expand Up @@ -58,6 +59,7 @@ class WebGLRenderingContext {
WebGLRenderingContext(napi_env env, GLContextOptions opts);
~WebGLRenderingContext();
void DestroyNativeResources();
bool IsExtensionAllowed(const char* extension_name) const;

static napi_value InitInternal(napi_env env, napi_callback_info info);
static void Cleanup(napi_env env, void* native, void* hint);
Expand Down Expand Up @@ -346,6 +348,9 @@ class WebGLRenderingContext {
PixelStoreState pixel_store_state_;
bool supports_webgl2_pixel_store_;
std::deque<GLenum> pending_errors_;
bool has_enabled_extensions_filter_;
std::vector<std::string> enabled_extensions_;
std::vector<std::string> disabled_extensions_;

std::atomic<size_t> alloc_count_;
};
Expand Down
93 changes: 92 additions & 1 deletion src/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,67 @@ export type NodeGlesWEBGLDrawBuffers = {
drawBuffersWEBGL(buffers: number[] | Uint32Array): void;
};

export type NodeGlesEXTBlendMinmax = {
readonly MAX_EXT: number;
readonly MIN_EXT: number;
};

export type NodeGlesEXTColorBufferFloat = {};

export type NodeGlesEXTColorBufferHalfFloat = {};

export type NodeGlesEXTFragDepth = {};

export type NodeGlesEXTFloatBlend = {};

export type NodeGlesEXTSRGB = {
readonly SRGB_EXT: number;
readonly SRGB_ALPHA_EXT: number;
readonly SRGB8_ALPHA8_EXT: number;
readonly FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT: number;
};

export type NodeGlesEXTShaderTextureLod = {};

export type NodeGlesEXTTextureFilterAnisotropic = {
readonly MAX_TEXTURE_MAX_ANISOTROPY_EXT: number;
readonly TEXTURE_MAX_ANISOTROPY_EXT: number;
};

export type NodeGlesEXTTextureMirrorClampToEdge = {
readonly MIRROR_CLAMP_TO_EDGE_EXT: number;
};

export type NodeGlesOESElementIndexUint = {};

export type NodeGlesOESStandardDerivatives = {
readonly FRAGMENT_SHADER_DERIVATIVE_HINT_OES: number;
};

export type NodeGlesOESTextureFloat = {};

export type NodeGlesOESTextureFloatLinear = {};

export type NodeGlesOESTextureHalfFloat = {
readonly HALF_FLOAT_OES: number;
};

export type NodeGlesOESTextureHalfFloatLinear = {};

export type NodeGlesWEBGLDebugRendererInfo = {
readonly UNMASKED_VENDOR_WEBGL: number;
readonly UNMASKED_RENDERER_WEBGL: number;
};

export type NodeGlesWEBGLDepthTexture = {
readonly UNSIGNED_INT_24_8_WEBGL: number;
};

export type NodeGlesWEBGLLoseContext = {
loseContext(): void;
restoreContext(): void;
};

export type NodeGlesWebGL2RenderingContext = WebGL2RenderingContext & {
drawingBufferColorSpace: "srgb" | "display-p3";
readonly drawingBufferFormat: number;
Expand Down Expand Up @@ -189,14 +244,48 @@ export type NodeGlesWebGL2RenderingContext = WebGL2RenderingContext & {
srcOffsetOrOffset?: number, srcLengthOverride?: number): void;
getExtension(extensionName: "ANGLE_instanced_arrays"):
NodeGlesANGLEInstancedArrays | null;
getExtension(extensionName: "EXT_blend_minmax"):
NodeGlesEXTBlendMinmax | null;
getExtension(extensionName: "EXT_color_buffer_float"):
NodeGlesEXTColorBufferFloat | null;
getExtension(extensionName: "WEBGL_color_buffer_float"):
NodeGlesEXTColorBufferFloat | null;
getExtension(extensionName: "EXT_color_buffer_half_float"):
NodeGlesEXTColorBufferHalfFloat | null;
getExtension(extensionName: "EXT_frag_depth"):
NodeGlesEXTFragDepth | null;
getExtension(extensionName: "EXT_float_blend"):
NodeGlesEXTFloatBlend | null;
getExtension(extensionName: "EXT_sRGB"):
NodeGlesEXTSRGB | null;
getExtension(extensionName: "EXT_shader_texture_lod"):
NodeGlesEXTShaderTextureLod | null;
getExtension(extensionName: "EXT_texture_filter_anisotropic"):
NodeGlesEXTTextureFilterAnisotropic | null;
getExtension(extensionName: "EXT_texture_mirror_clamp_to_edge"):
NodeGlesEXTTextureMirrorClampToEdge | null;
getExtension(extensionName: "OES_element_index_uint"):
NodeGlesOESElementIndexUint | null;
getExtension(extensionName: "OES_standard_derivatives"):
NodeGlesOESStandardDerivatives | null;
getExtension(extensionName: "OES_texture_float"):
NodeGlesOESTextureFloat | null;
getExtension(extensionName: "OES_texture_float_linear"):
NodeGlesOESTextureFloatLinear | null;
getExtension(extensionName: "OES_texture_half_float"):
NodeGlesOESTextureHalfFloat | null;
getExtension(extensionName: "OES_texture_half_float_linear"):
NodeGlesOESTextureHalfFloatLinear | null;
getExtension(extensionName: "OES_vertex_array_object"):
NodeGlesOESVertexArrayObject | null;
getExtension(extensionName: "WEBGL_debug_renderer_info"):
NodeGlesWEBGLDebugRendererInfo | null;
getExtension(extensionName: "WEBGL_depth_texture"):
NodeGlesWEBGLDepthTexture | null;
getExtension(extensionName: "WEBGL_draw_buffers"):
NodeGlesWEBGLDrawBuffers | null;
getExtension(extensionName: "WEBGL_lose_context"):
NodeGlesWEBGLLoseContext | null;
};

export interface NodeJsGlBinding {
Expand All @@ -205,6 +294,8 @@ export interface NodeJsGlBinding {
height: number,
client_major_es_version: number,
client_minor_es_version: number,
webgl_compatbility: boolean
webgl_compatibility: boolean,
enabled_extensions?: string[],
disabled_extensions?: string[]
): WebGLRenderingContext | NodeGlesWebGL2RenderingContext;
}
Loading
Loading