Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
140 changes: 140 additions & 0 deletions src/browser/canvas/WebGLRenderingContext.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <[email protected]>
// Pierre Tachoire <[email protected]>
//
// 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 <https://www.gnu.org/licenses/>.

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,
};
};
};

/// 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;

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), {}),
};
}

/// Returns a list of all the supported WebGL extensions.
pub fn _getSupportedExtensions(_: *const WebGLRenderingContext) []const []const u8 {
return &extension_array;
}
8 changes: 7 additions & 1 deletion src/browser/canvas/root.zig
Original file line number Diff line number Diff line change
@@ -1,6 +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 Extension = WebGLRenderingContext.Extension;

pub const Interfaces = .{
@import("./CanvasRenderingContext2D.zig"),
CanvasRenderingContext2D,
WebGLRenderingContext,
Extension.Type.WEBGL_debug_renderer_info,
};
15 changes: 11 additions & 4 deletions src/browser/html/elements.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
};

Expand Down
67 changes: 65 additions & 2 deletions src/tests/html/canvas.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<script src="../testing.js"></script>

<script id=canvas>
<script id=CanvasRenderingContext2D>
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
Expand All @@ -11,7 +11,7 @@
}
</script>

<script id=canvas#fillStyle>
<script id=CanvasRenderingContext2D#fillStyle>
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
Expand All @@ -25,5 +25,68 @@
// No changes made if color is invalid.
ctx.fillStyle = "invalid-color";
testing.expectEqual(ctx.fillStyle, "#663399");
ctx.fillStyle = "#fc0";
testing.expectEqual(ctx.fillStyle, "#ffcc00");
ctx.fillStyle = "#ff0000";
testing.expectEqual(ctx.fillStyle, "#ff0000");
ctx.fillStyle = "#fF00000F";
testing.expectEqual(ctx.fillStyle, "rgba(255, 0, 0, 0.06)");
}
</script>

<script id=WebGLRenderingContext#getSupportedExtensions>
{
const element = document.createElement("canvas");
const ctx = element.getContext("webgl");
testing.expectEqual(true, ctx instanceof WebGLRenderingContext);

const supportedExtensions = ctx.getSupportedExtensions();
// The order Chrome prefer.
const expectedExtensions = [
"ANGLE_instanced_arrays",
"EXT_blend_minmax",
"EXT_clip_control",
"EXT_color_buffer_half_float",
"EXT_depth_clamp",
"EXT_disjoint_timer_query",
"EXT_float_blend",
"EXT_frag_depth",
"EXT_polygon_offset_clamp",
"EXT_shader_texture_lod",
"EXT_texture_compression_bptc",
"EXT_texture_compression_rgtc",
"EXT_texture_filter_anisotropic",
"EXT_texture_mirror_clamp_to_edge",
"EXT_sRGB",
"KHR_parallel_shader_compile",
"OES_element_index_uint",
"OES_fbo_render_mipmap",
"OES_standard_derivatives",
"OES_texture_float",
"OES_texture_float_linear",
"OES_texture_half_float",
"OES_texture_half_float_linear",
"OES_vertex_array_object",
"WEBGL_blend_func_extended",
"WEBGL_color_buffer_float",
"WEBGL_compressed_texture_astc",
"WEBGL_compressed_texture_etc",
"WEBGL_compressed_texture_etc1",
"WEBGL_compressed_texture_pvrtc",
"WEBGL_compressed_texture_s3tc",
"WEBGL_compressed_texture_s3tc_srgb",
"WEBGL_debug_renderer_info",
"WEBGL_debug_shaders",
"WEBGL_depth_texture",
"WEBGL_draw_buffers",
"WEBGL_lose_context",
"WEBGL_multi_draw",
"WEBGL_polygon_mode"
];

testing.expectEqual(expectedExtensions.length, supportedExtensions.length);
for (let i = 0; i < expectedExtensions.length; i++) {
testing.expectEqual(expectedExtensions[i], supportedExtensions[i]);
}
}
</script>