gdritter repos reason-gl-basic / master bin / main.re
master

Tree @master (Download .tar.gz)

main.re @master

7c91fbe
 
 
 
 
 
81164b1
 
 
7c91fbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81164b1
7c91fbe
 
 
 
 
81164b1
 
 
 
 
 
 
7c91fbe
 
 
 
 
 
81164b1
 
 
 
7c91fbe
 
 
 
 
 
81164b1
 
 
 
7c91fbe
 
 
81164b1
 
 
 
7c91fbe
 
 
 
 
 
81164b1
 
 
 
 
 
7c91fbe
 
 
 
81164b1
 
 
 
 
 
7c91fbe
81164b1
 
 
 
7c91fbe
 
 
 
 
81164b1
 
 
 
 
 
 
 
 
7c91fbe
81164b1
 
 
 
7c91fbe
 
 
 
 
 
 
81164b1
 
 
 
 
 
 
 
 
 
 
 
7c91fbe
 
 
81164b1
7c91fbe
81164b1
7c91fbe
 
81164b1
7c91fbe
 
81164b1
7c91fbe
 
 
 
 
 
81164b1
7c91fbe
81164b1
 
7c91fbe
 
 
81164b1
 
7c91fbe
 
81164b1
7c91fbe
 
81164b1
 
7c91fbe
 
81164b1
7c91fbe
 
 
 
 
81164b1
7c91fbe
 
 
81164b1
 
7c91fbe
 
81164b1
7c91fbe
81164b1
 
7c91fbe
81164b1
 
7c91fbe
 
 
 
 
 
 
 
81164b1
7c91fbe
81164b1
7c91fbe
81164b1
7c91fbe
81164b1
7c91fbe
81164b1
7c91fbe
81164b1
 
 
 
 
 
7c91fbe
81164b1
7c91fbe
 
 
 
 
 
81164b1
 
 
 
 
7c91fbe
 
open Tsdl;
open Tgl4;
open Result;

/* the bind operator for the exception monad, for quick error
handling */
let (>>=) = (x, f) => switch x {
  | Ok(v) => f(v)
  | Error(e) => Error(e)
};

/* our vertex shader takes a 2d position and renders it on the screen,
and that's it */
let vertex_shader =
  "#version 130
in vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
";

/* our fragment shader just draws white */
let fragment_shader =
  "#version 130
out vec4 out_color;
void main() {
  out_color = vec4(1.0, 1.0, 1.0, 1.0);
}
";

/* we use a lot of Bigarrays, which are contiguous chunks of memory
that we can hand off to C */
let bigarray_create = (k, len) => Bigarray.(Array1.create(k, c_layout, len));

/* our vertex data is [0.0, 0.5, 0.5, -0.5, -0.5, -0.5], but in a
contiguous array of memory */
let vertices = {
  open Bigarray.Array1;
  let arr = bigarray_create(Bigarray.float32, 6);
  set(arr, 0, 0.0);
  set(arr, 1, 0.5);
  set(arr, 2, 0.5);
  set(arr, 3, -0.5);
  set(arr, 4, -0.5);
  set(arr, 5, -0.5);
  arr
};

/* This takes a function which expects to fill a buffer with a single
32-bit integer; this will call that function and then conver it to an
OCaml integer */
let buf_to_int = (f) => {
  let a = bigarray_create(Bigarray.int32, 1);
  f(a);
  Int32.to_int (Bigarray.Array1.get(a, 0))
};

/* This takes a function that expects to use a buffer containing a
   single 32-bit integer as well as an integer itself; this will
   convert the integer into a buffer and then call the function on
   it */
let int_as_buf = (f, i) => {
  let a = bigarray_create(Bigarray.int32, 1);
  Bigarray.Array1.set(a, 0, Int32.of_int(i));
  f(a)
};

/* Like buf_to_int, but for an arbitrary-sized string */
let get_string = (len, f) => {
  let a = bigarray_create(Bigarray.char, len);
  f(a);
  Gl.string_of_bigarray(a)
};



/* The first thing to do is to get a new window from SDL and then
initialize an OpenGL context in that window. */
let window = () => {
  Sdl.init(Sdl.Init.video) >>= () =>
  Sdl.create_window(~w=640, ~h=480, "test", Sdl.Window.opengl) >>= (w) =>
  Sdl.gl_create_context(w) >>= (ctx) =>
  Sdl.gl_make_current(w, ctx) >>= () =>
    Ok((w, ctx))
};

