Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-08-23 23:54:00 +02:00
commit ac0d491786
21 changed files with 1094 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
sokol
*.dSYM
sokol-ecs
sokol-shdc

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
build-shaders:
./sokol-shdc -i shader.glsl -o shader.odin -l metal_macos -f sokol_odin
build: build-shaders
odin build . -use-single-module
build-debug: build-shaders
odin build . -debug -use-single-module
run: build-shaders
odin run . -use-single-module
run-debug: build-shaders
odin run . -debug -use-single-module
check: build-shaders
odin strip-semicolon .
odin check .
odin test . -use-single-module

View file

@ -0,0 +1,66 @@
package ecs
import "core:log"
ComponentManager :: struct {
component_types: map[typeid]ComponentType,
component_pools: map[typeid]^ComponentPool(any),
next_component_type: ComponentType,
}
component_manager_create :: proc() -> ComponentManager {
component_manager := ComponentManager {
component_types = make(map[typeid]ComponentType, context.allocator),
component_pools = make(map[typeid]^ComponentPool(any), context.allocator),
next_component_type = 0
}
return component_manager
}
component_manager_register_component :: proc($T: typeid, component_manager: ^ComponentManager) {
log.assertf(T not_in component_manager.component_types, "Registering component more than once")
component_manager.component_types[T] = component_manager.next_component_type
component_pool := new(ComponentPool(T), context.allocator)
component_pool^ = component_pool_create(T)
component_manager.component_pools[T] = cast(^ComponentPool(any))component_pool
component_manager.next_component_type += 1
}
component_manager_get_component_type :: proc($T: typeid, component_manager: ^ComponentManager) -> ComponentType {
log.assertf(T in component_manager.component_types, "Component not registered before use")
return component_manager.component_types[T]
}
component_manager_add_component :: proc($T: typeid, component_manager: ^ComponentManager, entity_id: EntityID, component: T) {
component_pool := cast(^ComponentPool(T))component_manager.component_pools[T]
component_pool_insert_data(T, component_pool, entity_id, component)
}
component_manager_remove_component :: proc($T: typeid, component_manager: ^ComponentManager, entity_id: EntityID) {
component_pool := cast(^ComponentPool(T))component_manager.component_pools[T]
component_pool_remove_data(T, component_pool, entity_id)
}
component_manager_get_component :: proc($T: typeid, component_manager: ^ComponentManager, entity_id: EntityID) -> ^T {
component_pool := cast(^ComponentPool(T))component_manager.component_pools[T]
return component_pool_get(T, component_pool, entity_id)
}
component_manager_destroy_entity :: proc(component_manager: ^ComponentManager, entity_id: EntityID) {
for _, component_pool in component_manager.component_pools {
component_pool_destroy_entity(component_pool, entity_id)
}
}
component_manager_destroy :: proc(component_manager: ^ComponentManager) {
for _, &component_pool in component_manager.component_pools {
component_pool_destroy(component_pool)
free(component_pool)
}
delete(component_manager.component_pools)
delete(component_manager.component_types)
}

68
ecs/component_pool.odin Normal file
View file

