From 801f15a9dc9b4b5d87b51ae9a78375287c4aacfd Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sat, 13 Feb 2021 16:13:11 -0600 Subject: [PATCH] maybe have a working immediate renderer --- .gitignore | 3 +- build.sh | 3 + lib/events.onyx | 104 ++++++++++++++++++++++++++++ lib/gl/gl_utils.onyx | 73 +++++++++++++++++++ lib/imgui.onyx | 2 +- lib/immediate_renderer.onyx | 98 ++++++++++++++++++++++++++ lib/shaders/immediate_fragment.glsl | 14 ++++ lib/shaders/immediate_vertex.glsl | 18 +++++ onyx-imgui.sublime-project | 3 + test/basic.onyx | 40 ++++++++++- 10 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 build.sh create mode 100644 lib/events.onyx create mode 100644 lib/gl/gl_utils.onyx create mode 100644 lib/immediate_renderer.onyx create mode 100644 lib/shaders/immediate_fragment.glsl create mode 100644 lib/shaders/immediate_vertex.glsl diff --git a/.gitignore b/.gitignore index c51a0ea..3805cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -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 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 index 0000000..8ac6025 --- /dev/null +++ b/lib/events.onyx @@ -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 index 0000000..beb5c16 --- /dev/null +++ b/lib/gl/gl_utils.onyx @@ -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; + } + } +} + diff --git a/lib/imgui.onyx b/lib/imgui.onyx index f35030f..7b8db6d 100644 --- a/lib/imgui.onyx +++ b/lib/imgui.onyx @@ -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 index 0000000..2b7840d --- /dev/null +++ b/lib/immediate_renderer.onyx @@ -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 index 0000000..37bdf80 --- /dev/null +++ b/lib/shaders/immediate_fragment.glsl @@ -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 index 0000000..e8531ed --- /dev/null +++ b/lib/shaders/immediate_vertex.glsl @@ -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 diff --git a/onyx-imgui.sublime-project b/onyx-imgui.sublime-project index 24db303..21fd46a 100644 --- a/onyx-imgui.sublime-project +++ b/onyx-imgui.sublime-project @@ -3,6 +3,9 @@ [ { "path": "." + }, + { + "path": "C:\\dev\\onyx\\core" } ] } diff --git a/test/basic.onyx b/test/basic.onyx index f867063..1a1a32f 100644 --- a/test/basic.onyx +++ b/test/basic.onyx @@ -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(); +} -- 2.25.1