HandmadeHero/project/platform/win32_platform.cpp

1346 lines
43 KiB
C++
Raw Normal View History

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
#include "platform.hpp"
#include "jsl.hpp" // Using this to get dualsense controllers
#include "win32.hpp"
2023-09-15 18:35:27 -07:00
// Engine layer headers
#include "engine.hpp"
#include "platform_engine_api.hpp"
2023-09-24 19:37:05 -07:00
// Standard-Library stand-ins
// #include <malloc.h>
// TODO(Ed) : Remove these ^^
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-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;
// 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;
# 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 );
// TODO(Ed) : Clear to black
2023-09-08 21:01:53 -07:00
}
internal void
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
#if 0
2023-09-08 21:01:53 -07:00
, x, y, width, height
, x, y, width, height
#endif
, 0, 0, window_width, window_height
, 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 );
}
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;
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 21:01:53 -07:00
using namespace win32;
2023-09-20 11:43:55 -07:00
using namespace platform;
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-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-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-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-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-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-10 11:14:47 -07:00
OutputDebugStringA( "Error: JSLStillConnected returned false\n" );
continue;
}
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-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 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;
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
}
}
return 0;
}