|
#include "game.h" |
|
#include "game.c" |
|
|
|
#include "sdl_game.h" |
|
|
|
global_variable struct sdl_audio_ring_buffer GlobalAudioRingBuffer; |
|
|
|
internal void |
|
sdl_audio_callback(void *user_data, u8 *audio_data, int length) |
|
{ |
|
struct sdl_audio_ring_buffer *ring_buffer = (struct sdl_audio_ring_buffer *)user_data; |
|
|
|
i32 region_1_size = length; |
|
i32 region_2_size = 0; |
|
/* Not enough space on ring buffer region */ |
|
if ( (ring_buffer->play_cursor + length) > ring_buffer->size) |
|
{ |
|
region_1_size = ring_buffer->size - ring_buffer->play_cursor; |
|
region_2_size = length - region_1_size; |
|
} |
|
|
|
memcpy(audio_data, (u8*)(ring_buffer->data) + ring_buffer->play_cursor, region_1_size); |
|
memcpy(audio_data + region_1_size, ring_buffer->data, region_2_size); |
|
ring_buffer->play_cursor = (ring_buffer->play_cursor + length) % ring_buffer->size; |
|
ring_buffer->write_cursor = (ring_buffer->play_cursor + length) % ring_buffer->size; |
|
} |
|
|
|
internal void |
|
sdl_init_audio(struct sdl_sound_output *sound_output) |
|
{ |
|
SDL_AudioSpec audio_settings = {}; |
|
|
|
audio_settings.freq = sound_output->samples_per_second; |
|
audio_settings.format = AUDIO_S16LSB; |
|
audio_settings.channels = 2; |
|
audio_settings.samples = 512; |
|
audio_settings.callback = &sdl_audio_callback; |
|
audio_settings.userdata = &GlobalAudioRingBuffer; |
|
|
|
GlobalAudioRingBuffer.size = sound_output->secondary_buffer_size; |
|
GlobalAudioRingBuffer.play_cursor = 0; |
|
GlobalAudioRingBuffer.write_cursor = 0; |
|
GlobalAudioRingBuffer.data = mmap(NULL, |
|
sound_output->secondary_buffer_size, |
|
PROT_READ | PROT_WRITE, |
|
MAP_ANONYMOUS | MAP_PRIVATE, |
|
-1, |
|
0); |
|
|
|
if(!GlobalAudioRingBuffer.data) |
|
{ |
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, |
|
"%s: mmap(GlobalAudioRingBuffer.data)\n", __func__); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
sound_output->audio_device = SDL_OpenAudioDevice(NULL, 0, &audio_settings, NULL, 0); |
|
SDL_Log("%s: Audio opened ID (%d): freq: %d, channels: %d, samples: %d, bufsz: %d\n", |
|
__func__, |
|
sound_output->audio_device, |
|
audio_settings.freq, |
|
audio_settings.channels, |
|
audio_settings.samples, |
|
audio_settings.size); |
|
|
|
if (audio_settings.format != AUDIO_S16LSB) |
|
{ |
|
SDL_LogError (SDL_LOG_CATEGORY_APPLICATION, |
|
"%s: SDL_OpenAudio(): Could not get S16LE format\n", |
|
__func__); |
|
SDL_CloseAudioDevice(sound_output->audio_device); |
|
sound_output->audio_device = 0; |
|
} |
|
|
|
SDL_PauseAudioDevice(sound_output->audio_device, SDL_FALSE); |
|
} |
|
|
|
internal void |
|
sdl_SoundGetCursors(struct sdl_audio_ring_buffer *ring_buffer, |
|
struct sdl_sound_output *sound_output, |
|
int *byte_to_lock, int *bytes_to_write) |
|
{ |
|
|
|
SDL_LockAudioDevice(sound_output->audio_device); |
|
*byte_to_lock = (sound_output->running_sample_index * sound_output->bytes_per_sample) % sound_output->secondary_buffer_size; |
|
|
|
int target_cursor = 0; |
|
target_cursor = (ring_buffer->play_cursor + (sound_output->safety_bytes * sound_output->bytes_per_sample)) % sound_output->secondary_buffer_size; |
|
|
|
|
|
if(*byte_to_lock > target_cursor) |
|
{ |
|
*bytes_to_write = (sound_output->secondary_buffer_size - *byte_to_lock); |
|
*bytes_to_write += target_cursor; |
|
} |
|
else |
|
{ |
|
*bytes_to_write = target_cursor - *byte_to_lock; |
|
} |
|
SDL_UnlockAudioDevice(sound_output->audio_device); |
|
} |
|
|
|
internal void |
|
sdl_FillSoundBuffer(struct sdl_sound_output *sound_output, |
|
int byte_to_lock, |
|
int bytes_to_write, |
|
struct game_sound_output_buffer *sound_buffer) |
|
{ |
|
i16 *samples = sound_buffer->samples; |
|
i16 *sample_out = NULL; |
|
|
|
/* Calculate region sizes */ |
|
void *region1 = (u8*)GlobalAudioRingBuffer.data + byte_to_lock; |
|
int region1_size = bytes_to_write; |
|
|
|
if(region1_size + byte_to_lock > sound_output->secondary_buffer_size) |
|
region1_size = sound_output->secondary_buffer_size - byte_to_lock; |
|
|
|
void *region2 = GlobalAudioRingBuffer.data; |
|
int region2_size = bytes_to_write - region1_size; |
|
|
|
/* Fill region 1 */ |
|
int region1_sample_count = region1_size / sound_output->bytes_per_sample; |
|
sample_out = (i16*)region1; |
|
for (int i = 0; i < region1_sample_count; ++i) |
|
{ |
|
*sample_out++ = *samples++; // L |
|
*sample_out++ = *samples++; // R |
|
++sound_output->running_sample_index; |
|
} |
|
|
|
/* Fill region 2 */ |
|
int region2_sample_count = region2_size / sound_output->bytes_per_sample; |
|
sample_out = (i16*)region2; |
|
for (int i = 0; i < region2_sample_count; ++i) |
|
{ |
|
*sample_out++ = *samples; // L |
|
*sample_out++ = *samples; // R |
|
++sound_output->running_sample_index; |
|
} |
|
} |
|
|
|
internal int |
|
sdl_GetWindowRefreshRate(SDL_Window *Window) |
|
{ |
|
SDL_DisplayMode Mode; |
|
int DisplayIndex = SDL_GetWindowDisplayIndex(Window); |
|
int DefaultRefreshRate = 60; |
|
if (SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0) |
|
return DefaultRefreshRate; |
|
else if(Mode.refresh_rate == 0) |
|
return DefaultRefreshRate; |
|
else |
|
return Mode.refresh_rate; |
|
} |
|
|
|
internal void |
|
sdl_perf_init(struct sdl_performance_counters *perf) |
|
{ |
|
//perf->game_update_hz = sdl_GetWindowRefreshRate(window); |
|
perf->game_update_hz = 60; |
|
perf->target_seconds_per_frame = 1.0f/(f64)(perf->game_update_hz); |
|
perf->last_counter = SDL_GetPerformanceCounter(); |
|
perf->last_cycle_count = _rdtsc(); |
|
perf->performance_count_frequency = SDL_GetPerformanceFrequency(); |
|
} |
|
|
|
|
|
internal SDL_Window * |
|
sdl_InitGame(u16 screen_width, u16 screen_height) |
|
{ |
|
int retval; |
|
int sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO; |
|
int wnd_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; |
|
|
|
retval = SDL_Init(sdl_flags); |
|
if(retval < 0) |
|
{ |
|
SDL_LogError (SDL_LOG_CATEGORY_APPLICATION, |
|
"%s: SDL_Init(): %s\n", |
|
__func__, SDL_GetError()); |
|
return NULL; |
|
} |
|
|
|
SDL_Window *window; |
|
window = SDL_CreateWindow("HMH", |
|
SDL_WINDOWPOS_UNDEFINED, |
|
SDL_WINDOWPOS_UNDEFINED, |
|
screen_width, |
|
screen_height, |
|
wnd_flags); |
|
if(!window) |
|
{ |
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, |
|
"%s: SDL_CreateWindow(): %s\n", |
|
__func__, SDL_GetError()); |
|
return NULL; |
|
} |
|
|
|
return window; |
|
} |
|
|
|
|
|
internal f64 |
|
sdl_GetSecondsElapsed(u64 OldCounter, u64 CurrentCounter, u64 PerfCountFrequency) |
|
{ |
|
f64 result = ((f64)(CurrentCounter - OldCounter) / (f64)PerfCountFrequency); |
|
return result; |
|
} |
|
|
|
internal void |
|
sdl_WaitFrame(struct sdl_performance_counters *perf) |
|
{ |
|
if(sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency) < perf->target_seconds_per_frame) |
|
{ |
|
i32 TimeToSleep = ((perf->target_seconds_per_frame - sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency)) * 1000) - 1; |
|
if (TimeToSleep > 0) |
|
{ |
|
SDL_Delay(TimeToSleep); |
|
} |
|
|
|
while (sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency) < perf->target_seconds_per_frame) |
|
{ |
|
// Waiting... |
|
} |
|
} |
|
|
|
perf->end_counter = SDL_GetPerformanceCounter(); |
|
} |
|
|
|
internal void |
|
sdl_EvalPerformance(struct sdl_performance_counters *perf) |
|
{ |
|
perf->end_cycle_count = _rdtsc(); |
|
perf->counter_elapsed = perf->end_counter - perf->last_counter; |
|
perf->cycles_elapsed = perf->end_cycle_count - perf->last_cycle_count; |
|
perf->ms_per_frame = 1000.0 * (f64)(perf->counter_elapsed) / (f64)(perf->performance_count_frequency); |
|
perf->fps = (f64)(perf->performance_count_frequency) / (f64)(perf->counter_elapsed); |
|
perf->last_cycle_count = perf->end_cycle_count; |
|
perf->last_counter = perf->end_counter; |
|
} |
|
|
|
int |
|
main (void) |
|
{ |
|
int exitval = EXIT_FAILURE; |
|
int retval = RETURN_SUCCESS; |
|
|
|
SDL_Window *window = sdl_InitGame(320, 240); |
|
if(!window) goto __EXIT__; |
|
|
|
struct sdl_performance_counters perf = {}; |
|
sdl_perf_init(&perf); |
|
|
|
SDL_Log("Refresh rate is %d Hz\n", perf.game_update_hz); |
|
SDL_Log("Target FPS is %f Hz\n", perf.target_seconds_per_frame); |
|
|
|
struct sdl_sound_output sound_output = {}; |
|
sound_output.audio_device = 0; |
|
sound_output.samples_per_second = 48000; |
|
sound_output.channels = 2; |
|
sound_output.bytes_per_sample = sizeof(i16) * sound_output.channels; |
|
sound_output.secondary_buffer_size = sound_output.samples_per_second * sound_output.bytes_per_sample; |
|
sound_output.running_sample_index = 0; |
|
sound_output.safety_bytes = Align16((int)((f32)sound_output.secondary_buffer_size / ((f32)perf.game_update_hz / 3.0f))); |
|
|
|
sdl_init_audio(&sound_output); |
|
|
|
size_t max_possible_overrun_samples = 8; |
|
size_t max_possible_overrun_bytes = max_possible_overrun_samples * sound_output.bytes_per_sample; |
|
i16 *samples = (i16 *)mmap(NULL, |
|
sound_output.secondary_buffer_size + max_possible_overrun_bytes, |
|
PROT_READ | PROT_WRITE, |
|
MAP_ANONYMOUS | MAP_PRIVATE, |
|
-1, |
|
0); |
|
|
|
bool running = true; |
|
while(running == true) |
|
{ |
|
SDL_Event event; |
|
while(SDL_PollEvent(&event)) |
|
{ |
|
switch(event.type) |
|
{ |
|
case SDL_QUIT: { running = false; break; } |
|
default: break; |
|
} |
|
} |
|
|
|
int byte_to_lock = 0; |
|
int bytes_to_write = 0; |
|
sdl_SoundGetCursors(&GlobalAudioRingBuffer, |
|
&sound_output, |
|
&byte_to_lock, &bytes_to_write); |
|
|
|
struct game_sound_output_buffer sound_buffer = {0}; |
|
sound_buffer.samples_per_second = sound_output.samples_per_second; |
|
sound_buffer.sample_count = Align8(bytes_to_write / sound_output.bytes_per_sample); |
|
sound_buffer.samples = samples; |
|
bytes_to_write = sound_buffer.sample_count * sound_output.bytes_per_sample; |
|
|
|
GameGetSoundSamples(&sound_buffer); |
|
sdl_FillSoundBuffer(&sound_output, byte_to_lock, bytes_to_write, &sound_buffer); |
|
|
|
sdl_WaitFrame(&perf); |
|
sdl_EvalPerformance(&perf); |
|
} |
|
|
|
exitval = EXIT_SUCCESS; |
|
__EXIT__: |
|
if (sound_output.audio_device > 0) { |
|
munmap(GlobalAudioRingBuffer.data, sound_output.secondary_buffer_size); |
|
munmap(samples, sound_output.secondary_buffer_size + max_possible_overrun_bytes); |
|
SDL_CloseAudioDevice(sound_output.audio_device); |
|
} |
|
SDL_DestroyWindow(window); |
|
SDL_Quit(); |
|
return exitval; |
|
} |