initial implementation of WebGL-based brush renderer

This commit is contained in:
りき萌 2025-09-05 20:20:45 +02:00
parent b4c3260f49
commit bb55e23979
14 changed files with 385 additions and 247 deletions

View file

@ -8,41 +8,21 @@ pub const Canvas = opaque {
if (!status) return error.Draw;
}
pub fn begin(c: *Canvas) !void {
try wrap(__haku2_canvas_begin(c));
}
pub fn line(c: *Canvas, start: value.Vec2, end: value.Vec2) !void {
const x1, const y1 = start;
const x2, const y2 = end;
try wrap(__haku2_canvas_line(c, x1, y1, x2, y2));
}
pub fn rect(c: *Canvas, top_left: value.Vec2, size: value.Vec2) !void {
const x, const y = top_left;
const width, const height = size;
try wrap(__haku2_canvas_rectangle(c, x, y, width, height));
}
pub fn circle(c: *Canvas, center: value.Vec2, r: f32) !void {
const x, const y = center;
try wrap(__haku2_canvas_circle(c, x, y, r));
}
pub fn fill(c: *Canvas, color: value.Rgba8) !void {
pub fn stroke(c: *Canvas, color: value.Rgba8, thickness: f32, from: value.Vec2, to: value.Vec2) !void {
const r, const g, const b, const a = color;
try wrap(__haku2_canvas_fill(c, r, g, b, a));
}
pub fn stroke(c: *Canvas, color: value.Rgba8, thickness: f32) !void {
const r, const g, const b, const a = color;
try wrap(__haku2_canvas_stroke(c, r, g, b, a, thickness));
try wrap(__haku2_canvas_stroke(c, r, g, b, a, thickness, from[0], from[1], to[0], to[1]));
}
};
extern fn __haku2_canvas_begin(c: *Canvas) bool;
extern fn __haku2_canvas_line(c: *Canvas, x1: f32, y1: f32, x2: f32, y2: f32) bool;
extern fn __haku2_canvas_rectangle(c: *Canvas, x: f32, y: f32, width: f32, height: f32) bool;
extern fn __haku2_canvas_circle(c: *Canvas, x: f32, y: f32, r: f32) bool;
extern fn __haku2_canvas_fill(c: *Canvas, r: u8, g: u8, b: u8, a: u8) bool;
extern fn __haku2_canvas_stroke(c: *Canvas, r: u8, g: u8, b: u8, a: u8, thickness: f32) bool;
extern fn __haku2_canvas_stroke(
c: *Canvas,
r: u8,
g: u8,
b: u8,
a: u8,
thickness: f32,
from_x: f32,
from_y: f32,
to_x: f32,
to_y: f32,
) bool;

View file

@ -24,18 +24,13 @@ fn renderRec(vm: *Vm, canvas: *Canvas, val: Value, depth: usize, max_depth: usiz
switch (val.ref.*) {
.scribble => {
try canvas.begin();
switch (val.ref.scribble.shape) {
.point => |point| try canvas.line(point, point),
.line => |line| try canvas.line(line.start, line.end),
.rect => |rect| try canvas.rect(rect.top_left, rect.size),
.circle => |circle| try canvas.circle(circle.center, circle.radius),
}
switch (val.ref.scribble.action) {
.stroke => |stroke| try canvas.stroke(value.rgbaTo8(stroke.color), stroke.thickness),
.fill => |fill| try canvas.fill(value.rgbaTo8(fill.color)),
switch (val.ref.scribble) {
.stroke => |stroke| try canvas.stroke(
value.rgbaTo8(stroke.color),
stroke.thickness,
value.vec2From4(stroke.from),
value.vec2From4(stroke.to),
),
}
},
@ -46,10 +41,6 @@ fn renderRec(vm: *Vm, canvas: *Canvas, val: Value, depth: usize, max_depth: usiz
}
},
.shape => {
return vm.throw("the brush returned a bare shape, which cannot be drawn. try wrapping your shape in a fill or a stroke: (fill #000 <shape>)", .{});
},
else => return notAScribble(vm, val),
}
}

View file

