commit 1bdd01e9b27fd86f56f0b8a5029cb5a1ab89bc7b Author: Daniel Bross Date: Fri Oct 11 22:21:32 2024 +0200 Initial commit diff --git a/.build/.added_strings_w3.jai b/.build/.added_strings_w3.jai new file mode 100644 index 0000000..8c381ae --- /dev/null +++ b/.build/.added_strings_w3.jai @@ -0,0 +1,6 @@ +// Workspace: Game + +// +// String added via add_build_string() from c:/Personal/games/Coven/metaprogram.jai:30. +// +GAME_NAME :: "Test"; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e75df7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/*.exe +bin/*.pdb +.build +range_index +bin/mnar.exe +.build/*.obj diff --git a/animation/animator.jai b/animation/animator.jai new file mode 100644 index 0000000..9643ae5 --- /dev/null +++ b/animation/animator.jai @@ -0,0 +1,285 @@ +Animation_State_Type :: enum { + SINGLE; + BLEND_TREE_1D; +} + +Animation_Point :: struct { + position: float; + index: s32; + + time: float; + multiplier: float = 1.0; + looping: bool; +} + +Animation_Transition :: struct { + time: float; + duration: float; + + from_index: s32; + to_index: s32; +} + +Animation_State :: struct { + type: Animation_State_Type; + + union { + single : struct { + time: float; + multiplier: float; + looping: bool; + index: s32; + } + blend_tree : struct { + value: float; // Range is 0.0 - 1.0 + points: [..] Animation_Point; + } + } +} + +Animator :: struct { + playing: bool; + + state_index: s32; + states: [..] Animation_State; + + playing_transition: bool; + transition : Animation_Transition; +} + +sample_animation_node :: (animation: Animation, f0: s32, f1: s32, t: float, node_index: s32) -> position: Vector3, rotation: Quaternion, scale: Vector3 { + if node_index >= animation.nodes.count return .{0,0,0}, .{0,0,0,1}, .{1,1,1}; + + node_anim := animation.nodes[node_index]; + pos : Vector3 = ---; + rot : Quaternion = ---; + scale : Vector3 = ---; + + if node_anim.position.count == 0 { + pos = node_anim.const_position; + } else { + pos = lerp(node_anim.position[f0], node_anim.position[f1], t); + } + + if node_anim.rotation.count == 0 { + rot = node_anim.const_rotation; + } else { + rot = slerp(node_anim.rotation[f0], node_anim.rotation[f1], t); + } + + if node_anim.scale.count == 0 { + scale = node_anim.const_scale; + } else { + scale = lerp(node_anim.scale[f0], node_anim.scale[f1], t); + } + + return pos, rot, scale; +} + +sample_animation :: (e: *Entity, animation: Animation, time: float, weight: float) { + frame_time := time * animation.framerate; + + // Sample! + f0 := min(cast(s32)frame_time + 0, animation.num_frames - 1); + f1 := min(cast(s32)frame_time + 1, animation.num_frames - 1); + + t := cast(float)min(frame_time - cast(float)f0, 1.0); + + for * e.renderable.nodes { + pos, rot, scale := sample_animation_node(animation, f0, f1, t, cast(s32)it_index); + it.transform.position += pos * weight; + + // Make sure that we interpolate with the shortest path + if dot(it.transform.orientation, rot) < 0.0 { + rot = -rot; + } + + it.transform.orientation += rot * weight; + it.transform.scale += scale * weight; + } +} + +animation_is_done :: (e: *Entity) -> bool { + assert(cast(bool)(e.flags & Entity_Flags.ANIMATED)); + + animator := e.animator; + + if animator.playing_transition return false; + + state := animator.states[animator.state_index]; + + if state.type == { + case .SINGLE; + animation := e.renderable.model.animations[state.single.index]; + return state.single.time >= animation.duration - 0.0001; + case .BLEND_TREE_1D; + } + + return false; +} + +animation_time :: (e: *Entity) -> float { + assert(cast(bool)(e.flags & Entity_Flags.ANIMATED)); + + animator := e.animator; + + if animator.playing_transition return 0.0; + + state := animator.states[animator.state_index]; + + if state.type == { + case .SINGLE; + animation := e.renderable.model.animations[state.single.index]; + return state.single.time; + case .BLEND_TREE_1D; + } + + return 0.0; +} + +animation_time_normalized :: (e: *Entity) -> float { + assert(cast(bool)(e.flags & Entity_Flags.ANIMATED)); + + animator := e.animator; + + if animator.playing_transition return 0.0; + + state := animator.states[animator.state_index]; + + if state.type == { + case .SINGLE; + animation := e.renderable.model.animations[state.single.index]; + return state.single.time / xx animation.duration; + case .BLEND_TREE_1D; + } + + return 0.0; +} + +sample_animation_state :: (e: *Entity, index: s32, weight: float) { + state := *e.animator.states[index]; + + if state.type == { + case .SINGLE; + { + animation := *e.renderable.model.animations[state.single.index]; + sample_animation(e, animation, min(state.single.time, cast(float)animation.duration - 0.0001), weight); + } + case .BLEND_TREE_1D; + { + p0 : s32; + p1 : s32; + found := false; + current_value := state.blend_tree.value; + + for index: 0..state.blend_tree.points.count-1 { + p := *state.blend_tree.points[index]; + + if current_value >= p.position && index != state.blend_tree.points.count-1 { + p0 = xx index; + p1 = p0 + 1; + } + } + + p0_position := state.blend_tree.points[p0].position; + p1_position := state.blend_tree.points[p1].position; + position := state.blend_tree.value - p0_position; + range := p1_position - p0_position; + alpha := position / range; + + anim0 := e.renderable.model.animations[state.blend_tree.points[p0].index]; + anim1 := e.renderable.model.animations[state.blend_tree.points[p1].index]; + time0 := state.blend_tree.points[p0].time; + time1 := state.blend_tree.points[p1].time; + + sample_animation(e, anim0, time0, (1.0 - alpha) * weight); + sample_animation(e, anim1, time1, alpha * weight); + } + } +} + +transition_to_animation_state :: (e: *Entity, index: s32, transition_duration: float) { + assert(cast(bool)(e.flags & Entity_Flags.ANIMATED)); + + e.animator.playing_transition = true; + e.animator.transition.time = 0.0; + e.animator.transition.duration = transition_duration; + e.animator.transition.from_index = e.animator.state_index; + e.animator.transition.to_index = index; + + // @Incomplete: Missing blend tree + e.animator.states[index].single.time = 0.0; +} + +update_animation_state :: (e: *Entity, index: s32, dt: float) { + state := *e.animator.states[index]; + + if state.type == { + case .SINGLE; + { + state.single.time += dt * state.single.multiplier; + + animation := *e.renderable.model.animations[state.single.index]; + + if state.single.time > animation.duration { + if state.single.looping { + state.single.time = cast(float)(state.single.time - animation.duration); + } else { + state.single.time = xx animation.duration; + } + } + } + case .BLEND_TREE_1D; + { + p0 : s32; + p1 : s32; + found := false; + + for *p: state.blend_tree.points { + p.time += dt * p.multiplier; + + animation := *e.renderable.model.animations[p.index]; + + if p.time > animation.duration { + if p.looping { + p.time = cast(float)(p.time - animation.duration); + } else { + p.time = xx animation.duration - 0.0001; // Is this correct? + } + } + } + } + } +} + +update_animator :: (e: *Entity, animator: *Animator, dt: float) { + if animator.playing { + // Reset all transforms + for * e.renderable.nodes { + it.transform.position = .{0,0,0}; + it.transform.scale = .{0,0,0}; + it.transform.orientation = .{0,0,0,0}; + it.has_sampled_animation = false; + } + + if animator.playing_transition { + update_animation_state(e, animator.transition.from_index, dt); + update_animation_state(e, animator.transition.to_index, dt); + + animator.transition.time += dt; + alpha := animator.transition.time / animator.transition.duration; + clamped := clamp(alpha, 0.0, 1.0); + + sample_animation_state(e, animator.transition.from_index, 1.0 - clamped); + sample_animation_state(e, animator.transition.to_index, clamped); + + if alpha >= 1.0 { + animator.playing_transition = false; + animator.state_index = animator.transition.to_index; + } + } else { + update_animation_state(e, animator.state_index, dt); + sample_animation_state(e, animator.state_index, 1.0); + } + } +} diff --git a/audio/audio.jai b/audio/audio.jai new file mode 100644 index 0000000..cf43bd3 --- /dev/null +++ b/audio/audio.jai @@ -0,0 +1,86 @@ +Sound_Handle :: #type, distinct u32; +audio_system : *Audio_System; + +Sound :: struct { + fmod_sound: *FMOD_SOUND; +} + +Audio_Event :: struct { + instance : *FMOD_STUDIO_EVENTINSTANCE; +}; + +Delayed_Audio_Event :: struct { + event: Audio_Event; + time_left: float; +} + +Audio_System :: struct { + fmod: *FMOD_SYSTEM; + studio: *FMOD_STUDIO_SYSTEM; + sounds: PArray(Sound, Sound_Handle); + + delayed_events: [..] Delayed_Audio_Event; +} + +init_audio_system :: () { + audio_system = New(Audio_System); + assert(FMOD_System_Create(*audio_system.fmod, FMOD_VERSION) == .FMOD_OK); + assert(FMOD_System_Init(audio_system.fmod, 32, 0, null) == .FMOD_OK); + + assert(FMOD_Studio_System_Create(*audio_system.studio, FMOD_VERSION) == .FMOD_OK); + assert(FMOD_Studio_System_Initialize(audio_system.studio, 512, FMOD_STUDIO_INIT_LIVEUPDATE, FMOD_INIT_PROFILE_ENABLE, null) == .FMOD_OK); +} + +instance : *FMOD_STUDIO_EVENTINSTANCE; + +load_fmod_bank :: (path: string) { + bank : *FMOD_STUDIO_BANK; + result := FMOD_Studio_System_LoadBankFile(audio_system.studio, to_temp_c_string(path), FMOD_STUDIO_LOAD_BANK_NORMAL, *bank); + assert(result == .FMOD_OK); + loading_state : FMOD_STUDIO_LOADING_STATE; + res := FMOD_Studio_Bank_GetLoadingState(bank, *loading_state); +} + +create_audio_event :: (path: string) -> Audio_Event { + event : *FMOD_STUDIO_EVENTDESCRIPTION; + result := FMOD_Studio_System_GetEvent(audio_system.studio, to_temp_c_string(path), *event); + assert(result == .FMOD_OK); + + evt : Audio_Event; + assert(FMOD_Studio_EventDescription_CreateInstance(event, *evt.instance) == .FMOD_OK); + return evt; +} + +play_audio_event :: (evt: Audio_Event, delay: float = -1.0) { + if delay > 0.0 { + array_add(*audio_system.delayed_events, .{evt, delay}); + } else { + assert(FMOD_Studio_EventInstance_Start(evt.instance) == .FMOD_OK); + } +} + +create_sound :: (path: string) -> Sound_Handle { + sound: Sound; + assert(FMOD_System_CreateSound(audio_system.fmod, to_temp_c_string(path), FMOD_DEFAULT, null, *sound.fmod_sound) == .FMOD_OK); + + return parray_add(*audio_system.sounds, sound); +} + +play_sound :: (handle: Sound_Handle) { + sound := parray_get(audio_system.sounds, handle); + FMOD_System_PlaySound(audio_system.fmod, sound.fmod_sound, null, 0, null); +} + +update_audio :: (dt: float) { + for * audio_system.delayed_events { + if it.time_left <= 0.0 { + play_audio_event(it.event); + remove it; + } else { + it.time_left -= dt; + } + } + FMOD_Studio_System_Update(audio_system.studio); +} + +#import "fmod"; diff --git a/core/camera.jai b/core/camera.jai new file mode 100644 index 0000000..0284249 --- /dev/null +++ b/core/camera.jai @@ -0,0 +1,345 @@ +Camera_Type :: enum { + ORTHOGRAPHIC; + PERSPECTIVE; +} + +Camera_Buffer_Data :: struct { + projection_matrix: Matrix4; + view_matrix: Matrix4; + position: Vector4; +} + +Camera :: struct { + type : Camera_Type; + + position : Vector3; + rotation : struct { + yaw : float; + pitch : float; + roll : float; + } + + aspect_ratio : float; + fov : float; + z_near : float; + z_far : float; + + forward : Vector3; + up : Vector3; + right : Vector3; + world_up : Vector3 = .{0,1,0}; + + projection_matrix : Matrix4; + view_matrix : Matrix4; + + dirty : bool; +} + +create_perspective_camera :: (position: Vector3 = .{}, fov: float, aspect: float, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, z_near: float = 0.1, z_far: float = 1000.0) -> Camera { + camera : Camera; + camera.type = .PERSPECTIVE; + camera.world_up = .{0,1,0}; + camera.position = position; + camera.rotation.yaw = yaw; + camera.rotation.pitch = pitch; + camera.rotation.roll = roll; + camera.aspect_ratio = aspect; + camera.z_near = z_near; + camera.z_far = z_far; + camera.fov = fov; + camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), aspect, z_near, z_far); + update_view_matrix(*camera); + + return camera; +} + +create_orthographic_camera :: (position: Vector3 = .{}, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, left: float, right: float, bottom: float, top: float, z_near: float = 0.1, z_far: float = 1000.0) -> Camera { + camera : Camera; + camera.type = .ORTHOGRAPHIC; + camera.world_up = .{0,1,0}; + camera.position = position; + camera.rotation.yaw = yaw; + camera.rotation.pitch = pitch; + camera.rotation.roll = roll; + camera.aspect_ratio = 0.1;//aspect; + camera.z_near = z_near; + camera.z_far = z_far; + camera.fov = 0.0; + camera.projection_matrix = orthographic_lh_projection_matrix(left, right, bottom, top, z_near, z_far); + update_view_matrix(*camera); + + return camera; +} + +orthographic_lh_projection_matrix :: (left: float, right: float, bottom: float, top: float, near: float, far: float) -> Matrix4 +{ + m : Matrix4; + + width := right - left; + height := top - bottom; + + m._11 = 2.0 / width; + m._22 = 2.0 / height; + + m._33 = 1 / (far - near); + m._34 = -near / (near-far); + m._44 = 1.0; + + + return m; +} + + +make_lh_projection_matrix :: (fov_vertical: float, aspect_ratio_horizontal_over_vertical: float, z_near: float, z_far: float, x_offset:=0.0, y_offset:=0.0, depth_range_01:=false) -> Matrix4 { + result := Matrix4_Identity; + + tan_theta := tan(fov_vertical * 0.5); + y_scale := 1 / tan_theta; + x_scale := y_scale / aspect_ratio_horizontal_over_vertical; + + result._11 = x_scale; + result._22 = y_scale; + result._33 = z_far / (z_far - z_near); + result._34 = -z_near * z_far / (z_far - z_near); + result._43 = 1; + result._44 = 0; + + result._13 = x_offset; // / w; + result._23 = y_offset; // / h; + + if depth_range_01 { + // To map -1,1 depth range to 0,1 we transform z as follows: z' = z * 0.5 + 0.5 + result._33 = result._33 * 0.5 + result._43 * 0.5; + result._34 = result._34 * 0.5 + result._44 * 0.5; + } + + return result; +} + +world_to_screen :: (camera: Camera, world_position: Vector3) -> Vector3 { + pos : Vector4; + pos.x = world_position.x; + pos.y = world_position.y; + pos.z = world_position.z; + pos.w = 1.0; + position := camera.projection_matrix * camera.view_matrix * pos; + position.x /= position.w; + position.y /= position.w; + position.z /= position.w; + + screen_position : Vector3; + screen_position.x = (position.x + 1.0) * 0.5 * cast(float)renderer.render_target_width; + screen_position.y = (position.y + 1.0) * 0.5 * cast(float)renderer.render_target_height; + screen_position.z = position.z; + return screen_position; +} + +screen_to_world :: (camera: Camera, screen_position: Vector2) -> Vector3 { + pos : Vector4; + pos.x = (screen_position.x / cast(float)renderer.render_target_width) * 2.0 - 1.0; + pos.y = (screen_position.y / cast(float)renderer.render_target_height) * 2.0 - 1.0; + pos.z = 0.0; + pos.w = 1.0; + + result := inverse(camera.projection_matrix * camera.view_matrix) * pos; + result.x /= result.w; + result.y /= result.w; + result.z /= result.w; + + world_position : Vector3; + world_position.x = result.x; + world_position.y = result.y; + world_position.z = result.z; + + return world_position; +} + +normalized_screen_to_ray_v2 :: (camera: *Camera, screen_position: Vector2) -> Ray { + nds : Vector2; + nds.x = (2.0 * screen_position.x) - 1.0; + nds.y = (2.0 * screen_position.y) - 1.0; + + origin : Vector4; + origin.x = nds.x; + origin.y = nds.y; + origin.z = 0.0; + origin.w = 1.0; + + far : Vector4; + far.x = nds.x; + far.y = nds.y; + far.z = 1.0; + far.w = 1.0; + + inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix); + ray_origin := inverse_view_proj * origin; + ray_end := inverse_view_proj * far; + + // The hero we didn't know we needed + ray_origin /= ray_origin.w; + ray_end /= ray_end.w; + + ray : Ray; + ray.origin = to_v3(ray_origin); + ray.direction = normalize(to_v3(ray_end) - ray.origin); + + return ray; +} + + +screen_to_ray_v2 :: (camera: Camera, screen_position: Vector2, screen_size: Vector2) -> Ray { + nds : Vector2; + nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0; + nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0; + + origin : Vector4; + origin.x = nds.x; + origin.y = nds.y; + origin.z = 0.0; + origin.w = 1.0; + + far : Vector4; + far.x = nds.x; + far.y = nds.y; + far.z = 1.0; + far.w = 1.0; + + inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix); + ray_origin := inverse_view_proj * origin; + ray_end := inverse_view_proj * far; + + // The hero we didn't know we needed + ray_origin /= ray_origin.w; + ray_end /= ray_end.w; + + ray : Ray; + ray.origin = to_v3(ray_origin); + ray.direction = normalize(to_v3(ray_end) - ray.origin); + + return ray; +} + +screen_to_ray :: (camera: *Camera, screen_position: Vector2, screen_size: Vector2) -> Ray { + ray : Ray; + ray.origin = camera.position; + + ray_nds : Vector3; + ray_nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0; + ray_nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0; + ray_nds.z = 0.0; + + ray_clip : Vector4; + ray_clip.x = ray_nds.x; + ray_clip.y = ray_nds.y; + ray_clip.z = -1.0; + ray_clip.w = 1.0; + + ray_eye := inverse(camera.projection_matrix) * ray_clip; + ray_eye.z = 1.0; + ray_eye.w = 0.0; + + ray_world := to_v3(inverse(camera.view_matrix) * ray_eye); + ray.direction = normalize(ray_world); + + return ray; +} + +set_fov :: (camera: *Camera, fov: float) { + camera.fov = fov; + camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), camera.aspect_ratio, camera.z_near, camera.z_far); + camera.dirty = true; +} + +set_position :: (camera: *Camera, position: Vector3) { + camera.position = position; + update_view_matrix(camera); +} + +set_yaw :: (camera: *Camera, yaw: float) { + camera.rotation.yaw = yaw; + update_view_matrix(camera); +} + +set_pitch :: (camera: *Camera, pitch: float) { + camera.rotation.pitch = pitch; + update_view_matrix(camera); +} + +set_roll :: (camera: *Camera, roll: float) { + camera.rotation.roll = roll; + update_view_matrix(camera); +} + +set_pitch_yaw_roll :: (camera: *Camera, pitch: float, yaw: float, roll: float) { + camera.rotation.pitch = pitch; + camera.rotation.yaw = yaw; + camera.rotation.roll = roll; + update_view_matrix(camera); +} + +update_view_matrix :: (using camera: *Camera) { + camera.rotation.pitch = clamp(camera.rotation.pitch, -89.0, 89.0); + + pitch := rotation.pitch * DEGREES_TO_RADIANS; + yaw := rotation.yaw * DEGREES_TO_RADIANS; + roll := rotation.roll * DEGREES_TO_RADIANS; + + pitch_mat := Matrix4.{ + 1, 0, 0, 0, + 0, cos(pitch), sin(pitch), 0, + 0, -sin(pitch), cos(pitch), 0, + 0, 0, 0, 1}; + + yaw_mat := Matrix4.{ + cos(yaw), 0, -sin(yaw), 0, + 0, 1, 0, 0, + sin(yaw), 0, cos(yaw), 0, + 0, 0, 0, 1}; + + roll_mat := Matrix4.{ + cos(roll), sin(roll), 0, 0, + -sin(roll), cos(roll), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1}; + + matrix := yaw_mat * pitch_mat * roll_mat; + + camera.forward = .{0,0,1}; + camera.right = .{1,0,0}; + camera.up = .{0,1,0}; + + camera.forward = normalize(to_v3(matrix * Vector4.{camera.forward.x, camera.forward.y, camera.forward.z, 0.0})); + camera.right = normalize(to_v3(matrix * Vector4.{camera.right.x, camera.right.y, camera.right.z, 0.0})); + camera.up = normalize(to_v3(matrix * Vector4.{camera.up.x, camera.up.y, camera.up.z, 0.0})); + + eye := camera.position + camera.forward; + + //camera.forward = normalize(direction); + //camera.right = normalize(cross_product(Vector3.{0,1,0}, camera.forward)); + //camera.up = normalize(cross_product(camera.forward, camera.right)); + + m := Matrix4_Identity; + m._11 = camera.right.x; + m._12 = camera.right.y; + m._13 = camera.right.z; + m._14 = -dot(camera.right, eye); + + m._21 = camera.up.x; + m._22 = camera.up.y; + m._23 = camera.up.z; + m._24 = -dot(camera.up, eye); + + m._31 = camera.forward.x; + m._32 = camera.forward.y; + m._33 = camera.forward.z; + m._34 = -dot(camera.forward, eye); + + m._41 = 0.0; + m._42 = 0.0; + m._43 = 0.0; + m._44 = 1.0; + + camera.view_matrix = m; + + camera.dirty = true; +} diff --git a/core/console.jai b/core/console.jai new file mode 100644 index 0000000..1eacdbd --- /dev/null +++ b/core/console.jai @@ -0,0 +1,271 @@ +#import "File_Utilities"; + +console : *Console; + +Command_Proc :: struct { + name: string; + proc: (arguments: [] string, buffer: *[..] Buffer_Entry); +} + +Buffer_Entry :: struct { + text: string; + color: Color; +} + +Console :: struct { + buffer: [..] Buffer_Entry; + current_string: string; + commands: [..] Command_Proc; + + active: bool; + visible: bool; + open_amount: float; + animation_position: float; // 1.0 means fully visible + + font: Font_Handle; + rect_pipeline: Pipeline_State_Handle; + text_pipeline: Pipeline_State_Handle; + vb: Buffer_Handle; + cursor_vb: Buffer_Handle; + verts: [..] Colored_Vert; +} + +add_command :: (console: *Console, cmd: string, proc: (arguments: [] string, buffer: *[..] Buffer_Entry)) { + command : Command_Proc; + command.name = copy_string(cmd); + command.proc = proc; + + array_add(*console.commands, command); +} + +init_console :: () { + console = New(Console); + console.verts.allocator = temp; + buffer_size := size_of(Colored_Vert) * 12; + console.vb = create_vertex_buffer(renderer, null, xx buffer_size, stride=size_of(Colored_Vert), mappable=true); + console.cursor_vb = create_vertex_buffer(renderer, null, xx buffer_size, stride=size_of(Colored_Vert), mappable=true); + + { + vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS"); + ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS"); + + layout : [2] Vertex_Data_Info; + layout[0] = .{0, .POSITION2D, 0}; + layout[1] = .{0, .COLOR_WITH_ALPHA, 0}; + params : [0] Shader_Parameter; + + console.rect_pipeline = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + { + vs := create_vertex_shader(renderer, "../assets/shaders/font.hlsl", "VS"); + ps := create_pixel_shader(renderer, "../assets/shaders/font.hlsl", "PS"); + + layout : [3] Vertex_Data_Info; + layout[0] = .{0,.POSITION2D, 0}; + layout[1] = .{0,.TEXCOORD0, 0}; + layout[2] = .{0,.COLOR_WITH_ALPHA, 0}; + params : [0] Shader_Parameter; + + console.text_pipeline = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + console.font = create_font(renderer, "../assets/fonts/Inconsolata-Regular.ttf", 18); + console.current_string = alloc_string(256); + console.current_string.count = 0; + + //add_command(console, "stats", toggle_stats); + //add_command(console, "camset", save_camera); + //add_command(console, "load", load_scene); + //add_command(console, "copy", copy_scene); + //add_command(console, "vsync", set_vsync); + + //engine.console = console; +} + +find_command :: (console: *Console, cmd_str: string) -> Command_Proc, bool { + for console.commands { + if equal(it.name, cmd_str) { + return it, true; + } + } + + return .{}, false; +} + +update_console :: () { + if key_down(.TILDE) { + console.active = !console.active; + input.has_char = false; // Make sure that the tilde is not used as the first input character in the console + } + + if console.active { + if key_down(.BACKSPACE) { + if console.current_string.count > 0 { + console.current_string.count -= 1; + } + } + + if key_down(.RETURN) { + if console.current_string.count > 0 { + // @Incomplete(niels): Using split with space will take spaces as part of the args. + // So each subsequent space will be an argument itself. + // This is a bit of a hacky fix for now + arguments := split(console.current_string, " "); + index := 0; + while index < arguments.count - 1 { + if arguments[index].count == 0 { + arguments[index] = arguments[arguments.count - 1]; + arguments.count -= 1; + } else { + index += 1; + } + } + + if arguments[arguments.count - 1].count == 0 { + arguments.count -= 1; + } + + cmd, success := find_command(console, arguments[0]); + + push_entry(*console.buffer, console.current_string, .{1,1,1,1}); + + if success { + cmd.proc(arguments, *console.buffer); + } else { + push_entry(*console.buffer, console.current_string, .{1,0,0,1}); + } + + console.current_string.count = 0; + } + } + + if input.has_char { + if console.current_string.count < 256 { + console.current_string.data[console.current_string.count] = xx input.current_char; + console.current_string.count += 1; + input.has_char = false; + } + } + + eat_all_input(*input); + } +} + +make_vert :: (x: float, y: float, color: Color) -> Colored_Vert { + vert : Colored_Vert; + vert.position.x = x; + vert.position.y = y; + vert.color = color; + return vert; +} + +add_rect :: (renderer: *Renderer, x: float, y: float, width: float, height: float, color: Color, verts: *[..] Colored_Vert) { + inv_w := 1.0 / cast(float)renderer.render_target_width; + inv_h := 1.0 / cast(float)renderer.render_target_height; + + x = x * inv_w * 2.0 - 1.0; + y = (cast(float)renderer.render_target_height - y) * inv_h * 2.0 - 1.0; + w : float = width * inv_w * 2.0; + h : float = height * inv_h * 2.0; + + array_add(verts, make_vert(x + w, y - h, color)); + array_add(verts, make_vert(x, y - h, color)); + array_add(verts, make_vert(x, y, color)); + + array_add(verts, make_vert(x + w, y - h, color)); + array_add(verts, make_vert(x, y, color)); + array_add(verts, make_vert(x + w, y, color)); +} + +render_console :: () { + if console.active { + console.visible = true; + console.open_amount += dt * 3.0; + } else { + console.open_amount -= dt * 3.0; + if console.open_amount <= 0.0 { + console.visible = false; + } + } + + console.open_amount = clamp(console.open_amount, 0.0, 1.0); + + if !console.visible return; + + offset_y := (1.0 - console.open_amount) * 200.0 - 75; + + console.verts.count = 0; + console_color : Color = .{48.0/255.0, 64.0/255.0, 36.0/255.0, 0.7}; + console_line_color : Color = .{18.0/255.0, 34.0/255.0, 6.0/255.0, 0.7}; + + add_rect(renderer, 0.0, cast(float)renderer.render_target_height - 200 + offset_y, cast(float)renderer.render_target_width, 200.0, console_color, *console.verts); + add_rect(renderer, 0.0, cast(float)renderer.render_target_height - 30 + offset_y, cast(float)renderer.render_target_width, 30.0, console_line_color , *console.verts); + + upload_data_to_buffer(renderer, console.vb, console.verts.data, cast(s32)console.verts.count * size_of(Colored_Vert)); + + push_cmd_set_draw_mode(renderer, .FILL); + push_cmd_set_depth_write(renderer, false); + push_cmd_set_pipeline_state(renderer, console.rect_pipeline); + + push_cmd_set_vertex_buffer(renderer, console.vb); + push_cmd_draw(renderer, console.verts.count); + + push_cmd_set_pipeline_state(renderer, console.text_pipeline); + font := *renderer.fonts[console.font - 1]; + push_cmd_set_texture(renderer, 0, font.texture); + + size := get_text_size(renderer, ">", console.font); + + render_data := bake_text(renderer, 5.0, size.y - offset_y, ">", console.font, .{1,1,1,1}); + push_cmd_set_vertex_buffer(renderer, render_data.vb); + push_cmd_draw(renderer, render_data.vert_count); + + x := 15.0; + + if console.current_string.count > 0 { + render_data := bake_text(renderer, x, size.y - offset_y, console.current_string, console.font, .{1,1,1,1}); + push_cmd_set_vertex_buffer(renderer, render_data.vb); + push_cmd_draw(renderer, render_data.vert_count); + + x = 10 + render_data.size.x + 2.0; + } + + y := 37.0; + i := console.buffer.count - 1; + count := 0; + + while i >= 0 && count < 8 { + defer i -= 1; + defer count += 1; + + entry := console.buffer[i]; + + render_data := bake_text(renderer, 15.0, y - offset_y, entry.text, console.font, entry.color); + push_cmd_set_vertex_buffer(renderer, render_data.vb); + push_cmd_draw(renderer, render_data.vert_count); + + y += 20.0; + } + + console.verts.count = 0; + + //#if !NEW_UI { + add_rect(renderer, x, cast(float)renderer.render_target_height - 25 + offset_y, 10, 20.0, .{1,1,1,1}, *console.verts); + //} + + upload_data_to_buffer(renderer, console.cursor_vb, console.verts.data, cast(s32)console.verts.count * size_of(Colored_Vert)); + + push_cmd_set_draw_mode(renderer, .FILL); + push_cmd_set_depth_write(renderer, false); + push_cmd_set_pipeline_state(renderer, console.rect_pipeline); + push_cmd_set_vertex_buffer(renderer, console.cursor_vb); + push_cmd_draw(renderer, console.verts.count); +} + +push_entry :: (buffer: *[..] Buffer_Entry, text: string, color: Color) { + entry : Buffer_Entry; + entry.text = copy_string(text); + entry.color = color; + array_add(buffer, entry); +} diff --git a/core/entity.jai b/core/entity.jai new file mode 100644 index 0000000..6aea8fb --- /dev/null +++ b/core/entity.jai @@ -0,0 +1,189 @@ +Entity_Id :: #type, isa s64; + +Entity_Flags :: enum_flags u8 { + NONE; + RENDERABLE; + COLLISION; + PHYSICS; + STATIC; + TRIGGER; + + ANIMATED; + + DONT_SAVE; +} + +Renderable_Type :: enum { + MODEL; +} + +Entity_Material :: struct { + base_color : Vector4; +} + +MAX_NODES :: 256; + +Node_Render_Data :: struct { + enabled : bool = true; + transform: Transform; + material : Entity_Material; + has_sampled_animation: bool; + + // Buffers + transform_buffer: Buffer_Handle; + material_buffer: Buffer_Handle; + + bone_buffers : [MAX_BONES] Buffer_Handle; + num_bones: s64; +} + +Renderable :: struct { + visible: bool = true; + type : Renderable_Type; + + model: *Model; + nodes: [MAX_NODES] Node_Render_Data; + num_nodes: s64; +} + +MAX_CHILDREN :: 16; + +Entity :: struct { + id: Entity_Id; + type : Type; + + enabled: bool = true; + + name: string; + + parent: *Entity; @DontSerialize + children: [MAX_CHILDREN] *Entity; @DontSerialize + num_children: s64; @DontSerialize + attach_node_index: s64 = -1; @DontSerialize + + flags : Entity_Flags; + + transform: Transform; + + grid_position: Vector3i; + snap_offset: Vector3; + + renderable: Renderable; @DontSerialize + animator: Animator; @DontSerialize + + // Physics + body : Physics_Body; @DontSerialize + collider : Collider; @DontSerialize + // End physics + + remote_id: Entity_Id; @DontSerialize + is_proxy: bool; @DontSerialize + last_replication_time: float; @DontSerialize + + _locator: Bucket_Locator; @DontSerialize + scene: *Scene; @DontSerialize +} + +add_child :: (e: *Entity, child: *Entity, node_name: string = "") { + set_parent(child, e, node_name); +} + +set_parent :: (e: *Entity, parent: *Entity, node_name: string = "") { + parent.children[parent.num_children] = e; + e.parent = parent; + parent.num_children += 1; + + for node, index: parent.renderable.model.nodes { + if node.name == node_name { + e.attach_node_index = index; + break; + } + } +} + +set_base_color :: (e: *Entity, color: Vector4, node_name: string = "") { + if e.renderable.type == .MODEL { + for i: 0..e.renderable.num_nodes-1 { + actual_node := e.renderable.model.nodes[i]; + if node_name.count == 0 || node_name == actual_node.name { + data := *e.renderable.nodes[i]; + if data.material_buffer > 0 { + material : Entity_Material = ---; + material.base_color = color; + upload_data_to_buffer(renderer, data.material_buffer, *material, size_of(Entity_Material)); + } + } + } + } +} + +set_node_enabled :: (e: *Entity, node_name: string, enabled : bool) { + if e.renderable.type == .MODEL { + for i: 0..e.renderable.num_nodes-1 { + actual_node := e.renderable.model.nodes[i]; + if node_name.count == 0 || node_name == actual_node.name { + data := *e.renderable.nodes[i]; + data.enabled = enabled; + } + } + } +} + +load_model_into_entity :: (e: *Entity, model: *Model) { + e.renderable.type = .MODEL; + + assert(model.nodes.count <= MAX_NODES); + + e.renderable.num_nodes = model.nodes.count; + e.renderable.model = model; + + for *model.nodes { + data : Node_Render_Data; + data.transform = it.transform; + update_matrix(*it.transform); + + if it.meshes.count > 0 { + material : Entity_Material = ---; + material.base_color = it.material_defaults[0].base_color; // @Incomplete: What if there are multiple meshes? + + data.material = material; + data.transform_buffer = create_constant_buffer(renderer, null, size_of(Matrix4), mappable=true); + data.material_buffer = create_constant_buffer(renderer, *material, size_of(Entity_Material), mappable=true); + data.num_bones = it.num_bones; + + if it.num_bones > 0 { + for bone_index: 0..it.num_bones-1 { + data.bone_buffers[bone_index] = create_constant_buffer(renderer, null, size_of(Matrix4) * MAX_BONES, mappable=true); + } + } + } + e.renderable.nodes[it_index] = data; + } +} + +destroy_entity :: (e: *Entity, remove_from_scene: bool = true) { + free(e.name); + + for 0..e.renderable.num_nodes-1 { + node_data := e.renderable.nodes[it]; + + if node_data.transform_buffer > 0 { + destroy_buffer(renderer, node_data.transform_buffer); + } + + if node_data.material_buffer > 0 { + destroy_buffer(renderer, node_data.material_buffer); + } + + for bi: 0..node_data.num_bones-1 { + destroy_buffer(renderer, node_data.bone_buffers[bi]); + } + } + + if remove_from_scene { + array_unordered_remove_by_value(*game_state.current_scene.entities, e); + if e.type == { + case Block; bucket_array_remove(*game_state.current_scene.by_type._Block, e._locator); + } + } +} diff --git a/core/fps.jai b/core/fps.jai new file mode 100644 index 0000000..9b947a1 --- /dev/null +++ b/core/fps.jai @@ -0,0 +1,19 @@ +FPS_COUNT_AMOUNT :: 8; +average_fps: int; +fps_counts: [FPS_COUNT_AMOUNT] int; +fps_count_cursor: int; + +update_fps_counter :: (dt: float) { + fps_counts[fps_count_cursor] = cast(int)(1.0/dt); + fps_count_cursor += 1; + if fps_count_cursor == FPS_COUNT_AMOUNT { + fps_count_cursor = 0; + } + + total_fps : int; + for 0..FPS_COUNT_AMOUNT-1 { + total_fps += fps_counts[it]; + } + + average_fps = total_fps / FPS_COUNT_AMOUNT; +} \ No newline at end of file diff --git a/core/math.jai b/core/math.jai new file mode 100644 index 0000000..51d002d --- /dev/null +++ b/core/math.jai @@ -0,0 +1,366 @@ +DEGREES_TO_RADIANS : float : 0.017453292; +RADIANS_TO_DEGREES : float : 57.295779; + +hfov_to_vfov :: (hfov: float, aspect_ratio: float) -> float { + return 2.0 * atan(tan(hfov * DEGREES_TO_RADIANS * 0.5) / aspect_ratio) * RADIANS_TO_DEGREES; +} + +lerp_angle :: (from: float, to: float, weight: float) -> float { + start_angle := fmod_cycling(from, 2*PI); + end_angle := fmod_cycling(to, 2*PI); + + angle_distance := fmod_cycling(end_angle - start_angle + PI, 2 * PI) - PI; + + interpolated_angle := start_angle + angle_distance * weight; + return fmod_cycling(interpolated_angle, 2*PI); +} + +short_angle_dist :: (from: float, to: float) -> float { + max_angle :: PI * 2.0; + difference := fmod_cycling(to - from, max_angle); + return fmod_cycling(2.0 * difference, max_angle) - difference; +} + +degrees_to_radians :: (degrees: float) -> float#expand { + constant : float : 0.01745329; + return degrees * constant; +} + +radians_to_degrees :: (radians: float) -> float#expand { + constant : float : 57.295779; + return radians * constant; +} + +Vector2i :: struct { + x: s32; + y: s32; +} + +Vector3i :: struct { + x: s32; + y: s32; + z: s32; +} + +Color :: #type,isa Vector4; + +Colored_Vert :: struct { + position: Vector2; + color : Color; +} + +AABB :: struct { + min: Vector3 = .{FLOAT32_INFINITY, FLOAT32_INFINITY, FLOAT32_INFINITY}; + max: Vector3 = .{-FLOAT32_INFINITY, -FLOAT32_INFINITY, -FLOAT32_INFINITY}; +} + +apply_min_max :: (min: *Vector3, max: *Vector3, p: Vector3) { + if p.x < min.x min.x = p.x; + if p.y < min.y min.y = p.y; + if p.z < min.z min.z = p.z; + if p.x > max.x max.x = p.x; + if p.y > max.y max.y = p.y; + if p.z > max.z max.z = p.z; +} + +point_inside_aabb :: (aabb: AABB, point: Vector3) -> bool { + return point.x >= aabb.min.x && point.x <= aabb.max.x + && point.y >= aabb.min.y && point.y <= aabb.max.y + && point.z >= aabb.min.z && point.z <= aabb.max.z; +} + +operator == :: inline (a: Vector3i, b: Vector3i) -> bool { + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +operator + :: inline (a: Vector3i, b: Vector3i) -> Vector3i { + return .{a.x + b.x, a.y + b.y, a.z + b.z}; +} + +operator - :: inline (a: Vector3i, b: Vector3i) -> Vector3i { + return .{a.x - b.x, a.y - b.y, a.z - b.z}; +} + +to_v4 :: (v3: Vector3) -> Vector4 { + v4 : Vector4; + v4.x = v3.x; + v4.y = v3.y; + v4.z = v3.z; + v4.w = 1.0; + return v4; +} + +to_v3 :: (v4: Vector4) -> Vector3 { + v3 : Vector3; + v3.x = v4.x; + v3.y = v4.y; + v3.z = v4.z; + return v3; +} + +to_v3 :: (v2: Vector2) -> Vector3 { + v : Vector3; + v.x = v2.x; + v.y = v2.y; + return v; +} + +to_v2 :: (v3: Vector3) -> Vector2 { + v : Vector2; + v.x = v3.x; + v.y = v3.y; + return v; +} + + +round :: (val: float) -> float { + return floor(val + 0.5); +} + +move_towards :: (origin: float, target: float, amount: float) -> float { + if origin < target { + return min(origin + amount, target); + } else if origin > target { + return max(origin - amount, target); + } else { + return target; + } +} + +move_towards :: (origin: Vector3, target: Vector3, amount: float) -> Vector3 { + result : Vector3; + dir := normalize(target - origin); + result.x = move_towards(origin.x, target.x, abs(dir.x) * amount); + result.y = move_towards(origin.y, target.y, abs(dir.y) * amount); + result.z = move_towards(origin.z, target.z, abs(dir.z) * amount); + return result; +} + +smooth_damp :: (current: Vector3, target: Vector3, current_velocity: *Vector3, smooth_time: float, max_speed: float, delta_time: float) -> Vector3 { + output_x := 0.0; + output_y := 0.0; + output_z := 0.0; + + // Based on Game Programming Gems 4 Chapter 1.10 + smooth_time = max(0.0001, smooth_time); + omega := 2.0 / smooth_time; + + x := omega * delta_time; + exp := 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x); + + change_x := current.x - target.x; + change_y := current.y - target.y; + change_z := current.z - target.z; + original_to := target; + + // Clamp maximum speed + max_change := max_speed * smooth_time; + + max_change_sq := max_change * max_change; + sqrmag := change_x * change_x + change_y * change_y + change_z * change_z; + if sqrmag > max_change_sq { + mag := cast(float)sqrt(sqrmag); + change_x = change_x / mag * max_change; + change_y = change_y / mag * max_change; + change_z = change_z / mag * max_change; + } + + final_target := target; + final_target.x = current.x - change_x; + final_target.y = current.y - change_y; + final_target.z = current.z - change_z; + + temp_x := (current_velocity.x + omega * change_x) * delta_time; + temp_y := (current_velocity.y + omega * change_y) * delta_time; + temp_z := (current_velocity.z + omega * change_z) * delta_time; + + current_velocity.x = (current_velocity.x - omega * temp_x) * exp; + current_velocity.y = (current_velocity.y - omega * temp_y) * exp; + current_velocity.z = (current_velocity.z - omega * temp_z) * exp; + + output_x = final_target.x + (change_x + temp_x) * exp; + output_y = final_target.y + (change_y + temp_y) * exp; + output_z = final_target.z + (change_z + temp_z) * exp; + + // Prevent overshooting + orig_minus_current_x := original_to.x - current.x; + orig_minus_current_y := original_to.y - current.y; + orig_minus_current_z := original_to.z - current.z; + out_minus_orig_x := output_x - original_to.x; + out_minus_orig_y := output_y - original_to.y; + out_minus_orig_z := output_z - original_to.z; + + if orig_minus_current_x * out_minus_orig_x + orig_minus_current_y * out_minus_orig_y + orig_minus_current_z * out_minus_orig_z > 0 { + output_x = original_to.x; + output_y = original_to.y; + output_z = original_to.z; + + current_velocity.x = (output_x - original_to.x) / delta_time; + current_velocity.y = (output_y - original_to.y) / delta_time; + current_velocity.z = (output_z - original_to.z) / delta_time; + } + + return .{output_x, output_y, output_z}; +} + +smooth_damp :: (current: float, target: float, current_velocity: *float, smooth_time: float, max_speed: float, delta_time: float) -> float { + smooth_time = max(0.0001, smooth_time); + omega := 2.0 / smooth_time; + + x := omega * delta_time; + exp := 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x); + change := current - target; + original_to := target; + + // Clamp maximum speed + max_change := max_speed * smooth_time; + change = clamp(change, -max_change, max_change); + target = current - change; + + temp := (< 0.0) == (output > original_to) { + output = original_to; + < Vector2 { + result : Vector2; + angle := random_get_within_range(0.0, PI * 2.0); + radius := random_get_within_range(0.0, 1.0); + result.x = radius * cos(angle); + result.y = radius * sin(angle); + + return result; +} + +random_in_unit_sphere :: () -> Vector3 { + result : Vector3; + result.x = random_get_within_range(-1.0, 1.0); + result.y = random_get_within_range(-1.0, 1.0); + result.z = random_get_within_range(-1.0, 1.0); + + return normalize(result); +} + +look_at_lh :: (position: Vector3, target: Vector3, up: Vector3) -> Matrix4 { + z_axis := normalize(target - position); + x_axis := normalize(cross(up, z_axis)); + y_axis := cross(z_axis, x_axis); + + m : Matrix4; + + m._11 = x_axis.x; + m._12 = x_axis.y; + m._13 = x_axis.z; + m._14 = -dot(x_axis, position); + + m._21 = y_axis.x; + m._22 = y_axis.y; + m._23 = y_axis.z; + m._24 = -dot(y_axis, position); + + m._31 = z_axis.x; + m._32 = z_axis.y; + m._33 = z_axis.z; + m._34 = -dot(z_axis, position); + + m._41 = 0.0; + m._42 = 0.0; + m._43 = 0.0; + m._44 = 1.0; + + return m; +} + +project :: (v1: Vector3, v2: Vector3) -> Vector3 { + return (v2*v1)/(v2*v2)*v2; +} + +reject :: (v1: Vector3, v2: Vector3) -> Vector3 { + return v1 - project(v1, v2); +} + +horizontal_distance :: inline (v1: Vector3, v2: Vector3) -> float { + return distance(Vector2.{v1.x,v1.z}, Vector2.{v2.x,v2.z}); +} + +horizontal_direction :: inline (from: Vector3, to: Vector3) -> Vector3 { + adjusted_from := from; + adjusted_from.y = to.y; + return normalize(to - adjusted_from); +} + +closest_point_on_line_segment :: (a: Vector3, b: Vector3, point: Vector3, ignore_y: bool = false) -> Vector3, float { + actual_a := a; + actual_b := b; + + if ignore_y { + actual_a.y = point.y; + actual_b.y = point.y; + } + + ab := actual_b - actual_a; + ap := point - actual_a; + + proj := dot(ap, ab); + ab_len_sq := length_squared(ab); + d := proj / ab_len_sq; + + cp : Vector3 = ---; + if d <= 0.0 { + cp = actual_a; + } else if d >= 1.0 { + cp = actual_b; + } else { + cp = actual_a + ab * d; + } + + return cp, distance(point, cp); +} + +reflect :: (incident: Vector3, normal: Vector3) -> Vector3 { + dot_product := dot(incident, normal); + reflected : Vector3 = ---; + reflected.x = incident.x - 2.0 * dot_product * normal.x; + reflected.y = incident.y - 2.0 * dot_product * normal.y; + reflected.z = incident.z - 2.0 * dot_product * normal.z; + return reflected; +} + +quat_to_pitch_yaw_roll :: (using q: Quaternion) -> pitch: float, yaw: float, roll: float { + roll := atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z); + pitch := atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z); + yaw := asin(2*x*y + 2*z*w); + return pitch, yaw, roll; +} + +ease_in :: (x : float, exp : int = 2) -> float { + return pow(x, xx exp); +} + +ease_out :: (x : float, exp : int = 2) -> float { + return 1.0 - pow(1 - x, xx exp); +} + +ease_in_sine :: (x: float) -> float { + return 1.0 - cos((x * PI) * 0.5); +} + +ease_out_sine :: (x: float) -> float { + return 1.0 - sin((x * PI) * 0.5); +} + +ease_in_out_sine :: (x: float) -> float { + return -(cos(PI * x) - 1.0) * 0.5; +} + +#import "PCG"; +#import "Math"; + diff --git a/core/parray.jai b/core/parray.jai new file mode 100644 index 0000000..bc71755 --- /dev/null +++ b/core/parray.jai @@ -0,0 +1,87 @@ +PArray :: struct (Data_Type : Type, Handle_Type : Type) { + data: [..] Data_Type; + indices: [..] u32; +} + +parray_reset :: (using array: *PArray) { + array_reset(*array.data); + array_reset(*array.indices); +} + +parray_free :: (using array: PArray) { + array_free(array.data); + array_free(array.indices); +} + +parray_reserve :: (using parray: *PArray, capacity: s32) { + if capacity > 0 { + array_reserve(*data, capacity); + array_reserve(*indices, capacity); + } +} + +parray_get :: (using parray: PArray, handle: parray.Handle_Type) -> *parray.Data_Type { + index := indices[handle - 1] - 1; + return *data[index]; +} + +parray_get_val :: (using parray: PArray, handle: parray.Handle_Type) -> parray.Data_Type { + assert(xx handle > 0 && xx handle <= indices.count); + index := indices[handle - 1] - 1; + return data[index]; +} + +parray_add :: (using parray: *PArray, value: parray.Data_Type) -> parray.Handle_Type { + array_add(*data, value); + index := cast(u32)data.count; + + handle : Handle_Type = 0; + + // find the next empty index + for *val, i: indices { + if < float { + t1 := (aabb.min.x - origin.x) / direction.x; + t2 := (aabb.max.x - origin.x) / direction.x; + t3 := (aabb.min.y - origin.y) / direction.y; + t4 := (aabb.max.y - origin.y) / direction.y; + t5 := (aabb.min.z - origin.z) / direction.z; + t6 := (aabb.max.z - origin.z) / direction.z; + + tmin := max(max(min(t1, t2), min(t3, t4)), min(t5, t6)); + tmax := min(min(max(t1, t2), max(t3, t4)), max(t5, t6)); + + // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us + if tmax < 0 { + return -1; + } + + // if tmin > tmax, ray doesn't intersect AABB + if tmin > tmax { + return -1; + } + + if tmin < 0 { + return tmax; + } + return tmin; +} + +ray_sphere_intersect :: (ray: Ray, center: Vector3, radius: float) -> bool { + t : float; + oc := ray.origin - center; + a := dot(ray.direction, ray.direction); + b := 2.0 * dot(oc, ray.direction); + c := dot(oc, oc) - radius * radius; + discriminant := b * b - 4 * a * c; + + if discriminant > 0 { + // Two intersection points, choose the closest one (smallest positive t) + t1 := (-b - sqrt(discriminant)) / (2.0 * a); + t2 := (-b + sqrt(discriminant)) / (2.0 * a); + + if t1 > 0 || t2 > 0 { + t = ifx (t1 < t2) then t1 else t2; + return true; + } + } else if discriminant == 0 { + // One intersection point + t = -b / (2.0 * a); + return t > 0; + } + + // No intersection + return false; +} + +ray_plane_intersection :: (ray: Ray, p: Vector3, n: Vector3) -> bool, float { + denom := dot(n, ray.direction); + if abs(denom) > 0.0001 { + offset := p - ray.origin; + t := dot(offset, n) / denom; + return t >= 0.0, t; + } + + return false, 0.0; +} + +ray_triangle_intersect :: (origin: Vector3, dir: Vector3, v0: Vector3, v1: Vector3, v2: Vector3) -> bool, float, Vector3 { + EPSILON := 0.0000001; + edge1 := v1 - v0; + edge2 := v2 - v0; + h := cross(dir, edge2); + a := dot(edge1, h); + + if a > -EPSILON && a < EPSILON return false, 0.0, .{0,0,0}; // This ray is parallel to this triangle. + + f := 1.0/a; + s := origin - v0; + u := f * dot(s, h); + + if u < 0.0 || u > 1.0 return false, 0.0, .{0,0,0}; + + q := cross(s, edge1); + v := f * dot(dir, q); + + if v < 0.0 || u + v > 1.0 return false, 0.0, .{0,0,0}; + + // At this stage we can compute t to find out where the intersection point is on the line. + t := f * dot(edge2, q); + if t > EPSILON { // ray intersection + //outIntersectionPoint = rayOrigin + rayVector * t; + normal := normalize(cross(edge1, edge2)); + return true, t, normal; + } + else // This means that there is a line intersection but not a ray intersection. + return false, 0.0, .{0,0,0}; + //e0 := v1 - v0; + //e1 := v2 - v0; + //pvec := cross(dir, e1); + + //det := dot(pvec, e0); + + //if det > 0 { + // inv_det := 1.0 / det; + // tvec := origin - v0; + // u := inv_det * dot(tvec, pvec); + + // if u < 0.0 || u > 1.0 { + // return false, 0.0; + // } + + // qvec := cross(tvec, e0); + // v := inv_det * dot(qvec, dir); + + // if v < 0.0 || u + v > 1.0 { + // return false, 0.0; + // } + + // return true, dot(e1, qvec) * inv_det; + //} + + //return false, 0.0; +} + +distance_from_ray_to_point :: (ray: Ray, p: Vector3) -> float { + pb := p - ray.origin; + t0 := dot(ray.direction, pb) / dot(ray.direction, ray.direction); + if t0 <= 0.0 { + return length(pb); + } else if t0 > 0.0 && t0 < 1.0 { + return length(p - (ray.origin + t0 * ray.direction)); + } else { + return length(p - (ray.origin + ray.direction)); + } +} + +closest_point_on_ray :: (from: Vector3, dir: Vector3, p: Vector3) -> Vector3 { + lhs := p - from; + dot_p := dot(lhs, dir); + return from + dir * dot_p; +} + +closest_distance_between_rays :: (l1: Ray, l2: Ray) -> distance: float, t1: float, t2: float { + u := l1.direction; + v := l2.direction; + w := l1.origin - l2.origin; + a := dot(u, u); + b := dot(u, v); + c := dot(v, v); + d := dot(u, w); + e := dot(v, w); + D := a * c - b * b; + + sc: float; + tc: float; + + if D < 0.00001 { + sc = 0.0; + + if b > c { + tc = d / b; + } else { + tc = e / c; + } + } else { + sc = ((b * e) - (c * d)) / D; + tc = ((a * e) - (b * d)) / D; + } + + P1 := u * sc; + P2 := v * tc; + dP := w + P1 - P2; + + return length(dP), sc, tc; +} + +get_positions :: (e : *Entity, n : Node_Render_Data, m : Mesh, i0 : u32, i1 : u32, i2 : u32) -> Vector3, Vector3, Vector3 { + p0 : Vector3; + p1 : Vector3; + p2 : Vector3; + + p0 = transform_point(n.transform.world_matrix, m.positions[i0]); + p1 = transform_point(n.transform.world_matrix, m.positions[i1]); + p2 = transform_point(n.transform.world_matrix, m.positions[i2]); + + return p0, p1, p2; +} + +ray_entity_intersect :: (ray: Ray, e: *Entity) -> bool, float, Vector3 { + has_hit := false; + closest : float = 10000000; + closest_normal : Vector3; + + for n: e.renderable.model.nodes { + render_node := e.renderable.nodes[it_index]; + for handle: n.meshes { + m := parray_get(*renderer.meshes, handle); + index := 0; + + + if m.indices.count > 0 { + while index < m.indices.count - 1 { + i0 := m.indices[index]; + i1 := m.indices[index + 1]; + i2 := m.indices[index + 2]; + + p2, p1, p0 := get_positions(e, render_node, m, i0, i1, i2); + + result, t, normal := ray_triangle_intersect(ray.origin, ray.direction, p0, p1, p2); + if result && t < closest { + has_hit = true; + closest = t; + closest_normal = normal; + } + + index += 3; + } + } else { + while index < m.positions.count - 1 { + p2, p1, p0 := get_positions(e, render_node, m, xx index, xx (index + 1), xx (index + 2)); + + result, t, normal := ray_triangle_intersect(ray.origin, ray.direction, p0, p1, p2); + if result && t < closest { + has_hit = true; + closest = t; + closest_normal = normal; + } + + index += 3; + } + } + } + } + + return has_hit, closest, closest_normal; +} \ No newline at end of file diff --git a/core/scene.jai b/core/scene.jai new file mode 100644 index 0000000..9852b02 --- /dev/null +++ b/core/scene.jai @@ -0,0 +1,142 @@ +#load "../renderer/directional_light.jai"; +#load "particles.jai"; + +#placeholder Entity_Storage; + +MAX_CACHED_PILES :: 8; + +Scene :: struct { + name: string; + + entities : [..] *Entity; + + particle_systems : Bucket_Array(Particle_System, 64); + + bullet_impact_particle_systems : [..] *Particle_System; + + by_type : Entity_Storage; + + pool : Flat_Pool; + allocator : Allocator; + + camera : Camera; + directional_light : Directional_Light; + +} + +Entity_File_Info :: struct { + id: s64; + full_path: string; +} + +visitor :: (info : *File_Visit_Info, files: *[..] Entity_File_Info) { + if info.is_directory + return; + path, basename, ext := path_decomp (info.full_name); + + // Entity text files + if ext == "ent" && basename != "cam" { + file_info : Entity_File_Info; + file_info.id = cast(s32)string_to_int(basename); + file_info.full_path = copy_temporary_string(info.full_name); + array_add(files, file_info); + } +} + +load_scene :: (path: string) -> *Scene { + scene := create_scene("", 1024); + + files : [..] Entity_File_Info; + files.allocator = temp; + visit_files(path, true, *files, visitor); + + for file: files { + deserialize_entity(scene, file.full_path); + } + + return scene; +} + +save_scene :: (scene: *Scene, path: string) { + scene.camera = game_state.camera; + builder : String_Builder; + builder.allocator = temp; + + full_path := tprint("%/%", path, scene.name); + + make_directory_if_it_does_not_exist(full_path); + + for scene.entities { + if it.flags & .DONT_SAVE continue; + + serialize_entity(it, full_path); + } + + // Save camera + //print_to_builder(*builder, "Camera: % % % % %\n", scene.camera.position.x, scene.camera.position.y, scene.camera.position.z, scene.camera.rotation.yaw, scene.camera.rotation.pitch); + + //write_entire_file(path, builder_to_string(*builder)); +} + +unload_scene :: (scene: *Scene) { + for e: scene.entities { + destroy_entity(e, false); + } + + fini(*scene.pool); + free(scene); +} + +create_scene :: (name: string, max_entities: s64 = 256) -> *Scene { + scene := New(Scene); + scene.name = copy_string(name); + + // Setup allocator + scene.pool = .{}; + scene.allocator.data = *scene.pool; + scene.allocator.proc = flat_pool_allocator_proc; + + // Assign allocator to everything that needs allocations + scene.entities.allocator = scene.allocator; + scene.particle_systems.allocator = scene.allocator; + scene.bullet_impact_particle_systems.allocator = scene.allocator; + + array_reserve(*scene.entities, max_entities); + + scene.directional_light.color_and_intensity = .{1,1,1,1}; + scene.directional_light.direction = to_v4(normalize(Vector3.{0.3, -0.3, 0.5})); + + dir_light_data : Directional_Light_Buffer_Data; + dir_light_data.color_and_intensity = scene.directional_light.color_and_intensity; + dir_light_data.direction = scene.directional_light.direction; + upload_data_to_buffer(renderer, directional_light_buffer, *dir_light_data, size_of(Directional_Light_Buffer_Data)); + + array_resize(*scene.bullet_impact_particle_systems, 32); + + for 0..31 { + scene.bullet_impact_particle_systems[it] = create_particle_system(particle_pipeline, on_update_bullet_hit_particles, null, scene); + } + + return scene; +} + +register_entity :: (scene: *Scene, entity: *Entity) { + entity.scene = scene; + entity.id = next_entity_id; + array_add(*scene.entities, entity); + next_entity_id += 1; + + if net_data.net_mode == { + case .LISTEN_SERVER; #through; + case .DEDICATED_SERVER; { + //net_spawn_entity(entity); + } + } +} + +unregister_entity :: (scene: *Scene, entity: *Entity) { + array_unordered_remove_by_value(*scene.entities, entity); +} + +#scope_file +next_entity_id: Entity_Id; \ No newline at end of file diff --git a/core/stack.jai b/core/stack.jai new file mode 100644 index 0000000..0e5b126 --- /dev/null +++ b/core/stack.jai @@ -0,0 +1,29 @@ +Stack :: struct(Value_Type : Type) { + values: [..] Value_Type; +} + +stack_push :: (stack: *Stack, value: stack.Value_Type) { + array_add(*stack.values, value); +} + +stack_pop :: (stack: *Stack) -> stack.Value_Type { + if stack.values.count > 0 { + index := stack.values.count - 1; + stack.values.count -= 1; + return stack.values.data[index]; + } + + return null; +} + +stack_peek :: (stack: *Stack) -> stack.Value_Type { + if stack.values.count > 0 { + return stack.values[stack.values.count-1]; + } + + return null; +} + +is_stack_empty :: (stack: *Stack) -> bool { + return stack.values.count == 0; +} diff --git a/core/static_array.jai b/core/static_array.jai new file mode 100644 index 0000000..b6b9a7b --- /dev/null +++ b/core/static_array.jai @@ -0,0 +1,21 @@ +Static_Array :: struct (Data_Type : Type, Count: s64) { + data: [Count] Data_Type; + count: s64; +} + +array_add :: (static_array: *Static_Array, value: static_array.Data_Type) { + assert(static_array.count <= static_array.Count); + static_array.data[static_array.count] = value; + static_array.count += 1; +} + +for_expansion :: (static_array: *Static_Array, body: Code, flags: For_Flags) #expand { + for `it_index: 0..static_array.count-1 { + #if flags & .POINTER { + `it := *static_array.data[it_index]; + } else { + `it := static_array.data[it_index]; + } + #insert body; + } +} \ No newline at end of file diff --git a/core/string_helpers.jai b/core/string_helpers.jai new file mode 100644 index 0000000..b2ecae7 --- /dev/null +++ b/core/string_helpers.jai @@ -0,0 +1,6 @@ +to_temp_c_string :: (s: string) -> *u8 { + result : *u8 = alloc(s.count + 1,, allocator=temp); + memcpy(result, s.data, s.count); + result[s.count] = 0; + return result; +} diff --git a/core/transform.jai b/core/transform.jai new file mode 100644 index 0000000..b9146e4 --- /dev/null +++ b/core/transform.jai @@ -0,0 +1,172 @@ +#import "Math"; + +Transform_Identity : Transform : .{ .{0,0,0}, .{0,0,0,1}, .{1,1,1}, Matrix4_Identity, Matrix4_Identity, false }; + +Transform :: struct { + position: Vector3; + orientation: Quaternion; + scale : Vector3; + + model_matrix: Matrix4 = Matrix4_Identity; @DontSerialize + + world_matrix: Matrix4; @DontSerialize + + dirty: bool; @DontSerialize +} + +create_identity_transform :: () -> Transform { + transform : Transform; + + transform.position = .{}; + transform.orientation = .{0,0,0,1}; + transform.scale = .{1,1,1}; + transform.model_matrix = Matrix4_Identity; + + update_matrix(*transform); + + return transform; +} + +make_matrix :: (position: Vector3, orientation: Quaternion, scale: Vector3) -> Matrix4 { + trans_mat := make_translation_matrix4(position); + scale_mat := make_scale_matrix4(scale); + rot_mat := rotation_matrix(Matrix4, orientation); + return trans_mat * rot_mat * scale_mat; +} + +create_transform :: (position: Vector3, orientation: Quaternion, scale: Vector3) -> Transform { + transform : Transform; + + transform.position = position; + transform.orientation = orientation; + transform.scale = scale; + transform.model_matrix = make_matrix(position, orientation, scale); + + return transform; +} + +update_matrix :: (transform: *Transform) { + transform.model_matrix = make_matrix(transform.position, transform.orientation, transform.scale); + transform.dirty = true; +} + +set_position :: (transform: *Transform, position: Vector3, calculate_matrix: bool = true) { + transform.position = position; + if calculate_matrix update_matrix(transform); +} + +set_position :: (transform: *Transform, x: float, y: float, z: float, calculate_matrix: bool = true) { + transform.position.x = x; + transform.position.y = y; + transform.position.z = z; + if calculate_matrix update_matrix(transform); +} + +translate :: (transform: *Transform, translation: Vector3, calculate_matrix: bool = true) { + transform.position += translation; + if calculate_matrix update_matrix(transform); +} + +euler_to_quaternion :: (yaw: float, pitch: float, roll: float) -> Quaternion { + cy := cos(yaw * 0.5); + sy := sin(yaw * 0.5); + cp := cos(pitch * 0.5); + sp := sin(pitch * 0.5); + cr := cos(roll * 0.5); + sr := sin(roll * 0.5); + + q: Quaternion; + q.w = cr * cp * cy + sr * sp * sy; + q.x = sr * cp * cy - cr * sp * sy; + q.y = cr * sp * cy + sr * cp * sy; + q.z = cr * cp * sy - sr * sp * cy; + + return q; +} + +sign :: (val: $T) -> T { + if val < 0 return -1.0; + if val > 0 return 1.0; + return 0.0; +} + +quaternion_to_euler_v3 :: (q: Quaternion) -> Vector3 { + yaw, pitch, roll := quaternion_to_euler(q); + + v : Vector3 = ---; + v.x = yaw; + v.y = pitch; + v.z = roll; + + return v; +} + +quaternion_to_euler :: (q: Quaternion) -> yaw: float, pitch: float, roll: float { + yaw: float; + pitch: float; + roll: float; + + sinr_cosp := 2.0 * (q.w * q.x + q.y * q.z); + cosr_cosp := 1.0 - 2.0 * (q.x * q.x + q.y * q.y); + roll = atan2(sinr_cosp, cosr_cosp); + + // pitch (y-axis rotation) + sinp := 2.0 * (q.w * q.y - q.z * q.x); + if abs(sinp) >= 1.0 + pitch = sign(sinp) * (PI / 2); // use 90 degrees if out of range + else + pitch = asin(sinp); + + // yaw (z-axis rotation) + siny_cosp := 2.0 * (q.w * q.z + q.x * q.y); + cosy_cosp := 1.0 - 2.0 * (q.y * q.y + q.z * q.z); + yaw = atan2(siny_cosp, cosy_cosp); + + return yaw, pitch, roll; +} + +set_rotation :: (transform: *Transform, orientation: Quaternion, calculate_matrix: bool = true) { + transform.orientation = orientation; + if calculate_matrix update_matrix(transform); +} + +set_rotation :: (transform: *Transform, euler_angles: Vector3, calculate_matrix: bool = true) { + orientation := euler_to_quaternion(degrees_to_radians(euler_angles.y), degrees_to_radians(euler_angles.x), degrees_to_radians(euler_angles.z)); + transform.orientation = orientation; + if calculate_matrix update_matrix(transform); +} + +set_scale :: (transform: *Transform, scale: Vector3, calculate_matrix: bool = true) { + transform.scale = scale; + if calculate_matrix update_matrix(transform); +} + +set_scale :: (transform: *Transform, scale: float, calculate_matrix: bool = true) { + transform.scale = .{scale, scale, scale}; + if calculate_matrix update_matrix(transform); +} + +get_forward :: (transform: Transform) -> Vector3 { + v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{0,0,1,0}; + return .{v.x, v.y, v.z}; +} + +get_right :: (transform: Transform) -> Vector3 { + v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{1,0,0,0}; + return .{v.x, v.y, v.z}; +} + +get_up :: (transform: Transform) -> Vector3 { + v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{0,1,0,0}; + return .{v.x, v.y, v.z}; +} + +get_position :: (transform: Transform) -> Vector3 { + return .{transform.model_matrix._14, transform.model_matrix._24, transform.model_matrix._34}; +} + +get_rotation :: (transform: Transform) -> Vector3 { + + return .{transform.model_matrix._14, transform.model_matrix._24, transform.model_matrix._34}; +} + diff --git a/editor/editor.jai b/editor/editor.jai new file mode 100644 index 0000000..95e7b72 --- /dev/null +++ b/editor/editor.jai @@ -0,0 +1,378 @@ +Gizmo_Space :: enum { + LOCAL; + WORLD; +} + +Transform_Axis :: enum { + NONE; + UP; + FORWARD; + RIGHT; + CENTER; +} + +Transform_Type :: enum { + TRANSLATION; + ROTATION; + SCALE; +} + +Buffer_Info :: struct { + buffer: Buffer_Handle; + vertex_count: u32; +} + +Transform_Gizmo :: struct { + active: bool = false; + transform_type: Transform_Type = .TRANSLATION; + space: Gizmo_Space; + + selected_axis: Transform_Axis; + + first_hit_position: Vector3; + actual_entity_position: Vector3; // The actual position of the selected entity. Used for snapping + actual_entity_scale: Vector3; // The actual position of the selected entity. Used for snapping + last_circle_dir: Vector3; + + clicked: bool; + + transform: Transform; + + can_use: bool = true; + + center_model_buffer: Buffer_Handle; + up_model_buffer: Buffer_Handle; + forward_model_buffer: Buffer_Handle; + right_model_buffer: Buffer_Handle; + + color_center: Color; + color_up: Color; + color_forward: Color; + color_right: Color; + + color_center_buffer: Buffer_Handle; + color_up_buffer: Buffer_Handle; + color_forward_buffer: Buffer_Handle; + color_right_buffer: Buffer_Handle; + + uniform_gizmo_scale: float; +} + + +Editor :: struct { + show_menu: bool; + should_check_entities: bool; + camera: Camera; + transform_gizmo: Transform_Gizmo; + selected_entity: *Entity; + + mouse_viewport_state: Interaction_State; + last_right_mouse_click_time: float; + + menu_position: Vector2; +} + +editor : Editor; + +update_transform_gizmo :: (ray: Ray, mouse_position: Vector2) -> bool { + return false; +// selected_entity := engine.editor.selected_entity; +// +// if key_down(.TAB) { +// if engine.editor.transform_gizmo.space == { +// case .WORLD; +// engine.editor.transform_gizmo.space = .LOCAL; +// case .LOCAL; +// engine.editor.transform_gizmo.space = .WORLD; +// } +// } +// +// if engine.editor.transform_gizmo.space == { +// case .WORLD; +// set_rotation(*engine.editor.transform_gizmo.transform, .{0,0,0,1}); +// case .LOCAL; +// set_rotation(*engine.editor.transform_gizmo.transform, selected_entity.transform.orientation); +// } +// +// if engine.editor.transform_gizmo.transform_type == { +// case .TRANSLATION; +// if !key_pressed(.MOUSE_LEFT) { +// selected_axis, t := intersect_translation_gizmo(ray); +// engine.editor.transform_gizmo.selected_axis = selected_axis; +// } else if engine.editor.transform_gizmo.can_use && engine.editor.transform_gizmo.selected_axis != .NONE { +// first_update := key_down(.MOUSE_LEFT); +// +// if first_update { +// engine.editor.transform_gizmo.actual_entity_position = selected_entity.transform.position; +// } +// +// // Move the currently selected entity along the selected axis +// axis_vec : Vector3; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .UP; +// axis_vec.y = 1; +// case .FORWARD; +// axis_vec.z = 1; +// case .RIGHT; +// axis_vec.x = 1; +// } +// +// r1 : Ray; +// r1.origin = selected_entity.transform.position; +// r1.direction = rotate(axis_vec, engine.editor.transform_gizmo.transform.orientation); +// +// r2 := normalized_screen_to_ray_v2(*engine.editor.camera, mouse_position); +// +// d, t1, t2 := closest_distance_between_rays(r1, r2); +// +// new_position := r1.origin + r1.direction * t1; +// +// if first_update { +// engine.editor.transform_gizmo.first_hit_position = new_position; +// } +// +// position_change := new_position - engine.editor.transform_gizmo.first_hit_position; +// +// entity_position := engine.editor.transform_gizmo.actual_entity_position + position_change; +// +// if selected_entity.flags & Entity_Flags.SNAP_TO_GRID { +// entity_position.x -= fmod_cycling(entity_position.x - selected_entity.snap_offset.x, selected_entity.snap_intervals.x);// + selected_entity.snap_offset.x; +// entity_position.y -= fmod_cycling(entity_position.y - selected_entity.snap_offset.y, selected_entity.snap_intervals.y);// + selected_entity.snap_offset.y; +// entity_position.z -= fmod_cycling(entity_position.z - selected_entity.snap_offset.z, selected_entity.snap_intervals.z);// + selected_entity.snap_offset.z; +// } +// +// selected_entity.transform.position = entity_position; +// set_position(*engine.editor.transform_gizmo.transform, entity_position); +// } +// +// color_up := engine.editor.transform_gizmo.color_up; +// color_forward := engine.editor.transform_gizmo.color_forward; +// color_right := engine.editor.transform_gizmo.color_right; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .NONE; +// case .UP; +// color_up = Color.{1,1,0,1}; +// case .FORWARD; +// color_forward = Color.{1,1,0,1}; +// case .RIGHT; +// color_right = Color.{1,1,0,1}; +// } +// +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color)); +// case .ROTATION; +// selected_entity := engine.editor.selected_entity; +// +// if !engine.editor.transform_gizmo.clicked { +// selected_axis, point := intersect_rotation_gizmo(ray); +// engine.editor.transform_gizmo.selected_axis = selected_axis; +// +// if engine.editor.transform_gizmo.selected_axis != .NONE && key_down(.MOUSE_LEFT) { +// engine.editor.transform_gizmo.clicked = true; +// engine.editor.transform_gizmo.last_circle_dir = normalize(point - selected_entity.transform.position); +// } +// } else if !key_pressed(.MOUSE_LEFT) { +// engine.editor.transform_gizmo.clicked = false; +// } else { +// direction : Vector3; +// if engine.editor.transform_gizmo.selected_axis == { +// case .UP; +// direction = .{0,1,0}; +// case .FORWARD; +// direction = .{0,0,1}; +// case .RIGHT; +// direction = .{1,0,0}; +// } +// direction = rotate(direction, engine.editor.transform_gizmo.transform.orientation); +// +// // Find the rotation +// circle : Circle; +// circle.radius = engine.editor.transform_gizmo.uniform_gizmo_scale; +// circle.center = selected_entity.transform.position; +// circle.orientation = direction; +// +// distance, point := closest_distance_ray_circle(ray, circle); +// new_dir := normalize(point - selected_entity.transform.position); +// +// dotp := dot(engine.editor.transform_gizmo.last_circle_dir, new_dir); +// angle := acos(clamp(dotp, -1.0, 1.0)); +// cp := cross(engine.editor.transform_gizmo.last_circle_dir, new_dir); +// +// if dot(direction, cp) < 0 { +// angle *= -1.0; +// } +// +// q : Quaternion; +// set_from_axis_and_angle(*q, direction, angle); +// +// selected_entity.transform.orientation = q * selected_entity.transform.orientation; +// update_matrix(*selected_entity.transform); +// +// set_rotation(*engine.editor.transform_gizmo.transform, selected_entity.transform.orientation); +// +// engine.editor.transform_gizmo.last_circle_dir = new_dir; +// } +// +// color_up := engine.editor.transform_gizmo.color_up; +// color_forward := engine.editor.transform_gizmo.color_forward; +// color_right := engine.editor.transform_gizmo.color_right; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .NONE; +// case .UP; +// color_up = Color.{1,1,0,1}; +// case .FORWARD; +// color_forward = Color.{1,1,0,1}; +// case .RIGHT; +// color_right = Color.{1,1,0,1}; +// } +// +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color)); +// case .SCALE; +// if !key_pressed(.MOUSE_LEFT) { +// selected_axis, t := intersect_scale_gizmo(ray); +// engine.editor.transform_gizmo.selected_axis = selected_axis; +// } else if engine.editor.transform_gizmo.selected_axis != .NONE { +// selected_entity := engine.editor.selected_entity; +// first_update := key_down(.MOUSE_LEFT); +// +// if first_update { +// engine.editor.transform_gizmo.actual_entity_position = selected_entity.transform.position; +// engine.editor.transform_gizmo.actual_entity_scale = selected_entity.transform.scale; +// } +// +// // Move the currently selected entity along the selected axis +// axis_vec : Vector3; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .UP; +// axis_vec.y = 1; +// case .FORWARD; +// axis_vec.z = -1; +// case .RIGHT; +// axis_vec.x = 1; +// case .CENTER; axis_vec = .{1,1,1}; +// } +// +// r1 : Ray; +// r1.origin = selected_entity.transform.position; +// r1.direction = rotate(axis_vec, engine.editor.transform_gizmo.transform.orientation); +// // Shoot a ray from screen to world +// mouse_position : Vector2; +// mouse_position.x = xx engine.input.mouse.x; +// mouse_position.y = xx engine.input.mouse.y; +// +// screen_size : Vector2; +// screen_size.x = cast(float)engine.window.width; +// screen_size.y = cast(float)engine.window.height; +// +// r2 := screen_to_ray_v2(*engine.editor.camera, mouse_position, screen_size); +// +// d, t1, t2 := closest_distance_between_rays(r1, r2); +// new_position := r1.origin + r1.direction * t1; +// +// if first_update { +// engine.editor.transform_gizmo.first_hit_position = new_position; +// } +// +// position_change := new_position - engine.editor.transform_gizmo.first_hit_position; +// scale_speed := ifx key_pressed(.SHIFT) then 4.0 else 1.0; +// +// if selected_entity.uses_uniform_scale { +// current_scale := selected_entity.transform.scale; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .UP; { +// current_scale.y = engine.editor.transform_gizmo.actual_entity_scale.y + position_change.y * scale_speed; +// position_change.x = 0; +// position_change.z = 0; +// } +// case .FORWARD; { +// current_scale.z = engine.editor.transform_gizmo.actual_entity_scale.z + position_change.z * scale_speed; +// position_change.x = 0; +// position_change.y = 0; +// } +// case .RIGHT; { +// current_scale.x = engine.editor.transform_gizmo.actual_entity_scale.x + position_change.x * scale_speed; +// position_change.y = 0; +// position_change.z = 0; +// } +// case .CENTER; { +// current_scale.x = engine.editor.transform_gizmo.actual_entity_scale.x + position_change.x * scale_speed; +// current_scale.y = current_scale.x; // @Incomplete: This is most definitely wrong! +// current_scale.z = current_scale.x; +// +// position_change.y = 0; +// position_change.z = 0; +// } +// } +// +// set_scale(*selected_entity.transform, current_scale); +// +// } else { +// if engine.editor.transform_gizmo.selected_axis == { +// case .UP; +// position_change.x = 0; +// position_change.z = 0; +// case .FORWARD; +// position_change.x = 0; +// position_change.y = 0; +// case .RIGHT; +// position_change.y = 0; +// position_change.z = 0; +// } +// +// entity_scale := engine.editor.transform_gizmo.actual_entity_scale + position_change * scale_speed; +// set_scale(*selected_entity.transform, entity_scale); +// } +// } +// +// color_up := engine.editor.transform_gizmo.color_up; +// color_forward := engine.editor.transform_gizmo.color_forward; +// color_right := engine.editor.transform_gizmo.color_right; +// color_center := Color.{1,1,1,1}; +// +// if engine.editor.transform_gizmo.selected_axis == { +// case .NONE; +// case .UP; +// color_up = Color.{1,1,0,1}; +// case .FORWARD; +// color_forward = Color.{1,1,0,1}; +// case .RIGHT; +// color_right = Color.{1,1,0,1}; +// case .CENTER; +// color_center = Color.{1,1,0,1}; +// } +// +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_center_buffer, *color_center, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color)); +// } +// +// return engine.editor.transform_gizmo.selected_axis != .NONE; +} + +update_gizmo_buffers :: () { +// update_matrix(*engine.editor.transform_gizmo.transform); +// +// up_rotation := rotation_matrix(Matrix3, euler_to_quaternion(degrees_to_radians(90), degrees_to_radians(90), degrees_to_radians(0))); +// right_rotation := rotation_matrix(Matrix3, euler_to_quaternion(degrees_to_radians(90), degrees_to_radians(0), degrees_to_radians(90))); +// up_model := engine.editor.transform_gizmo.transform.model_matrix * up_rotation; +// right_model := engine.editor.transform_gizmo.transform.model_matrix * right_rotation; +// center_scale := make_scale_matrix4(.{0.15, 0.15, 0.15}); +// center_model := engine.editor.transform_gizmo.transform.model_matrix * center_scale; +// +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.center_model_buffer, *center_model, size_of(Matrix4)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.up_model_buffer, *up_model, size_of(Matrix4)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.forward_model_buffer, *engine.editor.transform_gizmo.transform.model_matrix, size_of(Matrix4)); +// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.right_model_buffer, *right_model, size_of(Matrix4)); +} + + +#load "editor_ui.jai"; \ No newline at end of file diff --git a/editor/editor_ui.jai b/editor/editor_ui.jai new file mode 100644 index 0000000..7e24dd5 --- /dev/null +++ b/editor/editor_ui.jai @@ -0,0 +1,310 @@ +#load "../ui/widgets.jai"; + +pick_scene_view_at :: (coordinates: Vector2) { + ray := normalized_screen_to_ray_v2(*editor.camera, coordinates); + + if editor.should_check_entities { + hit_entity : *Entity; + closest : float = 100000000; + + for game_state.current_scene.entities { + if !(it.flags & .RENDERABLE) continue; + //if it.flags & Entity_Flags.DELETED || !it.selectable || it.parent != null continue; + + success, dist := ray_entity_intersect(ray, it); + if success && dist < closest { + closest = dist; + hit_entity = it; + } + } + + if hit_entity != null { + if cast(*Entity)hit_entity != editor.selected_entity { + editor.selected_entity = hit_entity; + } + } else if editor.selected_entity != null { + editor.selected_entity = null; + } + } +} + +editor_ui :: () { + ui_full_size_background(); + ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_padding(1); + ui_toolbar(); + ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.HORIZONTAL); + { + if ui_toolbar_button("File") { + } + + //ui_space(15, 10); + + if ui_toolbar_button("Edit") { + } + + //ui_space(15, 10); + + if ui_toolbar_button("View") { + } + + ui_space(20, 0); + + //if mode == { + // case .PLAYING; { + // ui_set_next_background_color(.{0,0.6,0,1}); + // if ui_button_with_texture(editor.icons.stop) { + // edit_current_scene(); + // } + // ui_label(tprint("Playing '%'", current_scene.name), .{0,0.7,0,1}); + // } + // case .EDITING; { + ui_set_next_background_color(.{0.6,0,0,1}); + //if ui_button_with_texture(editor.icons.play) { + // play_current_editor_scene(); + //} + ui_label(tprint("Editing '%'", game_state.current_scene.name), .{1,1,1,1}); + //} + //} + } + ui_pop_parent(); + + ui_set_next_size_x(.PCT, 1.0); + ui_set_next_size_y(.PCT, 0.965); + ui_set_next_background_color(.{0,0,0,1}); + background := ui_box(.NONE); + + ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.HORIZONTAL); + { + ui_set_next_size_x(.PCT, 0.1); + ui_set_next_size_y(.PCT, 1.0); + ui_set_next_padding(1); + first_panel := ui_box(.NONE); + ui_push_parent(first_panel, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_size_x(.PCT, 1.0); + ui_set_next_size_y(.PCT, 1.0); + ui_set_next_background_color(.{0.04, 0.04, 0.04, 1.0}); + background := ui_box(.DRAW_BACKGROUND|.DRAW_BORDER); + + ui_push_parent(background, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_size_x(.PCT, 1.0); + ui_tab_title_bar("ENTITIES"); + + for game_state.current_scene.entities { + ui_set_next_padding(20); + clicked := false; + if it.name.count == 0 { + clicked = ui_clickable_label(tprint("%", it.type), it == editor.selected_entity, it_index); + } else { + clicked = ui_clickable_label(it.name, it == editor.selected_entity, it_index); + } + + if clicked { + editor.selected_entity = it; + } + //ui_space(0, 5); + } + } + ui_pop_parent(); + } + ui_pop_parent(); + + // Scene + ui_set_next_size_x(.PCT, 0.7); + ui_set_next_size_y(.PCT, 1.0); + ui_set_next_padding(1); + viewport_layer := ui_box(.NONE); + ui_push_parent(viewport_layer, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_size_x(.PCT, 1.0); + ui_tab_title_bar("SCENE"); + ui_set_next_size_x(.PCT, 1.0); + ui_set_next_size_y(.PCT, 0.9); + + state := ui_interactable_texture(get_texture_from_pass("FXAA")); + if state.left_mouse_down { + //pick_scene_view_at(.{state.normalized_local_mouse_coordinates.x, 1.0 - state.normalized_local_mouse_coordinates.y}); + + } + + editor.mouse_viewport_state = state; + + ui_set_next_size_x(.PCT, 1.0); + ui_set_next_size_y(.PCT, 0.1); + ui_tab_title_bar("SCENES"); + } + ui_pop_parent(); + + // Properties + ui_set_next_size_x(.PCT, 0.2); + ui_set_next_size_y(.PCT, 1.0); + ui_set_next_background_color(.{0.04, 0.04, 0.04, 1.0}); + ui_set_next_padding(1); + inspector := ui_box(.DRAW_BACKGROUND | .DRAW_BORDER); + ui_push_parent(inspector, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_size_x(.PCT, 1.0); + ui_tab_title_bar("PROPERTIES"); + + if editor.selected_entity != null { + ui_slider(*slider_value, 0.0, 1.0); + ui_label(tprint("Name: %", editor.selected_entity.name)); + ui_label(tprint("Id: %", editor.selected_entity.id)); + ui_label(tprint("Position: % % %", editor.selected_entity.transform.position.x, editor.selected_entity.transform.position.y, editor.selected_entity.transform.position.z)); + ui_label(tprint("Rotation: % % % %", editor.selected_entity.transform.orientation.x, editor.selected_entity.transform.orientation.y, editor.selected_entity.transform.orientation.z, editor.selected_entity.transform.orientation.w)); + ui_label(tprint("Scale: % % %", editor.selected_entity.transform.scale.x, editor.selected_entity.transform.scale.y, editor.selected_entity.transform.scale.z)); + } + + } + ui_pop_parent(); + } + ui_pop_parent(); + } + ui_pop_parent(); +} + +slider_value : float = 0.0; + +base_editor_update :: () { + if editor.show_menu && (key_down(.MOUSE_LEFT) || key_down(.ESCAPE)) { + editor.show_menu = false; + eat_key(.MOUSE_LEFT); + } + + // Check if we hit the gizmo + // @Incomplete: MOVE THIS + editor.should_check_entities = true; + + if editor.selected_entity != null { + gizmo_scale := distance(editor.selected_entity.transform.position, editor.camera.position) * 0.1 * 0.5; + editor.transform_gizmo.uniform_gizmo_scale = gizmo_scale; + set_scale(*editor.transform_gizmo.transform, .{gizmo_scale, gizmo_scale, gizmo_scale}); + + coordinates := Vector2.{editor.mouse_viewport_state.normalized_local_mouse_coordinates.x, 1.0 - editor.mouse_viewport_state.normalized_local_mouse_coordinates.y}; + ray := normalized_screen_to_ray_v2(*editor.camera, coordinates); + + if update_transform_gizmo(ray, coordinates) { + editor.should_check_entities = false; + } + } + + editor_ui(); + + //if editor.show_menu { + // placed_entity := imgui_show_place_entity(); + // if placed_entity != null { + // // @Incomplete: At some point we want this to place entities at either a set distance or a distance depending on a raycast in the camera's forward direction so that we don't place things behind other entities + // set_position(*placed_entity.transform, editor.camera.position + editor.camera.forward * 30); + // editor.show_menu = false; + // editor.selected_entity = placed_entity; + // } + //} + + camera := *editor.camera; + + if key_pressed(.CTRL) && key_down(.S) { + save_scene(game_state.current_scene, "../assets/scenes/"); + //show_message("Saved scene"); + } + + if editor.selected_entity != null { + // @Incomplete:@Incomplete: Duplicate + //if key_pressed(.CTRL) && key_down(.D) { + // make_directory_if_it_does_not_exist("../temp"); + // save_entity(editor.selected_entity, "../temp/", "temp"); + // duplicated := load_entity(editor_scene, "../temp/temp.ent"); + // editor.selected_entity = duplicated; + //} + + // DELETE + // DELETE + //if key_down(.DELETE) || key_down(.BACKSPACE) { + // delete_entity(editor.selected_entity); + // editor.selected_entity = null; + // editor.transform_gizmo.selected_axis = .NONE; + //} + } + + //if key_pressed(.CTRL) && key_down(.Z) { + // editor_undo(); + //} + + if game_state.mode == .EDITING { + if key_pressed(.MOUSE_RIGHT) { + set_show_cursor(false); + // Update camera + mouse_speed :: 54.0; + dir : Vector3; + + if key_pressed(.W) { + dir += camera.forward; + } else if key_pressed(.S) { + dir += -camera.forward; + } + + if key_pressed(.A) { + dir += -camera.right; + } else if key_pressed(.D) { + dir += camera.right; + } + + if key_pressed(.Q) { + dir += -camera.up; + } else if key_pressed(.E) { + dir += camera.up; + } + + dir = normalize(dir); + + if length(dir) > 0.05 { + multiplier := ifx key_pressed(.SHIFT) then 10.0 else 1.0; + camera.position += dir * 20.0 * multiplier * dt; + //print("Camera position %\n", camera.position); + } + + MOUSE_SENSITIVITY :: 0.06; + camera.rotation.yaw -= input.mouse.delta_x * MOUSE_SENSITIVITY; + camera.rotation.pitch += -input.mouse.delta_y * MOUSE_SENSITIVITY; + + if key_down(.MOUSE_RIGHT) { + editor.last_right_mouse_click_time = time; + } + } else { + set_show_cursor(true); + + if time - editor.last_right_mouse_click_time < 0.2 { + mouse_position : Vector2; + mouse_position.x = xx input.mouse.x; + mouse_position.y = xx input.mouse.y; + editor.show_menu = true; + editor.menu_position.x = mouse_position.x; + editor.menu_position.y = cast(float)renderer.render_target_height - mouse_position.y; + } + + if key_down(.W) { + editor.transform_gizmo.transform_type = .TRANSLATION; + } + + if key_down(.E) { + editor.transform_gizmo.transform_type = .ROTATION; + } + + if key_down(.R) { + editor.transform_gizmo.transform_type = .SCALE; + } + } + + update_view_matrix(camera); + } + + if editor.selected_entity != null { + e := editor.selected_entity; + set_position(*editor.transform_gizmo.transform, e.transform.position); + update_gizmo_buffers(); + } + + //edit_scene_settings(*game_state.current_scene.settings); +} diff --git a/input/input.jai b/input/input.jai new file mode 100644 index 0000000..24f1f7d --- /dev/null +++ b/input/input.jai @@ -0,0 +1,389 @@ +#load "sdl_input.jai"; + +LEFT_STICK_DEADZONE :: 0.239; +RIGHT_STICK_DEADZONE :: 0.265; + +Key_Flags :: enum_flags u8 { + NONE :: 0; + DOWN :: 1; + START :: 2; + END :: 4; + EATEN :: 8; +} + +Key_Code :: enum { + INVALID :: 0; + + A; + B; + C; + D; + E; + F; + G; + H; + I; + J; + K; + L; + M; + N; + O; + P; + Q; + R; + S; + T; + U; + V; + W; + X; + Y; + Z; + + ZERO; + ONE; + TWO; + THREE; + FOUR; + FIVE; + SIX; + SEVEN; + EIGHT; + NINE; + + F1; + F2; + F3; + F4; + F5; + F6; + F7; + F8; + F9; + F10; + F11; + F12; + + SPACE; + BACKSPACE; + RETURN; + TAB; + SHIFT; + CTRL; + ALT; + DELETE; + TILDE; + ESCAPE; + + LEFT; + RIGHT; + UP; + DOWN; + + MOUSE_LEFT; + MOUSE_RIGHT; + + KEYS_MAX; +} + +Gamepad_Button :: enum { + SPECIAL_BOTTOM; + SPECIAL_TOP; + SPECIAL_LEFT; + SPECIAL_RIGHT; + + LEFT_STICK_LEFT; + LEFT_STICK_RIGHT; + LEFT_STICK_UP; + LEFT_STICK_DOWN; + + LEFT_STICK_BUTTON; + RIGHT_STICK_BUTTON; + + DPAD_LEFT; + DPAD_RIGHT; + DPAD_UP; + DPAD_DOWN; + + LEFT_BUMPER; + RIGHT_BUMPER; + + LEFT_TRIGGER; + RIGHT_TRIGGER; + + GAMEPAD_MAX; +} + +Gamepad :: struct { + id : s32; + left_stick_x : float; + left_stick_y : float; + right_stick_x : float; + right_stick_y : float; + + buttons : [Gamepad_Button.GAMEPAD_MAX] Key_Flags; +} + +MAX_GAMEPADS :: 4; +MAX_MAPPINGS :: 4; + +Action :: enum {} + +Action_Mapping :: struct { + gamepad_buttons: [MAX_MAPPINGS] Gamepad_Button; + keys: [MAX_MAPPINGS] Key_Code; + gamepad_mapping_count: s32; + key_mapping_count: s32; +} + +Last_Touched_State :: enum { + KEYBOARD_AND_MOUSE; + GAMEPAD; +} + +Input_State :: struct { + exit : bool; + action_mappings: [..] Action_Mapping; + + keys: [Key_Code.KEYS_MAX] Key_Flags; + + mouse : struct { + x : float; + y : float; + delta_x : float; + delta_y : float; + first: bool = true; + wheel: float; + } + + gamepads: [MAX_GAMEPADS] Gamepad; + num_gamepads: s32; + + has_char : bool; + current_char: s8; + + last_touched: Last_Touched_State; +} + +input : Input_State; + +init_input :: () { + input = .{}; + input.has_char = false; + + init_sdl_input(); +} + +update_key_state :: (key: Key_Code, down: bool) { + using input; + flags := keys[key]; + + if down { + if !(flags & Key_Flags.DOWN) { + flags = Key_Flags.DOWN | Key_Flags.START; + } + } else { + flags = Key_Flags.END; + } + + keys[key] = flags; + + input.last_touched = .KEYBOARD_AND_MOUSE; +} + +update_gamepad_state :: (gamepad: *Gamepad, button: Gamepad_Button, down: bool) { + flags := gamepad.buttons[button]; + + if down { + if !(flags & Key_Flags.DOWN) { + flags = Key_Flags.DOWN | Key_Flags.START; + input.last_touched = .GAMEPAD; + } + } else if flags & Key_Flags.DOWN { + flags = Key_Flags.END; + } + + gamepad.buttons[button] = flags; + +} + +update_input :: () { + input.mouse.delta_x = 0.0; + input.mouse.delta_y = 0.0; + input.mouse.wheel = 0.0; + input.has_char = false; + remove_all_temp_key_flags(*input); + + update_sdl_input(); + + update_gamepad_input(); +} + +remove_all_temp_key_flags :: (using input_state: *Input_State) { + for 0..Key_Code.KEYS_MAX - 1 { + keys[it] &= ~Key_Flags.START; + keys[it] &= ~Key_Flags.END; + keys[it] &= ~Key_Flags.EATEN; + } + + for g: 0..num_gamepads-1 { + for 0..Gamepad_Button.GAMEPAD_MAX-1 { + gamepads[g].buttons[it] &= ~Key_Flags.START; + gamepads[g].buttons[it] &= ~Key_Flags.END; + } + } +} + +eat_all_input :: (using input_state: *Input_State) { + for 0..Key_Code.KEYS_MAX - 1 { + eat_key(cast(Key_Code)it); + } +} + +eat_mouse_input :: (using input_state: *Input_State) { + eat_key(.MOUSE_LEFT); + eat_key(.MOUSE_RIGHT); +} + +eat_key :: (key: Key_Code) { + input.keys[key] |= Key_Flags.EATEN; +} + +action_down :: (action: Action) -> bool { + mapping := input.action_mappings[action]; + for 0..mapping.key_mapping_count-1 { + if key_down(mapping.keys[it]) { + return true; + } + } + + for 0..mapping.gamepad_mapping_count-1 { + if gamepad_down(0, mapping.gamepad_buttons[it]) { + return true; + } + } + + return false; +} + +action_pressed :: (action: Action) -> bool { + mapping := input.action_mappings[action]; + for 0..mapping.key_mapping_count-1 { + if key_pressed(mapping.keys[it]) { + return true; + } + } + + for 0..mapping.gamepad_mapping_count-1 { + if gamepad_pressed(0, mapping.gamepad_buttons[it]) { + return true; + } + } + + return false; +} + +action_up :: (action: Action) -> bool { + mapping := input.action_mappings[action]; + for 0..mapping.key_mapping_count-1 { + if key_up(mapping.keys[it]) { + return true; + } + } + + for 0..mapping.gamepad_mapping_count-1 { + if gamepad_up(0, mapping.gamepad_buttons[it]) { + return true; + } + } + + return false; +} + +get_mouse_delta_x :: inline () -> float { + return input.mouse.delta_x; +} + +get_mouse_delta_y :: inline () -> float { + return input.mouse.delta_y; +} + +get_mouse_delta :: () -> float, float { + return get_mouse_delta_x(), get_mouse_delta_y(); +} + +get_mouse_wheel_input :: () -> float { + return input.mouse.wheel; +} + +get_horizontal_axis :: () -> float { + if #complete input.last_touched == { + case .KEYBOARD_AND_MOUSE; + if key_pressed(.A) || key_pressed(.LEFT) return -1.0; + else if key_pressed(.D) || key_pressed(.RIGHT) return 1.0; + else return 0.0; + case .GAMEPAD; + if gamepad_pressed(0, .DPAD_LEFT) return -1.0; + else if gamepad_pressed(0, .DPAD_RIGHT) return 1.0; + return input.gamepads[0].left_stick_x; + } +} + +get_vertical_axis :: () -> float { + if #complete input.last_touched == { + case .KEYBOARD_AND_MOUSE; + if key_pressed(.S) || key_pressed(.DOWN) return -1.0; + else if key_pressed(.W) || key_pressed(.UP) return 1.0; + else return 0.0; + case .GAMEPAD; + if gamepad_pressed(0, .DPAD_DOWN) return -1.0; + else if gamepad_pressed(0, .DPAD_UP) return 1.0; + return -input.gamepads[0].left_stick_y; + } +} + +get_right_horizontal_axis :: () -> float { + if #complete input.last_touched == { + case .KEYBOARD_AND_MOUSE; + return input.mouse.delta_x; + case .GAMEPAD; + return input.gamepads[0].right_stick_x; + } +} + +get_right_vertical_axis :: () -> float { + if #complete input.last_touched == { + case .KEYBOARD_AND_MOUSE; + return input.mouse.delta_y; + case .GAMEPAD; + return -input.gamepads[0].right_stick_y; + } +} + +key_down :: (key: Key_Code) -> bool { + flags := input.keys[key]; + return flags & .START && !(flags & .EATEN); +} + +key_pressed :: (key: Key_Code) -> bool { + flags := input.keys[key]; + return flags & .DOWN && !(flags & .EATEN); +} + +key_up :: (key: Key_Code) -> bool { + flags := input.keys[key]; + return flags & .END && !(flags & .EATEN); +} + +gamepad_down :: (index: s32, button: Gamepad_Button) -> bool { + return cast(bool)(input.gamepads[index].buttons[button] & .START); +} + +gamepad_pressed :: (index: s32, button: Gamepad_Button) -> bool { + return cast(bool)(input.gamepads[index].buttons[button] & .DOWN); +} + +gamepad_up :: (index: s32, button: Gamepad_Button) -> bool { + return cast(bool)(input.gamepads[index].buttons[button] & .END); +} diff --git a/input/sdl_input.jai b/input/sdl_input.jai new file mode 100644 index 0000000..877ab8c --- /dev/null +++ b/input/sdl_input.jai @@ -0,0 +1,260 @@ +key_mappings : Table(SDL_Keycode, Key_Code); +sdl_button_map : [SDL_CONTROLLER_BUTTON_MAX] Gamepad_Button; + +init_sdl_input :: () { + table_add(*key_mappings, SDLK_a, .A);//[SDLK_a] = .A; + table_add(*key_mappings, SDLK_b, .B); + table_add(*key_mappings, SDLK_c, .C); + table_add(*key_mappings, SDLK_d, .D); + table_add(*key_mappings, SDLK_e, .E); + table_add(*key_mappings, SDLK_f, .F); + table_add(*key_mappings, SDLK_g, .G); + table_add(*key_mappings, SDLK_h, .H); + table_add(*key_mappings, SDLK_i, .I); + table_add(*key_mappings, SDLK_j, .J); + table_add(*key_mappings, SDLK_k, .K); + table_add(*key_mappings, SDLK_l, .L); + table_add(*key_mappings, SDLK_m, .M); + table_add(*key_mappings, SDLK_n, .N); + table_add(*key_mappings, SDLK_o, .O); + table_add(*key_mappings, SDLK_p, .P); + table_add(*key_mappings, SDLK_q, .Q); + table_add(*key_mappings, SDLK_r, .R); + table_add(*key_mappings, SDLK_s, .S); + table_add(*key_mappings, SDLK_t, .T); + table_add(*key_mappings, SDLK_u, .U); + table_add(*key_mappings, SDLK_v, .V); + table_add(*key_mappings, SDLK_w, .W); + table_add(*key_mappings, SDLK_x, .X); + table_add(*key_mappings, SDLK_y, .Y); + table_add(*key_mappings, SDLK_z, .Z); + + table_add(*key_mappings, SDLK_0, .ZERO); + table_add(*key_mappings, SDLK_1, .ONE); + table_add(*key_mappings, SDLK_2, .TWO); + table_add(*key_mappings, SDLK_3, .THREE); + table_add(*key_mappings, SDLK_4, .FOUR); + table_add(*key_mappings, SDLK_5, .FIVE); + table_add(*key_mappings, SDLK_6, .SIX); + table_add(*key_mappings, SDLK_7, .SEVEN); + table_add(*key_mappings, SDLK_8, .EIGHT); + table_add(*key_mappings, SDLK_9, .NINE); + + table_add(*key_mappings, SDLK_F1, .F1); + table_add(*key_mappings, SDLK_F1, .F2); + table_add(*key_mappings, SDLK_F2, .F3); + table_add(*key_mappings, SDLK_F3, .F3); + table_add(*key_mappings, SDLK_F4, .F4); + table_add(*key_mappings, SDLK_F5, .F5); + table_add(*key_mappings, SDLK_F6, .F6); + table_add(*key_mappings, SDLK_F7, .F7); + table_add(*key_mappings, SDLK_F8, .F8); + table_add(*key_mappings, SDLK_F9, .F9); + table_add(*key_mappings, SDLK_F10, .F10); + table_add(*key_mappings, SDLK_F11, .F11); + table_add(*key_mappings, SDLK_F12, .F12); + + table_add(*key_mappings, SDLK_SPACE, .SPACE); + table_add(*key_mappings, SDLK_BACKSPACE, .BACKSPACE); + table_add(*key_mappings, SDLK_ESCAPE, .ESCAPE); + table_add(*key_mappings, SDLK_RETURN, .RETURN); + table_add(*key_mappings, SDLK_TAB, .TAB); + table_add(*key_mappings, SDLK_LSHIFT, .SHIFT); // @Incomplete + table_add(*key_mappings, SDLK_LCTRL, .CTRL); // @Incomplete + table_add(*key_mappings, SDLK_LALT, .ALT); // @Incomplete + table_add(*key_mappings, SDLK_DELETE, .DELETE); + table_add(*key_mappings, SDLK_BACKQUOTE, .TILDE); + + table_add(*key_mappings, SDLK_LEFT, .LEFT); + table_add(*key_mappings, SDLK_RIGHT, .RIGHT); + table_add(*key_mappings, SDLK_UP, .UP); + table_add(*key_mappings, SDLK_DOWN, .DOWN); + + //glfw_key_map[GLFW_MOUSE_BUTTON_LEFT] = .MOUSE_LEFT; + //glfw_key_map[GLFW_MOUSE_BUTTON_RIGHT] = .MOUSE_RIGHT; + +} + +update_sdl_input :: () { + event : SDL_Event; + while SDL_PollEvent(*event) { + if event.type == { + case SDL_QUIT; { + input.exit = true; + } + case SDL_KEYDOWN; { + keycode, success := table_find(*key_mappings, event.key.keysym.sym); + + if success { + update_key_state(keycode, true); + } + } + case SDL_KEYUP; { + keycode, success := table_find(*key_mappings, event.key.keysym.sym); + + if success { + update_key_state(keycode, false); + } + } + case SDL_TEXTINPUT; { + input.current_char = event.text.text[0]; + input.has_char = true; + } + case .SDL_MOUSEMOTION; { + input.mouse.delta_x += xx event.motion.xrel; + input.mouse.delta_y += xx event.motion.yrel; + input.mouse.x = xx event.motion.x; + input.mouse.y = xx event.motion.y; + } + case .SDL_MOUSEWHEEL; { + input.mouse.wheel += event.wheel.y; + } + case .SDL_CONTROLLERAXISMOTION; { + sdl_gamepad_axis_update(event.caxis); + } + case .SDL_MOUSEBUTTONDOWN; { + button := event.button.button; + if button == SDL_BUTTON_LEFT { + update_key_state(.MOUSE_LEFT, true); + } else if button == SDL_BUTTON_RIGHT { + update_key_state(.MOUSE_RIGHT, true); + } + } + case .SDL_MOUSEBUTTONUP; { + button := event.button.button; + if button == SDL_BUTTON_LEFT { + update_key_state(.MOUSE_LEFT, false); + } else if button == SDL_BUTTON_RIGHT { + update_key_state(.MOUSE_RIGHT, false); + } + } + case SDL_CONTROLLERBUTTONDOWN; #through; + case SDL_CONTROLLERBUTTONUP; { + sdl_gamepad_button_update(event.cbutton); + } + case SDL_CONTROLLERDEVICEADDED; #through; + case SDL_CONTROLLERDEVICEREMOVED; { + sdl_gamepad_device_event(event.cdevice); + } + case SDL_CONTROLLERDEVICEREMAPPED; { + } + } + } + + //mouse_x, mouse_y : s32; + //SDL_GetRelativeMouseState(*mouse_x, *mouse_y); + //input.mouse.delta_x = xx mouse_x; + //input.mouse.delta_y = xx mouse_y; + } + + sdl_connect_gamepad :: (input : *Input_State, id : SDL_JoystickID) { + if !SDL_IsGameController(id) { + return; + } else if input.num_gamepads >= MAX_GAMEPADS { + return; + } + + // @Incomplete: Is this necessary? + //if input.gamepads[id].controller != null { + // print("Already connected gamepad with id %\n", id); + // return; + //} + + gamepad : Gamepad; + controller := xx SDL_GameControllerOpen(id); + input.gamepads[id] = gamepad; + input.num_gamepads += 1; + } + + sdl_disconnect_gamepad :: (input : *Input_State, id : SDL_JoystickID) { + if !SDL_IsGameController(id) { + return; + } + + gamepad := input.gamepads[id]; + //if !gamepad.controller { + // return; + //} + + //input.gamepads[id].controller = null; +} + +update_gamepad_input :: () { + // Deadzone checks + for 0..input.num_gamepads-1 { + gamepad := *input.gamepads[it]; + + if abs(gamepad.left_stick_x) + abs(gamepad.left_stick_y) < LEFT_STICK_DEADZONE { + gamepad.left_stick_x = 0.0; + gamepad.left_stick_y = 0.0; + } + + if abs(gamepad.right_stick_x) + abs(gamepad.right_stick_y) < RIGHT_STICK_DEADZONE { + gamepad.right_stick_x = 0.0; + gamepad.right_stick_y = 0.0; + } + } +} + +sdl_gamepad_axis_update :: (using event : SDL_ControllerAxisEvent) { + if which < 0 || which >= input.num_gamepads { + return; + } + gamepad := *input.gamepads[which]; + + if cast(SDL_GameControllerAxis) axis == { + case SDL_CONTROLLER_AXIS_LEFTX; { + gamepad.left_stick_x = cast(float)value / 32767.0; + //update_gamepad_state(gamepad, .LEFT_STICK_LEFT, gamepad.left_stick_x < -0.1); + //update_gamepad_state(gamepad, .LEFT_STICK_RIGHT, gamepad.left_stick_x > 0.1); + } + case SDL_CONTROLLER_AXIS_LEFTY; { + gamepad.left_stick_y = cast(float)value / 32767.0; + //update_gamepad_state(gamepad, .LEFT_STICK_UP, gamepad.left_stick_y > 0.1); + //update_gamepad_state(gamepad, .LEFT_STICK_DOWN, gamepad.left_stick_y < -0.1); + } + case SDL_CONTROLLER_AXIS_RIGHTX; { + gamepad.right_stick_x = cast(float)value / 32767.0; + //update_gamepad_state(gamepad, .RIGHT_STICK_LEFT, gamepad.right_stick_x < -0.1); + //update_gamepad_state(gamepad, .RIGHT_STICK_RIGHT, gamepad.right_stick_x > 0.1); + } + case SDL_CONTROLLER_AXIS_RIGHTY; { + gamepad.right_stick_y = cast(float)value / 32767.0; + ///update_gamepad_state(gamepad, .RIGHT_STICK_UP, gamepad.right_stick_y > 0.1); + ///update_gamepad_state(gamepad, .RIGHT_STICK_DOWN, gamepad.right_stick_y < -0.1); + } + case SDL_CONTROLLER_AXIS_TRIGGERLEFT; { + //gamepad.trigger_left = cast(float)value; + } + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT; { + //gamepad.trigger_right = cast(float)value; + } + } +} + +sdl_gamepad_button_update :: (using event : SDL_ControllerButtonEvent) { + if which < 0 || which >= input.num_gamepads { + return; + } + down := state == SDL_PRESSED; + gamepad_button := sdl_button_map[button]; + + gamepad := *input.gamepads[which]; + + update_gamepad_state(gamepad, gamepad_button, down); +} + +sdl_gamepad_device_event :: (using event : SDL_ControllerDeviceEvent) { + if which < 0 || which >= MAX_GAMEPADS { + return; + } + + if type == { + case SDL_CONTROLLERDEVICEADDED; { + sdl_connect_gamepad(*input, which); + } + case SDL_CONTROLLERDEVICEREMOVED; { + sdl_disconnect_gamepad(*input, which); + } + } +} diff --git a/metaprogram.jai b/metaprogram.jai new file mode 100644 index 0000000..ff18e7e --- /dev/null +++ b/metaprogram.jai @@ -0,0 +1,334 @@ +#import "Compiler"; +#import "Basic"; +#import "String"; +#import "Sort"; + +build_release := false; + +build :: (main_path: string, game_name: string) { + set_working_directory(#filepath); + + w := compiler_create_workspace("Game"); + opts := get_build_options(w); + + opts.output_type = .EXECUTABLE; + opts.output_executable_name = game_name; + opts.output_path = "bin/"; + + new_path: [..] string; + array_add(*new_path, ..opts.import_path); + array_add(*new_path, "modules"); + opts.import_path = new_path; + + if build_release { + set_optimization(*opts, .VERY_OPTIMIZED); + } + + compiler_begin_intercept(w); + + set_build_options(opts, w); + add_build_string(tprint("GAME_NAME :: \"%\";", game_name), w); + add_build_file(main_path, w); + + message_loop(); + + compiler_end_intercept(w); + + set_build_options_dc(.{do_output=false}); +} + +// Contains all the auto-generated serialization procedures +entity_serialize_proc_string: [..] string; + +should_serialize :: (type: *Type_Info_Struct, member: Type_Info_Struct_Member) -> bool { + if type!= null { + if type.name == { + case "Vector2"; { + if member.name == { + case "x"; #through; + case "y"; #through; + case; return false; + } + } + case "Vector3"; { + if member.name == { + case "x"; #through; + case "y"; #through; + case "z"; return true; + case; return false; + } + } + case "Vector4"; #through; + case "Quaternion"; { + if member.name == { + case "x"; #through; + case "y"; #through; + case "z"; #through; + case "w"; return true; + case; return false; + } + } + } + } + + for member.notes { + if to_lower_copy_new(it,, allocator = temp) == "dontserialize" return false; + } + + return true; +} + +generate_member_serialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { + for type.members { + if should_serialize(type, it) { + new_path : string; + if path.count == 0 { + new_path = it.name; + } else { + new_path = tprint("%1.%2", path, it.name); + } + + if it.type.type == { + case .STRUCT; { + info_struct := cast(*Type_Info_Struct) it.type; + generate_member_serialization(info_struct, builder, new_path); + } + case .BOOL; #through; + case .FLOAT; #through; + case .ENUM; #through; + case .INTEGER; { + print_to_builder(builder, "\tprint_to_builder(builder, \"%: \%\\n\", e.%);\n", new_path, new_path); + } + } + } + } +} + +generate_member_deserialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { + for type.members { + if should_serialize(type, it) { + new_path : string; + if path.count == 0 { + new_path = it.name; + } else { + new_path = tprint("%1.%2", path, it.name); + } + if it.type.type == { + case .STRUCT; { + info_struct := cast(*Type_Info_Struct) it.type; + generate_member_deserialization(info_struct, builder, new_path); + } + case .BOOL; #through; + case .FLOAT; #through; + case .INTEGER; { + print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path); + print_to_builder(builder, "\t\t\t\t\tscan2(values[1], \"\%\", *e.%);\n", new_path); + } + } + } + } +} + +generate_serialize_procedure_for_entity :: (code_struct: *Code_Struct) { + name := code_struct.defined_type.name; + + // Serialize + serialize : String_Builder; + print_to_builder(*serialize, "serialize_entity :: (e: *%, builder: *String_Builder) {\n", name); + print_to_builder(*serialize, "\tprint_to_builder(builder, \"type: %\\n\");\n", name); + + generate_member_serialization(code_struct.defined_type, *serialize); + + print_to_builder(*serialize, "}\n"); + + array_add(*entity_serialize_proc_string, builder_to_string(*serialize)); + + // Deserialize + deserialize : String_Builder; + print_to_builder(*deserialize, "deserialize_entity :: (scene: *Scene, lines: [] string, e: *%) {\n", name); + print_to_builder(*deserialize, "\tfor line: lines {\n"); + print_to_builder(*deserialize, "\t\tvalues := split(line, \":\");\n"); + print_to_builder(*deserialize, "\t\tif values.count == 2 {\n"); + print_to_builder(*deserialize, "\t\t\tif trim(values[0], \" \") == {\n"); + + generate_member_deserialization(code_struct.defined_type, *deserialize); + + print_to_builder(*deserialize, "\t\t\t}\n"); + print_to_builder(*deserialize, "\t\t}\n"); + print_to_builder(*deserialize, "\t}\n"); + + print_to_builder(*deserialize, "}\n"); + array_add(*entity_serialize_proc_string, builder_to_string(*deserialize)); +} + +note_struct :: (code_struct: *Code_Struct) { + if is_subclass_of(code_struct.defined_type, "Entity") { + name := code_struct.defined_type.name; + + added := array_add_if_unique(*entity_type_names, name); + if added { + print("Detected entity '%'.\n", name); + + generate_serialize_procedure_for_entity(code_struct); + } + } +} + +message_loop :: () { + while true { + message := compiler_wait_for_message(); + // We ignore most message types. We mainly want to know about code that has been typechecked, + // so that we can look for the data structures we care about; or, when all the code is done + // being typechecked, we want to generate our new code. + if message.kind == { + case .TYPECHECKED; + typechecked := cast(*Message_Typechecked) message; + for typechecked.structs note_struct(it.expression); + + case .PHASE; + phase := cast(*Message_Phase) message; + if phase.phase == .TYPECHECKED_ALL_WE_CAN { + if !generated_code { + generate_code(message.workspace); + generated_code = true; + } + } + + case .COMPLETE; + break; + } + } +} + +generate_code :: (w: Workspace) { + // Let's sort the entity_type_names alphabetically, which is nice, but also + // gives us a reproducible build order, since our metaprogram may see these + // entities in any order: + + quick_sort(entity_type_names, compare_strings); + + // A piece of the code we want to generate is a comma-separated string of entity type + // names, so, let's make that using join(): + type_names := join(..entity_type_names, ", "); + + // We will also want to generate entity_storage_string, which contains a bunch of Bucket_Arrays + // for allocating each entity type. We might be able to do this with join() again, but + // we'll use String_Builder here, since that is a good example of how to do arbitrarily complex things: + + entity_storage_string: string; + { + builder: String_Builder; + + for entity_type_names { + // Let's say it's always 20 items per bucket, + // unless it's Player, in which case we want 64: + + items_per_bucket := 20; + if it == "Player" items_per_bucket = 64; + + print_to_builder(*builder, " _%1: Bucket_Array(%1, %2, true);\n", it, items_per_bucket); + } + + entity_storage_string = builder_to_string(*builder); + } + + { + builder: String_Builder; + + for entity_type_names { + print_to_builder(*builder, "\tcase %1; serialize_entity(cast(*%1)e, *builder);\n", it); + } + + build_string := sprint(SERIALIZE_ENTITY, builder_to_string(*builder)); + add_build_string(build_string, w); + } + + { + builder: String_Builder; + + for entity_type_names { + print_to_builder(*builder, "\tcase \"%1\"; p, locator := find_and_occupy_empty_slot(*scene.by_type._%1); p._locator = locator; e = p; register_entity(scene, p); init_entity(p); deserialize_entity(scene, lines, cast(*%1)e); update_matrix(*e.transform);\n", it); + } + + build_string := sprint(DESERIALIZE_ENTITY, builder_to_string(*builder)); + add_build_string(build_string, w); + } + { + builder: String_Builder; + + for entity_type_names { + print_to_builder(*builder, "new_%1 :: (scene: *Scene = null) -> *%2 { _scene := scene;\nif _scene == null { \n_scene = game_state.current_scene; }\np, locator := find_and_occupy_empty_slot(*_scene.by_type._%2); p._locator = locator; register_entity(_scene, p); p.transform = create_identity_transform(); init_entity(p); return p; }\n", to_lower_copy_new(it,, allocator=temp), it); + } + + add_build_string(builder_to_string(*builder), w); + } + + for entity_serialize_proc_string { + add_build_string(it, w); + } + + + // We fill out INSERTION_STRING to create the code we want to insert: + build_string := sprint(INSERTION_STRING, entity_type_names.count, type_names, entity_storage_string); + + // Add this string to the target program: + add_build_string(build_string, w); + + // We'll print out the added code just to show at compile-time what we are doing: + print("Adding build string:\n%\n", build_string); +} + +generated_code := false; +entity_type_names: [..] string; + +// INSERTION_STRING represents the code we want to add to the target program. +// We'll use print to insert useful things where the % markers are. +INSERTION_STRING :: #string DONE + +// NUM_ENTITY_TYPES tells the target program how many entity types there are. +NUM_ENTITY_TYPES :: %1; + +// entity_types is an array containing all the entity types. +entity_types : [%1] Type : .[ %2 ]; + +Entity_Storage :: struct { +%3 +} +DONE + +SERIALIZE_ENTITY :: #string DONE +serialize_entity :: (e: *Entity, path: string) { + builder: String_Builder; + builder.allocator = temp; + + if e.type == { + %1 + } + + write_entire_file(tprint("%%/%%.ent", path, e.id), builder_to_string(*builder)); +} +DONE + +DESERIALIZE_ENTITY :: #string DONE +deserialize_entity :: (scene: *Scene, path: string) -> *Entity { + content := read_entire_file(path); + if content.count == 0 return null; + + lines := split(content, "\n"); + first_line := split(lines[0], ":"); + + if first_line.count != 2 return null; + + e: *Entity; + + type := trim(first_line[1], " \n\r"); + + if type == { + %1 + } + + return e; +} +DONE + +#run build("Test"); diff --git a/module.jai b/module.jai new file mode 100644 index 0000000..f56864a --- /dev/null +++ b/module.jai @@ -0,0 +1,2 @@ +#load "core/entity.jai"; +#load "core/scene.jai"; \ No newline at end of file diff --git a/networking/networking.jai b/networking/networking.jai new file mode 100644 index 0000000..f4a06ec --- /dev/null +++ b/networking/networking.jai @@ -0,0 +1,596 @@ +Net_Mode :: enum { + DEDICATED_SERVER; + LISTEN_SERVER; + CLIENT; +} + +LOBBY_PORT :: 27015; + +MAX_LOBBIES :: 8; +lobbies : [MAX_LOBBIES] Lobby; +num_lobbies: s32; + +Lobby :: struct { + name: string; + address: Net_Address; +} + +Client_Id :: #type, isa s64; + +Client_Connection :: struct { + id: Client_Id; + address: Net_Address; +} + +Server_Connection :: struct { + address: Net_Address; +} + +Server_Data :: struct { + client_connections: [16] Client_Connection; + num_client_connections: s64; +} + +Client_Data :: struct { + client_id: Client_Id; + server_connection: Server_Connection; +} + +Net_Data :: struct { + net_mode: Net_Mode; + + socket: Socket.SOCKET; + + server: Server_Data; + client: Client_Data; +} + +net_data: Net_Data; + +Net_Address :: struct { + address: Socket.sockaddr_in; + valid: bool; +} + +Entity_Network_State :: enum { + LOCAL; + + LOCAL_REPLICATED; + + PROXY; +} + +pending_net_messages : [..] Net_Message; +messages_to_send : [..] Prepared_Net_Message; + +Join_Lobby_Data :: struct { + +} + +Net_Message_Type :: enum { + INVALID; + + LOOKING_FOR_LOBBY; + LOBBY_INFO; + JOIN_LOBBY; + + CLIENT_ACCEPTED; + + MESSAGE_RESPONSE; + + START_GAME; + + CLIENT_INPUT; + + SPAWN_ENTITY; + + PICKUP_EVENT; + + TRANSFORM_UPDATE; +} + +Net_Message_Header :: struct { + type : Net_Message_Type; + size: s64; + timestamp: float64; +} + +Prepared_Net_Message :: struct { + to: Net_Address; + message: Net_Message; +} + +Net_Message :: struct { + header: Net_Message_Header; + + body : [1024] u8; +} + +// Create a lobby/game +// Search for lobbies +// Join a lobby +// Start a game + +Net_Networking_State :: enum { + IDLE; + + SHOULD_LOOK_FOR_LOBBIES; + LOOKING_FOR_LOBBIES; + + CREATING_LOBBY; + CREATED_LOBBY; + + JOINING_LOBBY; + IN_LOBBY; + + READY_FOR_GAME_START; + + IN_GAME; +} + +LOBBY_NAME_SIZE :: 32; + +Lobby_Info_Data :: struct { + lobby_name: [LOBBY_NAME_SIZE] u8; + name_len: u8; +} + +Client_Joined_Data :: struct { + accepted: bool; + id: Client_Id; +} + +Message_Response_Data :: struct { + accepted: bool; +} + +Client_Input :: enum_flags { + UP; + DOWN; + LEFT; + RIGHT; + JUMP; + SHOOT; +} + +Client_Input_Data :: struct { + client_id: Client_Id; + flags: Client_Input; +} + +Spawn_Entity_Data :: struct { + type: Type; + remote_id: Entity_Id; + client_id: Client_Id; + spawn_position: Vector3; +} + +Transform_Update_Data :: struct { + entity_id: Entity_Id; + position: Vector3; + rotation: Quaternion; +} + +Player_Update_Data :: struct { + entity_id: Entity_Id; + position: Vector3; + yaw: float; + pitch: float; +} + +message_mutex: Mutex; +update_mutex: Mutex; + +net_init :: () { + net_log("Init\n"); + + data : Socket.WSAData; + if Socket.WSAStartup(0x0202, *data) != 0 { + assert(false); + net_log("Couldn't initialize Lyn\n"); + } + + if thread_init(*thread, networking_thread_proc) { + thread_start(*thread);; + } + + if thread_init(*message_thread, net_check_for_messages) { + thread_start(*message_thread);; + } + + init(*update_mutex); + init(*message_mutex); + init(*internal_message_mutex); +} + +net_get_state :: () -> Net_Networking_State { + return state; +} + +net_is_multiplayer :: () -> bool { + return state != .IDLE; +} + +net_is_client :: () -> bool { + return net_data.net_mode == .CLIENT; +} + +net_is_server :: () -> bool { + return net_data.net_mode != .CLIENT; +} + +net_can_start_game :: () -> bool { + return state == .READY_FOR_GAME_START; +} + +net_create_lobby :: () { + state = .CREATING_LOBBY; +} + +net_check_for_lobbies :: () { + state = .SHOULD_LOOK_FOR_LOBBIES; +} + +net_start_game :: () { + message := net_new_message(.START_GAME, 0); + + for 0..net_data.server.num_client_connections - 1 { + client := net_data.server.client_connections[it]; + net_send_message(*message, net_data.socket, client.address); + } + //state = .IN_GAME; +} + +net_cancel :: () { + // @Incomplete: Tell all clients to disconnect + Socket.closesocket(net_data.socket); + state = .IDLE; +} + +//net_pickup_item :: (player: *Player, item: *Item) { +// message := net_new_message(.PICKUP_EVENT, 0); +// net_send_message(*message, net_game_info.socket, net_game_info.other); +// state = .IN_GAME; +//} + +net_new_message :: (type: Net_Message_Type, size: s64) -> Net_Message { + message : Net_Message; + message.header.type = type; + message.header.size = size; + //message.body = alloc(size,temp); + + return message; +} + +//net_send_message :: (message: *Net_Message) { +// net_send_message(message, net_game_info.socket, net_game_info.other); +//} + +#scope_file +net_log :: inline (message: string, args: .. Any) { + log(tprint("NET: %", message), args); +} + +net_log_error :: inline (message: string, args: .. Any) { + log_error(tprint("NET: %", message), args); +} + +networking_thread_proc :: (thread: *Thread) -> s64 { + while true { + update_networking(); + sleep_milliseconds(10); + } + return 0; +} + +net_check_for_messages :: (thread: *Thread) -> s64 { + while true { + success, message, address := net_read_message(net_data.socket); + if success { + lock(*internal_message_mutex); + defer unlock(*internal_message_mutex); + + pending: Internal_Pending_Message; + pending.message = message; + pending.address = address; + + array_add(*internal_pending_messages, pending); + } + } + + return 0; +} + +net_broadcast_message :: (message: *Net_Message) { + net_send_message(message, net_data.socket, broadcast=true); +} + +net_send_message :: (msg: Net_Message, s: Socket.SOCKET, address: Net_Address = .{}, broadcast: bool = false) { + message := msg; + message.header.timestamp = to_float64_seconds(current_time_monotonic()); + + buffer : [1024] u8; + memcpy(buffer.data, *message.header, size_of(Net_Message_Header)); + memcpy(*buffer[size_of(Net_Message_Header)], message.body.data, message.header.size); + + wanted_size := size_of(Net_Message_Header) + message.header.size; + bytes_sent : s32; + + if broadcast { + broadcast_addr : Socket.sockaddr_in; + broadcast_addr.sin_family = Socket.AF_INET; + broadcast_addr.sin_port = Socket.htons(LOBBY_PORT); + broadcast_addr.sin_addr.S_un.S_addr = Socket.INADDR_BROADCAST; + bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*broadcast_addr, size_of(Socket.sockaddr_in)); + } else { + if address.valid { + bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in)); + } else { + bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0); + } + } + + if bytes_sent != wanted_size { + net_log_error("Couldn't send message through socket. Wrong size.\n"); + } + + if bytes_sent == Socket.SOCKET_ERROR { + net_log_error("Couldn't send message through socket. Socket error %.\n", Socket.WSAGetLastError()); + } +} + +net_read_message :: (s: Socket.SOCKET) -> bool, Net_Message, Net_Address { + BUFFER_SIZE :: 1024; + buffer : [BUFFER_SIZE] u8; + + addr: Net_Address; + addr.valid = true; + addr_len : Socket.socklen_t = size_of(Socket.sockaddr_in); + + // First receive the header + bytes_received := Socket.recvfrom(s, buffer.data, BUFFER_SIZE, 0, cast(*Socket.sockaddr)*addr.address, *addr_len); + + if bytes_received <= 0 return false, .{}, addr; + + message : Net_Message; + memcpy(*message.header, buffer.data, size_of(Net_Message_Header)); + //assert(bytes_received == message.header.size + size_of(Message_Header)); + + //if bytes_received != size_of(Message_Header) + message.header.size return false, .{}; + + //message.body = alloc(message.header.size,, temp); + memcpy(message.body.data, *buffer[size_of(Net_Message_Header)], message.header.size); + + return true, message, addr; +} + +net_send_message :: (type: Net_Message_Type, data: $T, s: Socket.SOCKET, address: Net_Address) { + message: Net_Message; + message.header.timestamp = to_float64_seconds(current_time_monotonic()); + message.header.size = size_of(T); + message.header.type = type; + + buffer : [1024] u8; + memcpy(buffer.data, *message.header, size_of(Net_Message_Header)); + memcpy(*buffer[size_of(Net_Message_Header)], *data, message.header.size); + + wanted_size := size_of(Net_Message_Header) + message.header.size; + bytes_sent : s32; + + if address.valid { + bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in)); + } else { + bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0); + } + + if bytes_sent != wanted_size { + net_log_error("Couldn't send message through socket. Wrong size.\n"); + } + + if bytes_sent == Socket.SOCKET_ERROR { + net_log_error("Couldn't send message through socket. Socket error.\n"); + } +} + +on_message_received :: (message: Net_Message, address: Net_Address) { + if message.header.type == { + case .LOOKING_FOR_LOBBY; { + if net_is_server() { + message := net_new_message(.LOBBY_INFO, size_of(Lobby_Info_Data)); + data : Lobby_Info_Data; + str := "My First Lobby"; + data.name_len = xx str.count; + memcpy(data.lobby_name.data, str.data, str.count); + memcpy(message.body.data, *data, size_of(Lobby_Info_Data)); + data.lobby_name[data.name_len] = #char "\0"; + net_send_message(*message, net_data.socket, address); + } + } + case .LOBBY_INFO; { + data := cast(*Lobby_Info_Data)message.body.data; + + lobby : Lobby; + lobby.address = address; + lobby.name = alloc_string(data.name_len); + memcpy(lobby.name.data, data.lobby_name.data, data.name_len); + + duplicate := false; + for 0..num_lobbies-1 { + if lobbies[it].name == lobby.name { + duplicate = true; + break; + } + } + + if !duplicate { + lobbies[num_lobbies] = lobby; + num_lobbies += 1; + net_log("Got a lobby info message: %\n", lobby.name); + + net_send_message(.JOIN_LOBBY, Join_Lobby_Data.{}, net_data.socket, address); + + state = .JOINING_LOBBY; + } else { + free(lobby.name); + } + + } + case .CLIENT_ACCEPTED; { + data := cast(*Client_Joined_Data)message.body.data; + state = .IN_LOBBY; + net_data.client.client_id = data.id; + net_data.client.server_connection.address = address; + net_log("Joined lobby as Client with id %!\n", net_data.client.client_id); + } + case .JOIN_LOBBY; { + assert(net_is_server()); + + connection: Client_Connection; + connection.address = address; + connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1); + net_data.server.client_connections[net_data.server.num_client_connections] = connection; + net_data.server.num_client_connections += 1; + + data: Client_Joined_Data; + data.accepted = true; + data.id = connection.id; + net_send_message(.CLIENT_ACCEPTED, data, net_data.socket, address); + + state = .READY_FOR_GAME_START; + } + case .START_GAME; { + { + lock(*update_mutex); + defer unlock(*update_mutex); + + array_add(*pending_net_messages, message); + } + } + case .SPAWN_ENTITY; { + { + lock(*update_mutex); + defer unlock(*update_mutex); + + array_add(*pending_net_messages, message); + } + } + case .TRANSFORM_UPDATE; { + lock(*update_mutex); + defer unlock(*update_mutex); + + array_add(*pending_net_messages, message); + } + case; { + lock(*update_mutex); + defer unlock(*update_mutex); + + array_add(*pending_net_messages, message); + } + } +} + +update_networking :: () { + reset_temporary_storage(); + + { + lock(*message_mutex); + defer unlock(*message_mutex); + + for * messages_to_send { + if net_is_client() { + print("Sending message of type %\n", it.message.header.type); + } + net_send_message(it.message, net_data.socket, it.to); + } + + messages_to_send.count = 0; + } + + { + lock(*internal_message_mutex); + defer unlock(*internal_message_mutex); + + for internal_pending_messages { + on_message_received(it.message, it.address); + } + + internal_pending_messages.count = 0; + } + + if state == { + case .IDLE; { + } + case .CREATING_LOBBY; { + net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0); + + if net_data.socket < 0 { + net_log_error("Could not create socket for lobby\n"); + state = .IDLE; + } else { + server_address : Socket.sockaddr_in; + server_address.sin_family = Socket.AF_INET; + server_address.sin_port = Socket.htons(LOBBY_PORT); // or whatever port you'd like to listen to + server_address.sin_addr.S_un.S_addr = Socket.INADDR_ANY; + + if Socket.bind(net_data.socket, cast(*Socket.sockaddr)*server_address, size_of(Socket.sockaddr_in)) < 0 { + Socket.closesocket(net_data.socket); + net_log_error("Could not bind socket for lobby to port %\n"); + state = .IDLE; + } else { + state = .CREATED_LOBBY; + net_log("Created lobby\n"); + net_data.net_mode = .LISTEN_SERVER; + + connection: Client_Connection; + connection.address = .{}; + connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1); + net_data.server.client_connections[net_data.server.num_client_connections] = connection; + net_data.server.num_client_connections += 1; + } + } + } + case .CREATED_LOBBY; { + } + case .SHOULD_LOOK_FOR_LOBBIES; { + net_data.net_mode = .CLIENT; + net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0); + if net_data.socket < 0 { + net_log_error("Could not create socket to look for lobbies\n"); + state = .IDLE; + } else { + true_flag : u8 = 1; + if Socket.setsockopt(net_data.socket, Socket.SOL_SOCKET, Socket.SO_BROADCAST, *true_flag, size_of(int)) < 0 { + Socket.closesocket(net_data.socket); + state = .IDLE; + + net_log_error("Could not set broadcast setting on socket\n"); + } else { + state = .LOOKING_FOR_LOBBIES; + net_log("Looking for lobbies\n"); + } + } + } + case .LOOKING_FOR_LOBBIES; { + message := net_new_message(.LOOKING_FOR_LOBBY, 0); + net_broadcast_message(*message); + } + } + + //net_check_for_messages(); +} + +Internal_Pending_Message :: struct { + message: Net_Message; + address: Net_Address; +} + +internal_pending_messages : [..] Internal_Pending_Message; +internal_message_mutex: Mutex; + +thread : Thread; +message_thread : Thread; +state : Net_Networking_State; + +#import "Thread"; +#import "Basic"; +Socket :: #import "Socket"; \ No newline at end of file diff --git a/physics/gjk.jai b/physics/gjk.jai new file mode 100644 index 0000000..eac7e29 --- /dev/null +++ b/physics/gjk.jai @@ -0,0 +1,363 @@ +Simplex :: struct { + points: [4] Vector3; + size: s32; +} + +Edge :: struct { + i1: s64; + i2: s64; +} + +Collision_Point :: struct { + normal: Vector3; + penetration_depth: float; + has_collision: bool; +} + +get_face_normals :: (polytope: [] Vector3, faces: [] s64) -> [..] Vector4, s64 { + normals : [..] Vector4; + normals.allocator = temp; + + min_triangle : s64 = 0; + min_distance := FLOAT32_MAX; + + i := 0; + while i < faces.count { + defer i += 3; + + a := polytope[faces[i+0]]; + b := polytope[faces[i+1]]; + c := polytope[faces[i+2]]; + + normal := normalize(cross(b - a, c - a)); + distance := dot(normal, a); + + if distance < 0 { + normal *= -1.0; + distance *= -1.0; + } + + data : Vector4; + data.x = normal.x; + data.y = normal.y; + data.z = normal.z; + data.w = distance; + array_add(*normals, data); + + if distance < min_distance { + min_triangle = i / 3; + min_distance = distance; + } + } + + return normals, min_triangle; +} + +add_if_unique_edge :: (edges: *[..] Edge, faces: [] s64, a: s64, b: s64) { + i := 0; + found := false; + while i < edges.count { + defer i += 1; + + edge := edges.*[i]; + if edge.i1 == faces[b] && edge.i2 == faces[a] { + array_unordered_remove_by_index(edges, i); + found = true; + break; + } + } + + if !found { + array_add(edges, .{faces[a], faces[b]}); + } +} + +nearly_equal :: (a: float, b: float) -> bool { + return abs(a - b) < 0.00001; +} + +epa :: (simplex: Simplex, collider_a: Collider, collider_b: Collider) -> Collision_Point { + polytope : [..] Vector3; + polytope.allocator = temp; + + array_reserve(*polytope, simplex.size); + for simplex.points { + array_add(*polytope, it); + } + + faces : [..] s64; + faces.allocator = temp; + array_resize(*faces, 12); + + faces[0] = 0; + faces[1] = 1; + faces[2] = 2; + faces[3] = 0; + faces[4] = 3; + faces[5] = 1; + faces[6] = 0; + faces[7] = 2; + faces[8] = 3; + faces[9] = 1; + faces[10] = 3; + faces[11] = 2; + + normals, min_face := get_face_normals(polytope, faces); + + min_normal : Vector3; + min_distance := FLOAT32_MAX; + + while nearly_equal(min_distance, FLOAT32_MAX) { + min_normal = to_v3(normals[min_face]); + min_distance = normals[min_face].w; + + s := support(collider_a, collider_b, min_normal); + s_distance := dot(min_normal, s); + + if abs(s_distance - min_distance) > 0.01 { + min_distance = FLOAT32_MAX; + + unique_edges : [..] Edge; + unique_edges.allocator = temp; + + i := 0; + while i < normals.count { + defer i += 1; + if dot(to_v3(normals[i]), s) > dot(to_v3(normals[i]), polytope[faces[i*3]]) { //same_direction(to_v3(normals[i]), s) { + f := i * 3; + add_if_unique_edge(*unique_edges, faces, f, f + 1); + add_if_unique_edge(*unique_edges, faces, f + 1, f + 2); + add_if_unique_edge(*unique_edges, faces, f + 2, f); + + faces[f+2] = faces[faces.count-1]; + faces[f+1] = faces[faces.count-2]; + faces[f+0] = faces[faces.count-3]; + + faces.count -= 3; + + normals[i] = normals[normals.count-1]; + normals.count -= 1; + i -= 1; + } + } + + new_faces : [..] s64; + new_faces.allocator = temp; + for e: unique_edges { + array_add(*new_faces, e.i1); + array_add(*new_faces, e.i2); + array_add(*new_faces, polytope.count); + } + + array_add(*polytope, s); + + new_normals, new_min_face := get_face_normals(polytope, new_faces); + + old_min_distance := FLOAT32_MAX; + + for n: normals { + if n.w < old_min_distance { + old_min_distance = n.w; + min_face = it_index; + } + } + + if new_normals[new_min_face].w < old_min_distance { + min_face = new_min_face + normals.count; + } + + + for f: new_faces { + array_add(*faces, f); + } + + for n: new_normals { + array_add(*normals, n); + } + } + } + + point : Collision_Point; + point.normal = min_normal; + point.penetration_depth = min_distance + 0.001; + point.has_collision = true; + + return point; +} + +find_furthest_point :: (collider: Collider, direction: Vector3) -> Vector3 { + max_point : Vector3; + max_distance := -FLOAT32_MAX; + + if collider.type == { + case .MESH; { + for collider.mesh.vertices { + distance := dot(it, direction); + if distance > max_distance { + max_distance = distance; + max_point = it; + } + } + } + } + + return max_point; +} + +push_front :: (simplex: *Simplex, p: Vector3) { + simplex.points[3] = simplex.points[2]; + simplex.points[2] = simplex.points[1]; + simplex.points[1] = simplex.points[0]; + simplex.points[0] = p; + + simplex.size = min(simplex.size + 1, 4); +} + +tetrahedron :: (simplex: *Simplex, direction: *Vector3) -> bool { + a := simplex.points[0]; + b := simplex.points[1]; + c := simplex.points[2]; + d := simplex.points[3]; + + ab := b - a; + ac := c - a; + ad := d - a; + ao := - a; + + abc := cross(ab, ac); + acd := cross(ac, ad); + adb := cross(ad, ab); + + if same_direction(abc, ao) { + simplex.points[0] = a; + simplex.points[1] = b; + simplex.points[2] = c; + simplex.size = 3; + return triangle(simplex, direction); + } + + if same_direction(acd, ao) { + simplex.points[0] = a; + simplex.points[1] = c; + simplex.points[2] = d; + simplex.size = 3; + return triangle(simplex, direction); + } + + if same_direction(adb, ao) { + simplex.points[0] = a; + simplex.points[1] = d; + simplex.points[2] = b; + simplex.size = 3; + return triangle(simplex, direction); + } + + return true; +} + +triangle :: (simplex: *Simplex, direction: *Vector3) -> bool { + a := simplex.points[0]; + b := simplex.points[1]; + c := simplex.points[2]; + + ab := b - a; + ac := c - a; + ao := - a; + + abc := cross(ab, ac); + + if same_direction(cross(abc, ac), ao) { + if same_direction(ac, ao) { + simplex.points[0] = a; + simplex.points[1] = c; + simplex.size = 2; + direction.* = cross(cross(ac, ao), ac); + } else { + simplex.points[0] = a; + simplex.points[1] = b; + simplex.size = 2; + return line(simplex, direction); + } + } else { + if same_direction(cross(ab, abc), ao) { + simplex.points[0] = a; + simplex.points[1] = b; + simplex.size = 2; + return line(simplex, direction); + } else { + if same_direction(abc, ao) { + direction.* = abc; + } else { + simplex.points[0] = a; + simplex.points[1] = c; + simplex.points[2] = b; + simplex.size = 3; + direction.* = -abc; + } + } + } + + return false; +} + +same_direction :: (direction: Vector3, ao: Vector3) -> bool { + return dot(direction, ao) > 0; +} + +line :: (simplex: *Simplex, direction: *Vector3) -> bool { + a := simplex.points[0]; + b := simplex.points[1]; + + ab := b - a; + ao := - a; + + if same_direction(ab, ao) { + direction.* = cross(cross(ab, ao), ab); + } else { + simplex.points[0] = a; + simplex.size = 1; + direction.* = ao; + } + + return false; +} + +next_simplex :: (simplex: *Simplex, direction: *Vector3) -> bool { + if simplex.size == { + case 2; return line(simplex, direction); + case 3; return triangle(simplex, direction); + case 4; return tetrahedron(simplex, direction); + } + + return false; +} + +support :: (a: Collider, b: Collider, direction: Vector3) -> Vector3 { + return find_furthest_point(a, direction) - find_furthest_point(b, -direction); +} + +gjk :: (a: Collider, b: Collider) -> Collision_Point { + sup := support(a, b, .{1,0,0}); + + points : Simplex; + push_front(*points, sup); + + direction := -sup; + + while true { + sup = support(a, b, direction); + + if dot(sup, direction) <= 0 { + return .{}; + } + + push_front(*points, sup); + + if next_simplex(*points, *direction) { + collision_point := epa(points, a, b); + return collision_point; + } + } + + return .{}; +} diff --git a/physics/physics.jai b/physics/physics.jai new file mode 100644 index 0000000..e94d99a --- /dev/null +++ b/physics/physics.jai @@ -0,0 +1,421 @@ +#load "gjk.jai"; + +WORLD_UP :: Vector3.{0,1,0}; +GRAVITY :: -20.8; + +Collider_Type :: enum { + AABB; + SPHERE; + MESH; + BOX; +} + +Sphere :: struct { + radius: float; +} + +Mesh_Collider :: struct { + vertices : [..] Vector3; + is_baked: bool; +} + +Trigger_Overlap :: struct { + entity: *Entity; + frame_index: u64; +} + +MAX_TRIGGER_OVERLAPS :: 16; + +Collider :: struct { + type : Collider_Type; + + aabb: AABB; + override_aabb: bool; + union { + sphere: Sphere; + mesh : Mesh_Collider; + } + + overlaps: [MAX_TRIGGER_OVERLAPS] Trigger_Overlap; + num_overlaps: s64; + + ignore: bool; +} + +Physics_Body :: struct { + velocity: Vector3; + + friction : float = 0.0; + bounciness : float = 0.0; + linear_damping : float = 0.0; + + check_for_grounded: bool; + grounded: bool; +} + +update_mesh_collider :: (e: *Entity) { + if e.collider.mesh.vertices.count == 0 { + array_resize(*e.collider.mesh.vertices, 8); + } + + m := e.transform.model_matrix; + e.collider.mesh.vertices[0] = transform_position(e.collider.aabb.min, m); + e.collider.mesh.vertices[1] = transform_position(e.collider.aabb.max, m); + e.collider.mesh.vertices[2] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[3] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[4] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.min.z}, m); + e.collider.mesh.vertices[5] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[6] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m); + e.collider.mesh.vertices[7] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m); + + e.collider.mesh.is_baked = true; +} + +update_mesh_colliders :: (scene: *Scene) { + for e: scene.entities { + if e.flags & .COLLISION { + if e.collider.type == .MESH { + if e.flags & .STATIC && e.collider.mesh.is_baked continue; + + update_mesh_collider(e); + } + } + } +} + +make_sure_entity_does_not_collide :: (e: *Entity, scene: *Scene) { + if e.flags & .PHYSICS { + aabb := e.collider.aabb; + aabb.min += e.transform.position; + aabb.max += e.transform.position; + + for other_e: scene.entities { + if e == other_e continue; + + if other_e.flags & .COLLISION { + other_aabb := other_e.collider.aabb; + other_aabb.min += other_e.transform.position; + other_aabb.max += other_e.transform.position; + if aabb_vs_aabb(aabb, other_aabb) { + offset := resolve_aabb_vs_aabb(aabb, other_aabb); + set_position(*e.transform, e.transform.position + offset * 1.0001); + } + } + } + } +} + +make_sure_nothing_collides :: (scene: *Scene) { + for e: scene.entities { + make_sure_entity_does_not_collide(e, scene); + } +} + +update_gravity :: (scene: *Scene, dt: float) { + for e: scene.entities { + if !e.enabled continue; + + if e.flags & .PHYSICS { + if e.is_proxy continue; + if e.collider.ignore continue; + e.body.velocity.y += GRAVITY * dt; + } + } +} + +update_positions :: (scene: *Scene, dt: float) { + for e: scene.entities { + if !e.enabled continue; + if e.is_proxy continue; + if e.collider.ignore continue; + + if e.flags & .PHYSICS { + delta := e.body.velocity * dt; + set_position(*e.transform, e.transform.position + delta); + + // @Speed: Only do this, if we actually moved + m := e.transform.model_matrix; + if e.collider.mesh.vertices.count < 8 continue; + + e.collider.mesh.vertices[0] = transform_position(e.collider.aabb.min, m); + e.collider.mesh.vertices[1] = transform_position(e.collider.aabb.max, m); + e.collider.mesh.vertices[2] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[3] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[4] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.min.z}, m); + e.collider.mesh.vertices[5] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.max.z}, m); + e.collider.mesh.vertices[6] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m); + e.collider.mesh.vertices[7] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m); + } + } +} + +add_trigger_overlap_if_new :: (e: *Entity, other_e: *Entity) { + for 0..e.collider.num_overlaps-1 { + overlap := *e.collider.overlaps[it]; + if overlap.entity == other_e { + overlap.frame_index = frame_index; + return; + } + } + + on_trigger_enter(e, other_e); + e.collider.overlaps[e.collider.num_overlaps] = .{ other_e, frame_index }; + e.collider.num_overlaps += 1; +} + +physics_step :: (scene: *Scene, timestep: float) { + update_gravity(scene, timestep); + update_positions(scene, timestep); + + for e: scene.entities { + if !e.enabled continue; + if e.is_proxy continue; + if e.collider.ignore continue; + + if e.flags & .PHYSICS { + for other_e: scene.entities { + if e == other_e continue; + if other_e.collider.ignore continue; + + if other_e.flags & .COLLISION { + point := gjk(e.collider, other_e.collider); + + if point.has_collision { + if other_e.flags & .TRIGGER { + // TRIGGER CALLBACK + add_trigger_overlap_if_new(e, other_e); + } else { + n := -point.normal; + speed_along_normal := dot(e.body.velocity, n); + + restitution := e.body.bounciness; + impulse := n * (-(1.0 + restitution) * speed_along_normal); + e.body.velocity += impulse; + + percent := 0.1; + slop := 0.005; + correction := n * max(point.penetration_depth - slop, 0.0) / (1.0 / percent); + set_position(*e.transform, e.transform.position + correction); + + if e.body.check_for_grounded { + e.body.grounded = dot(n, WORLD_UP) > 0.6; // @Incomplete: Add allowed angle variable at some point? + } + + // @Incomplete: This shouldn't be in here + //if e.type == Diamond && length(impulse) > 2.0 { + // play_audio_event(sfx_diamond_hit); + //} + } + } + } + } + } + } +} + +on_trigger_enter :: (e: *Entity, other_e: *Entity) { + //if (e.type == Player || e.type == Npc) && other_e.type == Door { + // other_e.enabled = false; + //} +} + +on_trigger_exit :: (e: *Entity, other_e: *Entity) { + //if other_e.type == Door { + // other_e.enabled = true; + //} +} + +update_physics :: (scene: *Scene, dt: float) { + for scene.entities { + if it.collider.type == .MESH { + if !it.collider.mesh.is_baked { + update_mesh_collider(it); + } + } + } + + iterations := 4; + step_time := dt / cast(float)iterations; + for 0..iterations-1 { + physics_step(scene, step_time); + + for e: scene.entities { + if e.flags & .PHYSICS { + //if e.body.friction > 0.0 { + // e.body.velocity *= 1.0 - (e.body.friction / cast(float)iterations); + //} + + e.body.velocity *= (1.0 - e.body.linear_damping / cast(float)iterations); + } + } + } + + for e: scene.entities { + index := 0; + while index < e.collider.num_overlaps { + defer index += 1; + + if e.collider.overlaps[index].frame_index < frame_index { + on_trigger_exit(e, e.collider.overlaps[index].entity); + + if e.collider.num_overlaps > 1 { + e.collider.overlaps[index] = e.collider.overlaps[e.collider.num_overlaps-1]; + } + + e.collider.num_overlaps -= 1; + } + } + } +} + +// DISCRETE +aabb_vs_aabb :: (box1: AABB, box2: AABB) -> bool { + // Check for no overlap along any axis + if box1.max.x < box2.min.x || box1.min.x > box2.max.x return false; + if box1.max.y < box2.min.y || box1.min.y > box2.max.y return false; + if box1.max.z < box2.min.z || box1.min.z > box2.max.z return false; + + return true; +} + +resolve_aabb_vs_aabb :: (moving_box: AABB, static_box: AABB) -> Vector3 { + overlap_x := min(moving_box.max.x, static_box.max.x) - max(moving_box.min.x, static_box.min.x); + overlap_y := min(moving_box.max.y, static_box.max.y) - max(moving_box.min.y, static_box.min.y); + overlap_z := min(moving_box.max.z, static_box.max.z) - max(moving_box.min.z, static_box.min.z); + + offset : Vector3; + + // Resolve overlap on each axis + if (overlap_x > 0 && overlap_y > 0 && overlap_z > 0) { + // Determine which axis has the smallest overlap + if (overlap_x <= overlap_y && overlap_x <= overlap_z) { + // Resolve overlap on X-axis + if (moving_box.max.x < static_box.max.x) { + offset.x -= overlap_x; + } else { + offset.x += overlap_x; + } + } else if (overlap_y <= overlap_x && overlap_y <= overlap_z) { + // Resolve overlap on Y-axis + if (moving_box.max.y < static_box.max.y) { + offset.y -= overlap_y; + } else { + offset.y += overlap_y; + } + } else { + // Resolve overlap on Z-axis + if (moving_box.max.z < static_box.max.z) { + offset.z -= overlap_z; + } else { + offset.z += overlap_z; + } + } + } + + return offset; +} + +// SWEPT +// Check for collision between two AABBs over a specified time interval +swept_aabb_collision :: (box1: AABB, box2: AABB, delta: Vector3) -> bool, t_enter: float, t_exit: float { + d_inv := 1.0 / delta; + + tx_enter, tx_exit, ty_enter, ty_exit, tz_enter, tz_exit : float; + + if (delta.x >= 0) { + tx_enter = (box2.min.x - box1.max.x) * d_inv.x; + tx_exit = (box2.max.x - box1.min.x) * d_inv.x; + } else { + tx_enter = (box2.max.x - box1.min.x) * d_inv.x; + tx_exit = (box2.min.x - box1.max.x) * d_inv.x; + } + + if (delta.y >= 0) { + ty_enter = (box2.min.y - box1.max.y) * d_inv.y; + ty_exit = (box2.max.y - box1.min.y) * d_inv.y; + } else { + ty_enter = (box2.max.y - box1.min.y) * d_inv.y; + ty_exit = (box2.min.y - box1.max.y) * d_inv.y; + } + + if (delta.z >= 0) { + tz_enter = (box2.min.z - box1.max.z) * d_inv.z; + tz_exit = (box2.max.z - box1.min.z) * d_inv.z; + } else { + tz_enter = (box2.max.z - box1.min.z) * d_inv.z; + tz_exit = (box2.min.z - box1.max.z) * d_inv.z; + } + + t_enter := max(max(tx_enter, ty_enter), tz_enter); + t_exit := min(min(tx_exit, ty_exit), tz_exit); + + return t_enter <= t_exit && t_exit >= 0 && t_enter <= 1, t_enter, t_exit; +} + +calculate_aabbs :: (scene: *Scene) { + for e: scene.entities { + if e.flags & .COLLISION && e.flags & .RENDERABLE { + if e.collider.override_aabb continue; + + aabb : AABB; + + for n : e.renderable.model.nodes { + if n.parent == 0 { + bake_aabb(*aabb, Matrix4_Identity, e, n); + } + } + + e.collider.aabb = aabb; + } + } +} + +bake_aabb :: (aabb: *AABB, parent_matrix: Matrix4, e: *Entity, n: Node) { + update_matrix(*n.transform); + node_matrix := parent_matrix * n.transform.model_matrix; + + for handle : n.meshes { + index := 0; + m := parray_get(*renderer.meshes, handle); + if m.indices.count > 0 { + while index < m.indices.count { + i1 := m.indices[index]; + i2 := m.indices[index + 1]; + i3 := m.indices[index + 2]; + p0 := to_v3(node_matrix * to_v4(m.positions[i1])); + p1 := to_v3(node_matrix * to_v4(m.positions[i2])); + p2 := to_v3(node_matrix * to_v4(m.positions[i3])); + + apply_min_max(*aabb.min, *aabb.max, p0); + apply_min_max(*aabb.min, *aabb.max, p1); + apply_min_max(*aabb.min, *aabb.max, p2); + + index += 3; + } + // assert("Meshes with indices currently for aabb collision baking." && false); + } else { + while index < m.positions.count - 1 { + p0 := to_v3(node_matrix * to_v4(m.positions[index])); + p1 := to_v3(node_matrix * to_v4(m.positions[index + 1])); + p2 := to_v3(node_matrix * to_v4(m.positions[index + 2])); + + apply_min_max(*aabb.min, *aabb.max, p0); + apply_min_max(*aabb.min, *aabb.max, p1); + apply_min_max(*aabb.min, *aabb.max, p2); + + index += 3; + } + } + } + + for n.children { + child := *e.renderable.model.nodes[it - 1]; + bake_aabb(aabb, node_matrix, e, child); + } + + if abs(aabb.min.y - aabb.max.y) < 0.00001 { + aabb.min.y -= 0.001; + } +} + + diff --git a/renderer/directional_light.jai b/renderer/directional_light.jai new file mode 100644 index 0000000..98535ac --- /dev/null +++ b/renderer/directional_light.jai @@ -0,0 +1,14 @@ +Directional_Light :: struct { + color_and_intensity : Vector4; + direction : Vector4; + + view_position : Vector3; +} + +Directional_Light_Buffer_Data :: struct { + color_and_intensity : Vector4; + direction : Vector4; + + //light_matrix: Matrix4; +} + diff --git a/renderer/dx11_renderer.jai b/renderer/dx11_renderer.jai new file mode 100644 index 0000000..d5c3554 --- /dev/null +++ b/renderer/dx11_renderer.jai @@ -0,0 +1,1503 @@ +#import "d3d11"; +#import "d3d_compiler"; +#import "dxgi"; +#import "File"; +#import "Windows"; + +Graphics_Backend :: D3D11_Backend; +Backend_Vertex_Input :: *ID3D11InputLayout; +Backend_Sampler :: *ID3D11SamplerState; +Backend_Blend_State :: *ID3D11BlendState; + +Backend_Render_Target :: struct { + render_target_view : *ID3D11RenderTargetView; + + texture: Backend_Texture; +}; + +Backend_Depth_Stencil_Buffer :: struct { + texture : *ID3D11Texture2D; + depth_stencil_view : *ID3D11DepthStencilView; + shader_resource_view : *ID3D11ShaderResourceView; +}; + +Backend_Buffer :: struct { + buffer : *ID3D11Buffer; + view : *ID3D11ShaderResourceView; +} + +Backend_Texture :: struct { + texture : *ID3D11Texture2D; + view : *ID3D11ShaderResourceView; +} + +Backend_Shader :: struct { + bytecode : string; + blob : *ID3DBlob; + + union { + vertex_shader : *ID3D11VertexShader; + pixel_shader : *ID3D11PixelShader; + } +} + +Rasterizer_State :: struct { + fill: bool; + cull_face: Cull_Face; + state: *ID3D11RasterizerState; +} + +D3D11_Backend :: struct { + driver_type := D3D_DRIVER_TYPE.NULL; + feature_level := D3D_FEATURE_LEVEL._11_0; + + d3d_device : *ID3D11Device; + d3d_context : *ID3D11DeviceContext; + factory : *IDXGIFactory1; + swap_chain : *IDXGISwapChain; + render_target_view : *ID3D11RenderTargetView; + + rasterizer_states: [..] Rasterizer_State; + + ds_state : *ID3D11DepthStencilState; + no_depth_ds_state : *ID3D11DepthStencilState; + + fill: bool = true; + cull_face: Cull_Face = .BACK; +} + +create_backend :: (w: *Window) -> *D3D11_Backend { + backend := New(D3D11_Backend); + hwnd := cast(HWND)get_hwnd(w); + if !init_device(hwnd, backend) { + print ("init_device failed!\n"); + cleanup_device(backend); + return null; + } + + //imgui_impldx11_init(backend.d3d_device, backend.d3d_context); + + return backend; +} + +deinit_backend :: (backend: *Graphics_Backend) { + cleanup_device(backend); +} + +destroy_shader :: (using shader: Shader) { + if type == { + case .VERTEX; + if backend_shader.vertex_shader IUnknown_Release(backend_shader.vertex_shader); + case .PIXEL; + if backend_shader.pixel_shader IUnknown_Release(backend_shader.pixel_shader); + } +} + +destroy_buffer :: (using buffer: Buffer) { + IUnknown_Release(backend_buffer.buffer); +} + +cleanup_device :: (using backend: *Graphics_Backend) { + if d3d_context ID3D11DeviceContext_ClearState(d3d_context); + + //if vertex_layout IUnknown_Release(vertex_layout); + + if render_target_view IUnknown_Release(render_target_view); + if swap_chain IUnknown_Release(swap_chain); + if d3d_context IUnknown_Release(d3d_context); + if d3d_device IUnknown_Release(d3d_device); +} + +init_device :: (hwnd: HWND, using renderer: *D3D11_Backend) -> bool { + hr : HRESULT = S_OK; + + flags : D3D11_CREATE_DEVICE_FLAG; + #if DEBUG flags |= .DEBUG; + + driver_types := D3D_DRIVER_TYPE.[.HARDWARE, .WARP, .REFERENCE]; + feature_levels := D3D_FEATURE_LEVEL.[._11_1, ._11_0, ._10_1, ._10_0]; + + for 0..driver_types.count-1 { + + driver_type = driver_types[it]; + hr = D3D11CreateDevice(null, driver_type, null, flags, feature_levels.data, feature_levels.count, D3D11_SDK_VERSION, *d3d_device, *feature_level, *d3d_context); + + if hr == E_INVALIDARG { + // DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it. + hr = D3D11CreateDevice(null, driver_type, null, flags, feature_levels.data + 1, feature_levels.count - 1, D3D11_SDK_VERSION, *d3d_device, *feature_level, *d3d_context); + } + + if SUCCEEDED(hr) break; + } + if FAILED(hr) { + print("D3D11CreateDevice failed.\n"); + return false; + } + + assert(d3d_device != null); + + // Obtain DXGI factory from device (since we used null for pAdapter above) + dxgi_factory : *IDXGIFactory1; + { + dxgi_device : *IDXGIDevice; + hr = IUnknown_QueryInterface(d3d_device, *uid(IDXGIDevice_UUID), xx *dxgi_device); + // You could also use the vtable helper instead of the C api: + // hr = vtable(d3d_device).QueryInterface(d3d_device, *uid(IDXGIDevice_UUID), xx *dxgi_device); + if SUCCEEDED(hr) { + adapter : *IDXGIAdapter; + hr = IDXGIDevice_GetAdapter(dxgi_device, *adapter); + if SUCCEEDED(hr) { + hr = IDXGIObject_GetParent(adapter, *uid(IDXGIFactory1_UUID), xx *dxgi_factory); + IUnknown_Release(adapter); + } + IUnknown_Release(dxgi_device); + } else { + print("QueryInterface FAILED\n"); + } + } + + factory = dxgi_factory; + + if FAILED(hr) { + print("GetAdapter failed.\n"); + return false; + } + + rc : RECT; + GetClientRect(hwnd, *rc); + width := rc.right - rc.left; + height := rc.bottom - rc.top; + + // Create swap chain + sd : DXGI_SWAP_CHAIN_DESC; + sd.SwapEffect = .SEQUENTIAL; + sd.BufferCount = 2; + sd.BufferDesc.Width = xx width; + sd.BufferDesc.Height = xx height; + sd.BufferDesc.Format = .R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.BufferUsage = .RENDER_TARGET_OUTPUT; + sd.OutputWindow = hwnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = BOOL.TRUE; + + hr = IDXGIFactory_CreateSwapChain(dxgi_factory, d3d_device, *sd, *swap_chain); + if FAILED(hr) { + log_error("CreateSwapChain failed: %", hr); + } + + // Note this tutorial doesn't handle full-screen swapchains so we block the ALT+ENTER shortcut + IDXGIFactory_MakeWindowAssociation(dxgi_factory, hwnd, cast (u32) DXGI_MWA.NO_ALT_ENTER); + IUnknown_Release(dxgi_factory); + + if FAILED(hr) return false; + + + back_buffer : *ID3D11Texture2D; + hr = IDXGISwapChain_GetBuffer(swap_chain, 0, *uid(ID3D11Texture2D_UUID), xx *back_buffer); + if FAILED(hr) { + log_error("GetBuffer failed: %", hr); + return false; + } + + hr = ID3D11Device_CreateRenderTargetView(d3d_device, back_buffer, null, *render_target_view); + IUnknown_Release(back_buffer); + if FAILED(hr) { + log_error("CreateRenderTargetView failed: %", hr); + return false; + } + + + // Depth stencil + ds_desc : D3D11_DEPTH_STENCIL_DESC; + + // Depth test parameters + ds_desc.DepthEnable = .TRUE; + ds_desc.DepthWriteMask = .D3D11_DEPTH_WRITE_MASK_ALL; + ds_desc.DepthFunc = .D3D11_COMPARISON_LESS_EQUAL; + + // Stencil test parameters + ds_desc.StencilEnable = .FALSE; + ds_desc.StencilReadMask = 0xFF; + ds_desc.StencilWriteMask = 0xFF; + + // Stencil operations if pixel is front-facing + ds_desc.FrontFace.StencilFailOp = .D3D11_STENCIL_OP_KEEP; + ds_desc.FrontFace.StencilDepthFailOp = .D3D11_STENCIL_OP_INCR; + ds_desc.FrontFace.StencilPassOp = .D3D11_STENCIL_OP_KEEP; + ds_desc.FrontFace.StencilFunc = .D3D11_COMPARISON_ALWAYS; + + // Stencil operations if pixel is back-facing + ds_desc.BackFace.StencilFailOp = .D3D11_STENCIL_OP_KEEP; + ds_desc.BackFace.StencilDepthFailOp = .D3D11_STENCIL_OP_DECR; + ds_desc.BackFace.StencilPassOp = .D3D11_STENCIL_OP_KEEP; + ds_desc.BackFace.StencilFunc = .D3D11_COMPARISON_ALWAYS; + + // Create depth stencil state + ID3D11Device_CreateDepthStencilState(d3d_device, *ds_desc, *ds_state); + ID3D11DeviceContext_OMSetDepthStencilState(d3d_context, ds_state, 1); + + ds_desc.DepthEnable = .FALSE; + ID3D11Device_CreateDepthStencilState(d3d_device, *ds_desc, *no_depth_ds_state); + + vp : D3D11_VIEWPORT; + vp.Width = xx width; + vp.Height = xx height; + vp.MinDepth = 0.0; + vp.MaxDepth = 1.0; + vp.TopLeftX = 0; + vp.TopLeftY = 0; + ID3D11DeviceContext_RSSetViewports(d3d_context, 1, *vp); + + array_reserve(*renderer.rasterizer_states, 8); + + create_rasterizer_state(renderer, true, .BACK); + create_rasterizer_state(renderer, false, .BACK); + create_rasterizer_state(renderer, true, .FRONT); + create_rasterizer_state(renderer, false, .FRONT); + + return true; +} + +get :: (array: [..]$T, handle: $S) -> *T { + assert(handle > 0 && handle <= xx array.count); + return *array[handle - 1]; +} + +// @Note(Daniel): Taken from the module to convert defines to D3D_SHADER_MACRO structs, since we need that for our engine +d3d_compile :: (shader: string, source_name: string, pDefines: []string, pInclude: *ID3DInclude, entry_point: string, target: string, Flags1: u32, Flags2: u32) -> bytecode:string, *ID3DBlob, errors:string, HRESULT { + pSourceName := temp_c_string(source_name); + pEntrypoint := temp_c_string(entry_point); + pTarget := temp_c_string(target); + + defines : [..] D3D_SHADER_MACRO; + defines.allocator = temp; + + for pDefines { + macro : D3D_SHADER_MACRO; + macro.Name = to_c_string(it); + array_add(*defines, macro); + } + + null_macro : D3D_SHADER_MACRO; + null_macro.Name = null; + null_macro.Definition = null; + array_add(*defines, null_macro); + + pCode: *ID3DBlob; + pErrorMsgs: *ID3DBlob; + + hr := D3DCompile(shader.data, cast(u64)shader.count, pSourceName, defines.data, pInclude, pEntrypoint, pTarget, Flags1, Flags2, *pCode, *pErrorMsgs); + + for defines { + free(it.Name); + } + + bytecode : string; + errors : string; + + if pCode { + bytecode = copy_string(to_string(ID3D10Blob_GetBufferPointer(pCode), cast(s64) ID3D10Blob_GetBufferSize(pCode))); + } + + if pErrorMsgs { + errors = copy_string(to_string(ID3D10Blob_GetBufferPointer(pErrorMsgs), cast(s64) ID3D10Blob_GetBufferSize(pErrorMsgs))); + } + + //safe_release(pCode); + safe_release(pErrorMsgs); + return bytecode, pCode, errors, hr; +} + +compile_shader :: (source: string, entry_point: string, shader_model: string, defines: [] string = string.[]) -> string, *ID3DBlob, HRESULT { + flags := D3DCOMPILE.ENABLE_STRICTNESS; + + #if DEBUG { + // Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders. + // Setting this flag improves the shader debugging experience, but still allows + // the shaders to be optimized and to run exactly the way they will run in + // the release configuration of this program. + flags |= .DEBUG; + + // Disable optimizations to further improve shader debugging + flags |= .SKIP_OPTIMIZATION; + } + + + bytecode, blob, errors, hr := d3d_compile(source, "source", defines, null, entry_point, shader_model, xx flags, 0); + defer free(errors); + + if FAILED(hr) { + free(bytecode); + if errors print ("Error msg: %\n", errors); + return "", null, hr; + } + + return bytecode, blob, hr; +} + +variable_type_to_property_type :: (type_desc: D3D11_SHADER_TYPE_DESC) -> Shader_Property_Type { + if type_desc.Type == { + case .SVT_BOOL; + return .BOOL; + case .SVT_INT; + return .INTEGER; + case .SVT_FLOAT; + if type_desc.Class == { + case .SVC_SCALAR; + return .FLOAT; + case .SVC_VECTOR; { + if type_desc.Columns == { + case 2; + return .FLOAT2; + case 3; + return .FLOAT3; + case 4; + return .FLOAT4; + } + } + case .SVC_MATRIX_COLUMNS; #through; + case .SVC_MATRIX_ROWS; { + if type_desc.Columns == { + case 3; + return .MATRIX3; + case 4; + return .MATRIX4; + } + } + } + return .FLOAT; + } + + return 0; +} + +// @Incomplete: Move this out of the DX11 backend code +look_for_mapping :: (name: string) -> Shader_Parameter_Mapping, string { + mapping_strs := split(name, "__"); + if mapping_strs.count < 2 { + return .NONE, ""; + } + + if mapping_strs[1] == { + case "MODEL_MATRIX"; + return .MODEL_MATRIX, ""; + case "TIME"; + return .TIME, ""; + case "CAMERA_DATA"; + return .CAMERA_DATA, ""; + case "MATERIAL"; + return .MATERIAL, ""; + case "DIRECTIONAL_LIGHT"; + return .DIRECTIONAL_LIGHT, ""; + case "POINT_LIGHTS"; + return .POINT_LIGHTS, ""; + case "BONE_MATRICES"; + return .BONE_MATRICES, ""; + case "SCREEN_DATA"; + return .SCREEN_DATA, ""; + case "BASE_COLOR_TEXTURE"; + return .BASE_COLOR_TEXTURE, ""; + case "NORMAL_MAP"; + return .NORMAL_MAP, ""; + case "ATT0"; + return .SHADER_ATTACHMENT0, ""; + case "ATT1"; + return .SHADER_ATTACHMENT1, ""; + case "ATT2"; + return .SHADER_ATTACHMENT2, ""; + case "ATT3"; + return .SHADER_ATTACHMENT3, ""; + case "ATT4"; + return .SHADER_ATTACHMENT4, ""; + case "ATT5"; + return .SHADER_ATTACHMENT5, ""; + case "ATT6"; + return .SHADER_ATTACHMENT6, ""; + case "REPEAT_SAMPLER"; + return .REPEAT_SAMPLER, ""; + case "CLAMP_SAMPLER"; + return .CLAMP_SAMPLER, ""; + } + + return .CUSTOM, copy_string(mapping_strs[1]); +} + +get_shader_info_from_blob :: (blob: *ID3DBlob, shader_type: Shader_Type) -> Shader_Info { + shader_info : Shader_Info; + + reflection : *ID3D11ShaderReflection; + reflection_result := D3DReflect(ID3D10Blob_GetBufferPointer(blob), ID3D10Blob_GetBufferSize(blob), uid("8d536ca1-0cca-4956-a837-786963755584"), cast(**void)*reflection); + + if !FAILED(reflection_result) { + defer safe_release(reflection); + + // Input layout + desc : D3D11_SHADER_DESC; + ID3D11ShaderReflection_GetDesc(reflection, *desc); + + param_index : u32 = 0; + while param_index < desc.InputParameters { + defer param_index += 1; + param_desc : D3D11_SIGNATURE_PARAMETER_DESC; + ID3D11ShaderReflection_GetInputParameterDesc(reflection, param_index, *param_desc); + + input : Vertex_Input; + input.semantic_name = copy_string(to_string(param_desc.SemanticName)); + input.semantic_index = param_desc.SemanticIndex; + input.slot = 0; // @Incomplete: this might have to change + + if param_desc.Mask == 1 { + if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_UINT32 + input.format = .R32_UINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_SINT32 + input.format = .R32_SINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_FLOAT32 + input.format = .R32_FLOAT; + } else if param_desc.Mask <= 3 { + if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_UINT32 + input.format = .R32G32_UINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_SINT32 + input.format = .R32G32_SINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_FLOAT32 + input.format = .R32G32_FLOAT; + } else if param_desc.Mask <= 7 { + if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_UINT32 + input.format = .R32G32B32_UINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_SINT32 + input.format = .R32G32B32_SINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_FLOAT32 + input.format = .R32G32B32_FLOAT; + } else if param_desc.Mask <= 15 { + if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_UINT32 + input.format = .R32G32B32A32_UINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_SINT32 + input.format = .R32G32B32A32_SINT; + else if param_desc.ComponentType == .D3D_REGISTER_COMPONENT_FLOAT32 + input.format = .R32G32B32A32_FLOAT; + } + + array_add(*shader_info.input, input); + } + + // Constant buffers + buffer_index : u32 = 0; + while buffer_index < desc.ConstantBuffers { + defer buffer_index += 1; + ptr := ID3D11ShaderReflection_GetConstantBufferByIndex(reflection, buffer_index); + buffer_desc : D3D11_SHADER_BUFFER_DESC; + if FAILED(ID3D11ShaderReflectionConstantBuffer_GetDesc(ptr, *buffer_desc)) assert(false); + + param : Shader_Parameter; + param.type = .BUFFER; + param.shader = shader_type; + param.name = copy_string(to_string(buffer_desc.Name)); + + param.size = buffer_desc.Size; + + input_desc : D3D11_SHADER_INPUT_BIND_DESC; + if FAILED(ID3D11ShaderReflection_GetResourceBindingDescByName(reflection, buffer_desc.Name, *input_desc)) assert(false); + param.slot = input_desc.BindPoint; + + param.mapping, param.mapping_str = look_for_mapping(param.name); + if param.mapping == .CUSTOM { + log("CUSTOM MAPPING FOUND %\n", param.mapping_str); + } + + var_index : u32 = 0; + while var_index < buffer_desc.Variables { + defer var_index += 1; + pvar := ID3D11ShaderReflectionConstantBuffer_GetVariableByIndex(ptr, var_index); + var_desc : D3D11_SHADER_VARIABLE_DESC; + if FAILED(ID3D11ShaderReflectionVariable_GetDesc(pvar, *var_desc)) assert(false); + ptype := ID3D11ShaderReflectionVariable_GetType(pvar); + var_type_desc : D3D11_SHADER_TYPE_DESC; + if FAILED(ID3D11ShaderReflectionType_GetDesc(ptype, *var_type_desc)) assert(false); + + prop : Shader_Property; + prop.name = copy_string(to_string(var_desc.Name)); + prop.type = variable_type_to_property_type(var_type_desc); + prop.buffer_offset = xx var_desc.StartOffset; + + if prop.type == .INVALID continue; + + param.properties[param.num_properties] = prop; + param.num_properties += 1; + } + + array_add(*shader_info.parameters, param); + } + + res_index : u32 = 0; + while res_index < desc.BoundResources { + defer res_index += 1; + + bind_desc : D3D11_SHADER_INPUT_BIND_DESC; + if FAILED(ID3D11ShaderReflection_GetResourceBindingDesc(reflection, res_index, *bind_desc)) assert(false); + param : Shader_Parameter; + + // @Incomplete: Maybe put the constant buffer stuff here and use the ByName function? + + if bind_desc.Type == { + case .SIT_SAMPLER; { + param.type = .SAMPLER; + } + case .SIT_TEXTURE; { + param.type = .TEXTURE; + prop : Shader_Property; + prop.name = copy_string(to_string(bind_desc.Name)); + prop.type = .TEXTURE; + param.properties[param.num_properties] = prop; + param.num_properties += 1; + } + case .SIT_CBUFFER; continue; + } + + param.shader = shader_type; + param.name = copy_string(to_string(bind_desc.Name)); + param.slot = bind_desc.BindPoint; + param.mapping, param.mapping_str = look_for_mapping(param.name); + + array_add(*shader_info.parameters, param); + } + + } + + return shader_info; +} + +create_backend_shader_from_source :: (using renderer: *D3D11_Backend, source: string, entry_point: string, shader_type: Shader_Type, defines: [] string = string.[]) -> Backend_Shader #must, Shader_Info, bool { + shader_model : string; + + if shader_type == .VERTEX shader_model = "vs_4_0"; + else shader_model = "ps_4_0"; + + bytecode, blob, hr := compile_shader(source, entry_point, shader_model, defines); + + if FAILED(hr) return .{}, .{}, false; + + shader : Backend_Shader; + shader.bytecode = bytecode; + shader.blob = blob; + + shader_info := get_shader_info_from_blob(blob, shader_type); + + if shader_type == { + case .VERTEX; + hr = ID3D11Device_CreateVertexShader(d3d_device, bytecode.data, cast(u64)bytecode.count, null, *shader.vertex_shader); + + if FAILED(hr) { + log_error("CreateVertexShader failed: %", hr); + return .{}, .{}, false; + } + + case .PIXEL; + hr = ID3D11Device_CreatePixelShader(d3d_device, bytecode.data, cast(u64)bytecode.count, null, *shader.pixel_shader); + + if FAILED(hr) { + log_error("CreatePixelShader failed: %", hr); + return .{}, .{}, false; + } + } + + return shader, shader_info, true; +} + +create_backend_shader :: (using renderer: *D3D11_Backend, path: string, entry_point: string, shader_type: Shader_Type, defines: [] string = string.[]) -> Backend_Shader #must, Shader_Info, bool { + content := read_entire_file(path); + if !content return .{}, .{}, false; + defer free(content); + + shader, info, success := create_backend_shader_from_source(renderer, content, entry_point, shader_type, defines); + + return shader, info, success; +} + +reload_backend_shader :: (using renderer: *D3D11_Backend, path: string, entry_point: string, shader_type: Shader_Type, defines: [] string = string.[]) -> Backend_Shader #must, bool { + content := read_entire_file(path); + if !content return .{}, false; + defer free(content); + + shader, success := reload_backend_shader_from_source(renderer, content, entry_point, shader_type, defines); + + return shader, success; +} + +reload_backend_shader_from_source :: (using renderer: *D3D11_Backend, source: string, entry_point: string, shader_type: Shader_Type, defines: [] string = string.[]) -> Backend_Shader #must, bool { + shader_model : string; + + if shader_type == .VERTEX shader_model = "vs_4_0"; + else shader_model = "ps_4_0"; + + bytecode, blob, hr := compile_shader(source, entry_point, shader_model, defines); + + if FAILED(hr) return .{}, false; + + shader : Backend_Shader; + shader.bytecode = bytecode; + shader.blob = blob; + + if shader_type == { + case .VERTEX; + hr = ID3D11Device_CreateVertexShader(d3d_device, bytecode.data, cast(u64)bytecode.count, null, *shader.vertex_shader); + + if FAILED(hr) { + log_error("CreateVertexShader failed: %", hr); + return .{}, false; + } + + case .PIXEL; + hr = ID3D11Device_CreatePixelShader(d3d_device, bytecode.data, cast(u64)bytecode.count, null, *shader.pixel_shader); + + if FAILED(hr) { + log_error("CreatePixelShader failed: %", hr); + return .{}, false; + } + } + + return shader, true; +} + +create_backend_blend_state :: (using renderer: *D3D11_Backend, type: Blend_Type) -> Backend_Blend_State #must, bool { + blend_state : Backend_Blend_State; + + if type == { + case .OPAQUE; + // We don't have to create a blend state for opaque objects... + assert(false); + case .TRANSPARENT; + blend_desc : D3D11_BLEND_DESC; + rtbd : D3D11_RENDER_TARGET_BLEND_DESC; + + rtbd.BlendEnable = .TRUE; + rtbd.SrcBlend = .D3D11_BLEND_SRC_ALPHA; + rtbd.DestBlend = .D3D11_BLEND_INV_SRC_ALPHA; + rtbd.BlendOp = .D3D11_BLEND_OP_ADD; + rtbd.SrcBlendAlpha = .D3D11_BLEND_ONE; + rtbd.DestBlendAlpha = .D3D11_BLEND_ZERO; + rtbd.BlendOpAlpha = .D3D11_BLEND_OP_ADD; + rtbd.RenderTargetWriteMask = xx D3D11_COLOR_WRITE_ENABLE.ALL; + + blend_desc.AlphaToCoverageEnable = .FALSE; + blend_desc.RenderTarget[0] = rtbd; + + success := !FAILED(ID3D11Device_CreateBlendState(d3d_device, *blend_desc, *blend_state)); + return blend_state, success; + } + + return null, false; +} + +semantic_index_for_vertex_data_type :: (type: Vertex_Data_Type) -> u32 { + if #complete type == { + case .POSITION; + #through; + case .POSITION2D; + #through; + case .NORMAL; + #through; + case .TANGENT0; + #through; + case .COLOR0; + #through; + case .COLOR; + #through; + case .COLOR_WITH_ALPHA; + #through; + case .COLOR_WITH_ALPHA8; + #through; + case .TEXCOORD0; + return 0; + case .TANGENT1; #through; + case .POSITION1; #through; + case .POSITION1_2D; + return 1; + case .TEXCOORD1; + return 1; + case .TEXCOORD2; + return 2; + case .TEXCOORD3; + return 3; + case .TEXCOORD4; + return 4; + case .INSTANCED_MAT0; + return 0; + case .INSTANCED_MAT1; + return 1; + case .INSTANCED_MAT2; + return 2; + case .INSTANCED_MAT3; + return 3; + case .VERTEX_ID; + return 0; + case .INSTANCE_ID; + return 0; + } +} + +semantic_name_for_vertex_data_type :: (type: Vertex_Data_Type) -> *u8 #expand { + str : string; + if #complete type == { + case .POSITION; + #through; + case .POSITION2D; + str = "POSITION"; + case .POSITION1; + str = "POSITION"; + case .POSITION1_2D; + str = "POSITION"; + case .NORMAL; + str = "NORMAL"; + case .TANGENT0; + str = "TANGENT"; + case .TANGENT1; + str = "TANGENT"; + case .COLOR0; + str = "COLOR0"; + case .COLOR; + #through; + case .COLOR_WITH_ALPHA; + #through; + case .COLOR_WITH_ALPHA8; + str = "COLOR"; + case .TEXCOORD0; + str = "TEXCOORD"; + case .TEXCOORD1; + str = "TEXCOORD"; + case .TEXCOORD2; + str = "TEXCOORD"; + case .TEXCOORD3; + str = "TEXCOORD"; + case .TEXCOORD4; + str = "TEXCOORD"; + case .INSTANCED_MAT0; #through; + case .INSTANCED_MAT1; #through; + case .INSTANCED_MAT2; #through; + case .INSTANCED_MAT3; + str = "INSTANCED_MAT"; + case .VERTEX_ID; { + str = "SV_VertexID"; + } + case .INSTANCE_ID; { + str = "SV_InstanceID"; + } + } + + return temp_c_string(str); +} + +format_for_vertex_data_type :: (type: Vertex_Data_Type) -> DXGI_FORMAT { + if #complete type == { + case .POSITION; #through; + case .POSITION1; + return .R32G32B32_FLOAT; + case .POSITION2D; #through; + case .POSITION1_2D; + return .R32G32_FLOAT; + case .NORMAL; + return .R32G32B32_FLOAT; + case .TANGENT0; #through; + case .TANGENT1; + return .R32G32B32_FLOAT; + case .COLOR; + return .R32G32B32_FLOAT; + case .COLOR0; #through; + case .COLOR_WITH_ALPHA; + return .R32G32B32A32_FLOAT; + case .COLOR_WITH_ALPHA8; + return .R8G8B8A8_UNORM; + case .TEXCOORD0; + return .R32G32_FLOAT; + case .TEXCOORD1; + return .R32G32B32A32_FLOAT; + case .TEXCOORD2; + return .R32G32B32A32_FLOAT; + case .TEXCOORD3; + return .R32G32B32A32_FLOAT; + case .TEXCOORD4; + return .R32G32B32A32_FLOAT; + case .INSTANCED_MAT0; #through; + case .INSTANCED_MAT1; #through; + case .INSTANCED_MAT2; #through; + case .INSTANCED_MAT3; + return .R32G32B32A32_FLOAT; + case .VERTEX_ID; { + return .R32_UINT; + } + case .INSTANCE_ID; { + return .R32_UINT; + } + } +} + +create_backend_input_layout :: (using renderer: *D3D11_Backend, layout: [] Vertex_Data_Info, shader: Backend_Shader) -> *ID3D11InputLayout { + d3d_layout : [..]D3D11_INPUT_ELEMENT_DESC; + d3d_layout.allocator = temp; + + for vl, index: layout { + semantic_name := semantic_name_for_vertex_data_type(vl.type); + if semantic_name == "SV_InstanceID" continue; + + l : D3D11_INPUT_ELEMENT_DESC; + l.SemanticIndex = semantic_index_for_vertex_data_type(vl.type); + l.SemanticName = semantic_name; + l.Format = format_for_vertex_data_type(vl.type); + l.InputSlot = xx vl.slot; + l.InstanceDataStepRate = xx vl.instanced_step_rate; + l.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + + if vl.instanced_step_rate == 0 { + l.InputSlotClass = .VERTEX_DATA; + } else { + l.InputSlotClass = .INSTANCE_DATA; + } + + array_add(*d3d_layout, l); + } + + vertex_layout : *ID3D11InputLayout; + hr := ID3D11Device_CreateInputLayout(d3d_device, d3d_layout.data, cast(u32) d3d_layout.count, shader.bytecode.data, cast(u64) shader.bytecode.count, *vertex_layout); + + if FAILED(hr) { + log_error("CreateInputLayout failed: %", hr); + return null; + } + + return vertex_layout; +} + +create_backend_input_layout2 :: (using renderer: *D3D11_Backend, layout: [] Vertex_Input, shader: Backend_Shader) -> *ID3D11InputLayout { + d3d_layout : [..]D3D11_INPUT_ELEMENT_DESC; + d3d_layout.allocator = temp; + + for vl, index: layout { + if vl.semantic_name == "SV_InstanceID" continue; + + l : D3D11_INPUT_ELEMENT_DESC; + l.SemanticIndex = xx vl.semantic_index; + l.SemanticName = to_temp_c_string(vl.semantic_name); + l.Format = xx vl.format; // @Robustness: Dangerous, since right now the formats are directly mapped with the integer values of DXGI_FORMAT, but this might change at some point and we wouldn't want weird errors + l.InputSlot = xx vl.slot; + l.InstanceDataStepRate = xx vl.instanced_step_rate; + l.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + + if vl.instanced_step_rate == 0 { + l.InputSlotClass = .VERTEX_DATA; + } else { + l.InputSlotClass = .INSTANCE_DATA; + } + + array_add(*d3d_layout, l); + } + + vertex_layout : *ID3D11InputLayout; + hr := ID3D11Device_CreateInputLayout(d3d_device, d3d_layout.data, cast(u32) d3d_layout.count, shader.bytecode.data, cast(u64) shader.bytecode.count, *vertex_layout); + + if FAILED(hr) { + log_error("CreateInputLayout failed: %", hr); + return null; + } + + return vertex_layout; +} + +create_backend_buffer :: (using renderer: *D3D11_Backend, data: *void, size: u32, stride: u32, type: Buffer_Type, mappable: bool) -> Backend_Buffer #must, bool { + bd : D3D11_BUFFER_DESC; + + flag : D3D11_BIND_FLAG; + if type == { + case .VERTEX; { + bd.BindFlags = .VERTEX_BUFFER; + } + case .INDEX; { + bd.BindFlags = .INDEX_BUFFER; + } + case .CONSTANT; { + bd.BindFlags = .CONSTANT_BUFFER; + } + case .STRUCTURED; { + bd.MiscFlags = .BUFFER_STRUCTURED; + bd.BindFlags = .SHADER_RESOURCE; + bd.StructureByteStride = stride; + } + } + + bd.ByteWidth = size; + + if mappable { + bd.Usage = .DYNAMIC; + bd.CPUAccessFlags = .WRITE; + } else { + bd.Usage = .DEFAULT; + bd.CPUAccessFlags = 0; + } + + InitData : D3D11_SUBRESOURCE_DATA; + InitData.pSysMem = data; + + data_ptr : *D3D11_SUBRESOURCE_DATA; + if data { + data_ptr = *InitData; + } + + buffer : Backend_Buffer; + hr := ID3D11Device_CreateBuffer(d3d_device, *bd, data_ptr, *buffer.buffer); + + + if FAILED(hr) { + log_error("CreateBuffer failed: %", hr); + return .{}, false; + } + + // If it's a structured buffer, create the shader resource view as well + if type == .STRUCTURED { + srv_desc : D3D11_SHADER_RESOURCE_VIEW_DESC; + srv_desc.Format = .UNKNOWN; + srv_desc.ViewDimension = .D3D11_SRV_DIMENSION_BUFFER; + srv_desc.Buffer.ElementOffset = 0; + srv_desc.Buffer.ElementWidth = stride; + srv_desc.Buffer.NumElements = size / stride; + + hr = ID3D11Device_CreateShaderResourceView(d3d_device, buffer.buffer, *srv_desc, *buffer.view); + if FAILED(hr) { + log_error("CreateBuffer failed: %", hr); + return .{}, false; + } + } + + return buffer, true; +} + +destroy_backend_buffer :: (using backend: *D3D11_Backend, buffer: Backend_Buffer) { + IUnknown_Release(buffer.buffer); + if buffer.view != null { + IUnknown_Release(buffer.view); + } +} + +map_backend_buffer :: (using backend: *D3D11_Backend, buffer: Backend_Buffer) -> *void #must { + mapped_resource : D3D11_MAPPED_SUBRESOURCE; + memset(*mapped_resource, 0, size_of(D3D11_MAPPED_SUBRESOURCE)); + ID3D11DeviceContext_Map(d3d_context, buffer.buffer, 0, .D3D11_MAP_WRITE_DISCARD, 0, *mapped_resource); + + return mapped_resource.pData; +} + +unmap_backend_buffer :: (using backend: *D3D11_Backend, buffer: Backend_Buffer) { + ID3D11DeviceContext_Unmap(d3d_context, buffer.buffer, 0); +} + +engine_format_to_dx_format :: (format: Format) -> DXGI_FORMAT { + return cast(DXGI_FORMAT)format; +} + +create_backend_texture :: (using backend: *D3D11_Backend, data: *u8, width: u32, height : u32, channels: u32, generate_mips: bool=true, format: Format) -> Backend_Texture, bool { + dx_format : DXGI_FORMAT = engine_format_to_dx_format(format); + + //if channels == 1 { + // format = .DXGI_FORMAT_R8_UNORM; + //} else { + // if srgb { + // format = .DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + // } else { + // format = .DXGI_FORMAT_R8G8B8A8_UNORM; + // } + //} + + desc : D3D11_TEXTURE2D_DESC; + desc.Width = width; + desc.Height = height; + desc.ArraySize = 1; + + if generate_mips { + desc.MipLevels = 0; + desc.BindFlags = D3D11_BIND_FLAG.SHADER_RESOURCE | D3D11_BIND_FLAG.RENDER_TARGET; + desc.MiscFlags = .D3D11_RESOURCE_MISC_GENERATE_MIPS; + } else { + desc.MipLevels = 1; + desc.BindFlags = D3D11_BIND_FLAG.SHADER_RESOURCE; + desc.MiscFlags = 0; + } + + desc.Format = dx_format; + desc.SampleDesc.Count = 1; + desc.Usage = .D3D11_USAGE_DEFAULT; + desc.CPUAccessFlags = 0; + + texture : Backend_Texture; + + if generate_mips { + hr := ID3D11Device_CreateTexture2D(backend.d3d_device, *desc, null, *texture.texture); + ID3D11DeviceContext_UpdateSubresource(backend.d3d_context, texture.texture, 0, null, data, width * channels * size_of(u8), 0); + + if FAILED(hr) { + return .{}, false; + } else { + srv_desc : D3D11_SHADER_RESOURCE_VIEW_DESC; + srv_desc.Format = desc.Format; + srv_desc.ViewDimension = .D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MostDetailedMip = 0; + srv_desc.Texture2D.MipLevels = cast,no_check(u32)-1; + + hr = ID3D11Device_CreateShaderResourceView(backend.d3d_device, texture.texture, *srv_desc, *texture.view); + + if FAILED(hr) { + return .{}, false; + } + + ID3D11DeviceContext_GenerateMips(backend.d3d_context, texture.view); + } + } else { + image_subresource_data : D3D11_SUBRESOURCE_DATA; + image_subresource_data.pSysMem = data; + image_subresource_data.SysMemPitch = width * channels * size_of(u8); + + data_ptr : *D3D11_SUBRESOURCE_DATA; + + if data { + data_ptr = *image_subresource_data; + } + + hr := ID3D11Device_CreateTexture2D(backend.d3d_device, *desc, data_ptr, *texture.texture); + + if FAILED(hr) { + return .{}, false; + } else { + srv_desc : D3D11_SHADER_RESOURCE_VIEW_DESC; + srv_desc.Format = desc.Format; + srv_desc.ViewDimension = .D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MostDetailedMip = 0; + srv_desc.Texture2D.MipLevels = 1; + + hr = ID3D11Device_CreateShaderResourceView(backend.d3d_device, texture.texture, *srv_desc, *texture.view); + + if FAILED(hr) { + return .{}, false; + } + } + } + + return texture, true; +} + +destroy_backend_texture :: (using backend: *D3D11_Backend, texture: Backend_Texture) { + IUnknown_Release(texture.view); + IUnknown_Release(texture.texture); +} + +update_backend_texture_region :: (using backend: *D3D11_Backend, texture: Backend_Texture, xoffset: u32, yoffset: u32, width: u32, height: u32, pitch: u32, data: *void) { + box : D3D11_BOX; + box.front = 0; + box.back = 1; + box.left = xoffset; + box.right = xoffset + width; + box.top = yoffset; + box.bottom = yoffset + height; + ID3D11DeviceContext_UpdateSubresource(backend.d3d_context, texture.texture, 0, *box, data, width * pitch, width * height * pitch); +} + +create_backend_render_target :: (using backend: *D3D11_Backend, width: u32, height: u32, format: Format) -> Backend_Render_Target { + rt : Backend_Render_Target; + + texture_desc : D3D11_TEXTURE2D_DESC; + result : HRESULT; + render_target_view_desc : D3D11_RENDER_TARGET_VIEW_DESC; + shader_resource_view_desc : D3D11_SHADER_RESOURCE_VIEW_DESC; + + // Setup the render target texture description. + texture_desc.Width = width; + texture_desc.Height = height; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = 1; + texture_desc.Format = .DXGI_FORMAT_R32G32B32A32_FLOAT; // @Incomplete + texture_desc.SampleDesc.Count = 1; + texture_desc.Usage = .D3D11_USAGE_DEFAULT; + texture_desc.BindFlags = D3D11_BIND_FLAG.RENDER_TARGET | D3D11_BIND_FLAG.SHADER_RESOURCE; + texture_desc.CPUAccessFlags = 0; + texture_desc.MiscFlags = 0; + + // Create the render target texture. + result = ID3D11Device_CreateTexture2D(backend.d3d_device, *texture_desc, null, *rt.texture.texture); + assert(!FAILED(result)); + + render_target_view_desc.Format = texture_desc.Format; + render_target_view_desc.ViewDimension = .D3D11_RTV_DIMENSION_TEXTURE2D; + render_target_view_desc.Texture2D.MipSlice = 0; + + result = ID3D11Device_CreateRenderTargetView(backend.d3d_device, rt.texture.texture, *render_target_view_desc, *rt.render_target_view); + assert(!FAILED(result)); + + shader_resource_view_desc.Format = texture_desc.Format; + shader_resource_view_desc.ViewDimension = .D3D11_SRV_DIMENSION_TEXTURE2D; + shader_resource_view_desc.Texture2D.MostDetailedMip = 0; + shader_resource_view_desc.Texture2D.MipLevels = 1; + + result = ID3D11Device_CreateShaderResourceView(backend.d3d_device, rt.texture.texture, *shader_resource_view_desc, *rt.texture.view); + assert(!FAILED(result)); + + return rt; +} + +get_backend_texture_from_render_target :: (using backend: *D3D11_Backend, render_target: Backend_Render_Target) -> Backend_Texture { + return render_target.texture; +} + +create_backend_depth_stencil_buffer :: (using backend: *D3D11_Backend, width: u32, height: u32) -> Backend_Depth_Stencil_Buffer { + ds : Backend_Depth_Stencil_Buffer; + + result : HRESULT; + + // Setup the render target texture description. + texture_desc : D3D11_TEXTURE2D_DESC; + texture_desc.Width = width; + texture_desc.Height = height; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = 1; + texture_desc.Format = .DXGI_FORMAT_R32_TYPELESS; + texture_desc.SampleDesc.Count = 1; + texture_desc.Usage = .D3D11_USAGE_DEFAULT; + texture_desc.BindFlags = D3D11_BIND_FLAG.DEPTH_STENCIL | D3D11_BIND_FLAG.SHADER_RESOURCE; + texture_desc.CPUAccessFlags = 0; + texture_desc.MiscFlags = 0; + + // Create the render target texture. + result = ID3D11Device_CreateTexture2D(backend.d3d_device, *texture_desc, null, *ds.texture); + assert(!FAILED(result)); + + depth_stencil_view_desc : D3D11_DEPTH_STENCIL_VIEW_DESC; + depth_stencil_view_desc.Format = .DXGI_FORMAT_D32_FLOAT; + depth_stencil_view_desc.ViewDimension = .D3D11_DSV_DIMENSION_TEXTURE2D; + depth_stencil_view_desc.Texture2D.MipSlice = 0; + + result = ID3D11Device_CreateDepthStencilView(backend.d3d_device, ds.texture, *depth_stencil_view_desc, *ds.depth_stencil_view); + assert(!FAILED(result)); + + shader_resource_view_desc : D3D11_SHADER_RESOURCE_VIEW_DESC; + shader_resource_view_desc.Format = .DXGI_FORMAT_R32_FLOAT; + shader_resource_view_desc.ViewDimension = .D3D11_SRV_DIMENSION_TEXTURE2D; + shader_resource_view_desc.Texture2D.MostDetailedMip = 0; + shader_resource_view_desc.Texture2D.MipLevels = 1; + + result = ID3D11Device_CreateShaderResourceView(backend.d3d_device, ds.texture, *shader_resource_view_desc, *ds.shader_resource_view); + assert(!FAILED(result)); + + return ds; +} + +create_backend_sampler :: (using backend: *D3D11_Backend, filter: Sampling_Filter, wrap_mode: Wrap_Mode) -> Backend_Sampler, bool { + samp_desc : D3D11_SAMPLER_DESC; + //samp_desc.Filter = .D3D11_FILTER_ANISOTROPIC; + //samp_desc.AddressU = .D3D11_TEXTURE_ADDRESS_CLAMP; + //samp_desc.AddressV = .D3D11_TEXTURE_ADDRESS_CLAMP; + //samp_desc.AddressW = .D3D11_TEXTURE_ADDRESS_CLAMP; + //samp_desc.MaxAnisotropy = D3D11_REQ_MAXANISOTROPY; + //samp_desc.ComparisonFunc = .D3D11_COMPARISON_NEVER; + //samp_desc.MinLOD = 0; + //samp_desc.MaxLOD = xx D3D11_FLOAT32_MAX; + //samp_desc.BorderColor[0] = 0.0; + //samp_desc.BorderColor[1] = 0.0; + //samp_desc.BorderColor[2] = 0.0; + //samp_desc.BorderColor[3] = 0.0; + + //samp_desc.Filter = .D3D11_FILTER_MIN_MAG_MIP_POINT;//.D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;//.D3D11_FILTER_MIN_MAG_MIP_LINEAR; + if filter == { + case .POINT; + samp_desc.Filter = .D3D11_FILTER_MIN_MAG_MIP_POINT; + case .LINEAR; + samp_desc.Filter = .D3D11_FILTER_MIN_MAG_MIP_LINEAR; + + case .ANISOTROPIC; + assert(false); + } + + if wrap_mode == { + case .CLAMP; + samp_desc.AddressU = .D3D11_TEXTURE_ADDRESS_CLAMP; + samp_desc.AddressV = .D3D11_TEXTURE_ADDRESS_CLAMP; + samp_desc.AddressW = .D3D11_TEXTURE_ADDRESS_CLAMP; + case .REPEAT; + samp_desc.AddressU = .D3D11_TEXTURE_ADDRESS_WRAP; + samp_desc.AddressV = .D3D11_TEXTURE_ADDRESS_WRAP; + samp_desc.AddressW = .D3D11_TEXTURE_ADDRESS_WRAP; + } + + samp_desc.MipLODBias = 0; + samp_desc.MaxAnisotropy = 1; + samp_desc.ComparisonFunc = .D3D11_COMPARISON_NEVER; + samp_desc.MinLOD = xx -D3D11_FLOAT32_MAX; + samp_desc.MaxLOD = xx D3D11_FLOAT32_MAX; + + sampler : Backend_Sampler; + hr := ID3D11Device_CreateSamplerState(backend.d3d_device, *samp_desc, *sampler); + + if FAILED(hr) return null, false; + + return sampler, true; +} + +render :: (backend: *D3D11_Backend, command_buffer: *Render_Command_Buffer) { + time := seconds_since_init(); + + d3d_renderer := cast(*D3D11_Backend)backend; + using d3d_renderer; + + for command_buffer.commands { + if #complete it.type == { + case .CLEAR_BACKBUFFER; + clear_color : [4] float; + clear_color[0] = it.clear_backbuffer.color.x; + clear_color[1] = it.clear_backbuffer.color.y; + clear_color[2] = it.clear_backbuffer.color.z; + clear_color[3] = it.clear_backbuffer.color.w; + ID3D11DeviceContext_ClearRenderTargetView(d3d_context, render_target_view, *clear_color); + case .CLEAR_RENDER_TARGET; + clear_color : [4] float; + clear_color[0] = it.clear_render_target.color.x; + clear_color[1] = it.clear_render_target.color.y; + clear_color[2] = it.clear_render_target.color.z; + clear_color[3] = it.clear_render_target.color.w; + ID3D11DeviceContext_ClearRenderTargetView(d3d_context, it.clear_render_target.rt.render_target_view, *clear_color); + case .CLEAR_DEPTH_STENCIL; + ID3D11DeviceContext_ClearDepthStencilView(d3d_context, it.clear_depth_stencil.ds.depth_stencil_view, xx D3D11_CLEAR_FLAG.DEPTH, it.clear_depth_stencil.depth, 0); + case .SET_DRAW_MODE; + if it.set_draw_mode.draw_mode == { + case .FILL; + { + state := get_or_create_rasterizer_state(d3d_renderer, true, d3d_renderer.cull_face); + d3d_renderer.fill = true; + ID3D11DeviceContext_RSSetState(d3d_context, state.state); + } + case .WIREFRAME; + { + state := get_or_create_rasterizer_state(d3d_renderer, false, d3d_renderer.cull_face); + d3d_renderer.fill = false; + ID3D11DeviceContext_RSSetState(d3d_context, state.state); + } + } + case .SET_CULL_FACE; + { + if it.set_cull_face.cull_face == { + case .FRONT; + { + state := get_or_create_rasterizer_state(d3d_renderer, d3d_renderer.fill, .FRONT); + d3d_renderer.cull_face = .FRONT; + ID3D11DeviceContext_RSSetState(d3d_context, state.state); + } + case .BACK; + { + state := get_or_create_rasterizer_state(d3d_renderer, d3d_renderer.fill, .BACK); + d3d_renderer.cull_face = .BACK; + ID3D11DeviceContext_RSSetState(d3d_context, state.state); + } + case .NONE; + { + state := get_or_create_rasterizer_state(d3d_renderer, d3d_renderer.fill, .NONE); + d3d_renderer.cull_face = .NONE; + ID3D11DeviceContext_RSSetState(d3d_context, state.state); + } + } + } + case .SET_DEPTH_WRITE; + if it.set_depth_write.enabled { + ID3D11DeviceContext_OMSetDepthStencilState(d3d_context, ds_state, 1); + } else { + ID3D11DeviceContext_OMSetDepthStencilState(d3d_context, no_depth_ds_state, 1); + } + case .SET_PIPELINE_STATE; + ID3D11DeviceContext_VSSetShader(d3d_context, it.set_pipeline_state.vs.vertex_shader, null, 0); + ID3D11DeviceContext_PSSetShader(d3d_context, it.set_pipeline_state.ps.pixel_shader, null, 0); + ID3D11DeviceContext_IASetInputLayout(d3d_context, it.set_pipeline_state.vertex_layout); + + if it.set_pipeline_state.blend_type == { + case .OPAQUE; + ID3D11DeviceContext_OMSetBlendState(d3d_context, null, null, 0xffffffff); + case .TRANSPARENT; + blend_factor : [4]float; + blend_factor[0] = 0.0; + blend_factor[1] = 0.0; + blend_factor[2] = 0.0; + blend_factor[3] = 0.0; + ID3D11DeviceContext_OMSetBlendState(d3d_context, it.set_pipeline_state.blend_state, null, 0xffffffff); + } + case .SET_VERTEX_BUFFER; + offset : u32 = 0; + ID3D11DeviceContext_IASetVertexBuffers(d3d_context, it.set_vertex_buffer.start_slot, 1, *it.set_vertex_buffer.buffer.buffer, *it.set_vertex_buffer.stride, *offset); + case .SET_INDEX_BUFFER; { + ID3D11DeviceContext_IASetIndexBuffer(d3d_context, it.set_index_buffer.buffer.buffer, .R32_UINT, 0); + } + case .SET_CONSTANT_BUFFER; { + if it.set_constant_buffer.shader_type == { + case .VERTEX; + ID3D11DeviceContext_VSSetConstantBuffers(d3d_context, it.set_constant_buffer.slot, 1, *it.set_constant_buffer.buffer.buffer); + case .PIXEL; + ID3D11DeviceContext_PSSetConstantBuffers(d3d_context, it.set_constant_buffer.slot, 1, *it.set_constant_buffer.buffer.buffer); + } + } + case .SET_STRUCTURED_BUFFER; { + ID3D11DeviceContext_VSSetShaderResources(d3d_context, xx it.set_structured_buffer.slot, 1, *it.set_structured_buffer.buffer.view); + } + case .SET_TEXTURE; + ID3D11DeviceContext_PSSetShaderResources(d3d_context, xx it.set_texture.slot, 1, *it.set_texture.texture.view); + case .SET_TEXTURE_FROM_RENDER_TARGET; + view := it.set_texture_from_rt.rt.texture.view; + ID3D11DeviceContext_PSSetShaderResources(d3d_context, xx it.set_texture_from_rt.slot, 1, *view); + case .SET_TEXTURE_FROM_DEPTH_STENCIL; + view := it.set_texture_from_ds.ds.shader_resource_view; + ID3D11DeviceContext_PSSetShaderResources(d3d_context, xx it.set_texture_from_ds.slot, 1, *view); + case .SET_RENDER_TARGETS; + + views : [..] *ID3D11RenderTargetView; + views.allocator = temp; + + render_targets := it.set_render_targets.render_targets; + for rt, index: render_targets { + array_add(*views, rt.render_target_view); + } + depth_stencil_view : *ID3D11DepthStencilView; + if it.set_render_targets.depth_stencil_enabled { + depth_stencil_view = it.set_render_targets.depth_stencil_buffer.depth_stencil_view; + } + + ID3D11DeviceContext_OMSetRenderTargets(d3d_context, xx views.count, views.data, depth_stencil_view); + case .SET_BACKBUFFER; + ID3D11DeviceContext_OMSetRenderTargets(d3d_context, 1, *render_target_view, null); + case .SET_SAMPLER; + ID3D11DeviceContext_PSSetSamplers(d3d_context, xx it.set_sampler.slot, 1, *it.set_sampler.sampler); + case .SET_VIEWPORT; + vp : D3D11_VIEWPORT; + vp.Width = xx it.set_viewport.width; + vp.Height = xx it.set_viewport.height; + vp.MinDepth = it.set_viewport.min_depth; + vp.MaxDepth = it.set_viewport.max_depth; + vp.TopLeftX = xx it.set_viewport.x; + vp.TopLeftY = xx it.set_viewport.y; + ID3D11DeviceContext_RSSetViewports(d3d_context, 1, *vp); + case .DRAW; + if it.draw.topology == { + case .TRIANGLE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLELIST); + case .LINE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINELIST); + case .TRIANGLE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLESTRIP); + case .LINE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINESTRIP); + } + + ID3D11DeviceContext_Draw(d3d_context, xx it.draw.vertex_count, xx it.draw.start_vertex_index); + case .DRAW_INDEXED; + if it.draw.topology == { + case .TRIANGLE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLELIST); + case .LINE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINELIST); + case .TRIANGLE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLESTRIP); + case .LINE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINESTRIP); + } + + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLELIST); + ID3D11DeviceContext_DrawIndexed(d3d_context, xx it.draw_indexed.index_count, xx it.draw_indexed.start_index, xx it.draw_indexed.base_vertex); + case .DRAW_INSTANCED; + if it.draw.topology == { + case .TRIANGLE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLELIST); + case .LINE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINELIST); + case .TRIANGLE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLESTRIP); + case .LINE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINESTRIP); + } + + ID3D11DeviceContext_DrawInstanced(d3d_context, xx it.draw_instanced.vertex_count, xx it.draw_instanced.instance_count, xx it.draw_instanced.start_vertex_index, xx it.draw_instanced.start_instance_index); + case .DRAW_INDEXED_INSTANCED; + if it.draw.topology == { + case .TRIANGLE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLELIST); + case .LINE_LIST; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINELIST); + case .TRIANGLE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.TRIANGLESTRIP); + case .LINE_STRIP; + ID3D11DeviceContext_IASetPrimitiveTopology(d3d_context, D3D11_PRIMITIVE_TOPOLOGY.LINESTRIP); + } + + ID3D11DeviceContext_DrawIndexedInstanced(d3d_context, xx it.draw_indexed_instanced.index_count, xx it.draw_indexed_instanced.instance_count, xx it.draw_indexed_instanced.start_index, xx it.draw_indexed_instanced.base_vertex, 0); + } + } + + + now := seconds_since_init(); + renderer.last_render_time_cpu = cast(float)(now - time) * 1000.0; + time = now; + + // Present the information rendered to the back buffer to the front buffer (the screen) + hr := IDXGISwapChain_Present(swap_chain, xx ifx renderer.vsync then 1 else 0, 0); + renderer.last_render_time_gpu = cast(float)(now - time) * 1000.0; + + if FAILED(hr) { + log_error("Present failed: %", hr); + removed_reason := ID3D11Device_GetDeviceRemovedReason(d3d_device); + log_error("Removed reason: %", removed_reason); + } +} + +#scope_file +get_or_create_rasterizer_state :: (backend: *D3D11_Backend, fill: bool, cull_face: Cull_Face) -> *Rasterizer_State { + for * backend.rasterizer_states { + if it.fill == fill && it.cull_face == cull_face { + return it; + } + } + + return create_rasterizer_state(backend, fill, cull_face); +} + +create_rasterizer_state :: (backend: *D3D11_Backend, fill: bool, cull_face: Cull_Face) -> *Rasterizer_State { + raster_desc : D3D11_RASTERIZER_DESC; + raster_desc.AntialiasedLineEnable = .FALSE; + + if cull_face == { + case .FRONT; + raster_desc.CullMode = .D3D11_CULL_FRONT; + case .BACK; + raster_desc.CullMode = .D3D11_CULL_BACK; + case .NONE; + raster_desc.CullMode = .D3D11_CULL_NONE; + } + + raster_desc.DepthBias = 0; + raster_desc.DepthBiasClamp = 0.0; + raster_desc.DepthClipEnable = .TRUE; + + if fill { + raster_desc.FillMode = .D3D11_FILL_SOLID; + } else { + raster_desc.FillMode = .D3D11_FILL_WIREFRAME; + } + + raster_desc.FrontCounterClockwise = .FALSE; + raster_desc.MultisampleEnable = .FALSE; + raster_desc.ScissorEnable = .FALSE; + raster_desc.SlopeScaledDepthBias = 0.0; + + state : *ID3D11RasterizerState; + result := ID3D11Device_CreateRasterizerState(backend.d3d_device, *raster_desc, *state); + assert(!FAILED(result)); + + rasterizer_info : Rasterizer_State; + rasterizer_info.fill = fill; + rasterizer_info.cull_face = cull_face; + rasterizer_info.state = state; + + array_add(*backend.rasterizer_states, rasterizer_info); + + return *backend.rasterizer_states[backend.rasterizer_states.count-1]; +} + +//#load "imgui_dx11.jai"; +//#import "shader_parsing"; diff --git a/renderer/font.jai b/renderer/font.jai new file mode 100644 index 0000000..8c02516 --- /dev/null +++ b/renderer/font.jai @@ -0,0 +1,105 @@ +Font_Handle :: #type, distinct u32; + +Glyph :: struct { + ax: float; // advance.x + ay: float; // advance.y + bw: float; // bitmap.width; + bh: float; // bitmap.rows; + bl: float; // bitmap_left; + bt: float; // bitmap_top; + tx: float; // x offset of glyph in texture coordinates +} + +Point :: struct { + x: float; + y: float; + s: float; + t: float; + + color: Vector4; +}; + +Font :: struct { + glyphs: [..] Glyph; + face: FT_Face; + pixel_size: u32; + atlas_width: u32; + atlas_height: u32; + texture: Texture_Handle; +} + +ft : FT_Library; + +init_freetype :: () { + if FT_Init_FreeType(*ft) { + log("Couldn't init Freetype\n"); + } + +} + +create_font :: (renderer: *Renderer, path: string, pixel_size: u32) -> Font_Handle { + font: Font; + font.pixel_size = pixel_size; + array_resize(*font.glyphs, 128); + + if FT_New_Face(ft, path.data, 0, *font.face) { + log("Couldn't create new face\n"); + assert(false); + } else { + FT_Set_Pixel_Sizes(font.face, 0, pixel_size); + + g := font.face.glyph; + w : u32 = 0; + h : u32 = 0; + + index := 0; + for 32..128-1 { + if FT_Load_Char(font.face, xx it, FT_LOAD_RENDER) { + continue; + } + + w += g.bitmap.width + 1; + h = max(h, g.bitmap.rows); + } + + atlas_width := w; + + bytes : [..] u8; + bytes.allocator = temp; + array_resize(*bytes, w * h); + + texture := create_texture(renderer, bytes.data, w, h, 1, generate_mips=false, format=.R8_UNORM); + + font.atlas_width = w; + font.atlas_height = h; + + font.texture = texture; + + x : u32 = 0; + + pixel_width := 1.0 / cast(float)atlas_width; + + for 32..128-1 { + if FT_Load_Char(font.face, xx it, FT_LOAD_RENDER) continue; + + // @Speed: We should put this buffer data into an array and upload everything once after this loop + update_texture_region(renderer, texture, x, 0, g.bitmap.width, g.bitmap.rows, 1, g.bitmap.buffer); + + font.glyphs[it].ax = cast(float)(g.advance.x >> 6); + font.glyphs[it].ay = cast(float)(g.advance.y >> 6); + font.glyphs[it].bw = cast(float)g.bitmap.width; + font.glyphs[it].bh = cast(float)g.bitmap.rows; + font.glyphs[it].bl = cast(float)g.bitmap_left; + font.glyphs[it].bt = cast(float)g.bitmap_top; + font.glyphs[it].tx = cast(float)x / cast(float)w; + x += g.bitmap.width + 1; + } + + array_add(*renderer.fonts, font); + return xx renderer.fonts.count; + } + + return 0; +} + +#import "freetype-2.12.1"; diff --git a/renderer/material.jai b/renderer/material.jai new file mode 100644 index 0000000..c4d9823 --- /dev/null +++ b/renderer/material.jai @@ -0,0 +1,465 @@ +MAX_BUFFER_MAPPINGS :: 4; +MAX_PARAMETERS :: 16; +MAX_PROPERTY_REFERENCES :: 4; + +Pass_Reference :: struct { + pass_index: s64; + parameter_index: s64; + + offset: u32; // Only for buffers +} + +Material_Property :: struct { + name: string; + type: Shader_Property_Type; + + union { + float_val : float; + float2_val : Vector2; + float3_val : Vector3; + float4_val : Vector4; + mat3_val : Matrix3; + mat4_val : Matrix4; + int_val : int; + bool_val : bool; + texture_val : Texture_Handle; + sampler_val : Sampler_Handle; + } + + references : [MAX_PROPERTY_REFERENCES] Pass_Reference; + num_references: s64; +} + +Material_Pass_Parameter :: struct { + slot: u32; + type: Shader_Parameter_Type; + shader: Shader_Type; + mapping: Shader_Parameter_Mapping; + mapping_str: string; + + union { + buffer_info : struct { + buffer: Buffer_Handle; + size: u32; + data: *void; + dirty: bool; + } + sampler: Sampler_Handle; + texture: Texture_Handle; + } +} + +Material_Pass :: struct { + pass: Render_Pass_Handle; + pipeline: Pipeline_State_Handle; + + parameters: [MAX_PARAMETERS] Material_Pass_Parameter; + num_parameters: s64; +} + +Material :: struct { + name: string; + + properties: [..] Material_Property; + + passes: [..] Material_Pass; + + allocator: Allocator; + pool: Flat_Pool; + + // For removing the material later + _locator: Bucket_Locator; +} + +Material_Pass_Info :: struct { + pass: Render_Pass_Handle; + pipeline: Pipeline_State_Handle; +} + +materials: Bucket_Array(Material, 32); + +delete_material :: (mat: *Material) { + for pass: mat.passes { + for pi: 0..pass.num_parameters-1 { + param := pass.parameters[pi]; + if param.type != .BUFFER continue; + + if param.buffer_info.data != null && param.mapping == .NONE && param.buffer_info.buffer != 0 { // I guess if there is a mapping, the data ptr is null as well, but checking for both, if this changes at a later point + destroy_buffer(renderer, param.buffer_info.buffer); + } + param.buffer_info.buffer = 0; + } + } + + fini(*mat.pool); + + bucket_array_remove(*materials, mat._locator); +} + +create_material :: (name: string) -> *Material { + p, locator := find_and_occupy_empty_slot(*materials); + + p.pool = .{}; + p.allocator.proc = flat_pool_allocator_proc; + p.allocator.data = *p.pool; + + p.name = copy_string(name,, p.allocator); + + p.passes.allocator = p.allocator; + p.properties.allocator = p.allocator; + + p._locator = locator; + + return p; +} + +// Create the material, but reuse the material's arrays? +//copy_material :: (material: *Material) -> *Material { +// new_material := create_material(material.name); +// +// new_context := context; +// new_context.allocator = new_material.allocator; +// +// push_context new_context { +// array_reserve(*new_material.passes, material.passes.count); +// array_reserve(*new_material.properties, material.properties.count); +// +// for pass: material.passes { +// new_pass := pass; +// for pi: 0..new_pass.num_parameters-1 { +// new_pass.parameters[pi].mapping_str = copy_string(pass.parameters[pi].mapping_str); +// } +// array_add(*new_material.passes, new_pass); +// } +// +// for prop: material.properties { +// new_prop := prop; +// new_prop.name = copy_string(prop.name); +// array_add(*new_material.properties, new_prop); +// } +// } +// +// return new_material; +//} + +create_material_for_passes :: (name: string, material_pass_infos: [] Material_Pass_Info) -> *Material { + add_properties_from_shader :: (mat: *Material, pass_index: s64, shader: Shader) { + pass := *mat.passes[pass_index]; + + for *param: shader.info.parameters { + pass_param : Material_Pass_Parameter; + pass_param.type = param.type; + pass_param.slot = param.slot; + pass_param.mapping = param.mapping; + pass_param.mapping_str = copy_string(param.mapping_str); // @Incomplete: MEMORY + pass_param.shader = param.shader; + pass_param.buffer_info.size = param.size; + pass_param.buffer_info.data = alloc(param.size); + memset(pass_param.buffer_info.data, 0, param.size); + + if pass_param.type == { + case .BUFFER; { + if param.mapping == .NONE { // Only allocate if the parameter doesn't have a mapping + // allocate the buffer + param.mapping = .NONE; + pass_param.buffer_info.buffer = create_constant_buffer(renderer, pass_param.buffer_info.data, pass_param.buffer_info.size, mappable=true); + } + } + case .TEXTURE; { + // @Incomplete: Set default texture + } + case .SAMPLER; { + // @Incomplete: Set default texture + } + } + + pass.parameters[pass.num_parameters] = pass_param; + pass.num_parameters += 1; + + if param.mapping == .NONE { + for pi: 0..param.num_properties-1 { + param_prop := param.properties[pi]; + + prop : *Material_Property; + + // Check if property exists + for *existing: mat.properties { + if existing.name == param_prop.name && existing.type == param_prop.type { + // We found one! + prop = existing; + break; + } + } + + if prop == null { + array_add(*mat.properties, .{}); + prop = *mat.properties[mat.properties.count-1]; + prop.name = param_prop.name; + prop.type = param_prop.type; + } + + // Add the data about the pass buffer, texture, etc here + reference : Pass_Reference; + reference.pass_index = pass_index; + reference.parameter_index = pass.num_parameters - 1; + reference.offset = xx param_prop.buffer_offset; + prop.references[prop.num_references] = reference; + prop.num_references += 1; + } + } + } + } + + mat := create_material(name); + + new_context := context; + new_context.allocator = mat.allocator; + + push_context new_context { + for info: material_pass_infos { + pipeline_state := *renderer.pipeline_states[info.pipeline - 1]; + vs := get_shader(pipeline_state.vs); + ps := get_shader(pipeline_state.ps); + + pass : Material_Pass; + + pass.pipeline = info.pipeline; + pass.pass = info.pass; + array_add(*mat.passes, pass); + + add_properties_from_shader(mat, mat.passes.count-1, vs); + add_properties_from_shader(mat, mat.passes.count-1, ps); + } + } + + return mat; +} + +set_material_property :: (material: *Material, name: string, val: float) { + found := false; + for *prop: material.properties { + if prop.name == name && prop.type == .FLOAT { + prop.float_val = val; + + for 0..prop.num_references-1 { + ref := prop.references[it]; + pass := *material.passes[ref.pass_index]; + param := *pass.parameters[ref.parameter_index]; + memcpy(*param.buffer_info.data[ref.offset], *val, size_of(float)); + param.buffer_info.dirty = true; + } + found = true; + break; + } + } + + if !found { + log_error("MATERIAL: Tried setting float property '%' on material '%' but the property wasn't found\n", name, material.name); + } +} + +set_material_property :: (material: *Material, name: string, val: Vector2) { + found := false; + for *prop: material.properties { + if prop.name == name && prop.type == .FLOAT2 { + prop.float2_val = val; + + for 0..prop.num_references-1 { + ref := prop.references[it]; + pass := *material.passes[ref.pass_index]; + param := *pass.parameters[ref.parameter_index]; + memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector2)); + param.buffer_info.dirty = true; + } + found = true; + break; + } + } + + if !found { + log_error("MATERIAL: Tried setting Vector2 property '%' on material '%' but the property wasn't found\n", name, material.name); + } +} + +set_material_property :: (material: *Material, name: string, val: Vector3) { + found := false; + for *prop: material.properties { + if prop.name == name && prop.type == .FLOAT3 { + prop.float3_val = val; + + for 0..prop.num_references-1 { + ref := prop.references[it]; + pass := *material.passes[ref.pass_index]; + param := *pass.parameters[ref.parameter_index]; + memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector3)); + param.buffer_info.dirty = true; + } + found = true; + break; + } + } + if !found { + log_error("MATERIAL: Tried setting Vector3 property '%' on material '%' but the property wasn't found\n", name, material.name); + } +} + +set_material_property :: (material: *Material, name: string, val: Vector4) { + found := false; + for *prop: material.properties { + if prop.name == name && prop.type == .FLOAT4 { + prop.float4_val = val; + + for 0..prop.num_references-1 { + ref := prop.references[it]; + pass := *material.passes[ref.pass_index]; + param := *pass.parameters[ref.parameter_index]; + memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector4)); + param.buffer_info.dirty = true; + } + found = true; + break; + } + } + if !found { + log_error("MATERIAL: Tried setting Vector4 property '%' on material '%' but the property wasn't found\n", name, material.name); + } +} + +set_material_property :: (material: *Material, name: string, val: Texture_Handle) { + found := false; + for *prop: material.properties { + if prop.name == name && prop.type == .TEXTURE { + prop.texture_val = val; + + for 0..prop.num_references-1 { + ref := prop.references[it]; + pass := *material.passes[ref.pass_index]; + param := *pass.parameters[ref.parameter_index]; + param.texture = val; + } + found = true; + break; + } + } + if !found { + log_error("MATERIAL: Tried setting texture property '%' on material '%' but the property wasn't found\n", name, material.name); + } +} + +set_material_pass_parameters :: (material: *Material, pass_index: s64, render_pass: Render_Pass, transform_buffer: Buffer_Handle, material_buffer: Buffer_Handle, bone_buffer: Buffer_Handle, defaults: Model_Material) { + mat_pass := material.passes[pass_index]; + + for 0..mat_pass.num_parameters-1 { + param := mat_pass.parameters[it]; + if param.buffer_info.dirty { + param.buffer_info.dirty = false; + upload_data_to_buffer(renderer, param.buffer_info.buffer, param.buffer_info.data, param.buffer_info.size); + } + + if param.type == { + case .BUFFER; { + if param.mapping == { + case .TIME; { + //param.buffer_info.buffer = engine.time_buffer; // @Incomplete + } + case .MODEL_MATRIX; { + param.buffer_info.buffer = transform_buffer; + } + case .CAMERA_DATA; { + //param.buffer_info.buffer = renderer.engine_buffers.camera; // @Incomplete + } + case .MATERIAL; { + param.buffer_info.buffer = material_buffer; + } + case .DIRECTIONAL_LIGHT; { + //param.buffer_info.buffer = renderer.engine_buffers.directional_light; // @Incomplete + } + case .POINT_LIGHTS; { + //param.buffer_info.buffer = renderer.engine_buffers.point_lights; // @Incomplete + } + case .BONE_MATRICES; { + param.buffer_info.buffer = bone_buffer; + } + case .CUSTOM; { + if renderer.callbacks.get_custom_material_parameter_mapping != null { + success, mapping_info := renderer.callbacks.get_custom_material_parameter_mapping(param.mapping_str); + if success { + param.buffer_info.buffer = mapping_info.buffer; + } + } + } + } + + if param.buffer_info.buffer > 0 { + push_cmd_set_constant_buffer(renderer, param.slot, param.buffer_info.buffer, param.shader); + } + } + case .SAMPLER; + if param.mapping == { + case .REPEAT_SAMPLER; { + param.sampler = renderer.default_samplers.repeat; + } + case .CLAMP_SAMPLER; { + param.sampler = renderer.default_samplers.clamp; + } + } + if param.sampler != 0 { + push_cmd_set_sampler(renderer, param.slot, param.sampler); + } + case .TEXTURE; + { + is_texture_input := false; + input_index := 0; + + if param.mapping == { + case .BASE_COLOR_TEXTURE; { + param.texture = defaults.textures.base_color; + } + case .NORMAL_MAP; { + param.texture = defaults.textures.normal; + } + case .SHADER_ATTACHMENT0; { + is_texture_input = true; + input_index = 0; + } + case .SHADER_ATTACHMENT1; { + is_texture_input = true; + input_index = 1; + } + case .SHADER_ATTACHMENT2; { + is_texture_input = true; + input_index = 2; + } + case .SHADER_ATTACHMENT3; { + is_texture_input = true; + input_index = 3; + } + case .CUSTOM; { + if renderer.callbacks.get_custom_material_parameter_mapping != null { + success, mapping_info := renderer.callbacks.get_custom_material_parameter_mapping(param.mapping_str); + if success { + param.texture = mapping_info.texture; + } + } + } + } + + if is_texture_input { + input := render_pass.inputs[input_index]; + owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle); + + if input.rt_index == DEPTH_STENCIL_SLOT { + push_cmd_set_texture(renderer, param.slot, owning_pass.depth_stencil); + } else { + push_cmd_set_texture(renderer, param.slot, owning_pass.render_targets[input.rt_index]); + } + } else { + if param.texture > 0 { + push_cmd_set_texture(renderer, param.slot, param.texture); + } + } + } + + } + } +} diff --git a/renderer/mesh.jai b/renderer/mesh.jai new file mode 100644 index 0000000..746568c --- /dev/null +++ b/renderer/mesh.jai @@ -0,0 +1,191 @@ +Skin_Vertex :: struct { + bone_index: [4] float; + bone_weight: [4] float; +} + +Mesh_Vertex_Data_Type :: enum { + NONE; + POSITION; + NORMAL; + TEXCOORD; + TANGENT; + COLOR; + BITANGENT; + BONE_INDICES; + BONE_WEIGHTS; +} + +Mesh_Handle :: #type, distinct u32; + +Mesh :: struct { + name : string; + positions : [..] Vector3; + normals : [..] Vector3; + texcoords : [..] Vector2; + tangents : [..] Vector3; + bitangents : [..] Vector3; + colors : [..] Vector4; + skin_data : [..] Skin_Vertex; + + bone_indices: [..] s32; + bone_matrices: [..] Matrix4; + num_bones: s32; + + indices : [..] u32; + ib : Buffer_Handle; + + vbs : Table(u32, Buffer_Handle); + //vb : Buffer_Handle; +} + +get_mesh_vb :: (mesh: *Mesh, pipeline_handle: Pipeline_State_Handle = 0) -> Buffer_Handle { + handle := ifx pipeline_handle == 0 then renderer.current_state.last_set_pipeline else pipeline_handle; + + pipeline_state := *renderer.pipeline_states[handle-1]; + return get_mesh_vb(mesh, pipeline_state.mesh_data_types); +} + +get_mesh_vb :: (mesh: *Mesh, input: [] Mesh_Vertex_Data_Type) -> Buffer_Handle { + hash : u32 = 0; + + nums : [8] u32 : .[13, 61, 84, 86, 65, 10000, 100000, 126]; + for input, i: input { + hash += nums[i] * cast(u32)input; + } + + if !table_contains(*mesh.vbs, hash) { + final_vertices : [..] float; + final_vertices.allocator = temp; + + stride : u32 = 0; + + for input: input { + if input == { + case .POSITION; stride += 3; + case .NORMAL; stride += 3; + case .TEXCOORD; stride += 2; + case .COLOR; stride += 4; + case .TANGENT; stride += 3; + case .BITANGENT; stride += 3; + case .BONE_INDICES; stride += 4; + case .BONE_WEIGHTS; stride += 4; + } + } + + for 0..mesh.positions.count-1 { + for input: input { + if input == { + case .POSITION; { + array_add(*final_vertices, mesh.positions[it].x); + array_add(*final_vertices, mesh.positions[it].y); + array_add(*final_vertices, mesh.positions[it].z); + } + case .NORMAL; { + if mesh.normals.count > 0 { + array_add(*final_vertices, mesh.normals[it].x); + array_add(*final_vertices, mesh.normals[it].y); + array_add(*final_vertices, mesh.normals[it].z); + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required normals\n"); + } + } + case .TEXCOORD; { + if mesh.texcoords.count > 0 { + array_add(*final_vertices, mesh.texcoords[it].x); + array_add(*final_vertices, mesh.texcoords[it].y); + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required texcoords\n"); + } + } + case .COLOR; { + if mesh.colors.count > 0 { + array_add(*final_vertices, mesh.colors[it].x); + array_add(*final_vertices, mesh.colors[it].y); + array_add(*final_vertices, mesh.colors[it].z); + array_add(*final_vertices, mesh.colors[it].w); + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required colors\n"); + } + } + case .TANGENT; { + if mesh.tangents.count > 0 { + array_add(*final_vertices, mesh.tangents[it].x); + array_add(*final_vertices, mesh.tangents[it].y); + array_add(*final_vertices, mesh.tangents[it].z); + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required tangents\n"); + } + } + case .BITANGENT; { + if mesh.bitangents.count > 0 { + array_add(*final_vertices, mesh.bitangents[it].x); + array_add(*final_vertices, mesh.bitangents[it].y); + array_add(*final_vertices, mesh.bitangents[it].z); + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required bitangents\n"); + } + } + case .BONE_INDICES; { + if mesh.skin_data.count > 0 { + for index: 0..3 { + array_add(*final_vertices, mesh.skin_data[it].bone_index[index]); + } + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required bone indices\n"); + } + } + case .BONE_WEIGHTS; { + if mesh.skin_data.count > 0 { + for index: 0..3 { + array_add(*final_vertices, mesh.skin_data[it].bone_weight[index]); + } + } else { + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + array_add(*final_vertices, 0.0); + log_error("Mesh didn't have required bone weights\n"); + } + } + } + } + } + + vb_size := size_of(float)*stride*mesh.positions.count; + vb := create_vertex_buffer(renderer, final_vertices.data, xx vb_size, stride=size_of(float)*stride); + table_add(*mesh.vbs, hash, vb); + } + + value, success := table_find(*mesh.vbs, hash); + return value; +} + +delete_mesh :: (handle: Mesh_Handle) { + mesh := parray_get(*renderer.meshes, handle); + for mesh.vbs { + destroy_buffer(renderer, it); + } + + deinit(*mesh.vbs); + + parray_remove(*renderer.meshes, handle); +} diff --git a/renderer/model.jai b/renderer/model.jai new file mode 100644 index 0000000..d40bd1e --- /dev/null +++ b/renderer/model.jai @@ -0,0 +1,668 @@ +Node_Handle :: #type, distinct u32; +Material_Handle :: #type, distinct u32; + +MAX_BONES :: 128; +MAX_WEIGHTS :: 4; + +Node :: struct { + name : string; + path : string; + transform : Transform; + + meshes : [..] Mesh_Handle; + material_defaults : [..] Model_Material; + materials_old : [..] Material_Old; + + num_bones: s32; + + parent : Node_Handle; + children : [..] Node_Handle; + + // Instance variables + materials : [..] *Material; + + has_sampled_animation: bool; +} + +Node_Animation :: struct { + framerate : float64; + num_frames : s32; + + rotation : [..] Quaternion; + position : [..] Vector3; + scale : [..] Vector3; + + constant : bool; + const_rotation : Quaternion; + const_position : Vector3; + const_scale : Vector3; + + is_set: bool; +} + +Animation :: struct { + name: string; + duration: float64; + num_frames: s32; + framerate: float64; + + nodes : [..] Node_Animation; +} + +Model_Material :: struct { + base_color : Vector4; + + textures : struct { + base_color: Texture_Handle; + normal: Texture_Handle; + } +} + +Model :: struct { + name : string; + path : string; + nodes : [..] Node; + animations : [..] Animation; + materials : [..] Model_Material; +} + +get_first_mesh_from_model :: (model: Model) -> Mesh_Handle, bool { + for model.nodes { + for m: it.meshes { + return m, true; + } + } + + return 0, false; +} + +get_mesh_by_name :: (model: Model, name: string) -> Mesh_Handle, bool { + for model.nodes { + for m: it.meshes { + mesh := parray_get(*renderer.meshes, m); + if mesh.name == name { + return m, true; + } + } + } + + return 0, false; +} + +Coloru16 :: struct { + r: u16; + g: u16; + b: u16; + a: u16; +} + +parse_fbx_node :: (model: *Model, fbx_node: *ufbx_node) { + node : Node; + node.name = copy_string(to_string(fbx_node.name.data)); + if fbx_node.parent != null { + node.parent = cast(Node_Handle) fbx_node.parent.typed_id + 1; // UFBX ids are 0-indexed, but 0 means none in our API + } + + t := create_identity_transform(); + + transform := ufbx_matrix_to_transform(*fbx_node.node_to_parent); + + t.position.x = cast(float)transform.translation.x; + t.position.y = cast(float)transform.translation.y; + t.position.z = cast(float)transform.translation.z; + + t.scale.x = xx transform.scale.x; + t.scale.y = xx transform.scale.y; + t.scale.z = xx transform.scale.z; + + t.orientation.x = cast(float)transform.rotation.x; + t.orientation.y = cast(float)transform.rotation.y; + t.orientation.z = cast(float)transform.rotation.z; + t.orientation.w = cast(float)transform.rotation.w; + + update_matrix(*t); + + //t.model_matrix = make_matrix(world_position, world_orientation, world_scale); + t.dirty = true; + node.transform = t; + + Mesh_Vertex :: struct { + position: Vector3; + normal : Vector3; + uv : Vector2; + }; + + if fbx_node.mesh != null { + fbx_mesh := fbx_node.mesh; + max_parts : size_t = 0; + max_triangles : size_t = 0; + + for pi: 0..cast(s32)fbx_mesh.materials.count-1 { + mesh_mat := fbx_mesh.materials.data[pi]; + if mesh_mat.num_triangles == 0 continue; + max_parts += 1; + max_triangles = max(max_triangles, mesh_mat.num_triangles); + } + + num_tri_indices := fbx_mesh.max_face_triangles * 3; + tri_indices : [..] u32; + vertices : [..] Mesh_Vertex; + indices : [..] u32; + skin_vertices : [..] Skin_Vertex; + mesh_skin_vertices : [..] Skin_Vertex; + + tri_indices.allocator = temp; + vertices.allocator = temp; + indices.allocator = temp; + skin_vertices.allocator = temp; + mesh_skin_vertices.allocator = temp; + indices.allocator = temp; + array_resize(*tri_indices, xx num_tri_indices); + array_resize(*vertices, xx max_triangles * 3); + array_resize(*indices, xx max_triangles * 3); + array_resize(*skin_vertices, xx max_triangles * 3); + array_resize(*mesh_skin_vertices, xx max_triangles * 3); + + num_bones : s32 = 0; + skin : *ufbx_skin_deformer = null; + skinned : bool; + + bone_indices: [..] s32; + bone_matrices: [..] Matrix4; + bone_indices.allocator = temp; + bone_matrices.allocator = temp; + + if fbx_mesh.skin_deformers.count > 0 && fbx_mesh.skin_deformers.data[0].clusters.count > 0 { + skinned = true; + + skin = fbx_mesh.skin_deformers.data[0]; + + for ci: 0..skin.clusters.count-1 { + cluster := skin.clusters.data[ci]; + if num_bones < MAX_BONES { + array_add(*bone_indices, cast(s32)cluster.bone_node.typed_id); + array_add(*bone_matrices, ufbx_to_mat4(cluster.geometry_to_bone)); + num_bones += 1; + } + } + + for vi: 0..fbx_mesh.num_vertices-1 { + num_weights := 0; + total_weight := 0.0; + weights : [4] float; + clusters : [4] float; + + vertex_weights := skin.vertices.data[vi]; + for wi: 0..vertex_weights.num_weights-1 { + if num_weights >= 4 break; + weight := skin.weights.data[vertex_weights.weight_begin + wi]; + + if weight.cluster_index < MAX_BONES { + total_weight += cast(float)weight.weight; + clusters[num_weights] = cast(float)weight.cluster_index; + weights[num_weights] = cast(float)weight.weight; + num_weights += 1; + } + } + + if total_weight > 0.0 { + skin_vert := *mesh_skin_vertices[vi]; + quantized_sum : float = 0; + for i: 0..3 { + quantized_weight := weights[i] / total_weight; + quantized_sum += quantized_weight; + skin_vert.bone_index[i] = clusters[i]; + skin_vert.bone_weight[i] = quantized_weight; + } + skin_vert.bone_weight[0] += 1.0 - quantized_sum; + } + } + } + + node.num_bones = num_bones; + + for pi: 0..fbx_mesh.materials.count-1 { + mesh : Mesh; + mesh.name = copy_string(to_string(fbx_mesh.name.data)); + mesh.num_bones = num_bones; + + array_resize(*mesh.bone_indices, bone_indices.count); + array_resize(*mesh.bone_matrices, bone_matrices.count); + memcpy(mesh.bone_indices.data, bone_indices.data, size_of(s32) * bone_indices.count); + memcpy(mesh.bone_matrices.data, bone_matrices.data, size_of(Matrix4) * bone_matrices.count); + mesh_mat := fbx_mesh.materials.data[pi]; + if mesh_mat.num_triangles == 0 continue; + + num_indices : size_t = 0; + + for fi: 0..mesh_mat.num_faces-1 { + face := fbx_mesh.faces.data[mesh_mat.face_indices.data[fi]]; + num_tris := ufbx_catch_triangulate_face(null, tri_indices.data, num_tri_indices, fbx_mesh, face); + + default_uv : ufbx_vec2; + + for vi: 0..num_tris * 3 - 1 { + ix := tri_indices.data[num_tris * 3 - 1 -vi]; + vert := *vertices.data[num_indices]; + + pos := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_position, ix); + normal := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_normal, ix); + uv : ufbx_vec2; + + if fbx_mesh.vertex_uv.exists { + uv = ufbx_catch_get_vertex_vec2(null, *fbx_mesh.vertex_uv, ix); + } + + vert.position.x = xx pos.x; + vert.position.y = xx pos.y; + vert.position.z = xx pos.z; + vert.normal.x = xx normal.x; + vert.normal.y = xx normal.y; + vert.normal.z = xx normal.z; + vert.normal = normalize(vert.normal); + vert.uv.x = xx uv.x; + vert.uv.y = xx uv.y; + + if skin { + skin_vertices[num_indices] = mesh_skin_vertices[fbx_mesh.vertex_indices.data[ix]]; + } + + num_indices += 1; + } + } + + num_streams : u64 = 1; + streams : [2] ufbx_vertex_stream; + streams[0].data = vertices.data; + streams[0].vertex_size = size_of(Mesh_Vertex); + + if skin { + num_streams = 2; + streams[1].data = skin_vertices.data; + streams[1].vertex_size = size_of(Skin_Vertex); + } + + error : ufbx_error; + num_vertices := ufbx_generate_indices(streams.data, num_streams, indices.data, num_indices, null, *error); + + if error.type != .UFBX_ERROR_NONE { + log_error("Failed to generate index buffer\n"); + } + + array_reserve(*mesh.indices, xx num_indices); + for 0..num_indices-1 { + array_add(*mesh.indices, cast(u32)indices[it]); + } + + array_reserve(*mesh.positions, xx num_vertices); + array_reserve(*mesh.normals, xx num_vertices); + array_reserve(*mesh.texcoords, xx num_vertices); + + if skin { + array_reserve(*mesh.skin_data, xx num_vertices); + } + + for 0..num_vertices-1 { + v := vertices[it]; + array_add(*mesh.positions, v.position); + array_add(*mesh.normals, v.normal); + array_add(*mesh.texcoords, v.uv); + + if skin { + array_add(*mesh.skin_data, skin_vertices[it]); + } + } + + // Generate tangents + bitangents + if mesh.texcoords.count > 0 { + array_resize(*mesh.tangents, mesh.positions.count); + array_resize(*mesh.bitangents, mesh.positions.count); + + i : u64 = 0; + while i < num_indices - 1 { + defer i += 3; + i0 := mesh.indices[i+0]; + i1 := mesh.indices[i+1]; + i2 := mesh.indices[i+2]; + + pos0 := mesh.positions[i0]; + pos1 := mesh.positions[i1]; + pos2 := mesh.positions[i2]; + + uv0 := mesh.texcoords[i0]; + uv1 := mesh.texcoords[i1]; + uv2 := mesh.texcoords[i2]; + + edge1 := pos1 - pos0; + edge2 := pos2 - pos0; + delta_uv1 := uv1 - uv0; + delta_uv2 := uv2 - uv0; + + f := 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y); + + tangent : Vector3 = ---; + bitangent : Vector3 = ---; + tangent.x = f * (delta_uv2.y * edge1.x - delta_uv1.y * edge2.x); + tangent.y = f * (delta_uv2.y * edge1.y - delta_uv1.y * edge2.y); + tangent.z = f * (delta_uv2.y * edge1.z - delta_uv1.y * edge2.z); + + bitangent.x = f * (-delta_uv2.x * edge1.x + delta_uv1.x * edge2.x); + bitangent.y = f * (-delta_uv2.x * edge1.y + delta_uv1.x * edge2.y); + bitangent.z = f * (-delta_uv2.x * edge1.z + delta_uv1.x * edge2.z); + + mesh.tangents[i0] = tangent; + mesh.tangents[i1] = tangent; + mesh.tangents[i2] = tangent; + mesh.bitangents[i0] = bitangent; + mesh.bitangents[i1] = bitangent; + mesh.bitangents[i2] = bitangent; + } + } + + ib_size := size_of(u32)*mesh.indices.count; + mesh.ib = create_index_buffer(renderer, mesh.indices.data, xx ib_size); + + //material : Base_Material; + + //if mesh_mat.material != null { + // for 0..mesh_mat.material.props.props.count-1 { + // prop := mesh_mat.material.props.props.data[it]; + // prop_name := to_string(prop.name.data); + // if prop_name == "DiffuseColor" { + // material.base_color.x = xx prop.value_vec3.x; + // material.base_color.y = xx prop.value_vec3.y; + // material.base_color.z = xx prop.value_vec3.z; + // material.base_color.w = 1.0; + // } + // } + //} + + array_add(*node.meshes, parray_add(*renderer.meshes, mesh)); + array_add(*node.material_defaults, model.materials[mesh_mat.material.typed_id]); // @Incomplete + } + } + + array_reserve(*node.children, xx fbx_node.children.count); + for 0..cast(s32)fbx_node.children.count-1 { + array_add(*node.children, cast(Node_Handle)fbx_node.children.data[it].typed_id + 1); + } + + array_add(*model.nodes, node); +} + +ufbx_to_v3 :: (ufbx_v3: ufbx_vec3) -> Vector3 { + v : Vector3 = ---; + v.x = cast(float)ufbx_v3.x; + v.y = cast(float)ufbx_v3.y; + v.z = cast(float)ufbx_v3.z; + return v; +} + +ufbx_to_quat :: (ufbx_q: ufbx_quat) -> Quaternion { + q : Quaternion = ---; + q.x = cast(float)ufbx_q.x; + q.y = cast(float)ufbx_q.y; + q.z = cast(float)ufbx_q.z; + q.w = cast(float)ufbx_q.w; + return q; +} + +ufbx_to_mat4 :: (m: ufbx_matrix) -> Matrix4 { + result : Matrix4; + + result._11 = cast(float)m.m00; + result._12 = cast(float)m.m01; + result._13 = cast(float)m.m02; + result._14 = cast(float)m.m03; + + result._21 = cast(float)m.m10; + result._22 = cast(float)m.m11; + result._23 = cast(float)m.m12; + result._24 = cast(float)m.m13; + + result._31 = cast(float)m.m20; + result._32 = cast(float)m.m21; + result._33 = cast(float)m.m22; + result._34 = cast(float)m.m23; + + result._41 = 0.0; + result._42 = 0.0; + result._43 = 0.0; + result._44 = 1.0; + + return result; +} + +v3_equal :: (v1: Vector3, v2: Vector3) -> bool { + return abs(v1.x - v2.x) < 0.0001 && abs(v1.y - v2.y) < 0.0001 && abs(v1.z - v2.z) < 0.0001; +} + +quat_equal :: (q1: Quaternion, q2: Quaternion) -> bool { + return abs(q1.x - q2.x) < 0.0001 && abs(q1.y - q2.y) < 0.0001 && abs(q1.z - q2.z) < 0.0001 && abs(q1.w - q2.w) < 0.0001; +} + + +parse_node_anim :: (animation: *Animation, node_anim: *Node_Animation, stack: *ufbx_anim_stack, node: *ufbx_node) { + array_resize(*node_anim.rotation, animation.num_frames); + array_resize(*node_anim.position, animation.num_frames); + array_resize(*node_anim.scale, animation.num_frames); + + const_rotation := true; + const_position := true; + const_scale := true; + + if animation.num_frames == 0 { + node_anim.const_rotation = .{0,0,0,1}; + node_anim.const_position = .{0,0,0}; + node_anim.const_scale = .{1,1,1}; + } else { + // Sample the node's transform evenly for the whole animation stack duration + for i: 0..animation.num_frames-1 { + time := stack.time_begin + cast(float64)i / animation.framerate; + + transform := ufbx_evaluate_transform(*stack.anim, node, time); + node_anim.rotation[i] = ufbx_to_quat(transform.rotation); + node_anim.position[i] = ufbx_to_v3(transform.translation); + node_anim.scale[i] = ufbx_to_v3(transform.scale); + + if i > 0 { + // Negated quaternions are equivalent, but interpolating between ones of different + // polarity takes a the longer path, so flip the quaternion if necessary. + if dot(node_anim.rotation[i], node_anim.rotation[i - 1]) < 0.0 { + node_anim.rotation[i].x = -node_anim.rotation[i].x; + node_anim.rotation[i].y = -node_anim.rotation[i].y; + node_anim.rotation[i].z = -node_anim.rotation[i].z; + node_anim.rotation[i].w = -node_anim.rotation[i].w; + } + + // Keep track of which channels are constant for the whole animation as an optimization + if !quat_equal(node_anim.rotation[i - 1], node_anim.rotation[i]) const_rotation = false; + if !v3_equal(node_anim.position[i - 1], node_anim.position[i]) const_position = false; + if !v3_equal(node_anim.scale[i - 1], node_anim.scale[i]) const_scale = false; + } + } + + if const_rotation { node_anim.const_rotation = node_anim.rotation[0]; array_free(node_anim.rotation); node_anim.rotation.count = 0; } + if const_position { node_anim.const_position = node_anim.position[0]; array_free(node_anim.position); node_anim.position.count = 0; } + if const_scale { node_anim.const_scale = node_anim.scale[0]; array_free(node_anim.scale); node_anim.scale.count = 0; } + } + + node_anim.is_set = true; +} + +parse_anim_stack :: (animation: *Animation, stack: *ufbx_anim_stack, scene: *ufbx_scene, model: *Model) { + animation.name = copy_string(to_string(stack.name.data)); + animation.framerate = 24.0; + animation.duration = cast(float)(stack.time_end - stack.time_begin); + animation.num_frames = cast(s32)(animation.duration * animation.framerate); + animation.framerate = cast(float)(animation.num_frames - 1) / animation.duration; + + array_resize(*animation.nodes, xx model.nodes.count); + + if scene.nodes.count != xx model.nodes.count { + for 0..scene.nodes.count-1 { + scene_node := scene.nodes.data[it]; + for j: 0..model.nodes.count-1 { + model_node := model.nodes[j]; + if to_string(scene_node.name.data) == model_node.name { + animation.nodes[j] = .{}; + parse_node_anim(animation, *animation.nodes[j], stack, scene_node); + break; + } + } + } + + for 0..model.nodes.count-1 { + if !animation.nodes[it].is_set { + animation.nodes[it].const_position = model.nodes[it].transform.position; + animation.nodes[it].const_rotation = model.nodes[it].transform.orientation; + animation.nodes[it].const_scale = model.nodes[it].transform.scale; + animation.nodes[it].is_set = true; + } + } + + } else { + for 0..scene.nodes.count-1 { + parse_node_anim(animation, *animation.nodes[it], stack, scene.nodes.data[it]); + } + } +} + +load_fbx_animations_into_existing_model :: (path: string, model: *Model) { + // Load the file as normal, but only loop through the animations + opts : ufbx_load_opts = .{}; + opts.load_external_files = true; + opts.evaluate_skinning = true; + opts.target_axes.right = .POSITIVE_X; + opts.target_axes.up = .POSITIVE_Y; + opts.target_axes.front = .NEGATIVE_Z; + opts.target_unit_meters = 1.0; + //opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS; + + error : ufbx_error; + scene := ufbx_load_file(to_temp_c_string(path), *opts, *error); + + if scene == null { + log_error("FBX '%' could not be loaded\n", path); + } + + // The rig and nodes should be the same as the specified model + //if cast(s32)scene.nodes.count != model.nodes.count { + // log_error("Node count for '%' (%) not the same as original model (%)\n", path, scene.nodes.count, model.nodes.count); + // ufbx_free_scene(scene); + // return; + //} + + for 0..scene.anim_stacks.count-1 { + animation : Animation; + parse_anim_stack(*animation, scene.anim_stacks.data[it], scene, model); + array_add(*model.animations, animation); + } + + ufbx_free_scene(scene); +} + +get_model_by_path :: (path: string) -> *Model { + for * renderer.model_lib { + if it.path == path { + return it; + } + } + + return null; +} + +load_fbx_texture :: (map: ufbx_material_map, format: Format) -> Texture_Handle { + if map.texture != null && map.texture.content.size > 0 { + return load_texture_from_data(renderer, map.texture.content.data, map.texture.content.size, format=format); + } + + return 0; +} + +load_fbx :: (path: string) -> *Model, bool { + opts : ufbx_load_opts = .{}; + opts.load_external_files = true; + opts.evaluate_skinning = true; + opts.target_axes.right = .POSITIVE_X; + opts.target_axes.up = .POSITIVE_Y; + opts.target_axes.front = .NEGATIVE_Z; + opts.target_unit_meters = 1.0; + //opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS; + + error : ufbx_error; + scene := ufbx_load_file(to_temp_c_string(path), *opts, *error); + + if scene == null { + log_error("FBX '%' could not be loaded", path); + return null, false; + } + + model, locator := find_and_occupy_empty_slot(*renderer.model_lib); + model.path = copy_string(path); + model.name = copy_string(path); + + // Materials + for i: 0..scene.materials.count - 1 { + mat := scene.materials.data[i]; + model_material : Model_Material; + model_material.textures.base_color = load_fbx_texture(mat.pbr.base_color, format=.R8G8B8A8_UNORM_SRGB); + model_material.textures.normal = load_fbx_texture(mat.pbr.normal_map, format=.R8G8B8A8_UNORM); + + for 0..mat.props.props.count-1 { + prop := mat.props.props.data[it]; + prop_name := to_string(prop.name.data,, allocator=temp); + if prop_name == "DiffuseColor" { + model_material.base_color.x = xx prop.value_vec3.x; + model_material.base_color.y = xx prop.value_vec3.y; + model_material.base_color.z = xx prop.value_vec3.z; + model_material.base_color.w = 1.0; + } + } + + //mat.pbr.base_factor; + //create_texture :: (using renderer: *Renderer, data: *void, width: u32, height: u32, channels: u32, path: string = "", generate_mips: bool = true) -> Texture_Handle { + //Material &dst = materials[i + 1]; + //dst.base_factor.value.x = 1.0f; + //setup_texture(dst.base_factor, mat->pbr.base_factor); + //setup_texture(dst.base_color, mat->pbr.base_color); + //setup_texture(dst.roughness, mat->pbr.roughness); + //setup_texture(dst.metallic, mat->pbr.metalness); + //setup_texture(dst.emission_factor, mat->pbr.emission_factor); + //setup_texture(dst.emission_color, mat->pbr.emission_color); + //dst.base_color.image.srgb = true; + //dst.emission_color.image.srgb = true; + array_add(*model.materials, model_material); + } + + array_reserve(*model.nodes, xx scene.nodes.count); + + zero_handle : Node_Handle = 0; + + for i: 0..scene.nodes.count-1 { + node := scene.nodes.data[i]; + parse_fbx_node(model, node); + } + + // Load animations + array_resize(*model.animations, xx scene.anim_stacks.count); + for 0..model.animations.count-1 { + parse_anim_stack(*model.animations[it], scene.anim_stacks.data[it], scene, model); + } + + ufbx_free_scene(scene); + + return model, false; +} + +get_or_load_model :: (path: string) -> *Model { + for * renderer.model_lib { + if it.path == path { + return it; + } + } + + return load_fbx(path); +} + +#import "ufbx"; diff --git a/renderer/render_graph.jai b/renderer/render_graph.jai new file mode 100644 index 0000000..4335e3e --- /dev/null +++ b/renderer/render_graph.jai @@ -0,0 +1,140 @@ +Render_Pass_Handle :: #type, distinct u32; + + +Pass_Buffer_Info :: struct { + shader_type: Shader_Type; + slot: u32; + buffer: Buffer_Handle; +} + +Render_Graph :: struct { + temp_sampler: Sampler_Handle; + render_passes: PArray(Render_Pass, Render_Pass_Handle); +} + +new_render_graph :: () -> *Render_Graph { + graph := New(Render_Graph); + return graph; +} + +execute_render_graph :: (graph: *Render_Graph) { + for graph.render_passes { + execute_render_pass(it); + } +} + +push_material_pass_properties :: (render_pass: Render_Pass, mat_pass: Material_Pass_Old) { + for mat_pass.properties { + if it.parameter.type == { + case .BUFFER; + push_cmd_set_constant_buffer(renderer, it.parameter.slot, it.buffer, it.parameter.shader); + case .SAMPLER; + push_cmd_set_sampler(renderer, it.parameter.slot, it.sampler); + case .TEXTURE; + { + is_texture_input := false; + input_index := 0; + + if it.parameter.mapping == { + case .SHADER_ATTACHMENT0; + is_texture_input = true; + input_index = 0; + case .SHADER_ATTACHMENT1; + is_texture_input = true; + input_index = 1; + case .SHADER_ATTACHMENT2; + is_texture_input = true; + input_index = 2; + case .SHADER_ATTACHMENT3; + is_texture_input = true; + input_index = 3; + } + + if is_texture_input { + input := render_pass.inputs[input_index]; + owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle); + + if input.rt_index == DEPTH_STENCIL_SLOT { + push_cmd_set_texture(renderer, it.parameter.slot, owning_pass.depth_stencil); + } else { + push_cmd_set_texture(renderer, it.parameter.slot, owning_pass.render_targets[input.rt_index]); + } + } else { + if it.texture > 0 { + push_cmd_set_texture(renderer, it.parameter.slot, it.texture); + } + } + } + + } + } +} + +setup_pass_inputs :: (render_pass: Render_Pass) { + for input: render_pass.inputs { + owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle); + + if input.rt_index == DEPTH_STENCIL_SLOT { + push_cmd_set_texture(renderer, xx it_index, owning_pass.depth_stencil); + } else { + push_cmd_set_texture(renderer, xx it_index, owning_pass.render_targets[input.rt_index]); + } + } +} + +set_render_pass_clear_color :: (rp: Render_Pass_Handle, input_index: s32, color: Color) { + pass := parray_get(*renderer.render_graph.render_passes, rp); + pass.clear_colors[input_index] = color; +} + +execute_render_pass :: (render_pass: Render_Pass) { // @Incomplete: Add command buffer as parameter + if render_pass.render_targets.count == 0 { + if render_pass.has_depth_stencil { + push_cmd_set_render_targets(renderer, depth_stencil_enabled=render_pass.has_depth_stencil, depth_stencil_buffer=render_pass.depth_stencil); + push_cmd_clear_depth_stencil(renderer, render_pass.depth_stencil, 1.0); + width := ifx render_pass.width == SWAPCHAIN_SIZE then renderer.render_target_width else render_pass.width; + height := ifx render_pass.height == SWAPCHAIN_SIZE then renderer.render_target_height else render_pass.height; + push_cmd_set_viewport(renderer, width, height); + } else { + assert(render_pass.uses_backbuffer); + push_cmd_set_backbuffer(renderer); + + color: Vector4; + color.x = 0.0; + color.y = 0.0; + color.z = 0.0; + color.w = 1.0; + push_cmd_clear_backbuffer(renderer, color); + push_cmd_set_viewport(renderer, xx renderer.render_target_width, xx renderer.render_target_height); + } + } else { + + width := render_pass.width; + height := render_pass.height; + + if render_pass.width == SWAPCHAIN_SIZE { + width = renderer.render_target_width; + } + + if render_pass.height == SWAPCHAIN_SIZE { + height = renderer.render_target_height; + } + push_cmd_set_render_targets(renderer, ..render_pass.render_targets, render_pass.has_depth_stencil, render_pass.depth_stencil); + push_cmd_set_viewport(renderer, width, height); + + for render_pass.render_targets { + push_cmd_clear_render_target(renderer, it, render_pass.clear_colors[it_index]); + } + + if render_pass.has_depth_stencil { + push_cmd_clear_depth_stencil(renderer, render_pass.depth_stencil, 1.0); + } + } + + // @NOCHECKIN Setup inputs! + + setup_pass_inputs(render_pass); + render_pass.callback(render_pass); +} + +#load "render_pass.jai"; diff --git a/renderer/render_pass.jai b/renderer/render_pass.jai new file mode 100644 index 0000000..ecf389f --- /dev/null +++ b/renderer/render_pass.jai @@ -0,0 +1,97 @@ +DEPTH_STENCIL_SLOT :: 42; + +// The slots used by the render pass render targets +// Slots 10-17 are reserved for the passes +Render_Target_Shader_Slots :: enum { + SLOT0 :: 10; + SLOT1 :: 11; + SLOT2 :: 12; + SLOT3 :: 13; + SLOT4 :: 14; + SLOT5 :: 15; + SLOT6 :: 16; + SLOT7 :: 17; +} + +Render_Target_Info :: struct { + format: Format; + width: u32; + height: u32; + + clear_color: Color; +} + +Depth_Stencil_Info :: struct { + enabled: bool; + format: Format; + width: u32; + height: u32; +} + +Render_Pass_Input_Info :: struct { + pass_handle: Render_Pass_Handle; + rt_index: u32; // the index into the render pass +} + +Render_Pass :: struct { + name: string; + + inputs: [..] Render_Pass_Input_Info; + + uses_backbuffer: bool; + render_targets: [..] Render_Target_Handle; + clear_colors: [..] Color; + depth_stencil: Depth_Stencil_Buffer_Handle; + has_depth_stencil: bool; + + width: u32; + height: u32; + + callback: (pass: Render_Pass); +} + +create_render_pass :: (graph: *Render_Graph, name: string, callback: (Render_Pass), render_target_infos: [] Render_Target_Info = .[], depth_stencil_info : Depth_Stencil_Info = .{}, uses_backbuffer: bool = false) -> Render_Pass_Handle { + render_pass : Render_Pass; + render_pass.name = copy_string(name); + render_pass.uses_backbuffer = uses_backbuffer; + render_pass.callback = callback; + + if depth_stencil_info.enabled { + render_pass.has_depth_stencil = true; + render_pass.depth_stencil = create_depth_stencil_buffer(depth_stencil_info.width, depth_stencil_info.height, 0); + render_pass.width = depth_stencil_info.width; + render_pass.height = depth_stencil_info.height; + } + + array_reserve(*render_pass.render_targets, render_target_infos.count); + array_reserve(*render_pass.clear_colors, render_target_infos.count); + + for render_target_infos { + array_add(*render_pass.render_targets, create_render_target(it.width, it.height, it.format)); + array_add(*render_pass.clear_colors, it.clear_color); + render_pass.width = it.width; + render_pass.height = it.height; + } + + return parray_add(*graph.render_passes, render_pass); +} + +add_render_pass_input :: (graph: *Render_Graph, to_pass: Render_Pass_Handle, from_pass: Render_Pass_Handle, rt_index: u32) { + info : Render_Pass_Input_Info; + info.pass_handle = from_pass; + info.rt_index = rt_index; + + pass := parray_get(*graph.render_passes, to_pass); + array_add(*pass.inputs, info); +} + +get_texture_from_pass :: (name: string) -> Texture_Handle { + for renderer.render_graph.render_passes { + if it.name == name { + rt := parray_get(renderer.render_targets, it.render_targets[0]); + return rt.texture; + } + } + + return xx 0; +} diff --git a/renderer/renderer.jai b/renderer/renderer.jai new file mode 100644 index 0000000..e70a951 --- /dev/null +++ b/renderer/renderer.jai @@ -0,0 +1,1630 @@ +renderer : *Renderer; + +Pipeline_State_Handle :: #type, distinct u32; +Shader_Handle :: #type, distinct u32; +Buffer_Handle :: #type, distinct u32; +Texture_Handle :: #type, distinct u32; +Sampler_Handle :: #type, distinct u32; +Render_Target_Handle :: #type, distinct u32; +Depth_Stencil_Buffer_Handle :: #type, distinct u32; + +Format :: enum { + UNKNOWN :: 0; + R32G32B32A32_TYPELESS :: 1; + R32G32B32A32_FLOAT :: 2; + R32G32B32A32_UINT :: 3; + R32G32B32A32_SINT :: 4; + R32G32B32_TYPELESS :: 5; + R32G32B32_FLOAT :: 6; + R32G32B32_UINT :: 7; + R32G32B32_SINT :: 8; + R16G16B16A16_TYPELESS :: 9; + R16G16B16A16_FLOAT :: 10; + R16G16B16A16_UNORM :: 11; + R16G16B16A16_UINT :: 12; + R16G16B16A16_SNORM :: 13; + R16G16B16A16_SINT :: 14; + R32G32_TYPELESS :: 15; + R32G32_FLOAT :: 16; + R32G32_UINT :: 17; + R32G32_SINT :: 18; + R32G8X24_TYPELESS :: 19; + D32_FLOAT_S8X24_UINT :: 20; + R32_FLOAT_X8X24_TYPELESS :: 21; + X32_TYPELESS_G8X24_UINT :: 22; + R10G10B10A2_TYPELESS :: 23; + R10G10B10A2_UNORM :: 24; + R10G10B10A2_UINT :: 25; + R11G11B10_FLOAT :: 26; + R8G8B8A8_TYPELESS :: 27; + R8G8B8A8_UNORM :: 28; + R8G8B8A8_UNORM_SRGB :: 29; + R8G8B8A8_UINT :: 30; + R8G8B8A8_SNORM :: 31; + R8G8B8A8_SINT :: 32; + R16G16_TYPELESS :: 33; + R16G16_FLOAT :: 34; + R16G16_UNORM :: 35; + R16G16_UINT :: 36; + R16G16_SNORM :: 37; + R16G16_SINT :: 38; + R32_TYPELESS :: 39; + D32_FLOAT :: 40; + R32_FLOAT :: 41; + R32_UINT :: 42; + R32_SINT :: 43; + R24G8_TYPELESS :: 44; + D24_UNORM_S8_UINT :: 45; + R24_UNORM_X8_TYPELESS :: 46; + X24_TYPELESS_G8_UINT :: 47; + R8G8_TYPELESS :: 48; + R8G8_UNORM :: 49; + R8G8_UINT :: 50; + R8G8_SNORM :: 51; + R8G8_SINT :: 52; + R16_TYPELESS :: 53; + R16_FLOAT :: 54; + D16_UNORM :: 55; + R16_UNORM :: 56; + R16_UINT :: 57; + R16_SNORM :: 58; + R16_SINT :: 59; + R8_TYPELESS :: 60; + R8_UNORM :: 61; + R8_UINT :: 62; + R8_SNORM :: 63; + R8_SINT :: 64; + A8_UNORM :: 65; + R1_UNORM :: 66; + R9G9B9E5_SHAREDEXP :: 67; + R8G8_B8G8_UNORM :: 68; + G8R8_G8B8_UNORM :: 69; + BC1_TYPELESS :: 70; + BC1_UNORM :: 71; + BC1_UNORM_SRGB :: 72; + BC2_TYPELESS :: 73; + BC2_UNORM :: 74; + BC2_UNORM_SRGB :: 75; + BC3_TYPELESS :: 76; + BC3_UNORM :: 77; + BC3_UNORM_SRGB :: 78; + BC4_TYPELESS :: 79; + BC4_UNORM :: 80; + BC4_SNORM :: 81; + BC5_TYPELESS :: 82; + BC5_UNORM :: 83; + BC5_SNORM :: 84; + B5G6R5_UNORM :: 85; + B5G5R5A1_UNORM :: 86; + B8G8R8A8_UNORM :: 87; + B8G8R8X8_UNORM :: 88; + R10G10B10_XR_BIAS_A2_UNORM :: 89; + B8G8R8A8_TYPELESS :: 90; + B8G8R8A8_UNORM_SRGB :: 91; + B8G8R8X8_TYPELESS :: 92; + B8G8R8X8_UNORM_SRGB :: 93; + BC6H_TYPELESS :: 94; + BC6H_UF16 :: 95; + BC6H_SF16 :: 96; + BC7_TYPELESS :: 97; + BC7_UNORM :: 98; + BC7_UNORM_SRGB :: 99; + AYUV :: 100; + Y410 :: 101; + Y416 :: 102; + NV12 :: 103; + P010 :: 104; + P016 :: 105; + //420_OPAQUE :: 106; + YUY2 :: 107; + Y210 :: 108; + Y216 :: 109; + NV11 :: 110; + AI44 :: 111; + IA44 :: 112; + P8 :: 113; + A8P8 :: 114; + B4G4R4A4_UNORM :: 115; + P208 :: 130; + V208 :: 131; + V408 :: 132; + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE; + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE; + FORCE_UINT :: 0xffffffff; +} + +SWAPCHAIN_SIZE :: 42000; + +Render_Target :: struct { + width: u32; + height: u32; + format: Format; + backend_render_target: Backend_Render_Target; + texture: Texture_Handle; + + actual_width: u32; + actual_height: u32; +} + +Depth_Stencil_Buffer :: struct { + width: u32; + height: u32; + format: Format; + + backend_depth_stencil_buffer: Backend_Depth_Stencil_Buffer; + + actual_width: u32; + actual_height: u32; +} + +Blend_Type :: enum { + OPAQUE; + TRANSPARENT; +} + +Shader_Type :: enum { + VERTEX; + PIXEL; +} + +Shader_Info :: struct { + input: [..] Vertex_Input; + parameters: [..] Shader_Parameter; +} + +Shader :: struct { + type : Shader_Type; + backend_shader : Backend_Shader; + defines : [..] string; + mesh_data_types : [..] Mesh_Vertex_Data_Type; + entry_point : string; + + info : Shader_Info; + + path: string; +} + +Primitive_Topology :: enum { + TRIANGLE_LIST; + LINE_LIST; + TRIANGLE_STRIP; + LINE_STRIP; +} + +Buffer_Type :: enum { + VERTEX; + INDEX; + CONSTANT; + STRUCTURED; +} + +Vertex_Data_Type :: enum { + POSITION; + POSITION2D; + POSITION1; + POSITION1_2D; + NORMAL; + COLOR; + COLOR_WITH_ALPHA; + COLOR_WITH_ALPHA8; + TEXCOORD0; + TEXCOORD1; + TEXCOORD2; + TEXCOORD3; + TEXCOORD4; + COLOR0; + + TANGENT0; + TANGENT1; + + INSTANCED_MAT0; + INSTANCED_MAT1; + INSTANCED_MAT2; + INSTANCED_MAT3; + + VERTEX_ID; + INSTANCE_ID; +} + +Vertex_Data_Info :: struct { + slot: s32; + type: Vertex_Data_Type; + instanced_step_rate: s32 = 0; +} + +Vertex_Input :: struct { + format : Format; + mesh_data_type : Mesh_Vertex_Data_Type; + slot: int; + semantic_name: string; + semantic_index: int; + instanced_step_rate: s32; +} + +Buffer :: struct { + type : Buffer_Type; + stride : u32; + backend_buffer : Backend_Buffer; +} + +Texture :: struct { + //format; + width: u32; + height: u32; + backend_texture: Backend_Texture; + format: Format; + + path: string; +} + +Sampling_Filter :: enum { + POINT; + LINEAR; + ANISOTROPIC; +} + +Wrap_Mode :: enum { + CLAMP; + REPEAT; +} + +Sampler :: struct { + filter: Sampling_Filter; + wrap_mode: Wrap_Mode; + backend_sampler : Backend_Sampler; +} + +Cull_Face :: enum { + NONE; + FRONT; + BACK; +} + +Shader_Parameter_Mapping :: enum { + NONE; + TIME; + MODEL_MATRIX; + MATERIAL; + CAMERA_DATA; + DIRECTIONAL_LIGHT; + POINT_LIGHTS; + BONE_MATRICES; + + BASE_COLOR_TEXTURE; + NORMAL_MAP; + + SCREEN_DATA; + + SHADER_ATTACHMENT0; + SHADER_ATTACHMENT1; + SHADER_ATTACHMENT2; + SHADER_ATTACHMENT3; + SHADER_ATTACHMENT4; + SHADER_ATTACHMENT5; + SHADER_ATTACHMENT6; + + REPEAT_SAMPLER; + CLAMP_SAMPLER; + + CUSTOM; +} + +Shader_Parameter_Type :: enum { + BUFFER; + TEXTURE; + SAMPLER; +} + +Shader_Property_Type :: enum { + INVALID; + + FLOAT; + FLOAT2; + FLOAT3; + FLOAT4; + + MATRIX3; + MATRIX4; + + INTEGER; + BOOL; + + TEXTURE; + SAMPLER; +} + +Shader_Property :: struct { + type : Shader_Property_Type; + name: string; + buffer_offset: u32; +} + +Shader_Parameter :: struct { + name: string; + type: Shader_Parameter_Type; + mapping: Shader_Parameter_Mapping; + mapping_str: string; + shader: Shader_Type; + + slot: u32; + + default_value : struct { + union { + buffer: Buffer_Handle; + texture: Texture_Handle; + sampler: Sampler_Handle; + } + } + + size: u32; + + // Only for buffers + properties: [16] Shader_Property; + num_properties: s64; +} + +Pipeline_State :: struct { + vs : Shader_Handle; + ps : Shader_Handle; + mesh_data_types : [..] Mesh_Vertex_Data_Type; + + vertex_layout : Backend_Vertex_Input; + blend_state : Backend_Blend_State; + + blend_type : Blend_Type; + + shader_input: : [..] Vertex_Data_Info; + shader_parameters : [..] Shader_Parameter; + + is_new : bool; +} + +Material_Property_Old :: struct { + parameter : Shader_Parameter; + + union { + buffer: Buffer_Handle; + texture: Texture_Handle; + sampler: Sampler_Handle; + } +} + +Material_Pass_Old :: struct { + pass: Render_Pass_Handle; + pipeline: Pipeline_State_Handle; + properties: [..] Material_Property_Old; +} + +Base_Material :: struct { + base_color : Vector4; +} + +Material_Old :: struct { + passes: [..] Material_Pass_Old; + + depth_write : bool = true; + cull_face : Cull_Face = .BACK; +} + +Draw_Mode :: enum { + FILL; + WIREFRAME; +} + +Render_Command_Type :: enum { + SET_PIPELINE_STATE; + SET_VERTEX_BUFFER; + SET_INDEX_BUFFER; + SET_CONSTANT_BUFFER; + SET_STRUCTURED_BUFFER; + SET_TEXTURE; + SET_TEXTURE_FROM_RENDER_TARGET; + SET_TEXTURE_FROM_DEPTH_STENCIL; + SET_RENDER_TARGETS; + SET_BACKBUFFER; + SET_SAMPLER; + SET_VIEWPORT; + CLEAR_RENDER_TARGET; + CLEAR_DEPTH_STENCIL; + CLEAR_BACKBUFFER; + DRAW; + DRAW_INDEXED; + DRAW_INSTANCED; + DRAW_INDEXED_INSTANCED; + SET_DRAW_MODE; + SET_CULL_FACE; + SET_DEPTH_WRITE; +} + +MAX_RENDER_TARGETS :: 8; + +Render_Command :: struct { + type : Render_Command_Type; + + union { + set_pipeline_state : struct { + vs: Backend_Shader; + ps: Backend_Shader; + vertex_layout: Backend_Vertex_Input; + blend_type: Blend_Type; + blend_state: Backend_Blend_State; + } + set_vertex_buffer : struct { + buffer: Backend_Buffer; + stride: u32; + offset: u32; + start_slot: u32; + } + set_index_buffer : struct { + buffer: Backend_Buffer; + } + set_constant_buffer : struct { + buffer : Backend_Buffer; + slot : u32; + shader_type : Shader_Type; + } + set_structured_buffer : struct { + buffer : Backend_Buffer; + slot : u32; + shader_type : Shader_Type; + } + set_texture : struct { + texture : Backend_Texture; + slot : u32; + } + set_texture_from_rt : struct { + rt : Backend_Render_Target; + slot : u32; + } + set_texture_from_ds : struct { + ds : Backend_Depth_Stencil_Buffer; + slot : u32; + } + set_render_targets : struct { + render_targets: [MAX_RENDER_TARGETS] Backend_Render_Target; + num_render_targets: u32; + depth_stencil_enabled: bool; + depth_stencil_buffer: Backend_Depth_Stencil_Buffer; + } + set_backbuffer : struct { + } + set_sampler : struct { + sampler : Backend_Sampler; + slot : u32; + } + set_viewport : struct { + width: u32; + height: u32; + x: u32; + y: u32; + min_depth: float; + max_depth: float = 1.0; + } + clear_render_target : struct { + rt: Backend_Render_Target; + color : Vector4; + } + clear_depth_stencil : struct { + ds: Backend_Depth_Stencil_Buffer; + depth: float; + } + clear_backbuffer : struct { + color : Vector4; + } + draw : struct { + topology : Primitive_Topology; + vertex_count : s64; + start_vertex_index : s64; + } + draw_instanced : struct { + topology : Primitive_Topology; + vertex_count : s64; + instance_count: u32; + start_vertex_index : s64; + start_instance_index : s64; + } + draw_indexed : struct { + topology : Primitive_Topology; + index_count : s64; + start_index : s64; + base_vertex : s64; + } + draw_indexed_instanced : struct { + topology : Primitive_Topology; + index_count : s64; + instance_count: u32; + start_index : s64; + base_vertex : s64; + } + set_draw_mode : struct { + draw_mode : Draw_Mode; + } + set_cull_face : struct { + cull_face : Cull_Face; + } + set_depth_write : struct { + enabled : bool; + } + } +} + +Render_Command_Buffer :: struct { + commands: [..] Render_Command; +} + +Material_Mapping_Info :: struct { + buffer: Buffer_Handle; + texture: Texture_Handle; +} + +Renderer :: struct { + pool : Flat_Pool; + allocator : Allocator; + backend : *Graphics_Backend; + command_buffer : Render_Command_Buffer; + draw_call_count : s32; + last_draw_call_count : s32; + last_render_time_cpu : float; + last_render_time_gpu : float; + shaders : [..] Shader; + buffers : PArray(Buffer, Buffer_Handle); + textures : PArray(Texture, Texture_Handle); + render_targets : PArray(Render_Target, Render_Target_Handle); + depth_stencil_buffers : PArray(Depth_Stencil_Buffer, Depth_Stencil_Buffer_Handle); + meshes : PArray(Mesh, Mesh_Handle); + samplers : [..] Sampler; + pipeline_states : [..] Pipeline_State; + + fonts : [..] Font; + + render_graph : *Render_Graph; + model_lib : Bucket_Array(Model, 128); + + callbacks : struct { + get_custom_material_parameter_mapping: (mapping_str: string) -> bool, Material_Mapping_Info; + } + + current_state : struct { + last_set_pipeline : Pipeline_State_Handle; + } + + // Text buffers + max_characters_per_buffer :: 256; + used_text_buffers_count: u32; + text_render_buffers : [..] Buffer_Handle; + + render_target_width: u32; + render_target_height: u32; + + default_meshes : struct { + plane : Mesh_Handle; + cube : Mesh_Handle; + sphere : Mesh_Handle; + capsule : Mesh_Handle; + fullscreen_plane : Mesh_Handle; + textured_quad : Mesh_Handle; + + translation_gizmo : Mesh_Handle; + scale_gizmo : Mesh_Handle; + rotation_gizmo : Mesh_Handle; + } + + default_pipelines : struct { + message_text : Pipeline_State_Handle; + } + + default_samplers : struct { + repeat : Sampler_Handle; + clamp : Sampler_Handle; + } + + watcher: File_Watcher(string); + + vsync: bool = true; +} + +create_renderer :: (window: *Window) -> *Renderer { + renderer = New(Renderer); + renderer.pool = .{}; + a : Allocator; + a.proc = flat_pool_allocator_proc; + a.data = *renderer.pool; + renderer.allocator = a; + + if !init(*renderer.watcher, file_change_callback, *"Cowabunga", events_to_watch = .MODIFIED) { + log_error("Could not initialize watcher"); + } + + // @Incomplete + //if !add_directories(*renderer.watcher, "../assets/shaders") { + // exit(4); + //} + + + renderer.backend = create_backend(window); + renderer.render_target_width = window.width; + renderer.render_target_height = window.height; + + renderer.pipeline_states.allocator = renderer.allocator; + renderer.fonts.allocator = renderer.allocator; + renderer.textures.data.allocator = renderer.allocator; + renderer.textures.indices.allocator = renderer.allocator; + renderer.buffers.data.allocator = renderer.allocator; + renderer.buffers.indices.allocator = renderer.allocator; + renderer.shaders.allocator = renderer.allocator; + renderer.command_buffer.commands.allocator = renderer.allocator; + renderer.text_render_buffers.allocator = renderer.allocator; + renderer.render_targets.data.allocator = renderer.allocator; + renderer.render_targets.indices.allocator = renderer.allocator; + renderer.depth_stencil_buffers.data.allocator = renderer.allocator; + renderer.depth_stencil_buffers.indices.allocator = renderer.allocator; + + for 0..1024 { + buffer := create_vertex_buffer(renderer, null, size_of(Point) * 6 * renderer.max_characters_per_buffer, stride=size_of(Point), mappable=true); + array_add(*renderer.text_render_buffers, buffer); + } + + init_freetype(); + init_default_meshes(); + + array_reserve(*renderer.command_buffer.commands, 4096); + + renderer.render_graph = new_render_graph(); + + return renderer; +} + +init_default_pipelines :: () { + { + vs := create_vertex_shader(renderer, "../modules/Coven/shaders/font.hlsl", "VS"); + ps := create_pixel_shader(renderer, "../modules/Coven/shaders/font.hlsl", "PS"); + + layout : [3] Vertex_Data_Info; + layout[0] = .{0,.POSITION2D, 0}; + layout[1] = .{0,.TEXCOORD0, 0}; + layout[2] = .{0,.COLOR_WITH_ALPHA, 0}; + + params : [2] Shader_Parameter; + params[0].shader = .PIXEL; + params[0].type = .SAMPLER; + params[0].name = "ss"; + params[0].slot = 0; + params[0].mapping = .CLAMP_SAMPLER; + + params[1].shader = .PIXEL; + params[1].type = .TEXTURE; + params[1].name = "tex"; + params[1].slot = 1; + + renderer.default_pipelines.message_text = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } +} + +init_default_meshes :: () { + { + // Default plane + // Since this is used for sprites, we want the origin to be in the center in x, but at the bottom in y + mesh : Mesh; + mesh.name = copy_string("Plane"); + array_add(*mesh.positions, .{1, 0, 1}); + array_add(*mesh.positions, .{-1, 0, -1}); + array_add(*mesh.positions, .{1, 0, -1}); + array_add(*mesh.positions, .{-1, 0, 1}); + + array_add(*mesh.normals, .{0, 1, 0}); + array_add(*mesh.normals, .{0, 1, 0}); + array_add(*mesh.normals, .{0, 1, 0}); + array_add(*mesh.normals, .{0, 1, 0}); + + array_add(*mesh.texcoords, .{1, 0}); + array_add(*mesh.texcoords, .{0, 1}); + array_add(*mesh.texcoords, .{1, 1}); + array_add(*mesh.texcoords, .{0, 0}); + + array_add(*mesh.indices, 2); + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 0); + + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 3); + array_add(*mesh.indices, 0); + + stride := size_of(float)*8; + vb_size := stride*mesh.positions.count; + ib_size := size_of(u32)*mesh.indices.count; + + //mesh.vb = create_vertex_buffer(renderer, vertices.data, xx vb_size, stride=xx stride); + mesh.ib = create_index_buffer(renderer, mesh.indices.data, xx ib_size); + + renderer.default_meshes.plane = parray_add(*renderer.meshes, mesh); + } + { + // Fullscreeboat_n plane + mesh : Mesh; + array_add(*mesh.positions, .{1, 1, 0}); + array_add(*mesh.positions, .{-1, -1, 0}); + array_add(*mesh.positions, .{1, -1, 0}); + array_add(*mesh.positions, .{-1, 1, 0}); + + array_add(*mesh.texcoords, .{1, 0}); + array_add(*mesh.texcoords, .{0, 1}); + array_add(*mesh.texcoords, .{1, 1}); + array_add(*mesh.texcoords, .{0, 0}); + + array_add(*mesh.indices, 2); + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 0); + + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 3); + array_add(*mesh.indices, 0); + + ib_size := size_of(u32)*mesh.indices.count; + + mesh.ib = create_index_buffer(renderer, mesh.indices.data, xx ib_size); + + renderer.default_meshes.fullscreen_plane = parray_add(*renderer.meshes, mesh); + } + + { + // Textured quad + mesh : Mesh; + array_add(*mesh.positions, .{0.5, 0.5, 0}); + array_add(*mesh.positions, .{-0.5, -0.5, 0}); + array_add(*mesh.positions, .{0.5, -0.5, 0}); + array_add(*mesh.positions, .{-0.5, 0.5, 0}); + + array_add(*mesh.texcoords, .{1, 0}); + array_add(*mesh.texcoords, .{0, 1}); + array_add(*mesh.texcoords, .{1, 1}); + array_add(*mesh.texcoords, .{0, 0}); + + array_add(*mesh.indices, 2); + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 0); + + array_add(*mesh.indices, 1); + array_add(*mesh.indices, 3); + array_add(*mesh.indices, 0); + + ib_size := size_of(u32)*mesh.indices.count; + + mesh.ib = create_index_buffer(renderer, mesh.indices.data, xx ib_size); + + renderer.default_meshes.textured_quad = parray_add(*renderer.meshes, mesh); + } + + renderer.default_samplers.repeat = create_sampler(renderer, wrap_mode = .REPEAT); + renderer.default_samplers.clamp = create_sampler(renderer, wrap_mode = .CLAMP); + + //sphere_model := load_fbx("../modules/Coven/models/sphere.fbx"); + + //sphere_mesh, success := get_first_mesh_from_model(sphere_model); + //assert(success); + //renderer.default_meshes.sphere = sphere_mesh; + + cube_model := load_fbx("../assets/models/cube.fbx"); + + cube_mesh, success := get_first_mesh_from_model(cube_model); + assert(success); + renderer.default_meshes.cube = cube_mesh; + + //translation_arrow_model := load_fbx("../modules/Coven/models/translation_arrow.fbx"); + //translation_arrow_mesh :, success = get_first_mesh_from_model(translation_arrow_model); + //assert(success); + //renderer.default_meshes.translation_gizmo = translation_arrow_mesh; + + //scale_gizmo_model := load_fbx("../modules/Coven/models/scale_gizmo.fbx"); + //scale_gizmo_mesh :, success = get_first_mesh_from_model(scale_gizmo_model); + //assert(success); + //renderer.default_meshes.scale_gizmo = scale_gizmo_mesh; + + //rotation_gizmo_model := load_fbx("../modules/Coven/models/rotation_gizmo.fbx"); + //rotation_gizmo_mesh :, success = get_first_mesh_from_model(rotation_gizmo_model); + //assert(success); + //renderer.default_meshes.rotation_gizmo = rotation_gizmo_mesh; +} + +deinit_renderer :: (renderer: *Renderer) { + deinit_backend(renderer.backend); +} + +get_shader :: (handle: Shader_Handle) -> Shader { + assert(handle - 1 < xx renderer.shaders.count); + return renderer.shaders[handle-1]; +} + +create_shader :: (using renderer: *Renderer, path: string, entry_point: string, shader_type: Shader_Type, defines: [] string = .[], mesh_data_types: [] Mesh_Vertex_Data_Type = .[.POSITION, .NORMAL, .TEXCOORD]) -> Shader_Handle { + shader : Shader; + shader.type = shader_type; + shader.path = copy_string(path); + shader.entry_point = copy_string(entry_point); + + array_reserve(*shader.defines, defines.count); + for i: 0..defines.count-1 { + array_add(*shader.defines, copy_string(defines[i])); + } + + if shader_type == .VERTEX { + array_reserve(*shader.mesh_data_types, mesh_data_types.count); + for i: 0..mesh_data_types.count-1 { + array_add(*shader.mesh_data_types, mesh_data_types[i]); + } + } + + backend_shader, info, success := create_backend_shader(renderer.backend, path, entry_point, shader_type, defines); + + if success { + shader.info = info; + shader.backend_shader = backend_shader; + array_add(*shaders, shader); + return xx shaders.count; + } + + return 0; +} + +reload_shader :: (using renderer: *Renderer, shader: *Shader) { + backend_shader, success := reload_backend_shader(renderer.backend, shader.path, shader.entry_point, shader.type, shader.defines); + + if success { + destroy_shader(shader.*); + shader.backend_shader = backend_shader; + log("SHADER: Successfully reloaded shader '%'\n", shader.path); + } else { + log("SHADER: Compilation error when reloading shader '%'\n", shader.path); + } +} + +create_shader_from_source :: (using renderer: *Renderer, source: string, entry_point: string, shader_type: Shader_Type, defines: [] string = string.[], mesh_data_types: [] Mesh_Vertex_Data_Type = .[.POSITION, .NORMAL, .TEXCOORD]) -> Shader_Handle { + shader : Shader; + shader.type = shader_type; + + // lexer : Lexer; + // read_input_from_string(*lexer, source); + + // token := scan_next_token(*lexer); + // while token.type != .TOKEN_EOF { + // token = scan_next_token(*lexer); + // } + + if shader_type == .VERTEX { + array_reserve(*shader.mesh_data_types, mesh_data_types.count); + for i: 0..mesh_data_types.count-1 { + array_add(*shader.mesh_data_types, mesh_data_types[i]); + } + } + + backend_shader, info, success := create_backend_shader_from_source(renderer.backend, source, entry_point, shader_type, defines); + + if success { + shader.info = info; + shader.backend_shader = backend_shader; + array_add(*shaders, shader); + return xx shaders.count; + } + + return 0; +} + +create_vertex_shader :: #bake_arguments create_shader(shader_type=.VERTEX); +create_pixel_shader :: #bake_arguments create_shader(shader_type=.PIXEL); +create_vertex_shader_from_source :: #bake_arguments create_shader_from_source(shader_type=.VERTEX); +create_pixel_shader_from_source :: #bake_arguments create_shader_from_source(shader_type=.PIXEL); + +create_buffer :: (using renderer: *Renderer, data: *void, size: u32, type: Buffer_Type, stride: u32 = 0, mappable: bool = false) -> Buffer_Handle { + buffer : Buffer; + buffer.type = type; + buffer.stride = stride; + + if type == .VERTEX { + assert(stride > 0); + } + + backend_buffer, success := create_backend_buffer(renderer.backend, data, size, stride, type, mappable); + + if success { + buffer.backend_buffer = backend_buffer; + return parray_add(*buffers, buffer); + } + + return 0; +} + +create_vertex_buffer :: #bake_arguments create_buffer(type=.VERTEX); +create_index_buffer :: #bake_arguments create_buffer(type=.INDEX); +create_constant_buffer :: #bake_arguments create_buffer(type=.CONSTANT); +create_structured_buffer :: #bake_arguments create_buffer(type=.STRUCTURED); + +destroy_buffer :: (using renderer: *Renderer, handle: Buffer_Handle) { + assert(handle > 0); + buffer := parray_get(*buffers, handle); + + destroy_backend_buffer(renderer.backend, buffer.backend_buffer); + + parray_remove(*buffers, handle); +} + +create_texture :: (using renderer: *Renderer, path: string, generate_mips: bool = true, format: Format = .R8G8B8A8_UNORM_SRGB) -> Texture_Handle { + image_width : s32; + image_height : s32; + image_channels : s32; + image_desired_channels : s32 = 4; + + image_data := stbi_load(to_temp_c_string(path), *image_width, *image_height, *image_channels, image_desired_channels); + + data : [32] u8; + stbi_load_from_memory(data.data, 32, *image_width, *image_height, *image_channels, image_desired_channels); + + assert(image_data != null); + + defer stbi_image_free(image_data); + + return create_texture(renderer, image_data, xx image_width, xx image_height, xx image_desired_channels, path, generate_mips=generate_mips, format); +} + +create_texture :: (using renderer: *Renderer, data: *void, width: u32, height: u32, channels: u32, path: string = "", generate_mips: bool = true, format: Format = .R8G8B8A8_UNORM_SRGB) -> Texture_Handle { + texture : Texture; + texture.path = copy_string(path); + texture.width = width; + texture.height = height; + texture.format = format; + backend_texture, success := create_backend_texture(renderer.backend, data, width, height, channels, generate_mips, format); + + if success { + texture.backend_texture = backend_texture; + return parray_add(*textures, texture); + } + + assert(false); + return 0; +} + +load_texture_from_data :: (using renderer: *Renderer, data: *void, length: u64, generate_mips: bool = true, format: Format = .R8G8B8A8_UNORM_SRGB) -> Texture_Handle { + image_width : s32; + image_height : s32; + image_channels : s32; + image_desired_channels : s32 = 4; + + image_data := stbi_load_from_memory(data, xx length, *image_width, *image_height, *image_channels, image_desired_channels); + //image_data := stbi_load(to_temp_c_string(path), *image_width, *image_height, *image_channels, image_desired_channels); + + assert(image_data != null); + + defer stbi_image_free(image_data); + + return create_texture(renderer, image_data, xx image_width, xx image_height, xx image_desired_channels, "", generate_mips=generate_mips, format); +} + +destroy_texture :: (using renderer: *Renderer, handle: Texture_Handle) { + assert(handle > 0); + texture := parray_get(*textures, handle); + + destroy_backend_texture(renderer.backend, texture.backend_texture); + + parray_remove(*textures, handle); +} + +get_texture_size :: (using renderer: *Renderer, handle: Texture_Handle) -> u32, u32 { + assert(handle > 0); + texture := parray_get(*textures, handle); + return texture.width, texture.height; +} + +get_texture_path :: (using renderer: *Renderer, handle: Texture_Handle) -> string { + assert(handle > 0); + texture := parray_get(*textures, handle); + return texture.path; +} + +update_texture_region :: (using renderer: *Renderer, handle: Texture_Handle, xoffset: u32, yoffset: u32, width: u32, height: u32, pitch: u32, data: *void) { + assert(handle > 0); + texture := parray_get(*textures, handle); + update_backend_texture_region(renderer.backend, texture.backend_texture, xoffset, yoffset, width, height, pitch, data); +} + +create_sampler :: (using renderer: *Renderer, filter: Sampling_Filter = .LINEAR, wrap_mode: Wrap_Mode = .CLAMP) -> Sampler_Handle { + sampler : Sampler; + sampler.filter = filter; + sampler.wrap_mode = wrap_mode; + + backend_sampler, success := create_backend_sampler(renderer.backend, filter, wrap_mode); + + if success { + sampler.backend_sampler = backend_sampler; + array_add(*samplers, sampler); + return xx samplers.count; + } + + return 0; +} + +create_pipeline_state2 :: (using renderer: *Renderer, vs: Shader_Handle, ps: Shader_Handle, blend_type: Blend_Type = .OPAQUE) -> Pipeline_State_Handle { + pipeline_state : Pipeline_State; + pipeline_state.is_new = true; + pipeline_state.vs = vs; + pipeline_state.ps = ps; + + pipeline_state.blend_type = blend_type; + + if pipeline_state.blend_type == .TRANSPARENT { + pipeline_state.blend_state = create_backend_blend_state(renderer.backend, .TRANSPARENT); + } + + shader := *shaders[vs - 1]; + + pipeline_state.vertex_layout = create_backend_input_layout2(renderer.backend, shader.info.input, shader.backend_shader); + array_reserve(*pipeline_state.mesh_data_types, shader.mesh_data_types.count); + for type: shader.mesh_data_types { + array_add(*pipeline_state.mesh_data_types, type); + } + + array_add(*pipeline_states, pipeline_state); + + return xx pipeline_states.count; +} + +create_pipeline_state :: (using renderer: *Renderer, vs: Shader_Handle, ps: Shader_Handle, layout: [] Vertex_Data_Info, shader_parameters: [] Shader_Parameter, blend_type: Blend_Type = .OPAQUE) -> Pipeline_State_Handle { + pipeline_state : Pipeline_State; + pipeline_state.vs = vs; + pipeline_state.ps = ps; + + array_resize(*pipeline_state.shader_parameters, shader_parameters.count); + for shader_parameters { + pipeline_state.shader_parameters[it_index] = it; + } + + pipeline_state.blend_type = blend_type; + + if pipeline_state.blend_type == .TRANSPARENT { + pipeline_state.blend_state = create_backend_blend_state(renderer.backend, .TRANSPARENT); + } + + shader := *shaders[vs - 1]; + if layout.count > 0 { + pipeline_state.vertex_layout = create_backend_input_layout(renderer.backend, layout, shader.backend_shader); + } + + array_reserve(*pipeline_state.mesh_data_types, shader.mesh_data_types.count); + for type: shader.mesh_data_types { + array_add(*pipeline_state.mesh_data_types, type); + } + + array_add(*pipeline_states, pipeline_state); + + return xx pipeline_states.count; +} + +push_cmd_set_pipeline_state :: (using renderer: *Renderer, handle: Pipeline_State_Handle) { + assert(handle > 0); + + renderer.current_state.last_set_pipeline = handle; + + state := *pipeline_states[handle - 1]; + vs := *shaders[state.vs - 1]; + ps := *shaders[state.ps - 1]; + + command : Render_Command; + command.type = .SET_PIPELINE_STATE; + command.set_pipeline_state.vs = vs.backend_shader; + command.set_pipeline_state.ps = ps.backend_shader; + command.set_pipeline_state.vertex_layout = state.vertex_layout; + command.set_pipeline_state.blend_type = state.blend_type; + command.set_pipeline_state.blend_state = state.blend_state; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_vertex_buffer :: (using renderer: *Renderer, handle: Buffer_Handle, start_slot: u32 = 0) { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + + command : Render_Command; + command.type = .SET_VERTEX_BUFFER; + command.set_vertex_buffer.buffer = buffer.backend_buffer; + command.set_vertex_buffer.stride = buffer.stride; + command.set_vertex_buffer.start_slot = start_slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_index_buffer :: (using renderer: *Renderer, handle: Buffer_Handle) { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + + command : Render_Command; + command.type = .SET_INDEX_BUFFER; + command.set_index_buffer.buffer = buffer.backend_buffer; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_constant_buffer :: (using renderer: *Renderer, slot: u32, handle: Buffer_Handle, shader_type: Shader_Type) { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + + command : Render_Command; + command.type = .SET_CONSTANT_BUFFER; + command.set_constant_buffer.buffer = buffer.backend_buffer; + command.set_constant_buffer.shader_type = shader_type; + command.set_constant_buffer.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_structured_buffer :: (using renderer: *Renderer, slot: u32, handle: Buffer_Handle, shader_type: Shader_Type) { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + + command : Render_Command; + command.type = .SET_STRUCTURED_BUFFER; + command.set_structured_buffer.buffer = buffer.backend_buffer; + command.set_structured_buffer.shader_type = shader_type; + command.set_structured_buffer.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_texture :: (using renderer: *Renderer, slot: u32, handle: Texture_Handle) { + assert(handle > 0); + + texture := parray_get(*textures, handle); + + command : Render_Command; + command.type = .SET_TEXTURE; + command.set_texture.texture = texture.backend_texture; + command.set_texture.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_texture :: (using renderer: *Renderer, slot: u32, handle: Render_Target_Handle) { + assert(handle > 0); + + rt := parray_get(*render_targets, handle); + + command : Render_Command; + command.type = .SET_TEXTURE_FROM_RENDER_TARGET; + command.set_texture_from_rt.rt = rt.backend_render_target; + command.set_texture_from_rt.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_texture :: (using renderer: *Renderer, slot: u32, handle: Depth_Stencil_Buffer_Handle) { + assert(handle > 0); + + ds := parray_get(*depth_stencil_buffers, handle); + + command : Render_Command; + command.type = .SET_TEXTURE_FROM_DEPTH_STENCIL; + command.set_texture_from_ds.ds = ds.backend_depth_stencil_buffer; + command.set_texture_from_ds.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_render_targets :: (renderer: *Renderer, render_targets: ..Render_Target_Handle = .[], depth_stencil_enabled: bool = false, depth_stencil_buffer: Depth_Stencil_Buffer_Handle = 0) { + assert(render_targets.count <= MAX_RENDER_TARGETS); + + command : Render_Command; + command.type = .SET_RENDER_TARGETS; + + command.set_render_targets.num_render_targets = xx render_targets.count; + + for render_targets { + rt := parray_get(*renderer.render_targets, it); + command.set_render_targets.render_targets[it_index] = rt.backend_render_target; + } + + command.set_render_targets.depth_stencil_enabled = depth_stencil_enabled; + if depth_stencil_enabled { + ds := parray_get(*renderer.depth_stencil_buffers, depth_stencil_buffer); + command.set_render_targets.depth_stencil_buffer = ds.backend_depth_stencil_buffer; + } + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_backbuffer :: (renderer: *Renderer) { + command : Render_Command; + command.type = .SET_BACKBUFFER; + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_sampler :: (using renderer: *Renderer, slot: u32, handle: Sampler_Handle) { + assert(handle > 0); + + sampler := *samplers[handle - 1]; + + command : Render_Command; + command.type = .SET_SAMPLER; + command.set_sampler.sampler = sampler.backend_sampler; + command.set_sampler.slot = slot; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_viewport :: (using renderer: *Renderer, width: u32, height: u32, x: u32 = 0, y: u32 = 0, min_depth: float = 0.0, max_depth: float = 1.0) { + command : Render_Command; + command.type = .SET_VIEWPORT; + command.set_viewport.width = width; + command.set_viewport.height = height; + command.set_viewport.x = x; + command.set_viewport.y = y; + command.set_viewport.min_depth = min_depth; + command.set_viewport.max_depth = max_depth; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_clear_render_target :: (using renderer: *Renderer, rt: Render_Target_Handle, color: Vector4) { + command : Render_Command; + command.type = .CLEAR_RENDER_TARGET; + + render_target := parray_get(*renderer.render_targets, rt); + command.clear_render_target.rt = render_target.backend_render_target; + command.clear_render_target.color = color; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_clear_depth_stencil :: (using renderer: *Renderer, handle: Depth_Stencil_Buffer_Handle, depth: float) { + command : Render_Command; + command.type = .CLEAR_DEPTH_STENCIL; + + ds := parray_get(*renderer.depth_stencil_buffers, handle); + command.clear_depth_stencil.ds = ds.backend_depth_stencil_buffer; + command.clear_depth_stencil.depth = depth; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_clear_backbuffer :: (using renderer: *Renderer, color: Vector4) { + command : Render_Command; + command.type = .CLEAR_BACKBUFFER; + + command.clear_backbuffer.color = color; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_draw :: (using renderer: *Renderer, vertex_count: s64, start_vertex_index: s64 = 0, topology: Primitive_Topology = .TRIANGLE_LIST) { + renderer.draw_call_count += 1; + + command : Render_Command; + command.type = .DRAW; + command.draw.topology = topology; + command.draw.vertex_count = vertex_count; + command.draw.start_vertex_index = start_vertex_index; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_draw_instanced :: (using renderer: *Renderer, vertex_count: s64, instance_count: s64, start_vertex_index: s64 = 0, start_instance_index: s64 = 0, topology: Primitive_Topology = .TRIANGLE_LIST) { + renderer.draw_call_count += 1; + + command : Render_Command; + command.type = .DRAW_INSTANCED; + command.draw_instanced.topology = topology; + command.draw_instanced.vertex_count = vertex_count; + command.draw_instanced.instance_count = xx instance_count; + command.draw_instanced.start_vertex_index = start_vertex_index; + command.draw_instanced.start_instance_index = start_instance_index; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_draw_indexed :: (using renderer: *Renderer, index_count: s64, start_index: s64 = 0, base_vertex: s64 = 0, topology: Primitive_Topology = .TRIANGLE_LIST) { + renderer.draw_call_count += 1; + + command : Render_Command; + command.type = .DRAW_INDEXED; + command.draw_indexed.topology = topology; + command.draw_indexed.index_count = index_count; + command.draw_indexed.start_index = start_index; + command.draw_indexed.base_vertex = base_vertex; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_draw_indexed_instanced :: (using renderer: *Renderer, index_count: s64, instance_count: u32, start_index: s64 = 0, base_vertex: s64 = 0, topology: Primitive_Topology = .TRIANGLE_LIST) { + renderer.draw_call_count += 1; + + command : Render_Command; + command.type = .DRAW_INDEXED_INSTANCED; + command.draw_indexed_instanced.topology = topology; + command.draw_indexed_instanced.index_count = index_count; + command.draw_indexed_instanced.instance_count = instance_count; + command.draw_indexed_instanced.start_index = start_index; + command.draw_indexed_instanced.base_vertex = base_vertex; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_draw_mode :: (using renderer: *Renderer, draw_mode: Draw_Mode) { + command : Render_Command; + command.type = .SET_DRAW_MODE; + command.set_draw_mode.draw_mode = draw_mode; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_cull_face :: (using renderer: *Renderer, cull_face: Cull_Face) { + command : Render_Command; + command.type = .SET_CULL_FACE; + command.set_cull_face.cull_face = cull_face; + + array_add(*renderer.command_buffer.commands, command); +} + +push_cmd_set_depth_write :: (using renderer: *Renderer, enabled: bool) { + command : Render_Command; + command.type = .SET_DEPTH_WRITE; + command.set_depth_write.enabled = enabled; + + array_add(*renderer.command_buffer.commands, command); +} + +map_buffer :: (using renderer: *Renderer, handle: Buffer_Handle) -> *void #must { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + return map_backend_buffer(backend, buffer.backend_buffer); +} + +unmap_buffer :: (using renderer: *Renderer, handle: Buffer_Handle) { + assert(handle > 0); + + buffer := parray_get(*buffers, handle); + unmap_backend_buffer(backend, buffer.backend_buffer); +} + +upload_data_to_buffer :: (using renderer: *Renderer, handle: Buffer_Handle, data: *void, size: s64) { + mapped_ptr := map_buffer(renderer, handle); + if mapped_ptr != null { + memcpy(mapped_ptr, data, size); + unmap_buffer(renderer, handle); + } else { + log_error("Buffer with handle % was not mappable\n", handle); + } +} + +Text_Render_Data :: struct { + vb: Buffer_Handle; + vert_count: u32; + size: Vector2; +} + +make_point :: (x: float, y: float, s: float, t: float, color: Vector4) -> Point { + point : Point = ---; + point.x = x; + point.y = y; + point.s = s; + point.t = t; + point.color = color; + return point; +} + +get_text_size :: (using renderer: *Renderer, text: string, font_handle: Font_Handle) -> Vector2 { + size : Vector2; + + font := *renderer.fonts[font_handle - 1]; + characters := font.glyphs; + has_character := false; + + for 0..text.count-1 { + char := text.data[it]; + if char >= characters.count continue; + + has_character = true; + c := characters[char]; + + size.x += c.ax; + size.y += c.ay; + size.y = max(size.y, c.bh); + } + + // @Hack: This is just to get a random valid size, if the string is empty + if !has_character { + index := 0; + c := characters[#char "S"]; + + size.x += c.ax; + size.y += c.ay; + size.y = max(size.y, c.bh); + } + + return size; +} + +bake_text :: (using renderer: *Renderer, x: float, y: float, text: string, font_handle: Font_Handle, color: Vector4 = .{1, 1, 1, 1}) -> Text_Render_Data { + buffer := renderer.text_render_buffers[renderer.used_text_buffers_count]; + renderer.used_text_buffers_count += 1; + + x -= (cast(float)renderer.render_target_width) * 0.5; + y -= (cast(float)renderer.render_target_height) * 0.5; + + sx := 2.0 / cast(float)renderer.render_target_width; + sy := 2.0 / cast(float)renderer.render_target_height; + render_x := x * sx; + render_y := y * sy; + + coords : [..] Point; + coords.allocator = temp; + + font := *renderer.fonts[font_handle - 1]; + characters := font.glyphs; + atlas_width := cast(float)font.atlas_width; + atlas_height := cast(float)font.atlas_height; + + size : Vector2; + + for 0..text.count-1 { + char := text.data[it]; + if char >= characters.count continue; + c := characters[char]; + x2 := render_x + c.bl * sx; + y2 := -render_y - c.bt * sy; + w := c.bw * sx; + h := c.bh * sy; + + /* Advance the cursor to the start of the next character */ + render_x += c.ax * sx; + render_y += c.ay * sy; + + size.x += c.ax; + size.y += c.ay; + size.y = max(size.y, c.bh); + + /* Skip glyphs that have no pixels */ + if !w || !h continue; + + array_add(*coords, make_point(x2, -y2, c.tx, 0.0, color)); + array_add(*coords, make_point(x2 + w, -y2, c.tx + c.bw / atlas_width, 0.0, color)); + array_add(*coords, make_point(x2, -y2 - h, c.tx, c.bh / atlas_height, color)); + + array_add(*coords, make_point(x2 + w, -y2 - h, c.tx + c.bw / atlas_width, c.bh / atlas_height, color)); + array_add(*coords, make_point(x2, -y2 - h, c.tx, c.bh / atlas_height, color)); + array_add(*coords, make_point(x2 + w, -y2, c.tx + c.bw / atlas_width, 0.0, color)); + } + + data : Text_Render_Data; + data.vb = buffer; + data.vert_count = xx coords.count; + data.size = size; + + upload_data_to_buffer(renderer, buffer, coords.data, cast(s32)size_of(Point) * cast(s32)coords.count); + + return data; +} + +draw_fullscreen_quad :: () { + plane := parray_get(*renderer.meshes, renderer.default_meshes.fullscreen_plane); + vb := get_mesh_vb(plane); + push_cmd_set_vertex_buffer(renderer, vb); + push_cmd_set_index_buffer(renderer, plane.ib); + push_cmd_draw_indexed(renderer, plane.indices.count); +} + +create_render_target :: (width: u32, height: u32, format: Format) -> Render_Target_Handle { + render_target : Render_Target; + + if width == SWAPCHAIN_SIZE || height == SWAPCHAIN_SIZE { + render_target.width = width; + render_target.height = height; + render_target.actual_width = renderer.render_target_width; + render_target.actual_height = renderer.render_target_height; + } else { + render_target.width = width; + render_target.height = height; + render_target.actual_width = width; + render_target.actual_height = height; + } + + render_target.format = format; + render_target.backend_render_target = create_backend_render_target(renderer.backend, render_target.actual_width, render_target.actual_height, format); + + texture : Texture; + texture.width = render_target.actual_width; + texture.height = render_target.actual_height; + texture.backend_texture = get_backend_texture_from_render_target(renderer.backend, render_target.backend_render_target); + + render_target.texture = parray_add(*renderer.textures, texture); + + return parray_add(*renderer.render_targets, render_target); +} + +create_depth_stencil_buffer :: (width: u32, height: u32, format: Format) -> Depth_Stencil_Buffer_Handle { + depth_stencil_buffer : Depth_Stencil_Buffer; + + if width == SWAPCHAIN_SIZE || height == SWAPCHAIN_SIZE { + depth_stencil_buffer.width = width; + depth_stencil_buffer.height = height; + depth_stencil_buffer.actual_width = renderer.render_target_width; + depth_stencil_buffer.actual_height = renderer.render_target_height; + } else { + depth_stencil_buffer.width = width; + depth_stencil_buffer.height = height; + depth_stencil_buffer.actual_width = width; + depth_stencil_buffer.actual_height = height; + } + + //depth_stencil_buffer.format = format; + depth_stencil_buffer.backend_depth_stencil_buffer = create_backend_depth_stencil_buffer(renderer.backend, depth_stencil_buffer.actual_width, depth_stencil_buffer.actual_height); + + return parray_add(*renderer.depth_stencil_buffers, depth_stencil_buffer); +} + +create_material_from_pipeline :: (pipeline: Pipeline_State_Handle) -> Material_Old { + material : Material_Old; + + pass : Material_Pass_Old; + pass.pipeline = pipeline; + pipeline_state := *renderer.pipeline_states[pipeline-1]; + for pipeline_state.shader_parameters { + prop : Material_Property_Old; + prop.parameter = it; + if it.mapping == { + case .REPEAT_SAMPLER; + prop.sampler = renderer.default_samplers.repeat; + case .CLAMP_SAMPLER; + prop.sampler = renderer.default_samplers.clamp; + case .TIME; + //prop.buffer = engine.time_buffer; // @Incomplete + } + + array_add(*pass.properties, prop); + } + array_add(*material.passes, pass); + + return material; +} + +check_for_shader_modifications :: () { + changed, needs_wait, wait_seconds := process_changes(*renderer.watcher); +} + +render :: () { + renderer.command_buffer.commands.count = 0; + renderer.last_draw_call_count = renderer.draw_call_count; + renderer.draw_call_count = 0; + execute_render_graph(renderer.render_graph); + render(renderer.backend, *renderer.command_buffer); + renderer.used_text_buffers_count = 0; +} + +#load "render_graph.jai"; +#load "font.jai"; +#load "mesh.jai"; +#load "material.jai"; +#load "model.jai"; +#import "Bucket_Array"; +#import "Hash_Table"; +#import "stb_image"; +#load "vertex.jai"; + +//#if NEW_UI { +// #load "ui/ui.jai"; +//} else { +// #load "ui_system.jai"; +// #load "ui.jai"; +//} +//ImGui :: #import "ImGui"; + +#scope_module +#load "dx11_renderer.jai"; + +#import "File_Watcher"; + +file_change_callback :: (watcher: *File_Watcher(string), change: *File_Change, user_data: *string) { + for *shader: renderer.shaders { + if shader.path == change.full_path { + reload_shader(renderer, shader); + } + } +} diff --git a/renderer/vertex.jai b/renderer/vertex.jai new file mode 100644 index 0000000..1918ba0 --- /dev/null +++ b/renderer/vertex.jai @@ -0,0 +1,9 @@ +Textured_Vert :: struct { + position: Vector2; + texcoord: Vector2; +} + +Textured_Vert_3D :: struct { + position: Vector3; + texcoord: Vector2; +} \ No newline at end of file diff --git a/ui/ui.jai b/ui/ui.jai new file mode 100644 index 0000000..3f34d0c --- /dev/null +++ b/ui/ui.jai @@ -0,0 +1,974 @@ +MAX_VERT_BUFFERS :: 64; + +UI_Box_Flags :: enum_flags u32 { + NONE :: 0; + CLICKABLE :: 1; + DRAW_BORDER :: 2; + DRAW_BACKGROUND :: 4; + DRAW_TEXT :: 8; + CLIP :: 16; + + ANIMATE_ON_HOVER :: 32; +} + +Rect :: struct { + x: float; + y: float; + w: float; + h: float; +} + +Rect_Instance_Data :: struct { + p0 : Vector2; + p1 : Vector2; + colors : [4] Color; + + corner_radius : float; + edge_softness : float; + border_thickness : float; +} + +Interaction_State :: struct { + clicked: bool; + + left_mouse_down: bool; + right_mouse_down: bool; + left_mouse_pressed: bool; + right_mouse_pressed: bool; + + normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1] +} + +UI_Box :: struct { + hash: u32; + last_used_frame_index: u64; + + parent: *UI_Box; + first_child: *UI_Box; + last_child: *UI_Box; + num_children: u64; + + next: *UI_Box; + prev: *UI_Box; + + // Specified per-frame + flags : UI_Box_Flags; + text: string; + alignment_flags: UI_Text_Alignment_Flags; + semantic_size: [2] UI_Size; + padding_left: float; + padding_right: float; + padding_top: float; + padding_bottom: float; + + // Computed per-frame + rect : Rect; + size: Vector2; + + // Persistent + animation_t: float; + hover_animation_t: float; + + interaction : Interaction_State; + + style : struct { + texture: Texture_Handle; + background_color: Color; + border_color: Color; + border_width: float; + text_color: Color; + } + + layout : struct { + alignment: UI_Child_Alignment; + axis: UI_Child_Axis; + } +} + +UI_Child_Axis :: enum { + HORIZONTAL; + VERTICAL; +} + +UI_Child_Alignment :: enum { + LEFT; + RIGHT; + CENTERED; +} + +UI_Text_Alignment_Flags :: enum_flags u8 { + LEFT_BOTTOM :: 0; + CENTER_HORIZONTALLY :: 1; + CENTER_VERTICALLY :: 2; + RIGHT_BOTTOM :: 4; +} + +UI_Size_Kind :: enum { + PIXELS; + TEXT_DIM; + PCT; + CHILDREN_SUM; +} + +UI_Size :: struct { + size_kind: UI_Size_Kind; + value: float; + strictness: float; +} + +Textured_Vert_Buffer :: struct { + vb : Buffer_Handle; + verts: [6] Textured_Vert; +} + +UI_State :: struct { + begun: bool; + frame_index: u64; + + root: *UI_Box; + boxes: Table(u32, UI_Box); + + last_box: *UI_Box; + + parent_stack: Stack(*UI_Box); + + allocator: Allocator; + pool: Flat_Pool; + + // Rendering + shaders : struct { + ui: Pipeline_State_Handle; + ui_rect: Pipeline_State_Handle; + ui_rect_textured: Pipeline_State_Handle; + text: Pipeline_State_Handle; + } + + fonts : struct { + regular: Font_Handle; + button: Font_Handle; + } + + rect_vb : Buffer_Handle; + colored_verts: [..] Colored_Vert; + max_verts: u32; + + instanced_rects : [..] Rect_Instance_Data; + instance_rect_sb : Buffer_Handle; + + texture_vert_buffers : [MAX_VERT_BUFFERS] Textured_Vert_Buffer; + next_available_texture_buffer_index: s64; + + sampler: Sampler_Handle; +} + +ui_state : UI_State; + +ui_box :: (flags: UI_Box_Flags, identifier: s64 = 0, loc := #caller_location) -> *UI_Box { + return ui_box_make(flags, hash=get_hash(loc, identifier)); +} + +ui_box_make :: (flags: UI_Box_Flags, hash: u32) -> *UI_Box { + actual_hash := hash; + parent := get_current_parent(); + + if parent != null { + parent.num_children += 1; + //actual_hash += parent.hash; + //actual_hash += xx (parent.num_children*2); + } + + box := get_ui_box_or_create_new(actual_hash); + box.last_used_frame_index = ui_state.frame_index; + box.first_child = null; + box.last_child = null; + box.next = null; + box.prev = null; + box.num_children = 0; + box.flags = flags; + box.parent = null; + + // Set the links + box.parent = parent; + if box.parent != null { + if box.parent.first_child == null { + box.parent.first_child = box; + } + + if box.parent.last_child != null { + box.parent.last_child.next = box; + box.prev = box.parent.last_child; + } + + box.parent.last_child = box; + } + + box.text = current_text; + box.style.texture = current_texture; + box.style.background_color = background_color; + box.style.text_color = text_color; + box.style.border_color = border_color; + box.style.border_width = border_width; + box.semantic_size[0] = current_size_x; + box.semantic_size[1] = current_size_y; + box.padding_left = padding_left; + box.padding_right = padding_right; + box.padding_top = padding_top; + box.padding_bottom = padding_bottom; + box.alignment_flags = current_text_alignment; + + // Reset everything + set_properties_to_defaults(); + + if ui_state.root == null { + ui_state.root = box; + } + + ui_state.last_box = box; + + return box; +} + +get_hash :: (loc: Source_Code_Location, identifier: s64) -> u32 { + hash := cast(u32) loc.fully_pathed_filename.data * cast,no_check(u32)(loc.line_number + 1) * cast(u32)(identifier + 1); + return hash; +} + +get_current_parent :: () -> *UI_Box { + return stack_peek(*ui_state.parent_stack); +} + +ui_push_parent :: (box: *UI_Box, alignment: UI_Child_Alignment = .LEFT, axis: UI_Child_Axis = .HORIZONTAL) { + box.layout.alignment = alignment; + box.layout.axis = axis; + + stack_push(*ui_state.parent_stack, box); +} + +ui_pop_parent :: () { + stack_pop(*ui_state.parent_stack); +} + +ui_init :: () { + a: Allocator; + a.proc = flat_pool_allocator_proc; + a.data = *ui_state.pool; + ui_state.allocator = a; + + ui_state.colored_verts.allocator = ui_state.allocator; + ui_state.boxes.allocator = ui_state.allocator; + + init(*ui_state.boxes, 1024); + + ui_state.sampler = create_sampler(renderer); + ui_state.fonts.regular = create_font(renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12); + ui_state.fonts.button = create_font(renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12); + + // ui_rect + { + vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS"); + ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS"); + + layout : [2] Vertex_Data_Info; + layout[0] = .{0,.POSITION2D, 0}; + layout[1] = .{0,.COLOR_WITH_ALPHA, 0}; + + params : [2] Shader_Parameter; + params[0].shader = .PIXEL; + params[0].type = .SAMPLER; + params[0].name = "samp"; + params[0].slot = 0; + params[0].mapping = .CLAMP_SAMPLER; + + params[1].shader = .PIXEL; + params[1].type = .TEXTURE; + params[1].name = "tex"; + params[1].slot = 1; + + ui_state.shaders.ui_rect = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + // ui_rect + { + vs := create_vertex_shader(renderer, "../assets/shaders/ui.hlsl", "VS_Main"); + ps := create_pixel_shader(renderer, "../assets/shaders/ui.hlsl", "PS_Main"); + + layout : [0] Vertex_Data_Info; + + params : [2] Shader_Parameter; + params[0].shader = .PIXEL; + params[0].type = .SAMPLER; + params[0].name = "samp"; + params[0].slot = 0; + params[0].mapping = .CLAMP_SAMPLER; + + params[1].shader = .PIXEL; + params[1].type = .TEXTURE; + params[1].name = "tex"; + params[1].slot = 1; + + ui_state.shaders.ui = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + { + vs := create_vertex_shader(renderer, "../assets/shaders/font.hlsl", "VS"); + ps := create_pixel_shader(renderer, "../assets/shaders/font.hlsl", "PS"); + + layout : [3] Vertex_Data_Info; + layout[0] = .{0,.POSITION2D, 0}; + layout[1] = .{0,.TEXCOORD0, 0}; + layout[2] = .{0,.COLOR_WITH_ALPHA, 0}; + + params : [2] Shader_Parameter; + params[0].shader = .PIXEL; + params[0].type = .SAMPLER; + params[0].name = "ss"; + params[0].slot = 0; + params[0].mapping = .CLAMP_SAMPLER; + + params[1].shader = .PIXEL; + params[1].type = .TEXTURE; + params[1].name = "tex"; + params[1].slot = 1; + + ui_state.shaders.text = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + // Make into one shader.... + // ui_rect textured + { + vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS", string.["USE_TEXTURE"]); + ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS", string.["USE_TEXTURE"]); + + layout : [2] Vertex_Data_Info; + layout[0] = .{0,.POSITION2D, 0}; + layout[1] = .{0,.TEXCOORD0, 0}; + + params : [2] Shader_Parameter; + params[0].shader = .PIXEL; + params[0].type = .SAMPLER; + params[0].name = "samp"; + params[0].slot = 0; + params[0].mapping = .CLAMP_SAMPLER; + + params[1].shader = .PIXEL; + params[1].type = .TEXTURE; + params[1].name = "tex"; + params[1].slot = 1; + + ui_state.shaders.ui_rect_textured = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); + } + + ui_state.max_verts = 4096; + { + dynamic_buffer_size := size_of(Colored_Vert) * ui_state.max_verts; + ui_state.rect_vb = create_vertex_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Colored_Vert), mappable=true); + + for 0..MAX_VERT_BUFFERS-1 { + { + buffer : Textured_Vert_Buffer; + + dynamic_buffer_size := size_of(Textured_Vert) * ui_state.max_verts; + buffer.vb = create_vertex_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Textured_Vert), mappable=true); + + ui_state.texture_vert_buffers[it] = buffer; + } + } + } + + { + dynamic_buffer_size := size_of(Rect_Instance_Data) * ui_state.max_verts; + ui_state.instance_rect_sb = create_structured_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Rect_Instance_Data), mappable=true); + } +} + +// # BEGIN # LAYOUT ALGORITHM +ui_figure_out_sizes :: () { + // SET ALL PIXEL AND TEXT SIZES + for *box : ui_state.boxes { + if box.semantic_size[0].size_kind == { + case .PIXELS; { + box.size.x = box.semantic_size[0].value + box.padding_left + box.padding_right; + } + case .TEXT_DIM; { + text_size := get_text_size(renderer, box.text, ui_state.fonts.button); + box.size.x = text_size.x + box.padding_left + box.padding_right; + } + } + + if box.semantic_size[1].size_kind == { + case .PIXELS; { + box.size.y = box.semantic_size[1].value + box.padding_top + box.padding_bottom; + } + case .TEXT_DIM; { + text_size := get_text_size(renderer, box.text, ui_state.fonts.button); + box.size.y = text_size.y + box.padding_top + box.padding_bottom; + } + } + } + + // Upwards-dependent + for *box : ui_state.boxes { + if box.semantic_size[0].size_kind == .PCT { + assert(box.parent != null); + box.size.x = box.parent.size.x * box.semantic_size[0].value - (box.parent.padding_left + box.parent.padding_right); + } + if box.semantic_size[1].size_kind == .PCT { + assert(box.parent != null); + box.size.y = box.parent.size.y * box.semantic_size[1].value - (box.parent.padding_top + box.parent.padding_bottom); + } + } + + + // Downwards-dependent + for *box : ui_state.boxes { + if box.semantic_size[0].size_kind == .CHILDREN_SUM { + assert(box.num_children != 0); + + size : float = 0.0; + child := box.first_child; + + while child != null { + defer child = child.next; + + if box.layout.axis == .HORIZONTAL { + size += child.size.x; + } else { + size = max(child.size.x, size); + } + } + + box.size.x = size + box.padding_left + box.padding_right; + } + if box.semantic_size[1].size_kind == .CHILDREN_SUM { + assert(box.num_children != 0); + + size : float = 0.0; + child := box.first_child; + + while child != null { + defer child = child.next; + + if box.layout.axis == .VERTICAL { + size += child.size.y; + } else { + size = max(child.size.y, size); + } + } + + box.size.y = size + box.padding_top + box.padding_bottom; + } + } + + + // Find final positions + for *box : ui_state.boxes { + if box.parent == null { + box.rect.x = 0.0; + box.rect.y = 0.0; + box.rect.w = box.size.x; + box.rect.h = box.size.y; + ui_set_rect_recursively(box); + } + } + + for *box : ui_state.boxes { + if box.style.texture != 0 { + w, h := get_texture_size(renderer, box.style.texture); + aspect := cast(float)w/cast(float)h; + current_aspect := cast(float)box.rect.w/cast(float)box.rect.h; + rect := box.rect; + if rect.w < rect.h { + box.rect.w = box.rect.h * aspect; + scale := rect.w / box.rect.w; + // @Note(Daniel): My brain can't think straight so this extra scaling might be circumvented by doing the calculations differently.... + if scale < 1 { + box.rect.w *= scale; + box.rect.h *= scale; + } + } else { + box.rect.h = box.rect.w / aspect; + scale := rect.h / box.rect.h; + if scale < 1 { + box.rect.w *= scale; + box.rect.h *= scale; + } + } + } + //if box.semantic_size[0].size_kind == .KEEP_ASPECT { + // assert(box.style.texture != 0); + // w, h := get_texture_size(renderer, box.style.texture); + // aspect := cast(float)w/cast(float)h; + // box.size.x = box.size.y * aspect; + //} + // + //if box.semantic_size[1].size_kind == .KEEP_ASPECT { + // assert(box.style.texture != 0); + // w, h := get_texture_size(renderer, box.style.texture); + // aspect := cast(float)h/cast(float)w; + // box.size.y = box.size.x * aspect; + //} + } + + //print("\n"); + for *box : ui_state.boxes { + //print("RECT % % % % TEXT %\n", box.rect.x, box.rect.y, box.rect.w, box.rect.h, box.text); + } + //print("\n"); + +} + +ui_set_rect_recursively :: (parent: *UI_Box) { + starting_offset_x := parent.rect.x + parent.padding_left; + starting_offset_y := parent.rect.y + parent.padding_bottom; + + child := parent.first_child; + while child != null { + defer child = child.next; + + child.rect.x = starting_offset_x; + child.rect.y = starting_offset_y; + child.rect.w = child.size.x; + child.rect.h = child.size.y; + + if parent.layout.axis == { + case .HORIZONTAL; { + starting_offset_x += child.rect.w; + } + case .VERTICAL; { + starting_offset_y += child.rect.h; + } + } + + if child.num_children > 0 { + ui_set_rect_recursively(child); + } + } +} +// # END # LAYOUT ALGORITHM + + +ui_begin :: () { + assert(!ui_state.begun); + ui_state.frame_index += 1; + ui_state.begun = true; + ui_state.colored_verts.count = 0; + ui_state.instanced_rects.count = 0; + ui_state.next_available_texture_buffer_index = 0; +} + +ui_end :: () { + hashes_to_remove : [..] u32; + hashes_to_remove.allocator = temp; + for * ui_state.boxes { + if it.last_used_frame_index != ui_state.frame_index { + array_add(*hashes_to_remove, it.hash); + } + } + + for hashes_to_remove { + table_remove(*ui_state.boxes, it); + } + + // Do all the layouting work + ui_figure_out_sizes(); + // Check for any input events + ui_update_input(); + + assert(ui_state.begun); + ui_state.begun = false; + ui_state.parent_stack.values.count = 0; +} + +// #### BOX PROPERTY FUNCTIONS +ui_set_next_background_color :: (color: Color) { + background_color = color; +} + +ui_set_next_text_color :: (color: Color) { + text_color = color; +} + +ui_set_next_border_color :: (color: Color) { + border_color = color; +} + +ui_set_next_border_width :: (width: float) { + border_width = width; +} + +ui_set_next_texture :: (handle: Texture_Handle) { + current_texture = handle; +} + +ui_set_next_padding :: (new_padding: float) { + padding_left = new_padding; + padding_right = new_padding; + padding_top = new_padding; + padding_bottom = new_padding; +} + +ui_set_next_padding_left :: (new_padding: float) { + padding_left = new_padding; +} + +ui_set_next_padding_right :: (new_padding: float) { + padding_right = new_padding; +} + +ui_set_next_padding_top :: (new_padding: float) { + padding_top = new_padding; +} + +ui_set_next_padding_bottom :: (new_padding: float) { + padding_bottom = new_padding; +} + +ui_set_next_text :: (text: string) { + current_text = copy_temporary_string(text); +} + +ui_set_next_text_alignment :: (flags: UI_Text_Alignment_Flags) { + current_text_alignment = flags; +} + +ui_set_next_size_x :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) { + current_size_x = .{size, value, strictness}; +} + +ui_set_next_size_y :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) { + current_size_y = .{size, value, strictness}; +} +// # END # BOX PROPERTY FUNCTIONS + +ui_render :: () { + // Render background + for * ui_state.boxes { + if it.parent == null { + ui_render_background_recursively(it); + } + } + + if ui_state.colored_verts.count > 0 { + //upload_data_to_buffer(renderer, ui_state.rect_vb, ui_state.colored_verts.data, cast(s32)ui_state.colored_verts.count * size_of(Colored_Vert)); + + //push_cmd_set_draw_mode(renderer, .FILL); + //push_cmd_set_depth_write(renderer, false); + //push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui_rect); + + //push_cmd_set_vertex_buffer(renderer, ui_state.rect_vb); + //push_cmd_draw(renderer, ui_state.colored_verts.count); + } + + // NEW DRAW TIME + if ui_state.instanced_rects.count > 0 { + upload_data_to_buffer(renderer, ui_state.instance_rect_sb, ui_state.instanced_rects.data, cast(s32)ui_state.instanced_rects.count * size_of(Rect_Instance_Data)); + + push_cmd_set_draw_mode(renderer, .FILL); + push_cmd_set_depth_write(renderer, false); + push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui); + + push_cmd_set_constant_buffer(renderer, 0, screen_data_buffer, .PIXEL); + push_cmd_set_structured_buffer(renderer, 0, ui_state.instance_rect_sb, .VERTEX); + push_cmd_draw_instanced(renderer, 4, ui_state.instanced_rects.count, topology=.TRIANGLE_STRIP); + } + + + // @Incomplete: this should be part of the same rendering somehow... Although it should happen on a per-texture basis + // Render backgrounds with texture + for * ui_state.boxes { + if it.parent == null { + ui_render_texture_background_recursively(it); + } + } + + // Render text + push_cmd_set_pipeline_state(renderer, ui_state.shaders.text); + + for * ui_state.boxes { + if it.parent == null { + ui_render_text_recursively(it); + } + } +} + +#scope_file +set_properties_to_defaults :: () { + background_color = .{1,1,1,1}; + text_color = .{0,0,0,1}; + border_color = .{130.0/255.0,95.0/255.0,38.0/255.0,1}; + border_width = 2.0; + current_text = ""; + current_texture = 0; + current_size_x = .{}; + current_size_y = .{}; + padding_left = 0; + padding_right = 0; + padding_top = 0; + padding_bottom = 0; + current_text_alignment = .LEFT_BOTTOM; +} + +get_ui_box_or_create_new :: (hash: u32) -> *UI_Box, bool { + ptr := table_find_pointer(*ui_state.boxes, hash); + + if ptr == null { + table_add(*ui_state.boxes, hash, .{}); + ptr = table_find_pointer(*ui_state.boxes, hash); + } + + ptr.hash = hash; + + return ptr, false; +} + +// #### RENDERING +make_vert :: (x: float, y: float, color: Color) -> Colored_Vert { + vert : Colored_Vert; + vert.position.x = x; + vert.position.y = y; + vert.color = color; + return vert; +} + +make_vert_textured :: (x: float, y: float, uv_x: float, uv_y: float) -> Textured_Vert { + vert : Textured_Vert; + vert.position.x = x; + vert.position.y = y; + vert.texcoord.x = uv_x; + vert.texcoord.y = uv_y; + return vert; +} + +should_draw_hover_animation :: (box: *UI_Box) -> bool { + return box.flags & .ANIMATE_ON_HOVER && box.hover_animation_t > 0.0; +} + +instance_data_from_rect :: (x: float, y: float, w: float, h: float, corner_radius: float, edge_softness: float, border_thickness: float, color: .. Color) -> Rect_Instance_Data { + data : Rect_Instance_Data = ---; + data.p0.x = x; + data.p0.y = y; + data.p1.x = x + w; + data.p1.y = y - h; + data.corner_radius = corner_radius; + data.edge_softness = edge_softness; + data.border_thickness = border_thickness; + + if color.count == 4 { + data.colors[0] = color[0]; + data.colors[1] = color[1]; + data.colors[2] = color[2]; + data.colors[3] = color[3]; + } else { + data.colors[0] = color[0]; + data.colors[1] = color[0]; + data.colors[2] = color[0]; + data.colors[3] = color[0]; + } + return data; +} + +ui_render_background_recursively :: (box: *UI_Box) { + inv_w := 1.0 / cast(float)renderer.render_target_width; + inv_h := 1.0 / cast(float)renderer.render_target_height; + + if box.flags & .DRAW_BACKGROUND && box.style.texture == 0 { + x : float = box.rect.x * inv_w * 2.0 - 1.0; + y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; + w : float = box.rect.w * inv_w * 2.0; + h : float = box.rect.h * inv_h * 2.0; + + color : Color = box.style.background_color; + instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color); + array_add(*ui_state.instanced_rects, instance_data); + + //array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color)); + //array_add(*ui_state.colored_verts, make_vert(x, y - h, color)); + //array_add(*ui_state.colored_verts, make_vert(x, y, color)); + + //array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color)); + //array_add(*ui_state.colored_verts, make_vert(x, y, color)); + //array_add(*ui_state.colored_verts, make_vert(x + w, y, color)); + } + + if box.flags & .DRAW_BORDER { + x : float = box.rect.x * inv_w * 2.0 - 1.0; + y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; + w : float = box.rect.w * inv_w * 2.0; + h : float = box.rect.h * inv_h * 2.0; + + color : Color = box.style.border_color; + instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, box.style.border_width, color); + array_add(*ui_state.instanced_rects, instance_data); + } + + if should_draw_hover_animation(box) { + x : float = box.rect.x * inv_w * 2.0 - 1.0; + y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; + w : float = box.rect.w * inv_w * 2.0; + h : float = box.rect.h * inv_h * 2.0; + + color := Color.{1,1,1, box.hover_animation_t * 0.3}; + instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color); + array_add(*ui_state.instanced_rects, instance_data); + } + + child := box.first_child; + while child != null { + defer child = child.next; + ui_render_background_recursively(child); + } +} + +get_next_available_texture_vert_buffer :: () -> *Textured_Vert_Buffer { + index := ui_state.next_available_texture_buffer_index; + assert(index < MAX_VERT_BUFFERS); + + ui_state.next_available_texture_buffer_index += 1; + + buffer := *ui_state.texture_vert_buffers[index]; + return buffer; +} + +ui_render_texture_background_recursively :: (box: *UI_Box) { + inv_w := 1.0 / cast(float)renderer.render_target_width; + inv_h := 1.0 / cast(float)renderer.render_target_height; + + if box.flags & .DRAW_BACKGROUND && box.style.texture != 0 { + buffer := get_next_available_texture_vert_buffer(); + rect := box.rect; + inv_w := 1.0 / cast(float)renderer.render_target_width; + inv_h := 1.0 / cast(float)renderer.render_target_height; + + x : float = rect.x * inv_w * 2.0 - 1.0; + y : float = (cast(float)renderer.render_target_height - rect.y) * inv_h * 2.0 - 1.0; + w : float = rect.w * inv_w * 2.0; + h : float = rect.h * inv_h * 2.0; + + buffer.verts[0] = make_vert_textured(x + w, y - h, 1.0, 1.0); + buffer.verts[1] = make_vert_textured(x, y - h, 0, 1.0); + buffer.verts[2] = make_vert_textured(x, y, 0.0, 0.0); + + buffer.verts[3] = make_vert_textured(x + w, y - h, 1.0, 1.0); + buffer.verts[4] = make_vert_textured(x, y, 0.0, 0.0); + buffer.verts[5] = make_vert_textured(x + w, y, 1.0, 0.0); + + upload_data_to_buffer(renderer, buffer.vb, buffer.verts.data, 6 * size_of(Textured_Vert)); + + push_cmd_set_draw_mode(renderer, .FILL); + push_cmd_set_depth_write(renderer, false); + push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui_rect_textured); + + push_cmd_set_sampler(renderer, 0, ui_state.sampler); + push_cmd_set_texture(renderer, 0, box.style.texture); + + push_cmd_set_vertex_buffer(renderer, buffer.vb); + push_cmd_draw(renderer, 6); + } + + child := box.first_child; + while child != null { + defer child = child.next; + ui_render_texture_background_recursively(child); + } +} + +ui_render_text_recursively :: (box: *UI_Box) { + inv_w := 1.0 / cast(float)renderer.render_target_width; + inv_h := 1.0 / cast(float)renderer.render_target_height; + + if box.flags & .DRAW_TEXT { + font_handle := ui_state.fonts.regular; + + text_size := get_text_size(renderer, box.text, font_handle); + + x := box.rect.x + box.padding_left; + + y := cast(float)renderer.render_target_height - box.rect.y - box.rect.h + box.padding_bottom; + if box.alignment_flags & .CENTER_HORIZONTALLY { + x += box.rect.w * 0.5 - text_size.x * 0.5; + } + + if box.alignment_flags & .CENTER_VERTICALLY { + y += box.rect.h * 0.5 - text_size.y * 0.5; + } + + if box.alignment_flags & .RIGHT_BOTTOM { + x += box.rect.w - text_size.x; + } + + render_data := bake_text(renderer, x, y, box.text, font_handle, box.style.text_color); + font := *renderer.fonts[font_handle - 1]; + + push_cmd_set_texture(renderer, 0, font.texture); + push_cmd_set_vertex_buffer(renderer, render_data.vb); + push_cmd_draw(renderer, render_data.vert_count); + } + + child := box.first_child; + while child != null { + defer child = child.next; + ui_render_text_recursively(child); + } +} + +ADJUSTMENT :: 0.1; + +is_mouse_inside :: (x: float, y: float, rect: Rect) -> bool { + return x - ADJUSTMENT >= rect.x && y - ADJUSTMENT >= rect.y && x + ADJUSTMENT <= rect.x + rect.w && y + ADJUSTMENT <= rect.y + rect.h; +} + +ui_update_input :: () { + mouse_x := input.mouse.x; + mouse_y := input.mouse.y; + + for *box: ui_state.boxes { + if box.flags & .CLICKABLE { + normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1] + box.interaction.normalized_local_mouse_coordinates.x = clamp(mouse_x - box.rect.x, 0.0, box.rect.w) / box.rect.w; + box.interaction.normalized_local_mouse_coordinates.y = clamp(mouse_y - box.rect.y, 0.0, box.rect.h) / box.rect.h; + if box.interaction.clicked { + box.interaction.clicked = false; + } + + if box.interaction.left_mouse_pressed { + box.interaction.left_mouse_down = false; + if !key_pressed(.MOUSE_LEFT) { + box.interaction.clicked = true; + box.interaction.left_mouse_pressed = false; + } + } else { + if is_mouse_inside(mouse_x, mouse_y, box.rect) && key_pressed(.MOUSE_LEFT) { + box.interaction.left_mouse_pressed = true; + box.interaction.left_mouse_down = true; + } + } + } + + if box.flags & .ANIMATE_ON_HOVER { + if is_mouse_inside(mouse_x, mouse_y, box.rect) { + box.hover_animation_t += dt * 10; + } else { + box.hover_animation_t -= dt * 10; + } + box.hover_animation_t = clamp(box.hover_animation_t, 0.0, 1.0); + } + } +} + +// style +background_color : Color = .{1,1,1,1}; +text_color : Color = .{0,0,0,1}; +border_color : Color; +border_width : float; +current_text : string = ""; +current_texture : Texture_Handle; +current_size_x : UI_Size; +current_size_y : UI_Size; +current_text_alignment : UI_Text_Alignment_Flags; +padding_left : float; +padding_right : float; +padding_top : float; +padding_bottom : float; + +#load "../core/stack.jai"; + diff --git a/ui/widgets.jai b/ui/widgets.jai new file mode 100644 index 0000000..1b13a65 --- /dev/null +++ b/ui/widgets.jai @@ -0,0 +1,184 @@ +ui_full_size_background :: (identifier: s64 = 0, loc := #caller_location) { + ui_set_next_size_x(.PIXELS, xx renderer.render_target_width); + ui_set_next_size_y(.PIXELS, xx renderer.render_target_height); + + ui_set_next_background_color(.{0.0, 0.0, 0.0, 1.0}); + + background := ui_box_make(.DRAW_BACKGROUND, hash=get_hash(loc, identifier)); +} + +ui_button :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_text(text); + ui_set_next_background_color(.{0.2,0.2,0.2,1}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + //ui_set_next_text_alignment(CENTER_HORIZONTALLY | .CENTER_VERTICALLY); + box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .DRAW_BACKGROUND | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_button_with_texture :: (texture_handle: Texture_Handle, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_texture(texture_handle); + ui_set_next_size_x(.PIXELS, 20); + ui_set_next_size_y(.PIXELS, 20); + box := ui_box_make(.CLICKABLE | .DRAW_BACKGROUND | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_button_no_bg :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_text(text); + ui_set_next_background_color(.{0.2,0.2,0.2,1}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + //ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY); + box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_button_no_border :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_text(text); + ui_set_next_background_color(.{0.2,0.2,0.2,1}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + //ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY); + box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_BACKGROUND | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_toolbar_button :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_text(text); + ui_set_next_border_width(1); + ui_set_next_border_color(.{0.5,0.5,0.5,1}); + ui_set_next_background_color(.{0.1,0.1,0.1,1}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding_left(20); + ui_set_next_padding_right(20); + ui_set_next_padding_top(5); + ui_set_next_padding_bottom(5); + //ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY); + box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_label :: (text: string, text_color: Color = .{1,1,1,1}, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_text(text); + ui_set_next_background_color(.{0.0,1.0,1.0,0.0}); + ui_set_next_text_color(text_color); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + box := ui_box_make(.DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); +} + +ui_label_fill_parent_x :: (text: string, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_text(text); + ui_set_next_background_color(.{0.0,1.0,1.0,0.0}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.PCT, 1); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + //ui_set_next_text_alignment(.CENTER_VERTICALLY); + box := ui_box_make(.DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier)); +} + +ui_clickable_label :: (text: string, selected: bool = false, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State { + ui_set_next_text(text); + if selected { + ui_set_next_background_color(.{0.5,0.5,0.5,1.0}); + } else { + ui_set_next_background_color(.{0.0,1.0,1.0,0.0}); + } + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.PCT, 1); + ui_set_next_size_y(.TEXT_DIM); + ui_set_next_padding(5); + //ui_set_next_text_alignment(.CENTER_VERTICALLY); + box := ui_box_make(.DRAW_BACKGROUND | .DRAW_TEXT | .ANIMATE_ON_HOVER | .CLICKABLE, get_hash(loc, identifier)); + return box.interaction.clicked, box.interaction; +} + +ui_toolbar :: (identifier: s64 = 0, loc := #caller_location) { + ui_set_next_background_color(.{0.2,0.2,0.2,1}); + ui_set_next_size_x(.PIXELS, xx renderer.render_target_width); + ui_set_next_size_y(.CHILDREN_SUM); + box := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier)); +} + +ui_tab_title_bar :: (title: string, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_background_color(.{0.03, 0.03, 0.03, 1.0}); + ui_set_next_padding(5); + ui_set_next_size_y(.CHILDREN_SUM); + parent := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier)); + ui_push_parent(parent, alignment=.LEFT, axis=.VERTICAL); + { + ui_set_next_text_alignment(.CENTER_VERTICALLY); + ui_set_next_text(title); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.TEXT_DIM); + ui_set_next_size_y(.TEXT_DIM); + text := ui_box(.DRAW_TEXT); + } + ui_pop_parent(); +} + + +ui_texture :: (texture: Texture_Handle, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_texture(texture); + ui_set_next_background_color(.{0.1,0.1,0.1,1}); + box := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier)); +} + +ui_interactable_texture :: (texture: Texture_Handle, identifier: s64 = 0, loc := #caller_location) -> Interaction_State { + ui_set_next_texture(texture); + ui_set_next_background_color(.{0.1,0.1,0.1,1}); + box := ui_box_make(.DRAW_BACKGROUND | .CLICKABLE, get_hash(loc, identifier)); + + return box.interaction; +} + +ui_space :: (width: float, height: float, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_background_color(.{0,0,0,0}); + ui_set_next_size_x(.PIXELS, width); + ui_set_next_size_y(.PIXELS, height); + box := ui_box_make(.NONE, get_hash(loc, identifier)); +} + +ui_slider :: (value: *float, min: float = 0.0, max: float = 1.0, identifier: s64 = 0, loc := #caller_location) { + ui_set_next_background_color(.{0.2,0.2,0.2,1}); + ui_set_next_text_color(.{1,1,1,1}); + ui_set_next_size_x(.PCT, 1.0); + ui_set_next_size_y(.PIXELS, 15); + ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY); + ui_set_next_text(tprint("%", < *Window { + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + + // Get the display resolution + display_mode : SDL_DisplayMode; + if (SDL_GetDesktopDisplayMode(0, *display_mode) != 0) { + log_error("Could not get display mode! SDL_Error: %\n", SDL_GetError()); + return null; + } + + screen_width : s32 = xx width; + screen_height : s32 = xx height; + flags := SDL_WINDOW_SHOWN; + + if fullscreen { + screen_width = display_mode.w; + screen_height = display_mode.h; + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + sdl_window := SDL_CreateWindow(to_c_string(title,, temp), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height, flags); + + if sdl_window == null { + // In the case that the window could not be made... + log_error("Could not create window: %\n", to_string(SDL_GetError())); + return null; + } + + window := New(SDL_Window_Type); + window.width = xx screen_width; + window.height = xx screen_height; + window.sdl_window = sdl_window; + + return window; +} + +get_hwnd :: (window: *Window) -> HWND { + sdl := cast(*SDL_Window_Type)window; + wm_info : SDL_SysWMinfo; + SDL_GetWindowWMInfo(sdl.sdl_window, *wm_info); + + return wm_info.info.win.window; +} + +set_show_cursor :: (show: bool) { + SDL_ShowCursor(xx show); + SDL_SetRelativeMouseMode(ifx show then SDL_FALSE else SDL_TRUE); +}