Day 20 complete

This commit is contained in:
Edward R. Gonzalez 2023-09-24 22:37:05 -04:00
parent 6fb75fd1ff
commit 805d9ec3a0
10 changed files with 388 additions and 148 deletions

View File

@ -25,5 +25,5 @@ The build is done in two stages:
## Gallery ## Gallery
![img](https://files.catbox.moe/xyh7u7.png) ![img](https://files.catbox.moe/9zau4s.png)
![img](https://files.catbox.moe/b7ifa8.png) ![img](https://files.catbox.moe/b7ifa8.png)

18
docs/Day 020.md Normal file
View File

@ -0,0 +1,18 @@
# Day 20
This stream was easier to follow than the last. I'm starting to gain some intuition on his style.
## Archtecture wise
I'm deciding to cause some more complexity leakage to the API layers..
I'll restrict it to on-demand but I want to have the engine have configurable options modern games tend to have, if I find it fullfilling todo so I may also expose some configuration for aspects of the engine, most games do not allow the user to mess with.
This was something I wanted to do with my previous attempt and constructing a "probeable" game engine, however, that attempt failed because I kept the API granularity far to high resolution.. and it was *Modern C++* which is the project I learned the hardway was not worth the mental effort to maintain or engage with.
(I also dove straight into graphics programming with Vulkan... making my own vulkan wrapper library... At least I learned a good chunk of the driver's graphics pipeline..)
Debug visualization for the audio was great.

View File

@ -5,39 +5,47 @@ NS_ENGINE_BEGIN
struct EngineState struct EngineState
{ {
s32 WavePeriod; s32 WaveSwitch;
s32 WaveToneHz; s32 WaveToneHz;
s32 ToneVolume; s32 ToneVolume;
s32 XOffset; s32 XOffset;
s32 YOffset; s32 YOffset;
}; };
using GetSoundSampleValueFn = s16( EngineState* state, SoundBuffer* sound_buffer ); using GetSoundSampleValueFn = s16( EngineState* state, AudioBuffer* sound_buffer );
internal s16 internal s16
square_wave_sample_value( EngineState* state, SoundBuffer* sound_buffer ) square_wave_sample_value( EngineState* state, AudioBuffer* sound_buffer )
{ {
s32 sample_value = (sound_buffer->RunningSampleIndex / (state->WavePeriod / 2) ) % 2 ? s32 wave_period = sound_buffer->SamplesPerSecond / state->WaveToneHz;
s32 sample_value = (sound_buffer->RunningSampleIndex / (wave_period / 2) ) % 2 ?
state->ToneVolume : - state->ToneVolume; state->ToneVolume : - state->ToneVolume;
return scast(s16, sample_value); return scast(s16, sample_value);
} }
internal s16 internal s16
sine_wave_sample_value( EngineState* state, SoundBuffer* sound_buffer ) sine_wave_sample_value( EngineState* state, AudioBuffer* sound_buffer )
{ {
local_persist f32 time = 0.f; local_persist f32 time = 0.f;
s32 wave_period = sound_buffer->SamplesPerSecond / state->WaveToneHz;
// time = TAU * (f32)sound_buffer->RunningSampleIndex / (f32)SoundTest_WavePeriod; // time = TAU * (f32)sound_buffer->RunningSampleIndex / (f32)SoundTest_WavePeriod;
f32 sine_value = sinf( time ); f32 sine_value = sinf( time );
s16 sample_value = scast(s16, sine_value * scast(f32, state->ToneVolume)); s16 sample_value = scast(s16, sine_value * scast(f32, state->ToneVolume));
time += TAU * 1.0f / scast(f32, state->WavePeriod ); time += TAU * 1.0f / scast(f32, wave_period );
if ( time > TAU )
{
time -= TAU;
}
return sample_value; return sample_value;
} }
internal void internal void
output_sound( EngineState* state, SoundBuffer* sound_buffer, GetSoundSampleValueFn* get_sample_value ) output_sound( EngineState* state, AudioBuffer* sound_buffer, GetSoundSampleValueFn* get_sample_value )
{ {
s16* sample_out = sound_buffer->Samples; s16* sample_out = sound_buffer->Samples;
for ( s32 sample_index = 0; sample_index < sound_buffer->NumSamples; ++ sample_index ) for ( s32 sample_index = 0; sample_index < sound_buffer->NumSamples; ++ sample_index )
@ -118,7 +126,7 @@ void shutdown()
} }
// TODO : I rather expose the back_buffer and sound_buffer using getters for access in any function. // TODO : I rather expose the back_buffer and sound_buffer using getters for access in any function.
void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBuffer* sound_buffer, Memory* memory ) void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* memory )
{ {
// Graphics & Input Test // Graphics & Input Test
local_persist u32 x_offset = 0; local_persist u32 x_offset = 0;
@ -145,10 +153,10 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
state->ToneVolume = 1000; state->ToneVolume = 1000;
state->WaveToneHz = 120; state->WaveToneHz = 120;
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz;
state->XOffset = 0; state->XOffset = 0;
state->YOffset = 0; state->YOffset = 0;
state->WaveSwitch = false;
#if Build_Debug && 0 #if Build_Debug && 0
{ {
@ -186,8 +194,13 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
b32 toggle_wave_tone = false; b32 toggle_wave_tone = false;
b32 pause_renderer = false;
local_persist b32 renderer_paused = false;
f32 analog_threshold = 0.5f; f32 analog_threshold = 0.5f;
#define pressed( btn ) (btn.EndedDown && btn.HalfTransitions < 1)
if ( controller->DSPad ) if ( controller->DSPad )
{ {
DualsensePadState* pad = controller->DSPad; DualsensePadState* pad = controller->DSPad;
@ -203,7 +216,7 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
raise_tone_hz |= pad->Square.EndedDown; raise_tone_hz |= pad->Square.EndedDown;
lower_tone_hz |= pad->X.EndedDown; lower_tone_hz |= pad->X.EndedDown;
toggle_wave_tone |= pad->Options.EndedDown; toggle_wave_tone |= pressed( pad->Options );
} }
if ( controller->XPad ) if ( controller->XPad )
{ {
@ -220,7 +233,7 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
raise_tone_hz |= pad->X.EndedDown; raise_tone_hz |= pad->X.EndedDown;
lower_tone_hz |= pad->A.EndedDown; lower_tone_hz |= pad->A.EndedDown;
toggle_wave_tone |= pad->Start.EndedDown; toggle_wave_tone |= pressed( pad->Start );
} }
if ( controller->Keyboard ) if ( controller->Keyboard )
{ {
@ -237,7 +250,9 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
raise_tone_hz |= keyboard->Right.EndedDown; raise_tone_hz |= keyboard->Right.EndedDown;
lower_tone_hz |= keyboard->Left.EndedDown; lower_tone_hz |= keyboard->Left.EndedDown;
toggle_wave_tone |= keyboard->Space.EndedDown; pause_renderer |= pressed( keyboard->Pause );
toggle_wave_tone |= pressed( keyboard->Space );
} }
x_offset += 3 * move_right; x_offset += 3 * move_right;
@ -252,31 +267,54 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
if ( lower_volume ) if ( lower_volume )
{ {
state->ToneVolume -= 10; state->ToneVolume -= 10;
if ( state->ToneVolume <= 0 )
state->ToneVolume = 0;
} }
if ( raise_tone_hz ) if ( raise_tone_hz )
{ {
state->WaveToneHz += 1; state->WaveToneHz += 1;
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz;
} }
if ( lower_tone_hz ) if ( lower_tone_hz )
{ {
state->WaveToneHz -= 1; state->WaveToneHz -= 1;
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz; if ( state->WaveToneHz <= 0 )
state->WaveToneHz = 1;
} }
if ( toggle_wave_tone ) if ( toggle_wave_tone )
{ {
wave_switch ^= true; state->WaveSwitch ^= true;
} }
render_weird_graident( back_buffer, x_offset, y_offset );
if ( pause_renderer )
{
if ( renderer_paused )
{
platform::set_pause_rendering(false);
renderer_paused = false;
}
else
{
platform::set_pause_rendering(true);
renderer_paused = true;
}
}
}
void update_audio( AudioBuffer* audio_buffer, Memory* memory )
{
EngineState* state = rcast( EngineState*, memory->Persistent );
do_once_start
do_once_end
// TODO(Ed) : Allow sample offsets here for more robust platform options // TODO(Ed) : Allow sample offsets here for more robust platform options
if ( ! wave_switch ) if ( ! state->WaveSwitch )
output_sound( state, sound_buffer, sine_wave_sample_value ); output_sound( state, audio_buffer, sine_wave_sample_value );
else else
output_sound( state, sound_buffer, square_wave_sample_value ); output_sound( state, audio_buffer, square_wave_sample_value );
render_weird_graident( back_buffer, x_offset, y_offset );
} }
NS_ENGINE_END NS_ENGINE_END

View File

@ -44,7 +44,7 @@ struct OffscreenBuffer
}; };
// TODO : Will be gutting this once we have other stuff lifted. // TODO : Will be gutting this once we have other stuff lifted.
struct SoundBuffer struct AudioBuffer
{ {
s16* Samples; s16* Samples;
u32 RunningSampleIndex; u32 RunningSampleIndex;
@ -93,6 +93,7 @@ union KeyboardState
DigitalBtn Left; DigitalBtn Left;
DigitalBtn Right; DigitalBtn Right;
DigitalBtn Space; DigitalBtn Space;
DigitalBtn Pause;
}; };
}; };

