diff --git a/.gitignore b/.gitignore index eb652c1..73c4fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,13 @@ *.cmxa .merlin +# opam switch +_opam + +# perf tracing +perf.data +perf.data.* + # ocamlbuild working directory _build/ diff --git a/benchmarks/.ocamlformat b/benchmarks/.ocamlformat new file mode 100644 index 0000000..5c1f1b1 --- /dev/null +++ b/benchmarks/.ocamlformat @@ -0,0 +1,2 @@ +profile=janestreet +version = 0.26.0 diff --git a/benchmarks/RobotoMono-Light.ttf b/benchmarks/RobotoMono-Light.ttf new file mode 100644 index 0000000..f03a2b9 Binary files /dev/null and b/benchmarks/RobotoMono-Light.ttf differ diff --git a/benchmarks/bench.ml b/benchmarks/bench.ml new file mode 100644 index 0000000..07da462 --- /dev/null +++ b/benchmarks/bench.ml @@ -0,0 +1,144 @@ +open! Tsdl +open! Tgles2 +open! Wall +module I = Image +module P = Path +module Text = Wall_text + +module type S = sig + type state + + val name : string + val init : Wall.Renderer.t -> state + val frame : state -> width:float -> height:float -> elapsed_seconds:float -> I.t + val clear_color : Color.t +end + +let load_font name = + let ic = open_in_bin name in + let dim = in_channel_length ic in + let fd = Unix.descr_of_in_channel ic in + let buffer = + Unix.map_file fd Bigarray.int8_unsigned Bigarray.c_layout false [| dim |] + |> Bigarray.array1_of_genarray + in + let offset = List.hd (Stb_truetype.enum buffer) in + match Stb_truetype.init buffer offset with + | None -> assert false + | Some font -> font +;; + +let font_sans = lazy (load_font "../example/Roboto-Regular.ttf") + +let run (module T : S) = + let window_width = 1000 in + let window_height = 800 in + Printexc.record_backtrace true; + match Sdl.init Sdl.Init.video with + | Error (`Msg e) -> + Sdl.log "Init error: %s" e; + exit 1 + | Ok () -> + ignore (Sdl.gl_set_attribute Sdl.Gl.depth_size 24 : _ result); + ignore (Sdl.gl_set_attribute Sdl.Gl.stencil_size 8 : _ result); + (match + Sdl.create_window + ~w:window_width + ~h:window_height + "SDL OpenGL" + Sdl.Window.(opengl + allow_highdpi + resizable + windowed) + with + | Error (`Msg e) -> + Sdl.log "Create window error: %s" e; + exit 1 + | Ok w -> + ignore (Sdl.gl_set_swap_interval 0); + let ow, oh = Sdl.gl_get_drawable_size w in + Sdl.log + "window size: %d,%d\topengl drawable size: %d,%d" + window_width + window_height + ow + oh; + let _sw = float ow /. float window_width + and _sh = float oh /. float window_height in + ignore (Sdl.gl_set_attribute Sdl.Gl.stencil_size 1); + (match Sdl.gl_create_context w with + | Error (`Msg e) -> + Sdl.log "Create context error: %s" e; + exit 1 + | Ok ctx -> + let context = Renderer.create ~antialias:true () in + let state = T.init context in + let quit = ref false in + let event = Sdl.Event.create () in + let prev_frame_fps = ref 0.0 in + let freq = Sdl.get_performance_frequency () in + let font_sans = Lazy.force font_sans in + let start_ticks = Sdl.get_ticks () in + while not !quit do + let window_width, window_height = Sdl.get_window_size w in + let ow, oh = Sdl.gl_get_drawable_size w in + let timing_start = Sdl.get_performance_counter () in + while Sdl.poll_event (Some event) do + match Sdl.Event.enum (Sdl.Event.get event Sdl.Event.typ) with + | `Quit -> quit := true + | _ -> () + done; + Gl.viewport 0 0 ow oh; + Gl.clear_color + (Color.r T.clear_color) + (Color.g T.clear_color) + (Color.b T.clear_color) + (Color.a T.clear_color); + Gl.(clear (color_buffer_bit lor depth_buffer_bit lor stencil_buffer_bit)); + Gl.enable Gl.blend; + Gl.blend_func_separate Gl.one Gl.src_alpha Gl.one Gl.one_minus_src_alpha; + Gl.enable Gl.cull_face_enum; + Gl.disable Gl.depth_test; + let elapsed_seconds = + Int32.to_float (Int32.sub (Sdl.get_ticks ()) start_ticks) /. 1000.0 + in + let () = + let width = float window_width in + let height = float window_height in + let image = T.frame state ~width ~height ~elapsed_seconds in + let fps = + let fps = Printf.sprintf " FPS: %d" (Float.to_int !prev_frame_fps) in + I.stack + (I.paint + (Paint.color (Color.v 0.0 0.0 0.0 1.0)) + Text.( + simple_text + (Font.make ~blur:2.0 ~size:30.0 font_sans) + ~valign:`TOP + ~halign:`LEFT + ~x:0.0 + ~y:0.0 + fps)) + (I.paint + (Paint.color (Color.v 1.0 1.0 1.0 1.0)) + Text.( + simple_text + (Font.make ~size:30.0 font_sans) + ~valign:`TOP + ~halign:`LEFT + ~x:0.0 + ~y:0.0 + fps)) + in + Renderer.render context ~width ~height (I.stack image fps) + in + Sdl.gl_swap_window w; + let timing_end = Sdl.get_performance_counter () in + let seconds_elapsed = + Int64.to_float (Int64.sub timing_end timing_start) /. Int64.to_float freq + in + prev_frame_fps := 1.0 /. seconds_elapsed; + () + done; + Sdl.gl_delete_context ctx; + Sdl.destroy_window w; + Sdl.quit (); + exit 0)) +;; diff --git a/benchmarks/dune b/benchmarks/dune new file mode 100644 index 0000000..abe9b96 --- /dev/null +++ b/benchmarks/dune @@ -0,0 +1,4 @@ +(executables + (names main) + (flags :standard -w -3-6-27) + (libraries tsdl tgls.tgles2 wall)) diff --git a/benchmarks/lots_of_text.ml b/benchmarks/lots_of_text.ml new file mode 100644 index 0000000..ba89798 --- /dev/null +++ b/benchmarks/lots_of_text.ml @@ -0,0 +1,79 @@ +open Wall +module I = Image +module Text = Wall_text + +type command = + { color : Color.t + ; text : string + ; leave : bool + ; x : float + ; y : float + } + +type state = + { font : Text.Font.t + ; commands : command list + } + +let init _ctx = + let font = + let tt = Lazy.force Bench.font_sans in + Text.Font.make ~size:30.0 ~placement:`Subpixel tt + in + let commands = ref [] in + let push i = commands := i :: !commands in + for x = 0 to 100 do + let x = Float.of_int x *. 100.0 in + for y = 0 to 100 do + let y = Float.of_int y *. 50.0 in + let c1, c2 = + ( Color.v + (Random.float 1.0) + (Random.float 1.0) + (Random.float 1.0) + (Random.float 1.0) + , Color.v + (Random.float 1.0) + (Random.float 1.0) + (Random.float 1.0) + (Random.float 1.0) ) + in + push { color = c1; text = "hello"; leave = Random.bool (); x; y }; + push { color = c2; text = "world"; leave = Random.bool (); x; y } + done + done; + { font; commands = !commands } +;; + +let paint_text ~x ~y ~color ~font s = + I.paint (Paint.color color) (Text.simple_text font ~valign:`TOP ~halign:`LEFT ~x ~y s) +;; + +let frame { font; commands } ~width ~height ~elapsed_seconds = + let matrix = + let scale = + Transform.scale + ~sx:((Float.sin elapsed_seconds +. 1.25) /. 2.0) + ~sy:((Float.sin elapsed_seconds +. 1.25) /. 2.0) + in + let forward = + Transform.translation + ~x:((-.width /. 2.0) -. 5000.0) + ~y:((-.height /. 2.0) -. 2500.0) + in + let backward = Transform.translation ~x:(width /. 2.0) ~y:(height /. 2.0) in + Transform.compose forward (Transform.compose scale backward) + in + let should_disappear = Float.to_int (elapsed_seconds /. 2.0) mod 2 = 0 in + List.filter_map + (fun { color; text; leave; x; y } -> + if should_disappear && leave + then None + else Some (paint_text ~x ~y ~color ~font text)) + commands + |> I.seq + |> I.transform matrix +;; + +let name = "lots-of-text" +let clear_color = Color.white diff --git a/benchmarks/lots_of_text.mli b/benchmarks/lots_of_text.mli new file mode 100644 index 0000000..2c850be --- /dev/null +++ b/benchmarks/lots_of_text.mli @@ -0,0 +1 @@ +include Bench.S diff --git a/benchmarks/main.ml b/benchmarks/main.ml new file mode 100644 index 0000000..6f81f10 --- /dev/null +++ b/benchmarks/main.ml @@ -0,0 +1,26 @@ +let benches : (module Bench.S) list = + [ (module Lots_of_text); (module Many_graphs); (module Source_code) ] +;; + +let print_usage argv0 = + print_endline "Usage:"; + benches + |> List.iter (fun (module M : Bench.S) -> + print_endline (Printf.sprintf " %s %s" argv0 M.name)) +;; + +let () = + match Array.get Sys.argv 1 with + | arg -> + let bench_to_run = + benches |> List.find_opt (fun (module M : Bench.S) -> String.equal M.name arg) + in + (match bench_to_run with + | None -> + print_usage (Array.get Sys.argv 0); + exit (-1) + | Some bench -> Bench.run bench) + | exception _ -> + print_usage (Array.get Sys.argv 0); + exit (-1) +;; diff --git a/benchmarks/many_graphs.ml b/benchmarks/many_graphs.ml new file mode 100644 index 0000000..e1186c7 --- /dev/null +++ b/benchmarks/many_graphs.ml @@ -0,0 +1,142 @@ +open Wall +module I = Image +module P = Path + +type state = unit + +let init _ctx = () + +let draw_graph x y w h t = + let samples = + [| (1.0 +. sin ((t *. 1.2345) +. (cos (t *. 0.33457) *. 0.44))) *. 0.5 + ; (1.0 +. sin ((t *. 0.68363) +. (cos (t *. 1.3) *. 1.55))) *. 0.5 + ; (1.0 +. sin ((t *. 1.1642) +. (cos (t *. 0.33457) *. 1.24))) *. 0.5 + ; (1.0 +. sin ((t *. 0.56345) +. (cos (t *. 1.63) *. 0.14))) *. 0.5 + ; (1.0 +. sin ((t *. 1.6245) +. (cos (t *. 0.254) *. 0.3))) *. 0.5 + ; (1.0 +. sin ((t *. 0.345) +. (cos (t *. 0.03) *. 0.6))) *. 0.5 + |] + in + let dx = w /. 5.0 in + let sx i = x +. (float i *. dx) in + let sy i = y +. (h *. samples.(i) *. 0.8) in + I.seq + [ (* Graph background *) + I.paint + (Paint.linear_gradient + ~sx:x + ~sy:y + ~ex:x + ~ey:(y +. h) + ~inner:(Color.v 0.00 0.60 0.75 0.00) + ~outer:(Color.v 0.00 0.60 0.75 0.25)) + (I.fill_path + @@ fun t -> + P.move_to t ~x:(sx 0) ~y:(sy 0); + for i = 1 to 5 do + P.bezier_to + t + ~c1x:(sx (i - 1) +. (dx *. 0.5)) + ~c1y:(sy (i - 1)) + ~c2x:(sx i -. (dx *. 0.5)) + ~c2y:(sy i) + ~x:(sx i) + ~y:(sy i) + done; + P.line_to t ~x:(x +. w) ~y:(y +. h); + P.line_to t ~x ~y:(y +. h)) + ; (* Graph line *) + I.paint + (Paint.color (Color.v 0.0 0.0 0.0 0.125)) + (I.stroke_path Outline.{ default with stroke_width = 3.0 } + @@ fun t -> + P.move_to t ~x:(sx 0) ~y:(sy 0 +. 2.0); + for i = 1 to 5 do + P.bezier_to + t + ~c1x:(sx (i - 1) +. (dx *. 0.5)) + ~c1y:(sy (i - 1) +. 2.0) + ~c2x:(sx i -. (dx *. 0.5)) + ~c2y:(sy i +. 2.0) + ~x:(sx i) + ~y:(sy i +. 2.0) + done) + ; I.paint + (Paint.color (Color.v 0.0 0.60 0.75 1.0)) + (I.stroke_path Outline.{ default with stroke_width = 3.0 } + @@ fun t -> + P.move_to t ~x:(sx 0) ~y:(sy 0); + for i = 1 to 5 do + P.bezier_to + t + ~c1x:(sx (i - 1) +. (dx *. 0.5)) + ~c1y:(sy (i - 1)) + ~c2x:(sx i -. (dx *. 0.5)) + ~c2y:(sy i) + ~x:(sx i) + ~y:(sy i) + done) + ; (* Graph sample pos *) + (let node = ref I.empty in + for i = 0 to 5 do + node + := I.stack + !node + (I.paint + (Paint.radial_gradient + ~cx:(sx i) + ~cy:(sy i +. 2.0) + ~inr:3.0 + ~outr:8.0 + ~inner:(Color.v 0.0 0.0 0.0 0.125) + ~outer:(Color.v 0.0 0.0 0.0 0.0)) + (I.fill_path + @@ fun t -> + P.rect t ~x:(sx i -. 10.0) ~y:(sy i -. 10.0 +. 2.0) ~w:20.0 ~h:20.0)) + done; + !node) + ; I.paint + (Paint.color (Color.v 0.0 0.6 0.75 1.0)) + (I.fill_path + @@ fun t -> + for i = 0 to 5 do + P.circle t ~cx:(sx i) ~cy:(sy i) ~r:4.0 + done) + ; I.paint + (Paint.color (Color.v 0.8 0.8 0.8 1.0)) + (I.fill_path + @@ fun t -> + for i = 0 to 5 do + P.circle t ~cx:(sx i) ~cy:(sy i) ~r:2.0 + done) + ] +;; + +let many_graphs ~width:w ~height:h t = + let node = ref I.empty in + let push n = node := I.stack !node n in + for i = 0 to 500 do + push @@ draw_graph 0.0 0.0 w h (t +. float i) + done; + !node +;; + +let many_graphs_cached = ref None + +let many_graphs ~width ~height time = + match !many_graphs_cached with + | Some (w, h, t, cached) + when Float.equal w width && Float.equal h height && Float.equal t time -> cached + | _ -> + let cached = many_graphs ~width ~height time in + many_graphs_cached := Some (width, height, time, cached); + cached +;; + +let frame () ~width ~height ~elapsed_seconds = + I.stack + (many_graphs ~width ~height 0.0) + (draw_graph 0.0 0.0 width height elapsed_seconds) +;; + +let name = "many-graphs" +let clear_color = Color.black diff --git a/benchmarks/many_graphs.mli b/benchmarks/many_graphs.mli new file mode 100644 index 0000000..2c850be --- /dev/null +++ b/benchmarks/many_graphs.mli @@ -0,0 +1 @@ +include Bench.S diff --git a/benchmarks/source_code.ml b/benchmarks/source_code.ml new file mode 100644 index 0000000..e5f72b1 --- /dev/null +++ b/benchmarks/source_code.ml @@ -0,0 +1,97 @@ +open Wall +module I = Image +module Text = Wall_text + +type command = + { color : Color.t + ; text : string + ; x : float + ; y : float + } + +type t = + { font : Text.Font.t + ; commands : command list + ; text_height : float + ; text_width : float + ; kind : string + } + +type state = t list + +let init ~placement = + let font = + let tt = Bench.load_font "./RobotoMono-Light.ttf" in + Text.Font.make ~size:18.0 ~placement tt + in + let metrics = Text.Font.font_metrics font in + let source_code = + In_channel.open_text "../lib/wall.ml" + |> In_channel.input_all + |> String.split_on_char '\n' + in + let gap = metrics.ascent +. metrics.descent +. metrics.line_gap in + let gap = gap *. 1.5 in + let (text_height, text_width), commands = + List.fold_left_map + (fun (y, width) line -> + let cmd = { color = Color.white; text = line; x = gap *. 2.0; y } in + let measure = Text.Font.text_measure font line in + let y = y +. gap in + let width = Float.max width measure.width in + (y, width), cmd) + (0.0, gap *. 2.0) + source_code + in + let kind = + match placement with + | `Aligned -> "ALIGNED" + | `Subpixel -> "SUBPIXEL" + in + { font; commands; text_height; text_width; kind } +;; + +let init _ctx = [ init ~placement:`Aligned; init ~placement:`Subpixel ] + +let paint_text ?(valign = `TOP) ?(halign = `LEFT) ~x ~y ~color ~font s = + I.paint (Paint.color color) (Text.simple_text font ~valign ~halign ~x ~y s) +;; + +let rotate_around ~x ~y ~a = + Transform.translation ~x ~y + |> Transform.rotate a + |> Transform.translate ~x:(-. x) ~y:(-. y) +;; + +let frame state ~width ~height ~elapsed_seconds = + let _x_offset, regions = + List.fold_left_map + (fun x_offset { font; commands; text_height; text_width; kind } -> + let matrix = + let t = (Float.cos (elapsed_seconds /. 5.0) +. 1.0) /. 2.0 in + let t = if t >= 0.8 then 1.0 else t *. (1.0 /. 0.8) in + Transform.translation ~x:x_offset ~y:(-.(t *. (text_height -. height))) + in + let image = + let code = + List.map + (fun { color; text; x; y } -> paint_text ~x ~y ~color ~font text) + commands + |> I.seq + |> I.transform matrix + in + let label = + paint_text ~x:(x_offset +. 5.0) ~y:(-. 5.0) ~color:Color.white ~valign:`BOTTOM ~font kind + |> I.transform (rotate_around ~x:x_offset ~y:0.0 ~a:(Float.pi /. 2.0)) + in + I.stack code label + in + x_offset +. text_width, image) + 0.0 + state + in + I.seq regions +;; + +let name = "source-code" +let clear_color = Color.black diff --git a/benchmarks/source_code.mli b/benchmarks/source_code.mli new file mode 100644 index 0000000..2c850be --- /dev/null +++ b/benchmarks/source_code.mli @@ -0,0 +1 @@ +include Bench.S diff --git a/example/example.ml b/example/example.ml index 88b12c7..a3ee238 100644 --- a/example/example.ml +++ b/example/example.ml @@ -782,23 +782,9 @@ let draw_thumbnails ~x ~y ~w ~h images t = ~w:(8.-.2.) ~h:(scrollh-.2.) ~r:2.) ] -let w = 1280 - -let h = 960 - -let many_graphs = - let t = 0.0 in - let node = ref I.empty in - let push n = node := I.stack !node n in - for i = 0 to 500 do - push @@ draw_graph 0.0 (float h /. 2.0) (float w) (float h /. 2.0) (t +. (float i)); - done; - !node - let draw_demo mx my w h t images = ( let node = ref I.empty in let push n = node := I.stack !node n in - push @@ many_graphs; push @@ draw_eyes (w -. 250.0) 50.0 150.0 100.0 mx my t; push @@ draw_graph 0.0 (h /. 2.0) w (h /. 2.0) t; push @@ draw_colorwheel (w -. 300.0) (h -. 300.0) 250.0 250.0 t;