(m)sdf text rendering

This commit is contained in:
tali 2024-01-20 12:46:56 -05:00
parent 7fffdd4798
commit 4c2d4be9fb
8 changed files with 154 additions and 38 deletions

26
assets/shaders/msdf.frag Normal file
View File

@ -0,0 +1,26 @@
#version 150 core
uniform sampler2D Texture;
uniform vec4 Fill;
in vec2 TextureCoord;
out vec4 FragColor;
#define MEDIAN(x, y, z) max(min(x, y), min(max(x, y), z))
void main() {
vec4 msd = texture2D(Texture, TextureCoord);
// float sd = MEDIAN(msd.r, msd.g, msd.b);
// float fw = fwidth(sd);
// float alpha = smoothstep(0.5 - fw, 0.5 + fw, sd);
float sd = msd.a;
float fw = 6 / 16.0;
float alpha = smoothstep(0.5 - fw, 0.5 + fw, sd);
// float alpha = msd.a;
FragColor = Fill * alpha;
}

26
assets/shaders/msdf.vert Normal file
View File

@ -0,0 +1,26 @@
#version 150 core
uniform ivec2 Viewport;
uniform mat3 Transform;
uniform sampler2D Texture;
/* TODO: instanced */
uniform vec2 Offset;
uniform vec4 Atlas;
uniform vec4 Plane;
in vec2 Vert;
out vec2 TextureCoord;
void main() {
vec2 planeVert = mix(Plane.sq, Plane.pt, Vert);
vec3 pos = Transform * vec3(planeVert + Offset, 1.0);
vec2 atlasVert = mix(Atlas.sq, Atlas.pt, Vert);
TextureCoord = atlasVert / textureSize(Texture, 0);
gl_Position.xy = pos.xy * vec2(2.0, -2.0) / Viewport + vec2(-1.0, 1.0);
gl_Position.z = 0.0;
gl_Position.w = 1.0;
}

View File

