diff --git a/TODO b/TODO new file mode 100644 index 0000000..19eea86 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ + +- Audio + - Optimize + - Spam simd + - Concurrent jobs for players? \ No newline at end of file diff --git a/build.bat b/build.bat index a93248e..fecb595 100644 --- a/build.bat +++ b/build.bat @@ -6,6 +6,6 @@ mkdir build pushd build -clang -g -o cgame.exe ../build.c -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -lkernel32 -lgdi32 -luser32 -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -femit-all-decls +clang -g -o cgame.exe ../build.c -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -lkernel32 -lgdi32 -luser32 -lruntimeobject -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -lole32 -lavrt -lksuser -femit-all-decls popd \ No newline at end of file diff --git a/build.c b/build.c index 18577c8..861eb33 100644 --- a/build.c +++ b/build.c @@ -3,6 +3,8 @@ /// // Build config stuff +#define OOGABOOGA_DEV 1 + #define INITIAL_PROGRAM_MEMORY_SIZE MB(5) typedef struct Context_Extra { @@ -26,12 +28,13 @@ typedef struct Context_Extra { // // this is a minimal starting point for new projects. Copy & rename to get started -#include "oogabooga/examples/minimal_game_loop.c" +// #include "oogabooga/examples/minimal_game_loop.c" // #include "oogabooga/examples/text_rendering.c" // #include "oogabooga/examples/custom_logger.c" // #include "oogabooga/examples/renderer_stress_test.c" // #include "oogabooga/examples/tile_game.c" +#include "oogabooga/examples/audio_test.c" // This is where you swap in your own project! // #include "entry_yourepicgamename.c" diff --git a/build_release.bat b/build_release.bat index 5d44482..06d5bc2 100644 --- a/build_release.bat +++ b/build_release.bat @@ -9,7 +9,7 @@ pushd build mkdir release pushd release -clang -o cgame.exe ../../build.c -Ofast -DNDEBUG -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -Wno-deprecated-declarations -lgdi32 -luser32 -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -finline-functions -finline-hint-functions -ffast-math -fno-math-errno -funsafe-math-optimizations -freciprocal-math -ffinite-math-only -fassociative-math -fno-signed-zeros -fno-trapping-math -ftree-vectorize -fomit-frame-pointer -funroll-loops -fno-rtti -fno-exceptions +clang -o cgame.exe ../../build.c -Ofast -DNDEBUG -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -Wno-deprecated-declarations -lkernel32 -lgdi32 -luser32 -lruntimeobject -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -lole32 -lavrt -lksuser -finline-functions -finline-hint-functions -ffast-math -fno-math-errno -funsafe-math-optimizations -freciprocal-math -ffinite-math-only -fassociative-math -fno-signed-zeros -fno-trapping-math -ftree-vectorize -fomit-frame-pointer -funroll-loops -fno-rtti -fno-exceptions popd popd \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 897bdf4..bd354c2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,21 @@ + +## v0.01.001 - AUDIO! + - Added audio sources + - File stream sources & Fully decoded sources + - 16-bit int & 32-bit float support + - WAV & OGG support + - Usage: + bool audio_source_init_file_stream(*src, path, bit_width, allocator) + bool audio_source_init_file_decode(*src, path, bit_width, allocator) + void audio_source_destroy(*src) + - Misc + - Win32 audio impl + - Make default logger thread safe + - Rename tm_scope_cycles & tm_scope_cycles_xxx -> tm_scope & tm_scope_xxx + - Minor cleanups + + + ## v0.00.005 - Z layers Renderer: diff --git a/oogabooga/audio.c b/oogabooga/audio.c index 1760d13..e5d8e03 100644 --- a/oogabooga/audio.c +++ b/oogabooga/audio.c @@ -1,37 +1,308 @@ -/* + bool check_wav_header(string data) { - return string_starts_with(data, STR("RIFFWAVE")); + return string_starts_with(data, STR("RIFF")); } bool check_ogg_header(string data) { - return string_starts_with(data, STR("oggs")); + return string_starts_with(data, STR("OggS")); } -typedef enum Audio_Format_Type { - AUDIO_FORMAT_TYPE_UNKNOWN, - AUDIO_FORMAT_TYPE_U8, - AUDIO_FORMAT_TYPE_S16, - AUDIO_FORMAT_TYPE_S24, - AUDIO_FORMAT_TYPE_S32, - AUDIO_FORMAT_TYPE_F32 -} Audio_Format_Type; +// Supporting more than s16 and f32 +// If it's a real thing that there's audio devices which support neither then I will be surprised +// The only format I might consider adding is S32 if it turns out people want VERY detailed audio +typedef enum Audio_Format_Bits { + AUDIO_BITS_16, // this will be s16 + AUDIO_BITS_32, // this will be f32 +} Audio_Format_Bits; +u64 get_audio_bit_width_byte_size(Audio_Format_Bits b) { + switch (b) { + case AUDIO_BITS_32: return 4; break; + case AUDIO_BITS_16: return 2; break; + } + panic(""); +} typedef struct Audio_Format { - Audio_Format_Type type; + Audio_Format_Bits bit_width; int channels; int sample_rate; } Audio_Format; -u64 get_audio_format_component_byte_size(Audio_Format_Type format) { - switch (format) { - case AUDIO_FORMAT_TYPE_F32: return 4; break; - case AUDIO_FORMAT_TYPE_S32: return 4; break; - case AUDIO_FORMAT_TYPE_S24: return 3; break; - case AUDIO_FORMAT_TYPE_S16: return 2; break; - case AUDIO_FORMAT_TYPE_U8: return 1; break; - case AUDIO_FORMAT_TYPE_UNKNOWN: return 0; break; - } - panic(""); +// I don't see a big reason for you to use anything else than WAV and OGG. +// If you use mp3 that's just not very smart. +// Ogg has better quality AND better compression AND you don't need any licensing (which you need for mp3) +// https://convertio.co/mp3-ogg/ +// I will probably add mp3 support at some point for compatibility reasonavg. +// - Charlie 2024-07-11 +typedef enum Audio_Decoder_Kind { + AUDIO_DECODER_WAV, + AUDIO_DECODER_OGG +} Audio_Decoder_Kind; + +typedef enum Audio_Source_Kind { + AUDIO_SOURCE_FILE_STREAM, + AUDIO_SOURCE_MEMORY, // Raw pcm frames +} Audio_Source_Kind; + +typedef struct Audio_Source { + + Audio_Source_Kind kind; + Audio_Format format; + u64 number_of_frames; + Allocator allocator; + string compressed_data; + + // For file stream + Audio_Decoder_Kind decoder; + union { + drwav wav; + stb_vorbis *ogg; + }; + + // For memory source + void *pcm_frames; + +} Audio_Source; + +int +_audio_file_stream_sample_frames(Audio_Source *src, u64 first_frame_index, + u64 number_of_frames, void *output_buffer); + +bool +audio_source_init_file_stream(Audio_Source *src, string path, Audio_Format_Bits bit_width, + Allocator allocator) { + *src = ZERO(Audio_Source); + + src->allocator = allocator; + src->kind = AUDIO_SOURCE_FILE_STREAM; + + string data; + bool read_ok = os_read_entire_file(path, &data, allocator); + src->compressed_data = data; + + third_party_allocator = allocator; + + if (!read_ok) { + third_party_allocator = ZERO(Allocator); + return false; + } + + if (check_wav_header(data)) { + drwav_bool32 init_ok = drwav_init_memory(&src->wav, data.data, data.count, null); + if (!init_ok) { + third_party_allocator = ZERO(Allocator); + return false; + } + src->decoder = AUDIO_DECODER_WAV; + + src->format.channels = src->wav.fmt.channels; + src->format.sample_rate = src->wav.fmt.sampleRate; + src->number_of_frames = src->wav.totalPCMFrameCount; + } else if (check_ogg_header(data)) { + int err; + src->ogg = stb_vorbis_open_memory(data.data, data.count, &err, null); + if (!src->ogg) { + third_party_allocator = ZERO(Allocator); + return false; + } + src->decoder = AUDIO_DECODER_OGG; + + stb_vorbis_info info = stb_vorbis_get_info(src->ogg); + src->format.channels = info.channels; + src->format.sample_rate = info.sample_rate; + src->number_of_frames = stb_vorbis_stream_length_in_samples(src->ogg); + } else { + log_error("Error in init_audio_source_file_stream(): Unrecognized audio format in file '%s'. We currently support WAV and OGG (Vorbis).", path); + third_party_allocator = ZERO(Allocator); + return false; + } + + src->format.bit_width = bit_width; + + third_party_allocator = ZERO(Allocator); + + return true; } + +bool +audio_source_init_file_decode(Audio_Source *src, string path, Audio_Format_Bits bit_width, + Allocator allocator) { + if (!audio_source_init_file_stream(src, path, bit_width, allocator)) return false; + src->kind = AUDIO_SOURCE_MEMORY; + + u64 comp_size = get_audio_bit_width_byte_size(src->format.bit_width); + u64 total_size = src->number_of_frames * src->format.channels * comp_size; + src->pcm_frames = alloc(allocator, total_size); + + int num_retrieved = _audio_file_stream_sample_frames(src, 0, src->number_of_frames, src->pcm_frames); + assert(num_retrieved == src->number_of_frames, "decoder failed failed"); + + return true; +} + +void +audio_source_destroy(Audio_Source *src) { + switch (src->kind) { + case AUDIO_SOURCE_FILE_STREAM: { + if (src->pcm_frames) dealloc(src->allocator, src->pcm_frames); + break; + } + case AUDIO_SOURCE_MEMORY: { + dealloc(src->allocator, src->pcm_frames); + break; + } + } + + + third_party_allocator = src->allocator; + switch (src->decoder) { + case AUDIO_DECODER_WAV: { + drwav_uninit(&src->wav); + break; + } + case AUDIO_DECODER_OGG: { + stb_vorbis_close(src->ogg); + break; + } + } + third_party_allocator = ZERO(Allocator); + + dealloc_string(src->allocator, src->compressed_data); +} + +int +_audio_file_stream_sample_frames(Audio_Source *src, u64 first_frame_index, + u64 number_of_frames, void *output_buffer) { + third_party_allocator = src->allocator; + int retrieved = 0; + switch (src->decoder) { + case AUDIO_DECODER_WAV: + bool seek_ok = drwav_seek_to_pcm_frame(&src->wav, first_frame_index); + assert(seek_ok); + switch(src->format.bit_width) { + case AUDIO_BITS_32: { + retrieved = drwav_read_pcm_frames_f32( + &src->wav, + number_of_frames, + (f32*)output_buffer + ); + break; + } + case AUDIO_BITS_16: { + retrieved = drwav_read_pcm_frames_s16( + &src->wav, + number_of_frames, + (s16*)output_buffer + ); + break; + } + default: panic("Invalid bits value"); + } break; // case AUDIO_DECODER_WAV: + case AUDIO_DECODER_OGG: + seek_ok = stb_vorbis_seek(src->ogg, first_frame_index); + assert(seek_ok); + switch(src->format.bit_width) { + case AUDIO_BITS_32: { + retrieved = stb_vorbis_get_samples_float_interleaved( + src->ogg, + src->format.channels, + (f32*)output_buffer, + number_of_frames * src->format.channels + ); + break; + } + case AUDIO_BITS_16: { + retrieved = stb_vorbis_get_samples_short_interleaved( + src->ogg, + src->format.channels, + (s16*)output_buffer, + number_of_frames * src->format.channels + ); + break; + } + default: panic("Invalid bits value"); + } break; // case AUDIO_DECODER_OGG: + default: panic("Invalid decoder value"); + } + + third_party_allocator = ZERO(Allocator); + return retrieved; +} + +u64 // New frame index +audio_source_sample_frames(Audio_Source *src, u64 first_frame_index, u64 number_of_frames, + void *output_buffer, bool looping) { + + u64 comp_size = get_audio_bit_width_byte_size(src->format.bit_width); + u64 frame_size = comp_size * src->format.channels; + u64 output_size = number_of_frames * frame_size; + + if (first_frame_index == src->number_of_frames) { + return first_frame_index; + } + + assert(first_frame_index < src->number_of_frames, "Invalid first_frame_index"); + + u64 new_index = first_frame_index; + + + + int num_retrieved; + switch (src->kind) { + case AUDIO_SOURCE_FILE_STREAM: { + + num_retrieved = _audio_file_stream_sample_frames( + src, + first_frame_index, + number_of_frames, + output_buffer + ); + new_index += num_retrieved; + + assert(num_retrieved <= number_of_frames); + + if (num_retrieved < number_of_frames) { + void *dst_remain = ((u8*)output_buffer) + num_retrieved*frame_size; + if (looping) { + num_retrieved = _audio_file_stream_sample_frames( + src, + 0, + number_of_frames-num_retrieved, + dst_remain + ); + new_index = number_of_frames-num_retrieved; + } else { + memset(dst_remain, 0, frame_size * (number_of_frames - num_retrieved)); + } + } + + break; // case AUDIO_SOURCE_FILE_STREAM + } + case AUDIO_SOURCE_MEMORY: { + s64 first_number_of_frames = min(number_of_frames, src->number_of_frames-first_frame_index); + void *src_pcm_start = (u8*)src->pcm_frames + first_frame_index*frame_size; + + memcpy(output_buffer, src_pcm_start, first_number_of_frames*frame_size); + new_index += first_number_of_frames; + + s64 remainder = number_of_frames-first_number_of_frames; + if (remainder > 0) { + void *dst_remain = (u8*)output_buffer + first_number_of_frames*frame_size; + + if (looping) { + memcpy(dst_remain, src->pcm_frames, frame_size*remainder); + new_index = remainder; + } else { + memset(dst_remain, 0, frame_size*remainder); + } + } + + break; + } + } + + return new_index; +} + #define U8_MAX 255 #define S16_MIN -32768 #define S16_MAX 32767 @@ -40,8 +311,9 @@ u64 get_audio_format_component_byte_size(Audio_Format_Type format) { #define S32_MIN -2147483648 #define S32_MAX 2147483647 -void mix_frames(void *dst, void *src, u64 frame_count, Audio_Format format) { - u64 comp_size = get_audio_format_component_byte_size(format.type); +void +mix_frames(void *dst, void *src, u64 frame_count, Audio_Format format) { + u64 comp_size = get_audio_bit_width_byte_size(format.bit_width); u64 frame_size = comp_size * format.channels; u64 output_size = frame_count * frame_size; @@ -57,54 +329,300 @@ void mix_frames(void *dst, void *src, u64 frame_count, Audio_Format format) { void *src_sample = (u8*)src + frame*frame_size + c*comp_size; void *dst_sample = (u8*)dst + frame*frame_size + c*comp_size; - switch (format.type) { - case AUDIO_FORMAT_TYPE_F32: { + switch (format.bit_width) { + case AUDIO_BITS_32: { *((f32*)dst_sample) += *((f32*)src_sample); } - case AUDIO_FORMAT_TYPE_S32: { - s32 dst_int = *((s32*)dst_sample); - s32 src_int = *((s32*)src_sample); - *((s32*)dst_sample) = (s32)clamp((s64)(dst_int + src_int), S32_MIN, S32_MAX); - break; - } - case AUDIO_FORMAT_TYPE_S24: { - s64 src_int = 0; - memcpy(&src_int, src_sample, 3); - - src_int <<= 40; - src_int >>= 40; - - s64 dst_int; - memcpy(&dst_int, dst_sample, 3); - - dst_int <<= 40; - dst_int >>= 40; - - s64 sum = clamp(src_int + dst_int, S24_MIN, S24_MAX); - memcpy(dst_sample, &sum, 3); - - break; - } - - case AUDIO_FORMAT_TYPE_S16: { + case AUDIO_BITS_16: { s16 dst_int = *((s16*)dst_sample); s16 src_int = *((s16*)src_sample); *((s16*)dst_sample) = (s16)clamp((s64)(dst_int + src_int), S16_MIN, S16_MAX); break; } - - case AUDIO_FORMAT_TYPE_U8: { - u8 dst_int = *((u8*)dst_sample); - u8 src_int = *((u8*)src_sample); - *((u8*)dst_sample) = (u8)clamp((s64(dst_int + src_int), 0, U8_MAX); - - break; - } - - case AUDIO_FORMAT_TYPE_UNKNOWN: break; } } } } -*/ \ No newline at end of file +void +convert_one_component(void *dst, Audio_Format_Bits dst_bits, + void *src, Audio_Format_Bits src_bits) { + switch (dst_bits) { + case AUDIO_BITS_32: { + switch (src_bits) { + case AUDIO_BITS_32: + memcpy(dst, src, get_audio_bit_width_byte_size(dst_bits)); break; + case AUDIO_BITS_16: + // #Simd + *(f32*)dst = (f64)((f32)*((s16*)src) * ((f64)1.0 / (f64)32768.0)); + break; + default: panic("Unhandled bits"); + } + break; + } + case AUDIO_BITS_16: { + switch (src_bits) { + case AUDIO_BITS_32: + // #Simd + *(s16*)dst = (s16)(*((f32*)src) * 32768.0f); + break; + case AUDIO_BITS_16: + memcpy(dst, src, get_audio_bit_width_byte_size(dst_bits)); + break; + default: panic("Unhandled bits"); + } + break; + } + default: panic("Unhandled bits"); + } +} + +// Assume dst buffer is large enough +// in-place conversion is OK +void +resample_frames(void *dst, Audio_Format dst_format, + void *src, Audio_Format src_format, u64 src_frame_count) { + assert(dst_format.channels == src_format.channels, "Channel count must be the same for sample rate conversion"); + assert(dst_format.bit_width == src_format.bit_width, "Types must be the same for sample rate conversion"); + + f32 src_ratio = (f32)src_format.sample_rate / (f32)dst_format.sample_rate; + u64 dst_frame_count = (u64)round(src_frame_count / src_ratio); + u64 dst_comp_size = get_audio_bit_width_byte_size(dst_format.bit_width); + u64 dst_frame_size = dst_comp_size * dst_format.channels; + u64 src_comp_size = get_audio_bit_width_byte_size(src_format.bit_width); + u64 src_frame_size = src_comp_size * src_format.channels; + + // Reverse in case dst == src (so we can do in-place conversion) + for (s64 dst_frame_index = dst_frame_count - 1; dst_frame_index >= 1; dst_frame_index--) { + f32 src_frame_index_f = dst_frame_index * src_ratio; + u64 src_frame_index_1 = (u64)src_frame_index_f; + u64 src_frame_index_2 = src_frame_index_1 + 1; + if (src_frame_index_2 >= src_frame_count) src_frame_index_2 = src_frame_count - 1; + + f32 lerp_factor = src_frame_index_f - (f32)src_frame_index_1; + + void *src_frame_1 = (u8*)src + src_frame_index_1 * src_frame_size; + void *src_frame_2 = (u8*)src + src_frame_index_2 * src_frame_size; + void *dst_frame = (u8*)dst + dst_frame_index * dst_frame_size; + + for (int c = 0; c < src_format.channels; c++) { + union { + s16 s16_sample; + f32 f32_sample; + u8 data[4]; + } sample_dst; + + void *src_comp_1 = (u8*)src_frame_1 + c * src_comp_size; + void *src_comp_2 = (u8*)src_frame_2 + c * src_comp_size; + void *dst_comp = (u8*)dst_frame + c * dst_comp_size; + + if (src_format.bit_width == AUDIO_BITS_32) { + float sample_1 = *((f32*)src_comp_1); + float sample_2 = *((f32*)src_comp_2); + sample_dst.f32_sample = sample_1 + lerp_factor * (sample_2 - sample_1); + } else if (src_format.bit_width == AUDIO_BITS_16) { + s16 sample_1 = *((s16*)src_comp_1); + s16 sample_2 = *((s16*)src_comp_2); + sample_dst.s16_sample = (s16)((f32)sample_1 + lerp_factor * ((f32)sample_2 - (f32)sample_1)); + } else { + panic("Unhandled bit width"); + } + + memcpy(dst_comp, sample_dst.data, dst_comp_size); + } + } + + // Correct padding if we downscaled (since we coverted in reverse) + if (src == dst && dst_format.sample_rate < src_format.sample_rate) { + void *dst_after_pad = (u8*)dst + (src_frame_count - dst_frame_count) * dst_frame_size; + u64 padding = (u64)dst_after_pad - (u64)dst; + memcpy( + dst, + dst_after_pad, + dst_frame_count * dst_frame_size + ); + memset((u8*)dst+dst_frame_count * dst_frame_size, 0, padding); + } + +} + +// Assumes dst buffer is large enough +void +convert_frames(void *dst, Audio_Format dst_format, + void *src, Audio_Format src_format, u64 src_frame_count) { + + u64 dst_comp_size = get_audio_bit_width_byte_size(dst_format.bit_width); + u64 dst_frame_size = dst_comp_size * dst_format.channels; + u64 src_comp_size = get_audio_bit_width_byte_size(src_format.bit_width); + u64 src_frame_size = src_comp_size * src_format.channels; + + if (dst_format.sample_rate != src_format.sample_rate) { + f32 ratio = (f32)src_format.sample_rate/(f32)dst_format.sample_rate; + src_frame_count = (u64)round((f32)src_frame_count*ratio); + } + + if (bytes_match(&dst_format, &src_format, sizeof(Audio_Format))) { + memcpy(dst, src, src_frame_count*src_frame_size); + return; + } + + u64 output_frame_count = src_frame_count; + + // #Speed #Simd + if (dst_format.channels != src_format.channels || dst_format.bit_width != src_format.bit_width) { + for (u64 src_frame_index = 0; src_frame_index < src_frame_count; src_frame_index++) { + void *src_frame = ((u8*)src) + src_frame_index*src_frame_size; + void *dst_frame = ((u8*)dst) + src_frame_index*dst_frame_size; + + // For getting average src sample + union { + s16 s16_sample; + f32 f32_sample; + u8 data[4]; + } avg; + if (src_format.channels != dst_format.channels) { + // This is where we get the average src sample + f32 sum = 0; + for (int c = 0; c < src_format.channels; c++) { + avg.s16_sample = 0; + void *src_comp = (u8*)src_frame + c * src_comp_size; + convert_one_component( + avg.data, dst_format.bit_width, + src_comp, src_format.bit_width + ); + if (dst_format.bit_width == AUDIO_BITS_32) sum += avg.f32_sample; + else if (dst_format.bit_width == AUDIO_BITS_16) sum += (f32)avg.s16_sample; + else panic("Unhandled bit width"); + } + if (dst_format.bit_width == AUDIO_BITS_32) { + avg.f32_sample = sum/(f32)src_format.channels; + } else if (dst_format.bit_width == AUDIO_BITS_16) { + avg.s16_sample = (s16)round(sum/(f32)src_format.channels); + } else panic("Unhandled bit width"); + } + + if (src_format.channels > dst_format.channels) { + + // #Limitation #Audioquality + // Here we are down-scaling the channel count. + // So what we do is we get the average sample for all channels in src and then + // set all channels in dst to that. This is fine for mono to stereo, but will + // be a loss for example for surround to mono. But I'm not sure we will ever + // care about non-stereo/mono audio. + + for (int c = 0; c < dst_format.channels; c++) { + void *dst_comp = (u8*)dst_frame + c * dst_comp_size; + memcpy(dst_comp, avg.data, dst_comp_size); + } + + } else if (dst_format.channels > src_format.channels) { + + // Here, we are upscaling to a higher channel count. + // I'm not sure what the best way to do this is, but for now I will try to just + // get the average in src and set that to the extra channels in dst. + // This is obviously fine for mono -> stereo but might be a problem for surround. + // Again, I'm not sure if surround will ever be on our list of worries. + + for (int c = 0; c < dst_format.channels; c++) { + void *dst_comp = (u8*)dst_frame + c * dst_comp_size; + void *src_comp = (u8*)src_frame + c * src_comp_size; + + if (c < src_format.channels) + convert_one_component(dst_comp, dst_format.bit_width, + src_comp, src_format.bit_width); + else + memcpy(dst_comp, avg.data, dst_comp_size); + } + + } else { + // Same channel count, just copy components over + for (int c = 0; c < dst_format.channels; c++) { + void *dst_comp = (u8*)dst_frame + c * dst_comp_size; + void *src_comp = (u8*)src_frame + c * src_comp_size; + convert_one_component(dst_comp, dst_format.bit_width, src_comp, src_format.bit_width); + } + } + } + } + if (dst_format.sample_rate != src_format.sample_rate) { + resample_frames( + dst, + (Audio_Format){dst_format.bit_width, dst_format.channels, dst_format.sample_rate}, + dst, + (Audio_Format){dst_format.bit_width, dst_format.channels, src_format.sample_rate}, + src_frame_count + ); + + } +} + + + + + + + +// #Temporary this is jsut for testing +Audio_Source *current_source = 0; +u64 current_index = 0; + +// This is supposed to be called by OS layer audio thread whenever it wants more audio samples +void do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format, + void *output) { + u64 out_comp_size = get_audio_bit_width_byte_size(out_format.bit_width); + u64 out_frame_size = out_comp_size * out_format.channels; + u64 output_size = number_of_output_frames * out_frame_size; + + memset(output, 0, output_size); + if (current_source) { + + bool need_convert = !bytes_match(&out_format, ¤t_source->format, sizeof(Audio_Format)); + + u64 in_comp_size = get_audio_bit_width_byte_size(current_source->format.bit_width); + u64 in_frame_size = in_comp_size * current_source->format.channels; + u64 input_size = number_of_output_frames * in_frame_size; + + void *target_buffer = output; + u64 number_of_sample_frames = number_of_output_frames; + + thread_local local_persist void *convert_buffer = 0; + thread_local local_persist u64 convert_buffer_size; + if (need_convert) { + if (current_source->format.sample_rate != out_format.sample_rate) { + f32 src_ratio + = (f32)current_source->format.sample_rate / (f32)out_format.sample_rate; + number_of_sample_frames = round(number_of_output_frames * src_ratio); + input_size = number_of_sample_frames * in_frame_size; + } + + u64 biggest_size = max(input_size, output_size); + + if (!convert_buffer || convert_buffer_size < biggest_size) { + // #Speed + if (convert_buffer) dealloc(get_heap_allocator(), convert_buffer); + convert_buffer = alloc(get_heap_allocator(), biggest_size); + convert_buffer_size = biggest_size; + } + target_buffer = convert_buffer; + memset(convert_buffer, 0, biggest_size); + + } + + current_index = audio_source_sample_frames( + current_source, + current_index, + number_of_sample_frames, + target_buffer, + true + ); + if (need_convert) { + convert_frames( + output, + out_format, + convert_buffer, + current_source->format, + number_of_output_frames + ); + } + } +} \ No newline at end of file diff --git a/oogabooga/concurrency.c b/oogabooga/concurrency.c index 4096753..94c2ed7 100644 --- a/oogabooga/concurrency.c +++ b/oogabooga/concurrency.c @@ -3,6 +3,13 @@ typedef struct Spinlock Spinlock; typedef struct Mutex Mutex; typedef struct Binary_Semaphore Binary_Semaphore; +// These are probably your best friend for sync-free multi-processing. +inline bool compare_and_swap_8(uint8_t *a, uint8_t b, uint8_t old); +inline bool compare_and_swap_16(uint16_t *a, uint16_t b, uint16_t old); +inline bool compare_and_swap_32(uint32_t *a, uint32_t b, uint32_t old); +inline bool compare_and_swap_64(uint64_t *a, uint64_t b, uint64_t old); +inline bool compare_and_swap_bool(bool *a, bool b, bool old); + /// // Spinlock "primitive" // Like a mutex but it eats up the entire core while waiting. diff --git a/oogabooga/cpu.c b/oogabooga/cpu.c index 8ac356e..62cc3c6 100644 --- a/oogabooga/cpu.c +++ b/oogabooga/cpu.c @@ -29,7 +29,8 @@ typedef struct Cpu_Capabilities { #define inline __forceinline #define alignat(x) __declspec(align(x)) #define COMPILER_HAS_MEMCPY_INTRINSICS 1 - inline void crash() { + inline void + crash() { __debugbreak(); volatile int *a = 0; *a = 5; @@ -38,7 +39,8 @@ typedef struct Cpu_Capabilities { } #include #pragma intrinsic(__rdtsc) - inline u64 rdtsc() { + inline u64 + rdtsc() { return __rdtsc(); } inline Cpu_Info_X86 cpuid(u32 function_id) { @@ -77,23 +79,28 @@ typedef struct Cpu_Capabilities { #pragma intrinsic(_InterlockedCompareExchange) #pragma intrinsic(_InterlockedCompareExchange64) - inline bool compare_and_swap_8(uint8_t *a, uint8_t b, uint8_t old) { + inline bool + compare_and_swap_8(uint8_t *a, uint8_t b, uint8_t old) { return _InterlockedCompareExchange8((volatile char*)a, (char)b, (char)old) == old; } - inline bool compare_and_swap_16(uint16_t *a, uint16_t b, uint16_t old) { + inline bool + compare_and_swap_16(uint16_t *a, uint16_t b, uint16_t old) { return _InterlockedCompareExchange16((volatile short*)a, (short)b, (short)old) == old; } - inline bool compare_and_swap_32(uint32_t *a, uint32_t b, uint32_t old) { + inline bool + compare_and_swap_32(uint32_t *a, uint32_t b, uint32_t old) { return _InterlockedCompareExchange((volatile long*)a, (long)b, (long)old) == old; } - inline bool compare_and_swap_64(uint64_t *a, uint64_t b, uint64_t old) { + inline bool + compare_and_swap_64(uint64_t *a, uint64_t b, uint64_t old) { return _InterlockedCompareExchange64((volatile long long*)a, (long long)b, (long long)old) == old; } - inline bool compare_and_swap_bool(bool *a, bool b, bool old) { + inline bool + compare_and_swap_bool(bool *a, bool b, bool old) { return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old); } @@ -103,20 +110,26 @@ typedef struct Cpu_Capabilities { #define inline __attribute__((always_inline)) inline #define alignat(x) __attribute__((aligned(x))) #define COMPILER_HAS_MEMCPY_INTRINSICS 1 - inline void crash() { + + inline void + crash() { __builtin_trap(); volatile int *a = 0; *a = 5; a = (int*)0xDEADBEEF; *a = 5; } - inline u64 rdtsc() { + + inline u64 + rdtsc() { unsigned int lo, hi; __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); return ((u64)hi << 32) | lo; } - inline Cpu_Info_X86 cpuid(u32 function_id) { - Cpu_Info_X86 info; + + inline + Cpu_Info_X86 cpuid(u32 function_id) { + Cpu_Info_X86 info; __asm__ __volatile__( "cpuid" : "=a"(info.eax), "=b"(info.ebx), "=c"(info.ecx), "=d"(info.edx) @@ -152,7 +165,8 @@ typedef struct Cpu_Capabilities { #define DEPRECATED(proc, msg) proc __attribute__((deprecated(msg))) - inline bool compare_and_swap_8(uint8_t *a, uint8_t b, uint8_t old) { + inline bool + compare_and_swap_8(uint8_t *a, uint8_t b, uint8_t old) { unsigned char result; __asm__ __volatile__( "lock; cmpxchgb %2, %1" @@ -163,7 +177,8 @@ typedef struct Cpu_Capabilities { return result == old; } - inline bool compare_and_swap_16(uint16_t *a, uint16_t b, uint16_t old) { + inline bool + compare_and_swap_16(uint16_t *a, uint16_t b, uint16_t old) { unsigned short result; __asm__ __volatile__( "lock; cmpxchgw %2, %1" @@ -174,7 +189,8 @@ typedef struct Cpu_Capabilities { return result == old; } - inline bool compare_and_swap_32(uint32_t *a, uint32_t b, uint32_t old) { + inline bool + compare_and_swap_32(uint32_t *a, uint32_t b, uint32_t old) { unsigned int result; __asm__ __volatile__( "lock; cmpxchgl %2, %1" @@ -185,7 +201,8 @@ typedef struct Cpu_Capabilities { return result == old; } - inline bool compare_and_swap_64(uint64_t *a, uint64_t b, uint64_t old) { + inline bool + compare_and_swap_64(uint64_t *a, uint64_t b, uint64_t old) { unsigned long long result; __asm__ __volatile__( "lock; cmpxchgq %2, %1" @@ -196,7 +213,8 @@ typedef struct Cpu_Capabilities { return result == old; } - inline bool compare_and_swap_bool(bool *a, bool b, bool old) { + inline bool + compare_and_swap_bool(bool *a, bool b, bool old) { return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old); } @@ -206,7 +224,8 @@ typedef struct Cpu_Capabilities { #define inline inline #define COMPILER_HAS_MEMCPY_INTRINSICS 0 - inline u64 rdtsc() { return 0; } + inline u64 + rdtsc() { return 0; } inline Cpu_Info_X86 cpuid(u32 function_id) {return (Cpu_Info_X86){0};} #define COMPILER_CAN_DO_SSE2 0 #define COMPILER_CAN_DO_AVX 0 @@ -220,7 +239,8 @@ typedef struct Cpu_Capabilities { #warning "Compiler is not explicitly supported, some things will probably not work as expected" #endif -Cpu_Capabilities query_cpu_capabilities() { +Cpu_Capabilities +query_cpu_capabilities() { Cpu_Capabilities result = {0}; Cpu_Info_X86 info = cpuid(1); diff --git a/oogabooga/examples/audio_test.c b/oogabooga/examples/audio_test.c new file mode 100644 index 0000000..f584b2a --- /dev/null +++ b/oogabooga/examples/audio_test.c @@ -0,0 +1,41 @@ + + + +int entry(int argc, char **argv) { + + window.title = STR("Audio test"); + window.scaled_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI) + window.scaled_height = 720; + window.x = 200; + window.y = 90; + window.clear_color = hex_to_rgba(0x6495EDff); + + Allocator heap = get_heap_allocator(); + + Audio_Source bruh, song; + bool bruh_ok = audio_source_init_file_decode(&bruh, STR("oogabooga/examples/bruh.wav"), AUDIO_BITS_32, heap); + assert(bruh_ok, "Could not load bruh.wav"); + + bool song_ok = audio_source_init_file_stream(&song, STR("oogabooga/examples/song.ogg"), AUDIO_BITS_16, heap); + assert(bruh_ok, "Could not load song.ogg"); + + // #Temporary This is not actually how it will work, I'm just testing audio source output. + current_source = &song; + + while (!window.should_close) { + reset_temporary_storage(); + + float64 now = os_get_current_time_in_seconds(); + Matrix4 rect_xform = m4_scalar(1.0); + rect_xform = m4_rotate_z(rect_xform, (f32)now); + rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0)); + draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_GREEN); + + draw_rect(v2(sin(now), -.8), v2(.5, .25), COLOR_RED); + + os_update(); + gfx_update(); + } + + return 0; +} \ No newline at end of file diff --git a/oogabooga/examples/bruh.wav b/oogabooga/examples/bruh.wav index 76b6bf0..e940a6e 100644 Binary files a/oogabooga/examples/bruh.wav and b/oogabooga/examples/bruh.wav differ diff --git a/oogabooga/examples/custom_logger.c b/oogabooga/examples/custom_logger.c index 05692c8..03f557b 100644 --- a/oogabooga/examples/custom_logger.c +++ b/oogabooga/examples/custom_logger.c @@ -5,6 +5,10 @@ and in-game logger. We also have log levels to be able to disable/enable the respective levels. + + BEWARE!! + This logger is not thread-safe. If multiple threads call log(), then nobody knows + what might happen. If you need to make it thread-safe, check out concurrency.c. */ diff --git a/oogabooga/examples/renderer_stress_test.c b/oogabooga/examples/renderer_stress_test.c index e257251..091d3b4 100644 --- a/oogabooga/examples/renderer_stress_test.c +++ b/oogabooga/examples/renderer_stress_test.c @@ -36,7 +36,7 @@ int entry(int argc, char **argv) { Matrix4 camera_view = m4_scalar(1.0); float64 last_time = os_get_current_time_in_seconds(); - while (!window.should_close) tm_scope_cycles("Frame") { + while (!window.should_close) tm_scope("Frame") { reset_temporary_storage(); float64 now = os_get_current_time_in_seconds(); @@ -47,7 +47,7 @@ int entry(int argc, char **argv) { delta = now - last_time; } last_time = now; - tm_scope_cycles("os_update") { + tm_scope("os_update") { os_update(); } @@ -134,7 +134,7 @@ int entry(int argc, char **argv) { if (show) draw_image(atlas->image, v2(-1.6, -1), v2(4, 4), COLOR_WHITE); - tm_scope_cycles("gfx_update") { + tm_scope("gfx_update") { gfx_update(); } diff --git a/oogabooga/examples/text_rendering.c b/oogabooga/examples/text_rendering.c index 0239d5b..6d28a32 100644 --- a/oogabooga/examples/text_rendering.c +++ b/oogabooga/examples/text_rendering.c @@ -14,7 +14,7 @@ int entry(int argc, char **argv) { const u32 font_height = 48; - while (!window.should_close) tm_scope_cycles("Frame") { + while (!window.should_close) tm_scope("Frame") { reset_temporary_storage(); // Text is easiest to deal with if our projection matches window pixel size, because diff --git a/oogabooga/gfx_impl_d3d11.c b/oogabooga/gfx_impl_d3d11.c index d018d69..ab477f8 100644 --- a/oogabooga/gfx_impl_d3d11.c +++ b/oogabooga/gfx_impl_d3d11.c @@ -6,6 +6,7 @@ #endif +// #Cleanup apparently there are C macros for these (COBJMACROS) #define D3D11Release(x) x->lpVtbl->Release(x) #define VTABLE(proc, ...) FIRST_ARG(__VA_ARGS__)->lpVtbl->proc(__VA_ARGS__) @@ -114,28 +115,30 @@ void CALLBACK d3d11_debug_callback(D3D11_MESSAGE_CATEGORY category, D3D11_MESSAG #define win32_check_hr(hr) win32_check_hr_impl(hr, __LINE__, __FILE__); void win32_check_hr_impl(HRESULT hr, u32 line, const char* file_name) { - if (FAILED(hr)) { - LPVOID errorMsg; + if (hr != S_OK) { + + LPVOID errorMsg; DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; - DWORD messageLength = FormatMessage( + DWORD messageLength = FormatMessageW( dwFlags, NULL, hr, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), - (LPTSTR) &errorMsg, + (LPWSTR) &errorMsg, 0, NULL ); if (messageLength > 0) { - MessageBox(NULL, (LPCTSTR)errorMsg, TEXT("Error"), MB_OK | MB_ICONERROR); + MessageBoxW(NULL, (LPWSTR)errorMsg, L"Error", MB_OK | MB_ICONERROR); } else { - MessageBox(NULL, TEXT("Failed to retrieve error message."), TEXT("Error"), MB_OK | MB_ICONERROR); + MessageBoxW(NULL, L"Failed to retrieve error message.", L"Error", MB_OK | MB_ICONERROR); } + - panic("win32 hr failed in file %cs on line %d", file_name, line); + panic("win32 hr failed in file %cs on line %d, hr was %d", file_name, line, hr); } } @@ -176,8 +179,8 @@ void d3d11_update_swapchain() { // Obtain DXGI factory from device - IDXGIDevice *dxgi_device; - hr = VTABLE(QueryInterface, d3d11_device, &IID_IDXGIDevice, cast(void**)&dxgi_device); + IDXGIDevice *dxgi_device = 0; + hr = ID3D11Device_QueryInterface(d3d11_device, &IID_IDXGIDevice, cast(void**)&dxgi_device); win32_check_hr(hr); IDXGIAdapter *adapter; @@ -599,8 +602,8 @@ void d3d11_process_draw_frame() { D3D11_Vertex* pointer = head; u64 number_of_rendered_quads = 0; - tm_scope_cycles("Quad processing") { - if (draw_frame.enable_z_sorting) tm_scope_cycles("Z sorting") { + tm_scope("Quad processing") { + if (draw_frame.enable_z_sorting) tm_scope("Z sorting") { if (!sort_quad_buffer || (sort_quad_buffer_size < allocated_quads*sizeof(Draw_Quad))) { // #Memory #Heapalloc if (sort_quad_buffer) dealloc(get_heap_allocator(), sort_quad_buffer); @@ -721,23 +724,23 @@ void d3d11_process_draw_frame() { } } - tm_scope_cycles("Write to gpu") { + tm_scope("Write to gpu") { D3D11_MAPPED_SUBRESOURCE buffer_mapping; - tm_scope_cycles("The Map call") { + tm_scope("The Map call") { hr = VTABLE(Map, d3d11_context, (ID3D11Resource*)d3d11_quad_vbo, 0, D3D11_MAP_WRITE_DISCARD, 0, &buffer_mapping); win32_check_hr(hr); } - tm_scope_cycles("The memcpy") { + tm_scope("The memcpy") { memcpy(buffer_mapping.pData, d3d11_staging_quad_buffer, number_of_rendered_quads*sizeof(D3D11_Vertex)*6); } - tm_scope_cycles("The Unmap call") { + tm_scope("The Unmap call") { VTABLE(Unmap, d3d11_context, (ID3D11Resource*)d3d11_quad_vbo, 0); } } /// // Draw call - tm_scope_cycles("Draw call") d3d11_draw_call(number_of_rendered_quads, textures, num_textures); + tm_scope("Draw call") d3d11_draw_call(number_of_rendered_quads, textures, num_textures); } reset_draw_frame(&draw_frame); @@ -762,7 +765,7 @@ void gfx_update() { d3d11_process_draw_frame(); - tm_scope_cycles("Present") { + tm_scope("Present") { VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING); } diff --git a/oogabooga/oogabooga.c b/oogabooga/oogabooga.c index b5a9d44..8cfc27c 100644 --- a/oogabooga/oogabooga.c +++ b/oogabooga/oogabooga.c @@ -98,16 +98,16 @@ Note: See timing macros in profile.c - tm_scope_cycles - tm_scope_cycles_var - tm_scope_cycles_accum + tm_scope + tm_scope_var + tm_scope_accum */ #define OGB_VERSION_MAJOR 0 -#define OGB_VERSION_MINOR 0 -#define OGB_VERSION_PATCH 5 +#define OGB_VERSION_MINOR 1 +#define OGB_VERSION_PATCH 0 #define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH) @@ -212,6 +212,7 @@ typedef u8 bool; #define MACOS 2 #ifdef _WIN32 + #define COBJMACROS #include #define TARGET_OS WINDOWS #define OS_PATHS_HAVE_BACKSLASH 1 @@ -314,6 +315,26 @@ typedef u8 bool; #define malloc please_use_alloc_for_memory_allocations_instead_of_malloc #define free please_use_dealloc_for_memory_deallocations_instead_of_free +Mutex _default_logger_mutex; +bool _default_logger_mutex_initted = false; +void default_logger(Log_Level level, string s) { + + if (!_default_logger_mutex_initted) { + mutex_init(&_default_logger_mutex); + _default_logger_mutex_initted = true; + } + + mutex_acquire_or_wait(&_default_logger_mutex); + switch (level) { + case LOG_VERBOSE: print("[VERBOSE]: %s\n", s); break; + case LOG_INFO: print("[INFO]: %s\n", s); break; + case LOG_WARNING: print("[WARNING]: %s\n", s); break; + case LOG_ERROR: print("[ERROR]: %s\n", s); break; + case LOG_LEVEL_COUNT: break; + } + mutex_release(&_default_logger_mutex); +} + void oogabooga_init(u64 program_memory_size) { context.logger = default_logger; temp = get_initialization_allocator(); diff --git a/oogabooga/os_impl_windows.c b/oogabooga/os_impl_windows.c index bb5a718..ff6094e 100644 --- a/oogabooga/os_impl_windows.c +++ b/oogabooga/os_impl_windows.c @@ -1,5 +1,11 @@ +#define CINTERFACE #include +#include +#include +#include +#include +#include #define VIRTUAL_MEMORY_BASE ((void*)0x0000690000000000ULL) @@ -132,12 +138,72 @@ LRESULT CALLBACK win32_window_proc(HWND passed_window, UINT message, WPARAM wpar return 0; } +void win32_audio_thread(Thread *t); +void +win32_audio_init(); +void win32_init_window() { + memset(&window, 0, sizeof(window)); + + window.title = STR("Unnamed Window"); + window.width = 1280; + window.height = 720; + window.x = 0; + window.y = 0; + window.should_close = false; + window._initialized = false; + window.clear_color.r = 0.392f; + window.clear_color.g = 0.584f; + window.clear_color.b = 0.929f; + window.clear_color.a = 1.0f; + + WNDCLASSEX wc = (WNDCLASSEX){0}; + MSG msg; + HINSTANCE instance = GetModuleHandle(0); + assert(instance != INVALID_HANDLE_VALUE, "Failed getting current HINSTANCE"); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_OWNDC; + wc.lpfnWndProc = win32_window_proc; + wc.hInstance = instance; + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszClassName = "sigma balls"; + wc.hIconSm = LoadIcon(0, IDI_APPLICATION); + + BOOL ok = RegisterClassEx(&wc); + assert(ok, "Failed registering window class (error code %lu)", GetLastError()); + + RECT rect = {0, 0, window.width, window.height}; + DWORD style = WS_OVERLAPPEDWINDOW; + DWORD ex_style = WS_EX_CLIENTEDGE; + ok = AdjustWindowRectEx(&rect, style, FALSE, ex_style); + assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError()); + + u32 actual_window_width = rect.right - rect.left; + u32 actual_window_height = rect.bottom - rect.top; + // Create the window + window._os_handle = CreateWindowEx( + ex_style, + "sigma balls", + temp_convert_to_null_terminated_string(window.title), + style, + CW_USEDEFAULT, CW_USEDEFAULT, actual_window_width, actual_window_height, + 0, 0, instance, 0); + assert(window._os_handle != 0, "Window creation failed, error: %lu", GetLastError()); + window._initialized = true; + ShowWindow(window._os_handle, SW_SHOWDEFAULT); + UpdateWindow(window._os_handle); +} void os_init(u64 program_memory_size) { + HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + win32_check_hr(hr); + context.thread_id = GetCurrentThreadId(); - memset(&window, 0, sizeof(window)); + #if CONFIGURATION == RELEASE SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); @@ -159,7 +225,7 @@ void os_init(u64 program_memory_size) { unsigned char* addr = 0; while (VirtualQuery(addr, &mbi, sizeof(mbi))) { if (mbi.Type == MEM_IMAGE) { - if (os.static_memory_start == NULL) { + if (os.static_memory_start == 0) { os.static_memory_start = mbi.BaseAddress; } os.static_memory_end = (unsigned char*)mbi.BaseAddress + mbi.RegionSize; @@ -171,7 +237,6 @@ void os_init(u64 program_memory_size) { program_memory_mutex = os_make_mutex(); os_grow_program_memory(program_memory_size); - heap_init(); os.crt = os_load_dynamic_library(STR("msvcrt.dll")); @@ -189,61 +254,8 @@ void os_init(u64 program_memory_size) { os.crt_memset = (Crt_Memset_Proc)os_dynamic_library_load_symbol(os.crt, STR("memset")); assert(os.crt_memset, "Missing memset in crt"); - window.title = STR("Unnamed Window"); - window.width = 1280; - window.height = 720; - window.x = 0; - window.y = 0; - window.should_close = false; - window._initialized = false; - window.clear_color.r = 0.392f; - window.clear_color.g = 0.584f; - window.clear_color.b = 0.929f; - window.clear_color.a = 1.0f; - - WNDCLASSEX wc = (WNDCLASSEX){0}; - MSG msg; - HINSTANCE instance = GetModuleHandle(NULL); - assert(instance != INVALID_HANDLE_VALUE, "Failed getting current HINSTANCE"); - - wc.cbSize = sizeof(WNDCLASSEX); - wc.style = CS_OWNDC; - wc.lpfnWndProc = win32_window_proc; - wc.hInstance = instance; - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wc.lpszClassName = "sigma balls"; - wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); - - BOOL ok = RegisterClassEx(&wc); - assert(ok, "Failed registering window class (error code %lu)", GetLastError()); - - RECT rect = {0, 0, window.width, window.height}; - DWORD style = WS_OVERLAPPEDWINDOW; - DWORD ex_style = WS_EX_CLIENTEDGE; - ok = AdjustWindowRectEx(&rect, style, FALSE, ex_style); - assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError()); - - u32 actual_window_width = rect.right - rect.left; - u32 actual_window_height = rect.bottom - rect.top; - // Create the window - window._os_handle = CreateWindowEx( - ex_style, - "sigma balls", - temp_convert_to_null_terminated_string(window.title), - style, - CW_USEDEFAULT, CW_USEDEFAULT, actual_window_width, actual_window_height, - NULL, NULL, instance, NULL); - assert(window._os_handle != NULL, "Window creation failed, error: %lu", GetLastError()); - window._initialized = true; - ShowWindow(window._os_handle, SW_SHOWDEFAULT); - UpdateWindow(window._os_handle); - - - - - + win32_init_window(); + os_start_thread(os_make_thread(win32_audio_thread, get_heap_allocator())); } void s64_to_null_terminated_string_reverse(char str[], int length) @@ -573,7 +585,7 @@ void os_write_string_to_stdout(string s) { HANDLE win32_stdout = GetStdHandle(STD_OUTPUT_HANDLE); if (win32_stdout == INVALID_HANDLE_VALUE) return; - WriteFile(win32_stdout, s.data, s.count, 0, NULL); + WriteFile(win32_stdout, s.data, s.count, 0, 0); } u16 *win32_fixed_utf8_to_null_terminated_wide(string utf8, Allocator allocator) { @@ -591,7 +603,7 @@ u16 *win32_fixed_utf8_to_null_terminated_wide(string utf8, Allocator allocator) int result = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)utf8.data, (int)utf8.count, utf16_str, utf16_length); if (result == 0) { dealloc(allocator, utf16_str); - return NULL; + return 0; } utf16_str[utf16_length] = 0; @@ -646,7 +658,7 @@ File os_file_open_s(string path, Os_Io_Open_Flags flags) { u16 *wide = temp_win32_fixed_utf8_to_null_terminated_wide(path); - return CreateFileW(wide, access, FILE_SHARE_READ, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + return CreateFileW(wide, access, FILE_SHARE_READ, 0, creation, FILE_ATTRIBUTE_NORMAL, 0); } void os_file_close(File f) { @@ -672,7 +684,7 @@ bool os_make_directory_s(string path, bool recursive) { wchar_t *sep = wcschr(wide_path + 1, L'\\'); while (sep) { *sep = 0; - if (!CreateDirectoryW(wide_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + if (!CreateDirectoryW(wide_path, 0) && GetLastError() != ERROR_ALREADY_EXISTS) { return false; } *sep = L'\\'; @@ -680,7 +692,7 @@ bool os_make_directory_s(string path, bool recursive) { } } - if (!CreateDirectoryW(wide_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + if (!CreateDirectoryW(wide_path, 0) && GetLastError() != ERROR_ALREADY_EXISTS) { return false; } @@ -733,19 +745,19 @@ bool os_delete_directory_s(string path, bool recursive) { bool os_file_write_string(File f, string s) { DWORD written; - BOOL result = WriteFile(f, s.data, s.count, &written, NULL); + BOOL result = WriteFile(f, s.data, s.count, &written, 0); return result && (written == s.count); } bool os_file_write_bytes(File f, void *buffer, u64 size_in_bytes) { DWORD written; - BOOL result = WriteFile(f, buffer, (DWORD)size_in_bytes, &written, NULL); + BOOL result = WriteFile(f, buffer, (DWORD)size_in_bytes, &written, 0); return result && (written == size_in_bytes); } bool os_file_read(File f, void* buffer, u64 bytes_to_read, u64 *actual_read_bytes) { DWORD read; - BOOL result = ReadFile(f, buffer, (DWORD)bytes_to_read, &read, NULL); + BOOL result = ReadFile(f, buffer, (DWORD)bytes_to_read, &read, 0); if (actual_read_bytes) { *actual_read_bytes = read; } @@ -799,7 +811,7 @@ bool os_read_entire_file_s(string path, string *result, Allocator allocator) { bool os_is_file_s(string path) { u16 *path_wide = temp_win32_fixed_utf8_to_null_terminated_wide(path); assert(path_wide, "Invalid path string"); - if (path_wide == NULL) { + if (path_wide == 0) { return false; } @@ -815,7 +827,7 @@ bool os_is_file_s(string path) { bool os_is_directory_s(string path) { u16 *path_wide = temp_win32_fixed_utf8_to_null_terminated_wide(path); assert(path_wide, "Invalid path string"); - if (path_wide == NULL) { + if (path_wide == 0) { return false; } @@ -901,10 +913,10 @@ bool os_do_paths_match(string a, string b) { wchar_t full_path_b[MAX_PATH]; // Get the full path for both paths - if (!GetFullPathNameW(wide_path_a, MAX_PATH, full_path_a, NULL)) { + if (!GetFullPathNameW(wide_path_a, MAX_PATH, full_path_a, 0)) { return false; } - if (!GetFullPathNameW(wide_path_b, MAX_PATH, full_path_b, NULL)) { + if (!GetFullPathNameW(wide_path_b, MAX_PATH, full_path_b, 0)) { return false; } @@ -950,9 +962,270 @@ void* os_get_stack_limit() { return tib->StackLimit; } +// Actually fuck you bill gates +const GUID CLSID_MMDeviceEnumerator = {0xbcde0395, 0xe52f, 0x467c, {0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e}}; +const GUID IID_IMMDeviceEnumerator = {0xa95664d2, 0x9614, 0x4f35, {0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6}}; +const GUID IID_IAudioClient = {0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2}}; +const GUID IID_IAudioRenderClient = {0xf294acfc, 0x3146, 0x4483, {0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2}}; +DEFINE_GUID(IID_ISimpleAudioVolume, +0x87CE5498, 0x68D6, 0x44E5, 0x92, 0x15, 0x6D, 0xA4, 0x7E, 0xF8, 0x83, 0xD8); + +IAudioClient* win32_audio_client; +IAudioRenderClient* win32_render_client; +bool win32_audio_deactivated = false; +Audio_Format win32_output_format; +IMMDevice* win32_audio_device = 0; +IMMDeviceEnumerator* win32_device_enumerator = 0; +ISimpleAudioVolume* win32_audio_volume = 0; + +void +win32_audio_init() { + + win32_audio_client = 0; + win32_render_client = 0; + win32_audio_deactivated = 0; + win32_audio_device = 0; + win32_device_enumerator = 0; + + HRESULT hr; + WAVEFORMATEX* device_base_format = 0; + WAVEFORMATEX* output_format = 0; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&win32_device_enumerator); + win32_check_hr(hr); + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(win32_device_enumerator, eRender, eConsole, &win32_audio_device); + win32_check_hr(hr); + + hr = IMMDevice_Activate( + win32_audio_device, + &IID_IAudioClient, + CLSCTX_ALL, 0, + (void**)&win32_audio_client + ); + win32_check_hr(hr); + + hr = IAudioClient_GetMixFormat(win32_audio_client, &device_base_format); + + WAVEFORMATEXTENSIBLE *format_f32 + = (WAVEFORMATEXTENSIBLE*)CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + WAVEFORMATEXTENSIBLE *format_s16 + = (WAVEFORMATEXTENSIBLE*)CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + + memcpy(format_f32, device_base_format, sizeof(WAVEFORMATEX)); + + format_f32->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format_f32->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + format_f32->Samples.wValidBitsPerSample = format_f32->Format.wBitsPerSample; + format_f32->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + + memcpy(format_s16, format_f32, sizeof(WAVEFORMATEXTENSIBLE)); + + format_f32->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + format_f32->Format.wBitsPerSample = 32; + format_f32->Format.nBlockAlign + = format_f32->Format.nChannels * format_f32->Format.wBitsPerSample / 8; + format_f32->Format.nAvgBytesPerSec + = format_f32->Format.nSamplesPerSec * format_f32->Format.nBlockAlign; + + format_s16->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + format_s16->Format.wBitsPerSample = 16; + format_s16->Format.nBlockAlign = format_s16->Format.nChannels * format_s16->Format.wBitsPerSample / 8; + format_s16->Format.nAvgBytesPerSec = format_s16->Format.nSamplesPerSec * format_s16->Format.nBlockAlign; + + // First look for f32 support + WAVEFORMATEX *closest_match = NULL; + hr = IAudioClient_IsFormatSupported( + win32_audio_client, + AUDCLNT_SHAREMODE_SHARED, + (WAVEFORMATEX *)format_f32, + &closest_match + ); + + // If f32 fails, look for s16 + if (hr != S_OK) { + hr = IAudioClient_IsFormatSupported( + win32_audio_client, + AUDCLNT_SHAREMODE_SHARED, + (WAVEFORMATEX *)format_s16, + &closest_match + ); + if (hr != S_OK) { + win32_audio_deactivated = true; + log_error("Default audio output device is not supported."); + return; + } + output_format = (WAVEFORMATEX*)format_s16; + } else { + output_format = (WAVEFORMATEX*)format_f32; + } + + const s64 BUFFER_DURATION_MS = 40; + hr = IAudioClient_Initialize( + win32_audio_client, + AUDCLNT_SHAREMODE_SHARED, + 0, + BUFFER_DURATION_MS*10000ll, 0, + output_format, 0 + ); + win32_check_hr(hr); + + hr = IAudioClient_GetService(win32_audio_client, &IID_IAudioRenderClient, (void**)&win32_render_client); + win32_check_hr(hr); + + win32_output_format.channels = output_format->nChannels; + win32_output_format.sample_rate = output_format->nSamplesPerSec; + if (output_format == (WAVEFORMATEX*)format_s16) { + win32_output_format.bit_width = AUDIO_BITS_16; + } else if (output_format == (WAVEFORMATEX*)format_f32) { + win32_output_format.bit_width = AUDIO_BITS_32; + } else { + panic("What"); + } + + DWORD task_index; + AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &task_index); + + hr = IAudioClient_GetService(win32_audio_client, &IID_ISimpleAudioVolume, (void**)&win32_audio_volume); + if (SUCCEEDED(hr)) { + hr = ISimpleAudioVolume_SetMasterVolume(win32_audio_volume, 1.0f, 0); + win32_check_hr(hr); + ISimpleAudioVolume_Release(win32_audio_volume); + } + + log_info("Successfully initialized default audio device. Channels: %d, sample_rate: %d, bits: %d", win32_output_format.channels, win32_output_format.sample_rate, get_audio_bit_width_byte_size(win32_output_format.bit_width)*8); +} +void +win32_audio_thread(Thread *t) { + + win32_audio_init(); + + timeBeginPeriod(1); + + u32 buffer_frame_count; + HRESULT hr = IAudioClient_GetBufferSize(win32_audio_client, &buffer_frame_count); + if (FAILED(hr)) win32_audio_deactivated = true; + + bool started = false; + + while (!window.should_close) tm_scope("Audio update") { + if (win32_audio_deactivated) tm_scope("Retry audio device") { + os_sleep(100); + win32_audio_init(); + started = false; + if (win32_audio_deactivated) { + hr = IAudioClient_GetBufferSize(win32_audio_client, &buffer_frame_count); + if (FAILED(hr)) win32_audio_deactivated = true; + } + continue; + } + + // We gotta check if there is a new default endpoint (#Speed ?) + + IMMDevice *now_default = 0; + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(win32_device_enumerator, eRender, eConsole, &now_default); + win32_check_hr(hr); + + WCHAR *now_default_id = NULL; + hr = IMMDevice_GetId(now_default, &now_default_id); + win32_check_hr(hr); + + WCHAR *previous_id = NULL; + hr = IMMDevice_GetId(win32_audio_device, &previous_id); + win32_check_hr(hr); + + if (wcscmp(now_default_id, previous_id) != 0) { + win32_audio_deactivated = true; + } + + CoTaskMemFree(now_default_id); + CoTaskMemFree(previous_id); + + IMMDevice_Release(now_default); + + if (win32_audio_deactivated) continue; + + BYTE *buffer = 0; + + u32 num_frames_available = 0; + hr = IAudioClient_GetCurrentPadding(win32_audio_client, &num_frames_available); + if (FAILED(hr)) { + win32_audio_deactivated = true; + continue; + } + u32 num_frames_to_write = buffer_frame_count - num_frames_available; + + if (!started) { + hr = IAudioClient_Start(win32_audio_client); + win32_check_hr(hr); + started = true; + } + + while (num_frames_to_write == 0) tm_scope("Chill") { + // We yield & sleep until we have any work to do + os_yield_thread(); + os_sleep(1); + + hr = IAudioClient_GetCurrentPadding(win32_audio_client, &num_frames_available); + if (FAILED(hr)) { + win32_audio_deactivated = true; + break; + } + num_frames_to_write = buffer_frame_count - num_frames_available; + + } + if (win32_audio_deactivated) continue; + + + if (num_frames_to_write > 0) tm_scope("Output frames") { + hr = IAudioRenderClient_GetBuffer( + win32_render_client, + num_frames_to_write, + &buffer + ); + if (FAILED(hr)) { + win32_audio_deactivated = true; + continue; + } + + do_program_audio_sample(num_frames_to_write, win32_output_format, buffer); + //f32 s = 0.5; + //for (u32 i = 0; i < num_frames_to_write * win32_output_format.channels; ++i) { + // ((f32*)buffer)[i] = s; + //} + /*float64 time = 0; + float *fbuffer = (float *)buffer; + for (UINT32 frameIndex = 0; frameIndex < num_frames_to_write; frameIndex++) { + float amplitude = (float)(sin(time)*0.2); + + *fbuffer++ = amplitude; // left + *fbuffer++ = amplitude; // right + + time += 0.05; + }*/ + + + //for (u64 i = 0; i < num_frames_to_write; i++) { + // f32 s = *(((f32*)buffer)+i*win32_output_format.channels); + // print("%f ", s); + //} + hr = IAudioRenderClient_ReleaseBuffer( + win32_render_client, + num_frames_to_write, + 0 + ); + if (FAILED(hr)) { + win32_audio_deactivated = true; + continue; + } + + } + + } +} void os_update() { @@ -989,7 +1262,7 @@ void os_update() { u32 actual_x = update_rect.left; u32 actual_y = screen_height - update_rect.top - (update_rect.bottom - update_rect.top); - SetWindowPos(window._os_handle, NULL, actual_x, actual_y, actual_width, actual_height, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(window._os_handle, 0, actual_x, actual_y, actual_width, actual_height, SWP_NOZORDER | SWP_NOACTIVATE); } RECT client_rect; @@ -1050,7 +1323,7 @@ void os_update() { MSG msg; while (input_frame.number_of_events < MAX_EVENTS_PER_FRAME - && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + && PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { window.should_close = true; break; diff --git a/oogabooga/os_interface.c b/oogabooga/os_interface.c index 2397e1e..fc06f2b 100644 --- a/oogabooga/os_interface.c +++ b/oogabooga/os_interface.c @@ -101,6 +101,7 @@ typedef struct Thread { /// // Thread primitive +// #Cleanup this shouldn't be allocating just for the pointer!! Just do os_thread_init(*) Thread* os_make_thread(Thread_Proc proc, Allocator allocator); void os_destroy_thread(Thread *t); void os_start_thread(Thread* t); diff --git a/oogabooga/profiling.c b/oogabooga/profiling.c index 7c837e8..f65cd58 100644 --- a/oogabooga/profiling.c +++ b/oogabooga/profiling.c @@ -29,20 +29,20 @@ void _profiler_report_time_cycles(string name, u64 count, u64 start) { spinlock_release(&_profiler_lock); } #if ENABLE_PROFILING -#define tm_scope_cycles(name) \ +#define tm_scope(name) \ for (u64 start_time = os_get_current_cycle_count(), end_time = start_time, elapsed_time = 0; \ elapsed_time == 0; \ elapsed_time = (end_time = os_get_current_cycle_count()) - start_time, _profiler_report_time_cycles(STR(name), elapsed_time, start_time)) -#define tm_scope_cycles_var(name, var) \ +#define tm_scope_var(name, var) \ for (u64 start_time = os_get_current_cycle_count(), end_time = start_time, elapsed_time = 0; \ elapsed_time == 0; \ elapsed_time = (end_time = os_get_current_cycle_count()) - start_time, var=elapsed_time) -#define tm_scope_cycles_accum(name, var) \ +#define tm_scope_accum(name, var) \ for (u64 start_time = os_get_current_cycle_count(), end_time = start_time, elapsed_time = 0; \ elapsed_time == 0; \ elapsed_time = (end_time = os_get_current_cycle_count()) - start_time, var+=elapsed_time) #else - #define tm_scope_cycles(...) - #define tm_scope_cycles_var(...) - #define tm_scope_cycles_accum(...) + #define tm_scope(...) + #define tm_scope_var(...) + #define tm_scope_accum(...) #endif \ No newline at end of file diff --git a/oogabooga/string_format.c b/oogabooga/string_format.c index 9148adb..a60f751 100644 --- a/oogabooga/string_format.c +++ b/oogabooga/string_format.c @@ -223,16 +223,6 @@ typedef void(*Logger_Proc)(Log_Level level, string s); #define log(...) LOG_BASE(LOG_INFO, __VA_ARGS__) -void default_logger(Log_Level level, string s) { - switch (level) { - case LOG_VERBOSE: print("[VERBOSE]: %s\n", s); break; - case LOG_INFO: print("[INFO]: %s\n", s); break; - case LOG_WARNING: print("[WARNING]: %s\n", s); break; - case LOG_ERROR: print("[ERROR]: %s\n", s); break; - case LOG_LEVEL_COUNT: break; - } -} - typedef struct String_Builder { union {