@ -0,0 +1,68 @@
package ecs
import "core:log"
ComponentPool :: struct($T: typeid) {
data: []T,
entity_to_index: map[EntityID]uintptr,
index_to_entity: map[uintptr]EntityID,
size: uintptr,
}
component_pool_create :: proc($T: typeid) -> ComponentPool(T) {
component_pool := ComponentPool(T) {
data = make([]T, ENTITY_MAX),
entity_to_index = make(map[EntityID]uintptr, context.allocator),
index_to_entity = make(map[uintptr]EntityID, context.allocator),
size = 0,
}
return component_pool
}
component_pool_insert_data :: proc($T: typeid, component_pool: ^ComponentPool(T), entity_id: EntityID, component: T) {
log.assertf(entity_id not_in component_pool.entity_to_index, "Component already added to entity")
new_idx := component_pool.size
component_pool.entity_to_index[entity_id] = new_idx
component_pool.index_to_entity[new_idx] = entity_id
component_pool.data[new_idx] = component
component_pool.size += 1
}
component_pool_remove_data :: proc(component_pool: ^ComponentPool(any), entity_id: EntityID) {
log.assertf(entity_id in component_pool.entity_to_index, "Entity doesn't have component")
removed_entity_idx := component_pool.entity_to_index[entity_id]
last_element_idx := component_pool.size - 1
component_pool.data[removed_entity_idx] = component_pool.data[last_element_idx]
last_element_entity_id := component_pool.index_to_entity[last_element_idx]
component_pool.entity_to_index[last_element_entity_id] = removed_entity_idx
component_pool.index_to_entity[removed_entity_idx] = last_element_entity_id
delete_key(&component_pool.entity_to_index, entity_id)
delete_key(&component_pool.index_to_entity, last_element_idx)
component_pool.size -= 1
}
component_pool_get :: proc($T: typeid, component_pool: ^ComponentPool(T), entity_id: EntityID) -> ^T {
log.assertf(entity_id in component_pool.entity_to_index, "Entity doesn't have component")
idx := component_pool.entity_to_index[entity_id]
return &component_pool.data[idx]
}
component_pool_destroy_entity :: proc(component_pool: ^ComponentPool(any), entity_id: EntityID) {
if entity_id in component_pool.entity_to_index {
component_pool_remove_data(component_pool, entity_id)
}
}
component_pool_destroy :: proc(component_pool: ^ComponentPool(any)) {
delete(component_pool.entity_to_index)
delete(component_pool.index_to_entity)
delete(component_pool.data)
}

9
ecs/constants.odin Normal file
View file

@ -0,0 +1,9 @@
package ecs
ENTITY_MAX :: 4096
COMPONENT_MAX :: 32
ID :: u32
EntityID :: ID
ComponentType :: u8

82
ecs/coordinator.odin Normal file
View file

@ -0,0 +1,82 @@
package ecs
Coordinator :: struct {
component_manager: ^ComponentManager,
entity_manager: ^EntityManager,
system_manager: ^SystemManager,
}
coordinator_create :: proc() -> Coordinator {
coordinator := Coordinator{}
coordinator.component_manager = new(ComponentManager, context.allocator)
coordinator.component_manager^ = component_manager_create()
coordinator.entity_manager = new(EntityManager, context.allocator)
coordinator.entity_manager^ = entity_manager_create()
coordinator.system_manager = new(SystemManager, context.allocator)
coordinator.system_manager^ = system_manager_create()
return coordinator
}
coordinator_create_entity :: proc(coordinator: ^Coordinator) -> EntityID {
return entity_manager_create_entity(coordinator.entity_manager)
}
coordinator_destroy_entity :: proc(coordinator: ^Coordinator, entity_id: EntityID) {
component_manager_destroy_entity(coordinator.component_manager, entity_id)
entity_manager_destroy_entity(coordinator.entity_manager, entity_id)
system_manager_destroy_entity(coordinator.system_manager, entity_id)
}
coordinator_register_component :: proc($T: typeid, coordinator: ^Coordinator) {
component_manager_register_component(T, coordinator.component_manager)
}
coordinator_add_component :: proc($T: typeid, coordinator: ^Coordinator, entity_id: EntityID, component: T) {
component_manager_add_component(T, coordinator.component_manager, entity_id, component)
signature := entity_manager_get_signature(coordinator.entity_manager, entity_id)
signature_set(&signature, component_manager_get_component_type(T, coordinator.component_manager))
entity_manager_set_signature(coordinator.entity_manager, entity_id, signature)
system_manager_change_entity_signature(coordinator.system_manager, entity_id, signature)
}
coordinator_remove_component :: proc($T: typeid, coordinator: ^Coordinator, entity_id: EntityID) {
component_manager_remove_component(T, coordinator.component_manager, entity_id)
signature := entity_manager_get_signature(coordinator.entity_manager, entity_id)
signature_unset(&signature, component_manager_get_component_type(T, coordinator.component_manager))
entity_manager_set_signature(coordinator.entity_manager, entity_id, signature)
system_manager_change_entity_signature(coordinator.system_manager, entity_id, signature)
}
coordinator_get_component :: proc($T: typeid, coordinator: ^Coordinator, entity_id: EntityID) -> ^T {
return component_manager_get_component(T, coordinator.component_manager, entity_id)
}
coordinator_get_component_type :: proc($T: typeid, coordinator: ^Coordinator) -> ComponentType {
return component_manager_get_component_type(T, coordinator.component_manager)
}
coordinator_register_system :: proc($T: typeid, coordinator: ^Coordinator) -> ^T {
return system_manager_register_system(T, coordinator.system_manager)
}
coordinator_set_system_signature :: proc($T: typeid, coordinator: ^Coordinator, signature: Signature) {
system_manager_set_signature(T, coordinator.system_manager, signature)
}
coordinator_destroy :: proc(coordinator: ^Coordinator) {
component_manager_destroy(coordinator.component_manager)
entity_manager_destroy(coordinator.entity_manager)
system_manager_destroy(coordinator.system_manager)
free(coordinator.component_manager)
free(coordinator.entity_manager)
free(coordinator.system_manager)
}