/* This compiles a shader, producing an informative error message if
the compilation process fails. */
let compile_shader = (typ, src) => {
  let shader = Gl.create_shader(typ);
  Gl.shader_source(shader, src);
  Gl.compile_shader(shader);
  if (buf_to_int(Gl.get_shaderiv(shader, Gl.compile_status)) == Gl.true_) {
    Ok(shader)
  } else {
    let len = buf_to_int(Gl.get_shaderiv(shader, Gl.info_log_length));
    let log = get_string(len, (Gl.get_shader_info_log(shader, len, None)));
    Gl.delete_shader(shader);
    Error(`Msg(log))
  }
};

/* This links our two shaders together into a single OpenGL program,
again producing an informative error message if it fails. */
let create_program = () => {
  compile_shader(Gl.vertex_shader, vertex_shader) >>= (vertex) =>
  compile_shader(Gl.fragment_shader, fragment_shader) >>= (fragment) => {
    let program = Gl.create_program();
    Gl.attach_shader(program, vertex);
    Gl.attach_shader(program, fragment);
    Gl.link_program(program);
    if (buf_to_int(Gl.get_programiv(program, Gl.link_status)) == Gl.true_) {
      Ok(program)
    } else {
      let len = buf_to_int(Gl.get_programiv(program, Gl.info_log_length));
      let log = get_string(len, (Gl.get_program_info_log(program, len, None)));
      Gl.delete_program(program);
      Error(`Msg(log))
    }
  }
};


/* This initializes a vertex array and a vertex buffer using our
vertex information above. */
let init_scene = (program) => {
  let array = buf_to_int(Gl.gen_vertex_arrays(1));
  Gl.bind_vertex_array(array);

  let buffer = buf_to_int(Gl.gen_buffers(1));
  Gl.bind_buffer(Gl.array_buffer, buffer);
  Gl.buffer_data(
    Gl.array_buffer,
    Gl.bigarray_byte_size(vertices),
    Some(vertices),
    Gl.static_draw
  );

  /* This bit is first telling OpenGL where our fragment data can be
  found */
  Gl.bind_frag_data_location(program, 0, "out_color");
  /* and then asking it the location of the position attribute */
  let pos_attr = Gl.get_attrib_location(program, "position");
  /* and finally telling OpenGL that the stuff in our vertex buffer
  corresponds to the aforementioned "position" attribute */
  Gl.enable_vertex_attrib_array(pos_attr);
  /* and tells OpenGL that it consists of two floats with no stride
  and no offset. */
  Gl.vertex_attrib_pointer(pos_attr, 2, Gl.float, false, 0, `Offset(0));

  /* We return these for later use! */
  (array, buffer)
};


let draw = (program, buffer, w) => {
  /* we clear the background color to a pleasing robin's-egg blue */
  Gl.clear_color(0.3, 0.6, 0.9, 1.0);
  Gl.clear(Gl.color_buffer_bit);

  /* we tell OpenGL we want to use that program we made earlier, and
  the array buffer we created earlier, too */
  Gl.use_program(program);
  Gl.bind_buffer(Gl.array_buffer, buffer);
  /* and tell it to draw the first 3 things in that array buffer as
  triangles */
  Gl.draw_arrays(Gl.triangles, 0, 3);
  /* and then clear that shit out. (We can skip this step, to be fair,
  but it's good to get in the habit for later.) */
  Gl.bind_buffer(Gl.array_buffer, 0);
  Gl.use_program(0);

  /* and finally swap the window! */
  Sdl.gl_swap_window(w)
};


/* The drawing function here is of type () -> () so we can pass in a
closure and not have to pass other data down the chain */
let event_loop = (draw, w) => {
  let e = Sdl.Event.create();
  /* these helper functions are just to reduce the verbosity down
  below */
  let keycode = (e) => Sdl.Scancode.enum(Sdl.Event.(get(e, keyboard_scancode)));
  let event = (e) => Sdl.Event.(enum (get(e, typ)));
  /* the crunchy center is a loop that draws and then dispatches on
  the relevant events */
  let rec loop = () => {
    draw ();
    Sdl.wait_event(Some(e)) >>= () =>
    switch (event(e)) {
    | `Quit => Ok ()
    | `Key_down when keycode(e) == `Escape => Ok ()
    | `Key_down when keycode(e) == `Q => Ok ()
    | _ => loop ()
    }
  };
  loop ()
};


/* and the main function! */
let main = () => {
  /* create the window */
  window () >>= ((w, ctx)) =>
  /* create the program */
  create_program () >>= (program) => {
    /* create the vertex data */
    let (array, buffer) = init_scene(program);
    /* run the event loop with the relevant drawing function */
    event_loop(() => draw(program,array, w), w) >>= () => {
      /* and clean it all up! */
      int_as_buf(Gl.delete_vertex_arrays(1), array);
      int_as_buf(Gl.delete_buffers(1), buffer);
      Gl.delete_program(program);
      Sdl.gl_delete_context(ctx);
      Sdl.destroy_window(w);
      Sdl.quit();
      /* s'all good yo */
      Ok()
    }
  }
};

/* and, at the top-level, run it and print any error messages we
get. */
switch (main()) {
  | Ok() => ()
  | Error(`Msg(e)) => {
      Sdl.log("%s", e);
      exit(1)
    };
};