@ -118,14 +118,6 @@ fn fromArgument(cx: Context, comptime T: type, i: usize) Vm.Error!T {
if (val != .ref or val.ref.* != .list) return typeError(cx.vm, val, i, "list");
return val.ref.list;
},
value.Shape => {
const val = cx.args[i];
if (toShape(val)) |shape| {
return shape;
} else {
return typeError(cx.vm, val, i, "shape");
}
},
*const value.Closure => {
const val = cx.args[i];
if (val != .ref or val.ref.* != .closure) return typeError(cx.vm, val, i, "function");
@ -282,12 +274,7 @@ pub const fns = makeFnTable(&[_]SparseFn{
.{ 0x94, erase("filter", filter) },
.{ 0x95, erase("reduce", reduce) },
.{ 0x96, erase("flatten", flatten) },
.{ 0xc0, erase("toShape", valueToShape) },
.{ 0xc1, erase("line", line) },
.{ 0xc2, erase("rect", rect) },
.{ 0xc3, erase("circle", circle) },
.{ 0xe0, erase("stroke", stroke) },
.{ 0xe1, erase("fill", fill) },
.{ 0xf0, erase("withDotter", withDotter) },
});
@ -738,51 +725,14 @@ fn flatten(list: value.List, cx: Context) Vm.Error!value.Ref {
return .{ .list = flattened_list };
}
fn toShape(val: value.Value) ?value.Shape {
return switch (val) {
.nil, .false, .true, .tag, .number, .rgba => null,
.vec4 => |v| .{ .point = value.vec2From4(v) },
.ref => |r| if (r.* == .shape) r.shape else null,
};
}
/// `toShape`
fn valueToShape(val: value.Value) ?value.Ref {
if (toShape(val)) |shape| {
return .{ .shape = shape };
} else {
return null;
}
}
fn line(start: Vec4, end: Vec4) value.Ref {
return .{ .shape = .{ .line = .{
.start = value.vec2From4(start.value),
.end = value.vec2From4(end.value),
} } };
}
fn rect(top_left: Vec4, size: Vec4) value.Ref {
return .{ .shape = .{ .rect = .{
.top_left = value.vec2From4(top_left.value),
.size = value.vec2From4(size.value),
} } };
}
fn circle(center: Vec4, radius: f32) value.Ref {
return .{ .shape = .{ .circle = .{
.center = value.vec2From4(center.value),
.radius = radius,
} } };
}
fn stroke(thickness: f32, color: Rgba, shape: value.Shape) value.Ref {
fn stroke(thickness: f32, color: Rgba, from: Vec4, to: Vec4) value.Ref {
return .{ .scribble = .{
.shape = shape,
.action = .{ .stroke = .{
.stroke = .{
.thickness = thickness,
.color = color.value,
} },
.from = from.value,
.to = to.value,
},
} };
}

View file

@ -62,7 +62,6 @@ pub const Value = union(enum) {
.ref => |r| switch (r.*) {
.closure => "function",
.list => "list",
.shape => "shape",
.scribble => "scribble",
.reticle => "reticle",
},
@ -86,7 +85,7 @@ pub const Value = union(enum) {
}
try writer.writeAll("]");
},
inline .shape, .scribble, .reticle => |x| try writer.print("{}", .{x}),
inline .scribble, .reticle => |x| try writer.print("{}", .{x}),
},
}
}
@ -122,7 +121,6 @@ pub fn rgbaTo8(rgba: Rgba) Rgba8 {
pub const Ref = union(enum) {
closure: Closure,
list: List,
shape: Shape,
scribble: Scribble,
reticle: Reticle,
};
@ -161,44 +159,14 @@ pub const Closure = struct {
pub const List = []Value;
pub const Shape = union(enum) {
point: Vec2,
line: Line,
rect: Rect,
circle: Circle,
pub const Scribble = union(enum) {
stroke: Stroke,
pub const Line = struct {
start: Vec2,
end: Vec2,
};
pub const Rect = struct {
top_left: Vec2,
size: Vec2,
};
pub const Circle = struct {
center: Vec2,
radius: f32,
};
};
pub const Scribble = struct {
shape: Shape,
action: Action,
pub const Action = union(enum) {
stroke: Stroke,
fill: Fill,
pub const Stroke = struct {
thickness: f32,
color: Rgba,
};
pub const Fill = struct {
color: Rgba,
};
pub const Stroke = struct {
thickness: f32,
color: Rgba,
from: Vec4,
to: Vec4,
};
};