added initial immediate mode rendering module
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 5 May 2021 17:56:50 +0000 (12:56 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 5 May 2021 17:56:50 +0000 (12:56 -0500)
.vimspector.json
bin/onyx
core/io/binary_reader.onyx
modules/immediate_mode/gl_utils.onyx [new file with mode: 0644]
modules/immediate_mode/immediate_renderer.onyx [new file with mode: 0644]
modules/immediate_mode/module.onyx [new file with mode: 0644]
progs/odin_example.onyx
src/onyxlex.c
src/onyxsymres.c

index 428040d826ef8dfc074bbe6d2954cdc322b8388e..ca0980d46704d904f53859f0af5e687f8147495e 100644 (file)
@@ -5,8 +5,8 @@
             "configuration": {
                 "type": "cppdbg",
                 "request": "launch",
-                "program": "${workspaceFolder}/bin/onyx",
-                "args": ["-VVV", "tmp/iter_test.onyx"],
+                "program": "${workspaceFolder}/bin/onyx-debug",
+                "args": ["-VVV", "tmp/a.onyx"],
                 "stopAtEntry": true,
                 "cwd": "${workspaceFolder}",
                 "environment": [],
index 9870c926db10c24c9168feef6b46e593171e7037..f0437b204741cbb280418e68bc8cd3849a5e1527 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index df6ab7050556dc51de9e914b7e0fa2273429a98c..3202917f5463cc9af09ea96cb8df2e633c1be586 100644 (file)
@@ -8,7 +8,8 @@ package core.io.binary
 
 // After sleeping on it, everything here shouldn't need to exist. The already
 // existing BinaryReader from binary.onyx should be able to do all of this
-// when big endian integers are added to the language.
+// when big endian integers are added to the language. This will be moved to
+// the ttf library when that is added to the core modules folder.
 
 BinaryReader :: struct {
     data : [] u8;
diff --git a/modules/immediate_mode/gl_utils.onyx b/modules/immediate_mode/gl_utils.onyx
new file mode 100644 (file)
index 0000000..fbf8c1e
--- /dev/null
@@ -0,0 +1,84 @@
+package immediate_mode
+
+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;
+    
+    texture_uniform : gl.GLUniformLocation;
+    view_uniform    : gl.GLUniformLocation;
+    world_uniform   : 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");
+
+        texture_uniform = gl.getUniformLocation(program, "u_texture");
+        view_uniform    = gl.getUniformLocation(program, "u_view");
+        world_uniform   = 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/modules/immediate_mode/immediate_renderer.onyx b/modules/immediate_mode/immediate_renderer.onyx
new file mode 100644 (file)
index 0000000..4d55eac
--- /dev/null
@@ -0,0 +1,276 @@
+package immediate_mode
+
+use package core
+#private_file gl :: package gl
+
+// CLEANUP: Proper multi-line strings need to be added...
+// This is a gross abuse of the lexer.
+IMMEDIATE_VERTEX_SHADER :: "#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 vec2 v_texture;
+
+void main() {
+    gl_Position = u_view * u_world * vec4(a_vertex, 0, 1);
+
+    v_color = a_color;
+    v_texture = a_texture;
+}
+"
+
+IMMEDIATE_FRAGMENT_SHADER :: "#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;
+}
+"
+
+IMMEDIATE_FRAGMENT_SHADER_TEXTURED :: "#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 * texture(u_texture, v_texture);
+}
+"
+
+
+
+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 {
+    // Will point to either the simple_shader or the textured_shader.
+    active_shader : ^Shader;
+
+    simple_shader, textured_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;
+
+    // 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) {
+        simple_shader   = Shader.make_from_source(IMMEDIATE_VERTEX_SHADER, IMMEDIATE_FRAGMENT_SHADER);
+        textured_shader = Shader.make_from_source(IMMEDIATE_VERTEX_SHADER, IMMEDIATE_FRAGMENT_SHADER_TEXTURED);
+        active_shader   = ^simple_shader;
+
+        verticies = memory.make_slice(Immediate_Vertex, max_verticies);
+        memory.set(verticies.data, 0, verticies.count * sizeof Immediate_Vertex);
+
+        vertex_array = gl.createVertexArray();
+
+        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);
+        
+        ir->init_shader_params(^simple_shader);
+        ir->init_shader_params(^textured_shader);
+
+        gl.useProgram(active_shader.program);
+    }
+
+    init_shader_params :: (use ir: ^Immediate_Renderer, shader: ^Shader) {
+        gl.useProgram(shader.program);
+
+        gl.bindVertexArray(vertex_array);
+        defer gl.bindVertexArray(-1);
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
+
+        // 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);
+
+        // TEMPORARY
+        gl.uniformMatrix4(shader.world_uniform, false,
+                          f32.[ 1, 0, 0, 0,
+                                0, 1, 0, 0,
+                                0, 0, 1, 0,
+                                0, 0, 0, 1 ]);
+    }
+
+    free :: (use ir: ^Immediate_Renderer) {
+        simple_shader->free();
+        textured_shader->free();
+
+        gl.deleteVertexArray(vertex_array);
+        gl.deleteBuffer(vertex_buffer);
+    }
+
+    flush :: (use ir: ^Immediate_Renderer) {
+        if vertex_count == 0 do return;
+
+        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(active_shader.program);
+        gl.bindVertexArray(vertex_array);
+        gl.drawArrays(gl.TRIANGLES, 0, vertex_count);
+        gl.bindVertexArray(-1);
+
+        vertex_count = 0;
+    }
+
+    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, texture: Vector2 = .{0,0}) {
+            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 = texture;
+        },
+    }
+
+    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 });
+    }
+
+    textured_quad :: (use ir: ^Immediate_Renderer, position: Vector2, size: Vector2, texture_position: Vector2, texture_size: Vector2, color: Color4 = .{1,1,1}) {
+        push_vertex(ir, .{ position.x,          position.y          }, color, .{ texture_position.x,                   texture_position.y                  });
+        push_vertex(ir, .{ position.x + size.x, position.y          }, color, .{ texture_position.x + texture_size.x,  texture_position.y                  });
+        push_vertex(ir, .{ position.x + size.x, position.y + size.y }, color, .{ texture_position.x + texture_size.x,  texture_position.y + texture_size.y });
+
+        push_vertex(ir, .{ position.x,          position.y          }, color, .{ texture_position.x,                   texture_position.y                  });
+        push_vertex(ir, .{ position.x + size.x, position.y + size.y }, color, .{ texture_position.x + texture_size.x,  texture_position.y + texture_size.y });
+        push_vertex(ir, .{ position.x,          position.y + size.y }, color, .{ texture_position.x,                   texture_position.y + texture_size.y });
+    }
+
+    // NOTE: Calling set_texture without a parameter will disable textured rendering.
+    set_texture :: (use ir: ^Immediate_Renderer, texture_id: i32 = -1) {
+        if vertex_count > 0 do flush(ir);
+
+        if texture_id >= 0 do active_shader = ^textured_shader;
+        else               do active_shader = ^simple_shader;
+
+        gl.useProgram(active_shader.program);
+        gl.uniform1i(active_shader.texture_uniform, math.max(texture_id, 0));
+    }
+
+    use_ortho_projection :: (use ir: ^Immediate_Renderer, left: f32, right: f32, top: f32, bottom: f32) {
+        projection_matrix := f32.[
+            2 / (right - left), 0, 0, -(right + left) / (right - left),
+            0, 2 / (top - bottom), 0, -(top + bottom) / (top - bottom),
+            0, 0, -2, -1,
+            0, 0, 0, 1
+        ];
+
+        gl.uniformMatrix4(active_shader.view_uniform, true, projection_matrix);
+    }
+}
+
+
+
+
+// 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);
+}
+
+immediate_renderer_free :: () {
+    Immediate_Renderer.free(^immediate_renderer);
+}
+
+vertex :: proc {
+    (position: Vector2)                { immediate_renderer->push_vertex(position); },
+    (position: Vector2, color: Color4) { immediate_renderer->push_vertex(position, color); },
+}
+
+quad :: (position: Vector2, size: Vector2, color: Color4 = .{1,1,1}) {
+    immediate_renderer->quad(position, size, color);
+}
+
+textured_quad :: (position: Vector2, size: Vector2, texture_position: Vector2, texture_size: Vector2, color: Color4 = .{1,1,1}) {
+    immediate_renderer->textured_quad(position, size, texture_position, texture_size, color);
+}
+
+flush :: () do immediate_renderer->flush();
+
+set_texture :: (texture_id: i32 = -1) do immediate_renderer->set_texture(texture_id);
+
+use_ortho_projection :: (left: f32, right: f32, top: f32, bottom: f32) {
+    immediate_renderer->use_ortho_projection(left, right, top, bottom);
+}
diff --git a/modules/immediate_mode/module.onyx b/modules/immediate_mode/module.onyx
new file mode 100644 (file)
index 0000000..1e88679
--- /dev/null
@@ -0,0 +1,5 @@
+
+package immediate_mode
+
+#load "modules/immediate_mode/immediate_renderer"
+#load "modules/immediate_mode/gl_utils"
index ab16c27be34c8159fc312387f69e7e08be5af06c..1e6bf90b9e46b8eaec6a1e5738fc27c8d94f8429 100644 (file)
@@ -116,7 +116,7 @@ main :: (args: [] cstr) {
     }
 
     use package test { foo as foo_pkg }
