Switch to jbuild-based build system
Getty Ritter
6 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 |