2023-09-15 18:35:27 -07:00
/*
TODO : This is not a final platform layer
- Saved game locations
- Getting a handle to our own executable file
- Asset loading path
- Threading ( launch a thread )
- Raw Input ( support for multiple keyboards )
- Sleep / timeBeginPeriod
- ClipCursor ( ) ( for multimonitor support )
- Fullscreen support
- WM_SETCURSOR ( control cursor visibility )
- QueryCancelAutoplay
- WM_ACTIVATEAPP ( for when not active )
- Blit speed improvemnts ( BitBlt )
- Hardware acceleration ( OpenGL or Direct3D or both )
- GetKeyboardLayout ( for French keyboards , international WASD support )
*/
// Platform Layer headers
2023-09-23 18:52:38 -07:00
# include "platform.hpp"
# include "jsl.hpp" // Using this to get dualsense controllers
# include "win32.hpp"
2023-09-09 00:03:03 -07:00
2023-09-15 18:35:27 -07:00
// Engine layer headers
2023-09-23 18:52:38 -07:00
# include "engine.hpp"
# include "platform_engine_api.hpp"
2023-09-09 14:06:47 -07:00
2023-09-24 19:37:05 -07:00
// Standard-Library stand-ins
// #include <malloc.h>
// TODO(Ed) : Remove these ^^
2023-09-09 17:14:40 -07:00
2023-09-10 07:40:22 -07:00
2023-09-23 18:03:33 -07:00
# if 1
2023-09-24 19:37:05 -07:00
// TODO(Ed): Redo these macros properly later.
2023-09-09 20:58:28 -07:00
# define congrats( message ) do { \
JslSetLightColour ( 0 , ( 255 < < 16 ) | ( 215 < < 8 ) ) ; \
MessageBoxA ( 0 , message , " Congratulations! " , MB_OK | MB_ICONEXCLAMATION ) ; \
JslSetLightColour ( 0 , ( 255 < < 8 ) ) ; \
} while ( 0 )
2023-09-10 07:40:22 -07:00
# define ensure( condition, message ) ensure_impl( condition, message )
inline bool
ensure_impl ( bool condition , char const * message ) {
if ( ! condition ) {
JslSetLightColour ( 0 , ( 255 < < 16 ) ) ;
MessageBoxA ( 0 , message , " Ensure Failure " , MB_OK | MB_ICONASTERISK ) ;
JslSetLightColour ( 0 , ( 255 < < 8 ) ) ;
}
return condition ;
}
2023-09-09 20:58:28 -07:00
# define fatal(message) do { \
JslSetLightColour ( 0 , ( 255 < < 16 ) ) ; \
MessageBoxA ( 0 , message , " Fatal Error " , MB_OK | MB_ICONERROR ) ; \
JslSetLightColour ( 0 , ( 255 < < 8 ) ) ; \
} while ( 0 )
2023-09-23 18:03:33 -07:00
# endif
2023-09-09 20:58:28 -07:00
2023-09-20 11:43:55 -07:00
NS_PLATFORM_BEGIN
using namespace win32 ;
2023-09-08 18:08:57 -07:00
2023-09-24 19:37:05 -07:00
// This is the "backbuffer" data related to the windowing surface provided by the operating system.
2023-09-09 09:47:06 -07:00
struct OffscreenBuffer
{
BITMAPINFO Info ;
2023-09-20 21:26:23 -07:00
char _PAD_ [ 4 ] ;
2023-09-09 09:47:06 -07:00
void * Memory ; // Lets use directly mess with the "pixel's memory buffer"
2023-09-20 21:26:23 -07:00
s32 Width ;
s32 Height ;
s32 Pitch ;
s32 BytesPerPixel ;
2023-09-09 09:47:06 -07:00
} ;
struct WinDimensions
{
u32 Width ;
u32 Height ;
} ;
2023-09-17 18:20:11 -07:00
// TODO : This will def need to be looked over.
2023-09-23 18:03:33 -07:00
struct DirectSoundBuffer
2023-09-17 18:20:11 -07:00
{
2023-09-23 18:03:33 -07:00
LPDIRECTSOUNDBUFFER SecondaryBuffer ;
s16 * Samples ;
u32 SecondaryBufferSize ;
u32 SamplesPerSecond ;
u32 BytesPerSample ;
2023-09-24 19:37:05 -07:00
// TODO(Ed) : Makes math easier...
u32 BytesPerSecond ;
u32 GuardSampleBytes ;
2023-09-23 18:03:33 -07:00
DWORD IsPlaying ;
u32 RunningSampleIndex ;
2023-09-24 19:37:05 -07:00
// TODO(Ed) : Should this be in bytes?
2023-09-23 18:03:33 -07:00
u32 LatencySampleCount ;
2023-09-17 18:20:11 -07:00
} ;
2023-09-10 07:40:22 -07:00
2023-09-23 18:03:33 -07:00
# pragma region Static Data
// TODO(Ed) : This is a global for now.
2023-09-24 19:37:05 -07:00
global b32 Running = false ;
global b32 Pause_Rendering = false ;
2023-09-10 16:56:09 -07:00
2023-09-23 18:03:33 -07:00
// Max controllers for the platform layer and thus for all other layers is 4. (Sanity and xinput limit)
constexpr u32 Max_Controllers = 4 ;
2023-09-10 07:40:22 -07:00
2023-09-23 18:03:33 -07:00
global WinDimensions Window_Dimensions ;
global OffscreenBuffer Surface_Back_Buffer ;
using DirectSoundCreateFn = HRESULT WINAPI ( LPGUID lpGuid , LPDIRECTSOUND * ppDS , LPUNKNOWN pUnkOuter ) ;
global DirectSoundCreateFn * direct_sound_create ;
2023-09-16 15:41:07 -07:00
2023-09-22 12:13:18 -07:00
constexpr u64 Tick_To_Millisecond = 1000 ;
constexpr u64 Tick_To_Microsecond = 1000 * 1000 ;
2023-09-20 11:43:55 -07:00
2023-09-22 12:13:18 -07:00
global u64 Performance_Counter_Frequency ;
2023-09-23 18:03:33 -07:00
// As of 2023 the highest refreshrate on the market is 500 hz. I'll just make this higher if something comes out beyond that...
constexpr u32 Monitor_Refresh_Max_Supported = 500 ;
// Anything at or below the high performance frame-time is too low latency to sleep against the window's scheduler.
constexpr f32 High_Perf_Frametime_MS = 1000.f / 240.f ;
global u32 Monitor_Refresh_Hz = 60 ;
global u32 Engine_Refresh_Hz = Monitor_Refresh_Hz / 2 ;
global f32 Engine_Frame_Target_MS = 1000.f / scast ( f32 , Engine_Refresh_Hz ) ;
# pragma endregion Static Data
2023-09-20 11:43:55 -07:00
# if Build_Debug
2023-09-23 18:03:33 -07:00
struct DebugTimeMarker
{
2023-09-24 19:37:05 -07:00
DWORD OutputPlayCusror ;
DWORD OutputWriteCursor ;
DWORD OutputLocation ;
DWORD OutputByteCount ;
DWORD FlipPlayCursor ;
DWORD FlipWriteCursor ;
DWORD ExpectedFlipCursor ;
2023-09-23 18:03:33 -07:00
} ;
2023-09-20 11:43:55 -07:00
void debug_file_free_content ( Debug_FileContent * content )
{
if ( content - > Data )
{
VirtualFree ( content - > Data , 0 , MEM_Release ) ;
* content = { } ;
}
}
2023-09-23 18:03:33 -07:00
Debug_FileContent debug_file_read_content ( char const * file_path )
2023-09-20 11:43:55 -07:00
{
Debug_FileContent result { } ;
HANDLE file_handle = CreateFileA ( file_path
, GENERIC_READ , FILE_SHARE_READ , 0
, OPEN_EXISTING , 0 , 0
) ;
if ( file_handle = = INVALID_HANDLE_VALUE )
{
// TODO(Ed) : Logging
return result ;
}
GetFileSizeEx ( file_handle , rcast ( LARGE_INTEGER * , & result . Size ) ) ;
if ( result . Size = = 0 )
{
// TODO(Ed) : Logging
return result ;
}
result . Data = VirtualAlloc ( 0 , result . Size , MEM_Commit_Zeroed | MEM_Reserve , Page_Read_Write ) ;
u32 bytes_read ;
if ( ReadFile ( file_handle , result . Data , result . Size , rcast ( LPDWORD , & bytes_read ) , 0 ) = = false )
{
// TODO(Ed) : Logging
return { } ;
}
if ( bytes_read ! = result . Size )
{
// TODO : Logging
return { } ;
}
CloseHandle ( file_handle ) ;
return result ;
}
2023-09-23 18:03:33 -07:00
b32 debug_file_write_content ( char const * file_path , u32 content_size , void * content_memory )
2023-09-20 11:43:55 -07:00
{
HANDLE file_handle = CreateFileA ( file_path
, GENERIC_WRITE , 0 , 0
, CREATE_ALWAYS , 0 , 0
) ;
if ( file_handle = = INVALID_HANDLE_VALUE )
{
// TODO : Logging
return false ;
}
DWORD bytes_written ;
if ( WriteFile ( file_handle , content_memory , content_size , & bytes_written , 0 ) = = false )
{
// TODO : Logging
return false ;
}
CloseHandle ( file_handle ) ;
return true ;
}
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
void set_pause_rendering ( b32 value )
{
Pause_Rendering = value ;
}
2023-09-23 18:03:33 -07:00
internal void
2023-09-24 19:37:05 -07:00
debug_draw_vertical ( s32 x_pos , s32 top , s32 bottom , s32 color )
2023-09-23 18:03:33 -07:00
{
2023-09-24 19:37:05 -07:00
if ( top < = 0 )
{
top = 0 ;
}
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
if ( bottom > Surface_Back_Buffer . Height )
2023-09-23 18:03:33 -07:00
{
2023-09-24 19:37:05 -07:00
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 ;
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
pixel_byte + = Surface_Back_Buffer . Pitch ;
}
2023-09-23 18:03:33 -07:00
}
}
inline void
2023-09-24 19:37:05 -07:00
debug_draw_sound_buffer_marker ( DirectSoundBuffer * sound_buffer , f32 ratio
2023-09-23 18:03:33 -07:00
, u32 pad_x , u32 pad_y
, u32 top , u32 bottom
, DWORD value , u32 color )
{
2023-09-24 19:37:05 -07:00
// assert( value < sound_buffer->SecondaryBufferSize );
u32 x = pad_x + scast ( u32 , ratio * scast ( f32 , value ) ) ;
debug_draw_vertical ( x , top , bottom , color ) ;
2023-09-23 18:03:33 -07:00
}
internal void
debug_sync_display ( DirectSoundBuffer * sound_buffer
, u32 num_markers , DebugTimeMarker * markers
2023-09-24 19:37:05 -07:00
, u32 current_marker
2023-09-23 18:03:33 -07:00
, f32 ms_per_frame )
{
2023-09-24 19:37:05 -07:00
u32 pad_x = 32 ;
u32 pad_y = 16 ;
f32 buffers_ratio = scast ( f32 , Surface_Back_Buffer . Width ) / ( scast ( f32 , sound_buffer - > SecondaryBufferSize ) * 1 ) ;
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
u32 line_height = 64 ;
2023-09-23 18:03:33 -07:00
for ( u32 marker_index = 0 ; marker_index < num_markers ; + + marker_index )
{
DebugTimeMarker * marker = & markers [ marker_index ] ;
2023-09-24 19:37:05 -07:00
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 ) ;
2023-09-23 18:03:33 -07:00
}
}
2023-09-20 11:43:55 -07:00
# endif
2023-09-22 12:13:18 -07:00
inline u64
timing_get_wall_clock ( )
{
u64 clock ;
QueryPerformanceCounter ( rcast ( LARGE_INTEGER * , & clock ) ) ;
return clock ;
}
inline f32
timing_get_seconds_elapsed ( u64 start , u64 end )
{
u64 delta = end - start ;
f32 result = scast ( f32 , delta ) / scast ( f32 , Performance_Counter_Frequency ) ;
2023-09-23 18:03:33 -07:00
return result ;
2023-09-22 12:13:18 -07:00
}
inline f32
timing_get_ms_elapsed ( u64 start , u64 end )
{
u64 delta = ( end - start ) * Tick_To_Millisecond ;
f32 result = scast ( f32 , delta ) / scast ( f32 , Performance_Counter_Frequency ) ;
return result ;
}
inline f32
timing_get_us_elapsed ( u64 start , u64 end )
{
u64 delta = ( end - start ) * Tick_To_Microsecond ;
f32 result = scast ( f32 , delta ) / scast ( f32 , Performance_Counter_Frequency ) ;
return result ;
}
2023-09-21 23:16:40 -07:00
internal void
input_process_digital_btn ( engine : : DigitalBtn * old_state , engine : : DigitalBtn * new_state , u32 raw_btns , u32 btn_flag )
{
# define had_transition() ( old_state->EndedDown == new_state->EndedDown )
new_state - > EndedDown = ( raw_btns & btn_flag ) > 0 ;
new_state - > HalfTransitions = had_transition ( ) ? 1 : 0 ;
# undef had_transition
}
internal f32
xinput_process_axis_value ( s16 value , s16 deadzone_threshold )
{
f32 result = 0 ;
if ( value < - deadzone_threshold )
{
result = scast ( f32 , value + deadzone_threshold ) / ( 32768.0f - scast ( f32 , deadzone_threshold ) ) ;
}
else if ( value > deadzone_threshold )
{
result = scast ( f32 , value + deadzone_threshold ) / ( 32767.0f - scast ( f32 , deadzone_threshold ) ) ;
}
return result ;
}
internal f32
input_process_axis_value ( f32 value , f32 deadzone_threshold )
{
f32 result = 0 ;
if ( value < - deadzone_threshold )
{
result = ( value + deadzone_threshold ) / ( 1.0f - deadzone_threshold ) ;
2023-09-22 12:13:18 -07:00
if ( result < - 1.0f )
2023-09-21 23:16:40 -07:00
result = - 1.0f ; // Clamp to ensure it doesn't go below -1
}
else if ( value > deadzone_threshold )
{
result = ( value - deadzone_threshold ) / ( 1.0f - deadzone_threshold ) ;
2023-09-22 12:13:18 -07:00
if ( result > 1.0f )
2023-09-21 23:16:40 -07:00
result = 1.0f ; // Clamp to ensure it doesn't exceed 1
}
return result ;
}
internal void
2023-09-23 18:03:33 -07:00
poll_input ( engine : : InputState * input )
2023-09-21 23:16:40 -07:00
{
2023-09-23 18:03:33 -07:00
2023-09-21 23:16:40 -07:00
}
2023-09-10 07:40:22 -07:00
internal void
2023-09-23 18:03:33 -07:00
init_sound ( HWND window_handle , DirectSoundBuffer * sound_buffer )
2023-09-10 07:40:22 -07:00
{
// Load library
HMODULE sound_library = LoadLibraryA ( " dsound.dll " ) ;
if ( ! ensure ( sound_library , " Failed to load direct sound library " ) )
{
// TOOD : Diagnostic
return ;
}
// Get direct sound object
2023-09-20 21:26:23 -07:00
# pragma warning( push )
# pragma warning( disable: 4191 )
2023-09-10 07:40:22 -07:00
direct_sound_create = rcast ( DirectSoundCreateFn * , GetProcAddress ( sound_library , " DirectSoundCreate " ) ) ;
2023-09-10 11:14:47 -07:00
if ( ! ensure ( direct_sound_create , " Failed to get direct_sound_create_procedure " ) )
2023-09-10 07:40:22 -07:00
{
// TOOD : Diagnostic
return ;
}
2023-09-20 21:26:23 -07:00
# pragma warning( pop )
2023-09-10 07:40:22 -07:00
LPDIRECTSOUND direct_sound ;
if ( ! SUCCEEDED ( direct_sound_create ( 0 , & direct_sound , 0 ) ) )
{
// TODO : Diagnostic
}
if ( ! SUCCEEDED ( direct_sound - > SetCooperativeLevel ( window_handle , DSSCL_PRIORITY ) ) )
{
// TODO : Diagnostic
}
WAVEFORMATEX
wave_format { } ;
wave_format . wFormatTag = WAVE_FORMAT_PCM ; /* format type */
wave_format . nChannels = 2 ; /* number of channels (i.e. mono, stereo...) */
2023-09-23 18:03:33 -07:00
wave_format . nSamplesPerSec = scast ( u32 , sound_buffer - > SamplesPerSecond ) ; /* sample rate */
2023-09-10 07:40:22 -07:00
wave_format . wBitsPerSample = 16 ; /* number of bits per sample of mono data */
wave_format . nBlockAlign = wave_format . nChannels * wave_format . wBitsPerSample / 8 ; /* block size of data */
wave_format . nAvgBytesPerSec = wave_format . nSamplesPerSec * wave_format . nBlockAlign ; /* for buffer estimation */
wave_format . cbSize = 0 ; /* the count in bytes of the size of */
LPDIRECTSOUNDBUFFER primary_buffer ;
{
DSBUFFERDESC
buffer_description { sizeof ( buffer_description ) } ;
buffer_description . dwFlags = DSBCAPS_PRIMARYBUFFER ;
buffer_description . dwBufferBytes = 0 ;
if ( ! SUCCEEDED ( direct_sound - > CreateSoundBuffer ( & buffer_description , & primary_buffer , 0 ) ) )
{
// TODO : Diagnostic
}
if ( ! SUCCEEDED ( primary_buffer - > SetFormat ( & wave_format ) ) )
{
// TODO : Diagnostic
}
}
2023-09-10 11:14:47 -07:00
DSBUFFERDESC
buffer_description { sizeof ( buffer_description ) } ;
2023-09-23 18:03:33 -07:00
buffer_description . dwFlags = DSBCAPS_GETCURRENTPOSITION2 ;
buffer_description . dwBufferBytes = sound_buffer - > SecondaryBufferSize ;
2023-09-10 11:14:47 -07:00
buffer_description . lpwfxFormat = & wave_format ;
2023-09-10 07:40:22 -07:00
2023-09-23 18:03:33 -07:00
if ( ! SUCCEEDED ( direct_sound - > CreateSoundBuffer ( & buffer_description , & sound_buffer - > SecondaryBuffer , 0 ) ) )
2023-09-10 11:14:47 -07:00
{
// TODO : Diagnostic
}
2023-09-23 18:03:33 -07:00
if ( ! SUCCEEDED ( sound_buffer - > SecondaryBuffer - > SetFormat ( & wave_format ) ) )
2023-09-10 11:14:47 -07:00
{
// TODO : Diagnostic
2023-09-10 07:40:22 -07:00
}
}
2023-09-10 16:56:09 -07:00
internal void
2023-09-23 18:03:33 -07:00
ds_clear_sound_buffer ( DirectSoundBuffer * sound_buffer )
2023-09-10 16:56:09 -07:00
{
2023-09-16 15:41:07 -07:00
LPVOID region_1 ;
DWORD region_1_size ;
LPVOID region_2 ;
DWORD region_2_size ;
2023-09-23 18:03:33 -07:00
HRESULT ds_lock_result = sound_buffer - > SecondaryBuffer - > Lock ( 0 , sound_buffer - > SecondaryBufferSize
2023-09-16 15:41:07 -07:00
, & region_1 , & region_1_size
, & region_2 , & region_2_size
, 0 ) ;
if ( ! SUCCEEDED ( ds_lock_result ) )
2023-09-10 16:56:09 -07:00
{
2023-09-16 15:41:07 -07:00
return ;
}
2023-09-10 16:56:09 -07:00
2023-09-16 15:41:07 -07:00
u8 * sample_out = rcast ( u8 * , region_1 ) ;
for ( DWORD byte_index = 0 ; byte_index < region_1_size ; + + byte_index )
{
* sample_out = 0 ;
2023-09-10 16:56:09 -07:00
+ + sample_out ;
2023-09-16 15:41:07 -07:00
}
2023-09-10 16:56:09 -07:00
2023-09-16 15:41:07 -07:00
sample_out = rcast ( u8 * , region_2 ) ;
for ( DWORD byte_index = 0 ; byte_index < region_2_size ; + + byte_index )
{
* sample_out = 0 ;
2023-09-10 16:56:09 -07:00
+ + sample_out ;
}
2023-09-16 15:41:07 -07:00
2023-09-23 18:03:33 -07:00
if ( ! SUCCEEDED ( sound_buffer - > SecondaryBuffer - > Unlock ( region_1 , region_1_size , region_2 , region_2_size ) ) )
2023-09-16 15:41:07 -07:00
{
return ;
}
2023-09-10 16:56:09 -07:00
}
internal void
2023-09-23 18:03:33 -07:00
ds_fill_sound_buffer ( DirectSoundBuffer * sound_buffer , DWORD byte_to_lock , DWORD bytes_to_write )
2023-09-10 16:56:09 -07:00
{
LPVOID region_1 ;
DWORD region_1_size ;
LPVOID region_2 ;
DWORD region_2_size ;
2023-09-23 18:03:33 -07:00
HRESULT ds_lock_result = sound_buffer - > SecondaryBuffer - > Lock ( byte_to_lock , bytes_to_write
2023-09-10 16:56:09 -07:00
, & region_1 , & region_1_size
, & region_2 , & region_2_size
, 0 ) ;
if ( ! SUCCEEDED ( ds_lock_result ) )
{
return ;
}
// TODO : Assert that region sizes are valid
2023-09-23 18:03:33 -07:00
DWORD region_1_sample_count = region_1_size / sound_buffer - > BytesPerSample ;
2023-09-16 15:41:07 -07:00
s16 * sample_out = rcast ( s16 * , region_1 ) ;
s16 * sample_in = sound_buffer - > Samples ;
for ( DWORD sample_index = 0 ; sample_index < region_1_sample_count ; + + sample_index )
{
* sample_out = * sample_in ;
+ + sample_out ;
+ + sample_in ;
* sample_out = * sample_in ;
+ + sample_out ;
+ + sample_in ;
2023-09-23 18:03:33 -07:00
+ + sound_buffer - > RunningSampleIndex ;
2023-09-16 15:41:07 -07:00
}
2023-09-23 18:03:33 -07:00
DWORD region_2_sample_count = region_2_size / sound_buffer - > BytesPerSample ;
2023-09-16 15:41:07 -07:00
sample_out = rcast ( s16 * , region_2 ) ;
for ( DWORD sample_index = 0 ; sample_index < region_2_sample_count ; + + sample_index )
{
* sample_out = * sample_in ;
+ + sample_out ;
+ + sample_in ;
* sample_out = * sample_in ;
+ + sample_out ;
+ + sample_in ;
2023-09-23 18:03:33 -07:00
+ + sound_buffer - > RunningSampleIndex ;
2023-09-16 15:41:07 -07:00
}
2023-09-10 16:56:09 -07:00
2023-09-23 18:03:33 -07:00
if ( ! SUCCEEDED ( sound_buffer - > SecondaryBuffer - > Unlock ( region_1 , region_1_size , region_2 , region_2_size ) ) )
2023-09-10 16:56:09 -07:00
{
return ;
}
}
2023-09-10 07:40:22 -07:00
internal WinDimensions
get_window_dimensions ( HWND window_handle )
2023-09-09 09:47:06 -07:00
{
RECT client_rect ;
GetClientRect ( window_handle , & client_rect ) ;
WinDimensions result ;
result . Width = client_rect . right - client_rect . left ;
result . Height = client_rect . bottom - client_rect . top ;
return result ;
}
2023-09-09 00:03:03 -07:00
2023-09-08 21:01:53 -07:00
internal void
2023-09-09 09:47:06 -07:00
resize_dib_section ( OffscreenBuffer * buffer , u32 width , u32 height )
2023-09-08 21:01:53 -07:00
{
// TODO(Ed) : Bulletproof memory handling here for the bitmap memory
2023-09-09 09:47:06 -07:00
if ( buffer - > Memory )
2023-09-08 21:01:53 -07:00
{
2023-09-09 09:47:06 -07:00
VirtualFree ( buffer - > Memory , 0 , MEM_RELEASE ) ;
2023-09-08 21:01:53 -07:00
}
2023-09-09 09:47:06 -07:00
buffer - > Width = width ;
buffer - > Height = height ;
buffer - > BytesPerPixel = 4 ;
buffer - > Pitch = buffer - > Width * buffer - > BytesPerPixel ;
2023-09-09 00:03:03 -07:00
// Negative means top-down in the context of the biHeight
# define Top_Down -
2023-09-09 09:47:06 -07:00
BITMAPINFOHEADER &
header = buffer - > Info . bmiHeader ;
header . biSize = sizeof ( buffer - > Info . bmiHeader ) ;
header . biWidth = buffer - > Width ;
header . biHeight = Top_Down buffer - > Height ;
header . biPlanes = 1 ;
header . biBitCount = 32 ; // Need 24, but want 32 ( alignment )
header . biCompression = BI_RGB_Uncompressed ;
// header.biSizeImage = 0;
// header.biXPelsPerMeter = 0;
// header.biYPelsPerMeter = 0;
// header.biClrUsed = 0;
// header.biClrImportant = 0;
2023-09-09 00:03:03 -07:00
# undef Top_Down
// We want to "touch" a pixel on every 4-byte boundary
2023-09-09 09:47:06 -07:00
u32 BitmapMemorySize = ( buffer - > Width * buffer - > Height ) * buffer - > BytesPerPixel ;
2023-09-10 07:40:22 -07:00
buffer - > Memory = VirtualAlloc ( NULL , BitmapMemorySize , MEM_Commit_Zeroed | MEM_Reserve , Page_Read_Write ) ;
2023-09-09 00:03:03 -07:00
// TODO(Ed) : Clear to black
2023-09-08 21:01:53 -07:00
}
internal void
2023-09-09 14:06:47 -07:00
display_buffer_in_window ( HDC device_context , u32 window_width , u32 window_height , OffscreenBuffer * buffer
2023-09-08 21:01:53 -07:00
, u32 x , u32 y
, u32 width , u32 height )
{
2023-09-09 09:47:06 -07:00
// TODO(Ed) : Aspect ratio correction
2023-09-08 21:01:53 -07:00
StretchDIBits ( device_context
2023-09-09 00:03:03 -07:00
#if 0
2023-09-08 21:01:53 -07:00
, x , y , width , height
, x , y , width , height
2023-09-09 00:03:03 -07:00
# endif
, 0 , 0 , window_width , window_height
2023-09-09 14:06:47 -07:00
, 0 , 0 , buffer - > Width , buffer - > Height
, buffer - > Memory , & buffer - > Info
2023-09-08 21:01:53 -07:00
, DIB_ColorTable_RGB , RO_Source_To_Dest ) ;
}
2023-09-09 14:06:47 -07:00
internal LRESULT CALLBACK
2023-09-23 18:03:33 -07:00
main_window_callback ( HWND handle
, UINT system_messages
, WPARAM w_param
, LPARAM l_param )
2023-09-08 18:08:57 -07:00
{
2023-09-15 18:35:27 -07:00
LRESULT result = 0 ;
2023-09-08 18:08:57 -07:00
switch ( system_messages )
{
case WM_ACTIVATEAPP :
{
OutputDebugStringA ( " WM_ACTIVATEAPP \n " ) ;
}
break ;
case WM_CLOSE :
{
2023-09-08 21:01:53 -07:00
// TODO(Ed) : Handle with a message to the user
Running = false ;
2023-09-08 18:08:57 -07:00
}
break ;
case WM_DESTROY :
{
2023-09-08 21:01:53 -07:00
// TODO(Ed) : Handle with as an error and recreate the window
Running = false ;
2023-09-08 18:08:57 -07:00
}
break ;
case WM_PAINT :
{
PAINTSTRUCT info ;
HDC device_context = BeginPaint ( handle , & info ) ;
u32 x = info . rcPaint . left ;
u32 y = info . rcPaint . top ;
u32 width = info . rcPaint . right - info . rcPaint . left ;
2023-09-09 00:03:03 -07:00
u32 height = info . rcPaint . bottom - info . rcPaint . top ;
2023-09-09 09:47:06 -07:00
WinDimensions dimensions = get_window_dimensions ( handle ) ;
2023-09-08 18:08:57 -07:00
2023-09-23 18:03:33 -07:00
display_buffer_in_window ( device_context , dimensions . Width , dimensions . Height , & Surface_Back_Buffer
2023-09-08 18:08:57 -07:00
, x , y
2023-09-08 21:01:53 -07:00
, width , height ) ;
2023-09-08 18:08:57 -07:00
EndPaint ( handle , & info ) ;
}
break ;
case WM_SIZE :
{
}
break ;
default :
{
result = DefWindowProc ( handle , system_messages , w_param , l_param ) ;
}
}
return result ;
}
2023-09-16 15:41:07 -07:00
2023-09-17 18:20:11 -07:00
internal void
2023-09-21 23:16:40 -07:00
process_pending_window_messages ( engine : : KeyboardState * keyboard )
2023-09-17 18:20:11 -07:00
{
2023-09-21 23:16:40 -07:00
MSG window_msg_info ;
while ( PeekMessageA ( & window_msg_info , 0 , 0 , 0 , PM_Remove_Messages_From_Queue ) )
{
if ( window_msg_info . message = = WM_QUIT )
{
OutputDebugStringA ( " WM_QUIT \n " ) ;
Running = false ;
}
2023-09-17 18:20:11 -07:00
2023-09-21 23:16:40 -07:00
// Keyboard input handling
switch ( window_msg_info . message )
{
// I rather do this with GetAsyncKeyState...
case WM_SYSKEYDOWN :
case WM_SYSKEYUP :
{
WPARAM vk_code = window_msg_info . wParam ;
b32 is_down = scast ( b32 , ( window_msg_info . lParam > > 31 ) = = 0 ) ;
b32 was_down = scast ( b32 , ( window_msg_info . lParam > > 30 ) ) ;
b32 alt_down = scast ( b32 , ( window_msg_info . lParam & ( 1 < < 29 ) ) ) ;
switch ( vk_code )
{
case VK_F4 :
{
if ( alt_down )
Running = false ;
}
break ;
}
}
break ;
default :
TranslateMessage ( & window_msg_info ) ;
DispatchMessageW ( & window_msg_info ) ;
}
}
2023-09-20 21:26:23 -07:00
}
2023-09-20 11:43:55 -07:00
NS_PLATFORM_END
2023-09-08 21:01:53 -07:00
int CALLBACK
2023-09-23 18:03:33 -07:00
WinMain ( HINSTANCE instance , HINSTANCE prev_instance , LPSTR commandline , int show_command )
2023-09-08 12:14:09 -07:00
{
2023-09-08 21:01:53 -07:00
using namespace win32 ;
2023-09-20 11:43:55 -07:00
using namespace platform ;
2023-09-09 17:14:40 -07:00
2023-09-22 12:13:18 -07:00
// Timing
# if Build_Development
u64 launch_clock = timing_get_wall_clock ( ) ;
u64 launch_cycle = __rdtsc ( ) ;
# endif
// Sets the windows scheduler granulaity for this process to 1 ms
2023-09-23 18:03:33 -07:00
constexpr u32 desired_scheduler_ms = 1 ;
2023-09-22 12:13:18 -07:00
b32 sleep_is_granular = ( timeBeginPeriod ( desired_scheduler_ms ) = = TIMERR_NOERROR ) ;
2023-09-23 18:03:33 -07:00
// If its a high-perofmrance frame-time (a refresh rate that produces a target frametime at or below 4.16~ ms, we cannot allow the scheduler to mess things up)
b32 sub_ms_granularity_required = scast ( f32 , Engine_Refresh_Hz ) < = High_Perf_Frametime_MS ;
2023-09-22 12:13:18 -07:00
QueryPerformanceFrequency ( rcast ( LARGE_INTEGER * , & Performance_Counter_Frequency ) ) ;
2023-09-18 17:16:40 -07:00
// Memory
engine : : Memory engine_memory { } ;
{
engine_memory . PersistentSize = megabytes ( 64 ) ;
// engine_memory.FrameSize = megabytes( 64 );
engine_memory . TransientSize = gigabytes ( 2 ) ;
u64 total_size = engine_memory . PersistentSize
// + engine_memory.FrameSize
+ engine_memory . TransientSize ;
# if Build_Debug
2023-09-23 18:03:33 -07:00
void * base_address = rcast ( void * , terabytes ( 1 ) ) ;
2023-09-18 17:16:40 -07:00
# else
2023-09-23 18:03:33 -07:00
void * base_address = 0 ;
2023-09-18 17:16:40 -07:00
# endif
2023-09-23 18:03:33 -07:00
engine_memory . Persistent = VirtualAlloc ( base_address , total_size , MEM_Commit_Zeroed | MEM_Reserve , Page_Read_Write ) ;
2023-09-18 17:16:40 -07:00
engine_memory . Transient = rcast ( u8 * , engine_memory . Persistent ) + engine_memory . PersistentSize ;
2023-09-22 12:13:18 -07:00
if ( engine_memory . Persistent = = nullptr
| | engine_memory . Transient = = nullptr )
2023-09-18 17:16:40 -07:00
{
// TODO : Diagnostic Logging
return - 1 ;
}
}
2023-09-17 18:20:11 -07:00
WNDCLASSW window_class { } ;
HWND window_handle = nullptr ;
2023-09-09 17:14:40 -07:00
{
2023-09-17 18:20:11 -07:00
window_class . style = CS_Horizontal_Redraw | CS_Vertical_Redraw ;
window_class . lpfnWndProc = main_window_callback ;
// window_class.cbClsExtra = ;
// window_class.cbWndExtra = ;
window_class . hInstance = instance ;
// window_class.hIcon = ;
// window_class.hCursor = ;
// window_class.hbrBackground = ;
window_class . lpszMenuName = L " Handmade Hero! " ;
window_class . lpszClassName = L " HandmadeHeroWindowClass " ;
if ( ! RegisterClassW ( & window_class ) )
2023-09-13 21:43:35 -07:00
{
2023-09-17 18:20:11 -07:00
// TODO : Diagnostic Logging
return 0 ;
2023-09-13 21:43:35 -07:00
}
2023-09-09 14:06:47 -07:00
2023-09-17 18:20:11 -07:00
window_handle = CreateWindowExW (
0 ,
window_class . lpszClassName ,
L " Handmade Hero " ,
WS_Overlapped_Window | WS_Initially_Visible ,
CW_Use_Default , CW_Use_Default , // x, y
CW_Use_Default , CW_Use_Default , // width, height
0 , 0 , // parent, menu
instance , 0 // instance, param
) ;
if ( ! window_handle )
2023-09-09 20:58:28 -07:00
{
2023-09-17 18:20:11 -07:00
// TODO : Diagnostic Logging
return 0 ;
2023-09-09 20:58:28 -07:00
}
}
2023-09-20 21:26:23 -07:00
// WinDimensions dimensions = get_window_dimensions( window_handle );
2023-09-23 18:03:33 -07:00
resize_dib_section ( & Surface_Back_Buffer , 1280 , 720 ) ;
2023-09-08 18:08:57 -07:00
2023-09-24 19:37:05 -07:00
b32 sound_is_valid = false ;
DWORD ds_cursor_byte_delta = 0 ;
f32 ds_latency_ms = 0 ;
2023-09-23 18:03:33 -07:00
DirectSoundBuffer ds_sound_buffer ;
2023-09-10 11:14:47 -07:00
{
2023-09-23 18:03:33 -07:00
ds_sound_buffer . IsPlaying = 0 ;
ds_sound_buffer . SamplesPerSecond = 48000 ;
ds_sound_buffer . BytesPerSample = sizeof ( s16 ) * 2 ;
2023-09-10 11:14:47 -07:00
2023-09-23 18:03:33 -07:00
ds_sound_buffer . SecondaryBufferSize = ds_sound_buffer . SamplesPerSecond * ds_sound_buffer . BytesPerSample ;
init_sound ( window_handle , & ds_sound_buffer ) ;
2023-09-10 11:14:47 -07:00
2023-09-23 18:03:33 -07:00
ds_sound_buffer . Samples = rcast ( s16 * , VirtualAlloc ( 0 , 48000 * 2 * sizeof ( s16 )
2023-09-17 18:20:11 -07:00
, MEM_Commit_Zeroed | MEM_Reserve , Page_Read_Write ) ) ;
2023-09-10 11:14:47 -07:00
2023-09-23 18:03:33 -07:00
assert ( ds_sound_buffer . Samples ) ;
ds_sound_buffer . RunningSampleIndex = 0 ;
2023-09-17 18:20:11 -07:00
// ds_clear_sound_buffer( & sound_output );
2023-09-23 18:03:33 -07:00
ds_sound_buffer . SecondaryBuffer - > Play ( 0 , 0 , DSBPLAY_LOOPING ) ;
2023-09-10 11:14:47 -07:00
2023-09-24 19:37:05 -07:00
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 ;
}
2023-09-23 18:03:33 -07:00
}
# if Build_Development
u32 debug_marker_index = 0 ;
DebugTimeMarker debug_markers [ Monitor_Refresh_Max_Supported ] { } ;
u32 debug_marker_history_size = Engine_Refresh_Hz / 2 ;
assert ( debug_marker_history_size < = Monitor_Refresh_Max_Supported )
# endif
2023-09-17 18:20:11 -07:00
engine : : InputState input { } ;
2023-09-23 18:03:33 -07:00
// There can be 4 of any of each input API type : KB & Mouse, XInput, JSL.
#if 0
using EngineKeyboardStates = engine : : KeyboardState [ Max_Controllers ] ;
EngineKeyboardStates keyboard_states [ 2 ] { } ;
EngineKeyboardStates * old_keyboards = & keyboard_states [ 0 ] ;
EngineKeyboardStates * new_keyboards = & keyboard_states [ 1 ] ;
# endif
2023-09-22 12:13:18 -07:00
engine : : KeyboardState keyboard_states [ 2 ] { } ;
2023-09-21 23:16:40 -07:00
engine : : KeyboardState * old_keyboard = & keyboard_states [ 0 ] ;
engine : : KeyboardState * new_keyboard = & keyboard_states [ 1 ] ;
2023-09-20 21:26:23 -07:00
// Important: Assuming keyboard always connected for now, and assigning to first controller.
2023-09-17 18:20:11 -07:00
using EngineXInputPadStates = engine : : XInputPadState [ Max_Controllers ] ;
2023-09-22 12:13:18 -07:00
EngineXInputPadStates xpad_states [ 2 ] { } ;
2023-09-17 18:20:11 -07:00
EngineXInputPadStates * old_xpads = & xpad_states [ 0 ] ;
EngineXInputPadStates * new_xpads = & xpad_states [ 1 ] ;
using EngineDSPadStates = engine : : DualsensePadState [ Max_Controllers ] ;
2023-09-22 12:13:18 -07:00
EngineDSPadStates ds_pad_states [ 2 ] { } ;
2023-09-17 18:20:11 -07:00
EngineDSPadStates * old_ds_pads = & ds_pad_states [ 0 ] ;
EngineDSPadStates * new_ds_pads = & ds_pad_states [ 1 ] ;
using JSL_DeviceHandle = int ;
2023-09-22 12:13:18 -07:00
u32 jsl_num_devices = JslConnectDevices ( ) ;
2023-09-17 18:20:11 -07:00
JSL_DeviceHandle jsl_device_handles [ 4 ] { } ;
{
xinput_load_library_bindings ( ) ;
u32 jsl_getconnected_found = JslGetConnectedDeviceHandles ( jsl_device_handles , jsl_num_devices ) ;
{
if ( jsl_getconnected_found ! = jsl_num_devices )
{
OutputDebugStringA ( " Error: JSLGetConnectedDeviceHandles didn't find as many as were stated with JslConnectDevices \n " ) ;
}
if ( jsl_num_devices > 0 )
{
OutputDebugStringA ( " JSL Connected Devices: \n " ) ;
for ( u32 jsl_device_index = 0 ; jsl_device_index < jsl_num_devices ; + + jsl_device_index )
{
JslSetLightColour ( jsl_device_handles [ jsl_device_index ] , ( 255 < < 8 ) ) ;
}
}
}
if ( jsl_num_devices > 4 )
{
jsl_num_devices = 4 ;
MessageBoxA ( window_handle , " More than 4 JSL devices found, this engine will only support the first four found. " , " Warning " , MB_ICONEXCLAMATION ) ;
}
}
2023-09-22 12:13:18 -07:00
u64 last_frame_clock = timing_get_wall_clock ( ) ;
u64 last_frame_cycle = __rdtsc ( ) ;
2023-09-24 19:37:05 -07:00
u64 flip_wall_clock = last_frame_clock ;
2023-09-22 12:13:18 -07:00
# if Build_Development
u64 startup_cycles = last_frame_cycle - launch_cycle ;
f32 startup_ms = timing_get_ms_elapsed ( launch_clock , last_frame_clock ) ;
# endif
2023-09-17 18:20:11 -07:00
Running = true ;
2023-09-23 18:03:33 -07:00
#if 0
// This tests the play & write cursor update frequency.
while ( Running )
{
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 ) , " PC:%u WC:%u \n " , ( u32 ) play_cursor , ( u32 ) write_cursor ) ;
OutputDebugStringA ( text_buffer ) ;
}
# endif
2023-09-10 11:14:47 -07:00
while ( Running )
2023-09-08 18:08:57 -07:00
{
2023-09-21 23:16:40 -07:00
process_pending_window_messages ( new_keyboard ) ;
2023-09-10 11:14:47 -07:00
2023-09-23 18:03:33 -07:00
// TODO(Ed): Offload polling to these functions later.
// poll_xinput( & input, old_xpads, new_xpads );
// poll_jsl( & input, old_jsl_pads, new_jsl_pads );
// or
// poll_input();
2023-09-10 11:14:47 -07:00
// Input
2023-09-23 18:03:33 -07:00
// void poll_input();
2023-09-10 11:14:47 -07:00
{
2023-09-23 18:03:33 -07:00
// TODO(Ed) : Setup user definable deadzones for triggers and sticks.
2023-09-17 18:20:11 -07:00
// Swapping at the beginning of the input frame instead of the end.
2023-09-21 23:16:40 -07:00
swap ( old_keyboard , new_keyboard ) ;
swap ( old_xpads , new_xpads ) ;
swap ( old_ds_pads , new_ds_pads ) ;
2023-09-22 12:13:18 -07:00
2023-09-21 23:16:40 -07:00
// Keyboard Polling
2023-09-22 12:13:18 -07:00
// Keyboards are unified for now.
2023-09-21 23:16:40 -07:00
{
2023-09-24 19:37:05 -07:00
constexpr u32 is_down = 0x80000000 ;
2023-09-21 23:16:40 -07:00
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 ) ;
input_process_digital_btn ( & old_keyboard - > A , & new_keyboard - > A , GetAsyncKeyState ( ' A ' ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > S , & new_keyboard - > S , GetAsyncKeyState ( ' S ' ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > D , & new_keyboard - > D , GetAsyncKeyState ( ' D ' ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > Escape , & new_keyboard - > Escape , GetAsyncKeyState ( VK_ESCAPE ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > Backspace , & new_keyboard - > Backspace , GetAsyncKeyState ( VK_BACK ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > Up , & new_keyboard - > Up , GetAsyncKeyState ( VK_UP ) , is_down ) ;
input_process_digital_btn ( & old_keyboard - > Down , & new_keyboard - > Down , GetAsyncKeyState ( VK_DOWN ) , 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 - > Space , & new_keyboard - > Space , GetAsyncKeyState ( VK_SPACE ) , is_down ) ;
2023-09-24 19:37:05 -07:00
input_process_digital_btn ( & old_keyboard - > Pause , & new_keyboard - > Pause , GetAsyncKeyState ( VK_PAUSE ) , is_down ) ;
2023-09-22 12:13:18 -07:00
input . Controllers [ 0 ] . Keyboard = new_keyboard ;
2023-09-21 23:16:40 -07:00
}
2023-09-17 18:20:11 -07:00
2023-09-10 11:14:47 -07:00
// XInput Polling
// TODO(Ed) : Should we poll this more frequently?
2023-09-17 18:20:11 -07:00
for ( DWORD controller_index = 0 ; controller_index < Max_Controllers ; + + controller_index )
2023-09-10 11:14:47 -07:00
{
XINPUT_STATE controller_state ;
2023-09-17 18:20:11 -07:00
b32 xinput_detected = xinput_get_state ( controller_index , & controller_state ) = = XI_PluggedIn ;
2023-09-10 11:14:47 -07:00
if ( xinput_detected )
2023-09-09 14:06:47 -07:00
{
2023-09-17 18:20:11 -07:00
XINPUT_GAMEPAD * xpad = & controller_state . Gamepad ;
engine : : XInputPadState * old_xpad = old_xpads [ controller_index ] ;
engine : : XInputPadState * new_xpad = new_xpads [ controller_index ] ;
input_process_digital_btn ( & old_xpad - > DPad . Up , & new_xpad - > DPad . Up , xpad - > wButtons , XINPUT_GAMEPAD_DPAD_UP ) ;
input_process_digital_btn ( & old_xpad - > DPad . Down , & new_xpad - > DPad . Down , xpad - > wButtons , XINPUT_GAMEPAD_DPAD_DOWN ) ;
input_process_digital_btn ( & old_xpad - > DPad . Left , & new_xpad - > DPad . Left , xpad - > wButtons , XINPUT_GAMEPAD_DPAD_LEFT ) ;
input_process_digital_btn ( & old_xpad - > DPad . Right , & new_xpad - > DPad . Right , xpad - > wButtons , XINPUT_GAMEPAD_DPAD_RIGHT ) ;
input_process_digital_btn ( & old_xpad - > Y , & new_xpad - > Y , xpad - > wButtons , XINPUT_GAMEPAD_Y ) ;
input_process_digital_btn ( & old_xpad - > A , & new_xpad - > A , xpad - > wButtons , XINPUT_GAMEPAD_A ) ;
input_process_digital_btn ( & old_xpad - > B , & new_xpad - > B , xpad - > wButtons , XINPUT_GAMEPAD_B ) ;
input_process_digital_btn ( & old_xpad - > X , & new_xpad - > X , xpad - > wButtons , XINPUT_GAMEPAD_X ) ;
input_process_digital_btn ( & old_xpad - > Back , & new_xpad - > Back , xpad - > wButtons , XINPUT_GAMEPAD_BACK ) ;
input_process_digital_btn ( & old_xpad - > Start , & new_xpad - > Start , xpad - > wButtons , XINPUT_GAMEPAD_START ) ;
input_process_digital_btn ( & old_xpad - > LeftShoulder , & new_xpad - > LeftShoulder , xpad - > wButtons , XINPUT_GAMEPAD_LEFT_SHOULDER ) ;
input_process_digital_btn ( & old_xpad - > RightShoulder , & new_xpad - > RightShoulder , xpad - > wButtons , XINPUT_GAMEPAD_RIGHT_SHOULDER ) ;
new_xpad - > Stick . Left . X . Start = old_xpad - > Stick . Left . X . End ;
new_xpad - > Stick . Left . Y . Start = old_xpad - > Stick . Left . Y . End ;
2023-09-21 23:16:40 -07:00
f32 left_x = xinput_process_axis_value ( xpad - > sThumbLX , XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ) ;
f32 left_y = xinput_process_axis_value ( xpad - > sThumbLY , XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ) ;
2023-09-17 18:20:11 -07:00
// TODO(Ed) : Min/Max macros!!!
2023-09-21 23:16:40 -07:00
new_xpad - > Stick . Left . X . Min = new_xpad - > Stick . Left . X . Max = new_xpad - > Stick . Left . X . End = left_x ;
new_xpad - > Stick . Left . Y . Min = new_xpad - > Stick . Left . Y . Max = new_xpad - > Stick . Left . Y . End = left_y ;
2023-09-22 12:13:18 -07:00
2023-09-21 23:16:40 -07:00
// TODO(Ed): Make this actually an average for later
new_xpad - > Stick . Left . X . Average = left_x ;
new_xpad - > Stick . Left . Y . Average = left_y ;
2023-09-17 18:20:11 -07:00
input . Controllers [ controller_index ] . XPad = new_xpad ;
2023-09-09 14:06:47 -07:00
}
2023-09-10 11:14:47 -07:00
else
{
2023-09-17 18:20:11 -07:00
input . Controllers [ controller_index ] . XPad = nullptr ;
2023-09-10 11:14:47 -07:00
}
}
2023-09-09 14:06:47 -07:00
2023-09-10 11:14:47 -07:00
// JSL Input Polling
for ( u32 jsl_device_index = 0 ; jsl_device_index < jsl_num_devices ; + + jsl_device_index )
{
2023-09-17 18:20:11 -07:00
if ( ! JslStillConnected ( jsl_device_handles [ jsl_device_index ] ) )
2023-09-09 17:14:40 -07:00
{
2023-09-10 11:14:47 -07:00
OutputDebugStringA ( " Error: JSLStillConnected returned false \n " ) ;
continue ;
2023-09-09 17:14:40 -07:00
}
2023-09-17 18:20:11 -07:00
// TODO : Won't support more than 4 for now... (or prob ever)
if ( jsl_device_index > 4 )
break ;
2023-09-10 11:14:47 -07:00
2023-09-21 23:16:40 -07:00
JOY_SHOCK_STATE state = JslGetSimpleState ( jsl_device_handles [ jsl_device_index ] ) ;
2023-09-22 12:13:18 -07:00
// For now we're assuming anything that is detected via JSL is a dualsense pad.
// We'll eventually add support possibly for the nintendo pro controller.
2023-09-17 18:20:11 -07:00
engine : : DualsensePadState * old_ds_pad = old_ds_pads [ jsl_device_index ] ;
engine : : DualsensePadState * new_ds_pad = new_ds_pads [ jsl_device_index ] ;
2023-09-09 14:06:47 -07:00
2023-09-17 18:20:11 -07:00
input_process_digital_btn ( & old_ds_pad - > DPad . Up , & new_ds_pad - > DPad . Up , state . buttons , JSMASK_UP ) ;
input_process_digital_btn ( & old_ds_pad - > DPad . Down , & new_ds_pad - > DPad . Down , state . buttons , JSMASK_DOWN ) ;
input_process_digital_btn ( & old_ds_pad - > DPad . Left , & new_ds_pad - > DPad . Left , state . buttons , JSMASK_LEFT ) ;
input_process_digital_btn ( & old_ds_pad - > DPad . Right , & new_ds_pad - > DPad . Right , state . buttons , JSMASK_RIGHT ) ;
input_process_digital_btn ( & old_ds_pad - > Triangle , & new_ds_pad - > Triangle , state . buttons , JSMASK_N ) ;
input_process_digital_btn ( & old_ds_pad - > X , & new_ds_pad - > X , state . buttons , JSMASK_S ) ;
input_process_digital_btn ( & old_ds_pad - > Square , & new_ds_pad - > Square , state . buttons , JSMASK_W ) ;
input_process_digital_btn ( & old_ds_pad - > Circle , & new_ds_pad - > Circle , state . buttons , JSMASK_E ) ;
input_process_digital_btn ( & old_ds_pad - > Share , & new_ds_pad - > Share , state . buttons , JSMASK_SHARE ) ;
input_process_digital_btn ( & old_ds_pad - > Options , & new_ds_pad - > Options , state . buttons , JSMASK_OPTIONS ) ;
input_process_digital_btn ( & old_ds_pad - > L1 , & new_ds_pad - > L1 , state . buttons , JSMASK_L ) ;
input_process_digital_btn ( & old_ds_pad - > R1 , & new_ds_pad - > R1 , state . buttons , JSMASK_R ) ;
2023-09-21 23:16:40 -07:00
new_ds_pad - > Stick . Left . X . Start = old_ds_pad - > Stick . Left . X . End ;
new_ds_pad - > Stick . Left . Y . Start = old_ds_pad - > Stick . Left . Y . End ;
// Joyshock abstracts the sticks to a float value already for us of -1.f to 1.f.
// We'll assume a deadzone of 10% for now.
f32 left_x = input_process_axis_value ( state . stickLX , 0.1f ) ;
f32 left_y = input_process_axis_value ( state . stickLY , 0.1f ) ;
new_ds_pad - > Stick . Left . X . Min = new_ds_pad - > Stick . Left . X . Max = new_ds_pad - > Stick . Left . X . End = left_x ;
new_ds_pad - > Stick . Left . Y . Min = new_ds_pad - > Stick . Left . Y . Max = new_ds_pad - > Stick . Left . Y . End = left_y ;
2023-09-22 12:13:18 -07:00
2023-09-21 23:16:40 -07:00
// TODO(Ed): Make this actually an average for later
new_ds_pad - > Stick . Left . X . Average = left_x ;
new_ds_pad - > Stick . Left . Y . Average = left_y ;
2023-09-17 18:20:11 -07:00
input . Controllers [ jsl_device_index ] . DSPad = new_ds_pad ;
2023-09-08 18:08:57 -07:00
}
}
2023-09-10 11:14:47 -07:00
2023-09-24 19:37:05 -07:00
// 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 2 ms 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 ;
2023-09-16 15:41:07 -07:00
2023-09-17 18:20:11 -07:00
if ( byte_to_lock > target_cursor )
2023-09-16 15:41:07 -07:00
{
// Infront of play cursor |--play--byte_to_write-->--|
2023-09-23 18:03:33 -07:00
bytes_to_write = ds_sound_buffer . SecondaryBufferSize - byte_to_lock ;
2023-09-16 15:41:07 -07:00
bytes_to_write + = target_cursor ;
}
else
{
// Behind play cursor |--byte_to_write-->--play--|
bytes_to_write = target_cursor - byte_to_lock ;
}
2023-09-24 19:37:05 -07:00
// Engine Sound
// s16 samples[ 48000 * 2 ];
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 ) ;
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 ;
2023-09-23 18:03:33 -07:00
// Update audio buffer
if ( ! sound_is_valid )
break ;
# if Build_Development && 0
2023-09-24 19:37:05 -07:00
#if 0
2023-09-23 18:03:33 -07:00
DWORD play_cursor ;
DWORD write_cursor ;
ds_sound_buffer . SecondaryBuffer - > GetCurrentPosition ( & play_cursor , & write_cursor ) ;
2023-09-24 19:37:05 -07:00
# 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 ;
2023-09-23 18:03:33 -07:00
char text_buffer [ 256 ] ;
2023-09-24 19:37:05 -07:00
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 ) ;
2023-09-23 18:03:33 -07:00
OutputDebugStringA ( text_buffer ) ;
# endif
2023-09-24 19:37:05 -07:00
ds_fill_sound_buffer ( & ds_sound_buffer , byte_to_lock , bytes_to_write ) ;
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
DWORD ds_status = 0 ;
if ( SUCCEEDED ( ds_sound_buffer . SecondaryBuffer - > GetStatus ( & ds_status ) ) )
{
ds_sound_buffer . IsPlaying = ds_status & DSBSTATUS_PLAYING ;
}
2023-09-23 18:03:33 -07:00
if ( ds_sound_buffer . IsPlaying )
break ;
ds_sound_buffer . SecondaryBuffer - > Play ( 0 , 0 , DSBPLAY_LOOPING ) ;
} while ( 0 ) ;
2023-09-15 18:35:27 -07:00
2023-09-24 19:37:05 -07:00
// Timing Update
2023-09-08 18:08:57 -07:00
{
2023-09-24 19:37:05 -07:00
u64 work_frame_end_cycle = __rdtsc ( ) ;
u64 work_frame_end_clock = timing_get_wall_clock ( ) ;
2023-09-22 12:13:18 -07:00
2023-09-24 19:37:05 -07:00
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
2023-09-23 18:03:33 -07:00
if ( frame_elapsed_ms < Engine_Frame_Target_MS )
{
2023-09-24 19:37:05 -07:00
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 ) ) ;
}
2023-09-23 18:03:33 -07:00
2023-09-24 19:37:05 -07:00
u64 frame_clock = timing_get_wall_clock ( ) ;
2023-09-22 12:13:18 -07:00
frame_elapsed_ms = timing_get_ms_elapsed ( last_frame_clock , frame_clock ) ;
2023-09-24 19:37:05 -07:00
if ( frame_elapsed_ms < Engine_Frame_Target_MS )
{
// TODO(Ed) : Log missed sleep here.
}
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 ) ;
}
}
else
{
// TODO(Ed) : Missed the display sync window!
2023-09-22 12:13:18 -07:00
}
2023-09-10 11:14:47 -07:00
2023-09-24 19:37:05 -07:00
last_frame_clock = timing_get_wall_clock ( ) ; // LastCouner
last_frame_cycle = __rdtsc ( ) ;
}
2023-09-16 15:41:07 -07:00
2023-09-23 18:03:33 -07:00
// Update surface back buffer
2023-09-24 19:37:05 -07:00
if ( ! Pause_Rendering )
2023-09-22 12:13:18 -07:00
{
WinDimensions dimensions = get_window_dimensions ( window_handle ) ;
HDC device_context = GetDC ( window_handle ) ;
2023-09-23 18:03:33 -07:00
# if Build_Development
2023-09-24 19:37:05 -07:00
// 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 ) ;
2023-09-23 18:03:33 -07:00
# endif
display_buffer_in_window ( device_context , dimensions . Width , dimensions . Height , & Surface_Back_Buffer
2023-09-22 12:13:18 -07:00
, 0 , 0
, dimensions . Width , dimensions . Height ) ;
}
2023-09-13 21:43:35 -07:00
2023-09-24 19:37:05 -07:00
flip_wall_clock = timing_get_wall_clock ( ) ;
# if Build_Development
2023-09-23 18:03:33 -07:00
{
2023-09-24 19:37:05 -07:00
// Audio Debug
2023-09-23 18:03:33 -07:00
DWORD play_cursor = 0 ;
DWORD write_cursor = 0 ;
if ( SUCCEEDED ( ds_sound_buffer . SecondaryBuffer - > GetCurrentPosition ( & play_cursor , & write_cursor ) ) )
{
if ( ! sound_is_valid )
{
ds_sound_buffer . RunningSampleIndex = write_cursor / ds_sound_buffer . BytesPerSample ;
sound_is_valid = true ;
}
2023-09-24 19:37:05 -07:00
assert ( debug_marker_index < debug_marker_history_size )
DebugTimeMarker * marker = & debug_markers [ debug_marker_index ] ;
marker - > FlipPlayCursor = play_cursor ;
marker - > FlipWriteCursor = write_cursor ;
2023-09-23 18:03:33 -07:00
}
2023-09-24 19:37:05 -07:00
}
# endif
2023-09-23 18:03:33 -07:00
# if Build_Development
debug_marker_index + + ;
if ( debug_marker_index > = debug_marker_history_size )
debug_marker_index = 0 ;
# endif
2023-09-08 18:08:57 -07:00
}
2023-09-08 15:54:16 -07:00
2023-09-23 18:03:33 -07:00
engine : : shutdown ( ) ;
2023-09-09 20:58:28 -07:00
if ( jsl_num_devices > 0 )
{
for ( u32 jsl_device_index = 0 ; jsl_device_index < jsl_num_devices ; + + jsl_device_index )
{
2023-09-17 18:20:11 -07:00
JslSetLightColour ( jsl_device_handles [ jsl_device_index ] , 0 ) ;
2023-09-09 20:58:28 -07:00
}
}
2023-09-08 12:14:09 -07:00
return 0 ;
}