Skip to content

Commit d15a384

Browse files
authored
Merge pull request #1209 from lightpanda-io/nikneym/webgl-rendering-context
Dummy `WebGLRenderingContext`
2 parents b782cc6 + f419f05 commit d15a384

File tree

4 files changed

+253
-7
lines changed

4 files changed

+253
-7
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
21+
const WebGLRenderingContext = @This();
22+
_: u8 = 0,
23+
24+
/// On Chrome and Safari, a call to `getSupportedExtensions` returns total of 39.
25+
/// The reference for it lists lesser number of extensions:
26+
/// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions#extension_list
27+
pub const Extension = union(enum) {
28+
ANGLE_instanced_arrays: void,
29+
EXT_blend_minmax: void,
30+
EXT_clip_control: void,
31+
EXT_color_buffer_half_float: void,
32+
EXT_depth_clamp: void,
33+
EXT_disjoint_timer_query: void,
34+
EXT_float_blend: void,
35+
EXT_frag_depth: void,
36+
EXT_polygon_offset_clamp: void,
37+
EXT_shader_texture_lod: void,
38+
EXT_texture_compression_bptc: void,
39+
EXT_texture_compression_rgtc: void,
40+
EXT_texture_filter_anisotropic: void,
41+
EXT_texture_mirror_clamp_to_edge: void,
42+
EXT_sRGB: void,
43+
KHR_parallel_shader_compile: void,
44+
OES_element_index_uint: void,
45+
OES_fbo_render_mipmap: void,
46+
OES_standard_derivatives: void,
47+
OES_texture_float: void,
48+
OES_texture_float_linear: void,
49+
OES_texture_half_float: void,
50+
OES_texture_half_float_linear: void,
51+
OES_vertex_array_object: void,
52+
WEBGL_blend_func_extended: void,
53+
WEBGL_color_buffer_float: void,
54+
WEBGL_compressed_texture_astc: void,
55+
WEBGL_compressed_texture_etc: void,
56+
WEBGL_compressed_texture_etc1: void,
57+
WEBGL_compressed_texture_pvrtc: void,
58+
WEBGL_compressed_texture_s3tc: void,
59+
WEBGL_compressed_texture_s3tc_srgb: void,
60+
WEBGL_debug_renderer_info: Type.WEBGL_debug_renderer_info,
61+
WEBGL_debug_shaders: void,
62+
WEBGL_depth_texture: void,
63+
WEBGL_draw_buffers: void,
64+
WEBGL_lose_context: Type.WEBGL_lose_context,
65+
WEBGL_multi_draw: void,
66+
WEBGL_polygon_mode: void,
67+
68+
/// Reified enum type from the fields of this union.
69+
const Kind = blk: {
70+
const info = @typeInfo(Extension).@"union";
71+
const fields = info.fields;
72+
var items: [fields.len]std.builtin.Type.EnumField = undefined;
73+
for (fields, 0..) |field, i| {
74+
items[i] = .{ .name = field.name, .value = i };
75+
}
76+
77+
break :blk @Type(.{
78+
.@"enum" = .{
79+
.tag_type = std.math.IntFittingRange(0, if (fields.len == 0) 0 else fields.len - 1),
80+
.fields = &items,
81+
.decls = &.{},
82+
.is_exhaustive = true,
83+
},
84+
});
85+
};
86+
87+
/// Returns the `Extension.Kind` by its name.
88+
fn find(name: []const u8) ?Kind {
89+
// Just to make you really sad, this function has to be case-insensitive.
90+
// So here we copy what's being done in `std.meta.stringToEnum` but replace
91+
// the comparison function.
92+
const kvs = comptime build_kvs: {
93+
const T = Extension.Kind;
94+
const EnumKV = struct { []const u8, T };
95+
var kvs_array: [@typeInfo(T).@"enum".fields.len]EnumKV = undefined;
96+
for (@typeInfo(T).@"enum".fields, 0..) |enumField, i| {
97+
kvs_array[i] = .{ enumField.name, @field(T, enumField.name) };
98+
}
99+
break :build_kvs kvs_array[0..];
100+
};
101+
const Map = std.StaticStringMapWithEql(Extension.Kind, std.static_string_map.eqlAsciiIgnoreCase);
102+
const map = Map.initComptime(kvs);
103+
return map.get(name);
104+
}
105+
106+
/// Extension types.
107+
pub const Type = struct {
108+
pub const WEBGL_debug_renderer_info = struct {
109+
pub const UNMASKED_VENDOR_WEBGL: u64 = 0x9245;
110+
pub const UNMASKED_RENDERER_WEBGL: u64 = 0x9246;
111+
112+
pub fn get_UNMASKED_VENDOR_WEBGL() u64 {
113+
return UNMASKED_VENDOR_WEBGL;
114+
}
115+
116+
pub fn get_UNMASKED_RENDERER_WEBGL() u64 {
117+
return UNMASKED_RENDERER_WEBGL;
118+
}
119+
};
120+
121+
pub const WEBGL_lose_context = struct {
122+
_: u8 = 0,
123+
pub fn _loseContext(_: *const WEBGL_lose_context) void {}
124+
pub fn _restoreContext(_: *const WEBGL_lose_context) void {}
125+
};
126+
};
127+
};
128+
129+
/// Enables a WebGL extension.
130+
pub fn _getExtension(self: *const WebGLRenderingContext, name: []const u8) ?Extension {
131+
_ = self;
132+
133+
const tag = Extension.find(name) orelse return null;
134+
135+
return switch (tag) {
136+
.WEBGL_debug_renderer_info => @unionInit(Extension, "WEBGL_debug_renderer_info", .{}),
137+
.WEBGL_lose_context => @unionInit(Extension, "WEBGL_lose_context", .{}),
138+
inline else => |comptime_enum| @unionInit(Extension, @tagName(comptime_enum), {}),
139+
};
140+
}
141+
142+
/// Returns a list of all the supported WebGL extensions.
143+
pub fn _getSupportedExtensions(_: *const WebGLRenderingContext) []const []const u8 {
144+
return std.meta.fieldNames(Extension.Kind);
145+
}

