Switch to jbuild-based build system
Getty Ritter
7 years ago
| 1 | OCAMLC = ocamlfind ocamlc | |
| 2 | REQUIRES = tsdl tgls.tgl4 | |
| 1 | build: | |
| 2 | jbuilder build | |
| 3 | 3 | |
| 4 | main: main.ml | |
| 5 | $(OCAMLC) -package "$(REQUIRES)" -linkpkg $< -o $@ | |
| 4 | run: build | |
| 5 | ./_build/install/default/bin/ocaml-gl-test | |
| 6 | 6 | |
| 7 | 7 | clean: |
| 8 |
rm -rf |
|
| 8 | rm -rf _build *.install |
| 1 | opam-version: "1.2" | |
| 2 | name: "OCamlGLBasic" | |
| 3 | version: "0.0.1" | |
| 4 | maintainer: "Getty Ritter <samothes@infinitenegativeutility.com>" | |
| 5 | authors: [ | |
| 6 | "Getty Ritter <samothes@infinitenegativeutility.com>" | |
| 7 | ] | |
| 8 | license: "BSD" | |
| 9 | homepage: "https://git.gdritter.com/ocaml-gl-basic" | |
| 10 | dev-repo: "https://git.gdritter.com/ocaml-gl-basic" | |
| 11 | tags: [ "graphics" ] | |
| 12 | build: [ | |
| 13 | ["jbuilder" "build" "-p" name "-j" jobs] | |
| 14 | ] | |
| 15 | depends: [ | |
| 16 | "jbuilder" {build} | |
| 17 | "tsdl" | |
| 18 | "tgls" | |
| 19 | ] | |
| 20 | available: [ ocaml-version >= "4.02" & ocaml-version < "4.05" ] |
| 1 | (jbuild_version 1) | |
| 2 | ||
| 3 | (executable | |
| 4 | ((name main) | |
| 5 | ; This will be installable as a global binary | |
| 6 | (public_name ocaml-gl-test) | |
| 7 | ; and it depends on 2 local libraries | |
| 8 | (libraries (tsdl tgls.tgl4)))) |
| 1 | open Tsdl | |
| 2 | open Tgl4 | |
| 3 | open Result | |
| 4 | ||
| 5 | (* the bind operator for the exception monad, for quick error | |
| 6 | handling *) | |
| 7 | let (>>=) x f = match x with | |
| 8 | | Ok v -> f v | |
| 9 | | Error _ as e -> e | |
| 10 | ||
| 11 | (* our vertex shader takes a 2d position and renders it on the screen, | |
| 12 | and that's it *) | |
| 13 | let vertex_shader = | |
| 14 | "#version 130 | |
| 15 | in vec2 position; | |
| 16 | void main() { | |
| 17 | gl_Position = vec4(position, 0.0, 1.0); | |
| 18 | } | |
| 19 | " | |
| 20 | ||
| 21 | (* our fragment shader just draws white *) | |
| 22 | let fragment_shader = | |
| 23 | "#version 130 | |
| 24 | out vec4 out_color; | |
| 25 | void main() { | |
| 26 | out_color = vec4(1.0, 1.0, 1.0, 1.0); | |
| 27 | } | |
| 28 | " | |
| 29 | ||
| 30 | (* we use a lot of Bigarrays, which are contiguous chunks of memory | |
| 31 | that we can hand off to C *) | |
| 32 | let bigarray_create k len = Bigarray.(Array1.create k c_layout len) | |
| 33 | ||
| 34 | (* our vertex data is [0.0, 0.5, 0.5, -0.5, -0.5, -0.5], but in a | |
| 35 | contiguous array of memory *) | |
| 36 | let vertices = | |
| 37 | let arr = bigarray_create Bigarray.float32 6 in | |
| 38 | arr.{0} <- 0.0; | |
| 39 | arr.{1} <- 0.5; | |
| 40 | arr.{2} <- 0.5; | |
| 41 | arr.{3} <- -0.5; | |
| 42 | arr.{4} <- -0.5; | |
| 43 | arr.{5} <- -0.5; | |
| 44 | arr | |
| 45 | ||
| 46 | (* This takes a function which expects to fill a buffer with a single | |
| 47 | 32-bit integer; this will call that function and then conver it to an | |
| 48 | OCaml integer *) | |
| 49 | let buf_to_int = | |
| 50 | let a = bigarray_create Bigarray.int32 1 in | |
| 51 | fun f -> f a; Int32.to_int a.{0} | |
| 52 | ||
| 53 | (* This takes a function that expects to use a buffer containing a | |
| 54 | single 32-bit integer as well as an integer itself; this will | |
| 55 | convert the integer into a buffer and then call the function on | |
| 56 | it *) | |
| 57 | let int_as_buf = | |
| 58 | let a = bigarray_create Bigarray.int32 1 in | |
| 59 | fun f i -> a.{0} <- Int32.of_int i; f a | |
| 60 | ||
| 61 | (* Like buf_to_int, but for an arbitrary-sized string *) | |
| 62 | let get_string len f = | |
| 63 | let a = bigarray_create Bigarray.char len in | |
| 64 | f a; Gl.string_of_bigarray a | |
| 65 | ||
| 66 | (* The first thing to do is to get a new window from SDL and then | |
| 67 | initialize an OpenGL context in that window. *) | |
| 68 | let window () = | |
| 69 | Sdl.init Sdl.Init.video >>= fun () -> | |
| 70 | Sdl.create_window ~w:640 ~h:480 "test" Sdl.Window.opengl >>= fun w -> | |
| 71 | Sdl.gl_create_context w >>= fun ctx -> | |
| 72 | Sdl.gl_make_current w ctx >>= fun () -> | |
| 73 | Ok (w, ctx) | |
| 74 | ||
| 75 | (* This compiles a shader, producing an informative error message if | |
| 76 | the compilation process fails. *) | |
| 77 | let compile_shader typ src = | |
| 78 | let shader = Gl.create_shader typ in | |
| 79 | Gl.shader_source shader src; | |
| 80 | Gl.compile_shader shader; | |
| 81 | if buf_to_int (Gl.get_shaderiv shader Gl.compile_status) = Gl.true_ | |
| 82 | then Ok shader | |
| 83 | else | |
| 84 | let len = buf_to_int (Gl.get_shaderiv shader Gl.info_log_length) in | |
| 85 | let log = get_string len (Gl.get_shader_info_log shader len None) in | |
| 86 | (Gl.delete_shader shader; Error (`Msg log)) | |
| 87 | ||
| 88 | (* This links our two shaders together into a single OpenGL program, | |
| 89 | again producing an informative error message if it fails. *) | |
| 90 | let create_program () = | |
| 91 | compile_shader Gl.vertex_shader vertex_shader >>= fun vertex -> | |
| 92 | compile_shader Gl.fragment_shader fragment_shader >>= fun fragment -> | |
| 93 | let program = Gl.create_program () in | |
| 94 | Gl.attach_shader program vertex; | |
| 95 | Gl.attach_shader program fragment; | |
| 96 | Gl.link_program program; | |
| 97 | if buf_to_int (Gl.get_programiv program Gl.link_status) = Gl.true_ | |
| 98 | then Ok program | |
| 99 | else | |
| 100 | let len = buf_to_int (Gl.get_programiv program Gl.info_log_length) in | |
| 101 | let log = get_string len (Gl.get_program_info_log program len None) in | |
| 102 | (Gl.delete_program program; Error (`Msg log)) | |
| 103 | ||
| 104 | (* This initializes a vertex array and a vertex buffer using our | |
| 105 | vertex information above. *) | |
| 106 | let init_scene program = | |
| 107 | let array = buf_to_int (Gl.gen_vertex_arrays 1) in | |
| 108 | Gl.bind_vertex_array array; | |
| 109 | ||
| 110 | let buffer = buf_to_int (Gl.gen_buffers 1) in | |
| 111 | Gl.bind_buffer Gl.array_buffer buffer; | |
| 112 | Gl.buffer_data | |
| 113 | Gl.array_buffer | |
| 114 | (Gl.bigarray_byte_size vertices) | |
| 115 | (Some vertices) | |
| 116 | Gl.static_draw; | |
| 117 | ||
| 118 | (* This bit is first telling OpenGL where our fragment data can be | |
| 119 | found *) | |
| 120 | Gl.bind_frag_data_location program 0 "out_color"; | |
| 121 | (* and then asking it the location of the position attribute *) | |
| 122 | let pos_attr = Gl.get_attrib_location program "position" in | |
| 123 | (* and finally telling OpenGL that the stuff in our vertex buffer | |
| 124 | corresponds to the aforementioned "position" attribute *) | |
| 125 | Gl.enable_vertex_attrib_array pos_attr; | |
| 126 | (* and tells OpenGL that it consists of two floats with no stride | |
| 127 | and no offset. *) | |
| 128 | Gl.vertex_attrib_pointer pos_attr 2 Gl.float false 0 (`Offset 0); | |
| 129 | ||
| 130 | (* We return these for later use! *) | |
| 131 | (array, buffer) | |
| 132 | ||
| 133 | let draw program buffer w = | |
| 134 | (* we clear the background color to a pleasing robin's-egg blue *) | |
| 135 | Gl.clear_color 0.3 0.6 0.9 1.; | |
| 136 | Gl.clear Gl.color_buffer_bit; | |
| 137 | ||
| 138 | (* we tell OpenGL we want to use that program we made earlier, and | |
| 139 | the array buffer we created earlier, too *) | |
| 140 | Gl.use_program program; | |
| 141 | Gl.bind_buffer Gl.array_buffer buffer; | |
| 142 | (* and tell it to draw the first 3 things in that array buffer as | |
| 143 | triangles *) | |
| 144 | Gl.draw_arrays Gl.triangles 0 3; | |
| 145 | (* and then clear that shit out. (We can skip this step, to be fair, | |
| 146 | but it's good to get in the habit for later.) *) | |
| 147 | Gl.bind_buffer Gl.array_buffer 0; | |
| 148 | Gl.use_program 0; | |
| 149 | ||
| 150 | (* and finally swap the window! *) | |
| 151 | Sdl.gl_swap_window w | |
| 152 | ||
| 153 | (* The drawing function here is of type () -> () so we can pass in a | |
| 154 | closure and not have to pass other data down the chain *) | |
| 155 | let event_loop draw w = | |
| 156 | let e = Sdl.Event.create() in | |
| 157 | (* these helper functions are just to reduce the verbosity down | |
| 158 | below *) | |
| 159 | let keycode e = Sdl.Scancode.enum Sdl.Event.(get e keyboard_scancode) in | |
| 160 | let event e = Sdl.Event.(enum (get e typ)) in | |
| 161 | (* the crunchy center is a loop that draws and then dispatches on | |
| 162 | the relevant events *) | |
| 163 | let rec loop () = | |
| 164 | draw (); | |
| 165 | Sdl.wait_event (Some e) >>= fun () -> | |
| 166 | match event e with | |
| 167 | | `Quit -> Ok () | |
| 168 | | `Key_down when keycode e = `Escape -> Ok () | |
| 169 | | `Key_down when keycode e = `Q -> Ok () | |
| 170 | | _ -> loop () | |
| 171 | in loop () | |
| 172 | ||
| 173 | (* and the main function! *) | |
| 174 | let main () = | |
| 175 | (* create the window *) | |
| 176 | window () >>= fun (w, ctx) -> | |
| 177 | (* create the program *) | |
| 178 | create_program () >>= fun program -> | |
| 179 | (* create the vertex data *) | |
| 180 | let (array, buffer) = init_scene program in | |
| 181 | (* run the event loop with the relevant drawing function *) | |
| 182 | event_loop (fun () -> draw program array w) w >>= fun () -> | |
| 183 | (* and clean it all up! *) | |
| 184 | int_as_buf (Gl.delete_vertex_arrays 1) array; | |
| 185 | int_as_buf (Gl.delete_buffers 1) buffer; | |
| 186 | Gl.delete_program program; | |
| 187 | Sdl.gl_delete_context ctx; | |
| 188 | Sdl.destroy_window w; | |
| 189 | Sdl.quit (); | |
| 190 | (* s'all good yo *) | |
| 191 | Ok () | |
| 192 | ||
| 193 | (* and, at the top-level, run it and print any error messages we | |
| 194 | get. *) | |
| 195 | let () = match main () with | |
| 196 | | Ok () -> () | |
| 197 | | Error (`Msg e) -> | |
| 198 | Sdl.log "%s" e; | |
| 199 | exit 1 |
| 1 | open Tsdl | |
| 2 | open Tgl4 | |
| 3 | ||
| 4 | (* the bind operator for the exception monad, for quick error | |
| 5 | handling *) | |
| 6 | let (>>=) x f = match x with | |
| 7 | | Ok v -> f v | |
| 8 | | Error _ as e -> e | |
| 9 | ||
| 10 | (* our vertex shader takes a 2d position and renders it on the screen, | |
| 11 | and that's it *) | |
| 12 | let vertex_shader = | |
| 13 | "#version 130 | |
| 14 | in vec2 position; | |
| 15 | void main() { | |
| 16 | gl_Position = vec4(position, 0.0, 1.0); | |
| 17 | } | |
| 18 | " | |
| 19 | ||
| 20 | (* our fragment shader just draws white *) | |
| 21 | let fragment_shader = | |
| 22 | "#version 130 | |
| 23 | out vec4 out_color; | |
| 24 | void main() { | |
| 25 | out_color = vec4(1.0, 1.0, 1.0, 1.0); | |
| 26 | } | |
| 27 | " | |
| 28 | ||
| 29 | (* we use a lot of Bigarrays, which are contiguous chunks of memory | |
| 30 | that we can hand off to C *) | |
| 31 | let bigarray_create k len = Bigarray.(Array1.create k c_layout len) | |
| 32 | ||
| 33 | (* our vertex data is [0.0, 0.5, 0.5, -0.5, -0.5, -0.5], but in a | |
| 34 | contiguous array of memory *) | |
| 35 | let vertices = | |
| 36 | let arr = bigarray_create Bigarray.float32 6 in | |
| 37 | arr.{0} <- 0.0; | |
| 38 | arr.{1} <- 0.5; | |
| 39 | arr.{2} <- 0.5; | |
| 40 | arr.{3} <- -0.5; | |
| 41 | arr.{4} <- -0.5; | |
| 42 | arr.{5} <- -0.5; | |
| 43 | arr | |
| 44 | ||
| 45 | (* This takes a function which expects to fill a buffer with a single | |
| 46 | 32-bit integer; this will call that function and then conver it to an | |
| 47 | OCaml integer *) | |
| 48 | let buf_to_int = | |
| 49 | let a = bigarray_create Bigarray.int32 1 in | |
| 50 | fun f -> f a; Int32.to_int a.{0} | |
| 51 | ||
| 52 | (* This takes a function that expects to use a buffer containing a | |
| 53 | single 32-bit integer as well as an integer itself; this will | |
| 54 | convert the integer into a buffer and then call the function on | |
| 55 | it *) | |
| 56 | let int_as_buf = | |
| 57 | let a = bigarray_create Bigarray.int32 1 in | |
| 58 | fun f i -> a.{0} <- Int32.of_int i; f a | |
| 59 | ||
| 60 | (* Like buf_to_int, but for an arbitrary-sized string *) | |
| 61 | let get_string len f = | |
| 62 | let a = bigarray_create Bigarray.char len in | |
| 63 | f a; Gl.string_of_bigarray a | |
| 64 | ||
| 65 | (* The first thing to do is to get a new window from SDL and then | |
| 66 | initialize an OpenGL context in that window. *) | |
| 67 | let window () = | |
| 68 | Sdl.init Sdl.Init.video >>= fun () -> | |
| 69 | Sdl.create_window ~w:640 ~h:480 "test" Sdl.Window.opengl >>= fun w -> | |
| 70 | Sdl.gl_create_context w >>= fun ctx -> | |
| 71 | Sdl.gl_make_current w ctx >>= fun () -> | |
| 72 | Ok (w, ctx) | |
| 73 | ||
| 74 | (* This compiles a shader, producing an informative error message if | |
| 75 | the compilation process fails. *) | |
| 76 | let compile_shader typ src = | |
| 77 | let shader = Gl.create_shader typ in | |
| 78 | Gl.shader_source shader src; | |
| 79 | Gl.compile_shader shader; | |
| 80 | if buf_to_int (Gl.get_shaderiv shader Gl.compile_status) = Gl.true_ | |
| 81 | then Ok shader | |
| 82 | else | |
| 83 | let len = buf_to_int (Gl.get_shaderiv shader Gl.info_log_length) in | |
| 84 | let log = get_string len (Gl.get_shader_info_log shader len None) in | |
| 85 | (Gl.delete_shader shader; Error (`Msg log)) | |
| 86 | ||
| 87 | (* This links our two shaders together into a single OpenGL program, | |
| 88 | again producing an informative error message if it fails. *) | |
| 89 | let create_program () = | |
| 90 | compile_shader Gl.vertex_shader vertex_shader >>= fun vertex -> | |
| 91 | compile_shader Gl.fragment_shader fragment_shader >>= fun fragment -> | |
| 92 | let program = Gl.create_program () in | |
| 93 | Gl.attach_shader program vertex; | |
| 94 | Gl.attach_shader program fragment; | |
| 95 | Gl.link_program program; | |
| 96 | if buf_to_int (Gl.get_programiv program Gl.link_status) = Gl.true_ | |
| 97 | then Ok program | |
| 98 | else | |
| 99 | let len = buf_to_int (Gl.get_programiv program Gl.info_log_length) in | |
| 100 | let log = get_string len (Gl.get_program_info_log program len None) in | |
| 101 | (Gl.delete_program program; Error (`Msg log)) | |
| 102 | ||
| 103 | (* This initializes a vertex array and a vertex buffer using our | |
| 104 | vertex information above. *) | |
| 105 | let init_scene program = | |
| 106 | let array = buf_to_int (Gl.gen_vertex_arrays 1) in | |
| 107 | Gl.bind_vertex_array array; | |
| 108 | ||
| 109 | let buffer = buf_to_int (Gl.gen_buffers 1) in | |
| 110 | Gl.bind_buffer Gl.array_buffer buffer; | |
| 111 | Gl.buffer_data | |
| 112 | Gl.array_buffer | |
| 113 | (Gl.bigarray_byte_size vertices) | |
| 114 | (Some vertices) | |
| 115 | Gl.static_draw; | |
| 116 | ||
| 117 | (* This bit is first telling OpenGL where our fragment data can be | |
| 118 | found *) | |
| 119 | Gl.bind_frag_data_location program 0 "out_color"; | |
| 120 | (* and then asking it the location of the position attribute *) | |
| 121 | let pos_attr = Gl.get_attrib_location program "position" in | |
| 122 | (* and finally telling OpenGL that the stuff in our vertex buffer | |
| 123 | corresponds to the aforementioned "position" attribute *) | |
| 124 | Gl.enable_vertex_attrib_array pos_attr; | |
| 125 | (* and tells OpenGL that it consists of two floats with no stride | |
| 126 | and no offset. *) | |
| 127 | Gl.vertex_attrib_pointer pos_attr 2 Gl.float false 0 (`Offset 0); | |
| 128 | ||
| 129 | (* We return these for later use! *) | |
| 130 | (array, buffer) | |
| 131 | ||
| 132 | let draw program buffer w = | |
| 133 | (* we clear the background color to a pleasing robin's-egg blue *) | |
| 134 | Gl.clear_color 0.3 0.6 0.9 1.; | |
| 135 | Gl.clear Gl.color_buffer_bit; | |
| 136 | ||
| 137 | (* we tell OpenGL we want to use that program we made earlier, and | |
| 138 | the array buffer we created earlier, too *) | |
| 139 | Gl.use_program program; | |
| 140 | Gl.bind_buffer Gl.array_buffer buffer; | |
| 141 | (* and tell it to draw the first 3 things in that array buffer as | |
| 142 | triangles *) | |
| 143 | Gl.draw_arrays Gl.triangles 0 3; | |
| 144 | (* and then clear that shit out. (We can skip this step, to be fair, | |
| 145 | but it's good to get in the habit for later.) *) | |
| 146 | Gl.bind_buffer Gl.array_buffer 0; | |
| 147 | Gl.use_program 0; | |
| 148 | ||
| 149 | (* and finally swap the window! *) | |
| 150 | Sdl.gl_swap_window w | |
| 151 | ||
| 152 | (* The drawing function here is of type () -> () so we can pass in a | |
| 153 | closure and not have to pass other data down the chain *) | |
| 154 | let event_loop draw w = | |
| 155 | let e = Sdl.Event.create() in | |
| 156 | (* these helper functions are just to reduce the verbosity down | |
| 157 | below *) | |
| 158 | let keycode e = Sdl.Scancode.enum Sdl.Event.(get e keyboard_scancode) in | |
| 159 | let event e = Sdl.Event.(enum (get e typ)) in | |
| 160 | (* the crunchy center is a loop that draws and then dispatches on | |
| 161 | the relevant events *) | |
| 162 | let rec loop () = | |
| 163 | draw (); | |
| 164 | Sdl.wait_event (Some e) >>= fun () -> | |
| 165 | match event e with | |
| 166 | | `Quit -> Ok () | |
| 167 | | `Key_down when keycode e = `Escape -> Ok () | |
| 168 | | `Key_down when keycode e = `Q -> Ok () | |
| 169 | | _ -> loop () | |
| 170 | in loop () | |
| 171 | ||
| 172 | (* and the main function! *) | |
| 173 | let main () = | |
| 174 | (* create the window *) | |
| 175 | window () >>= fun (w, ctx) -> | |
| 176 | (* create the program *) | |
| 177 | create_program () >>= fun program -> | |
| 178 | (* create the vertex data *) | |
| 179 | let (array, buffer) = init_scene program in | |
| 180 | (* run the event loop with the relevant drawing function *) | |
| 181 | event_loop (fun () -> draw program array w) w >>= fun () -> | |
| 182 | (* and clean it all up! *) | |
| 183 | int_as_buf (Gl.delete_vertex_arrays 1) array; | |
| 184 | int_as_buf (Gl.delete_buffers 1) buffer; | |
| 185 | Gl.delete_program program; | |
| 186 | Sdl.gl_delete_context ctx; | |
| 187 | Sdl.destroy_window w; | |
| 188 | Sdl.quit (); | |
| 189 | (* s'all good yo *) | |
| 190 | Ok () | |
| 191 | ||
| 192 | (* and, at the top-level, run it and print any error messages we | |
| 193 | get. *) | |
| 194 | let () = match main () with | |
| 195 | | Ok () -> () | |
| 196 | | Error (`Msg e) -> | |
| 197 | Sdl.log "%s" e; | |
| 198 | exit 1 |