maybe have a working immediate renderer
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 13 Feb 2021 22:13:11 +0000 (16:13 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 13 Feb 2021 22:13:11 +0000 (16:13 -0600)
.gitignore
build.sh [new file with mode: 0644]
lib/events.onyx [new file with mode: 0644]
lib/gl/gl_utils.onyx [new file with mode: 0644]
lib/imgui.onyx
lib/immediate_renderer.onyx [new file with mode: 0644]
lib/shaders/immediate_fragment.glsl [new file with mode: 0644]
lib/shaders/immediate_vertex.glsl [new file with mode: 0644]
onyx-imgui.sublime-project
test/basic.onyx

index c51a0ea1f534e787ad4c1ca6d1a5a9f0ba8d8191..3805cfce6fa84eac061a9d85d3936bb0e7867901 100644 (file)
@@ -1 +1,2 @@
-*.sublime-workspace
\ No newline at end of file
+*.sublime-workspace
+*.wasm
diff --git a/build.sh b/build.sh
new file mode 100644 (file)
index 0000000..1d3687f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+onyx -r js -V --use-post-mvp-features test/basic.onyx -o imgui.wasm
\ No newline at end of file
diff --git a/lib/events.onyx b/lib/events.onyx
new file mode 100644 (file)
index 0000000..8ac6025
--- /dev/null
@@ -0,0 +1,104 @@
+// This is a simple buffered system to receive events from the webbrowser
+// in a buffer that you can poll and consume all of the events. It is not
+// direclty used by the immediate mode graphics, but it makes standing up
+// a simple application much easier.
+
+package imgui.events
+
+#private_file Num_Buffered_Events :: 16
+
+// NOTE: These need to match exactly what is in the corresponding javascript
+DomEventKind :: enum {
+    None       :: 0x00;
+
+    MouseDown  :: 0x01;
+    MouseUp    :: 0x02;
+    MouseMove  :: 0x03;
+    MouseWheel :: 0x07;
+
+    KeyDown    :: 0x04;
+    KeyUp      :: 0x05;
+
+    Resize     :: 0x06;
+}
+
+DomEvent :: struct {
+    kind : DomEventKind;
+    timestamp : u32;
+}
+
+KeyboardEvent :: struct {
+    use event : DomEvent;
+
+    keycode : u32;
+}
+
+MouseButton :: enum {
+    Left :: 0x00;
+    Middle :: 0x01;
+    Right :: 0x02;
+
+    WheelUp :: 0x03;
+    WheelDown :: 0x04;
+}
+
+MouseEvent :: struct {
+    use event : DomEvent;
+
+    pos_x  : u32;
+    pos_y  : u32;
+    button : MouseButton;
+}
+
+ResizeEvent :: struct {
+    use event : DomEvent;
+
+    width  : u32;
+    height : u32;
+}
+
+Event :: struct #union {
+    use dom : DomEvent;
+
+    keyboard : KeyboardEvent;
+    mouse    : MouseEvent;
+    resize   : ResizeEvent;
+}
+
+clear_event :: (ev: ^Event) {
+    ev.kind = DomEventKind.None;
+    ev.timestamp = 0;
+}
+
+init :: () {
+    event_storage.event_count = 0;
+    event_storage.max_events = Num_Buffered_Events;
+
+    for ^ev: event_storage.event_buffer do clear_event(ev);
+
+    event_setup(^event_storage, sizeof Event);
+}
+
+poll :: (ev: ^Event) -> bool {
+    if event_storage.event_count == 0 do return false;
+
+    *ev = event_storage.event_buffer[0];
+    for i: 0 .. Num_Buffered_Events - 2 {
+        event_storage.event_buffer[i] = event_storage.event_buffer[i + 1];
+    }
+
+    event_storage.event_count -= 1;
+
+    return true;
+}
+
+/* Private members */
+
+#private_file EventStorage :: struct {
+    event_count  : u32;
+    max_events   : u32;
+    event_buffer : [Num_Buffered_Events] Event;
+}
+
+#private_file event_storage : EventStorage;
+#private_file event_setup :: (event_storage: ^EventStorage, event_size: u32) -> void #foreign "event" "setup" ---
diff --git a/lib/gl/gl_utils.onyx b/lib/gl/gl_utils.onyx
new file mode 100644 (file)
index 0000000..beb5c16
--- /dev/null
@@ -0,0 +1,73 @@
+package imgui.gl
+
+use package core
+use package gl as 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) {
+        // @Robustness: Errors in compiling the shaders are ignored right now.
+        vertex_shader, _   := compile_shader(vertex_source,   gl.VERTEX_SHADER);
+        fragment_shader, _ := compile_shader(fragment_source, gl.FRAGMENT_SHADER);
+
+        // @Robustness: Errors in linkning the program are ignored right now.
+        shader_program, _ := link_program(vertex_shader, fragment_shader);
+        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;
+        }
+    }
+}
+
index f35030f49bb2a6cb2d0a240c4adb8f24ab3cb03f..7b8db6d6479926bed108b9bd592e6ea2d1ce8cda 100644 (file)
@@ -16,4 +16,4 @@
 
 package imgui
 
