commit ac0d49178646d698e04d43fa50b502a9df3547ea Author: Hugo MÄrdbrink Date: Sat Aug 23 23:54:00 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7d1a49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +sokol +*.dSYM +sokol-ecs +sokol-shdc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bcc58bf --- /dev/null +++ b/Makefile @@ -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 diff --git a/ecs/component_manager.odin b/ecs/component_manager.odin new file mode 100644 index 0000000..562bb14 --- /dev/null +++ b/ecs/component_manager.odin @@ -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) +} + diff --git a/ecs/component_pool.odin b/ecs/component_pool.odin new file mode 100644 index 0000000..80efcb9 --- /dev/null +++ b/ecs/component_pool.odin @@ -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) +} diff --git a/ecs/constants.odin b/ecs/constants.odin new file mode 100644 index 0000000..c0c3af9 --- /dev/null +++ b/ecs/constants.odin @@ -0,0 +1,9 @@ +package ecs + +ENTITY_MAX :: 4096 +COMPONENT_MAX :: 32 + +ID :: u32 + +EntityID :: ID +ComponentType :: u8 diff --git a/ecs/coordinator.odin b/ecs/coordinator.odin new file mode 100644 index 0000000..81feded --- /dev/null +++ b/ecs/coordinator.odin @@ -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) +} + diff --git a/ecs/entity_manager.odin b/ecs/entity_manager.odin new file mode 100644 index 0000000..a670f9e --- /dev/null +++ b/ecs/entity_manager.odin @@ -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.. 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) +} diff --git a/ecs/physics_system.odin b/ecs/physics_system.odin new file mode 100644 index 0000000..39e2b4f --- /dev/null +++ b/ecs/physics_system.odin @@ -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 + } +} diff --git a/ecs/render_system.odin b/ecs/render_system.odin new file mode 100644 index 0000000..ff041e9 --- /dev/null +++ b/ecs/render_system.odin @@ -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 { + } +} diff --git a/ecs/signature.odin b/ecs/signature.odin new file mode 100644 index 0000000..6d8c0a9 --- /dev/null +++ b/ecs/signature.odin @@ -0,0 +1,19 @@ +package ecs + +Signature :: bit_set[0.. 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^ = {} +} diff --git a/ecs/system_manager.odin b/ecs/system_manager.odin new file mode 100644 index 0000000..4b04612 --- /dev/null +++ b/ecs/system_manager.odin @@ -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) +} diff --git a/main.odin b/main.odin new file mode 100644 index 0000000..f8d091e --- /dev/null +++ b/main.odin @@ -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) +} diff --git a/res/texture_blue.png b/res/texture_blue.png new file mode 100644 index 0000000..e069832 Binary files /dev/null and b/res/texture_blue.png differ diff --git a/res/texture_green.png b/res/texture_green.png new file mode 100644 index 0000000..99fa4ce Binary files /dev/null and b/res/texture_green.png differ diff --git a/res/texture_orange.png b/res/texture_orange.png new file mode 100644 index 0000000..916ba29 Binary files /dev/null and b/res/texture_orange.png differ diff --git a/res/texture_pink.png b/res/texture_pink.png new file mode 100644 index 0000000..b47f7b2 Binary files /dev/null and b/res/texture_pink.png differ diff --git a/res/texture_purple.png b/res/texture_purple.png new file mode 100644 index 0000000..40b1f8a Binary files /dev/null and b/res/texture_purple.png differ diff --git a/res/texture_red.png b/res/texture_red.png new file mode 100644 index 0000000..c9b3fe0 Binary files /dev/null and b/res/texture_red.png differ diff --git a/res/texture_yellow.png b/res/texture_yellow.png new file mode 100644 index 0000000..18d1f03 Binary files /dev/null and b/res/texture_yellow.png differ diff --git a/shader.glsl b/shader.glsl new file mode 100644 index 0000000..6529946 --- /dev/null +++ b/shader.glsl @@ -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 diff --git a/shader.odin b/shader.odin new file mode 100644 index 0000000..531fc43 --- /dev/null +++ b/shader.odin @@ -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 + #include + + 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 + #include + + 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 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 +}