-    use package test as test
+    test :: package test
     use test.foo.SomeEnum
 
     printf("Val2: %i\n", cast(i32) Val2);
index 0f60262b64141811ceeb6b06ac9858c326c7820b..b244065c05bdee76313c4f183385285a8be35870 100644 (file)
@@ -1,6 +1,7 @@
 #include "bh.h"
 #include "onyxlex.h"
 #include "onyxutils.h"
+#include "onyxerrors.h"
 
 u64 lexer_lines_processed = 0;
 u64 lexer_tokens_processed = 0;
@@ -210,6 +211,10 @@ whitespace_skipped:
         while (!(*tokenizer->curr == '"' && slash_count == 0)) {
             len++;
 
+            // if (*tokenizer->curr == '\n') {
+            //     onyx_report_error(tk.pos, "String literal not terminated by end of line.");
+            // }
+
             if (*tokenizer->curr == '\\') {
                 slash_count += 1;
                 slash_count %= 2;
index ee64296b069db953f8f8529eb2c408c826116520..e02f608e98e07fc0dba6366e43b786cfb55bd8f4 100644 (file)
@@ -991,6 +991,12 @@ static SymresStatus symres_polyproc(AstPolyProc* pp) {
     bh_arr_each(AstPolyParam, param, pp->poly_params) {
         if (param->kind != PPK_Baked_Value) continue;
 
+        // FIX: Looking up symbols immediately in the type of the baked value does not always work
+        // because I think the following should be possible:
+        //
+        //     baked_proc :: (x: $T, $f: (T) -> T) -> T ...
+        // 
+        // The type of 'f' depends on resolving the value for the polyvar 'T'.
         SYMRES(type, &param->type_expr);
     }