View File

@ -12,5 +12,5 @@
#if Build_Unity #if Build_Unity
#include "handmade.cpp" #include "handmade.cpp"
#include "engine.cpp" #include "engine.cpp"
#include "platform/platform_win32.cpp" #include "platform/win32_platform.cpp"
#endif #endif

View File

@ -2,6 +2,7 @@
#include "grime.hpp" #include "grime.hpp"
// JoyShock does not provide a proper c-linkage definition for its structs, so we have to push this warning ignore.
#ifdef COMPILER_CLANG #ifdef COMPILER_CLANG
# pragma clang diagnostic push # pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wreturn-type-c-linkage" # pragma clang diagnostic ignored "-Wreturn-type-c-linkage"

View File

@ -2,7 +2,8 @@
Platform abstraction layer for the project. Platform abstraction layer for the project.
Services the platform provides to the engine & game. Services the platform provides to the engine & game.
This should be the only file the engine or game layer can include This should be the only file the engine or game layer can include related to the platform layer.
(Public Interface essentially...)
*/ */
#pragma once #pragma once
@ -46,9 +47,12 @@ struct Debug_FileContent
void debug_file_free_content ( Debug_FileContent* file_content ); void debug_file_free_content ( Debug_FileContent* file_content );
Debug_FileContent debug_file_read_content ( char const* file_path ); Debug_FileContent debug_file_read_content ( char const* file_path );
b32 debug_file_write_content( char const* file_path, u32 content_size, void* content_memory ); b32 debug_file_write_content( char const* file_path, u32 content_size, void* content_memory );
#endif
NS_PLATFORM_END // Allows the engine or game to pause the renderering of any next frames.
// ( Prevents blipping of the black buffer )
void set_pause_rendering( b32 value );
#endif
// On-Demand platform interface. // On-Demand platform interface.
// Everything exposed here should be based on a feature a game may want to provide a user // Everything exposed here should be based on a feature a game may want to provide a user
@ -56,17 +60,19 @@ NS_PLATFORM_END
// TODO(Ed) : Implement this later when settings UI is setup. // TODO(Ed) : Implement this later when settings UI is setup.
#pragma region Settings Exposure #pragma region Settings Exposure
// Exposing specific variables for user configuration in settings // Exposing specific properties for user configuration in settings
// Returns the current monitor refresh rate. // Returns the current monitor refresh rate.
u32 const get_monitor_refresh_rate(); u32 get_monitor_refresh_rate();
// Sets the monitor refresh rate // Sets the monitor refresh rate
// Must be of the compatiable listing for the monitor the window surface is presenting to. // Must be of the compatiable listing for the monitor the window surface is presenting to.
void set_monitor_refresh_rate( u32 rate_in_hz ); void set_monitor_refresh_rate( u32 rate_in_hz );
u32 const get_engine_frame_rate_target(); u32 get_engine_frame_target();
void set_engine_frame_rate_target( u32 rate_in_hz ); void set_engine_frame_target( u32 rate_in_hz );
#pragma endregion Settings Exposure #pragma endregion Settings Exposure
NS_PLATFORM_END

