From 81b1a407ae0141abcea93da5597a3a3874cfc22b Mon Sep 17 00:00:00 2001 From: katajisto Date: Sun, 8 Feb 2026 12:17:59 +0200 Subject: [PATCH] work on audio mixer implementation --- src/assets/asset_manager.jai | 2 +- src/assets/loaders.jai | 1 + src/audio/backend.jai | 15 +----- src/audio/load.jai | 2 +- src/audio/mixer.jai | 80 ++++++++++++++++++++++++---- src/main.jai | 14 +++++ src/profiling.jai | 93 +++++++++++++++++++++++++++++++++ src/rendering/backend_sokol.jai | 29 ++++++++-- src/rendering/core.jai | 3 ++ src/shaders/jai/shader_ssao.jai | 12 ++--- src/shaders/shader_ssao.glsl | 2 +- src/time.jai | 13 +++++ 12 files changed, 227 insertions(+), 39 deletions(-) create mode 100644 src/profiling.jai diff --git a/src/assets/asset_manager.jai b/src/assets/asset_manager.jai index f786a78..e93ad9d 100644 --- a/src/assets/asset_manager.jai +++ b/src/assets/asset_manager.jai @@ -5,7 +5,7 @@ hash :: #import "Hash"; #load "loaders.jai"; -MAX_FILE_SIZE :: 2_000_000_000; +MAX_FILE_SIZE :: 200_000_000; buf : [MAX_FILE_SIZE]u8; Pack_Request :: struct { diff --git a/src/assets/loaders.jai b/src/assets/loaders.jai index 827857c..4498487 100644 --- a/src/assets/loaders.jai +++ b/src/assets/loaders.jai @@ -46,5 +46,6 @@ load_wav_from_memory :: (data: []u8) -> Audio_Data { array_add(*audio.data, cast(float)sample / 32768.0); } } + audio.channels = format.nChannels; return audio; } diff --git a/src/audio/backend.jai b/src/audio/backend.jai index 23b2091..3b9ae26 100644 --- a/src/audio/backend.jai +++ b/src/audio/backend.jai @@ -1,17 +1,4 @@ sokol_audio_callback :: (buffer: *float, num_frames: s32, num_channels: s32) #c_call { push_context,defer_pop default_context; - audio := get_audio_from_pack("game_core", "sound/music/monoco"); - if !audio { - print("Audio was null!!\n"); - return; - } - if audio.data.count < 1 then return; - - for i : 0..num_frames-1 { - if cur_sample >= audio.data.count { - cur_sample = 0; - } - buffer[i] = audio.data[cur_sample] * 0.5; - cur_sample += 1; - } + mixer_get_samples(buffer, num_frames, num_channels); } diff --git a/src/audio/load.jai b/src/audio/load.jai index f48b2a3..1c4a271 100644 --- a/src/audio/load.jai +++ b/src/audio/load.jai @@ -1,4 +1,4 @@ Audio_Data :: struct { - channels: u16; + channels: s16; data: [..]float; } diff --git a/src/audio/mixer.jai b/src/audio/mixer.jai index d888288..60a001c 100644 --- a/src/audio/mixer.jai +++ b/src/audio/mixer.jai @@ -50,25 +50,83 @@ Mixer :: struct { tasks : [..]Mixer_Play_Task; } -// Removes tasks from the mixer's task list that -// no longer have a reason to be on the task list. -// Eg. a sound that is .ONESHOT but has already been played. -mixer_clean_tasks :: () { - mixer_lock(*g_mixer); - defer mixer_unlock(*g_mixer); - -} - mixer_add_task :: (task: Mixer_Play_Task) { mixer_lock(*g_mixer); defer mixer_unlock(*g_mixer); - + array_add(*g_mixer.tasks, task); } -mixer_get_samples :: (samples: *float, sampleCount: s32, channelCount: s32) { +mixer_add_task :: (audio: *Audio_Data, bus: Mixer_Bus, mode: Play_Mode) { + mixer_add_task(.{ + audio = audio, + bus = bus, + mode = mode + }); +} + +mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) { mixer_lock(*g_mixer); defer mixer_unlock(*g_mixer); + + total_samples := frame_count * channel_count; + memset(buffer, 0, total_samples * size_of(float)); + + if g_mixer.tasks.count < 1 then return; + for < i : 0..g_mixer.tasks.count-1 { + task := *g_mixer.tasks[i]; + + if !task.audio { + array_unordered_remove_by_index(*g_mixer.tasks, i); + continue; + } + + bus_vol := 1.0; + if task.bus == { + case .MUSIC; bus_vol = g_mixer.config.musicVolume; + case .SOUND_EFFECT; bus_vol = g_mixer.config.soundEffectVolume; + case .DIALOGUE; bus_vol = g_mixer.config.dialogueVolume; + } + + vol := bus_vol * g_mixer.config.masterVolume; + + source_data := task.audio.data; + source_channels := task.audio.channels; + + task_finished := false; + + for f : 0..frame_count-1 { + if task.curSample >= source_data.count { + if task.mode == .ONESHOT { + task_finished = true; + break; + } else { + task.curSample = 0; + } + } + + cursor := task.curSample; + + for ch : 0..channel_count-1 { + out_index := (f * channel_count) + ch; + offset := ifx source_channels == 1 then 0 else ch; + + sample : float = 0; + + if cursor + offset < source_data.count { + sample = source_data[cursor + offset]; + } + + buffer[out_index] += sample * vol; + } + + task.curSample += source_channels; + } + + if task_finished { + array_unordered_remove_by_index(*g_mixer.tasks, i); + } + } } mixer_lock :: (mixer: *Mixer) { diff --git a/src/main.jai b/src/main.jai index 4046986..1969317 100644 --- a/src/main.jai +++ b/src/main.jai @@ -25,6 +25,7 @@ stbi :: #import "stb_image"; #load "events.jai"; #load "load.jai"; #load "ray.jai"; +#load "profiling.jai"; #load "world.jai"; #load "utils.jai"; #load "audio/audio.jai"; @@ -155,12 +156,15 @@ is_in_reflection_pass : bool = false; delta_time_accumulator : float64 = 0; frame :: () { + clear_frame_profiling(); check_and_handle_window_resize(); delta_time = get_time() - last_frame_time; last_frame_time = get_time(); asset_manager_tick(); + add_frame_profiling_point("After asset manager tick"); + if !mandatory_loads_done() then return; if !init_after_mandatory_done { @@ -180,6 +184,8 @@ frame :: () { fonsClearState(state.fons); + add_frame_profiling_point("After loading logic"); + frame_start_time := get_time(); dpis := state.dpi_scale; @@ -200,17 +206,25 @@ frame :: () { } + add_frame_profiling_point("After game ticks"); tick_ui(); + add_frame_profiling_point("After UI tick"); // This populates our render task queue. if !in_editor_view then game_draw(); + add_frame_profiling_point("After game draw"); ui_clear_mouse_occluders(); ui_pass(); + add_frame_profiling_point("After UI draw"); prepare_text(debug_font, tprint("frametime: % ms", latest_frametime * 1000)); draw_prepared_text(debug_font, 10, 10, .{0.0, 1.0, 0.0, 1.0}); draw_editor(); + add_frame_profiling_point("After editor draw"); render(); + add_frame_profiling_point("After rendering"); + + end_frame_profiling(); #if MEM_DEBUG { memory_visualizer_per_frame_update(); } diff --git a/src/profiling.jai b/src/profiling.jai new file mode 100644 index 0000000..e0edaad --- /dev/null +++ b/src/profiling.jai @@ -0,0 +1,93 @@ +/* + Profiling functions for profiling the performance of a single frame. +*/ + +#scope_file + +DO_FRAME_PROFILING :: false; + +Frame_Point :: struct { + name : string; + time : Apollo_Time; + depth : s32; + type : Frame_Point_Type; +} + +Frame_Point_Type :: enum { + POINT; + GROUP_START; + GROUP_END; +} + +add_frame_profiling_point_with_type :: (pointName: string, pointType: Frame_Point_Type) { + array_add(*current_frame_points, .{pointName, get_apollo_time(), current_depth, pointType}); +} + + +print_point :: (name: string, us: s64, depth: s32, type: Frame_Point_Type) { + for 0..depth print(" "); + if type == { + case .POINT; + print("(%): %µs\n", name, us); + case .GROUP_START; + print("GROUP_START(%): %µs\n", name, us); + case .GROUP_END; + print("GROUP_END(%): %µs\n", name, us); + } +} + +current_frame_points : [..]Frame_Point; +current_depth : s32; +#scope_export + +clear_frame_profiling :: () { + #if DO_FRAME_PROFILING { + array_reset_keeping_memory(*current_frame_points); + add_frame_profiling_point("frame_start"); + } +} + +add_frame_profiling_point :: (pointName: string) { + #if DO_FRAME_PROFILING { + add_frame_profiling_point_with_type(pointName, .POINT); + } +} + +start_frame_profiling_group :: (groupName: string) { + #if DO_FRAME_PROFILING { + add_frame_profiling_point_with_type(groupName, .GROUP_START); + current_depth += 1; + } +} + +end_frame_profiling_group :: (groupName: string) { + #if DO_FRAME_PROFILING { + current_depth -= 1; + add_frame_profiling_point_with_type(groupName, .GROUP_END); + } +} + +end_frame_profiling :: () { + #if DO_FRAME_PROFILING { + endTime := get_apollo_time(); + startPoint : Apollo_Time; + startPointSet := false; + + print("\n\n-----Frame profiling report ----\n"); + + add_frame_profiling_point("frame_end"); + + for current_frame_points { + if !startPointSet { + startPoint = it.time; + startPointSet = true; + print_point(it.name, 0, it.depth, it.type); + } else { + startUs := to_microseconds(startPoint); + curUs := to_microseconds(it.time); + print_point(it.name, curUs - startUs, it.depth, it.type); + } + } + } +} + diff --git a/src/rendering/backend_sokol.jai b/src/rendering/backend_sokol.jai index 9b7f5f8..d4da4e2 100644 --- a/src/rendering/backend_sokol.jai +++ b/src/rendering/backend_sokol.jai @@ -143,7 +143,8 @@ backend_draw_trile_positions_gbuffer :: (trile : string, amount : s32, worldConf } backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *World_Config) { - mvp : Matrix4; + start_frame_profiling_group("Draw trile positions"); + mvp : Matrix4; if !in_shadowmap_pass { mvp = create_viewproj(*camera); } else { @@ -191,7 +192,9 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: * sg_apply_uniforms(UB_trile_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) })); sg_apply_uniforms(UB_trile_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params))})); sg_apply_uniforms(UB_trile_world_config, *(sg_range.{ptr = *world_conf, size = size_of(type_of(world_conf))})); + add_frame_profiling_point("After drawing setup"); sg_draw(0, cast(s32) trilegfx.vertex_count, amount); + end_frame_profiling_group("Draw trile positions"); } backend_draw_sky :: (wc: *World_Config) { mvp := create_viewproj(*camera); @@ -324,10 +327,13 @@ backend_draw_ground_gbuf :: (wc: *World_Config) { backend_process_command_buckets :: () { // 1. Set up textures and buffers. + start_frame_profiling_group("Setup"); for render_command_buckets.setup { backend_handle_command(it); } + end_frame_profiling_group("Setup"); + start_frame_profiling_group("Shadow pass"); // 2. Shadow pass if current_world_config != null { shadow_mvp = create_shadow_viewproj(*camera, current_world_config); @@ -340,7 +346,9 @@ backend_process_command_buckets :: () { in_shadowmap_pass = false; } current_trile_offset_index = 0; // This is not optimal, but it is nice and simple. + end_frame_profiling_group("Shadow pass"); + start_frame_profiling_group("Reflection pass"); // 3. Reflection pass sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = gPipelines.plane.attachments})); in_reflection_pass = true; @@ -350,8 +358,10 @@ backend_process_command_buckets :: () { in_reflection_pass = false; sg_end_pass(); current_trile_offset_index = 0; // This is not optimal, but it is nice and simple. + end_frame_profiling_group("Reflection pass"); // 4. G-Buffer pass + start_frame_profiling_group("G-Buffer pass"); in_gbuffer_pass = true; sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear_gbuf, attachments = g_gbuf_attachments})); for render_command_buckets.gbuffer { @@ -360,7 +370,9 @@ backend_process_command_buckets :: () { sg_end_pass(); in_gbuffer_pass = false; current_trile_offset_index = 0; // This is not optimal, but it is nice and simple. - + end_frame_profiling_group("G-Buffer pass"); + + start_frame_profiling_group("SSAO pass"); sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_postprocess_attach_a })); sg_apply_pipeline(gPipelines.ssao.pipeline); gPipelines.ssao.bind.images[0] = g_gbuf_position; @@ -375,9 +387,9 @@ backend_process_command_buckets :: () { sg_apply_uniforms(UB_ssao_fs_params, *(sg_range.{ ptr = *ssao_fs_uniform, size = size_of(type_of(ssao_fs_uniform)) })); sg_draw(0, 6, 1); sg_end_pass(); + end_frame_profiling_group("SSAO pass"); - - + start_frame_profiling_group("SSAO blur pass"); sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_ssao_attachments })); sg_apply_pipeline(gPipelines.op.pipeline); op_uniform : Op_Fs_Params; @@ -388,18 +400,24 @@ backend_process_command_buckets :: () { sg_apply_bindings(*gPipelines.op.bind); sg_draw(0, 6, 1); sg_end_pass(); + end_frame_profiling_group("SSAO blur pass"); // 5. Main pass + start_frame_profiling_group("Main pass"); sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_rendertex_attachments})); for render_command_buckets.main { backend_handle_command(it); } sg_end_pass(); current_trile_offset_index = 0; // This is not optimal, but it is nice and simple. + end_frame_profiling_group("Main pass"); + start_frame_profiling_group("Postprocess pass"); bloom_process(); dof_process(); - + end_frame_profiling_group("Postprocess pass"); + + start_frame_profiling_group("Final pass"); // Begin drawing to swapchain sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, swapchain = cast,force(sg_swapchain) sglue_swapchain() })); @@ -419,6 +437,7 @@ backend_process_command_buckets :: () { arb_tri_flush(); sg_end_pass(); + end_frame_profiling_group("Final pass"); sg_commit(); diff --git a/src/rendering/core.jai b/src/rendering/core.jai index a48bae4..db626dc 100644 --- a/src/rendering/core.jai +++ b/src/rendering/core.jai @@ -9,7 +9,10 @@ init_rendering :: () { // Core rendering function, runs several passes. render :: () { tasks_to_commands(); + add_frame_profiling_point("After task->command conversion"); + start_frame_profiling_group("Rendering command bucket processing"); process_command_buckets(); + end_frame_profiling_group("Rendering command bucket processing"); } #scope_file diff --git a/src/shaders/jai/shader_ssao.jai b/src/shaders/jai/shader_ssao.jai index eb4c6cd..62f95ec 100644 --- a/src/shaders/jai/shader_ssao.jai +++ b/src/shaders/jai/shader_ssao.jai @@ -101,7 +101,7 @@ vs_ssao_source_glsl430 := u8.[ vec3 _60 = normalize(_51 - (_35 * dot(_51, _35))); mat3 _85 = mat3(_60, cross(_35, _60), _35); float occlusion = 0.0; - for (int i = 0; i < 64; i++) + for (int i = 0; i < 16; i++) { vec3 _121 = _26 + ((_85 * ssao_fs_params[i * 1 + 4].xyz) * 0.5); vec4 _133 = mat4(ssao_fs_params[0], ssao_fs_params[1], ssao_fs_params[2], ssao_fs_params[3]) * vec4(_121, 1.0); @@ -176,7 +176,7 @@ fs_ssao_source_glsl430 := u8.[ 0x2c,0x20,0x5f,0x33,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, 0x74,0x20,0x6f,0x63,0x63,0x6c,0x75,0x73,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x2e, 0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6f,0x72,0x20,0x28,0x69,0x6e,0x74,0x20, - 0x69,0x20,0x3d,0x20,0x30,0x3b,0x20,0x69,0x20,0x3c,0x20,0x36,0x34,0x3b,0x20,0x69, + 0x69,0x20,0x3d,0x20,0x30,0x3b,0x20,0x69,0x20,0x3c,0x20,0x31,0x36,0x3b,0x20,0x69, 0x2b,0x2b,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x5f,0x31,0x32,0x31,0x20,0x3d,0x20,0x5f,0x32, 0x36,0x20,0x2b,0x20,0x28,0x28,0x5f,0x38,0x35,0x20,0x2a,0x20,0x73,0x73,0x61,0x6f, @@ -286,7 +286,7 @@ vs_ssao_source_glsl300es := u8.[ highp vec3 _60 = normalize(_51 - (_35 * dot(_51, _35))); highp mat3 _85 = mat3(_60, cross(_35, _60), _35); highp float occlusion = 0.0; - for (int i = 0; i < 64; i++) + for (int i = 0; i < 16; i++) { highp vec3 _121 = _26 + ((_85 * ssao_fs_params[i * 1 + 4].xyz) * 0.5); highp vec4 _133 = mat4(ssao_fs_params[0], ssao_fs_params[1], ssao_fs_params[2], ssao_fs_params[3]) * vec4(_121, 1.0); @@ -364,7 +364,7 @@ fs_ssao_source_glsl300es := u8.[ 0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x6f,0x63,0x63,0x6c,0x75,0x73,0x69,0x6f,0x6e, 0x20,0x3d,0x20,0x30,0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6f,0x72,0x20, 0x28,0x69,0x6e,0x74,0x20,0x69,0x20,0x3d,0x20,0x30,0x3b,0x20,0x69,0x20,0x3c,0x20, - 0x36,0x34,0x3b,0x20,0x69,0x2b,0x2b,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20, + 0x31,0x36,0x3b,0x20,0x69,0x2b,0x2b,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63, 0x33,0x20,0x5f,0x31,0x32,0x31,0x20,0x3d,0x20,0x5f,0x32,0x36,0x20,0x2b,0x20,0x28, 0x28,0x5f,0x38,0x35,0x20,0x2a,0x20,0x73,0x73,0x61,0x6f,0x5f,0x66,0x73,0x5f,0x70, @@ -517,7 +517,7 @@ vs_ssao_source_metal_macos := u8.[ float3 _60 = fast::normalize(_51 - (_35 * dot(_51, _35))); float3x3 _85 = float3x3(_60, cross(_35, _60), _35); float occlusion = 0.0; - for (int i = 0; i < 64; i++) + for (int i = 0; i < 16; i++) { float3 _121 = _26 + ((_85 * _109.samples[i].xyz) * 0.5); float4 _133 = _109.projection * float4(_121, 1.0); @@ -613,7 +613,7 @@ fs_ssao_source_metal_macos := u8.[ 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x6f,0x63,0x63,0x6c,0x75,0x73, 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, 0x6f,0x72,0x20,0x28,0x69,0x6e,0x74,0x20,0x69,0x20,0x3d,0x20,0x30,0x3b,0x20,0x69, - 0x20,0x3c,0x20,0x36,0x34,0x3b,0x20,0x69,0x2b,0x2b,0x29,0x0a,0x20,0x20,0x20,0x20, + 0x20,0x3c,0x20,0x31,0x36,0x3b,0x20,0x69,0x2b,0x2b,0x29,0x0a,0x20,0x20,0x20,0x20, 0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33, 0x20,0x5f,0x31,0x32,0x31,0x20,0x3d,0x20,0x5f,0x32,0x36,0x20,0x2b,0x20,0x28,0x28, 0x5f,0x38,0x35,0x20,0x2a,0x20,0x5f,0x31,0x30,0x39,0x2e,0x73,0x61,0x6d,0x70,0x6c, diff --git a/src/shaders/shader_ssao.glsl b/src/shaders/shader_ssao.glsl index e5b890b..ca045e8 100644 --- a/src/shaders/shader_ssao.glsl +++ b/src/shaders/shader_ssao.glsl @@ -38,7 +38,7 @@ void main() { mat3 tbn = mat3(tangent, bitangent, normal); float occlusion = 0.0; - for(int i = 0; i < 64; i++) { + for(int i = 0; i < 16; i++) { vec3 sample_pos = tbn * samples[i].xyz; sample_pos = frag_pos + sample_pos * 0.5; diff --git a/src/time.jai b/src/time.jai index 4049a6d..df53691 100644 --- a/src/time.jai +++ b/src/time.jai @@ -1,3 +1,16 @@ +get_apollo_time :: () -> Apollo_Time { + #if OS != .WASM { + // Since we are making a game engine, we want the monotonic time. + // This means that we won't take corrections to the time into account + // since this could make a delta time negative and that would be bad. + return current_time_monotonic(); + } else { + // Implement a WASM Apollo_Time backend here. + #assert false; + } + +} + get_time :: () -> float64 { return stm_sec(stm_now()); }