From 7c0f8dd73808755fc1e39b3d638125f1178cdca1 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Wed, 21 Apr 2021 22:44:13 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + build.sh | 10 + lib/bitmap/bitmap.onyx | 321 ++++++++++++ lib/gl/gl_utils.onyx | 82 +++ lib/imgui.onyx | 22 + lib/immediate_renderer.onyx | 179 +++++++ lib/shaders/immediate_fragment.glsl | 19 + lib/shaders/immediate_vertex.glsl | 15 + lib/ttf/types.onyx | 773 ++++++++++++++++++++++++++++ site/index.html | 25 + site/js/game.js | 15 + site/js/js_events.js | 67 +++ site/js/onyx-loader.js | 47 ++ site/js/webgl2.js | 291 +++++++++++ site/sand_toy.wasm | Bin 0 -> 31706 bytes src/build.onyx | 8 + src/sand_toy.onyx | 66 +++ 17 files changed, 1942 insertions(+) create mode 100644 .gitignore create mode 100644 build.sh create mode 100644 lib/bitmap/bitmap.onyx create mode 100644 lib/gl/gl_utils.onyx create mode 100644 lib/imgui.onyx create mode 100644 lib/immediate_renderer.onyx create mode 100644 lib/shaders/immediate_fragment.glsl create mode 100644 lib/shaders/immediate_vertex.glsl create mode 100644 lib/ttf/types.onyx create mode 100644 site/index.html create mode 100644 site/js/game.js create mode 100644 site/js/js_events.js create mode 100644 site/js/onyx-loader.js create mode 100644 site/js/webgl2.js create mode 100644 site/sand_toy.wasm create mode 100644 src/build.onyx create mode 100644 src/sand_toy.onyx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f6344f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.sublime-project +*.sublime-workspace diff --git a/build.sh b/build.sh new file mode 100644 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 index 0000000..e72ca10 --- /dev/null +++ b/lib/bitmap/bitmap.onyx @@ -0,0 +1,321 @@ +package imgui.bitmap + +use package core +#private_file ttf :: package imgui.ttf + +Point :: struct { + x, y: f32; +} + + +Bitmap :: struct { + Style :: enum { + Outlined_Aliased; + Outlined_Border; + Filled; + Raw; + } + + allocator : Allocator; + ttf : ^ttf.TrueTypeFont; + buffer : [] u8; + + width : i32 = 1; + height : i32 = 1; + + bytes_per_pixel : u32 = 4; + background_color : u32 = 0xFFFFFF00; + foreground_color : u32 = 0x000000FF; + + scale : f32 = 1.0f; + scale_x : f32 = 1.0f; + scale_y : f32 = 1.0f; + + space_width : f32 = 1.0f; + space_multiplier : f32 = 0.0f; + + transformation_matrix: [9] f32 = f32.[1,0,0,0,1,0,0,0,0]; + character_matrix: [9] f32 = f32.[1,0,0,0,1,0,0,0,0]; + + style: Style = Style.Filled; + justify := false; + justify_fill_ratio : f32 = 0.5; + + filler : [..][..] i32; + + use_font_metrics: bool = false; +} + +bitmap_clear :: (use bitmap: ^Bitmap) { + size := width * width * bytes_per_pixel; + memory.set(buffer.data, 0, size); +} + +bitmap_transform_text :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) { + return + cast(i32) (p.x * transformation_matrix[0] + p.y * transformation_matrix[1] + transformation_matrix[4]), + cast(i32) (p.x * transformation_matrix[2] + p.y * transformation_matrix[3] + transformation_matrix[5]); +} + +bitmap_transform_character :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) { + return + cast(i32) (p.x * character_matrix[0] + p.y * character_matrix[1] + character_matrix[4]), + cast(i32) (p.x * character_matrix[2] + p.y * character_matrix[3] + character_matrix[5]); +} + +bitmap_set_pos :: (use bitmap: ^Bitmap, x: f32, y: f32) { + transformation_matrix[6] = x; + transformation_matrix[7] = y; +} + +bitmap_set_rotation :: (use bitmap: ^Bitmap, angle: f32) { + transformation_matrix[0] = math.cos(angle); + transformation_matrix[1] = -math.sin(angle); + transformation_matrix[2] = math.sin(angle); + transformation_matrix[3] = math.cos(angle); +} + +bitmap_init_filler :: (use bitmap: ^Bitmap) { + h := height - filler.count; + if h < 1 do return; + + for _: h { + new_row := array.make(i32, allocator=allocator); + array.push(^filler, new_row); + } +} + +bitmap_clear_filler :: (use bitmap: ^Bitmap) { + for i: height { + array.clear(^filler[i]); + } +} + +bitmap_exec_filler :: (use bitmap: ^Bitmap) { + cmp_asc :: (x: $T, y: T) -> i32 do return cast(i32) (x - y); + + for y: height { + if filler[y].count > 0 { + array.sort(^filler[y], cmp_asc); + + if filler[y].count % 2 != 0 { + // Need to have an even number of lines + continue; + } + + index := 0; + while index < filler[y].count { + defer index += 2; + + startx := filler[y][index] + 1; + endx := filler[y][index + 1]; + + if startx >= endx do continue; + + for x: startx .. endx + 1 { // @Check: +1 may not be necessary + bitmap_plot(bitmap, x, y, foreground_color); + } + } + } + } +} + +// This function should be inlined at some point because it is very small +// and cheap. +bitmap_plot :: (use bitmap: ^Bitmap, x: i32, y: i32, color: u32) -> bool { + if x < 0 || x >= width || y < 0 || y >= height do return false; + + index := (x + y * width) * bytes_per_pixel; + buffer[index] = cast(u8) (color & 0xFF); + + return true; +} + +bitmap_fline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + + if (ix0 < 0 && ix1 < 0) || (ix0 > width && ix1 > width) do return; + + x0 := cast(f32) ix0; + y0 := cast(f32) iy0; + x1 := cast(f32) ix1; + y1 := cast(f32) iy1; + + if y1 < y0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + if dy == 0 { + if iy0 >= 0 && iy0 < filler.count { + if ix0 <= ix1 { + array.push(^filler[iy0], ix0); + array.push(^filler[iy0], ix1); + } else { + array.push(^filler[iy0], ix1); + array.push(^filler[iy0], ix0); + } + } + + return; + } + + n := dx / dy; + for y: cast(i32) (dy + 0.5) { + yd := cast(i32) (y + iy0); + x := n * ~~y + x0; + + if x > ~~width || yd >= ~~filler.count do break; + + if yd >= 0 && yd < filler.count { + array.push(^filler[yd], cast(i32) (x + 0.5)); + } + } +} + +bitmap_aline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + x0 := cast(f32) ix0; + y0 := cast(f32) iy0; + x1 := cast(f32) ix1; + y1 := cast(f32) iy1; + + dx := x1 - x0; + dy := y1 - y0; + + dist := cast(f32) 0.4; + if math.abs(dx) > math.abs(dy) { + if x1 > x0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + x0 += 0.5; + y0 += 0.5; + + m := dy / dx; + x := x0; + while x <= x1 + 0.5 { + y := m * (x - x0) + y0; + e := 1 - math.abs(y - 0.5 - math.floor(y)); + bitmap_plot(bitmap, ~~x, ~~y, foreground_color); // Needs to be aliased! + + ys1 := y + dist; + if cast(i32) ys1 != cast(i32) y { + v1 := math.abs(ys1 - y) / dist * (1 - e); + bitmap_plot(bitmap, ~~x, ~~ys1, foreground_color); // Needs to be aliased! + } + + ys2 := y - dist; + if cast(i32) ys2 != cast(i32) y { + v2 := math.abs(y - ys2) / dist * (1 - e); + bitmap_plot(bitmap, ~~x, ~~ys2, foreground_color); // Needs to be aliased! + } + + x += 1; + } + + } else { + if y1 > y0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + x0 += 0.5; + y0 += 0.5; + + n := dx / dy; + y := y0; + while y <= y1 + 0.5 { + x := n * (y - x0) + y0; + e := 1 - math.abs(x - 0.5 - math.floor(x)); + bitmap_plot(bitmap, ~~x, ~~y, foreground_color); // Needs to be aliased! + + xs1 := x + dist; + if cast(i32) xs1 != cast(i32) x { + v1 := math.abs(xs1 - x) / dist * (1 - e); + bitmap_plot(bitmap, ~~xs1, ~~y, foreground_color); // Needs to be aliased! + } + + xs2 := x - dist; + if cast(i32) xs1 != cast(i32) x { + v1 := math.abs(x - xs2) / dist * (1 - e); + bitmap_plot(bitmap, ~~xs2, ~~y, foreground_color); // Needs to be aliased! + } + + y += 1.0; + } + } +} + +bitmap_line :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + use Bitmap.Style; + switch style { + case Outlined_Aliased { + bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + case Filled { + bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color); + bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + case Raw { + bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + } + + assert(false, "Unhandled bitmap_line case"); +} + +bitmap_box :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + bitmap_line(bitmap, ix0, iy0, ix1, iy0, color); + bitmap_line(bitmap, ix1, iy0, ix1, iy1, color); + bitmap_line(bitmap, ix0, iy1, ix1, iy1, color); + bitmap_line(bitmap, ix0, iy0, ix0, iy1, color); +} + +bitmap_quadratic :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, icx: i32, icy: i32, color: u32) { + x0 := cast(f64) ix0; + x1 := cast(f64) ix1; + y0 := cast(f64) iy0; + y1 := cast(f64) iy1; + cx := cast(f64) icx; + cy := cast(f64) icy; + + division := cast(f64) 1; + dx := math.abs(x0 - x1); + dy := math.abs(y0 - y1); + + if dx <= 2 || dy <= 2 { + bitmap_line(bitmap, ~~x0, ~~y0, ~~x1, ~~y1, color); + return; + } + + division = 1 / cast(f64) math.max(dx, dy); + + x_old := x0; + y_old := y0; + t := cast(f64) 0; + + while t <= (1 + division / 2) { + s := 1 - t; + x := s * s * x0 + 2.0 * s * t * cx + t * t * x1; + y := s * s * y0 + 2.0 * s * t * cy + t * t * y1; + xi := cast(i32) (x + 0.5); + yi := cast(i32) (y + 0.5); + + bitmap_line(bitmap, ~~x_old, ~~y_old, ~~xi, ~~yi, color); + x_old = ~~ xi; + y_old = ~~ yi; + t += division; + } +} diff --git a/lib/gl/gl_utils.onyx b/lib/gl/gl_utils.onyx new file mode 100644 index 0000000..b306d5a --- /dev/null +++ b/lib/gl/gl_utils.onyx @@ -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 index 0000000..e93467d --- /dev/null +++ b/lib/imgui.onyx @@ -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 index 0000000..e37a0ca --- /dev/null +++ b/lib/immediate_renderer.onyx @@ -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 index 0000000..204e095 --- /dev/null +++ b/lib/shaders/immediate_fragment.glsl @@ -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 index 0000000..cf95128 --- /dev/null +++ b/lib/shaders/immediate_vertex.glsl @@ -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 index 0000000..c9b59b1 --- /dev/null +++ b/lib/ttf/types.onyx @@ -0,0 +1,773 @@ +package imgui.ttf + +use package core + +TrueTypeFont :: struct { + allocator : Allocator; + reader: io.binary.BinaryReader; + + scalar_type : u32; + search_range : u16; + entry_selector : u16; + range_shift : u16; + + table_map : map.Map(str, TTTableInfo); + char_maps : [..] TTCmap; + + version : u32; + font_revision : u32; + checksum_adjustment : u32; + magic_number : u32; + flags : u16; + units_per_em : u16; + created : u64; + modified : u64; + x_min : i16; + x_max : i16; + y_min : i16; + y_max : i16; + mac_style : u16; + lowest_rec_ppem : u16; + font_direction_hint : i16; + index_to_loc_format : TTIndexToLocFormat; + glyph_data_format : i16; + kern : [..] TTKern0Table; + + hhea : struct { + version : u32; + ascent : i16; + descent : i16; + line_gap : i16; + advance_width_max : u16; + min_left_side_bearing : i16; + min_right_side_bearing : i16; + x_max_extent : i16; + caret_slope_rise : i16; + caret_slope_run : i16; + caret_offset : i16; + metric_data_format : i16; + num_of_long_hor_metrics : u16; + }; +} + +ttf_create :: (ttf_data: [] u8, allocator := context.allocator) -> ^TrueTypeFont { + ttf := new(TrueTypeFont, allocator=allocator); + ttf.allocator = allocator; + + ttf.reader = io.binary.create_reader(ttf_data); + + map.init(^ttf.table_map, .{}); + array.init(^ttf.char_maps, allocator=allocator); + array.init(^ttf.kern, allocator=allocator); + ttf_read_offset_table(ttf); + ttf_read_head_table(ttf); + ttf_read_cmap_table(ttf); + ttf_read_hhea_table(ttf); + ttf_read_kern_table(ttf); + + return ttf; +} + +ttf_free :: (ttf: ^TrueTypeFont) { + array.free(^ttf.char_maps); + array.free(^ttf.kern); + map.free(^ttf.table_map); + + raw_free(ttf.allocator, ttf); +} + +TTTableInfo :: struct { + checksum : u32 = 0; + offset : u32 = 0; + length : u32 = 0; +} + +TTIndexToLocFormat :: enum (i16) { + Short :: 0x00; + Long :: 0x01; +} + +TTGlyph :: struct { + Type :: enum { + Unknown :: 0x00; + Simple :: 0x01; + Compound :: 0x02; + } + + type: Type = Type.Unknown; + allocator: Allocator; + + valid_glyph := false; + + contour_count : i16; + x_min : i16; + x_max : i16; + y_min : i16; + y_max : i16; + + points : [..] TTGlyphPoint; + contour_ends : [..] u16; + + components: [] GlyphComponent; + + GlyphComponent :: struct { + glyph_index : u16; + dest_point_index : i16; + src_point_index : i16; + matrix : [6] f32; + } +} + +TTGlyphPoint :: struct { + on_curve : bool; + x : i16 = 0; + y : i16 = 0; +} + +ttf_read_offset_table :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + scalar_type = read_u32(^reader); + num_tables := cast(u32) read_u16(^reader); + search_range = read_u16(^reader); + entry_selector = read_u16(^reader); + range_shift = read_u16(^reader); + + for i: num_tables { + tag := read_string(^reader, 4); + + table_info : TTTableInfo; + table_info.checksum = read_u32(^reader); + table_info.offset = read_u32(^reader); + table_info.length = read_u32(^reader); + + map.put(^table_map, tag, table_info); + + // CLEANUP: There should be a "!=" for strings. + if !(tag == "head") { + csum := ttf_calc_table_checksum(^reader, table_info.offset, table_info.length); + if table_info.checksum != csum { + printf("WARNING: Checksum for table '%s' did not match.\n", tag); + } + } + } +} + +ttf_read_head_table :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + head_table_info := map.get(^table_map, "head"); + seek(^reader, head_table_info.offset); + + version = read_u32(^reader); + font_revision = read_u32(^reader); + checksum_adjustment = read_u32(^reader); + magic_number = read_u32(^reader); + flags = read_u16(^reader); + units_per_em = read_u16(^reader); + created = read_date(^reader); + modified = read_date(^reader); + x_min = read_fword(^reader); + y_min = read_fword(^reader); + x_max = read_fword(^reader); + y_max = read_fword(^reader); + mac_style = read_u16(^reader); + lowest_rec_ppem = read_u16(^reader); + font_direction_hint = read_i16(^reader); + index_to_loc_format = cast(TTIndexToLocFormat) read_i16(^reader); + glyph_data_format = read_i16(^reader); + + assert(magic_number == 0x5f0f3cf5, "TTF Magic Number wrong!"); +} + +ttf_lookup_glyph_offset :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> u32 { + use package core.io.binary + + loca_table_info := map.get(^table_map, "loca"); + glyf_table_info := map.get(^table_map, "glyf"); + + old: u32; + defer seek(^reader, old); + + switch index_to_loc_format { + case TTIndexToLocFormat.Long { + old = seek(^reader, loca_table_info.offset + glyph_index * 4); + return read_u32(^reader) + glyf_table_info.offset; + } + + case #default { + old = seek(^reader, loca_table_info.offset + glyph_index * 2); + return 2 * cast(u32) read_u16(^reader) + glyf_table_info.offset; + } + } + + return 0xffffffff; +} + +// Result is expected to be freed +ttf_read_glyph :: (use ttf: ^TrueTypeFont, glyph_index: i32, glyph_allocator := context.allocator) -> ^TTGlyph { + use package core.io.binary + + offset := ttf_lookup_glyph_offset(ttf, glyph_index); + + glyf_table_info := map.get(^table_map, "glyf"); + + if offset >= glyf_table_info.offset + glyf_table_info.length do return null; + + seek(^reader, offset); + + glyph := new(TTGlyph, glyph_allocator); + glyph.allocator = glyph_allocator; + glyph.contour_count = read_i16(^reader); + glyph.x_min = read_fword(^reader); + glyph.y_min = read_fword(^reader); + glyph.x_max = read_fword(^reader); + glyph.y_max = read_fword(^reader); + + if glyph.contour_count < 0 { raw_free(glyph_allocator, glyph); return null; } + if glyph.contour_count == ~~ -1 { + // Compound glyph + return null; + } else { + // Simple glyph + ttf_read_simple_glyph(ttf, glyph); + } + + glyph.valid_glyph = true; + return glyph; +} + +ttf_glyph_destroy :: (glyph: ^TTGlyph) { + array.free(^glyph.contour_ends); + array.free(^glyph.points); + raw_free(glyph.allocator, glyph); +} + +#private_file +TTGlyphFlags :: enum #flags { + On_Curve :: 0x01; + X_Is_Byte :: 0x02; + Y_Is_Byte :: 0x04; + Repeat :: 0x08; + X_Delta :: 0x10; + Y_Delta :: 0x20; +} + +ttf_read_simple_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) { + use package core.io.binary + + if glyph.contour_count == 0 do return; + + array.init(^glyph.contour_ends, ~~glyph.contour_count); + array.init(^glyph.points); + + for _: 0 .. ~~glyph.contour_count { + array.push(^glyph.contour_ends, read_u16(^reader)); + } + + seek(^reader, ~~read_u16(^reader) + tell(^reader)); + + num_points := array.fold(^glyph.contour_ends, cast(u16) 0, math.max_poly) + 1; + + flags : [..] TTGlyphFlags; + array.init(^flags); + defer array.free(^flags); + + while i := 0; i < ~~num_points { + defer i += 1; + + flag := cast(TTGlyphFlags) read_u8(^reader); + array.push(^flags, flag); + array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 }); + + if (flag & TTGlyphFlags.Repeat) != ~~0 { + rep_count := read_u8(^reader); + i += ~~ rep_count; + + for _: 0 .. ~~rep_count { + array.push(^flags, flag); + array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 }); + } + } + } + + value: i16 = 0; + for i: 0 .. ~~num_points { + flag := flags[i]; + + if (flag & TTGlyphFlags.X_Is_Byte) != ~~0 { + if (flag & TTGlyphFlags.X_Delta) != ~~0 { + value += ~~read_u8(^reader); + } else { + value -= ~~read_u8(^reader); + } + } elseif (flag & TTGlyphFlags.X_Delta) == ~~0 { + value += read_i16(^reader); + } + + glyph.points[i].x = value; + } + + value = 0; + for i: 0 .. ~~num_points { + flag := flags[i]; + + if (flag & TTGlyphFlags.Y_Is_Byte) != ~~0 { + if (flag & TTGlyphFlags.Y_Delta) != ~~0 { + value += ~~read_u8(^reader); + } else { + value -= ~~read_u8(^reader); + } + } elseif (flag & TTGlyphFlags.Y_Delta) == ~~0 { + value += read_i16(^reader); + } + + glyph.points[i].y = value; + } +} + +#private +TFKC_Flags :: enum #flags { + arg_1_and_2_are_words; + args_are_xy_values; + round_xy_to_grid; + we_have_a_scale; + _; // reserved + more_components; + we_have_an_x_and_y_scale; + we_have_a_two_by_two; + we_have_instructions; + use_my_metrics; + overlap_component; +} + +ttf_read_compound_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) { + use package core.io.binary + + glyph.type = TTGlyph.Type.Compound; + + component : TTGlyph.GlyphComponent; + flags := TFKC_Flags.more_components; + + while (flags & TFKC_Flags.more_components) != ~~ 0 { + arg1, arg2 : i16; + + flags = ~~ read_u16(^reader); + + component.glyph_index = read_u16(^reader); + + if (flags & TFKC_Flags.arg_1_and_2_are_words) != ~~ 0 { + arg1 = read_i16(^reader); + arg2 = read_i16(^reader); + } else { + arg1 = cast(i16) read_u8(^reader); + arg2 = cast(i16) read_u8(^reader); + } + + if (flags & TFKC_Flags.args_are_xy_values) != ~~ 0 { + component.matrix[4] = ~~ cast(i32) arg1; + component.matrix[5] = ~~ cast(i32) arg2; + } else { + component.dest_point_index = arg1; + component.src_point_index = arg2; + } + + if (flags & TFKC_Flags.we_have_a_scale) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[3] = component.matrix[0]; + } + elseif (flags & TFKC_Flags.we_have_an_x_and_y_scale) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[3] = read_2dot14(^reader); + } + elseif (flags & TFKC_Flags.we_have_a_two_by_two) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[2] = read_2dot14(^reader); + component.matrix[3] = read_2dot14(^reader); + component.matrix[4] = read_2dot14(^reader); + } + + old_pos := tell(^reader); + + simple_glyph := ttf_read_glyph(ttf, ~~ component.glyph_index, ttf.allocator); + if simple_glyph.valid_glyph { + point_offset := glyph.points.count; + for i: simple_glyph.contour_ends.count { + array.push(^glyph.contour_ends, cast(u16) (simple_glyph.contour_ends[i] + ~~ point_offset)); + } + + for p: simple_glyph.points { + px := cast(f32) cast(i32) p.x; + py := cast(f32) cast(i32) p.y; + + x := component.matrix[0] * px + component.matrix[1] * py + component.matrix[4]; + y := component.matrix[2] * px + component.matrix[3] * py + component.matrix[5]; + + array.push(^glyph.points, .{ + x = ~~ cast(i32) x, + y = ~~ cast(i32) y, + on_curve = p.on_curve, + }); + } + } + + seek(^reader, old_pos); + } + + glyph.contour_count = cast(i16) glyph.contour_ends.count; + + if (flags & TFKC_Flags.we_have_instructions) != ~~ 0 { + seek(^reader, tell(^reader) + ~~ read_u16(^reader)); + } + + glyph.valid_glyph = true; +} + +ttf_glyph_count :: (use ttf: ^TrueTypeFont) -> u32 { + use package core.io.binary + + maxp_table_info := map.get(^table_map, "maxp"); + old := seek(^reader, maxp_table_info.offset + 4); + defer seek(^reader, old); + + return ~~read_u16(^reader); +} + +ttf_read_cmap_table :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + cmap_table_info := map.get(^table_map, "cmap"); + seek(^reader, cmap_table_info.offset); + + version := read_u16(^reader); + num_subtables := read_u16(^reader); + + for i: 0 .. ~~num_subtables { + platform_id := read_u16(^reader); + platform_specific_id := read_u16(^reader); + offset := read_u32(^reader); + + // Microsoft Unicode, BMP only + if platform_id == 3 && platform_specific_id <= 1 { + ttf_read_cmap(ttf, offset + cmap_table_info.offset); + } + } +} + +TTCmapFormat :: enum (u16) { + Simple :: 0x00; + Segmented :: 0x04; +} + +TTCmapBase :: struct { format : TTCmapFormat; } +TTCmap0 :: struct { + use base: TTCmapBase; + + glyph_indicies: [] u8; +} +TTCmap4 :: struct { + use base: TTCmapBase; + + seg_count : u16; + search_range : u16; + entry_selector : u16; + range_shift : u16; + + segments : [..] TTSegment; + cache : map.Map(i32, i32); +} + +TTSegment :: struct { + start_code : u16; + end_code : u16; + id_delta : u16; + id_range_offset : u16; +} + +TTCmap :: struct #union { + use base: TTCmapBase; + cmap0: TTCmap0; + cmap4: TTCmap4; +} + +ttf_read_cmap :: (use ttf: ^TrueTypeFont, offset: u32) { + use package core.io.binary + + old := seek(^reader, offset); + defer seek(^reader, old); + + format := read_u16(^reader); + length := read_u16(^reader); + lang := read_u16(^reader); + + switch cast(i32) format { + case 0 do ttf_read_cmap0(ttf); + case 4 do ttf_read_cmap4(ttf); + + case #default { printf("Unsupported cmap format: %d\n", cast(i32) format); } + } +} + +ttf_read_cmap0 :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + cmap : TTCmap; + cmap.cmap0.format = TTCmapFormat.Simple; + + glyphs : [..] u8; + array.init(^glyphs, 256); + for i: 0 .. 256 do array.push(^glyphs, read_u8(^reader)); + + cmap.cmap0.glyph_indicies = array.to_slice(^glyphs); + + array.push(^char_maps, cmap); +} + +ttf_read_cmap4 :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + cmap : TTCmap; + cmap.cmap4.format = TTCmapFormat.Segmented; + imap := ^cmap.cmap4; + map.init(^imap.cache); + + imap.seg_count = read_u16(^reader) >> 1; + imap.search_range = read_u16(^reader); + imap.entry_selector = read_u16(^reader); + imap.range_shift = read_u16(^reader); + + array.init(^imap.segments, ~~imap.seg_count); + imap.segments.count = cast(u32) imap.seg_count; + + for ^seg: imap.segments do seg.end_code = read_u16(^reader); + read_u16(^reader); // Reserved and unused + for ^seg: imap.segments do seg.start_code = read_u16(^reader); + for ^seg: imap.segments do seg.id_delta = read_u16(^reader); + for ^seg: imap.segments { + seg.id_range_offset = read_u16(^reader); + if seg.id_range_offset != 0 do seg.id_range_offset += ~~(tell(^reader) - 2); + } + + array.push(^char_maps, cmap); +} + +ttf_lookup_glyph_by_char :: (use ttf: ^TrueTypeFont, charcode: u32) -> u32 { + potential_code := 0; + + for ^cmap: char_maps { + switch cmap.format { + case TTCmapFormat.Simple do potential_code = ttf_lookup_in_cmap0(ttf, ~~cmap, charcode); + case TTCmapFormat.Segmented do potential_code = ttf_lookup_in_cmap4(ttf, ~~cmap, charcode); + } + + if potential_code != 0 do return potential_code; + } + + return potential_code; +} + +#private_file +ttf_lookup_in_cmap0 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap0, charcode: u32) -> u32 { + if charcode < 0 || charcode >= 256 do return 0; + return ~~cmap.glyph_indicies[charcode]; +} + +#private_file +ttf_lookup_in_cmap4 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap4, charcode: u32) -> u32 { + use package core.io.binary + + if map.has(^cmap.cache, charcode) do return map.get(^cmap.cache, charcode); + + index := 0; + for ^seg: cmap.segments { + if ~~seg.start_code <= charcode && ~~charcode <= seg.end_code { + if seg.id_range_offset != 0 { + glyph_index_address := ~~seg.id_range_offset + 2 * (charcode - ~~seg.start_code); + seek(^reader, glyph_index_address); + index = cast(u32) read_u16(^reader); + } else { + index = (~~seg.id_delta + charcode) & 0xffff; + } + + break; + } + } + + map.put(^cmap.cache, charcode, index); + + return index; +} + +ttf_read_hhea_table :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + + hhea_table_info := map.get(^table_map, "hhea"); + seek(^reader, hhea_table_info.offset); + + hhea.version = read_u32(^reader); + hhea.ascent = read_fword(^reader); + hhea.descent = read_fword(^reader); + hhea.line_gap = read_fword(^reader); + hhea.advance_width_max = read_u16(^reader); + hhea.min_left_side_bearing = read_u16(^reader); + hhea.min_right_side_bearing = read_u16(^reader); + hhea.x_max_extent = read_fword(^reader); + hhea.caret_slope_rise = read_i16(^reader); + hhea.caret_slope_run = read_i16(^reader); + hhea.caret_offset = read_fword(^reader); + read_i16(^reader); // Reserved + read_i16(^reader); // Reserved + read_i16(^reader); // Reserved + read_i16(^reader); // Reserved + hhea.metric_data_format = read_i16(^reader); + hhea.num_of_long_hor_metrics = read_u16(^reader); +} + +TTKern0Table :: struct { + swap : bool; + offset : u32; + n_pairs : i32; + kmap : map.Map(u32, i16); + old_index : i32 = -1; + + reset :: (use kern: ^TTKern0Table) { + old_index = -1; + } + + get :: (use kern: ^TTKern0Table, glyph_index: i32) -> (i32, i32) { + x := 0; + + if old_index >= 0 { + ch := ((cast(u32) old_index & 0xFFFF) << 16) | (cast(u32) glyph_index & 0xFFFF); + if map.has(^kmap, ch) { + x = cast(i32) map.get(^kmap, ch); + } + } + + old_index = glyph_index; + if swap do return 0, x; + else do return x, 0; + } +} + +ttf_read_kern_table :: (use ttf: ^TrueTypeFont) { + use package core.io.binary + if !map.has(^table_map, "kern") do return; + + kern_table_info := map.get(^table_map, "kern"); + seek(^reader, kern_table_info.offset); + + version := read_u16(^reader); + assert(version == 0, "Expected kern table version to be 0."); + n_tables := cast(u32) read_u16(^reader); + + for _: n_tables { + sub_table_version := cast(u32) read_u16(^reader); + length := cast(u32) read_u16(^reader); + coverage := cast(u32) read_u16(^reader); + format := coverage >> 8; + cross := coverage & 4; + vertical := (coverage & 1) == 0; + + if format == 0 { + kern_table := ttf_read_kern0(ttf, vertical, cross != 0); + array.push(^kern, kern_table); + + } else { + // Unknown format + seek(^reader, tell(^reader) + length); + } + } +} + +ttf_read_kern0 :: (use ttf: ^TrueTypeFont, vertical: bool, cross: bool) -> TTKern0Table { + use package core.io.binary + use package core.intrinsics.onyx { __zero_value } + + offset := tell(^reader); + n_pairs := cast(i32) read_u16(^reader); + search_range := cast(i32) read_u16(^reader); + entry_selector := cast(i32) read_u16(^reader); + range_shift := cast(i32) read_u16(^reader); + + kt := TTKern0Table.{ + swap = (vertical && !cross) || (!vertical && cross), + offset = offset, + n_pairs = n_pairs, + kmap = __zero_value(#type map.Map(u32, i16)) + }; + + for _: n_pairs { + left := cast(i32) read_u16(^reader); + right := cast(i32) read_u16(^reader); + value := read_fword(^reader); + tmp_index := (left << 16) | right; + map.put(^kt.kmap, tmp_index, value); + } + + return kt; +} + +ttf_next_kern :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> (i32, i32) { + // BUG: I cannot do this here because x and y do not + // automatically become a 'i32' type. + // x, y := 0, 0; + x := 0; + y := 0; + for i: 0 .. kern.count { + tmp_x, tmp_y := kern[i]->get(glyph_index); + x += tmp_x; + y += tmp_y; + } + return x, y; +} + +TTHorizontalMetrics :: struct { + advance_width : u16; + left_side_bearing : i16; +} + +ttf_lookup_horizontal_metrics :: (use ttf: ^TrueTypeFont, glyph_index: u32) -> TTHorizontalMetrics { + use package core.io.binary + + hmtx_table_info := map.get(^table_map, "hmtx"); + offset := hmtx_table_info.offset; + + hmtx : TTHorizontalMetrics; + + nmets := cast(u32) hhea.num_of_long_hor_metrics; + + if glyph_index < nmets { + offset += glyph_index * 4; + old := seek(^reader, offset); + defer seek(^reader, old); + + hmtx.advance_width = read_u16(^reader); + hmtx.left_side_bearing = read_i16(^reader); + + } else { + old := seek(^reader, offset + (nmets - 1) * 4); + defer seek(^reader, old); + + hmtx.advance_width = read_u16(^reader); + seek(^reader, offset + nmets * 4 + 2 * (glyph_index - nmets)); + hmtx.left_side_bearing = read_i16(^reader); + } + + return hmtx; +} + + +#private_file +ttf_calc_table_checksum :: (reader: ^io.binary.BinaryReader, offset: u32, length: u32) -> u32 { + use package core.io.binary + + old := seek(reader, offset); + defer seek(reader, old); + + sum := 0; + nlongs := (length + 3) >> 2; + for i: 0 .. nlongs do sum += read_u32(reader); + return sum; +} + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..427c477 --- /dev/null +++ b/site/index.html @@ -0,0 +1,25 @@ + + + IMGUI + + + + + + + + + + + + This browser does not support the Canvas API. + + diff --git a/site/js/game.js b/site/js/game.js new file mode 100644 index 0000000..122d816 --- /dev/null +++ b/site/js/game.js @@ -0,0 +1,15 @@ + +window.ONYX_MODULES = window.ONYX_MODULES || []; + +window.ONYX_MODULES.push({ + module_name: "game", + + start_loop: function() { + var loop = function(dt) { + window.ONYX_INSTANCE.exports.loop(); + window.requestAnimationFrame(loop); + }; + + window.requestAnimationFrame(loop); + }, +}); diff --git a/site/js/js_events.js b/site/js/js_events.js new file mode 100644 index 0000000..6d93929 --- /dev/null +++ b/site/js/js_events.js @@ -0,0 +1,67 @@ +window.ONYX_MODULES = window.ONYX_MODULES || []; + +function push_event_to_buffer(esp, event_size, event_kind, data) { + let WASM_U32 = new Uint32Array(ONYX_MEMORY.buffer); + + if (WASM_U32[esp] >= WASM_U32[esp + 1]) { + console.log("Event buffer full!"); + return; + } + + WASM_U32[esp] += 1; + + let event_idx = esp + (WASM_U32[esp] - 1) * (event_size / 4) + 2; + WASM_U32[event_idx] = event_kind; + WASM_U32[event_idx + 1] = Date.now(); + + for (let i = 0; i < data.length; i++) { + WASM_U32[event_idx + 2 + i] = data[i]; + } +} + +window.ONYX_MODULES.push({ + module_name: "js_events", + + setup: function(esp, event_size) { + // Indicies into a Uint32Array are not based on bytes, + // but on the index. + esp /= 4; + + document.addEventListener("keydown", function (ev) { + if (ev.isComposing || ev.keyCode === 229) return; + push_event_to_buffer(esp, event_size, 0x04, [ ev.keyCode ]); + }); + + document.addEventListener("keyup", function (ev) { + if (ev.isComposing || ev.keyCode === 229) return; + push_event_to_buffer(esp, event_size, 0x05, [ ev.keyCode ]); + }); + + document.addEventListener("mousedown", function (ev) { + push_event_to_buffer(esp, event_size, 0x01, [ ev.clientX, ev.clientY, ev.button ]); + }); + + document.addEventListener("mouseup", function (ev) { + push_event_to_buffer(esp, event_size, 0x02, [ ev.clientX, ev.clientY, ev.button ]); + }); + + document.addEventListener("mousemove", function (ev) { + push_event_to_buffer(esp, event_size, 0x03, [ ev.clientX, ev.clientY, -1 ]); + }); + + document.addEventListener("wheel", function (ev) { + push_event_to_buffer(esp, event_size, 0x07, [ ev.clientX, ev.clientY, ev.deltaY >= 0 ? 0x04 : 0x03 ]); + }); + + window.addEventListener("resize", function (ev) { + push_event_to_buffer(esp, event_size, 0x06, [ window.innerWidth, window.innerHeight ]); + }); + + push_event_to_buffer(esp, event_size, 0x06, [ window.innerWidth, window.innerHeight ]); + + document.oncontextmenu = (e) => { + e.preventDefault = true; + return false; + }; + }, +}); diff --git a/site/js/onyx-loader.js b/site/js/onyx-loader.js new file mode 100644 index 0000000..6e643a8 --- /dev/null +++ b/site/js/onyx-loader.js @@ -0,0 +1,47 @@ + +window.ONYX_MODULES = window.ONYX_MODULES || []; +window.ONYX_MEMORY = null; +window.ONYX_INSTANCE = null; + +window.ONYX_MODULES.push({ + module_name: "host", + + print_str: function(ptr, len) { + var buffer = new Uint8Array(ONYX_MEMORY.buffer, ptr, len); + var string = new TextDecoder().decode(buffer); + console.log(string); + }, + + exit: function() { debugger; } +}); + +function launch_onyx_program(script_path, call_start) { + fetch(script_path) + .then(function(res) { return res.arrayBuffer(); }) + .then(function(wasm_code) { + var import_object = {}; + + for (var i = 0; i < window.ONYX_MODULES.length; i++) { + import_object[window.ONYX_MODULES[i].module_name] = window.ONYX_MODULES[i]; + } + + return WebAssembly.instantiate(wasm_code, import_object); + }) + .then(function(wasm_module) { + window.ONYX_MEMORY = wasm_module.instance.exports.memory; + window.ONYX_INSTANCE = wasm_module.instance; + + wasm_module.instance.exports._start(); + }); +} + +window.onload = function() { + var script_tags = document.getElementsByTagName("script"); + + for (var i = 0; i < script_tags.length; i++) { + if (script_tags[i].getAttribute("type") == "application/onyx") { + // @ROBUSTNESS: It should be configurable which function is called on start up of a Onyx WASM module. + launch_onyx_program(script_tags[i].getAttribute("src"), true); + } + } +}; diff --git a/site/js/webgl2.js b/site/js/webgl2.js new file mode 100644 index 0000000..61d565a --- /dev/null +++ b/site/js/webgl2.js @@ -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 index 0000000000000000000000000000000000000000..ed634df6c1f57a6b6bf3f6c377235f4bbfa24dbe GIT binary patch literal 31706 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 RZ!Nz|_zm!D{$0w+{|6Lh>aG9) literal 0 HcmV?d00001 diff --git a/src/build.onyx b/src/build.onyx new file mode 100644 index 0000000..76f467b --- /dev/null +++ b/src/build.onyx @@ -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 index 0000000..62134b0 --- /dev/null +++ b/src/sand_toy.onyx @@ -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(); +} -- 2.25.1