diff --git a/assets/shaders/msdf.frag b/assets/shaders/msdf.frag new file mode 100644 index 0000000..b480ef4 --- /dev/null +++ b/assets/shaders/msdf.frag @@ -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; +} diff --git a/assets/shaders/msdf.vert b/assets/shaders/msdf.vert new file mode 100644 index 0000000..a102d80 --- /dev/null +++ b/assets/shaders/msdf.vert @@ -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; +} diff --git a/scripts/gen_fonts.sh b/scripts/gen_fonts.sh index 1a4f453..5a6b0dc 100755 --- a/scripts/gen_fonts.sh +++ b/scripts/gen_fonts.sh @@ -10,14 +10,19 @@ mkdir -p $out_dir function gen() { 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_map=$out_dir/$1.map - src=$(find /usr/share/fonts -name $2) emsize=$3 [[ "$src" -nt "$dst_png" ]] && ($msdf \ -font $src \ + -type mtsdf \ -imageout $dst_png \ -json >($gen_glyph_map > $dst_map) \ -size $emsize \ @@ -25,4 +30,7 @@ function gen() { | 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 diff --git a/src/main.ml b/src/main.ml index e9984f5..3fff58c 100644 --- a/src/main.ml +++ b/src/main.ml @@ -13,12 +13,22 @@ let main () = let ren = Renderer.make ~wnd in 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 SG.register_sprite_map sg "blocks" (Asset.load_sprite_map "blocks" ~dpi:192); SG.register_sprite_map sg "hud" (Asset.load_sprite_map "hud"); - debug (fun m -> m "loaded assets"); + debug (fun m -> m "loaded sprites"); let tg = TG.make () in let scene = Scene.load "main" ~tg ~sg in @@ -27,20 +37,20 @@ let main () = let render time = begin - let tf = TG.model (Scene.transform root) in begin - let tx = 512.0 +. Float.sin (time *. 3.0) *. 20.0 in - let ty = 400.0 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; Renderer.pre_draw ren; Renderer.clear ren (rgb24 0x131321); 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; @@ -56,7 +66,7 @@ let main () = end let () = - Ohlog.init () ~min_level:TRACE; + Ohlog.init () ~min_level:DEBUG; try main () with | Sdl.Error msg -> error (fun m -> m "SDL error: %s" msg) diff --git a/src/s2/font.ml b/src/s2/font.ml index b6ead14..4dd15ea 100644 --- a/src/s2/font.ml +++ b/src/s2/font.ml @@ -5,21 +5,30 @@ include (val Ohlog.sublogs logger "Font") type glyph = { advance : float; - clip : aabb; - rect : aabb; + atlas : aabb; + plane : aabb; } type t = { - texture : Texture.t; + msdf : Texture.t; glyphs : (char, glyph) Hashtbl.t; } let empty = aabb 1.0 1.0 0.0 0.0 module Renderer = struct - let draw_text ren ~tf ~fg fnt str = - ignore (ren, tf, fg, fnt, str); - failwith "TODO: Renderer.draw_text" + let offset = vec2 0.0 0.0 + + 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 @@ -40,32 +49,33 @@ let glyph_of_sexp = function | sexp -> Sexp_conv.of_sexp_error "expected glyph advance" (List sexp) in - let clip, rect = match args with - | clip :: rect :: _ -> - bounds_of_sexp clip, bounds_of_sexp rect - | clip :: _ -> - bounds_of_sexp clip, empty + let atlas, plane = match args with + | pl :: at :: _ -> + bounds_of_sexp at, bounds_of_sexp pl + | pl :: _ -> + bounds_of_sexp pl, empty | [] -> empty, empty in - chr.[0], { advance; clip; rect } + chr.[0], { advance; atlas; plane } | 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) -> let glyphs = List.to_seq glyphs |> Seq.map glyph_of_sexp |> Hashtbl.of_seq 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 module Asset = struct let load_font name = let tex_path = Format.sprintf "fonts/%s.png" name in let map_path = Format.sprintf "fonts/%s.map" name in - let texture = Texture.load_texture tex_path in - let font = Asset.load_sexp_conv map_path (of_sexp ~texture) in + let msdf = Texture.Asset.load_texture tex_path ~premultiply_alpha:false in + let font = Asset.load_sexp_conv map_path (of_sexp ~msdf) in debug (fun m -> m "loaded font %S" name); font end diff --git a/src/s2/renderer.ml b/src/s2/renderer.ml index dca514a..13e9e67 100644 --- a/src/s2/renderer.ml +++ b/src/s2/renderer.ml @@ -131,6 +131,7 @@ let uniform {spo} name = type 'a set_fn = 'a uniform -> 'a -> unit 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_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 @@ -223,11 +224,15 @@ type t = { gl_ctx : Sdl.gl_context; polygon : shader; - polygon_rect : geometry; + rect_g : geometry; sprite : shader; - sprite_rect : geometry; + sprite_g : geometry; (* sprite_instances : vertex_buffer; *) + + msdf : shader; + text_g : geometry; + (* msdf_instances : vertex_buffer; *) } let unit_square = @@ -273,7 +278,7 @@ let make ~(wnd : Sdl.window) : t = Gl.check_error "setup"; let polygon = load_shader ~name:"polygon" in - let polygon_rect = + let rect_g = make_geometry [ make_static_vertex_buffer unit_square_with_norm [ attr polygon "Vert" `float 2; @@ -285,10 +290,10 @@ let make ~(wnd : Sdl.window) : t = in let sprite = load_shader ~name:"sprite" in - let sprite_rect = + let sprite_g = make_geometry [ make_static_vertex_buffer unit_square [ - attr polygon "Vert" `float 2; + attr sprite "Vert" `float 2; ] (* sprite_instances *) ] @@ -296,13 +301,27 @@ let make ~(wnd : Sdl.window) : t = ~count:4 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; gl_ctx; polygon; - polygon_rect; + rect_g; sprite; - sprite_rect; + sprite_g; + msdf; + text_g; } let destroy t = @@ -314,6 +333,7 @@ let pre_draw t = Sdl.gl_make_current_exn t.window t.gl_ctx; use t.polygon; set_ivec2 (uniform t.polygon "Viewport") viewport; use t.sprite; set_ivec2 (uniform t.sprite "Viewport") viewport; + use t.msdf; set_ivec2 (uniform t.msdf "Viewport") viewport; end let post_draw t = @@ -327,27 +347,41 @@ let clear _t (bg : color) = Gl.clear Gl.color_buffer_bit; end +(* TODO: store uniforms *) +(* TODO: instanced rendering *) + let draw_rect t ~tf ~fill rect = - let sh = t.polygon in + let sh = t.polygon and ge = t.rect_g in begin - (* TODO: cache/store uniform locations in some way *) use sh; set_mat2a (uniform sh "Transform") tf; set_aabb (uniform sh "BoundingBox") rect; set_int (uniform sh "Border") 0; set_color (uniform sh "Fill") fill; - draw_geometry t.polygon_rect; + draw_geometry ge; end 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 - (* TODO: cache/store uniform locations in some way *) use sh; set_mat2a (uniform sh "Transform") tf; set_tex (uniform sh "Texture") tex; set_aabb (uniform sh "Rect") rect; set_aabb (uniform sh "Clip") clip; 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 diff --git a/src/s2/s2.ml b/src/s2/s2.ml index fb9cea0..f1319b6 100644 --- a/src/s2/s2.ml +++ b/src/s2/s2.ml @@ -4,6 +4,7 @@ module Font = Font module Renderer = struct include Renderer include Sprite.Renderer + include Font.Renderer end module Asset = struct include Asset diff --git a/src/s2/s2.mli b/src/s2/s2.mli index b2563aa..5f84887 100644 --- a/src/s2/s2.mli +++ b/src/s2/s2.mli @@ -36,6 +36,7 @@ module Renderer : sig val clear : t -> color -> 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_text : t -> tf:mat2a -> fg:color -> Font.t -> string -> unit end module Asset : sig