54
ecs/entity_manager.odin Normal file
View file

@ -0,0 +1,54 @@
package ecs
import "core:log"
import "core:container/queue"
EntityManager :: struct {
available_entities: queue.Queue(EntityID),
signatures: [ENTITY_MAX]Signature,
living_entity_count: u32,
}
entity_manager_create :: proc() -> EntityManager {
entity_manager := EntityManager{living_entity_count = 0}
queue.init(&entity_manager.available_entities)
for entity_id in 0..<ENTITY_MAX {
queue.push_back(&entity_manager.available_entities, cast(EntityID)entity_id)
}
return entity_manager
}
entity_manager_create_entity :: proc(entity_manager: ^EntityManager) -> EntityID {
log.assertf(entity_manager.living_entity_count < ENTITY_MAX, "Too many entities created")
entity_id := queue.pop_front(&entity_manager.available_entities)
entity_manager.living_entity_count += 1
return entity_id
}
entity_manager_destroy_entity :: proc(entity_manager: ^EntityManager, entity_id: EntityID) {
log.assertf(entity_id < ENTITY_MAX, "Entity ID out of range")
signature_clear(&entity_manager.signatures[entity_id])
queue.push_back(&entity_manager.available_entities, entity_id)
entity_manager.living_entity_count -= 1
}
entity_manager_set_signature :: proc(entity_manager: ^EntityManager, entity_id: EntityID, signature: Signature) {
log.assertf(entity_id < ENTITY_MAX, "Entity ID out of range")
entity_manager.signatures[entity_id] = signature
}
entity_manager_get_signature :: proc(entity_manager: ^EntityManager, entity_id: EntityID) -> Signature {
log.assertf(entity_id < ENTITY_MAX, "Entity ID out of range")
return entity_manager.signatures[entity_id]
}
entity_manager_destroy :: proc(entity_manager: ^EntityManager) {
queue.destroy(&entity_manager.available_entities)
}

34
ecs/physics_system.odin Normal file
View file

@ -0,0 +1,34 @@
package ecs
Vec3 :: distinct [3]f32
Gravity :: struct {
force: Vec3
}
RigidBody :: struct {
velocity: Vec3,
acceleration: Vec3,
}
Transform :: struct {
position: Vec3,
rotation: Vec3,
scale: Vec3,
}
PhysicsSystem :: struct {
using base: SystemBase,
}
physics_system_update :: proc(physics_system: ^PhysicsSystem, coordinator: ^Coordinator, dt: f32) {
for entity in physics_system.entities {
rigid_body := coordinator_get_component(RigidBody, coordinator, entity)
transform := coordinator_get_component(Transform, coordinator, entity)
gravity := coordinator_get_component(Gravity, coordinator, entity)
transform.position += rigid_body.velocity * dt
rigid_body.velocity += gravity.force * dt
}
}