src/browser/canvas/root.zig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
//! Canvas API.
22
//! https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
33

4+
const CanvasRenderingContext2D = @import("CanvasRenderingContext2D.zig");
5+
const WebGLRenderingContext = @import("WebGLRenderingContext.zig");
6+
const Extension = WebGLRenderingContext.Extension;
7+
48
pub const Interfaces = .{
5-
@import("./CanvasRenderingContext2D.zig"),
9+
CanvasRenderingContext2D,
10+
WebGLRenderingContext,
11+
Extension.Type.WEBGL_debug_renderer_info,
12+
Extension.Type.WEBGL_lose_context,
613
};

src/browser/html/elements.zig

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const DataSet = @import("DataSet.zig");
3333
const StyleSheet = @import("../cssom/StyleSheet.zig");
3434
const CSSStyleDeclaration = @import("../cssom/CSSStyleDeclaration.zig");
3535
const CanvasRenderingContext2D = @import("../canvas/CanvasRenderingContext2D.zig");
36+
const WebGLRenderingContext = @import("../canvas/WebGLRenderingContext.zig");
3637

3738
const WalkerChildren = @import("../dom/walker.zig").WalkerChildren;
3839

@@ -497,15 +498,21 @@ pub const HTMLCanvasElement = struct {
497498
color_space: []const u8 = "srgb",
498499
};
499500

501+
/// Returns a drawing context on the canvas, or null if the context identifier
502+
/// is not supported, or the canvas has already been set to a different context mode.
500503
pub fn _getContext(
501504
ctx_type: []const u8,
502505
_: ?ContextAttributes,
503-
) !CanvasRenderingContext2D {
504-
if (!std.mem.eql(u8, ctx_type, "2d")) {
505-
return error.NotSupported;
506+
) ?union(enum) { @"2d": CanvasRenderingContext2D, webgl: WebGLRenderingContext } {
507+
if (std.mem.eql(u8, ctx_type, "2d")) {
508+
return .{ .@"2d" = .{} };
506509
}
507510

508-
return .{};
511+
if (std.mem.eql(u8, ctx_type, "webgl") or std.mem.eql(u8, ctx_type, "experimental-webgl")) {
512+
return .{ .webgl = .{} };
513+
}
514+
515+
return null;
509516
}
510517
};
511518

src/tests/html/canvas.html

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!DOCTYPE html>
22
<script src="../testing.js"></script>
33

4-
<script id=canvas>
4+
<script id=CanvasRenderingContext2D>
55
{
66
const element = document.createElement("canvas");
77
const ctx = element.getContext("2d");
@@ -11,7 +11,7 @@
1111
}
1212
</script>
1313

