work on audio mixer implementation

This commit is contained in:
Tuomas Katajisto 2026-02-08 12:17:59 +02:00
parent b8b1aaaac5
commit 81b1a407ae
12 changed files with 227 additions and 39 deletions

View File

@ -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 {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
Audio_Data :: struct {
channels: u16;
channels: s16;
data: [..]float;
}

View File

@ -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) {

View File

@ -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();
}

93
src/profiling.jai Normal file
View File

@ -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);
}
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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());
}