View File

@ -10,7 +10,12 @@ void startup();
void shutdown(); void shutdown();
// Needs a contextual reference to four things: // Needs a contextual reference to four things:
// Timing, Input, Bitmap Buffer, Sound Buffer // Timing, Input, Bitmap Buffer
void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBuffer* sound_buffer, Memory* memory ); void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* memory );
// Audio timing is complicated, processing samples must be done at a different period from the rest of the engine's usual update.
// IMPORTANT: This has very tight timing, and cannot be more than a millisecond in execution.
// TODO(Ed) : Reduce timing pressure on performance by measuring it or pinging its time.
void update_audio( AudioBuffer* audio_buffer, Memory* memory );
NS_ENGINE_END NS_ENGINE_END

View File

@ -30,7 +30,7 @@
# define SWORD_MIN S64_MIN # define SWORD_MIN S64_MIN
# define SWORD_MAX S64_MAX # define SWORD_MAX S64_MAX
#else #else
# error Unknown architecture size. This library only supports 64 bit architectures. # error Unknown architecture size. This platform only supports 64 bit architectures.
#endif #endif
#define F32_MIN 1.17549435e-38f #define F32_MIN 1.17549435e-38f

View File

@ -21,16 +21,19 @@
#include "platform.hpp" #include "platform.hpp"
#include "jsl.hpp" // Using this to get dualsense controllers #include "jsl.hpp" // Using this to get dualsense controllers
#include "win32.hpp" #include "win32.hpp"
#include <malloc.h>
// Engine layer headers // Engine layer headers
#include "engine.hpp" #include "engine.hpp"
#include "platform_engine_api.hpp" #include "platform_engine_api.hpp"
// Standard-Library stand-ins
// #include <malloc.h>
// TODO(Ed) : Remove these ^^
// TOOD(Ed): Redo these macros properly later.
#if 1 #if 1
// TODO(Ed): Redo these macros properly later.
#define congrats( message ) do { \ #define congrats( message ) do { \
JslSetLightColour( 0, (255 << 16) | (215 << 8) ); \ JslSetLightColour( 0, (255 << 16) | (215 << 8) ); \
MessageBoxA( 0, message, "Congratulations!", MB_OK | MB_ICONEXCLAMATION ); \ MessageBoxA( 0, message, "Congratulations!", MB_OK | MB_ICONEXCLAMATION ); \
@ -58,6 +61,7 @@ ensure_impl( bool condition, char const* message ) {
NS_PLATFORM_BEGIN NS_PLATFORM_BEGIN
using namespace win32; using namespace win32;
// This is the "backbuffer" data related to the windowing surface provided by the operating system.
struct OffscreenBuffer struct OffscreenBuffer
{ {
BITMAPINFO Info; BITMAPINFO Info;
@ -84,15 +88,22 @@ struct DirectSoundBuffer
u32 SamplesPerSecond; u32 SamplesPerSecond;
u32 BytesPerSample; u32 BytesPerSample;
// TODO(Ed) : Makes math easier...
u32 BytesPerSecond;
u32 GuardSampleBytes;
DWORD IsPlaying; DWORD IsPlaying;
u32 RunningSampleIndex; u32 RunningSampleIndex;
// TODO(Ed) : Should this be in bytes?
u32 LatencySampleCount; u32 LatencySampleCount;
}; };
#pragma region Static Data #pragma region Static Data
// TODO(Ed) : This is a global for now. // TODO(Ed) : This is a global for now.
global bool Running; global b32 Running = false;
global b32 Pause_Rendering = false;
// Max controllers for the platform layer and thus for all other layers is 4. (Sanity and xinput limit) // Max controllers for the platform layer and thus for all other layers is 4. (Sanity and xinput limit)
constexpr u32 Max_Controllers = 4; constexpr u32 Max_Controllers = 4;
@ -123,8 +134,15 @@ global f32 Engine_Frame_Target_MS = 1000.f / scast(f32, Engine_Refresh_Hz);
#if Build_Debug #if Build_Debug
struct DebugTimeMarker struct DebugTimeMarker
{ {
DWORD PlayCursor; DWORD OutputPlayCusror;
DWORD WriteCursor; DWORD OutputWriteCursor;
DWORD OutputLocation;
DWORD OutputByteCount;
DWORD FlipPlayCursor;
DWORD FlipWriteCursor;
DWORD ExpectedFlipCursor;
}; };
void debug_file_free_content( Debug_FileContent* content ) void debug_file_free_content( Debug_FileContent* content )
@ -198,51 +216,116 @@ b32 debug_file_write_content( char const* file_path, u32 content_size, void* con
return true; return true;
} }
internal void void set_pause_rendering( b32 value )
debug_draw_vertical( u32 x_pos, u32 top, u32 bottom, u32 color )
{ {
u8* Pause_Rendering = value;
pixel_byte = rcast(u8*, Surface_Back_Buffer.Memory); }
pixel_byte += x_pos * Surface_Back_Buffer.BytesPerPixel;
pixel_byte += top * Surface_Back_Buffer.Pitch;
for ( u32 y = top; y < bottom; ++ y ) internal void
debug_draw_vertical( s32 x_pos, s32 top, s32 bottom, s32 color )
{
if ( top <= 0 )
{ {
u32* pixel = rcast(u32*, pixel_byte); top = 0;
*pixel = color; }
pixel_byte += Surface_Back_Buffer.Pitch; if ( bottom > Surface_Back_Buffer.Height )
{
bottom = Surface_Back_Buffer.Height;
}
if ( x_pos >= 0 && x_pos < Surface_Back_Buffer.Width )
{
u8*
pixel_byte = rcast(u8*, Surface_Back_Buffer.Memory);
pixel_byte += x_pos * Surface_Back_Buffer.BytesPerPixel;
pixel_byte += top * Surface_Back_Buffer.Pitch;
for ( s32 y = top; y < bottom; ++ y )
{
s32* pixel = rcast(s32*, pixel_byte);
*pixel = color;
pixel_byte += Surface_Back_Buffer.Pitch;
}
} }
} }
inline void inline void
debug_draw_sound_buffer_marker( DirectSoundBuffer* sound_buffer, f32 coefficient debug_draw_sound_buffer_marker( DirectSoundBuffer* sound_buffer, f32 ratio
, u32 pad_x, u32 pad_y , u32 pad_x, u32 pad_y
, u32 top, u32 bottom , u32 top, u32 bottom
, DWORD value, u32 color ) , DWORD value, u32 color )
{ {
assert( value < sound_buffer->SecondaryBufferSize ); // assert( value < sound_buffer->SecondaryBufferSize );
u32 x = pad_x + scast(u32, coefficient * scast(f32, value )); u32 x = pad_x + scast(u32, ratio * scast(f32, value ));
debug_draw_vertical( x, top, bottom, color ); debug_draw_vertical( x, top, bottom, color );
} }
internal void internal void
debug_sync_display( DirectSoundBuffer* sound_buffer debug_sync_display( DirectSoundBuffer* sound_buffer
, u32 num_markers, DebugTimeMarker* markers , u32 num_markers, DebugTimeMarker* markers
, u32 current_marker
, f32 ms_per_frame ) , f32 ms_per_frame )
{ {
u32 pad_x = 16; u32 pad_x = 32;
u32 pad_y = 16; u32 pad_y = 16;
f32 coefficient = scast(f32, Surface_Back_Buffer.Width) / scast(f32, sound_buffer->SecondaryBufferSize); f32 buffers_ratio = scast(f32, Surface_Back_Buffer.Width) / (scast(f32, sound_buffer->SecondaryBufferSize) * 1);
u32 top = pad_y;
u32 bottom = Surface_Back_Buffer.Height - pad_y;
u32 line_height = 64;
for ( u32 marker_index = 0; marker_index < num_markers; ++ marker_index ) for ( u32 marker_index = 0; marker_index < num_markers; ++ marker_index )
{ {
DebugTimeMarker* marker = & markers[marker_index]; DebugTimeMarker* marker = & markers[marker_index];
debug_draw_sound_buffer_marker( sound_buffer, coefficient, pad_x, pad_y, top, bottom, marker->PlayCursor, 0xFFFFFFFF ); assert( marker->OutputPlayCusror < sound_buffer->SecondaryBufferSize );
debug_draw_sound_buffer_marker( sound_buffer, coefficient, pad_x, pad_y, top, bottom, marker->WriteCursor, 0xFFFF0000 ); assert( marker->OutputWriteCursor < sound_buffer->SecondaryBufferSize );
assert( marker->OutputLocation < sound_buffer->SecondaryBufferSize );
assert( marker->OutputByteCount < sound_buffer->SecondaryBufferSize );
assert( marker->FlipPlayCursor < sound_buffer->SecondaryBufferSize );
assert( marker->FlipWriteCursor < sound_buffer->SecondaryBufferSize );
DWORD play_color = 0x88888888;
DWORD write_color = 0x88800000;
DWORD expected_flip_color = 0xFFFFF000;
DWORD play_window_color = 0xFFFF00FF;
u32 top = pad_y;
u32 bottom = pad_y + line_height;
if ( marker_index == current_marker )
{
play_color = 0xFFFFFFFF;
write_color = 0xFFFF0000;
top += pad_y + line_height;
bottom += pad_y + line_height;
u32 row_2_top = top;
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->OutputPlayCusror, play_color );
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->OutputWriteCursor, write_color );
play_color = 0xFFFFFFFF;
write_color = 0xFFFF0000;
top += pad_y + line_height;
bottom += pad_y + line_height;
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->OutputLocation, play_color );
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->OutputLocation + marker->OutputByteCount, write_color );
play_color = 0xFFFFFFFF;
write_color = 0xFFFF0000;
top += pad_y + line_height;
bottom += pad_y + line_height;
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, row_2_top, bottom, marker->ExpectedFlipCursor, expected_flip_color );
}
DWORD play_window = marker->FlipPlayCursor + 480 * sound_buffer->BytesPerSample;
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->FlipPlayCursor, play_color );
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, play_window, play_window_color );
debug_draw_sound_buffer_marker( sound_buffer, buffers_ratio, pad_x, pad_y, top, bottom, marker->FlipWriteCursor, write_color );
} }
} }
#endif #endif
@ -759,8 +842,9 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
// WinDimensions dimensions = get_window_dimensions( window_handle ); // WinDimensions dimensions = get_window_dimensions( window_handle );
resize_dib_section( &Surface_Back_Buffer, 1280, 720 ); resize_dib_section( &Surface_Back_Buffer, 1280, 720 );
DWORD last_play_cursor = 0; b32 sound_is_valid = false;
b32 sound_is_valid = false; DWORD ds_cursor_byte_delta = 0;
f32 ds_latency_ms = 0;
DirectSoundBuffer ds_sound_buffer; DirectSoundBuffer ds_sound_buffer;
{ {
ds_sound_buffer.IsPlaying = 0; ds_sound_buffer.IsPlaying = 0;
@ -778,9 +862,15 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
// ds_clear_sound_buffer( & sound_output ); // ds_clear_sound_buffer( & sound_output );
ds_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING ); ds_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING );
// Direct sound requires 3 frames of audio latency for no bugs to show u ds_sound_buffer.BytesPerSecond = ds_sound_buffer.SamplesPerSecond * ds_sound_buffer.BytesPerSample;
constexpr u32 frames_of_audio_latency = 3; ds_sound_buffer.GuardSampleBytes = (ds_sound_buffer.BytesPerSecond / Engine_Refresh_Hz) / 2;
ds_sound_buffer.LatencySampleCount = frames_of_audio_latency * ( ds_sound_buffer.SamplesPerSecond / Engine_Refresh_Hz );
// TODO(Ed): When switching to core audio at minimum, this will be 1 ms of lag and guard samples wont really be needed.
u32 min_guard_sample_bytes = 1540;
if ( ds_sound_buffer.GuardSampleBytes < min_guard_sample_bytes )
{
ds_sound_buffer.GuardSampleBytes = min_guard_sample_bytes;
}
} }
#if Build_Development #if Build_Development
u32 debug_marker_index = 0; u32 debug_marker_index = 0;
@ -847,13 +937,13 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
u64 last_frame_clock = timing_get_wall_clock(); u64 last_frame_clock = timing_get_wall_clock();
u64 last_frame_cycle = __rdtsc(); u64 last_frame_cycle = __rdtsc();
u64 flip_wall_clock = last_frame_clock;
#if Build_Development #if Build_Development
u64 startup_cycles = last_frame_cycle - launch_cycle; u64 startup_cycles = last_frame_cycle - launch_cycle;
f32 startup_ms = timing_get_ms_elapsed( launch_clock, last_frame_clock ); f32 startup_ms = timing_get_ms_elapsed( launch_clock, last_frame_clock );
#endif #endif
Running = true; Running = true;
#if 0 #if 0
// This tests the play & write cursor update frequency. // This tests the play & write cursor update frequency.
@ -892,7 +982,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
// Keyboard Polling // Keyboard Polling
// Keyboards are unified for now. // Keyboards are unified for now.
{ {
constexpr u32 is_down = 0x8000; constexpr u32 is_down = 0x80000000;
input_process_digital_btn( & old_keyboard->Q, & new_keyboard->Q, GetAsyncKeyState( 'Q' ), is_down ); input_process_digital_btn( & old_keyboard->Q, & new_keyboard->Q, GetAsyncKeyState( 'Q' ), is_down );
input_process_digital_btn( & old_keyboard->E, & new_keyboard->E, GetAsyncKeyState( 'E' ), is_down ); input_process_digital_btn( & old_keyboard->E, & new_keyboard->E, GetAsyncKeyState( 'E' ), is_down );
input_process_digital_btn( & old_keyboard->W, & new_keyboard->W, GetAsyncKeyState( 'W' ), is_down ); input_process_digital_btn( & old_keyboard->W, & new_keyboard->W, GetAsyncKeyState( 'W' ), is_down );
@ -906,6 +996,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
input_process_digital_btn( & old_keyboard->Left, & new_keyboard->Left, GetAsyncKeyState( VK_LEFT ), is_down ); input_process_digital_btn( & old_keyboard->Left, & new_keyboard->Left, GetAsyncKeyState( VK_LEFT ), is_down );
input_process_digital_btn( & old_keyboard->Right, & new_keyboard->Right, GetAsyncKeyState( VK_RIGHT ), is_down ); input_process_digital_btn( & old_keyboard->Right, & new_keyboard->Right, GetAsyncKeyState( VK_RIGHT ), is_down );
input_process_digital_btn( & old_keyboard->Space, & new_keyboard->Space, GetAsyncKeyState( VK_SPACE ), is_down ); input_process_digital_btn( & old_keyboard->Space, & new_keyboard->Space, GetAsyncKeyState( VK_SPACE ), is_down );
input_process_digital_btn( & old_keyboard->Pause, & new_keyboard->Pause, GetAsyncKeyState( VK_PAUSE ), is_down );
input.Controllers[0].Keyboard = new_keyboard; input.Controllers[0].Keyboard = new_keyboard;
} }
@ -1015,17 +1106,74 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
} }
} }
// Pain... // Engine's logical iteration and rendering process
// DWORD ds_play_cursor; engine::update_and_render( & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.Memory), & engine_memory );
// DWORD ds_write_cursor;
DWORD byte_to_lock = 0; u64 audio_frame_start = timing_get_wall_clock();
DWORD bytes_to_write = 0; f32 flip_to_audio_ms = timing_get_ms_elapsed( flip_wall_clock, audio_frame_start );
DWORD target_cursor = 0;
// if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & ds_play_cursor, & ds_write_cursor ) )) DWORD ds_play_cursor;
if ( sound_is_valid ) DWORD ds_write_cursor;
{ do {
byte_to_lock = (ds_sound_buffer.RunningSampleIndex * ds_sound_buffer.BytesPerSample) % ds_sound_buffer.SecondaryBufferSize; /*
target_cursor = (last_play_cursor + (ds_sound_buffer.LatencySampleCount * ds_sound_buffer.BytesPerSample)) % ds_sound_buffer.SecondaryBufferSize; Sound computation:
There is a sync boundary value, that is the number of samples that the engine's frame-time may vary by
(ex: approx 2ms of variance between frame-times).
On wakeup : Check play cursor position and forcast ahead where the cursor will be for the next sync boundary.
Based on that, check the write cursor position, if its (at least) before the synch boundary, the target write position is
the frame boundary plus one frame. (Low latency)
If its after (sync boundary), we cannot sync audio.
Write a frame's worth of audio plus some number of "guard" samples. (High Latency)
*/
if ( ! SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & ds_play_cursor, & ds_write_cursor ) ))
{
sound_is_valid = false;
break;
}
if ( ! sound_is_valid )
{
ds_sound_buffer.RunningSampleIndex = ds_write_cursor / ds_sound_buffer.BytesPerSample;
sound_is_valid = true;
}
DWORD byte_to_lock = 0;
DWORD target_cursor = 0;
DWORD bytes_to_write = 0;
byte_to_lock = (ds_sound_buffer.RunningSampleIndex * ds_sound_buffer.BytesPerSample) % ds_sound_buffer.SecondaryBufferSize;
DWORD bytes_per_second = ds_sound_buffer.BytesPerSample * ds_sound_buffer.SamplesPerSecond;
DWORD expected_samplebytes_per_frame = bytes_per_second / Engine_Refresh_Hz;
f32 left_until_flip_ms = Engine_Frame_Target_MS - flip_to_audio_ms;
DWORD expected_bytes_until_flip = scast(DWORD, (left_until_flip_ms / Engine_Frame_Target_MS) * scast(f32, expected_samplebytes_per_frame));
DWORD expected_sync_boundary_byte = ds_play_cursor + expected_bytes_until_flip;
DWORD sync_write_cursor = ds_write_cursor;
if ( sync_write_cursor < ds_play_cursor )
{
// unwrap the cursor so its ahead of the play curosr linearly.
sync_write_cursor += ds_sound_buffer.SecondaryBufferSize;
}
assert( sync_write_cursor >= ds_play_cursor );
sync_write_cursor += ds_sound_buffer.GuardSampleBytes;
b32 audio_interface_is_low_latency = sync_write_cursor < expected_sync_boundary_byte;
if ( audio_interface_is_low_latency )
{
target_cursor = ( expected_sync_boundary_byte + expected_samplebytes_per_frame );
}
else
{
target_cursor = (ds_write_cursor + expected_samplebytes_per_frame + ds_sound_buffer.GuardSampleBytes);
}
target_cursor %= ds_sound_buffer.SecondaryBufferSize;
if ( byte_to_lock > target_cursor) if ( byte_to_lock > target_cursor)
{ {
@ -1038,92 +1186,114 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
// Behind play cursor |--byte_to_write-->--play--| // Behind play cursor |--byte_to_write-->--play--|
bytes_to_write = target_cursor - byte_to_lock; bytes_to_write = target_cursor - byte_to_lock;
} }
// sound_is_valid = true;
}
// s16 samples[ 48000 * 2 ]; // Engine Sound
engine::SoundBuffer sound_buffer {}; // s16 samples[ 48000 * 2 ];
sound_buffer.NumSamples = bytes_to_write / ds_sound_buffer.BytesPerSample; engine::AudioBuffer sound_buffer {};
sound_buffer.RunningSampleIndex = ds_sound_buffer.RunningSampleIndex; sound_buffer.NumSamples = bytes_to_write / ds_sound_buffer.BytesPerSample;
sound_buffer.SamplesPerSecond = ds_sound_buffer.SamplesPerSecond; sound_buffer.RunningSampleIndex = ds_sound_buffer.RunningSampleIndex;
sound_buffer.Samples = ds_sound_buffer.Samples; sound_buffer.SamplesPerSecond = ds_sound_buffer.SamplesPerSecond;
sound_buffer.Samples = ds_sound_buffer.Samples;
engine::update_audio( & sound_buffer, & engine_memory );
engine::update_and_render( & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.Memory), & sound_buffer, & engine_memory ); DebugTimeMarker* marker = & debug_markers[ debug_marker_index ];
marker->OutputPlayCusror = ds_play_cursor;
marker->OutputWriteCursor = ds_write_cursor;
marker->OutputLocation = byte_to_lock;
marker->OutputByteCount = bytes_to_write;
marker->ExpectedFlipCursor = expected_sync_boundary_byte;
// Update audio buffer // Update audio buffer
do { if ( ! sound_is_valid )
break;
#if Build_Development && 0
#if 0
DWORD play_cursor;
DWORD write_cursor;
ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor );
#endif
DWORD unwrapped_write_cursor = ds_write_cursor;
if ( unwrapped_write_cursor < ds_play_cursor )
{
unwrapped_write_cursor += ds_sound_buffer.SecondaryBufferSize;
}
ds_cursor_byte_delta = unwrapped_write_cursor - ds_play_cursor;
constexpr f32 to_milliseconds = 1000.f;
f32 sample_delta = scast(f32, ds_cursor_byte_delta) / scast(f32, ds_sound_buffer.BytesPerSample);
f32 ds_latency_s = sample_delta / scast(f32, ds_sound_buffer.SamplesPerSecond);
ds_latency_ms = ds_latency_s * to_milliseconds;
char text_buffer[256];
sprintf_s( text_buffer, sizeof(text_buffer), "BTL:%u TC:%u BTW:%u - PC:%u WC:%u DELTA:%u bytes %f ms\n"
, (u32)byte_to_lock, (u32)target_cursor, (u32)bytes_to_write
, (u32)play_cursor, (u32)write_cursor, (u32)ds_cursor_byte_delta, ds_latency_ms );
OutputDebugStringA( text_buffer );
#endif
ds_fill_sound_buffer( & ds_sound_buffer, byte_to_lock, bytes_to_write );
DWORD ds_status = 0; DWORD ds_status = 0;
if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetStatus( & ds_status ) ) ) if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetStatus( & ds_status ) ) )
{ {
ds_sound_buffer.IsPlaying = ds_status & DSBSTATUS_PLAYING; ds_sound_buffer.IsPlaying = ds_status & DSBSTATUS_PLAYING;
} }
if ( ! sound_is_valid )
break;
ds_fill_sound_buffer( & ds_sound_buffer, byte_to_lock, bytes_to_write );
#if Build_Development && 0
DWORD play_cursor;
DWORD write_cursor;
ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor );
char text_buffer[256];
sprintf_s( text_buffer, sizeof(text_buffer), "LPC:%u BTL:%u TC:%u BTW:%u - PC:%u WC:%u\n"
, (u32)last_play_cursor, (u32)byte_to_lock, (u32)target_cursor, (u32)bytes_to_write, (u32)play_cursor, (u32)write_cursor );
OutputDebugStringA( text_buffer );
#endif
if ( ds_sound_buffer.IsPlaying ) if ( ds_sound_buffer.IsPlaying )
break; break;
ds_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING ); ds_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING );
} while(0); } while(0);
u64 work_frame_end_cycle = __rdtsc(); // Timing Update
u64 work_frame_end_clock = timing_get_wall_clock();
f32 work_frame_ms = timing_get_ms_elapsed( last_frame_clock, work_frame_end_clock ); // WorkSecondsElapsed
f32 work_cycles = timing_get_ms_elapsed( last_frame_cycle, work_frame_end_cycle );
f32 frame_elapsed_ms = work_frame_ms; // SecondsElapsedForFrame
if ( frame_elapsed_ms < Engine_Frame_Target_MS )
{ {
s32 sleep_ms = scast(DWORD, (Engine_Frame_Target_MS - frame_elapsed_ms)) - 1; u64 work_frame_end_cycle = __rdtsc();
if ( sleep_ms > 0 && ! sub_ms_granularity_required && sleep_is_granular ) u64 work_frame_end_clock = timing_get_wall_clock();
{
Sleep( scast(DWORD, sleep_ms) );
}
u64 frame_clock = timing_get_wall_clock(); f32 work_frame_ms = timing_get_ms_elapsed( last_frame_clock, work_frame_end_clock ); // WorkSecondsElapsed
frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock ); f32 work_cycles = timing_get_ms_elapsed( last_frame_cycle, work_frame_end_cycle );
f32 frame_elapsed_ms = work_frame_ms; // SecondsElapsedForFrame
if ( frame_elapsed_ms < Engine_Frame_Target_MS ) if ( frame_elapsed_ms < Engine_Frame_Target_MS )
{ {
// TODO(Ed) : Log ms discrepancy here. s32 sleep_ms = scast(DWORD, (Engine_Frame_Target_MS - frame_elapsed_ms)) - 1;
} if ( sleep_ms > 0 && ! sub_ms_granularity_required && sleep_is_granular )
{
Sleep( scast(DWORD, sleep_ms) );
}
assert( frame_elapsed_ms < Engine_Frame_Target_MS ); u64 frame_clock = timing_get_wall_clock();
while ( frame_elapsed_ms < Engine_Frame_Target_MS )
{
frame_clock = timing_get_wall_clock();
frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock ); frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock );
} if ( frame_elapsed_ms < Engine_Frame_Target_MS )
} {
else // TODO(Ed) : Log missed sleep here.
{ }
// TODO(Ed) : Missed the display sync window!
}
last_frame_clock = timing_get_wall_clock(); // LastCouner while ( frame_elapsed_ms < Engine_Frame_Target_MS )
last_frame_cycle = __rdtsc(); {
frame_clock = timing_get_wall_clock();
frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock );
}
}
else
{
// TODO(Ed) : Missed the display sync window!
}
last_frame_clock = timing_get_wall_clock(); // LastCouner
last_frame_cycle = __rdtsc();
}
// Update surface back buffer // Update surface back buffer
if ( ! Pause_Rendering )
{ {
WinDimensions dimensions = get_window_dimensions( window_handle ); WinDimensions dimensions = get_window_dimensions( window_handle );
HDC device_context = GetDC( window_handle ); HDC device_context = GetDC( window_handle );
#if Build_Development #if Build_Development
debug_sync_display( & ds_sound_buffer, debug_marker_history_size, debug_markers, Engine_Frame_Target_MS ); // Note: debug_marker_index is wrong for the 0th index
debug_sync_display( & ds_sound_buffer
, debug_marker_history_size, debug_markers, debug_marker_index - 1
, Engine_Frame_Target_MS );
#endif #endif
display_buffer_in_window( device_context, dimensions.Width, dimensions.Height, &Surface_Back_Buffer display_buffer_in_window( device_context, dimensions.Width, dimensions.Height, &Surface_Back_Buffer
@ -1131,33 +1301,34 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
, dimensions.Width, dimensions.Height ); , dimensions.Width, dimensions.Height );
} }
flip_wall_clock = timing_get_wall_clock();
#if Build_Development
{ {
// Audio Debug
DWORD play_cursor = 0; DWORD play_cursor = 0;
DWORD write_cursor = 0; DWORD write_cursor = 0;
if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor ) ) ) if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor ) ) )
{ {
last_play_cursor = play_cursor;
if ( ! sound_is_valid ) if ( ! sound_is_valid )
{ {
ds_sound_buffer.RunningSampleIndex = write_cursor / ds_sound_buffer.BytesPerSample; ds_sound_buffer.RunningSampleIndex = write_cursor / ds_sound_buffer.BytesPerSample;
sound_is_valid = true; sound_is_valid = true;
} }
assert( debug_marker_index < debug_marker_history_size )
DebugTimeMarker* marker = & debug_markers[ debug_marker_index ];
marker->FlipPlayCursor = play_cursor;
marker->FlipWriteCursor = write_cursor;
} }
else }
{ #endif
sound_is_valid = false;
}
#if Build_Development #if Build_Development
assert( debug_marker_index < debug_marker_history_size )
debug_markers[debug_marker_index] = { play_cursor, write_cursor };
debug_marker_index++; debug_marker_index++;
if ( debug_marker_index >= debug_marker_history_size ) if ( debug_marker_index >= debug_marker_history_size )
debug_marker_index = 0; debug_marker_index = 0;
#endif #endif
}
} }
engine::shutdown(); engine::shutdown();