10
ecs/render_system.odin Normal file
View file

@ -0,0 +1,10 @@
package ecs
RenderSystem :: struct {
using base: SystemBase,
}
render_system_update :: proc(render_system: ^RenderSystem, coordinator: ^Coordinator) {
for entity in render_system.entities {
}
}

19
ecs/signature.odin Normal file
View file

@ -0,0 +1,19 @@
package ecs
Signature :: bit_set[0..<COMPONENT_MAX]
signature_create :: proc() -> Signature {
return Signature{}
}
signature_set :: proc(signature: ^Signature, type: ComponentType) {
signature^ += {cast(int)type}
}
signature_unset :: proc(signature: ^Signature, type: ComponentType) {
signature^ -= {cast(int)type}
}
signature_clear :: proc(signature: ^Signature) {
signature^ = {}
}

65
ecs/system_manager.odin Normal file
View file

@ -0,0 +1,65 @@
package ecs
import "base:intrinsics"
import "core:log"
SystemBase :: struct {
entities: map[EntityID]struct{}, // Treat as set
}
SystemManager :: struct {
signatures: map[typeid]Signature,
systems: map[typeid]^SystemBase
}
system_manager_create :: proc() -> SystemManager {
system_manager := SystemManager{
signatures = make(map[typeid]Signature, context.allocator),
systems = make(map[typeid]^SystemBase, context.allocator),
}
return system_manager
}
system_manager_register_system :: proc($T: typeid, system_manager: ^SystemManager) -> ^T {
log.assertf(T not_in system_manager.systems, "Registering system more than once")
log.assertf(intrinsics.type_has_field(T, "entities"), "Registering non system type")
system := new(T, context.allocator)
system.entities = make(map[EntityID]struct{})
system_manager.systems[T] = system
return system
}
system_manager_set_signature :: proc($T: typeid, system_manager: ^SystemManager, signature: Signature) {
log.assertf(T in system_manager.systems, "System used before registered")
system_manager.signatures[T] = signature
}
system_manager_destroy_entity :: proc(system_manager: ^SystemManager, entity_id: EntityID) {
for _, system in system_manager.systems {
delete_key(&system.entities, entity_id)
}
}
system_manager_change_entity_signature :: proc(system_manager: ^SystemManager, entity_id: EntityID, entity_signature: Signature) {
for type, system in system_manager.systems {
system_signature := system_manager.signatures[type]
if (entity_signature & system_signature) == system_signature {
system.entities[entity_id] = {}
} else {
delete_key(&system.entities, entity_id)
}
}
}
system_manager_destroy :: proc(system_manager: ^SystemManager) {
for _, system in system_manager.systems {
delete(system.entities)
free(system)
}
delete(system_manager.systems)
delete(system_manager.signatures)
}

417
main.odin Normal file
View file