-
+#load "lib/immediate_renderer"
diff --git a/lib/immediate_renderer.onyx b/lib/immediate_renderer.onyx
new file mode 100644 (file)
index 0000000..2b7840d
--- /dev/null
@@ -0,0 +1,98 @@
+package imgui
+
+#load "core/js/webgl"
+#load "lib/gl/gl_utils"
+
+use package core
+use package imgui.gl
+use package gl as gl
+
+Vector2 :: struct {
+    x, y: f32;
+}
+
+Color4 :: struct {
+    r, g, b, a: f32;
+}
+
+Immediate_Vertex :: struct {
+    position   : Vector2;
+    color      : Color4;
+    texture    : Vector2;
+
+    // @Feature: At some point, it would be nice to bind multiple textures
+    // and then use a texture_id field to choose which texture to use for this
+    // vertex. Granted, this should probably be per triangle, not per vertex. But
+    // for now, I will just use a single texture.
+    // texture_id : i32;
+}
+
+
+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;
+
+    // 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);
+
+        verticies = memory.make_slice(Immediate_Vertex, max_verticies);
+    }
+
+    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) {
+            vertex_ptr := ^verticies[vertex_count];
+            defer vertex_count += 1;
+
+            vertex_ptr.position = position;
+            vertex_ptr.color = color;
+        },
+    }
+
+    flush :: (use ir: ^Immediate_Renderer) {
+        gl.useProgram(shader.program);
+        gl.drawArrays(gl.TRIANGLES, 0, vertex_count);
+
+        vertex_count = 0;
+    }
+}
+
+
+
+#private
+immediate_renderer : Immediate_Renderer;
+
+immediate_renderer_init :: () {
+    immediate_renderer = Immediate_Renderer.make();
+}
+
+immediate_vertex :: proc {
+    (position: Vector2)                { immediate_renderer->push_vertex(position); },
+    (position: Vector2, color: Color4) { immediate_renderer->push_vertex(position, color); },
+}
+
+immediate_flush :: () do immediate_renderer->flush();
diff --git a/lib/shaders/immediate_fragment.glsl b/lib/shaders/immediate_fragment.glsl
new file mode 100644 (file)
index 0000000..37bdf80
--- /dev/null
@@ -0,0 +1,14 @@
+#version 300 es
+
+precision mediump float;
+
+uniform sampler2D u_texture;
+
+in vec4 v_color;
+in vec2 v_texture;
+
+out vec4 fragColor;
+
+void main() {
+    fragColor = v_color + vec4(v_texture, 0, 0) * 0;
+}
\ No newline at end of file
diff --git a/lib/shaders/immediate_vertex.glsl b/lib/shaders/immediate_vertex.glsl
new file mode 100644 (file)
index 0000000..e8531ed
--- /dev/null
@@ -0,0 +1,18 @@
+#version 300 es
+
+layout(location = 0) in vec2 a_vertex;
+layout(location = 1) in vec4 a_color;
+layout(location = 2) in vec2 a_texture;
+
+uniform mat4 u_view;
+uniform mat4 u_world;
+
+out vec4 v_color;
+out vec4 v_texture;
+
+void main() {
+    gl_Position = u_view * u_world * vec4(a_vertex, 0, 1);
+
+    v_color = a_color;
+    v_texture = v_texture;
+}
\ No newline at end of file
index 24db30311b340c8d78001f0fb705810ab77a8c38..21fd46ae5282ccb1a2ab41d1d5c99dae45221798 100644 (file)
@@ -3,6 +3,9 @@
        [
                {
                        "path": "."
+               },
+               {
+                       "path": "C:\\dev\\onyx\\core"
                }
        ]
 }
index f867063a6e9b4128102bee20ac648d4490f803c0..1a1a32fe95d9f6261d57ff616ea0f7c9672a85a8 100644 (file)
@@ -1,11 +1,49 @@
+// Things still needed for an initial version:
+//  - HTML page that load the correct files. (ugh...)
+//  - Eventing system to talk to JS. This will not be directly used by the immediate
+//    mode renderer, because that defeats the whole point of the "immediate mode".
+//    However, it is some thing that should be provided, given that every application
+//    will need it or something very similar.
+//  - Event loop in this file.
+
+
 #load "core/std"
 #load "lib/imgui"
+#load "lib/events"
 
 use package core
 use package imgui as imgui
+use package imgui.events as events
+
+poll_events :: () {
+    use events.DomEventKind;
+
+    event: events.Event;
+    while events.poll(^event) do switch event.kind {
+        case MouseDown {
+            println("The mouse was pressed!");
+            printf("Specifically, the %i button.\n", event.mouse.button);
+        }
+    }
+}
 
+update :: () {
+}
 
+draw :: () {
+}
+
+loop :: () {
+    poll_events();
+
+    //update();
+    //draw();
+}
 
 main :: (args: [] cstr) {
     println("Hey! We got here!");
-}
\ No newline at end of file
+
+    events.init();
+
+    imgui.immediate_renderer_init();
+}