From 7404b202284702c0669c4a482cfe13688e42097e Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 12:56:18 +0300 Subject: [PATCH 1/7] initial effort for `WebGLRenderingContext` --- src/browser/canvas/WebGLRenderingContext.zig | 125 +++++++++++++++++++ src/browser/canvas/root.zig | 7 +- src/browser/html/elements.zig | 15 ++- 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/browser/canvas/WebGLRenderingContext.zig diff --git a/src/browser/canvas/WebGLRenderingContext.zig b/src/browser/canvas/WebGLRenderingContext.zig new file mode 100644 index 000000000..cd745927c --- /dev/null +++ b/src/browser/canvas/WebGLRenderingContext.zig @@ -0,0 +1,125 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +const WebGLRenderingContext = @This(); +_: u8 = 0, + +/// On Chrome and Safari, a call to `getSupportedExtensions` returns total of 39. +/// The reference for it lists lesser number of extensions: +/// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions#extension_list +pub const Extension = union(enum) { + ANGLE_instanced_arrays: void, + EXT_blend_minmax: void, + EXT_clip_control: void, + EXT_color_buffer_half_float: void, + EXT_depth_clamp: void, + EXT_disjoint_timer_query: void, + EXT_float_blend: void, + EXT_frag_depth: void, + EXT_polygon_offset_clamp: void, + EXT_shader_texture_lod: void, + EXT_texture_compression_bptc: void, + EXT_texture_compression_rgtc: void, + EXT_texture_filter_anisotropic: void, + EXT_texture_mirror_clamp_to_edge: void, + EXT_sRGB: void, + KHR_parallel_shader_compile: void, + OES_element_index_uint: void, + OES_fbo_render_mipmap: void, + OES_standard_derivatives: void, + OES_texture_float: void, + OES_texture_float_linear: void, + OES_texture_half_float: void, + OES_texture_half_float_linear: void, + OES_vertex_array_object: void, + WEBGL_blend_func_extended: void, + WEBGL_color_buffer_float: void, + WEBGL_compressed_texture_astc: void, + WEBGL_compressed_texture_etc: void, + WEBGL_compressed_texture_etc1: void, + WEBGL_compressed_texture_pvrtc: void, + WEBGL_compressed_texture_s3tc: void, + WEBGL_compressed_texture_s3tc_srgb: void, + WEBGL_debug_renderer_info: Type.WEBGL_debug_renderer_info, + WEBGL_debug_shaders: void, + WEBGL_depth_texture: void, + WEBGL_draw_buffers: void, + WEBGL_lose_context: void, + WEBGL_multi_draw: void, + WEBGL_polygon_mode: void, + + /// Reified enum type from the fields of this union. + const Kind = blk: { + const info = @typeInfo(Extension).@"union"; + const fields = info.fields; + var items: [fields.len]std.builtin.Type.EnumField = undefined; + for (fields, 0..) |field, i| { + items[i] = .{ .name = field.name, .value = i }; + } + + break :blk @Type(.{ + .@"enum" = .{ + .tag_type = std.math.IntFittingRange(0, if (fields.len == 0) 0 else fields.len - 1), + .fields = &items, + .decls = &.{}, + .is_exhaustive = true, + }, + }); + }; + + /// Returns the `Extension.Kind` by its name. + fn find(name: []const u8) ?Kind { + // Just to make you really sad, this function has to be case-insensitive. + // So here we copy what's being done in `std.meta.stringToEnum` but replace + // the comparison function. + const kvs = comptime build_kvs: { + const T = Extension.Kind; + const EnumKV = struct { []const u8, T }; + var kvs_array: [@typeInfo(T).@"enum".fields.len]EnumKV = undefined; + for (@typeInfo(T).@"enum".fields, 0..) |enumField, i| { + kvs_array[i] = .{ enumField.name, @field(T, enumField.name) }; + } + break :build_kvs kvs_array[0..]; + }; + const Map = std.StaticStringMapWithEql(Extension.Kind, std.static_string_map.eqlAsciiIgnoreCase); + const map = Map.initComptime(kvs); + return map.get(name); + } + + /// Extension types. + pub const Type = struct { + pub const WEBGL_debug_renderer_info = packed struct(u64) { + UNMASKED_RENDERER_WEBGL: u32 = 0, + UNMASKED_VENDOR_WEBGL: u32 = 0, + }; + }; +}; + +/// Enables a WebGL extension. +pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Extension { + _ = self; + + const tag = Extension.find(name) orelse return null; + + return switch (tag) { + .WEBGL_debug_renderer_info => @unionInit(Extension, "WEBGL_debug_renderer_info", .{}), + inline else => |comptime_enum| @unionInit(Extension, @tagName(comptime_enum), {}), + }; +} diff --git a/src/browser/canvas/root.zig b/src/browser/canvas/root.zig index ad4157457..5bd977e5e 100644 --- a/src/browser/canvas/root.zig +++ b/src/browser/canvas/root.zig @@ -1,6 +1,11 @@ //! Canvas API. //! https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API +const WebGLRenderingContext = @import("WebGLRenderingContext.zig"); +const ExtensionType = WebGLRenderingContext.Extension.Type; + pub const Interfaces = .{ - @import("./CanvasRenderingContext2D.zig"), + @import("CanvasRenderingContext2D.zig"), + WebGLRenderingContext, + ExtensionType.WEBGL_debug_renderer_info, }; diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index 037356e79..1180d1228 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -33,6 +33,7 @@ const DataSet = @import("DataSet.zig"); const StyleSheet = @import("../cssom/StyleSheet.zig"); const CSSStyleDeclaration = @import("../cssom/CSSStyleDeclaration.zig"); const CanvasRenderingContext2D = @import("../canvas/CanvasRenderingContext2D.zig"); +const WebGLRenderingContext = @import("../canvas/WebGLRenderingContext.zig"); const WalkerChildren = @import("../dom/walker.zig").WalkerChildren; @@ -497,15 +498,21 @@ pub const HTMLCanvasElement = struct { color_space: []const u8 = "srgb", }; + /// Returns a drawing context on the canvas, or null if the context identifier + /// is not supported, or the canvas has already been set to a different context mode. pub fn _getContext( ctx_type: []const u8, _: ?ContextAttributes, - ) !CanvasRenderingContext2D { - if (!std.mem.eql(u8, ctx_type, "2d")) { - return error.NotSupported; + ) ?union(enum) { @"2d": CanvasRenderingContext2D, webgl: WebGLRenderingContext } { + if (std.mem.eql(u8, ctx_type, "2d")) { + return .{ .@"2d" = .{} }; } - return .{}; + if (std.mem.eql(u8, ctx_type, "webgl") or std.mem.eql(u8, ctx_type, "experimental-webgl")) { + return .{ .webgl = .{} }; + } + + return null; } }; From a8298a0fda22d3f927bdd9a8473ab46330ca9211 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 14:35:53 +0300 Subject: [PATCH 2/7] support `getSupportedExtensions` --- src/browser/canvas/WebGLRenderingContext.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/browser/canvas/WebGLRenderingContext.zig b/src/browser/canvas/WebGLRenderingContext.zig index cd745927c..8ddfb2032 100644 --- a/src/browser/canvas/WebGLRenderingContext.zig +++ b/src/browser/canvas/WebGLRenderingContext.zig @@ -112,6 +112,16 @@ pub const Extension = union(enum) { }; }; +/// An array of supported WebGL extensions. +const extension_array = blk: { + const fields = @typeInfo(Extension.Kind).@"enum".fields; + var items: [fields.len][:0]const u8 = undefined; + for (fields, 0..) |field, i| { + items[i] = field.name; + } + break :blk items; +}; + /// Enables a WebGL extension. pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Extension { _ = self; @@ -123,3 +133,8 @@ pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Exte inline else => |comptime_enum| @unionInit(Extension, @tagName(comptime_enum), {}), }; } + +/// Returns a list of all the supported WebGL extensions. +pub fn _getSupportedExtensions(_: *const WebGLRenderingContext) []const []const u8 { + return &extension_array; +} From 37ac46569584538314ebddc01f7478f31d49adfa Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 14:36:07 +0300 Subject: [PATCH 3/7] add `WebGLRenderingContext` test --- src/tests/html/canvas.html | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/tests/html/canvas.html b/src/tests/html/canvas.html index ab076487c..a0844b1d0 100644 --- a/src/tests/html/canvas.html +++ b/src/tests/html/canvas.html @@ -27,3 +27,60 @@ testing.expectEqual(ctx.fillStyle, "#663399"); } + + From c459325a5f91c51445754c3ee995d584207d8978 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 14:55:12 +0300 Subject: [PATCH 4/7] update `CanvasRenderingContext2D` test Adds the missing RGBA and long digit hex format tests. --- src/browser/canvas/root.zig | 7 ++++--- src/tests/html/canvas.html | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/browser/canvas/root.zig b/src/browser/canvas/root.zig index 5bd977e5e..1d3f62b00 100644 --- a/src/browser/canvas/root.zig +++ b/src/browser/canvas/root.zig @@ -1,11 +1,12 @@ //! Canvas API. //! https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API +const CanvasRenderingContext2D = @import("CanvasRenderingContext2D.zig"); const WebGLRenderingContext = @import("WebGLRenderingContext.zig"); -const ExtensionType = WebGLRenderingContext.Extension.Type; +const Extension = WebGLRenderingContext.Extension; pub const Interfaces = .{ - @import("CanvasRenderingContext2D.zig"), + CanvasRenderingContext2D, WebGLRenderingContext, - ExtensionType.WEBGL_debug_renderer_info, + Extension.Type.WEBGL_debug_renderer_info, }; diff --git a/src/tests/html/canvas.html b/src/tests/html/canvas.html index a0844b1d0..dc9002548 100644 --- a/src/tests/html/canvas.html +++ b/src/tests/html/canvas.html @@ -1,7 +1,7 @@ - - From 73574dce521862ffc40f8463fc46b33e3d8a980c Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 15:38:48 +0300 Subject: [PATCH 5/7] prefer `std.meta.fieldNames` for creating the array --- src/browser/canvas/WebGLRenderingContext.zig | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/browser/canvas/WebGLRenderingContext.zig b/src/browser/canvas/WebGLRenderingContext.zig index 8ddfb2032..4ededf493 100644 --- a/src/browser/canvas/WebGLRenderingContext.zig +++ b/src/browser/canvas/WebGLRenderingContext.zig @@ -112,16 +112,6 @@ pub const Extension = union(enum) { }; }; -/// An array of supported WebGL extensions. -const extension_array = blk: { - const fields = @typeInfo(Extension.Kind).@"enum".fields; - var items: [fields.len][:0]const u8 = undefined; - for (fields, 0..) |field, i| { - items[i] = field.name; - } - break :blk items; -}; - /// Enables a WebGL extension. pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Extension { _ = self; @@ -136,5 +126,5 @@ pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Exte /// Returns a list of all the supported WebGL extensions. pub fn _getSupportedExtensions(_: *const WebGLRenderingContext) []const []const u8 { - return &extension_array; + return std.meta.fieldNames(Extension.Kind); } From 3c98e4f71ea6e048e7dd79f5fdc8d708e5e91d5f Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 13 Nov 2025 15:40:59 +0300 Subject: [PATCH 6/7] add `WEBGL_debug_renderer_info` --- src/browser/canvas/WebGLRenderingContext.zig | 14 +++++++++++--- src/tests/html/canvas.html | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/browser/canvas/WebGLRenderingContext.zig b/src/browser/canvas/WebGLRenderingContext.zig index 4ededf493..688376c44 100644 --- a/src/browser/canvas/WebGLRenderingContext.zig +++ b/src/browser/canvas/WebGLRenderingContext.zig @@ -105,9 +105,17 @@ pub const Extension = union(enum) { /// Extension types. pub const Type = struct { - pub const WEBGL_debug_renderer_info = packed struct(u64) { - UNMASKED_RENDERER_WEBGL: u32 = 0, - UNMASKED_VENDOR_WEBGL: u32 = 0, + pub const WEBGL_debug_renderer_info = struct { + pub const UNMASKED_VENDOR_WEBGL: u64 = 0x9245; + pub const UNMASKED_RENDERER_WEBGL: u64 = 0x9246; + + pub fn get_UNMASKED_VENDOR_WEBGL() u64 { + return UNMASKED_VENDOR_WEBGL; + } + + pub fn get_UNMASKED_RENDERER_WEBGL() u64 { + return UNMASKED_RENDERER_WEBGL; + } }; }; }; diff --git a/src/tests/html/canvas.html b/src/tests/html/canvas.html index dc9002548..8c82d0e8b 100644 --- a/src/tests/html/canvas.html +++ b/src/tests/html/canvas.html @@ -90,3 +90,16 @@ } } + + From f419f05a5e03b56c3c20410a65ed5d2b5a6f65d0 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 14 Nov 2025 12:18:13 +0300 Subject: [PATCH 7/7] support `WEBGL_lose_context` --- src/browser/canvas/WebGLRenderingContext.zig | 9 ++++++++- src/browser/canvas/root.zig | 1 + src/tests/html/canvas.html | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/browser/canvas/WebGLRenderingContext.zig b/src/browser/canvas/WebGLRenderingContext.zig index 688376c44..47f9df207 100644 --- a/src/browser/canvas/WebGLRenderingContext.zig +++ b/src/browser/canvas/WebGLRenderingContext.zig @@ -61,7 +61,7 @@ pub const Extension = union(enum) { WEBGL_debug_shaders: void, WEBGL_depth_texture: void, WEBGL_draw_buffers: void, - WEBGL_lose_context: void, + WEBGL_lose_context: Type.WEBGL_lose_context, WEBGL_multi_draw: void, WEBGL_polygon_mode: void, @@ -117,6 +117,12 @@ pub const Extension = union(enum) { return UNMASKED_RENDERER_WEBGL; } }; + + pub const WEBGL_lose_context = struct { + _: u8 = 0, + pub fn _loseContext(_: *const WEBGL_lose_context) void {} + pub fn _restoreContext(_: *const WEBGL_lose_context) void {} + }; }; }; @@ -128,6 +134,7 @@ pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Exte return switch (tag) { .WEBGL_debug_renderer_info => @unionInit(Extension, "WEBGL_debug_renderer_info", .{}), + .WEBGL_lose_context => @unionInit(Extension, "WEBGL_lose_context", .{}), inline else => |comptime_enum| @unionInit(Extension, @tagName(comptime_enum), {}), }; } diff --git a/src/browser/canvas/root.zig b/src/browser/canvas/root.zig index 1d3f62b00..fbb51caab 100644 --- a/src/browser/canvas/root.zig +++ b/src/browser/canvas/root.zig @@ -9,4 +9,5 @@ pub const Interfaces = .{ CanvasRenderingContext2D, WebGLRenderingContext, Extension.Type.WEBGL_debug_renderer_info, + Extension.Type.WEBGL_lose_context, }; diff --git a/src/tests/html/canvas.html b/src/tests/html/canvas.html index 8c82d0e8b..529813f4b 100644 --- a/src/tests/html/canvas.html +++ b/src/tests/html/canvas.html @@ -102,4 +102,15 @@ testing.expectEqual(rendererInfo.UNMASKED_VENDOR_WEBGL, 0x9245); testing.expectEqual(rendererInfo.UNMASKED_RENDERER_WEBGL, 0x9246); } + +// WEBGL_lose_context +{ + const element = document.createElement("canvas"); + const ctx = element.getContext("webgl"); + const loseContext = ctx.getExtension("WEBGL_lose_context"); + testing.expectEqual(true, loseContext instanceof WEBGL_lose_context); + + loseContext.loseContext(); + loseContext.restoreContext(); +}