From: Brendan Hansen Date: Wed, 5 May 2021 17:56:50 +0000 (-0500) Subject: added initial immediate mode rendering module X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=3febfbb303ccae225db8ab948ba32f02ebf744f4;p=onyx.git added initial immediate mode rendering module --- diff --git a/.vimspector.json b/.vimspector.json index 428040d8..ca0980d4 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -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": [], diff --git a/bin/onyx b/bin/onyx index 9870c926..f0437b20 100755 Binary files a/bin/onyx and b/bin/onyx differ diff --git a/core/io/binary_reader.onyx b/core/io/binary_reader.onyx index df6ab705..3202917f 100644 --- a/core/io/binary_reader.onyx +++ b/core/io/binary_reader.onyx @@ -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 index 00000000..fbf8c1e1 --- /dev/null +++ b/modules/immediate_mode/gl_utils.onyx @@ -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 index 00000000..4d55eace --- /dev/null +++ b/modules/immediate_mode/immediate_renderer.onyx @@ -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 index 00000000..1e88679a --- /dev/null +++ b/modules/immediate_mode/module.onyx @@ -0,0 +1,5 @@ + +package immediate_mode + +#load "modules/immediate_mode/immediate_renderer" +#load "modules/immediate_mode/gl_utils" diff --git a/progs/odin_example.onyx b/progs/odin_example.onyx index ab16c27b..1e6bf90b 100644 --- a/progs/odin_example.onyx +++ b/progs/odin_example.onyx @@ -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); diff --git a/src/onyxlex.c b/src/onyxlex.c index 0f60262b..b244065c 100644 --- a/src/onyxlex.c +++ b/src/onyxlex.c @@ -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; diff --git a/src/onyxsymres.c b/src/onyxsymres.c index ee64296b..e02f608e 100644 --- a/src/onyxsymres.c +++ b/src/onyxsymres.c @@ -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, ¶m->type_expr); }