initial commit
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 22 Apr 2021 03:44:13 +0000 (22:44 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 22 Apr 2021 03:44:13 +0000 (22:44 -0500)
17 files changed:
.gitignore [new file with mode: 0644]
build.sh [new file with mode: 0644]
lib/bitmap/bitmap.onyx [new file with mode: 0644]
lib/gl/gl_utils.onyx [new file with mode: 0644]
lib/imgui.onyx [new file with mode: 0644]
lib/immediate_renderer.onyx [new file with mode: 0644]
lib/shaders/immediate_fragment.glsl [new file with mode: 0644]
lib/shaders/immediate_vertex.glsl [new file with mode: 0644]
lib/ttf/types.onyx [new file with mode: 0644]
site/index.html [new file with mode: 0644]
site/js/game.js [new file with mode: 0644]
site/js/js_events.js [new file with mode: 0644]
site/js/onyx-loader.js [new file with mode: 0644]
site/js/webgl2.js [new file with mode: 0644]
site/sand_toy.wasm [new file with mode: 0644]
src/build.onyx [new file with mode: 0644]
src/sand_toy.onyx [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8f6344f
--- /dev/null
@@ -0,0 +1,2 @@
+*.sublime-project\r
+*.sublime-workspace\r
diff --git a/build.sh b/build.sh
new file mode 100644 (file)
index 0000000..74a7ef8
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Copy relative javascript modules from Onyx source directory
+cp ../onyx/bin/onyx-loader.js ./site/js/onyx-loader.js
+cp ../onyx/modules/webgl2/webgl2.js ./site/js/webgl2.js
+cp ../onyx/modules/js_events/js_events.js ./site/js/js_events.js
+
+onyx -r js --use-post-mvp-features -V -o site/sand_toy.wasm \
+    src/build.onyx \
+    -I ../onyx # Include the folder that contains the "modules"
diff --git a/lib/bitmap/bitmap.onyx b/lib/bitmap/bitmap.onyx
new file mode 100644 (file)
index 0000000..e72ca10
--- /dev/null
@@ -0,0 +1,321 @@
+package imgui.bitmap\r
+\r
+use package core\r
+#private_file ttf :: package imgui.ttf\r
+\r
+Point :: struct {\r
+    x, y: f32;\r
+}\r
+\r
+\r
+Bitmap :: struct {\r
+    Style :: enum {\r
+        Outlined_Aliased;\r
+        Outlined_Border;\r
+        Filled;\r
+        Raw;\r
+    }\r
+\r
+    allocator : Allocator;\r
+    ttf    : ^ttf.TrueTypeFont;\r
+    buffer : [] u8;\r
+\r
+    width  : i32 = 1;\r
+    height : i32 = 1;\r
+\r
+    bytes_per_pixel  : u32 = 4;\r
+    background_color : u32 = 0xFFFFFF00;\r
+    foreground_color : u32 = 0x000000FF;\r
+\r
+    scale   : f32 = 1.0f;\r
+    scale_x : f32 = 1.0f;\r
+    scale_y : f32 = 1.0f;\r
+\r
+    space_width      : f32 = 1.0f;\r
+    space_multiplier : f32 = 0.0f;\r
+\r
+    transformation_matrix: [9] f32 = f32.[1,0,0,0,1,0,0,0,0];\r
+    character_matrix:      [9] f32 = f32.[1,0,0,0,1,0,0,0,0];\r
+\r
+    style: Style = Style.Filled;\r
+    justify := false;\r
+    justify_fill_ratio : f32 = 0.5;\r
+\r
+    filler : [..][..] i32;\r
+\r
+    use_font_metrics: bool = false;\r
+}\r
+\r
+bitmap_clear :: (use bitmap: ^Bitmap) {\r
+    size := width * width * bytes_per_pixel;\r
+    memory.set(buffer.data, 0, size);\r
+}\r
+\r
+bitmap_transform_text :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) {\r
+    return\r
+        cast(i32) (p.x * transformation_matrix[0] + p.y * transformation_matrix[1] + transformation_matrix[4]),\r
+        cast(i32) (p.x * transformation_matrix[2] + p.y * transformation_matrix[3] + transformation_matrix[5]);\r
+}\r
+\r
+bitmap_transform_character :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) {\r
+    return\r
+        cast(i32) (p.x * character_matrix[0] + p.y * character_matrix[1] + character_matrix[4]),\r
+        cast(i32) (p.x * character_matrix[2] + p.y * character_matrix[3] + character_matrix[5]);\r
+}\r
+\r
+bitmap_set_pos :: (use bitmap: ^Bitmap, x: f32, y: f32) {\r
+    transformation_matrix[6] = x;\r
+    transformation_matrix[7] = y;\r
+}\r
+\r
+bitmap_set_rotation :: (use bitmap: ^Bitmap, angle: f32) {\r
+    transformation_matrix[0] =  math.cos(angle); \r
+    transformation_matrix[1] = -math.sin(angle); \r
+    transformation_matrix[2] =  math.sin(angle); \r
+    transformation_matrix[3] =  math.cos(angle); \r
+}\r
+\r
+bitmap_init_filler :: (use bitmap: ^Bitmap) {\r
+    h := height - filler.count;\r
+    if h < 1 do return;\r
+\r
+    for _: h {\r
+        new_row := array.make(i32, allocator=allocator);\r
+        array.push(^filler, new_row);\r
+    }\r
+}\r
+\r
+bitmap_clear_filler :: (use bitmap: ^Bitmap) {\r
+    for i: height {\r
+        array.clear(^filler[i]);\r
+    }\r
+}\r
+\r
+bitmap_exec_filler :: (use bitmap: ^Bitmap) {\r
+    cmp_asc :: (x: $T, y: T) -> i32 do return cast(i32) (x - y);\r
+\r
+    for y: height {\r
+        if filler[y].count > 0 {\r
+            array.sort(^filler[y], cmp_asc);\r
+\r
+            if filler[y].count % 2 != 0 {\r
+                // Need to have an even number of lines\r
+                continue;\r
+            }\r
+\r
+            index := 0;\r
+            while index < filler[y].count {\r
+                defer index += 2;\r
+\r
+                startx := filler[y][index] + 1;\r
+                endx   := filler[y][index + 1];\r
+\r
+                if startx >= endx do continue;\r
+\r
+                for x: startx .. endx + 1 { // @Check: +1 may not be necessary\r
+                    bitmap_plot(bitmap, x, y, foreground_color);\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+// This function should be inlined at some point because it is very small\r
+// and cheap.\r
+bitmap_plot :: (use bitmap: ^Bitmap, x: i32, y: i32, color: u32) -> bool {\r
+    if x < 0 || x >= width || y < 0 || y >= height do return false;\r
+\r
+    index := (x + y * width) * bytes_per_pixel;\r
+    buffer[index] = cast(u8) (color & 0xFF);\r
+\r
+    return true;\r
+}\r
+\r
+bitmap_fline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) {\r
+\r
+    if (ix0 < 0 && ix1 < 0) || (ix0 > width && ix1 > width) do return;\r
+\r
+    x0 := cast(f32) ix0;\r
+    y0 := cast(f32) iy0;\r
+    x1 := cast(f32) ix1;\r
+    y1 := cast(f32) iy1;\r
+\r
+    if y1 < y0 {\r
+        x0, x1 = x1, x0;\r
+        y0, y1 = y1, y0;\r
+    }\r
+\r
+    dx := x1 - x0;\r
+    dy := y1 - y0;\r
+\r
+    if dy == 0 {\r
+        if iy0 >= 0 && iy0 < filler.count {\r
+            if ix0 <= ix1 {\r
+                array.push(^filler[iy0], ix0);\r
+                array.push(^filler[iy0], ix1);\r
+            } else {\r
+                array.push(^filler[iy0], ix1);\r
+                array.push(^filler[iy0], ix0);\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    n := dx / dy;\r
+    for y: cast(i32) (dy + 0.5) {\r
+        yd := cast(i32) (y + iy0);\r
+        x := n * ~~y + x0;\r
+\r
+        if x > ~~width || yd >= ~~filler.count do break;\r
+\r
+        if yd >= 0 && yd < filler.count {\r
+            array.push(^filler[yd], cast(i32) (x + 0.5));\r
+        }\r
+    }\r
+}\r
+\r
+bitmap_aline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) {\r
+    x0 := cast(f32) ix0;\r
+    y0 := cast(f32) iy0;\r
+    x1 := cast(f32) ix1;\r
+    y1 := cast(f32) iy1;\r
+\r
+    dx := x1 - x0;\r
+    dy := y1 - y0;\r
+\r
+    dist := cast(f32) 0.4;\r
+    if math.abs(dx) > math.abs(dy) {\r
+        if x1 > x0 {\r
+            x0, x1 = x1, x0;\r
+            y0, y1 = y1, y0;\r
+        }\r
+\r
+        dx := x1 - x0;\r
+        dy := y1 - y0;\r
+\r
+        x0 += 0.5;\r
+        y0 += 0.5;\r
+\r
+        m := dy / dx;\r
+        x := x0;\r
+        while x <= x1 + 0.5 {\r
+            y := m * (x - x0) + y0;\r
+            e := 1 - math.abs(y - 0.5 - math.floor(y));\r
+            bitmap_plot(bitmap, ~~x, ~~y,  foreground_color); // Needs to be aliased!\r
+\r
+            ys1 := y + dist;\r
+            if cast(i32) ys1 != cast(i32) y {\r
+                v1 := math.abs(ys1 - y) / dist * (1 - e);\r
+                bitmap_plot(bitmap, ~~x, ~~ys1, foreground_color); // Needs to be aliased!\r
+            }\r
+\r
+            ys2 := y - dist;\r
+            if cast(i32) ys2 != cast(i32) y {\r
+                v2 := math.abs(y - ys2) / dist * (1 - e);\r
+                bitmap_plot(bitmap, ~~x, ~~ys2, foreground_color); // Needs to be aliased!\r
+            }\r
+\r
+            x += 1;\r
+        }\r
+\r
+    } else {\r
+        if y1 > y0 {\r
+            x0, x1 = x1, x0;\r
+            y0, y1 = y1, y0;\r
+        }\r
+\r
+        dx := x1 - x0;\r
+        dy := y1 - y0;\r
+\r
+        x0 += 0.5;\r
+        y0 += 0.5;\r
+\r
+        n := dx / dy;\r
+        y := y0;\r
+        while y <= y1 + 0.5 {\r
+            x := n * (y - x0) + y0;\r
+            e := 1 - math.abs(x - 0.5 - math.floor(x));\r
+            bitmap_plot(bitmap, ~~x, ~~y, foreground_color); // Needs to be aliased!\r
+\r
+            xs1 := x + dist;\r
+            if cast(i32) xs1 != cast(i32) x {\r
+                v1 := math.abs(xs1 - x) / dist * (1 - e);\r
+                bitmap_plot(bitmap, ~~xs1, ~~y, foreground_color); // Needs to be aliased!\r
+            }\r
+\r
+            xs2 := x - dist;\r
+            if cast(i32) xs1 != cast(i32) x {\r
+                v1 := math.abs(x - xs2) / dist * (1 - e);\r
+                bitmap_plot(bitmap, ~~xs2, ~~y, foreground_color); // Needs to be aliased!\r
+            }\r
+\r
+            y += 1.0;\r
+        }\r
+    }\r
+}\r
+\r
+bitmap_line :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) {\r
+    use Bitmap.Style;\r
+    switch style {\r
+        case Outlined_Aliased {\r
+            bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color);\r
+            return;\r
+        }\r
+        case Filled {\r
+            bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color);\r
+            bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color);\r
+            return;\r
+        }\r
+        case Raw {\r
+            bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color);\r
+            return;\r
+        }\r
+    }\r
+\r
+    assert(false, "Unhandled bitmap_line case");\r
+}\r
+\r
+bitmap_box :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) {\r
+    bitmap_line(bitmap, ix0, iy0, ix1, iy0, color);\r
+    bitmap_line(bitmap, ix1, iy0, ix1, iy1, color);\r
+    bitmap_line(bitmap, ix0, iy1, ix1, iy1, color);\r
+    bitmap_line(bitmap, ix0, iy0, ix0, iy1, color);\r
+}\r
+\r
+bitmap_quadratic :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, icx: i32, icy: i32, color: u32) {\r
+    x0 := cast(f64) ix0;\r
+    x1 := cast(f64) ix1;\r
+    y0 := cast(f64) iy0;\r
+    y1 := cast(f64) iy1;\r
+    cx := cast(f64) icx;\r
+    cy := cast(f64) icy;\r
+\r
+    division := cast(f64) 1;\r
+    dx := math.abs(x0 - x1);\r
+    dy := math.abs(y0 - y1);\r
+\r
+    if dx <= 2 || dy <= 2 {\r
+        bitmap_line(bitmap, ~~x0, ~~y0, ~~x1, ~~y1, color);\r
+        return;\r
+    }\r
+\r
+    division = 1 / cast(f64) math.max(dx, dy);\r
+\r
+    x_old := x0;\r
+    y_old := y0;\r
+    t     := cast(f64) 0;\r
+\r
+    while t <= (1 + division / 2) {\r
+        s := 1 - t;\r
+        x := s * s * x0 + 2.0 * s * t * cx + t * t * x1;\r
+        y := s * s * y0 + 2.0 * s * t * cy + t * t * y1;\r
+        xi := cast(i32) (x + 0.5);\r
+        yi := cast(i32) (y + 0.5);\r
+\r
+        bitmap_line(bitmap, ~~x_old, ~~y_old, ~~xi, ~~yi, color);\r
+        x_old = ~~ xi;\r
+        y_old = ~~ yi;\r
+        t += division;\r
+    }\r
+}\r
diff --git a/lib/gl/gl_utils.onyx b/lib/gl/gl_utils.onyx
new file mode 100644 (file)
index 0000000..b306d5a
--- /dev/null
@@ -0,0 +1,82 @@
+package imgui.gl
+
+use package core
+#private_file gl :: package gl
+
+// This Shader represents an OpenGL program, not a shader. The name
+// is confusing but conceptually for most people, shaders are a bundled
+// version of the vertex and fragment shader.
+Shader :: struct {
+    program : gl.GLProgram;
+
+    position_loc   : gl.GLint;
+    color_loc      : gl.GLint;
+    texture_loc    : gl.GLint;
+
+    view_matrix_loc  : gl.GLUniformLocation;
+    world_matrix_loc : gl.GLUniformLocation;
+
+    make_from_source :: (vertex_source: str, fragment_source: str) -> Shader {
+        shader: Shader;
+        init_from_source(^shader, vertex_source, fragment_source);
+        return shader;
+    }
+
+    init_from_source :: (use shader: ^Shader, vertex_source: str, fragment_source: str) {
+        vertex_shader, vertex_compiled     := compile_shader(vertex_source,   gl.VERTEX_SHADER);
+        fragment_shader, fragment_compiled := compile_shader(fragment_source, gl.FRAGMENT_SHADER);
+        assert(vertex_compiled, "Vertex shader failed to compile");
+        assert(fragment_compiled, "Fragment shader failed to compile");
+        defer {
+            gl.deleteShader(vertex_shader);
+            gl.deleteShader(fragment_shader);
+        }
+
+        shader_program, program_linked := link_program(vertex_shader, fragment_shader);
+        assert(program_linked, "Program failed to link");
+        program = shader_program;
+
+        position_loc   = gl.getAttribLocation(program, "a_position");
+        color_loc      = gl.getAttribLocation(program, "a_color");
+        texture_loc    = gl.getAttribLocation(program, "a_texture");
+
+        view_matrix_loc  = gl.getUniformLocation(program, "u_view");
+        world_matrix_loc = gl.getUniformLocation(program, "u_world");
+
+        compile_shader :: (source: str, shader_type: gl.GLenum) -> (gl.GLShader, bool) {
+            shader := gl.createShader(shader_type);
+            gl.shaderSource(shader, source);
+            gl.compileShader(shader);
+
+            success := true;
+            if gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0 {
+                printf("Error compiling shader.");
+                gl.printShaderInfoLog(shader);
+                success = false;
+            }
+
+            return shader, success;
+        }
+
+        link_program :: (vertex_shader: gl.GLShader, fragment_shader: gl.GLShader) -> (gl.GLProgram, bool) {
+            program := gl.createProgram();
+            gl.attachShader(program, vertex_shader);
+            gl.attachShader(program, fragment_shader);
+            gl.linkProgram(program);
+
+            success := true;
+            if gl.getProgramParameter(program, gl.LINK_STATUS) == 0 {
+                printf("Error linking program.");
+                gl.printProgramInfoLog(program);
+                success = false;
+            }
+
+            return program, success;
+        }
+    }
+
+    free :: (use shader: ^Shader) {
+        gl.deleteProgram(program);
+    }
+}
+
diff --git a/lib/imgui.onyx b/lib/imgui.onyx
new file mode 100644 (file)
index 0000000..e93467d
--- /dev/null
@@ -0,0 +1,22 @@
+// The goal of the onyx-imgui library is to provide a simple and fast immediate mode graphical
+// user interface library to be used in a webbrowser with WASM and WebGL. This library will
+// make prototyping applications faster and easier, and will make more complicated applications
+// doable without a ton of code.
+
+// Several things need to be developed:
+//  - Immediate mode renderer
+//  - TTF file loading and prerendering to a texture map
+//  - More things I can't think of at the moment
+
+// Something to consider is that there are two major use cases for a library like this. Either,
+// someone will create an application from scratch, or they will integrate it into their existing
+// code. This library should make both of those situations easy to manage.
+
+// I don't want to plan this too much. I want to just dive in and get working on it.
+
+package imgui
+
+#load "lib/gl/gl_utils"
+#load "lib/immediate_renderer"
+#load "lib/ttf/types"
+#load "lib/bitmap/bitmap"
diff --git a/lib/immediate_renderer.onyx b/lib/immediate_renderer.onyx
new file mode 100644 (file)
index 0000000..e37a0ca
--- /dev/null
@@ -0,0 +1,179 @@
+package imgui
+
+#load "lib/gl/gl_utils"
+
+use package core
+use package imgui.gl
+#private_file gl :: package gl
+
+Vector2 :: struct {
+    x, y: f32;
+}
+
+Color4 :: struct {
+    r, g, b: f32;
+    a: f32 = 1;
+}
+
+Immediate_Vertex :: struct {
+    position   : Vector2;
+    color      : Color4;
+    texture    : Vector2;
+}
+
+
+Immediate_Renderer :: struct {
+    shader : Shader;
+
+    // 'verticies' contains the vertex data and the maximum number of verticies
+    // that can be rendered at a given time. 'vertex_count' is used to store how
+    // many verticies will be rendered in the next draw call. 'vertex_count' is
+    // expected to be a multiple of 3, given that triangles are being rendered.
+    verticies : [] Immediate_Vertex;
+    vertex_count : u32;
+
+    clear_color : Color4;
+
+    vertex_array  : gl.GLVertexArrayObject;
+    vertex_buffer : gl.GLBuffer;
+
+    texture_uniform     : gl.GLUniformLocation;
+    use_texture_uniform : gl.GLUniformLocation;
+
+    // Needs to be a multiple of 3!!
+    Default_Max_Verticies :: 1023;
+
+    make :: (max_verticies := Default_Max_Verticies) -> Immediate_Renderer {
+        ir : Immediate_Renderer;
+        init(^ir, max_verticies);
+
+        return ir;
+    }
+
+    init :: (use ir: ^Immediate_Renderer, max_verticies := Default_Max_Verticies) {
+        vertex_source   := #file_contents "lib/shaders/immediate_vertex.glsl";
+        fragment_source := #file_contents "lib/shaders/immediate_fragment.glsl";
+        shader = Shader.make_from_source(vertex_source, fragment_source);
+        gl.useProgram(shader.program);
+
+        texture_uniform     = gl.getUniformLocation(shader.program, "u_texture");
+        use_texture_uniform = gl.getUniformLocation(shader.program, "u_use_texture");
+
+        verticies = memory.make_slice(Immediate_Vertex, max_verticies);
+        memory.set(verticies.data, 0, verticies.count * sizeof Immediate_Vertex);
+
+        vertex_array = gl.createVertexArray();
+        gl.bindVertexArray(vertex_array);
+        defer gl.bindVertexArray(-1);
+
+        vertex_buffer = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
+        gl.bufferData(gl.ARRAY_BUFFER, cast(gl.GLsizei) (max_verticies * sizeof Immediate_Vertex), gl.STREAM_DRAW);
+        
+        // Position
+        gl.enableVertexAttribArray(0);
+        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, sizeof Immediate_Vertex, 0);
+
+        // Color
+        gl.enableVertexAttribArray(1);
+        gl.vertexAttribPointer(1, 4, gl.FLOAT, false, sizeof Immediate_Vertex, 2 * sizeof f32);
+
+        // Texture
+        gl.enableVertexAttribArray(2);
+        gl.vertexAttribPointer(2, 2, gl.FLOAT, false, sizeof Immediate_Vertex, 6 * sizeof f32);
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, -1);
+    }
+
+    free :: (use ir: ^Immediate_Renderer) {
+        shader->free();
+
+        gl.deleteVertexArray(vertex_array);
+        gl.deleteBuffer(vertex_buffer);
+    }
+
+    flush :: (use ir: ^Immediate_Renderer) {
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
+        gl.bufferSubData(gl.ARRAY_BUFFER, 0, .{ count = vertex_count * sizeof Immediate_Vertex, data = ~~verticies.data });
+        gl.bindBuffer(gl.ARRAY_BUFFER, -1);
+
+        gl.useProgram(shader.program);
+        gl.bindVertexArray(vertex_array);
+        gl.drawArrays(gl.TRIANGLES, 0, vertex_count);
+
+        vertex_count = 0;
+    }
+
+    // As a small note, I love the pattern matching style of programming that
+    // can be done in situtations like this. Now, this isn't the most efficient
+    // because it is mostly just marshalling the arguments around, but the
+    // pattern matching at compile-time is very cool, I think at least.
+    push_vertex :: proc {
+        // If a color is not provided, the previous color is used.
+        (use ir: ^Immediate_Renderer, position: Vector2) {
+            push_vertex(ir, position, color = verticies[vertex_count - 1].color);
+        },
+
+        (use ir: ^Immediate_Renderer, position: Vector2, color: Color4) {
+            if vertex_count >= verticies.count do ir->flush();
+
+            vertex_ptr := ^verticies[vertex_count];
+            defer vertex_count += 1;
+
+            vertex_ptr.position = position;
+            vertex_ptr.color = color;
+            vertex_ptr.texture = .{ 0, 0 };
+        },
+    }
+
+    quad :: (use ir: ^Immediate_Renderer, position: Vector2, size: Vector2, color: Color4 = .{1,1,1}) {
+        push_vertex(ir, .{ position.x,          position.y          }, color);
+        push_vertex(ir, .{ position.x + size.x, position.y          });
+        push_vertex(ir, .{ position.x + size.x, position.y + size.y });
+
+        push_vertex(ir, .{ position.x,          position.y          });
+        push_vertex(ir, .{ position.x + size.x, position.y + size.y });
+        push_vertex(ir, .{ position.x,          position.y + size.y });
+    }
+
+    // NOTE: Calling set_texture without a paramter will disabled textured rendering.
+    set_texture :: (use ir: ^Immediate_Renderer, texture_id: i32 = -1) {
+        if vertex_count > 0 do flush(ir);
+
+        gl.uniform1i(texture_uniform, math.max(texture_id, 0));
+        gl.uniform1i(use_texture_uniform, cast(i32) (texture_id >= 0));
+    }
+}
+
+
+
+
+// While the immediate renderer can be used on its own, below is a set of wrapping functions
+// that operate on a global immediate renderer. This is probably the most common way that 
+// it will be used.
+
+#private
+immediate_renderer : Immediate_Renderer;
+
+immediate_renderer_init :: () {
+    Immediate_Renderer.init(^immediate_renderer);
+}
+
+// This is probably useless, since this will probably be initialized for the duration
+// of the webpage being open.
+immediate_renderer_free :: () {
+    Immediate_Renderer.free(^immediate_renderer);
+}
+
+immediate_vertex :: proc {
+    (position: Vector2)                { immediate_renderer->push_vertex(position); },
+    (position: Vector2, color: Color4) { immediate_renderer->push_vertex(position, color); },
+}
+
+immediate_quad :: (position: Vector2, size: Vector2, color: Color4 = .{1,1,1}) {
+    immediate_renderer->quad(position, size, color);
+}
+
+immediate_flush :: () do immediate_renderer->flush();
+
+immediate_set_texture :: (texture_id: i32 = -1) do immediate_renderer->set_texture(texture_id);
diff --git a/lib/shaders/immediate_fragment.glsl b/lib/shaders/immediate_fragment.glsl
new file mode 100644 (file)
index 0000000..204e095
--- /dev/null
@@ -0,0 +1,19 @@
+#version 300 es
+
+precision mediump float;
+
+uniform sampler2D u_texture;
+uniform bool      u_use_texture;
+
+in vec4 v_color;
+in vec2 v_texture;
+
+out vec4 fragColor;
+
+void main() {
+    if (u_use_texture) {
+        fragColor = v_color * texture(u_texture, v_texture);
+    } else {
+        fragColor = v_color;
+    }
+}
diff --git a/lib/shaders/immediate_vertex.glsl b/lib/shaders/immediate_vertex.glsl
new file mode 100644 (file)
index 0000000..cf95128
--- /dev/null
@@ -0,0 +1,15 @@
+#version 300 es
+
+layout(location = 0) in vec2 a_vertex;
+layout(location = 1) in vec4 a_color;
+layout(location = 2) in vec2 a_texture;
+
+out vec4 v_color;
+out vec2 v_texture;
+
+void main() {
+    gl_Position = vec4(a_vertex, 0, 1);
+
+    v_color = a_color;
+    v_texture = a_texture;
+}
diff --git a/lib/ttf/types.onyx b/lib/ttf/types.onyx
new file mode 100644 (file)
index 0000000..c9b59b1
--- /dev/null
@@ -0,0 +1,773 @@
+package imgui.ttf\r
+\r
+use package core\r
+\r
+TrueTypeFont :: struct {\r
+    allocator : Allocator;\r
+    reader: io.binary.BinaryReader;\r
+\r
+    scalar_type    : u32;\r
+    search_range   : u16;\r
+    entry_selector : u16;\r
+    range_shift    : u16;\r
+\r
+    table_map : map.Map(str, TTTableInfo);\r
+    char_maps : [..] TTCmap;\r
+\r
+    version             : u32;\r
+    font_revision       : u32;\r
+    checksum_adjustment : u32;\r
+    magic_number        : u32;\r
+    flags               : u16;\r
+    units_per_em        : u16;\r
+    created             : u64;\r
+    modified            : u64;\r
+    x_min               : i16;\r
+    x_max               : i16;\r
+    y_min               : i16;\r
+    y_max               : i16;\r
+    mac_style           : u16;\r
+    lowest_rec_ppem     : u16;\r
+    font_direction_hint : i16;\r
+    index_to_loc_format : TTIndexToLocFormat;\r
+    glyph_data_format   : i16;\r
+    kern                : [..] TTKern0Table;\r
+\r
+    hhea : struct {\r
+        version                 : u32;\r
+        ascent                  : i16;\r
+        descent                 : i16;\r
+        line_gap                : i16;\r
+        advance_width_max       : u16;\r
+        min_left_side_bearing   : i16;\r
+        min_right_side_bearing  : i16;\r
+        x_max_extent            : i16;\r
+        caret_slope_rise        : i16;\r
+        caret_slope_run         : i16;\r
+        caret_offset            : i16;\r
+        metric_data_format      : i16;\r
+        num_of_long_hor_metrics : u16;\r
+    };\r
+}\r
+\r
+ttf_create :: (ttf_data: [] u8, allocator := context.allocator) -> ^TrueTypeFont {\r
+    ttf := new(TrueTypeFont, allocator=allocator);\r
+    ttf.allocator = allocator;\r
+\r
+    ttf.reader = io.binary.create_reader(ttf_data);\r
+\r
+    map.init(^ttf.table_map, .{});\r
+    array.init(^ttf.char_maps, allocator=allocator);\r
+    array.init(^ttf.kern, allocator=allocator);\r
+    ttf_read_offset_table(ttf);\r
+    ttf_read_head_table(ttf);\r
+    ttf_read_cmap_table(ttf);\r
+    ttf_read_hhea_table(ttf);\r
+    ttf_read_kern_table(ttf);\r
+\r
+    return ttf;\r
+}\r
+\r
+ttf_free :: (ttf: ^TrueTypeFont) {\r
+    array.free(^ttf.char_maps);\r
+    array.free(^ttf.kern);\r
+    map.free(^ttf.table_map);\r
+\r
+    raw_free(ttf.allocator, ttf);\r
+}\r
+\r
+TTTableInfo :: struct {\r
+    checksum : u32 = 0;\r
+    offset   : u32 = 0;\r
+    length   : u32 = 0;\r
+}\r
+\r
+TTIndexToLocFormat :: enum (i16) {\r
+    Short :: 0x00;\r
+    Long  :: 0x01;\r
+}\r
+\r
+TTGlyph :: struct {\r
+    Type :: enum {\r
+        Unknown  :: 0x00;\r
+        Simple   :: 0x01;\r
+        Compound :: 0x02;\r
+    }\r
+\r
+    type: Type = Type.Unknown;\r
+    allocator: Allocator;\r
+\r
+    valid_glyph := false;\r
+\r
+    contour_count : i16;\r
+    x_min : i16;\r
+    x_max : i16;\r
+    y_min : i16;\r
+    y_max : i16;\r
+\r
+    points : [..] TTGlyphPoint;\r
+    contour_ends : [..] u16;\r
+\r
+    components: [] GlyphComponent;\r
+\r
+    GlyphComponent :: struct {\r
+        glyph_index      : u16;\r
+        dest_point_index : i16;\r
+        src_point_index  : i16;\r
+        matrix           : [6] f32;\r
+    }\r
+}\r
+\r
+TTGlyphPoint :: struct {\r
+    on_curve : bool;\r
+    x : i16 = 0;\r
+    y : i16 = 0;\r
+}\r
+\r
+ttf_read_offset_table :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    scalar_type = read_u32(^reader);\r
+    num_tables := cast(u32) read_u16(^reader);\r
+    search_range = read_u16(^reader);\r
+    entry_selector = read_u16(^reader);\r
+    range_shift = read_u16(^reader);\r
+\r
+    for i: num_tables {\r
+        tag := read_string(^reader, 4);\r
+\r
+        table_info : TTTableInfo;\r
+        table_info.checksum = read_u32(^reader);\r
+        table_info.offset   = read_u32(^reader);\r
+        table_info.length   = read_u32(^reader);\r
+\r
+        map.put(^table_map, tag, table_info);\r
+\r
+        // CLEANUP: There should be a "!=" for strings.\r
+        if !(tag == "head") {\r
+            csum := ttf_calc_table_checksum(^reader, table_info.offset, table_info.length);\r
+            if table_info.checksum != csum {\r
+                printf("WARNING: Checksum for table '%s' did not match.\n", tag);\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+ttf_read_head_table :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    head_table_info  := map.get(^table_map, "head");\r
+    seek(^reader, head_table_info.offset);\r
+\r
+    version             = read_u32(^reader);\r
+    font_revision       = read_u32(^reader);\r
+    checksum_adjustment = read_u32(^reader);\r
+    magic_number        = read_u32(^reader);\r
+    flags               = read_u16(^reader);\r
+    units_per_em        = read_u16(^reader);\r
+    created             = read_date(^reader);\r
+    modified            = read_date(^reader);\r
+    x_min               = read_fword(^reader);\r
+    y_min               = read_fword(^reader);\r
+    x_max               = read_fword(^reader);\r
+    y_max               = read_fword(^reader);\r
+    mac_style           = read_u16(^reader);\r
+    lowest_rec_ppem     = read_u16(^reader);\r
+    font_direction_hint = read_i16(^reader);\r
+    index_to_loc_format = cast(TTIndexToLocFormat) read_i16(^reader);\r
+    glyph_data_format   = read_i16(^reader);\r
+\r
+    assert(magic_number == 0x5f0f3cf5, "TTF Magic Number wrong!");\r
+}\r
+\r
+ttf_lookup_glyph_offset :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> u32 {\r
+    use package core.io.binary\r
+\r
+    loca_table_info  := map.get(^table_map, "loca");\r
+    glyf_table_info  := map.get(^table_map, "glyf");\r
+\r
+    old: u32;\r
+    defer seek(^reader, old);\r
+\r
+    switch index_to_loc_format {\r
+        case TTIndexToLocFormat.Long {\r
+            old = seek(^reader, loca_table_info.offset + glyph_index * 4);\r
+            return read_u32(^reader) + glyf_table_info.offset;\r
+        }\r
+\r
+        case #default {\r
+            old = seek(^reader, loca_table_info.offset + glyph_index * 2);\r
+            return 2 * cast(u32) read_u16(^reader) + glyf_table_info.offset;\r
+        }\r
+    }\r
+\r
+    return 0xffffffff;\r
+}\r
+\r
+// Result is expected to be freed\r
+ttf_read_glyph :: (use ttf: ^TrueTypeFont, glyph_index: i32, glyph_allocator := context.allocator) -> ^TTGlyph {\r
+    use package core.io.binary\r
+\r
+    offset := ttf_lookup_glyph_offset(ttf, glyph_index);\r
+\r
+    glyf_table_info  := map.get(^table_map, "glyf");\r
+\r
+    if offset >= glyf_table_info.offset + glyf_table_info.length do return null;\r
+\r
+    seek(^reader, offset);\r
+\r
+    glyph := new(TTGlyph, glyph_allocator);\r
+    glyph.allocator = glyph_allocator;\r
+    glyph.contour_count = read_i16(^reader);\r
+    glyph.x_min = read_fword(^reader);\r
+    glyph.y_min = read_fword(^reader);\r
+    glyph.x_max = read_fword(^reader);\r
+    glyph.y_max = read_fword(^reader);\r
+\r
+    if glyph.contour_count < 0 { raw_free(glyph_allocator, glyph); return null; }\r
+    if glyph.contour_count == ~~ -1 {\r
+        // Compound glyph\r
+        return null;\r
+    } else {\r
+        // Simple glyph\r
+        ttf_read_simple_glyph(ttf, glyph);\r
+    }\r
+\r
+    glyph.valid_glyph = true;\r
+    return glyph;\r
+}\r
+\r
+ttf_glyph_destroy :: (glyph: ^TTGlyph) {\r
+    array.free(^glyph.contour_ends);\r
+    array.free(^glyph.points);\r
+    raw_free(glyph.allocator, glyph);\r
+}\r
+\r
+#private_file\r
+TTGlyphFlags :: enum #flags {\r
+    On_Curve  :: 0x01;\r
+    X_Is_Byte :: 0x02;\r
+    Y_Is_Byte :: 0x04;\r
+    Repeat    :: 0x08;\r
+    X_Delta   :: 0x10;\r
+    Y_Delta   :: 0x20;\r
+}\r
+\r
+ttf_read_simple_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) {\r
+    use package core.io.binary\r
+\r
+    if glyph.contour_count == 0 do return;\r
+\r
+    array.init(^glyph.contour_ends, ~~glyph.contour_count);\r
+    array.init(^glyph.points);\r
+\r
+    for _: 0 .. ~~glyph.contour_count {\r
+        array.push(^glyph.contour_ends, read_u16(^reader));\r
+    }\r
+\r
+    seek(^reader, ~~read_u16(^reader) + tell(^reader));\r
+\r
+    num_points := array.fold(^glyph.contour_ends, cast(u16) 0, math.max_poly) + 1;\r
+\r
+    flags : [..] TTGlyphFlags;\r
+    array.init(^flags);\r
+    defer array.free(^flags);\r
+\r
+    while i := 0; i < ~~num_points {\r
+        defer i += 1;\r
+\r
+        flag := cast(TTGlyphFlags) read_u8(^reader);\r
+        array.push(^flags, flag);\r
+        array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 });\r
+\r
+        if (flag & TTGlyphFlags.Repeat) != ~~0 {\r
+            rep_count := read_u8(^reader);\r
+            i += ~~ rep_count;\r
+\r
+            for _: 0 .. ~~rep_count {\r
+                array.push(^flags, flag);\r
+                array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 });\r
+            }\r
+        }\r
+    }\r
+\r
+    value: i16 = 0;\r
+    for i: 0 .. ~~num_points {\r
+        flag := flags[i];\r
+\r
+        if (flag & TTGlyphFlags.X_Is_Byte) != ~~0 {\r
+            if (flag & TTGlyphFlags.X_Delta) != ~~0 {\r
+                value += ~~read_u8(^reader);\r
+            } else {\r
+                value -= ~~read_u8(^reader);\r
+            }\r
+        } elseif (flag & TTGlyphFlags.X_Delta) == ~~0 {\r
+            value += read_i16(^reader);\r
+        }\r
+\r
+        glyph.points[i].x = value;\r
+    }\r
+\r
+    value = 0;\r
+    for i: 0 .. ~~num_points {\r
+        flag := flags[i];\r
+\r
+        if (flag & TTGlyphFlags.Y_Is_Byte) != ~~0 {\r
+            if (flag & TTGlyphFlags.Y_Delta) != ~~0 {\r
+                value += ~~read_u8(^reader);\r
+            } else {\r
+                value -= ~~read_u8(^reader);\r
+            }\r
+        } elseif (flag & TTGlyphFlags.Y_Delta) == ~~0 {\r
+            value += read_i16(^reader);\r
+        }\r
+\r
+        glyph.points[i].y = value;\r
+    }\r
+}\r
+\r
+#private\r
+TFKC_Flags :: enum #flags {\r
+    arg_1_and_2_are_words;\r
+    args_are_xy_values;\r
+    round_xy_to_grid;\r
+    we_have_a_scale;\r
+    _; // reserved\r
+    more_components;\r
+    we_have_an_x_and_y_scale;\r
+    we_have_a_two_by_two;\r
+    we_have_instructions;\r
+    use_my_metrics;\r
+    overlap_component;\r
+}\r
+\r
+ttf_read_compound_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) {\r
+    use package core.io.binary\r
+\r
+    glyph.type = TTGlyph.Type.Compound;\r
+\r
+    component : TTGlyph.GlyphComponent;\r
+    flags := TFKC_Flags.more_components;\r
+\r
+    while (flags & TFKC_Flags.more_components) != ~~ 0 {\r
+        arg1, arg2 : i16;\r
+\r
+        flags = ~~ read_u16(^reader);\r
+\r
+        component.glyph_index = read_u16(^reader);\r
+\r
+        if (flags & TFKC_Flags.arg_1_and_2_are_words) != ~~ 0 {\r
+            arg1 = read_i16(^reader);\r
+            arg2 = read_i16(^reader);\r
+        } else {\r
+            arg1 = cast(i16) read_u8(^reader);\r
+            arg2 = cast(i16) read_u8(^reader);\r
+        }\r
+\r
+        if (flags & TFKC_Flags.args_are_xy_values) != ~~ 0 {\r
+            component.matrix[4] = ~~ cast(i32) arg1;\r
+            component.matrix[5] = ~~ cast(i32) arg2;\r
+        } else {\r
+            component.dest_point_index = arg1;\r
+            component.src_point_index  = arg2;\r
+        }\r
+\r
+        if (flags & TFKC_Flags.we_have_a_scale) != ~~ 0 {\r
+            component.matrix[0] = read_2dot14(^reader);\r
+            component.matrix[3] = component.matrix[0];\r
+        }\r
+        elseif (flags & TFKC_Flags.we_have_an_x_and_y_scale) != ~~ 0 {\r
+            component.matrix[0] = read_2dot14(^reader);\r
+            component.matrix[3] = read_2dot14(^reader);\r
+        }\r
+        elseif (flags & TFKC_Flags.we_have_a_two_by_two) != ~~ 0 {\r
+            component.matrix[0] = read_2dot14(^reader);\r
+            component.matrix[2] = read_2dot14(^reader);\r
+            component.matrix[3] = read_2dot14(^reader);\r
+            component.matrix[4] = read_2dot14(^reader);\r
+        }\r
+\r
+        old_pos := tell(^reader);\r
+\r
+        simple_glyph := ttf_read_glyph(ttf, ~~ component.glyph_index, ttf.allocator);\r
+        if simple_glyph.valid_glyph {\r
+            point_offset := glyph.points.count;\r
+            for i: simple_glyph.contour_ends.count {\r
+                array.push(^glyph.contour_ends, cast(u16) (simple_glyph.contour_ends[i] + ~~ point_offset));\r
+            }\r
+\r
+            for p: simple_glyph.points {\r
+                px := cast(f32) cast(i32) p.x;\r
+                py := cast(f32) cast(i32) p.y;\r
+\r
+                x := component.matrix[0] * px + component.matrix[1] * py + component.matrix[4];\r
+                y := component.matrix[2] * px + component.matrix[3] * py + component.matrix[5];\r
+\r
+                array.push(^glyph.points, .{\r
+                    x = ~~ cast(i32) x,\r
+                    y = ~~ cast(i32) y,\r
+                    on_curve = p.on_curve,\r
+                });\r
+            }\r
+        }\r
+\r
+        seek(^reader, old_pos);\r
+    }\r
+\r
+    glyph.contour_count = cast(i16) glyph.contour_ends.count;\r
+\r
+    if (flags & TFKC_Flags.we_have_instructions) != ~~ 0 {\r
+        seek(^reader, tell(^reader) + ~~ read_u16(^reader));\r
+    }\r
+\r
+    glyph.valid_glyph = true;\r
+}\r
+\r
+ttf_glyph_count :: (use ttf: ^TrueTypeFont) -> u32 {\r
+    use package core.io.binary\r
+\r
+    maxp_table_info  := map.get(^table_map, "maxp");\r
+    old := seek(^reader, maxp_table_info.offset + 4);\r
+    defer seek(^reader, old);\r
+\r
+    return ~~read_u16(^reader);\r
+}\r
+\r
+ttf_read_cmap_table :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    cmap_table_info  := map.get(^table_map, "cmap");\r
+    seek(^reader, cmap_table_info.offset);\r
+\r
+    version := read_u16(^reader);\r
+    num_subtables := read_u16(^reader);\r
+\r
+    for i: 0 .. ~~num_subtables {\r
+        platform_id := read_u16(^reader);\r
+        platform_specific_id := read_u16(^reader);\r
+        offset := read_u32(^reader);\r
+\r
+        // Microsoft Unicode, BMP only\r
+        if platform_id == 3 && platform_specific_id <= 1 {\r
+            ttf_read_cmap(ttf, offset + cmap_table_info.offset);\r
+        }\r
+    }\r
+}\r
+\r
+TTCmapFormat :: enum (u16) {\r
+    Simple    :: 0x00;\r
+    Segmented :: 0x04;\r
+}\r
+\r
+TTCmapBase  :: struct { format : TTCmapFormat; }\r
+TTCmap0 :: struct {\r
+    use base: TTCmapBase;\r
+\r
+    glyph_indicies: [] u8;\r
+}\r
+TTCmap4 :: struct {\r
+    use base: TTCmapBase;\r
+\r
+    seg_count      : u16;\r
+    search_range   : u16;\r
+    entry_selector : u16;\r
+    range_shift    : u16;\r
+\r
+    segments : [..] TTSegment;\r
+    cache    : map.Map(i32, i32);\r
+}\r
+\r
+TTSegment :: struct {\r
+    start_code      : u16;\r
+    end_code        : u16;\r
+    id_delta        : u16;\r
+    id_range_offset : u16;\r
+}\r
+\r
+TTCmap :: struct #union {\r
+    use base: TTCmapBase;\r
+    cmap0: TTCmap0;\r
+    cmap4: TTCmap4;\r
+}\r
+\r
+ttf_read_cmap :: (use ttf: ^TrueTypeFont, offset: u32) {\r
+    use package core.io.binary\r
+\r
+    old := seek(^reader, offset);\r
+    defer seek(^reader, old);\r
+\r
+    format := read_u16(^reader);\r
+    length := read_u16(^reader);\r
+    lang   := read_u16(^reader);\r
+\r
+    switch cast(i32) format {\r
+        case 0 do ttf_read_cmap0(ttf);\r
+        case 4 do ttf_read_cmap4(ttf);\r
+\r
+        case #default { printf("Unsupported cmap format: %d\n", cast(i32) format); }\r
+    }\r
+}\r
+\r
+ttf_read_cmap0 :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    cmap : TTCmap;\r
+    cmap.cmap0.format = TTCmapFormat.Simple;\r
+\r
+    glyphs : [..] u8;\r
+    array.init(^glyphs, 256);\r
+    for i: 0 .. 256 do array.push(^glyphs, read_u8(^reader));\r
+\r
+    cmap.cmap0.glyph_indicies = array.to_slice(^glyphs);\r
+\r
+    array.push(^char_maps, cmap);\r
+}\r
+\r
+ttf_read_cmap4 :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    cmap : TTCmap;\r
+    cmap.cmap4.format = TTCmapFormat.Segmented;\r
+    imap := ^cmap.cmap4;\r
+    map.init(^imap.cache);\r
+\r
+    imap.seg_count = read_u16(^reader) >> 1;\r
+    imap.search_range = read_u16(^reader);\r
+    imap.entry_selector = read_u16(^reader);\r
+    imap.range_shift = read_u16(^reader);\r
+\r
+    array.init(^imap.segments, ~~imap.seg_count);\r
+    imap.segments.count = cast(u32) imap.seg_count;\r
+\r
+    for ^seg: imap.segments do seg.end_code        = read_u16(^reader);\r
+    read_u16(^reader); // Reserved and unused\r
+    for ^seg: imap.segments do seg.start_code      = read_u16(^reader);\r
+    for ^seg: imap.segments do seg.id_delta        = read_u16(^reader);\r
+    for ^seg: imap.segments {\r
+        seg.id_range_offset = read_u16(^reader);\r
+        if seg.id_range_offset != 0 do seg.id_range_offset += ~~(tell(^reader) - 2);\r
+    }\r
+\r
+    array.push(^char_maps, cmap);\r
+}\r
+\r
+ttf_lookup_glyph_by_char :: (use ttf: ^TrueTypeFont, charcode: u32) -> u32 {\r
+    potential_code := 0;\r
+\r
+    for ^cmap: char_maps {\r
+        switch cmap.format {\r
+            case TTCmapFormat.Simple    do potential_code = ttf_lookup_in_cmap0(ttf, ~~cmap, charcode);\r
+            case TTCmapFormat.Segmented do potential_code = ttf_lookup_in_cmap4(ttf, ~~cmap, charcode);\r
+        }\r
+\r
+        if potential_code != 0 do return potential_code;\r
+    }\r
+\r
+    return potential_code;\r
+}\r
+\r
+#private_file\r
+ttf_lookup_in_cmap0 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap0, charcode: u32) -> u32 {\r
+    if charcode < 0 || charcode >= 256 do return 0;\r
+    return ~~cmap.glyph_indicies[charcode];\r
+}\r
+\r
+#private_file\r
+ttf_lookup_in_cmap4 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap4, charcode: u32) -> u32 {\r
+    use package core.io.binary\r
+\r
+    if map.has(^cmap.cache, charcode) do return map.get(^cmap.cache, charcode);\r
+\r
+    index := 0;\r
+    for ^seg: cmap.segments {\r
+        if ~~seg.start_code <= charcode && ~~charcode <= seg.end_code {\r
+            if seg.id_range_offset != 0 {\r
+                glyph_index_address := ~~seg.id_range_offset + 2 * (charcode - ~~seg.start_code);\r
+                seek(^reader, glyph_index_address);\r
+                index = cast(u32) read_u16(^reader);\r
+            } else {\r
+                index = (~~seg.id_delta + charcode) & 0xffff;\r
+            }\r
+\r
+            break;\r
+        }\r
+    }\r
+\r
+    map.put(^cmap.cache, charcode, index);\r
+\r
+    return index;\r
+}\r
+\r
+ttf_read_hhea_table :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+\r
+    hhea_table_info  := map.get(^table_map, "hhea");\r
+    seek(^reader, hhea_table_info.offset);\r
+\r
+    hhea.version = read_u32(^reader);\r
+    hhea.ascent  = read_fword(^reader);\r
+    hhea.descent = read_fword(^reader);\r
+    hhea.line_gap = read_fword(^reader);\r
+    hhea.advance_width_max = read_u16(^reader);\r
+    hhea.min_left_side_bearing = read_u16(^reader);\r
+    hhea.min_right_side_bearing = read_u16(^reader);\r
+    hhea.x_max_extent = read_fword(^reader);\r
+    hhea.caret_slope_rise = read_i16(^reader);\r
+    hhea.caret_slope_run = read_i16(^reader);\r
+    hhea.caret_offset = read_fword(^reader);\r
+    read_i16(^reader); // Reserved\r
+    read_i16(^reader); // Reserved\r
+    read_i16(^reader); // Reserved\r
+    read_i16(^reader); // Reserved\r
+    hhea.metric_data_format = read_i16(^reader);\r
+    hhea.num_of_long_hor_metrics = read_u16(^reader);\r
+}\r
+\r
+TTKern0Table :: struct {\r
+    swap      : bool;\r
+    offset    : u32;\r
+    n_pairs   : i32;\r
+    kmap      : map.Map(u32, i16);\r
+    old_index : i32 = -1;\r
+\r
+    reset :: (use kern: ^TTKern0Table) {\r
+        old_index = -1;\r
+    }\r
+\r
+    get :: (use kern: ^TTKern0Table, glyph_index: i32) -> (i32, i32) {\r
+        x := 0;\r
+\r
+        if old_index >= 0 {\r
+            ch := ((cast(u32) old_index & 0xFFFF) << 16) | (cast(u32) glyph_index & 0xFFFF);\r
+            if map.has(^kmap, ch) {\r
+                x = cast(i32) map.get(^kmap, ch);\r
+            }\r
+        }\r
+        \r
+        old_index = glyph_index;\r
+        if swap do return 0, x;\r
+        else    do return x, 0;\r
+    }\r
+}\r
+\r
+ttf_read_kern_table :: (use ttf: ^TrueTypeFont) {\r
+    use package core.io.binary\r
+    if !map.has(^table_map, "kern") do return;\r
+\r
+    kern_table_info := map.get(^table_map, "kern");\r
+    seek(^reader, kern_table_info.offset);\r
+\r
+    version := read_u16(^reader);\r
+    assert(version == 0, "Expected kern table version to be 0.");\r
+    n_tables := cast(u32) read_u16(^reader);\r
+\r
+    for _: n_tables {\r
+        sub_table_version := cast(u32) read_u16(^reader);\r
+        length            := cast(u32) read_u16(^reader);\r
+        coverage          := cast(u32) read_u16(^reader);\r
+        format            := coverage >> 8;\r
+        cross             := coverage & 4;\r
+        vertical          := (coverage & 1) == 0;\r
+\r
+        if format == 0 {\r
+            kern_table := ttf_read_kern0(ttf, vertical, cross != 0);\r
+            array.push(^kern, kern_table);\r
+\r
+        } else {\r
+            // Unknown format\r
+            seek(^reader, tell(^reader) + length);\r
+        }\r
+    }\r
+}\r
+\r
+ttf_read_kern0 :: (use ttf: ^TrueTypeFont, vertical: bool, cross: bool) -> TTKern0Table {\r
+    use package core.io.binary\r
+    use package core.intrinsics.onyx { __zero_value }\r
+\r
+    offset := tell(^reader);\r
+    n_pairs := cast(i32) read_u16(^reader);\r
+    search_range := cast(i32) read_u16(^reader);\r
+    entry_selector := cast(i32) read_u16(^reader);\r
+    range_shift := cast(i32) read_u16(^reader);\r
+\r
+    kt := TTKern0Table.{\r
+        swap    = (vertical && !cross) || (!vertical && cross),\r
+        offset  = offset,\r
+        n_pairs = n_pairs,\r
+        kmap    = __zero_value(#type map.Map(u32, i16))\r
+    };\r
+\r
+    for _: n_pairs {\r
+        left  := cast(i32) read_u16(^reader);\r
+        right := cast(i32) read_u16(^reader);\r
+        value := read_fword(^reader);\r
+        tmp_index := (left << 16) | right;\r
+        map.put(^kt.kmap, tmp_index, value);\r
+    }\r
+\r
+    return kt;\r
+}\r
+\r
+ttf_next_kern :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> (i32, i32) {\r
+    // BUG: I cannot do this here because x and y do not\r
+    // automatically become a 'i32' type.\r
+    // x, y := 0, 0;\r
+    x := 0;\r
+    y := 0;\r
+    for i: 0 .. kern.count {\r
+        tmp_x, tmp_y := kern[i]->get(glyph_index);\r
+        x += tmp_x;\r
+        y += tmp_y;\r
+    }\r
+    return x, y;\r
+}\r
+\r
+TTHorizontalMetrics :: struct {\r
+    advance_width     : u16;\r
+    left_side_bearing : i16;\r
+}\r
+\r
+ttf_lookup_horizontal_metrics :: (use ttf: ^TrueTypeFont, glyph_index: u32) -> TTHorizontalMetrics {\r
+    use package core.io.binary\r
+\r
+    hmtx_table_info  := map.get(^table_map, "hmtx");\r
+    offset := hmtx_table_info.offset;\r
+\r
+    hmtx : TTHorizontalMetrics;\r
+\r
+    nmets := cast(u32) hhea.num_of_long_hor_metrics;\r
+\r
+    if glyph_index < nmets {\r
+        offset += glyph_index * 4;\r
+        old := seek(^reader, offset);\r
+        defer seek(^reader, old);\r
+\r
+        hmtx.advance_width = read_u16(^reader);\r
+        hmtx.left_side_bearing = read_i16(^reader);\r
+\r
+    } else {\r
+        old := seek(^reader, offset + (nmets - 1) * 4);\r
+        defer seek(^reader, old);\r
+\r
+        hmtx.advance_width = read_u16(^reader);\r
+        seek(^reader, offset + nmets * 4 + 2 * (glyph_index - nmets));\r
+        hmtx.left_side_bearing = read_i16(^reader);\r
+    }\r
+\r
+    return hmtx;\r
+}\r
+\r
+\r
+#private_file\r
+ttf_calc_table_checksum :: (reader: ^io.binary.BinaryReader, offset: u32, length: u32) -> u32 {\r
+    use package core.io.binary\r
+\r
+    old := seek(reader, offset);\r
+    defer seek(reader, old);\r
+\r
+    sum := 0;\r
+    nlongs := (length + 3) >> 2;\r
+    for i: 0 .. nlongs do sum += read_u32(reader);\r
+    return sum;\r
+}\r
+\r
diff --git a/site/index.html b/site/index.html
new file mode 100644 (file)
index 0000000..427c477
--- /dev/null
@@ -0,0 +1,25 @@
+<html>
+    <head>
+        <title>IMGUI</title>
+
+        <style>
+        html, body, canvas {
+            margin: 0;
+            padding: 0;
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+        }
+        </style>
+
+        <script type="application/onyx" src="sand_toy.wasm"></script>
+        <script type="text/javascript" src="js/webgl2.js"></script>
+        <script type="text/javascript" src="js/js_events.js"></script>
+        <script type="text/javascript" src="js/game.js"></script>
+        <script type="text/javascript" src="js/onyx-loader.js"></script>
+    </head>
+
+    <body>
+        <canvas id="simulation-canvas">This browser does not support the Canvas API.</canvas>
+    </body>
+</html>
diff --git a/site/js/game.js b/site/js/game.js
new file mode 100644 (file)
index 0000000..122d816
--- /dev/null
@@ -0,0 +1,15 @@
+\r
+window.ONYX_MODULES = window.ONYX_MODULES || [];\r
+\r
+window.ONYX_MODULES.push({\r
+    module_name: "game",\r
+\r
+    start_loop: function() {\r
+        var loop = function(dt) {\r
+            window.ONYX_INSTANCE.exports.loop();\r
+            window.requestAnimationFrame(loop);\r
+        };\r
+\r
+        window.requestAnimationFrame(loop);\r
+    },\r
+});\r
diff --git a/site/js/js_events.js b/site/js/js_events.js
new file mode 100644 (file)
index 0000000..6d93929
--- /dev/null
@@ -0,0 +1,67 @@
+window.ONYX_MODULES = window.ONYX_MODULES || [];\r
+\r
+function push_event_to_buffer(esp, event_size, event_kind, data) {\r
+    let WASM_U32 = new Uint32Array(ONYX_MEMORY.buffer);\r
+\r
+    if (WASM_U32[esp] >= WASM_U32[esp + 1]) {\r
+        console.log("Event buffer full!");\r
+        return;\r
+    }\r
+\r
+    WASM_U32[esp] += 1;\r
+\r
+    let event_idx = esp + (WASM_U32[esp] - 1) * (event_size / 4) + 2;\r
+    WASM_U32[event_idx] = event_kind;\r
+    WASM_U32[event_idx + 1] = Date.now();\r
+\r
+    for (let i = 0; i < data.length; i++) {\r
+        WASM_U32[event_idx + 2 + i] = data[i];\r
+    }\r
+}\r
+\r
+window.ONYX_MODULES.push({\r
+    module_name: "js_events",\r
+\r
+    setup: function(esp, event_size) {\r
+        // Indicies into a Uint32Array are not based on bytes,\r
+        // but on the index.\r
+        esp /= 4;\r
+\r
+        document.addEventListener("keydown", function (ev) {\r
+            if (ev.isComposing || ev.keyCode === 229) return;\r
+            push_event_to_buffer(esp, event_size, 0x04, [ ev.keyCode ]);\r
+        });\r
+\r
+        document.addEventListener("keyup", function (ev) {\r
+            if (ev.isComposing || ev.keyCode === 229) return;\r
+            push_event_to_buffer(esp, event_size, 0x05, [ ev.keyCode ]);\r
+        });\r
+\r
+        document.addEventListener("mousedown", function (ev) {\r
+            push_event_to_buffer(esp, event_size, 0x01, [ ev.clientX, ev.clientY, ev.button ]);\r
+        });\r
+\r
+        document.addEventListener("mouseup", function (ev) {\r
+            push_event_to_buffer(esp, event_size, 0x02, [ ev.clientX, ev.clientY, ev.button ]);\r
+        });\r
+\r
+        document.addEventListener("mousemove", function (ev) {\r
+            push_event_to_buffer(esp, event_size, 0x03, [ ev.clientX, ev.clientY, -1 ]);\r
+        });\r
+\r
+        document.addEventListener("wheel", function (ev) {\r
+            push_event_to_buffer(esp, event_size, 0x07, [ ev.clientX, ev.clientY, ev.deltaY >= 0 ? 0x04 : 0x03 ]);\r
+        });\r
+\r
+        window.addEventListener("resize", function (ev) {\r
+            push_event_to_buffer(esp, event_size, 0x06, [ window.innerWidth, window.innerHeight ]);\r
+        });\r
+\r
+        push_event_to_buffer(esp, event_size, 0x06, [ window.innerWidth, window.innerHeight ]);\r
+\r
+        document.oncontextmenu = (e) => {\r
+            e.preventDefault = true;\r
+            return false;\r
+        };\r
+    },\r
+});\r
diff --git a/site/js/onyx-loader.js b/site/js/onyx-loader.js
new file mode 100644 (file)
index 0000000..6e643a8
--- /dev/null
@@ -0,0 +1,47 @@
+\r
+window.ONYX_MODULES  = window.ONYX_MODULES || [];\r
+window.ONYX_MEMORY   = null;\r
+window.ONYX_INSTANCE = null;\r
+\r
+window.ONYX_MODULES.push({\r
+    module_name: "host",\r
+\r
+    print_str: function(ptr, len) {\r
+        var buffer = new Uint8Array(ONYX_MEMORY.buffer, ptr, len);\r
+        var string = new TextDecoder().decode(buffer);\r
+        console.log(string);\r
+    },\r
+\r
+    exit: function() { debugger; }\r
+});\r
+\r
+function launch_onyx_program(script_path, call_start) {\r
+    fetch(script_path)\r
+    .then(function(res) { return res.arrayBuffer(); })\r
+    .then(function(wasm_code) {\r
+        var import_object = {};\r
+\r
+        for (var i = 0; i < window.ONYX_MODULES.length; i++) {\r
+            import_object[window.ONYX_MODULES[i].module_name] = window.ONYX_MODULES[i];\r
+        }\r
+\r
+        return WebAssembly.instantiate(wasm_code, import_object);\r
+    })\r
+    .then(function(wasm_module) {\r
+        window.ONYX_MEMORY = wasm_module.instance.exports.memory;\r
+        window.ONYX_INSTANCE = wasm_module.instance;\r
+\r
+        wasm_module.instance.exports._start();\r
+    });\r
+}\r
+\r
+window.onload = function() {\r
+    var script_tags = document.getElementsByTagName("script");\r
+\r
+    for (var i = 0; i < script_tags.length; i++) {\r
+        if (script_tags[i].getAttribute("type") == "application/onyx") {\r
+            // @ROBUSTNESS: It should be configurable which function is called on start up of a Onyx WASM module.\r
+            launch_onyx_program(script_tags[i].getAttribute("src"), true);\r
+        }\r
+    }\r
+};\r
diff --git a/site/js/webgl2.js b/site/js/webgl2.js
new file mode 100644 (file)
index 0000000..61d565a
--- /dev/null
@@ -0,0 +1,291 @@
+window.ONYX_MODULES = window.ONYX_MODULES || [];
+
+var programs = [];
+var shaders = [];
+var buffers = [];
+var framebuffers = [];
+var renderbuffers = [];
+var textures = [];
+var uniformlocs = [];
+var vertexArrays = [];
+var canvas = null;
+var gl = null;
+
+window.ONYX_MODULES.push({
+    module_name: "gl",
+
+    init(name, namelen) {
+        const decoder = new TextDecoder();
+        const str = new Uint8Array(window.ONYX_MEMORY.buffer, name, namelen);
+        const canvasname = decoder.decode(str);
+
+        canvas = document.getElementById(canvasname);
+        if (canvas == null) return 0;
+
+        gl = canvas.getContext("webgl2");
+        if (gl == null) return 0;
+
+        return 1;
+    },
+
+    activeTexture(texture) { gl.activeTexture(texture); },
+    attachShader(program, shader) { gl.attachShader(programs[program], shaders[shader]); return programs[program]; },
+    bindAttribLocation(program, index, name, namelen) { console.log("NOT IMPLEMENTED!"); },
+    bindBuffer(target, buffer) {
+        if (buffer == -1) {
+            gl.bindBuffer(target, null);
+        } else {
+            gl.bindBuffer(target, buffers[buffer]);
+        }
+    },
+    bindFramebuffer(target, framebuffer) { gl.bindFramebuffer(target, framebuffers[framebuffer]); },
+    bindRenderbuffer(target, renderbuffer) { gl.bindRenderbuffer(target, renderbuffers[renderbuffer]); },
+    bindTexture(target, texture) { gl.bindTexture(target, textures[texture]); },
+    bindVertexArray(vertexArray) { gl.bindVertexArray(vertexArrays[vertexArray]); },
+
+    blendColor(red, green, blue, alpha) { gl.blendColor(red, green, blue, alpha); },
+    blendEquation(mode) { gl.blendEquation(mode); },
+    blendEquationSeparate(modeRGB, modeAlpha) { gl.blendEquationSeparate(modeRGB, modeAlpha); },
+    blendFunc(sfactor, dfactor) { gl.blendFunc(sfactor, dfactor); },
+    blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha) { gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); },
+
+    blitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, mask, filter) {
+        gl.blitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, mask, filter);
+    },
+
+    bufferDataWithData(target, bufferdata, bufferlen, usage) {
+        const data = new DataView(window.ONYX_MEMORY.buffer, bufferdata, bufferlen);
+        gl.bufferData(target, data, usage);
+    },
+
+    bufferDataNoData(target, size, usage) { gl.bufferData(target, size, usage); },
+    bufferSubData(target, offset, bufferdata, bufferlen) {
+        const data = new DataView(window.ONYX_MEMORY.buffer, bufferdata, bufferlen);
+        gl.bufferSubData(target, offset, data);
+    },
+    canvasSize(width, height) {
+        canvas.width = width;
+        canvas.height = height;
+    },
+    checkFrameBufferStatus(target) { return gl.checkFrameBufferStatus(target); },
+    clear(bit) { gl.clear(bit); },
+    clearColor(r, g, b, a) { gl.clearColor(r, g, b, a); },
+    clearDepth(depth) { gl.clearDepth(depth); },
+    clearStencil(stencil) { gl.clearStencil(stencil); },
+    colorMask(r, g, b, a) { gl.colorMask(r, g, b, a); },
+    compileShader(shader) { gl.compileShader(shaders[shader]); },
+    compressedTexImage2D(target, level, internalformat, width, height, border, data, datalen) {
+        const pixels = new DataView(window.ONYX_MEMORY.buffer, data, datalen);
+        gl.compressedTexImage2D(target, level, internalformat, width, height, border, pixels);
+    },
+    compressedTexSubImage2D(target, level, internalformat, xoff, yoff, width, height, format, data, datalen) {
+        const pixels = new DataView(window.ONYX_MEMORY.buffer, data, datalen);
+        gl.compressedSubTexImage2D(target, level, internalformat, xoff, yoff, width, height, format, pixels);
+    },
+    copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size) { gl.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); },
+    copyTexImage2D(target, level, internalforamt, x, y, width, height, border) {
+        gl.copyTexImage2D(target, level, internalforamt, x, y, width, height, border);
+    },
+    copyTexSubImage2D(target, level, xoff, yoff, x, y, width, height) {
+        gl.copyTexSubImage2D(target, level, xoff, yoff, x, y, width, height);
+    },
+    createBuffer() {
+        const buf = gl.createBuffer();
+        if (buf == null) return -1;
+
+        buffers.push(buf);
+        return buffers.length - 1;
+    },
+    createFramebuffer() {
+        const buf = gl.createFramebuffer();
+        if (buf == null) return -1;
+
+        framebuffers.push(buf);
+        return framebuffers.length - 1;
+    },
+    createProgram() {
+        const prog = gl.createProgram();
+        if (prog == null) return -1;
+
+        programs.push(prog);
+        return programs.length - 1;
+    },
+    createRenderbuffer() {
+        const buf = gl.createRenderbuffer();
+        if (buf == null) return -1;
+
+        renderbuffers.push(buf);
+        return renderbuffers.length - 1;
+    },
+    createShader(type) {
+        const shader = gl.createShader(type);
+        if (shader == null) return -1;
+
+        shaders.push(shader);
+        return shaders.length - 1;
+    },
+    createTexture() {
+        const texture = gl.createTexture();
+        if (texture == null) return -1;
+
+        textures.push(texture);
+        return textures.length - 1;
+    },
+    createVertexArray() {
+        const vao = gl.createVertexArray();
+        if (vao == null) return -1;
+
+        vertexArrays.push(vao);
+        return vertexArrays.length - 1;
+    },
+    cullFace(mode) { gl.cullFace(mode); },
+    deleteBuffer(buffer) { gl.deleteBuffer(buffers[buffer]); },
+    deleteFramebuffer(framebuffer) { gl.deleteFramebuffer(framebuffers[framebuffer]); },
+    deleteProgram(program) { gl.deleteProgram(programs[program]); },
+    deleteRenderbuffer(renderbuffer) { gl.deleteRenderbuffer(renderbuffers[renderbuffer]); },
+    deleteShader(shader) { gl.deleteShader(shaders[shader]); },
+    deleteTexture(texture) { gl.deleteTexture(textures[texture]); },
+    deleteVertexArray(vertexArray) { gl.deleteVertexArray(vertexArrays[vertexArray]); },
+    depthFunc(func) { gl.depthFunc(func); },
+    depthMask(flag) { gl.depthMask(flag); },
+    depthRange(znear, zfar) { gl.depthRange(znear, zfar); },
+    detachShader(program, shader) { gl.detachShader(programs[program], shaders[shader]); },
+    disable(cap) { gl.disable(cap); },
+    disableVertexAttribArray(index) { gl.disableVertexAttribArray(index); },
+    drawArrays(mode, first, count) { gl.drawArrays(mode, first, count); },
+    drawArraysInstanced(mode, first, count, instanceCount) { gl.drawArraysInstanced(mode, first, count, instanceCount); },
+    drawElements(mode, count, type, offset) { gl.drawElements(mode, count, type, offset); },
+    drawElementsInstanced(mode, count, type, offset, instanceCount) { gl.drawElementsInstanced(mode, count, type, offset, instanceCount); },
+    enable(cap) { gl.enable(cap); },
+    enableVertexAttribArray(index) { gl.enableVertexAttribArray(index); },
+    finish() { gl.finish(); },
+    flush() { gl.flush(); },
+    framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer) {
+        gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffers[renderbuffer]);
+    },
+    framebufferTexture2D(target, attachment, texttarget, texture, level) {
+        gl.framebufferTexture2D(target, attachment, texttarget, textures[texture], level);
+    },
+    framebufferTextureLayer(target, attachment, texture, level, layer) { gl.framebufferTextureLayer(target, attachment, textures[texture], level, layer); },
+    frontFace(mode) { gl.frontFace(mode); },
+    generateMipmap(target) { gl.generateMipmap(target); },
+    getActiveAttrib(program, index, out) {
+        const loc = gl.getActiveAttrib(programs[program], index);
+        const data = new Int32Array(window.ONYX_MEMORY.buffer, out, 2);
+        data[0] = loc.size;
+        data[1] = loc.type;
+    },
+    getActiveUniform(program, index, out) {
+        const loc = gl.getActiveUniform(programs[program], index);
+        const data = new Int32Array(window.ONYX_MEMORY.buffer, out, 2);
+        data[0] = loc.size;
+        data[1] = loc.type;
+    },
+    // getAttachedShaders() { console.log("NOT IMPLEMENTED!"); },
+    getAttribLocation(program, name, namelen) {
+        const decoder = new TextDecoder();
+        const str = new Uint8Array(window.ONYX_MEMORY.buffer, name, namelen);
+        const attribname = decoder.decode(str);
+
+        return gl.getAttribLocation(programs[program], attribname);
+    },
+    // getBufferParameter() { console.log("NOT IMPLEMENTED!"); },
+    getBufferSubData(target, srcbyteoffset, dstbufferdata, dstbufferlen, dstoffset, length) {
+        const dst = new DataView(window.ONYX_MEMORY.buffer, dstbufferdata, dstbufferlen);
+        gl.getBufferSubData(target, srcbyteoffset, dst, dstoffset, length);
+    },
+    getError() { return gl.getError(); },
+    getInternalformatParameter(target, internalformat, pname) { return gl.getInternalformatParameter(target, internalformat, pname); },
+    // many of the 'gets() { console.log("NOT IMPLEMENTED!"); },
+    getShaderParameter(shader, param) { return gl.getShaderParameter(shaders[shader], param); },
+    getProgramParameter(program, param) { return gl.getProgramParameter(programs[program], param); },
+    getUniformLocation(program, name, namelen) {
+        const decoder = new TextDecoder();
+        const str = new Int8Array(window.ONYX_MEMORY.buffer, name, namelen);
+        const uniname = decoder.decode(str);
+
+        uniformlocs.push(gl.getUniformLocation(programs[program], uniname));
+        return uniformlocs.length - 1;
+    },
+    getVertexAttribOffset(index, pname) { return gl.getVertexAttribOffset(index, pname); },
+    hint(target, mode) { gl.hint(target, mode); },
+    isEnabled(cap) { return gl.isEnabled(cap); },
+    invalidateFramebuffer(target, attachdata, attachlen) {
+        const attachments = new Int32Array(window.ONYX_MEMORY.buffer, attachdata, attachlen);
+        gl.invalidateFramebuffer(target, attacements);
+    },
+    invalidateSubFramebuffer(target, attachdata, attachlen, x, y, width, height) {
+        const attachments = new Int32Array(window.ONYX_MEMORY.buffer, attachdata, attachlen);
+        gl.invalidateFramebuffer(target, attacements, x, y, width, height);
+    },
+    lineWidth(width) { gl.lineWidth(width); },
+    linkProgram(program) { gl.linkProgram(programs[program]); },
+    pixelStorei(pname, param) { gl.pixelStorei(pname, param); },
+    polygonOffset(factor, units) { gl.polygonOffset(factor, units); },
+    printProgramInfoLog(program) { console.log(gl.getProgramInfoLog(programs[program])); },
+    printShaderInfoLog(shader) { console.log(gl.getShaderInfoLog(shaders[shader])); },
+    readPixels(x, y, width, height, format, type, pixels, pixelslen) {
+        const pixeldata = new Uint8Array(window.ONYX_MEMORY.buffer, pixels, pixelslen);
+        gl.readPixels(x, y, width, height, format, type, pixeldata);
+    },
+    readBuffer(src) { gl.readBuffer(src); },
+    renderbufferStorageMultisample(target, samples, internalforamt, width, height) {
+        gl.renderbufferStorageMultisample(target, samples, internalforamt, width, height);
+    },
+    sampleCoverage(value, invert) { gl.sampleCoverage(value, invert); },
+    scissor(x, y, width, height) { gl.scissor(x, y, width, height); },
+    setSize(width, height) { canvas.width = width; canvas.height = height; },
+    shaderSource(shader, source, sourcelen) {
+        const decoder = new TextDecoder();
+        const str = new Int8Array(window.ONYX_MEMORY.buffer, source, sourcelen);
+        const sourcedata = decoder.decode(str);
+
+        gl.shaderSource(shaders[shader], sourcedata);
+    },
+    stencilFunc(func, ref, mask) { gl.stencilFunc(func, ref, mask); },
+    stencilFuncSeparate(face, func, ref, mask) { gl.stencilFuncSeparate(face, func, ref, mask); },
+    stencilMask(mask) { gl.stencilMask(mask); },
+    stencilMaskSeparate(face, mask) { gl.stencilMaskSeparate(face, mask); },
+    stencilOp(fail, zfail, mask) { gl.stencilOp(fail, zfail, mask); },
+    stencilOpSeparate(face, fail, zfail, zpass) { gl.stencilOpSeparate(face, fail, zfail, zpass); },
+    texImage2D(target, level, internalforamt, width, height, border, format, type, pixels, pixelslen) {
+        const data = new DataView(window.ONYX_MEMORY.buffer, pixels, pixelslen);
+        gl.texImage2D(target, level, internalforamt, width, height, border, format, type, data);
+    },
+    texParameterf(target, pname, param) { gl.texParameterf(target, pname, param); },
+    texParameteri(target, pname, param) { gl.texParameteri(target, pname, param); },
+    texSubImage2D(target, level, xoff, yoff, width, height, format, type, pixels, pixelslen) {
+        const data = new Uint8Array(window.ONYX_MEMORY.buffer, pixels, pixelslen);
+        gl.texSubImage2D(target, level, xoff, yoff, width, height, format, type, data);
+    },
+    uniform1f(loc, x) { gl.uniform1f(uniformlocs[loc], x); },
+    uniform1i(loc, x) { gl.uniform1i(uniformlocs[loc], x); },
+    uniform2f(loc, x, y) { gl.uniform2f(uniformlocs[loc], x, y); },
+    uniform2i(loc, x, y) { gl.uniform2i(uniformlocs[loc], x, y); },
+    uniform3f(loc, x, y, z) { gl.uniform3f(uniformlocs[loc], x, y, z); },
+    uniform3i(loc, x, y, z) { gl.uniform3i(uniformlocs[loc], x, y, z); },
+    uniform4f(loc, x, y, z, w) { gl.uniform4f(uniformlocs[loc], x, y, z, w); },
+    uniform4i(loc, x, y, z, w) { gl.uniform4i(uniformlocs[loc], x, y, z, w); },
+    uniformMatrix2(loc, transpose, valueptr) {
+        const data = new Float32Array(window.ONYX_MEMORY.buffer, valueptr, 4);
+        gl.uniformMatrix2fv(uniformlocs[loc], transpose, data);
+    },
+    uniformMatrix3(loc, transpose, valueptr) {
+        const data = new Float32Array(window.ONYX_MEMORY.buffer, valueptr, 9);
+        gl.uniformMatrix3fv(uniformlocs[loc], transpose, data);
+    },
+    uniformMatrix4(loc, transpose, valueptr) {
+        const data = new Float32Array(window.ONYX_MEMORY.buffer, valueptr, 16);
+        gl.uniformMatrix4fv(uniformlocs[loc], transpose, data);
+    },
+    useProgram(program) { gl.useProgram(programs[program]); },
+    validateProgram(program) { gl.validateProgram(program[program]); },
+    vertexAttrib1f(idx, x) { gl.vertexAttrib1f(idx, x); },
+    vertexAttrib2f(idx, x, y) { gl.vertexAttrib2f(idx, x, y); },
+    vertexAttrib3f(idx, x, y, z) { gl.vertexAttrib3f(idx, x, y, z); },
+    vertexAttrib4f(idx, x, y, z, w) { gl.vertexAttrib4f(idx, x, y, z, w); },
+    vertexAttribIPointer(idx, size, type, stride, offset) { gl.vertexAttribIPointer(idx, size, type, stride, offset); },
+    vertexAttribPointer(idx, size, type, normalized, stride, offset) { gl.vertexAttribPointer(idx, size, type, normalized, stride, offset); },
+    vertexAttribDivisor(idx, divisor) { gl.vertexAttribDivisor(idx, divisor); },
+    viewport(x, y, width, height) { gl.viewport(x, y, width, height); },
+});
diff --git a/site/sand_toy.wasm b/site/sand_toy.wasm
new file mode 100644 (file)
index 0000000..ed634df
Binary files /dev/null and b/site/sand_toy.wasm differ
diff --git a/src/build.onyx b/src/build.onyx
new file mode 100644 (file)
index 0000000..76f467b
--- /dev/null
@@ -0,0 +1,8 @@
+#load "core/std"
+
+#load "modules/webgl2/module"
+#load "modules/js_events/module"
+
+#load "lib/imgui"
+
+#load "src/sand_toy"
diff --git a/src/sand_toy.onyx b/src/sand_toy.onyx
new file mode 100644 (file)
index 0000000..62134b0
--- /dev/null
@@ -0,0 +1,66 @@
+use package core
+#private_file imgui  :: package imgui
+#private_file events :: package js_events
+#private_file gl     :: package gl
+
+init :: () {
+    events.init();
+
+    gl.init("simulation-canvas"); 
+    imgui.immediate_renderer_init();
+}
+
+window_width  := 0
+window_height := 0
+
+poll_events :: () {
+    use events.DomEventKind;
+
+    for event: events.consume() do switch event.kind {
+        case Resize {
+            println("The window was resized!");
+
+            window_width  = event.resize.width;
+            window_height = event.resize.height;
+
+            gl.setSize(window_width, window_height);
+            gl.viewport(0, 0, window_width, window_height);
+        }
+
+        case MouseDown {
+            if event.mouse.button == ~~0 {
+                println("Left button click!");
+            }
+        }
+    }
+}
+
+update :: () {
+}
+
+draw :: () {
+    gl.clearColor(0, 0, 0, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    use imgui;
+
+    immediate_set_texture();
+    immediate_quad(.{ -1, -1 }, .{ 2, 2 }, color=.{ 1, 0, 0 });
+
+    immediate_flush();
+}
+
+loop :: () -> void #export {
+    poll_events();
+
+    update();
+    draw();
+}
+
+
+main :: (args: [] cstr) {
+    init();
+
+    start_loop :: () -> void #foreign "game" "start_loop" ---
+    start_loop();
+}