@ -0,0 +1,417 @@
package main
import "base:runtime"
import "base:intrinsics"
import "core:log"
import "core:mem"
import "core:time"
import "core:fmt"
import "core:testing"
import "core:strings"
import "core:path/filepath"
import "core:math"
import "core:math/linalg"
import "core:os"
import stbi "vendor:stb/image"
import sa "sokol/app"
import sh "sokol/helpers"
import sg "sokol/gfx"
import "ecs"
Vec2 :: [2]f32
Vec3 :: [3]f32
Vec4 :: [4]f32
Mat4 :: matrix[4, 4]f32
VertexData :: struct {
pos: Vec3,
col: sg.Color,
uv: Vec2,
}
Scene :: struct {
physics_system: ^ecs.PhysicsSystem,
entities: []ecs.EntityID,
}
Globals :: struct {
shader: sg.Shader,
pipeline: sg.Pipeline,
vertex_buffer: sg.Buffer,
index_buffer: sg.Buffer,
image: sg.Image,
sampler: sg.Sampler,
rotation: f32,
camera: struct {
pos: Vec3,
target: Vec3,
look: Vec2,
},
coordinator: ecs.Coordinator,
scene: Scene,
dt: f32,
}
g: ^Globals
mouse_move: Vec2
key_down: #sparse[sa.Keycode]bool
default_context: runtime.Context
main :: proc() {
context.logger = log.create_console_logger()
tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, context.allocator)
context.allocator = mem.tracking_allocator(&tracking_allocator)
defer reset_tracking_allocator(&tracking_allocator)
default_context = context
sa.run({
window_title = "Ecs Test",
allocator = sa.Allocator(sh.allocator(&default_context)),
logger = sa.Logger(sh.logger(&default_context)),
init_cb = init_cb,
frame_cb = frame_cb,
cleanup_cb = cleanup_cb,
event_cb = event_cb,
})
g.coordinator = ecs.coordinator_create()
defer ecs.coordinator_destroy(&g.coordinator)
g.scene = create_scene(&g.coordinator)
defer delete(g.scene.entities)
}
init_cb :: proc "c" () {
context = default_context
sg.setup({
environment = sh.glue_environment(),
allocator = sg.Allocator(sh.allocator(&default_context)),
logger = sg.Logger(sh.logger(&default_context)),
})
sa.show_mouse(false)
sa.lock_mouse(true)
g = new(Globals)
g.camera = {
pos = { 0, 0, 2 },
target = { 0, 0, 1 },
}
g.shader = sg.make_shader(main_shader_desc(sg.query_backend()))
g.pipeline = sg.make_pipeline({
shader = g.shader,
layout = {
attrs = {
ATTR_main_pos = { format = .FLOAT3 },
ATTR_main_col = { format = .FLOAT4 },
ATTR_main_uv = { format = .FLOAT2 },
},
},
index_type = .UINT16,
cull_mode = .BACK,
depth = {
pixel_format = .DEFAULT,
write_enabled = true,
bias = 0.001,
bias_clamp = 0.0,
bias_slope_scale = 1.0,
compare = .LESS_EQUAL,
},
})
vertices := []VertexData {
{ pos = { -0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { 0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { 0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
{ pos = { -0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { -0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { 0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { 0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { -0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
{ pos = { -0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { 0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { 0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
{ pos = { -0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { -0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { 0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { 0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
{ pos = { -0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { 0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { 0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { 0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
{ pos = { 0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { -0.5, -0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 0 } },
{ pos = { -0.5, -0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 0 } },
{ pos = { -0.5, 0.5, -0.5 }, col = { 1, 1, 1, 1 }, uv = { 0, 1 } },
{ pos = { -0.5, 0.5, 0.5 }, col = { 1, 1, 1, 1 }, uv = { 1, 1 } },
}
g.vertex_buffer = sg.make_buffer({
data = sg_range(vertices)
})
indices := []u16 {
1, 0, 2,
3, 2, 0,
7, 4, 6,
5, 6, 4,
9, 8, 10,
11, 10, 8,
15, 12, 14,
13, 14, 12,
17, 16, 18,
19, 18, 16,
23, 20, 22,
21, 22, 20,
}
g.index_buffer = sg.make_buffer({
usage = { index_buffer = true },
data = sg_range(indices),
})
w, h: i32
pixels := stbi.load("res/texture_blue.png", &w, &h, nil, 4)
assert(pixels != nil)
defer(stbi.image_free(pixels))
g.image = sg.make_image({
width = w,
height = h,
pixel_format = .RGBA8,
data = {
subimage = {
0 = {
0 = {
ptr = pixels,
size = uint(w * h * 4)
}
}
}
}
})
g.sampler = sg.make_sampler({})
}
frame_cb:: proc "c" () {
context = default_context
if key_down[.ESCAPE] {
sa.quit()
return
}
start_time := time.now()
//ecs.physics_system_update(g.scene.physics_system, &g.coordinator, g.dt) // seg fault
g.dt = f32(sa.frame_duration())
update_camera(g.dt)
g.rotation += linalg.to_radians(50 * g.dt)
proj := linalg.matrix4_perspective_f32(70, sa.widthf() / sa.heightf(), 0.001, 1000)
view := linalg.matrix4_look_at_f32(g.camera.pos, g.camera.target, { 0, 1, 0 } )
Object :: struct {
pos: Vec3,
rot: Vec3,
}
objects := []Object {
{ { -2, 0, 0 }, { 0, 0, 0 } },
{ { 0, 0, 0 }, { 0, 0, 0 } },
{ { 2, 0, 0 }, { 0, 0, 0 } },
}
sg.begin_pass({ swapchain = sh.glue_swapchain() })
sg.apply_pipeline(g.pipeline)
sg.apply_bindings({
vertex_buffers = { 0 = g.vertex_buffer },
index_buffer = g.index_buffer,
images = { IMG_tex = g.image },
samplers = { SMP_smp = g.sampler },
})
for obj in objects {
model := linalg.matrix4_translate_f32(obj.pos) * linalg.matrix4_from_yaw_pitch_roll_f32(obj.rot.y, obj.rot.x, obj.rot.z)
sg.apply_uniforms(UB_VsParams, sg_range(&Vsparams {
mvp = proj * view * model
}))
sg.draw(0, 36, 1)
}
sg.end_pass()
sg.commit()
mouse_move = {}
}
update_camera :: proc(dt: f32) {
MOVE_SPEED :: 3
LOOK_SENSITIVITY :: 0.15
move_input: Vec3
if key_down[.W] do move_input.y = 1
else if key_down[.S] do move_input.y = -1
if key_down[.D] do move_input.x = 1
else if key_down[.A] do move_input.x = -1
if key_down[.SPACE] do move_input.z = 1
else if key_down[.LEFT_SHIFT] do move_input.z = -1
look_input: Vec2 = -mouse_move * LOOK_SENSITIVITY
g.camera.look += look_input
g.camera.look.x = math.wrap(g.camera.look.x, 360)
g.camera.look.y = math.clamp(g.camera.look.y, -89.5, 89.5)
look_mat := linalg.matrix4_from_yaw_pitch_roll_f32(
linalg.to_radians(g.camera.look.x),
linalg.to_radians(g.camera.look.y),
0,
)
forward := (look_mat * Vec4{0, 0, -1, 1}).xyz
right := (look_mat * Vec4{1, 0, 0, 1}).xyz
up := (look_mat * Vec4{0, 1, 0, 1}).xyz
move_dir := forward * move_input.y + right * move_input.x + up * move_input.z
motion := linalg.normalize0(move_dir) * MOVE_SPEED * dt
g.camera.pos += motion
g.camera.target = g.camera.pos + forward
}
cleanup_cb :: proc "c" () {
context = default_context
sg.destroy_buffer(g.index_buffer)
sg.destroy_buffer(g.vertex_buffer)
sg.destroy_image(g.image)
sg.destroy_sampler(g.sampler)
sg.destroy_pipeline(g.pipeline)
sg.destroy_shader(g.shader)
free(g)
sg.shutdown()
}
event_cb :: proc "c" (event: ^sa.Event) {
context = default_context
#partial switch event.type {
case .MOUSE_MOVE:
mouse_move += {event.mouse_dx, event.mouse_dy}
case .KEY_DOWN:
key_down[event.key_code] = true
case .KEY_UP:
key_down[event.key_code] = false
}
}
create_scene :: proc(coordinator: ^ecs.Coordinator) -> Scene {
scene := Scene{}
ecs.coordinator_register_component(ecs.Gravity, coordinator)
ecs.coordinator_register_component(ecs.RigidBody, coordinator)
ecs.coordinator_register_component(ecs.Transform, coordinator)
scene.physics_system = ecs.coordinator_register_system(ecs.PhysicsSystem, coordinator)
signature := ecs.signature_create()
ecs.signature_set(&signature, ecs.coordinator_get_component_type(ecs.Gravity, coordinator))
ecs.signature_set(&signature, ecs.coordinator_get_component_type(ecs.RigidBody, coordinator))
ecs.signature_set(&signature, ecs.coordinator_get_component_type(ecs.Transform, coordinator))
ecs.coordinator_set_system_signature(ecs.PhysicsSystem, coordinator, signature)
scene.entities = make([]ecs.EntityID, ecs.ENTITY_MAX)
for &entity in scene.entities {
entity = ecs.coordinator_create_entity(coordinator)
ecs.coordinator_add_component(
ecs.Gravity,
coordinator,
entity,
ecs.Gravity{
ecs.Vec3{0.0, -9.82, 0.0}
})
ecs.coordinator_add_component(
ecs.RigidBody,
coordinator,
entity,
ecs.RigidBody{
velocity = ecs.Vec3{0.0, 0.0, 0.0},
acceleration = ecs.Vec3{0.0, 0.0, 0.0},
})
ecs.coordinator_add_component(
ecs.Transform,
coordinator,
entity,
ecs.Transform{
position = ecs.Vec3{0.0, 0.0, 0.0},
rotation = ecs.Vec3{0.0, 0.0, 0.0},
scale = ecs.Vec3{1.0, 1.0, 1.0},
})
}
return scene
}
sg_range :: proc {
sg_range_from_slice,
sg_range_from_struct,
}
sg_range_from_slice :: proc(s: []$T) -> sg.Range {
return {
ptr = raw_data(s),
size = len(s) * size_of(T),
}
}
sg_range_from_struct :: proc(s: ^$T) -> sg.Range where intrinsics.type_is_struct(T) {
return {
ptr = s,
size = size_of(T),
}
}
reset_tracking_allocator :: proc(track: ^mem.Tracking_Allocator) {
if len(track.allocation_map) > 0 {
fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map))
for _, entry in track.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
if len(track.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees: ===\n", len(track.bad_free_array))
for entry in track.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
}
mem.tracking_allocator_destroy(track)
}

BIN
res/texture_blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
res/texture_green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
res/texture_orange.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
res/texture_pink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
res/texture_purple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
res/texture_red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

BIN
res/texture_yellow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

41
shader.glsl Normal file
View file

@ -0,0 +1,41 @@
@header package main
@header import sg "sokol/gfx"
@ctype mat4 Mat4
@vs vs
in vec3 pos;
in vec4 col;
in vec2 uv;
layout(binding = 0) uniform VsParams {
mat4 mvp;
};
out vec4 color;
out vec2 tex_coord;
void main() {
gl_Position = mvp * vec4(pos, 1);
color = col;
tex_coord = uv;
}
@end
@fs fs
in vec4 color;
in vec2 tex_coord;
layout(binding=0) uniform texture2D tex;
layout(binding=0) uniform sampler smp;
out vec4 frag_color;
void main() {
frag_color = texture(sampler2D(tex, smp), tex_coord) * color;
}
@end
@program main vs fs

207
shader.odin Normal file
View file

@ -0,0 +1,207 @@
package main
import sg "sokol/gfx"
/*
#version:1# (machine generated, don't edit!)
Generated by sokol-shdc (https://github.com/floooh/sokol-tools)
Cmdline:
sokol-shdc -i shader.glsl -o shader.odin -l metal_macos -f sokol_odin
Overview:
=========
Shader program: 'main':
Get shader desc: main_shader_desc(sg.query_backend())
Vertex Shader: vs
Fragment Shader: fs
Attributes:
ATTR_main_pos => 0
ATTR_main_col => 1
ATTR_main_uv => 2
Bindings:
Uniform block 'VsParams':
Odin struct: Vsparams
Bind slot: UB_VsParams => 0
Image 'tex':
Image type: ._2D
Sample type: .FLOAT
Multisampled: false
Bind slot: IMG_tex => 0
Sampler 'smp':
Type: .FILTERING
Bind slot: SMP_smp => 0
*/
ATTR_main_pos :: 0
ATTR_main_col :: 1
ATTR_main_uv :: 2
UB_VsParams :: 0
IMG_tex :: 0
SMP_smp :: 0
Vsparams :: struct #align(16) {
using _: struct #packed {
mvp: Mat4,
},
}
/*
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct VsParams
{
float4x4 mvp;
};
struct main0_out
{
float4 color [[user(locn0)]];
float2 tex_coord [[user(locn1)]];
float4 gl_Position [[position]];
};
struct main0_in
{
float3 pos [[attribute(0)]];
float4 col [[attribute(1)]];
float2 uv [[attribute(2)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant VsParams& _19 [[buffer(0)]])
{
main0_out out = {};
out.gl_Position = _19.mvp * float4(in.pos, 1.0);
out.color = in.col;
out.tex_coord = in.uv;
return out;
}
*/
@(private="file")
vs_source_metal_macos := [601]u8 {
0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f,
0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,
0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a,
0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20,
0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x56,
0x73,0x50,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,
0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,
0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,
0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,
0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,
0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,
0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,
0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,
0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,
0x20,0x70,0x6f,0x73,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,
0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
0x34,0x20,0x63,0x6f,0x6c,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,
0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,
0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,
0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,
0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,
0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,
0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,
0x73,0x74,0x61,0x6e,0x74,0x20,0x56,0x73,0x50,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,
0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,
0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,
0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,
0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,
0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a,0x20,0x66,0x6c,0x6f,
0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,
0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,
0x74,0x2e,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x20,0x3d,0x20,0x69,0x6e,
0x2e,0x75,0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,
0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
}
/*
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 frag_color [[color(0)]];
};
struct main0_in
{
float4 color [[user(locn0)]];
float2 tex_coord [[user(locn1)]];
};
fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler smp [[sampler(0)]])
{
main0_out out = {};
out.frag_color = tex.sample(smp, in.tex_coord) * in.color;
return out;
}
*/
@(private="file")
fs_source_metal_macos := [450]u8 {
0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f,
0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,
0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a,
0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20,
0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,
0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,
0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,
0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,
0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,
0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,
0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x20,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x20,0x5b,0x5b,0x75,0x73,
0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,
0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,
0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,
0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,
0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,
0x6f,0x61,0x74,0x3e,0x20,0x74,0x65,0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,
0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,
0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,
0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,
0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,
0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,
0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,
0x6d,0x70,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,
0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,
0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,
0x0a,0x00,
}
main_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc {
desc: sg.Shader_Desc
desc.label = "main_shader"
#partial switch backend {
case .METAL_MACOS:
desc.vertex_func.source = transmute(cstring)&vs_source_metal_macos
desc.vertex_func.entry = "main0"
desc.fragment_func.source = transmute(cstring)&fs_source_metal_macos
desc.fragment_func.entry = "main0"
desc.attrs[0].base_type = .FLOAT
desc.attrs[1].base_type = .FLOAT
desc.attrs[2].base_type = .FLOAT
desc.uniform_blocks[0].stage = .VERTEX
desc.uniform_blocks[0].layout = .STD140
desc.uniform_blocks[0].size = 64
desc.uniform_blocks[0].msl_buffer_n = 0
desc.images[0].stage = .FRAGMENT
desc.images[0].multisampled = false
desc.images[0].image_type = ._2D
desc.images[0].sample_type = .FLOAT
desc.images[0].msl_texture_n = 0
desc.samplers[0].stage = .FRAGMENT
desc.samplers[0].sampler_type = .FILTERING
desc.samplers[0].msl_sampler_n = 0
desc.image_sampler_pairs[0].stage = .FRAGMENT
desc.image_sampler_pairs[0].image_slot = 0
desc.image_sampler_pairs[0].sampler_slot = 0
}
return desc
}