1
Fork 0

odin(sdl-opengl-rendering): basic line rendering

This commit is contained in:
prescientmoon 2025-06-03 03:57:19 +02:00
parent 5c5cd55904
commit 30772e4255
Signed by: prescientmoon
SSH key fingerprint: SHA256:WFp/cO76nbarETAoQcQXuV+0h7XJsEsOCI0UsyPIy6U
11 changed files with 253 additions and 136 deletions

View file

@ -0,0 +1 @@
use flake

View file

@ -1 +1,2 @@
*.bin *.bin
!.envrc

View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1738680400, "lastModified": 1748693115,
"narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=", "narHash": "sha256-StSrWhklmDuXT93yc3GrTlb0cKSS0agTAxMGjLKAsY8=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "799ba5bffed04ced7067a91798353d360788b30d", "rev": "910796cabe436259a29a72e8d3f5e180fc6dfacc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,10 +34,61 @@
"type": "github" "type": "github"
} }
}, },
"odin": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1747681085,
"narHash": "sha256-tG177/u/vDUnxXeIFjJWYTRSuvYRfSCDYv3tjgdf+Ec=",
"owner": "starlitcanopy",
"repo": "odin",
"rev": "612433442cc03297474a31d3d40fce74ce3f5331",
"type": "github"
},
"original": {
"owner": "starlitcanopy",
"repo": "odin",
"type": "github"
}
},
"ols": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
],
"odin": [
"odin"
]
},
"locked": {
"lastModified": 1747682765,
"narHash": "sha256-uq7kh10bBIx/gnnYOUCO2YD5+4UaNeD1W6YSlrYtJyQ=",
"owner": "starlitcanopy",
"repo": "ols",
"rev": "81d3e8ecb1ade5c02c4769961027b3d86b7a6f94",
"type": "github"
},
"original": {
"owner": "starlitcanopy",
"repo": "ols",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs",
"odin": "odin",
"ols": "ols"
} }
}, },
"systems": { "systems": {

View file

@ -2,74 +2,42 @@
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
odin.url = "github:starlitcanopy/odin";
odin.inputs.nixpkgs.follows = "nixpkgs";
odin.inputs.flake-utils.follows = "flake-utils";
ols.url = "github:starlitcanopy/ols";
ols.inputs.nixpkgs.follows = "nixpkgs";
ols.inputs.flake-utils.follows = "flake-utils";
ols.inputs.odin.follows = "odin";
}; };
outputs = outputs =
inputs: inputs:
{ inputs.flake-utils.lib.eachSystem (with inputs.flake-utils.lib.system; [ x86_64-linux ]) (
overlays.default = final: prev: {
# {{{ Odin
odin = prev.odin.overrideAttrs (_: {
version = "unstable-2025-03-28";
src = final.fetchFromGitHub {
owner = "starlitcanopy";
repo = "Odin";
rev = "a1fc243f8df8b510e6de4f5e115fbfc09371cb9d";
sha256 = "0fb8lk47nb7ln0skjn3lyfi499q3wlnzp6w3qc4wf4s5zj43d6zh";
};
});
# }}}
# {{{ OLS
ols = prev.ols.overrideAttrs (_: {
version = "unstable-2025-03-12";
src = final.fetchFromGitHub {
owner = "DanielGavin";
repo = "ols";
rev = "1e44e3d78ad8a74ef09c7f54a6f6d3f7df517f8e";
sha256 = "16f7b8ijcaj5m2bdgbbl1q1mzgpgzzazrap2g17hkgy63aqq8qmf";
};
installPhase = ''
runHook preInstall
install -Dm755 ols odinfmt -t $out/bin/
cp -r builtin $out/bin/
wrapProgram $out/bin/ols --set-default ODIN_ROOT ${final.odin}/share
runHook postInstall
'';
});
# }}}
};
}
// inputs.flake-utils.lib.eachSystem (with inputs.flake-utils.lib.system; [ x86_64-linux ]) (
system: system:
let let
pkgs = inputs.nixpkgs.legacyPackages.${system}.extend inputs.self.overlays.default; pkgs = import inputs.nixpkgs {
cross = import inputs.nixpkgs { inherit system;
localSystem = system; overlays = [
crossSystem.config = "x86_64-w64-mingw32"; inputs.odin.overlays.default
inputs.ols.overlays.default
];
}; };
inherit (pkgs) lib; inherit (pkgs) lib;
in in
{ {
packages = {
inherit (pkgs) odin;
gcc = cross.callPackage (import ./gcc.nix) { };
libgl = cross.libGL;
};
# {{{ Shell # {{{ Shell
devShell = pkgs.mkShell rec { devShell = pkgs.mkShell rec {
nativeBuildInputs = [ nativeBuildInputs = [
pkgs.pkg-config pkgs.pkg-config
pkgs.entr # File change detection
pkgs.odin # Compiler pkgs.odin # Compiler
pkgs.mold # Linker pkgs.mold # Linker
pkgs.ols # Language server
pkgs.just # Script runner pkgs.just # Script runner
pkgs.samply # Profiler pkgs.samply # Profiler
pkgs.ols # Language server
pkgs.gdb # Debugger pkgs.gdb # Debugger
pkgs.seer # Debugger GUI pkgs.seer # Debugger GUI
pkgs.valgrind # Detect memory leaks pkgs.valgrind # Detect memory leaks

View file

@ -9,12 +9,16 @@ import "vendor:OpenGL"
import "vendor:sdl3" import "vendor:sdl3"
State :: struct { State :: struct {
tick: u32, tick: u32,
window: ^sdl3.Window, window: ^sdl3.Window,
program: u32, rect_program: u32,
rect_vao: VAO, circle_program: u32,
circle_vao: VAO, line_program: u32,
wireframe: bool, rect_vao: VAO,
circle_vao: VAO,
wireframe: bool,
buf_matrices: [INSTANCES]Mat3,
buf_colors: [INSTANCES]Color,
} }
// {{{ Initialization // {{{ Initialization
@ -98,26 +102,23 @@ init :: proc() -> (state: State, ok: bool) {
OpenGL.Enable(OpenGL.BLEND) OpenGL.Enable(OpenGL.BLEND)
OpenGL.BlendFunc(OpenGL.SRC_ALPHA, OpenGL.ONE_MINUS_SRC_ALPHA) OpenGL.BlendFunc(OpenGL.SRC_ALPHA, OpenGL.ONE_MINUS_SRC_ALPHA)
state.program = OpenGL.load_shaders_source( state.rect_program = OpenGL.load_shaders_source(
#load("./vert.glsl"), #load("./shaders/vert.glsl"),
#load("./frag.glsl"), #load("./shaders/rect.frag.glsl"),
) or_return
state.circle_program = OpenGL.load_shaders_source(
#load("./shaders/vert.glsl"),
#load("./shaders/circle.frag.glsl"),
) or_return
state.line_program = OpenGL.load_shaders_source(
#load("./shaders/vert.glsl"),
#load("./shaders/line.frag.glsl"),
) or_return ) or_return
state.rect_vao = create_vao({{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}, {0, 1, 2, 3}) or_return state.rect_vao = create_vao({{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}, {0, 1, 2, 3}) or_return
// state.circle_vao = create_vao({{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}, {0, 1, 2, 3}) or_return
CIRCLE_POINTS :: 127
circle_vertices: [CIRCLE_POINTS + 1]²
circle_indices: [CIRCLE_POINTS + 2]u32
for i in 0 ..< CIRCLE_POINTS {
θ := (i) * 2 * math.π / CIRCLE_POINTS
circle_vertices[i + 1] = {math.cos(θ), math.sin(θ)}
circle_indices[i + 1] = u32(i + 1)
}
circle_indices[CIRCLE_POINTS + 1] = circle_indices[1]
state.circle_vao = create_vao(circle_vertices[:], circle_indices[:]) or_return
init_command_queue() init_command_queue()
@ -126,7 +127,9 @@ init :: proc() -> (state: State, ok: bool) {
// }}} // }}}
// {{{ Close // {{{ Close
close :: proc(state: State) { close :: proc(state: State) {
OpenGL.DeleteProgram(state.program) OpenGL.DeleteProgram(state.rect_program)
OpenGL.DeleteProgram(state.circle_program)
_ = sdl3.StopTextInput(state.window) _ = sdl3.StopTextInput(state.window)
sdl3.DestroyWindow(state.window) sdl3.DestroyWindow(state.window)
sdl3.Quit() sdl3.Quit()
@ -136,16 +139,6 @@ close :: proc(state: State) {
// {{{ Render // {{{ Render
render :: proc(state: ^State) { render :: proc(state: ^State) {
state.tick += 1 state.tick += 1
OpenGL.Clear(OpenGL.COLOR_BUFFER_BIT | OpenGL.DEPTH_BUFFER_BIT)
OpenGL.UseProgram(state.program)
defer OpenGL.UseProgram(0)
if state.wireframe {
OpenGL.PolygonMode(OpenGL.FRONT_AND_BACK, OpenGL.LINE)
} else {
OpenGL.PolygonMode(OpenGL.FRONT_AND_BACK, OpenGL.FILL)
}
draw_rect({-0.5, 0}, {0.75, 0.5}, {1, 0, 0, 1}, z = 0.5) draw_rect({-0.5, 0}, {0.75, 0.5}, {1, 0, 0, 1}, z = 0.5)
draw_rect({0.5, 0.25}, {0.3, 0.5}, {0, 1, 0, 1}, z = -0.5) draw_rect({0.5, 0.25}, {0.3, 0.5}, {0, 1, 0, 1}, z = -0.5)
@ -171,6 +164,9 @@ render :: proc(state: ^State) {
} }
draw_circle({-0.25, -0.3}, 0.6, Color{0, 0, 0.5, 0.75}, z = -0.1) draw_circle({-0.25, -0.3}, 0.6, Color{0, 0, 0.5, 0.75}, z = -0.1)
draw_line({-0.25, 0}, {0.66, 0.4}, 0.01, Color{1, 1, 1, 1}, z = -0.5)
clear_screen()
render_queue(state) render_queue(state)
} }
// }}} // }}}

View file

@ -0,0 +1,13 @@
package visuals
:: f32
² :: [2]
³ :: [3]
:: uint
Mat3 :: matrix[3, 3]
Color :: [4]
// Get a vector perpendicular to the given input
vec2_perp :: proc(v: ²) -> ² {
return {-v.y, v.x}
}

View file

@ -0,0 +1,13 @@
#version 330
out vec4 FragColor;
in vec4 vertexColor;
in vec2 vertexPos;
void main() {
vec2 p = vertexPos;
float mask = p.x * p.x + p.y * p.y > 1 ? 0 : 1;
FragColor = mask * vertexColor;
}

View file

@ -0,0 +1,18 @@
#version 330
out vec4 FragColor;
in vec4 vertexColor;
in vec2 vertexPos;
float sdfLine(vec2 p) {
return abs(p.y) - 0.5;
}
void main() {
float aWidth = 0.001;
float dist = sdfLine(vertexPos);
float alpha = smoothstep(aWidth, -aWidth, dist);
FragColor = vec4(vertexColor.xyz, alpha * vertexColor.a);
}

View file

@ -5,9 +5,11 @@ layout (location = 1) in vec4 instanceFill;
layout (location = 2) in mat3 instanceMatrix; layout (location = 2) in mat3 instanceMatrix;
out vec4 vertexColor; out vec4 vertexColor;
out vec2 vertexPos;
void main() { void main() {
vec3 pos = instanceMatrix * vec3(aPos.xy, 1); vec3 pos = instanceMatrix * vec3(aPos.xy, 1);
gl_Position = vec4(pos.xyz, 1); gl_Position = vec4(pos.xyz, 1);
vertexColor = instanceFill; vertexColor = instanceFill;
vertexPos = aPos.xy;
} }

View file

@ -1,21 +1,15 @@
package visuals package visuals
import "core:log" import "core:log"
import "core:math/linalg"
import "vendor:OpenGL" import "vendor:OpenGL"
// {{{ Math constants
:: f32
² :: [2]
³ :: [3]
:: uint
Mat3 :: matrix[3, 3]
Color :: [4]
// }}}
// {{{ Command queues // {{{ Command queues
Shape :: struct { Shape :: struct {
z: , z: ,
fill: Color, fill: Color,
} }
Rect :: struct { Rect :: struct {
using shape: Shape, using shape: Shape,
top_left: ², top_left: ²,
@ -28,9 +22,17 @@ Circle :: struct {
radius: , radius: ,
} }
Line :: struct {
using shape: Shape,
from: ²,
to: ²,
thickness: ,
}
Command_Queue :: struct { Command_Queue :: struct {
rects: [dynamic]Rect, rects: [dynamic]Rect,
circles: [dynamic]Circle, circles: [dynamic]Circle,
lines: [dynamic]Line,
} }
queue: ^Command_Queue queue: ^Command_Queue
@ -38,7 +40,9 @@ queue: ^Command_Queue
init_command_queue :: proc() { init_command_queue :: proc() {
queue = new(Command_Queue) queue = new(Command_Queue)
queue^ = Command_Queue { queue^ = Command_Queue {
rects = make([dynamic]Rect), rects = make([dynamic]Rect),
circles = make([dynamic]Circle),
lines = make([dynamic]Line),
} }
} }
@ -71,6 +75,21 @@ draw_circle :: proc {
draw_circle_struct, draw_circle_struct,
draw_circle_args, draw_circle_args,
} }
draw_line_args :: proc(from, to: ², thickness: , color: Color, z: = 0) {
draw_line_struct(
Line{from = from, to = to, thickness = thickness, shape = Shape{z = z, fill = color}},
)
}
draw_line_struct :: proc(line: Line) {
append(&queue.lines, line)
}
draw_line :: proc {
draw_line_struct,
draw_line_args,
}
// }}} // }}}
// {{{ VAO & consts // {{{ VAO & consts
@ -151,12 +170,29 @@ create_vao :: proc(vertices: []ℝ², indices: []u32) -> (out: VAO, ok: bool) {
return out, true return out, true
} }
// }}} // }}}
// {{{ Set rect transforms // {{{ Set transforms
set_rect_transforms :: proc(vao: ^VAO, rects: []Rect) -> { // Commit the contents of `state.buf_matrices` and `state.buf_colors` to the
log.assert(len(rects) <= INSTANCES, "Attempting to send too many rects to the GPU") // GPU.
matrices := new([INSTANCES]Mat3, context.temp_allocator) commit_buffers :: proc(state: ^State, vao: ^VAO) {
fills := new([INSTANCES]Color, context.temp_allocator) OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_mat_buffer)
OpenGL.BufferData(
OpenGL.ARRAY_BUFFER,
INSTANCES * size_of(Mat3),
&state.buf_matrices,
OpenGL.DYNAMIC_DRAW,
)
OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_fill_buffer)
OpenGL.BufferData(
OpenGL.ARRAY_BUFFER,
INSTANCES * size_of(Color),
&state.buf_colors,
OpenGL.DYNAMIC_DRAW,
)
}
set_rect_transforms :: proc(state: ^State, vao: ^VAO, rects: []Rect) -> {
log.assert(len(rects) <= INSTANCES, "Attempting to send too many rects to the GPU")
for rect, i in rects { for rect, i in rects {
// This matrix must transform the rect [-1, 1]² into the desired rect // This matrix must transform the rect [-1, 1]² into the desired rect
mat: Mat3 mat: Mat3
@ -167,27 +203,15 @@ set_rect_transforms :: proc(vao: ^VAO, rects: []Rect) -> {
mat[2].xy = center.xy mat[2].xy = center.xy
mat[2].z = rect.z mat[2].z = rect.z
matrices[i] = mat state.buf_matrices[i] = mat
fills[i] = rect.fill state.buf_colors[i] = rect.fill
} }
OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_mat_buffer)
OpenGL.BufferData(
OpenGL.ARRAY_BUFFER,
INSTANCES * size_of(Mat3),
matrices,
OpenGL.DYNAMIC_DRAW,
)
OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_fill_buffer)
OpenGL.BufferData(OpenGL.ARRAY_BUFFER, INSTANCES * size_of(Color), fills, OpenGL.DYNAMIC_DRAW)
return len(rects) return len(rects)
} }
set_circle_transforms :: proc(vao: ^VAO, circles: []Circle) -> { set_circle_transforms :: proc(state: ^State, vao: ^VAO, circles: []Circle) -> {
log.assert(len(circles) <= INSTANCES, "Attempting to send too many circles to the GPU") log.assert(len(circles) <= INSTANCES, "Attempting to send too many circles to the GPU")
matrices := new([INSTANCES]Mat3, context.temp_allocator)
fills := new([INSTANCES]Color, context.temp_allocator)
for circle, i in circles { for circle, i in circles {
mat: Mat3 mat: Mat3
@ -197,22 +221,31 @@ set_circle_transforms :: proc(vao: ^VAO, circles: []Circle) -> {
mat[2].xy = circle.center.xy mat[2].xy = circle.center.xy
mat[2].z = circle.z mat[2].z = circle.z
matrices[i] = mat state.buf_matrices[i] = mat
fills[i] = circle.fill state.buf_colors[i] = circle.fill
} }
OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_mat_buffer)
OpenGL.BufferData(
OpenGL.ARRAY_BUFFER,
INSTANCES * size_of(Mat3),
matrices,
OpenGL.DYNAMIC_DRAW,
)
OpenGL.BindBuffer(OpenGL.ARRAY_BUFFER, vao.instance_fill_buffer)
OpenGL.BufferData(OpenGL.ARRAY_BUFFER, INSTANCES * size_of(Color), fills, OpenGL.DYNAMIC_DRAW)
return len(circles) return len(circles)
} }
set_line_transforms :: proc(state: ^State, vao: ^VAO, lines: []Line) -> {
log.assert(len(lines) <= INSTANCES, "Attempting to send too many lines to the GPU")
for line, i in lines {
mat: Mat3
dir := line.to - line.from
mat[0].xy = dir / 2
mat[1].xy = vec2_perp(linalg.normalize0(dir)) * line.thickness
mat[2].xy = (line.from + line.to) / 2
mat[2].z = line.z
state.buf_matrices[i] = mat
state.buf_colors[i] = line.fill
}
return len(lines)
}
// }}} // }}}
// {{{ Render the entire queue // {{{ Render the entire queue
draw_instances :: proc(vao: VAO, instances: ) { draw_instances :: proc(vao: VAO, instances: ) {
@ -226,26 +259,47 @@ draw_instances :: proc(vao: VAO, instances: ) {
) )
} }
render_queue :: proc(state: ^State) { clear_screen :: proc() {
rect_steps := len(queue.rects) / INSTANCES OpenGL.Clear(OpenGL.COLOR_BUFFER_BIT | OpenGL.DEPTH_BUFFER_BIT)
}
render_queue :: proc(state: ^State) {
if state.wireframe {
OpenGL.PolygonMode(OpenGL.FRONT_AND_BACK, OpenGL.LINE)
} else {
OpenGL.PolygonMode(OpenGL.FRONT_AND_BACK, OpenGL.FILL)
}
OpenGL.UseProgram(state.rect_program)
for i := 0; i < len(queue.rects); i += INSTANCES { for i := 0; i < len(queue.rects); i += INSTANCES {
slice := queue.rects[i:] slice := queue.rects[i:]
if len(slice) > INSTANCES {slice = slice[:INSTANCES]} if len(slice) > INSTANCES {slice = slice[:INSTANCES]}
instances := set_rect_transforms(&state.rect_vao, slice) instances := set_rect_transforms(state, &state.rect_vao, slice)
commit_buffers(state, &state.rect_vao)
draw_instances(state.rect_vao, instances) draw_instances(state.rect_vao, instances)
} }
clear(&queue.rects) clear(&queue.rects)
circle_steps := len(queue.circles) / INSTANCES
OpenGL.UseProgram(state.circle_program)
for i := 0; i < len(queue.circles); i += INSTANCES { for i := 0; i < len(queue.circles); i += INSTANCES {
slice := queue.circles[i:] slice := queue.circles[i:]
if len(slice) > INSTANCES {slice = slice[:INSTANCES]} if len(slice) > INSTANCES {slice = slice[:INSTANCES]}
instances := set_circle_transforms(&state.circle_vao, slice) instances := set_circle_transforms(state, &state.rect_vao, slice)
draw_instances(state.circle_vao, instances) commit_buffers(state, &state.rect_vao)
draw_instances(state.rect_vao, instances)
} }
clear(&queue.circles) clear(&queue.circles)
OpenGL.UseProgram(state.line_program)
for i := 0; i < len(queue.lines); i += INSTANCES {
slice := queue.lines[i:]
if len(slice) > INSTANCES {slice = slice[:INSTANCES]}
instances := set_line_transforms(state, &state.rect_vao, slice)
commit_buffers(state, &state.rect_vao)
draw_instances(state.rect_vao, instances)
}
clear(&queue.lines)
OpenGL.UseProgram(0)
} }
// }}} // }}}