--- /dev/null
+*.sublime-project\r
+*.sublime-workspace\r
--- /dev/null
+#!/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"
--- /dev/null
+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
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+// 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"
--- /dev/null
+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);
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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
--- /dev/null
+<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>
--- /dev/null
+\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
--- /dev/null
+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
--- /dev/null
+\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
--- /dev/null
+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); },
+});
--- /dev/null
+#load "core/std"
+
+#load "modules/webgl2/module"
+#load "modules/js_events/module"
+
+#load "lib/imgui"
+
+#load "src/sand_toy"
--- /dev/null
+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();
+}