@ -10,14 +10,19 @@ mkdir -p $out_dir
function gen() { function gen() {
echo "$1..." echo "$1..."
src=$(fc-list "$2" --format='%{file}\n')
[[ -f "$src" ]] ||
(echo "no font matching $2"; exit 1)
dst_png=$out_dir/$1.png dst_png=$out_dir/$1.png
dst_map=$out_dir/$1.map dst_map=$out_dir/$1.map
src=$(find /usr/share/fonts -name $2)
emsize=$3 emsize=$3
[[ "$src" -nt "$dst_png" ]] && [[ "$src" -nt "$dst_png" ]] &&
($msdf \ ($msdf \
-font $src \ -font $src \
-type mtsdf \
-imageout $dst_png \ -imageout $dst_png \
-json >($gen_glyph_map > $dst_map) \ -json >($gen_glyph_map > $dst_map) \
-size $emsize \ -size $emsize \
@ -25,4 +30,7 @@ function gen() {
| exit 1) | exit 1)
} }
gen liberation LiberationMono-Regular.ttf 64 gen liberation.128 "Liberation Mono:style=regular" 128
gen liberation.32 "Liberation Mono:style=regular" 32
gen p052-roman.96 "P052:style=roman" 96
gen p052-roman.32 "P052:style=roman" 32

View File

@ -13,12 +13,22 @@ let main () =
let ren = Renderer.make ~wnd in let ren = Renderer.make ~wnd in
info (fun m -> m "renderer initialized"); info (fun m -> m "renderer initialized");
let font1 = Asset.load_font "p052-roman.96" in
let text1 = "39" in
let text1_fg = rgb24 0xffffff in
let text1_tf = mat2a 50.0 100.0 48.0 48.0 in
let font2 = Asset.load_font "liberation.32" in
let text2 = "Hello, world" in
let text2_fg = rgb24 0xffffff in
let text2_tf = mat2a 50.0 200.0 24.0 24.0 in
let sg = SG.make () in let sg = SG.make () in
SG.register_sprite_map sg "blocks" SG.register_sprite_map sg "blocks"
(Asset.load_sprite_map "blocks" ~dpi:192); (Asset.load_sprite_map "blocks" ~dpi:192);
SG.register_sprite_map sg "hud" SG.register_sprite_map sg "hud"
(Asset.load_sprite_map "hud"); (Asset.load_sprite_map "hud");
debug (fun m -> m "loaded assets"); debug (fun m -> m "loaded sprites");
let tg = TG.make () in let tg = TG.make () in
let scene = Scene.load "main" ~tg ~sg in let scene = Scene.load "main" ~tg ~sg in
@ -27,20 +37,20 @@ let main () =
let render time = let render time =
begin begin
let tf = TG.model (Scene.transform root) in let tf = TG.model (Scene.transform root) in
begin begin
let tx = 512.0 +. Float.sin (time *. 3.0) *. 20.0 in
let ty = 400.0 in
let _ = time in let _ = time in
Mat2A.set tf ~tx ~ty ~sx:1.0 ~sy:1.0 Mat2A.set tf ~tx:512.0 ~ty:400.0 ~sx:1.0 ~sy:1.0
end; end;
Renderer.pre_draw ren; Renderer.pre_draw ren;
Renderer.clear ren (rgb24 0x131321); Renderer.clear ren (rgb24 0x131321);
TG.update tg; TG.update tg;
SG.render sg ~ren; if false then SG.render sg ~ren;
Renderer.draw_text ren font1 text1 ~tf:text1_tf ~fg:text1_fg;
Renderer.draw_text ren font2 text2 ~tf:text2_tf ~fg:text2_fg;
Renderer.post_draw ren; Renderer.post_draw ren;
@ -56,7 +66,7 @@ let main () =
end end
let () = let () =
Ohlog.init () ~min_level:TRACE; Ohlog.init () ~min_level:DEBUG;
try main () with try main () with
| Sdl.Error msg -> error (fun m -> m "SDL error: %s" msg) | Sdl.Error msg -> error (fun m -> m "SDL error: %s" msg)

View File

@ -5,21 +5,30 @@ include (val Ohlog.sublogs logger "Font")
type glyph = { type glyph = {
advance : float; advance : float;
clip : aabb; atlas : aabb;
rect : aabb; plane : aabb;
} }
type t = { type t = {
texture : Texture.t; msdf : Texture.t;
glyphs : (char, glyph) Hashtbl.t; glyphs : (char, glyph) Hashtbl.t;
} }
let empty = aabb 1.0 1.0 0.0 0.0 let empty = aabb 1.0 1.0 0.0 0.0
module Renderer = struct module Renderer = struct
let draw_text ren ~tf ~fg fnt str = let offset = vec2 0.0 0.0
ignore (ren, tf, fg, fnt, str);
failwith "TODO: Renderer.draw_text" let draw_text ren ~tf ~fg { msdf; glyphs } str =
offset.x <- 0.0;
offset.y <- 0.0;
for i = 0 to String.length str - 1 do
(* TODO: line breaks *)
let { advance; atlas; plane } = Hashtbl.find glyphs str.[i] in
if not (AABB.is_empty atlas) then
Renderer.draw_glyph ren msdf ~tf ~fg ~offset ~atlas ~plane;
offset.x <- offset.x +. advance;
done
end end
@ -40,32 +49,33 @@ let glyph_of_sexp = function
| sexp -> | sexp ->
Sexp_conv.of_sexp_error "expected glyph advance" (List sexp) Sexp_conv.of_sexp_error "expected glyph advance" (List sexp)
in in
let clip, rect = match args with let atlas, plane = match args with
| clip :: rect :: _ -> | pl :: at :: _ ->
bounds_of_sexp clip, bounds_of_sexp rect bounds_of_sexp at, bounds_of_sexp pl
| clip :: _ -> | pl :: _ ->
bounds_of_sexp clip, empty bounds_of_sexp pl, empty
| [] -> empty, empty | [] -> empty, empty
in in
chr.[0], { advance; clip; rect } chr.[0], { advance; atlas; plane }
| sexp -> Sexp_conv.of_sexp_error "bad msdf glyph" sexp | sexp -> Sexp_conv.of_sexp_error "bad msdf glyph" sexp
let of_sexp ~texture = function let of_sexp ~msdf = function
| Sexp.List (Atom "msdf" :: glyphs) -> | Sexp.List (Atom "msdf" :: glyphs) ->
let glyphs = let glyphs =
List.to_seq glyphs |> List.to_seq glyphs |>
Seq.map glyph_of_sexp |> Seq.map glyph_of_sexp |>
Hashtbl.of_seq Hashtbl.of_seq
in in
{ texture; glyphs } trace (fun m -> m "%S" (String.of_seq (Hashtbl.to_seq_keys glyphs)));
{ msdf; glyphs }
| sexp -> Sexp_conv.of_sexp_error "invalid msdf" sexp | sexp -> Sexp_conv.of_sexp_error "invalid msdf" sexp
module Asset = struct module Asset = struct
let load_font name = let load_font name =
let tex_path = Format.sprintf "fonts/%s.png" name in let tex_path = Format.sprintf "fonts/%s.png" name in
let map_path = Format.sprintf "fonts/%s.map" name in let map_path = Format.sprintf "fonts/%s.map" name in
let texture = Texture.load_texture tex_path in let msdf = Texture.Asset.load_texture tex_path ~premultiply_alpha:false in
let font = Asset.load_sexp_conv map_path (of_sexp ~texture) in let font = Asset.load_sexp_conv map_path (of_sexp ~msdf) in
debug (fun m -> m "loaded font %S" name); debug (fun m -> m "loaded font %S" name);
font font
end end

View File

@ -131,6 +131,7 @@ let uniform {spo} name =
type 'a set_fn = 'a uniform -> 'a -> unit type 'a set_fn = 'a uniform -> 'a -> unit
let set_int : int set_fn = fun (U l) x -> Gl.uniform1i l x let set_int : int set_fn = fun (U l) x -> Gl.uniform1i l x
let set_vec2 : vec2 set_fn = fun (U l) {x; y} -> Gl.uniform2f l x y
let set_ivec2 : ivec2 set_fn = fun (U l) (x, y) -> Gl.uniform2i l x y let set_ivec2 : ivec2 set_fn = fun (U l) (x, y) -> Gl.uniform2i l x y
let set_color : color set_fn = fun (U l) c -> Gl.uniform4f l c.r c.g c.b c.a let set_color : color set_fn = fun (U l) c -> Gl.uniform4f l c.r c.g c.b c.a
let set_aabb : aabb set_fn = fun (U l) b -> Gl.uniform4f l b.x0 b.y0 b.x1 b.y1 let set_aabb : aabb set_fn = fun (U l) b -> Gl.uniform4f l b.x0 b.y0 b.x1 b.y1
@ -223,11 +224,15 @@ type t = {
gl_ctx : Sdl.gl_context; gl_ctx : Sdl.gl_context;
polygon : shader; polygon : shader;
polygon_rect : geometry; rect_g : geometry;
sprite : shader; sprite : shader;
sprite_rect : geometry; sprite_g : geometry;
(* sprite_instances : vertex_buffer; *) (* sprite_instances : vertex_buffer; *)
msdf : shader;
text_g : geometry;
(* msdf_instances : vertex_buffer; *)
} }
let unit_square = let unit_square =
@ -273,7 +278,7 @@ let make ~(wnd : Sdl.window) : t =
Gl.check_error "setup"; Gl.check_error "setup";
let polygon = load_shader ~name:"polygon" in let polygon = load_shader ~name:"polygon" in
let polygon_rect = let rect_g =
make_geometry [ make_geometry [
make_static_vertex_buffer unit_square_with_norm [ make_static_vertex_buffer unit_square_with_norm [
attr polygon "Vert" `float 2; attr polygon "Vert" `float 2;
@ -285,10 +290,10 @@ let make ~(wnd : Sdl.window) : t =
in in
let sprite = load_shader ~name:"sprite" in let sprite = load_shader ~name:"sprite" in
let sprite_rect = let sprite_g =
make_geometry [ make_geometry [
make_static_vertex_buffer unit_square [ make_static_vertex_buffer unit_square [
attr polygon "Vert" `float 2; attr sprite "Vert" `float 2;
] ]
(* sprite_instances *) (* sprite_instances *)
] ]
@ -296,13 +301,27 @@ let make ~(wnd : Sdl.window) : t =
~count:4 ~count:4
in in
let msdf = load_shader ~name:"msdf" in
let text_g =
make_geometry [
make_static_vertex_buffer unit_square [
attr msdf "Vert" `float 2;
]
(* msdf_instances *)
]
~draw_mode:Gl.triangle_strip
~count:4
in
{ {
window = wnd; window = wnd;
gl_ctx; gl_ctx;
polygon; polygon;
polygon_rect; rect_g;
sprite; sprite;
sprite_rect; sprite_g;
msdf;
text_g;
} }
let destroy t = let destroy t =
@ -314,6 +333,7 @@ let pre_draw t =
Sdl.gl_make_current_exn t.window t.gl_ctx; Sdl.gl_make_current_exn t.window t.gl_ctx;
use t.polygon; set_ivec2 (uniform t.polygon "Viewport") viewport; use t.polygon; set_ivec2 (uniform t.polygon "Viewport") viewport;
use t.sprite; set_ivec2 (uniform t.sprite "Viewport") viewport; use t.sprite; set_ivec2 (uniform t.sprite "Viewport") viewport;
use t.msdf; set_ivec2 (uniform t.msdf "Viewport") viewport;
end end
let post_draw t = let post_draw t =
@ -327,27 +347,41 @@ let clear _t (bg : color) =
Gl.clear Gl.color_buffer_bit; Gl.clear Gl.color_buffer_bit;
end end
(* TODO: store uniforms *)
(* TODO: instanced rendering *)
let draw_rect t ~tf ~fill rect = let draw_rect t ~tf ~fill rect =
let sh = t.polygon in let sh = t.polygon and ge = t.rect_g in
begin begin
(* TODO: cache/store uniform locations in some way *)
use sh; use sh;
set_mat2a (uniform sh "Transform") tf; set_mat2a (uniform sh "Transform") tf;
set_aabb (uniform sh "BoundingBox") rect; set_aabb (uniform sh "BoundingBox") rect;
set_int (uniform sh "Border") 0; set_int (uniform sh "Border") 0;
set_color (uniform sh "Fill") fill; set_color (uniform sh "Fill") fill;
draw_geometry t.polygon_rect; draw_geometry ge;
end end
let draw_texture t ~tf ~rect ~clip ~tint tex = let draw_texture t ~tf ~rect ~clip ~tint tex =
let sh = t.sprite in let sh = t.sprite and ge = t.sprite_g in
begin begin
(* TODO: cache/store uniform locations in some way *)
use sh; use sh;
set_mat2a (uniform sh "Transform") tf; set_mat2a (uniform sh "Transform") tf;
set_tex (uniform sh "Texture") tex; set_tex (uniform sh "Texture") tex;
set_aabb (uniform sh "Rect") rect; set_aabb (uniform sh "Rect") rect;
set_aabb (uniform sh "Clip") clip; set_aabb (uniform sh "Clip") clip;
set_color (uniform sh "Tint") tint; set_color (uniform sh "Tint") tint;
draw_geometry t.sprite_rect; draw_geometry ge;
end
let draw_glyph t ~tf ~offset ~atlas ~plane ~fg msdf =
let sh = t.msdf and ge = t.text_g in
begin
use sh;
set_mat2a (uniform sh "Transform") tf;
set_tex (uniform sh "Texture") msdf;
set_vec2 (uniform sh "Offset") offset;
set_aabb (uniform sh "Atlas") atlas;
set_aabb (uniform sh "Plane") plane;
set_color (uniform sh "Fill") fg;
draw_geometry ge;
end end

View File

@ -4,6 +4,7 @@ module Font = Font
module Renderer = struct module Renderer = struct
include Renderer include Renderer
include Sprite.Renderer include Sprite.Renderer
include Font.Renderer
end end
module Asset = struct module Asset = struct
include Asset include Asset

View File

@ -36,6 +36,7 @@ module Renderer : sig
val clear : t -> color -> unit val clear : t -> color -> unit
val draw_rect : t -> tf:mat2a -> fill:color -> aabb -> unit val draw_rect : t -> tf:mat2a -> fill:color -> aabb -> unit
val draw_sprite : t -> tf:mat2a -> ?tint:color -> ?pos:vec2 -> Sprite.t -> unit val draw_sprite : t -> tf:mat2a -> ?tint:color -> ?pos:vec2 -> Sprite.t -> unit
val draw_text : t -> tf:mat2a -> fg:color -> Font.t -> string -> unit
end end
module Asset : sig module Asset : sig