mirror of
https://github.com/Ed94/HandmadeHero.git
synced 2024-12-22 06:14:45 -08:00
Day 20 complete
This commit is contained in:
parent
6fb75fd1ff
commit
805d9ec3a0
@ -25,5 +25,5 @@ The build is done in two stages:
|
||||
|
||||
## Gallery
|
||||
|
||||
![img](https://files.catbox.moe/xyh7u7.png)
|
||||
![img](https://files.catbox.moe/9zau4s.png)
|
||||
![img](https://files.catbox.moe/b7ifa8.png)
|
||||
|
18
docs/Day 020.md
Normal file
18
docs/Day 020.md
Normal 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.
|
@ -5,39 +5,47 @@ NS_ENGINE_BEGIN
|
||||
|
||||
struct EngineState
|
||||
{
|
||||
s32 WavePeriod;
|
||||
s32 WaveSwitch;
|
||||
s32 WaveToneHz;
|
||||
s32 ToneVolume;
|
||||
s32 XOffset;
|
||||
s32 YOffset;
|
||||
};
|
||||
|
||||
using GetSoundSampleValueFn = s16( EngineState* state, SoundBuffer* sound_buffer );
|
||||
using GetSoundSampleValueFn = s16( EngineState* state, AudioBuffer* sound_buffer );
|
||||
|
||||
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;
|
||||
|
||||
return scast(s16, sample_value);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
s32 wave_period = sound_buffer->SamplesPerSecond / state->WaveToneHz;
|
||||
|
||||
// time = TAU * (f32)sound_buffer->RunningSampleIndex / (f32)SoundTest_WavePeriod;
|
||||
f32 sine_value = sinf( time );
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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.
|
||||
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
|
||||
local_persist u32 x_offset = 0;
|
||||
@ -145,10 +153,10 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
|
||||
|
||||
state->ToneVolume = 1000;
|
||||
state->WaveToneHz = 120;
|
||||
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz;
|
||||
|
||||
state->XOffset = 0;
|
||||
state->YOffset = 0;
|
||||
state->WaveSwitch = false;
|
||||
|
||||
#if Build_Debug && 0
|
||||
{
|
||||
@ -186,8 +194,13 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
|
||||
|
||||
b32 toggle_wave_tone = false;
|
||||
|
||||
b32 pause_renderer = false;
|
||||
local_persist b32 renderer_paused = false;
|
||||
|
||||
f32 analog_threshold = 0.5f;
|
||||
|
||||
#define pressed( btn ) (btn.EndedDown && btn.HalfTransitions < 1)
|
||||
|
||||
if ( 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;
|
||||
lower_tone_hz |= pad->X.EndedDown;
|
||||
|
||||
toggle_wave_tone |= pad->Options.EndedDown;
|
||||
toggle_wave_tone |= pressed( pad->Options );
|
||||
}
|
||||
if ( controller->XPad )
|
||||
{
|
||||
@ -220,7 +233,7 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
|
||||
raise_tone_hz |= pad->X.EndedDown;
|
||||
lower_tone_hz |= pad->A.EndedDown;
|
||||
|
||||
toggle_wave_tone |= pad->Start.EndedDown;
|
||||
toggle_wave_tone |= pressed( pad->Start );
|
||||
}
|
||||
if ( controller->Keyboard )
|
||||
{
|
||||
@ -237,7 +250,9 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
|
||||
raise_tone_hz |= keyboard->Right.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;
|
||||
@ -252,31 +267,54 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBu
|
||||
if ( lower_volume )
|
||||
{
|
||||
state->ToneVolume -= 10;
|
||||
if ( state->ToneVolume <= 0 )
|
||||
state->ToneVolume = 0;
|
||||
}
|
||||
|
||||
if ( raise_tone_hz )
|
||||
{
|
||||
state->WaveToneHz += 1;
|
||||
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz;
|
||||
}
|
||||
if ( lower_tone_hz )
|
||||
{
|
||||
state->WaveToneHz -= 1;
|
||||
state->WavePeriod = sound_buffer->SamplesPerSecond / state->WaveToneHz;
|
||||
if ( state->WaveToneHz <= 0 )
|
||||
state->WaveToneHz = 1;
|
||||
}
|
||||
|
||||
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
|
||||
if ( ! wave_switch )
|
||||
output_sound( state, sound_buffer, sine_wave_sample_value );
|
||||
if ( ! state->WaveSwitch )
|
||||
output_sound( state, audio_buffer, sine_wave_sample_value );
|
||||
else
|
||||
output_sound( state, sound_buffer, square_wave_sample_value );
|
||||
|
||||
render_weird_graident( back_buffer, x_offset, y_offset );
|
||||
output_sound( state, audio_buffer, square_wave_sample_value );
|
||||
}
|
||||
|
||||
NS_ENGINE_END
|
||||
|
@ -44,7 +44,7 @@ struct OffscreenBuffer
|
||||
};
|
||||
|
||||
// TODO : Will be gutting this once we have other stuff lifted.
|
||||
struct SoundBuffer
|
||||
struct AudioBuffer
|
||||
{
|
||||
s16* Samples;
|
||||
u32 RunningSampleIndex;
|
||||
@ -93,6 +93,7 @@ union KeyboardState
|
||||
DigitalBtn Left;
|
||||
DigitalBtn Right;
|
||||
DigitalBtn Space;
|
||||
DigitalBtn Pause;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -12,5 +12,5 @@
|
||||
#if Build_Unity
|
||||
#include "handmade.cpp"
|
||||
#include "engine.cpp"
|
||||
#include "platform/platform_win32.cpp"
|
||||
#include "platform/win32_platform.cpp"
|
||||
#endif
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#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
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
|
||||
|
@ -2,7 +2,8 @@
|
||||
Platform abstraction layer for the project.
|
||||
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
|
||||
@ -46,9 +47,12 @@ struct Debug_FileContent
|
||||
void debug_file_free_content ( Debug_FileContent* file_content );
|
||||
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 );
|
||||
#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.
|
||||
// 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.
|
||||
#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.
|
||||
u32 const get_monitor_refresh_rate();
|
||||
u32 get_monitor_refresh_rate();
|
||||
|
||||
// Sets the monitor refresh rate
|
||||
// Must be of the compatiable listing for the monitor the window surface is presenting to.
|
||||
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
|
||||
|
||||
NS_PLATFORM_END
|
||||
|
@ -10,7 +10,12 @@ void startup();
|
||||
void shutdown();
|
||||
|
||||
// Needs a contextual reference to four things:
|
||||
// Timing, Input, Bitmap Buffer, Sound Buffer
|
||||
void update_and_render( InputState* input, OffscreenBuffer* back_buffer, SoundBuffer* sound_buffer, Memory* memory );
|
||||
// Timing, Input, Bitmap Buffer
|
||||
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
|
||||
|
@ -30,7 +30,7 @@
|
||||
# define SWORD_MIN S64_MIN
|
||||
# define SWORD_MAX S64_MAX
|
||||
#else
|
||||
# error Unknown architecture size. This library only supports 64 bit architectures.
|
||||
# error Unknown architecture size. This platform only supports 64 bit architectures.
|
||||
#endif
|
||||
|
||||
#define F32_MIN 1.17549435e-38f
|
||||
|
@ -21,16 +21,19 @@
|
||||
#include "platform.hpp"
|
||||
#include "jsl.hpp" // Using this to get dualsense controllers
|
||||
#include "win32.hpp"
|
||||
#include <malloc.h>
|
||||
|
||||
// Engine layer headers
|
||||
#include "engine.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
|
||||
// TODO(Ed): Redo these macros properly later.
|
||||
|
||||
#define congrats( message ) do { \
|
||||
JslSetLightColour( 0, (255 << 16) | (215 << 8) ); \
|
||||
MessageBoxA( 0, message, "Congratulations!", MB_OK | MB_ICONEXCLAMATION ); \
|
||||
@ -58,6 +61,7 @@ ensure_impl( bool condition, char const* message ) {
|
||||
NS_PLATFORM_BEGIN
|
||||
using namespace win32;
|
||||
|
||||
// This is the "backbuffer" data related to the windowing surface provided by the operating system.
|
||||
struct OffscreenBuffer
|
||||
{
|
||||
BITMAPINFO Info;
|
||||
@ -84,15 +88,22 @@ struct DirectSoundBuffer
|
||||
u32 SamplesPerSecond;
|
||||
u32 BytesPerSample;
|
||||
|
||||
// TODO(Ed) : Makes math easier...
|
||||
u32 BytesPerSecond;
|
||||
u32 GuardSampleBytes;
|
||||
|
||||
DWORD IsPlaying;
|
||||
u32 RunningSampleIndex;
|
||||
|
||||
// TODO(Ed) : Should this be in bytes?
|
||||
u32 LatencySampleCount;
|
||||
};
|
||||
|
||||
|
||||
#pragma region Static Data
|
||||
// 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)
|
||||
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
|
||||
struct DebugTimeMarker
|
||||
{
|
||||
DWORD PlayCursor;
|
||||
DWORD WriteCursor;
|
||||
DWORD OutputPlayCusror;
|
||||
DWORD OutputWriteCursor;
|
||||
DWORD OutputLocation;
|
||||
DWORD OutputByteCount;
|
||||
|
||||
DWORD FlipPlayCursor;
|
||||
DWORD FlipWriteCursor;
|
||||
|
||||
DWORD ExpectedFlipCursor;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal void
|
||||
debug_draw_vertical( u32 x_pos, u32 top, u32 bottom, u32 color )
|
||||
void set_pause_rendering( b32 value )
|
||||
{
|
||||
Pause_Rendering = value;
|
||||
}
|
||||
|
||||
internal void
|
||||
debug_draw_vertical( s32 x_pos, s32 top, s32 bottom, s32 color )
|
||||
{
|
||||
if ( top <= 0 )
|
||||
{
|
||||
top = 0;
|
||||
}
|
||||
|
||||
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 ( u32 y = top; y < bottom; ++ y )
|
||||
for ( s32 y = top; y < bottom; ++ y )
|
||||
{
|
||||
u32* pixel = rcast(u32*, pixel_byte);
|
||||
s32* pixel = rcast(s32*, pixel_byte);
|
||||
*pixel = color;
|
||||
|
||||
pixel_byte += Surface_Back_Buffer.Pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 top, u32 bottom
|
||||
, DWORD value, u32 color )
|
||||
{
|
||||
assert( value < sound_buffer->SecondaryBufferSize );
|
||||
u32 x = pad_x + scast(u32, coefficient * scast(f32, value ));
|
||||
// assert( value < sound_buffer->SecondaryBufferSize );
|
||||
u32 x = pad_x + scast(u32, ratio * scast(f32, value ));
|
||||
debug_draw_vertical( x, top, bottom, color );
|
||||
}
|
||||
|
||||
internal void
|
||||
debug_sync_display( DirectSoundBuffer* sound_buffer
|
||||
, u32 num_markers, DebugTimeMarker* markers
|
||||
, u32 current_marker
|
||||
, f32 ms_per_frame )
|
||||
{
|
||||
u32 pad_x = 16;
|
||||
u32 pad_x = 32;
|
||||
u32 pad_y = 16;
|
||||
f32 coefficient = scast(f32, Surface_Back_Buffer.Width) / scast(f32, sound_buffer->SecondaryBufferSize);
|
||||
|
||||
u32 top = pad_y;
|
||||
u32 bottom = Surface_Back_Buffer.Height - pad_y;
|
||||
f32 buffers_ratio = scast(f32, Surface_Back_Buffer.Width) / (scast(f32, sound_buffer->SecondaryBufferSize) * 1);
|
||||
|
||||
u32 line_height = 64;
|
||||
for ( u32 marker_index = 0; marker_index < num_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 );
|
||||
debug_draw_sound_buffer_marker( sound_buffer, coefficient, pad_x, pad_y, top, bottom, marker->WriteCursor, 0xFFFF0000 );
|
||||
assert( marker->OutputPlayCusror < sound_buffer->SecondaryBufferSize );
|
||||
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
|
||||
@ -759,8 +842,9 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
// WinDimensions dimensions = get_window_dimensions( window_handle );
|
||||
resize_dib_section( &Surface_Back_Buffer, 1280, 720 );
|
||||
|
||||
DWORD last_play_cursor = 0;
|
||||
b32 sound_is_valid = false;
|
||||
DWORD ds_cursor_byte_delta = 0;
|
||||
f32 ds_latency_ms = 0;
|
||||
DirectSoundBuffer ds_sound_buffer;
|
||||
{
|
||||
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_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING );
|
||||
|
||||
// Direct sound requires 3 frames of audio latency for no bugs to show u
|
||||
constexpr u32 frames_of_audio_latency = 3;
|
||||
ds_sound_buffer.LatencySampleCount = frames_of_audio_latency * ( ds_sound_buffer.SamplesPerSecond / Engine_Refresh_Hz );
|
||||
ds_sound_buffer.BytesPerSecond = ds_sound_buffer.SamplesPerSecond * ds_sound_buffer.BytesPerSample;
|
||||
ds_sound_buffer.GuardSampleBytes = (ds_sound_buffer.BytesPerSecond / Engine_Refresh_Hz) / 2;
|
||||
|
||||
// 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
|
||||
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_cycle = __rdtsc();
|
||||
u64 flip_wall_clock = last_frame_clock;
|
||||
|
||||
#if Build_Development
|
||||
u64 startup_cycles = last_frame_cycle - launch_cycle;
|
||||
f32 startup_ms = timing_get_ms_elapsed( launch_clock, last_frame_clock );
|
||||
#endif
|
||||
|
||||
|
||||
Running = true;
|
||||
#if 0
|
||||
// This tests the play & write cursor update frequency.
|
||||
@ -892,7 +982,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
// Keyboard Polling
|
||||
// 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->E, & new_keyboard->E, GetAsyncKeyState( 'E' ), 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->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->Pause, & new_keyboard->Pause, GetAsyncKeyState( VK_PAUSE ), is_down );
|
||||
|
||||
input.Controllers[0].Keyboard = new_keyboard;
|
||||
}
|
||||
@ -1015,17 +1106,74 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
}
|
||||
}
|
||||
|
||||
// Pain...
|
||||
// DWORD ds_play_cursor;
|
||||
// DWORD ds_write_cursor;
|
||||
DWORD byte_to_lock = 0;
|
||||
DWORD bytes_to_write = 0;
|
||||
DWORD target_cursor = 0;
|
||||
// if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & ds_play_cursor, & ds_write_cursor ) ))
|
||||
if ( sound_is_valid )
|
||||
// Engine's logical iteration and rendering process
|
||||
engine::update_and_render( & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.Memory), & engine_memory );
|
||||
|
||||
u64 audio_frame_start = timing_get_wall_clock();
|
||||
f32 flip_to_audio_ms = timing_get_ms_elapsed( flip_wall_clock, audio_frame_start );
|
||||
|
||||
DWORD ds_play_cursor;
|
||||
DWORD ds_write_cursor;
|
||||
do {
|
||||
/*
|
||||
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;
|
||||
target_cursor = (last_play_cursor + (ds_sound_buffer.LatencySampleCount * 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)
|
||||
{
|
||||
@ -1038,48 +1186,66 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
// Behind play cursor |--byte_to_write-->--play--|
|
||||
bytes_to_write = target_cursor - byte_to_lock;
|
||||
}
|
||||
// sound_is_valid = true;
|
||||
}
|
||||
|
||||
// Engine Sound
|
||||
// s16 samples[ 48000 * 2 ];
|
||||
engine::SoundBuffer sound_buffer {};
|
||||
engine::AudioBuffer sound_buffer {};
|
||||
sound_buffer.NumSamples = bytes_to_write / ds_sound_buffer.BytesPerSample;
|
||||
sound_buffer.RunningSampleIndex = ds_sound_buffer.RunningSampleIndex;
|
||||
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
|
||||
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;
|
||||
if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetStatus( & ds_status ) ) )
|
||||
{
|
||||
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 )
|
||||
break;
|
||||
|
||||
ds_sound_buffer.SecondaryBuffer->Play( 0, 0, DSBPLAY_LOOPING );
|
||||
} while(0);
|
||||
|
||||
// Timing Update
|
||||
{
|
||||
u64 work_frame_end_cycle = __rdtsc();
|
||||
u64 work_frame_end_clock = timing_get_wall_clock();
|
||||
|
||||
@ -1099,10 +1265,9 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock );
|
||||
if ( frame_elapsed_ms < Engine_Frame_Target_MS )
|
||||
{
|
||||
// TODO(Ed) : Log ms discrepancy here.
|
||||
// TODO(Ed) : Log missed sleep here.
|
||||
}
|
||||
|
||||
assert( frame_elapsed_ms < Engine_Frame_Target_MS );
|
||||
while ( frame_elapsed_ms < Engine_Frame_Target_MS )
|
||||
{
|
||||
frame_clock = timing_get_wall_clock();
|
||||
@ -1116,14 +1281,19 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
|
||||
last_frame_clock = timing_get_wall_clock(); // LastCouner
|
||||
last_frame_cycle = __rdtsc();
|
||||
}
|
||||
|
||||
// Update surface back buffer
|
||||
if ( ! Pause_Rendering )
|
||||
{
|
||||
WinDimensions dimensions = get_window_dimensions( window_handle );
|
||||
HDC device_context = GetDC( window_handle );
|
||||
|
||||
#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
|
||||
|
||||
display_buffer_in_window( device_context, dimensions.Width, dimensions.Height, &Surface_Back_Buffer
|
||||
@ -1131,34 +1301,35 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho
|
||||
, dimensions.Width, dimensions.Height );
|
||||
}
|
||||
|
||||
flip_wall_clock = timing_get_wall_clock();
|
||||
#if Build_Development
|
||||
{
|
||||
// Audio Debug
|
||||
DWORD play_cursor = 0;
|
||||
DWORD write_cursor = 0;
|
||||
|
||||
if ( SUCCEEDED( ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor ) ) )
|
||||
{
|
||||
last_play_cursor = play_cursor;
|
||||
if ( ! sound_is_valid )
|
||||
{
|
||||
ds_sound_buffer.RunningSampleIndex = write_cursor / ds_sound_buffer.BytesPerSample;
|
||||
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
|
||||
{
|
||||
sound_is_valid = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if Build_Development
|
||||
assert( debug_marker_index < debug_marker_history_size )
|
||||
debug_markers[debug_marker_index] = { play_cursor, write_cursor };
|
||||
debug_marker_index++;
|
||||
|
||||
if ( debug_marker_index >= debug_marker_history_size )
|
||||
debug_marker_index = 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
engine::shutdown();
|
||||
|
Loading…
Reference in New Issue
Block a user