14-
<script id=canvas#fillStyle>
14+
<script id=CanvasRenderingContext2D#fillStyle>
1515
{
1616
const element = document.createElement("canvas");
1717
const ctx = element.getContext("2d");
@@ -25,5 +25,92 @@
2525
// No changes made if color is invalid.
2626
ctx.fillStyle = "invalid-color";
2727
testing.expectEqual(ctx.fillStyle, "#663399");
28+
ctx.fillStyle = "#fc0";
29+
testing.expectEqual(ctx.fillStyle, "#ffcc00");
30+
ctx.fillStyle = "#ff0000";
31+
testing.expectEqual(ctx.fillStyle, "#ff0000");
32+
ctx.fillStyle = "#fF00000F";
33+
testing.expectEqual(ctx.fillStyle, "rgba(255, 0, 0, 0.06)");
34+
}
35+
</script>
36+
37+
<script id=WebGLRenderingContext#getSupportedExtensions>
38+
{
39+
const element = document.createElement("canvas");
40+
const ctx = element.getContext("webgl");
41+
testing.expectEqual(true, ctx instanceof WebGLRenderingContext);
42+
43+
const supportedExtensions = ctx.getSupportedExtensions();
44+
// The order Chrome prefer.
45+
const expectedExtensions = [
46+
"ANGLE_instanced_arrays",
47+
"EXT_blend_minmax",
48+
"EXT_clip_control",
49+
"EXT_color_buffer_half_float",
50+
"EXT_depth_clamp",
51+
"EXT_disjoint_timer_query",
52+
"EXT_float_blend",
53+
"EXT_frag_depth",
54+
"EXT_polygon_offset_clamp",
55+
"EXT_shader_texture_lod",
56+
"EXT_texture_compression_bptc",
57+
"EXT_texture_compression_rgtc",
58+
"EXT_texture_filter_anisotropic",
59+
"EXT_texture_mirror_clamp_to_edge",
60+
"EXT_sRGB",
61+
"KHR_parallel_shader_compile",
62+
"OES_element_index_uint",
63+
"OES_fbo_render_mipmap",
64+
"OES_standard_derivatives",
65+
"OES_texture_float",
66+
"OES_texture_float_linear",
67+
"OES_texture_half_float",
68+
"OES_texture_half_float_linear",
69+
"OES_vertex_array_object",
70+
"WEBGL_blend_func_extended",
71+
"WEBGL_color_buffer_float",
72+
"WEBGL_compressed_texture_astc",
73+
"WEBGL_compressed_texture_etc",
74+
"WEBGL_compressed_texture_etc1",
75+
"WEBGL_compressed_texture_pvrtc",
76+
"WEBGL_compressed_texture_s3tc",
77+
"WEBGL_compressed_texture_s3tc_srgb",
78+
"WEBGL_debug_renderer_info",
79+
"WEBGL_debug_shaders",
80+
"WEBGL_depth_texture",
81+
"WEBGL_draw_buffers",
82+
"WEBGL_lose_context",
83+
"WEBGL_multi_draw",
84+
"WEBGL_polygon_mode"
85+
];
86+
87+
testing.expectEqual(expectedExtensions.length, supportedExtensions.length);
88+
for (let i = 0; i < expectedExtensions.length; i++) {
89+
testing.expectEqual(expectedExtensions[i], supportedExtensions[i]);
90+
}
91+
}
92+
</script>
93+
94+
<script id=WebGLRenderingCanvas#getExtension>
95+
// WEBGL_debug_renderer_info
96+
{
97+
const element = document.createElement("canvas");
98+
const ctx = element.getContext("webgl");
99+
const rendererInfo = ctx.getExtension("WEBGL_debug_renderer_info");
100+
testing.expectEqual(true, rendererInfo instanceof WEBGL_debug_renderer_info);
101+
102+
testing.expectEqual(rendererInfo.UNMASKED_VENDOR_WEBGL, 0x9245);
103+
testing.expectEqual(rendererInfo.UNMASKED_RENDERER_WEBGL, 0x9246);
104+
}
105+
106+
// WEBGL_lose_context
107+
{
108+
const element = document.createElement("canvas");
109+
const ctx = element.getContext("webgl");
110+
const loseContext = ctx.getExtension("WEBGL_lose_context");
111+
testing.expectEqual(true, loseContext instanceof WEBGL_lose_context);
112+
113+
loseContext.loseContext();
114+
loseContext.restoreContext();
28115
}
29116
</script>

0 commit comments

Comments
 (0)