4coder/win32_4ed.cpp

2489 lines
78 KiB
C++

/*
* Mr. 4th Dimention - Allen Webster
*
* 12.12.2014
*
* Win32 layer for 4coder
*
*/
// TOP
//
// Architecture cracking
//
#if defined(_M_AMD64)
# define CALL_CONVENTION
# define BUILD_X64
#elif defined(_M_IX86)
# define CALL_CONVENTION __stdcall
# define BUILD_X86
#else
# error architecture not supported yet
#endif
//
// Program setup
//
#include <assert.h>
#include <string.h>
#include "4tech_defines.h"
#include "4coder_API/version.h"
#include "4coder_lib/4coder_utf8.h"
#if defined(FRED_SUPER)
# include "4coder_API/keycodes.h"
# include "4coder_API/style.h"
# define FSTRING_IMPLEMENTATION
# define FSTRING_C
# include "4coder_lib/4coder_string.h"
# include "4coder_lib/4coder_mem.h"
# include "4coder_API/types.h"
# include "4ed_os_custom_api.h"
#else
# include "4coder_default_bindings.cpp"
#endif
#include "4ed_math.h"
#include "4ed_system.h"
#include "4ed_rendering.h"
#include "4ed.h"
#include <Windows.h>
#include <GL/gl.h>
#include "win32_gl.h"
#define GL_TEXTURE_MAX_LEVEL 0x813D
#include "filetrack/4tech_file_track_win32.c"
#include "4ed_system_shared.h"
#define SUPPORT_DPI 1
#define FPS 60
#define frame_useconds (1000000 / FPS)
#define WM_4coder_ANIMATE (WM_USER + 0)
//
// Win32_Vars structs
//
struct Thread_Context{
u32 job_id;
b32 running;
b32 cancel;
Work_Queue *queue;
u32 id;
u32 group_id;
u32 windows_id;
HANDLE handle;
};
struct Thread_Group{
Thread_Context *threads;
i32 count;
Unbounded_Work_Queue queue;
i32 cancel_lock0;
i32 cancel_cv0;
};
struct Control_Keys{
b8 l_ctrl;
b8 r_ctrl;
b8 l_alt;
b8 r_alt;
};
static Control_Keys null_control_keys = {0};
struct Win32_Input_Chunk_Transient{
Key_Input_Data key_data;
b8 mouse_l_press, mouse_l_release;
b8 mouse_r_press, mouse_r_release;
b8 out_of_window;
i8 mouse_wheel;
b8 trying_to_kill;
};
static Win32_Input_Chunk_Transient null_input_chunk_transient = {0};
struct Win32_Input_Chunk_Persistent{
i32 mouse_x, mouse_y;
b8 mouse_l, mouse_r;
Control_Keys controls;
b8 control_keys[MDFR_INDEX_COUNT];
};
typedef struct Win32_Input_Chunk{
Win32_Input_Chunk_Transient trans;
Win32_Input_Chunk_Persistent pers;
} Win32_Input_Chunk;
typedef struct Win32_Coroutine{
Coroutine coroutine;
Win32_Coroutine *next;
i32 done;
} Win32_Coroutine;
enum CV_ID{
CANCEL_CV0,
CANCEL_CV1,
CANCEL_CV2,
CANCEL_CV3,
CANCEL_CV4,
CANCEL_CV5,
CANCEL_CV6,
CANCEL_CV7,
CV_COUNT
};
typedef struct Win32_Vars{
System_Functions system;
App_Functions app;
Custom_API custom_api;
HMODULE app_code;
HMODULE custom;
Plat_Settings settings;
Work_Queue queues[THREAD_GROUP_COUNT];
Thread_Group groups[THREAD_GROUP_COUNT];
CRITICAL_SECTION locks[LOCK_COUNT];
CONDITION_VARIABLE condition_vars[CV_COUNT];
Thread_Memory *thread_memory;
Win32_Coroutine coroutine_data[18];
Win32_Coroutine *coroutine_free;
Win32_Input_Chunk input_chunk;
b32 lctrl_lalt_is_altgr;
b32 got_useful_event;
b32 full_screen;
b32 do_toggle;
WINDOWPLACEMENT GlobalWindowPosition;
b32 send_exit_signal;
HCURSOR cursor_ibeam;
HCURSOR cursor_arrow;
HCURSOR cursor_leftright;
HCURSOR cursor_updown;
String clipboard_contents;
b32 next_clipboard_is_self;
DWORD clipboard_sequence;
HWND window_handle;
Render_Target target;
Partition font_part;
#if SUPPORT_DPI
i32 dpi_x, dpi_y;
#endif
u64 count_per_usecond;
b32 first;
i32 running_cli;
} Win32_Vars;
global Win32_Vars win32vars;
global Application_Memory memory_vars;
//
// Helpers
//
internal HANDLE
Win32Handle(Plat_Handle h){
HANDLE result;
memcpy(&result, &h, sizeof(result));
return(result);
}
internal Plat_Handle
Win32Handle(HANDLE h){
Plat_Handle result = {0};
Assert(sizeof(Plat_Handle) >= sizeof(h));
memcpy(&result, &h, sizeof(h));
return(result);
}
internal void*
Win32Ptr(Plat_Handle h){
void *result;
memcpy(&result, &h, sizeof(result));
return(result);
}
internal Plat_Handle
Win32Ptr(void *h){
Plat_Handle result = {0};
memcpy(&result, &h, sizeof(h));
return(result);
}
//
// Memory (not exposed to application, but needed in system_shared.cpp)
//
internal
Sys_Memory_Allocate_Sig(system_memory_allocate){
void *result = VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
return(result);
}
internal
Sys_Memory_Set_Protection_Sig(system_memory_set_protection){
bool32 result = false;
DWORD old_protect = 0;
DWORD protect = 0;
flags = flags & 0x7;
switch (flags){
case 0: protect = PAGE_NOACCESS; break;
case MemProtect_Read: protect = PAGE_READONLY; break;
case MemProtect_Write:
case MemProtect_Read|MemProtect_Write:
protect = PAGE_READWRITE; break;
case MemProtect_Execute: protect = PAGE_EXECUTE; break;
case MemProtect_Execute|MemProtect_Read: protect = PAGE_EXECUTE_READ; break;
case MemProtect_Execute|MemProtect_Write:
case MemProtect_Execute|MemProtect_Write|MemProtect_Read:
protect = PAGE_EXECUTE_READWRITE; break;
}
VirtualProtect(ptr, size, protect, &old_protect);
return(result);
}
internal
Sys_Memory_Free_Sig(system_memory_free){
VirtualFree(ptr, 0, MEM_RELEASE);
}
// TODO(allen): delete
internal
Sys_Get_Memory_Sig(system_get_memory_){
void *ptr = 0;
if (size > 0){
ptr = VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}
return(ptr);
}
// TODO(allen): delete
internal
Sys_Free_Memory_Sig(system_free_memory){
if (block){
VirtualFree(block, 0, MEM_RELEASE);
}
}
#define Win32GetMemory(size) system_get_memory_(size, __LINE__, __FILE__)
#define Win32FreeMemory(ptr) system_free_memory(ptr)
#define Win32ScratchPartition sysshared_scratch_partition
#define Win32ScratchPartitionGrow sysshared_partition_grow
#define Win32ScratchPartitionDouble sysshared_partition_double
#if 0
#if FRED_INTERNAL
internal void
INTERNAL_system_debug_message(char *message){
OutputDebugStringW(message);
}
#endif
#endif
//
// Multithreading
//
internal
Sys_Acquire_Lock_Sig(system_acquire_lock){
EnterCriticalSection(&win32vars.locks[id]);
}
internal
Sys_Release_Lock_Sig(system_release_lock){
LeaveCriticalSection(&win32vars.locks[id]);
}
internal void
system_wait_cv(i32 crit_id, i32 cv_id){
SleepConditionVariableCS(win32vars.condition_vars + cv_id,
win32vars.locks + crit_id,
INFINITE);
}
internal void
system_signal_cv(i32 crit_id, i32 cv_id){
AllowLocal(crit_id);
WakeConditionVariable(win32vars.condition_vars + cv_id);
}
internal DWORD CALL_CONVENTION
JobThreadProc(LPVOID lpParameter){
Thread_Context *thread = (Thread_Context*)lpParameter;
Work_Queue *queue = win32vars.queues + thread->group_id;
Thread_Group *group = win32vars.groups + thread->group_id;
i32 thread_index = thread->id - 1;
i32 cancel_lock = group->cancel_lock0 + thread_index;
i32 cancel_cv = group->cancel_cv0 + thread_index;
Thread_Memory *thread_memory = win32vars.thread_memory + thread_index;
if (thread_memory->size == 0){
i32 new_size = KB(64);
thread_memory->data = Win32GetMemory(new_size);
thread_memory->size = new_size;
}
for (;;){
u32 read_index = queue->read_position;
u32 write_index = queue->write_position;
if (read_index != write_index){
// NOTE(allen): Previously I was wrapping by the job wrap then
// wrapping by the queue wrap. That was super stupid what was that?
// Now it just wraps by the queue wrap.
u32 next_read_index = (read_index + 1) % QUEUE_WRAP;
u32 safe_read_index =
InterlockedCompareExchange(&queue->read_position,
next_read_index, read_index);
if (safe_read_index == read_index){
Full_Job_Data *full_job = queue->jobs + safe_read_index;
// NOTE(allen): This is interlocked so that it plays nice
// with the cancel job routine, which may try to cancel this job
// at the same time that we try to run it
i32 safe_running_thread =
InterlockedCompareExchange(&full_job->running_thread,
thread->id, THREAD_NOT_ASSIGNED);
if (safe_running_thread == THREAD_NOT_ASSIGNED){
thread->job_id = full_job->id;
thread->running = 1;
full_job->job.callback(&win32vars.system,
thread, thread_memory, full_job->job.data);
PostMessage(win32vars.window_handle, WM_4coder_ANIMATE, 0, 0);
//full_job->running_thread = 0;
thread->running = 0;
system_acquire_lock(cancel_lock);
if (thread->cancel){
thread->cancel = 0;
system_signal_cv(cancel_lock, cancel_cv);
}
system_release_lock(cancel_lock);
}
}
}
else{
WaitForSingleObject(Win32Handle(queue->semaphore), INFINITE);
}
}
}
internal void
initialize_unbounded_queue(Unbounded_Work_Queue *source_queue){
i32 max = 512;
source_queue->jobs = (Full_Job_Data*)system_get_memory(max*sizeof(Full_Job_Data));
source_queue->count = 0;
source_queue->max = max;
source_queue->skip = 0;
}
inline i32
get_work_queue_available_space(i32 write, i32 read){
// NOTE(allen): The only time that queue->write_position == queue->read_position
// is allowed is when the queue is empty. Thus if
// queue->write_position+1 == queue->read_position the available space is zero.
// So these computations both end up leaving one slot unused. The only way I can
// think to easily eliminate this is to have read and write wrap at twice the size
// of the underlying array but modulo their values into the array then if write
// has caught up with read it still will not be equal... but lots of modulos... ehh.
i32 available_space = 0;
if (write >= read){
available_space = QUEUE_WRAP - (write - read) - 1;
}
else{
available_space = (read - write) - 1;
}
return(available_space);
}
#define UNBOUNDED_SKIP_MAX 128
internal void
flush_to_direct_queue(Unbounded_Work_Queue *source_queue, Work_Queue *queue, i32 thread_count){
// NOTE(allen): It is understood that read_position may be changed by other
// threads but it will only make more space in the queue if it is changed.
// Meanwhile write_position should not ever be changed by anything but the
// main thread in this system, so it will not be interlocked.
u32 read_position = queue->read_position;
u32 write_position = queue->write_position;
u32 available_space = get_work_queue_available_space(write_position, read_position);
u32 available_jobs = source_queue->count - source_queue->skip;
u32 writable_count = Min(available_space, available_jobs);
if (writable_count > 0){
u32 count1 = writable_count;
if (count1+write_position > QUEUE_WRAP){
count1 = QUEUE_WRAP - write_position;
}
u32 count2 = writable_count - count1;
Full_Job_Data *job_src1 = source_queue->jobs + source_queue->skip;
Full_Job_Data *job_src2 = job_src1 + count1;
Full_Job_Data *job_dst1 = queue->jobs + write_position;
Full_Job_Data *job_dst2 = queue->jobs;
Assert((job_src1->id % QUEUE_WRAP) == write_position);
memcpy(job_dst1, job_src1, sizeof(Full_Job_Data)*count1);
memcpy(job_dst2, job_src2, sizeof(Full_Job_Data)*count2);
queue->write_position = (write_position + writable_count) % QUEUE_WRAP;
source_queue->skip += writable_count;
if (source_queue->skip == source_queue->count){
source_queue->skip = source_queue->count = 0;
}
else if (source_queue->skip > UNBOUNDED_SKIP_MAX){
u32 left_over = source_queue->count - source_queue->skip;
memmove(source_queue->jobs, source_queue->jobs + source_queue->skip,
sizeof(Full_Job_Data)*left_over);
source_queue->count = left_over;
source_queue->skip = 0;
}
}
i32 semaphore_release_count = writable_count;
if (semaphore_release_count > thread_count){
semaphore_release_count = thread_count;
}
// NOTE(allen): platform dependent portion...
// TODO(allen): pull out the duplicated part once I see
// that this is pretty much the same on linux.
for (i32 i = 0; i < semaphore_release_count; ++i){
ReleaseSemaphore(Win32Handle(queue->semaphore), 1, 0);
}
}
internal void
flush_thread_group(i32 group_id){
Thread_Group *group = win32vars.groups + group_id;
Work_Queue *queue = win32vars.queues + group_id;
Unbounded_Work_Queue *source_queue = &group->queue;
flush_to_direct_queue(source_queue, queue, group->count);
}
// Note(allen): post_job puts the job on the unbounded queue.
// The unbounded queue is entirely managed by the main thread.
// The thread safe queue is bounded in size so the unbounded
// queue is periodically flushed into the direct work queue.
internal
Sys_Post_Job_Sig(system_post_job){
Thread_Group *group = win32vars.groups + group_id;
Unbounded_Work_Queue *queue = &group->queue;
u32 result = queue->next_job_id++;
while (queue->count >= queue->max){
i32 new_max = queue->max*2;
Full_Job_Data *new_jobs = (Full_Job_Data*)
system_get_memory(new_max*sizeof(Full_Job_Data));
memcpy(new_jobs, queue->jobs, queue->count);
system_free_memory(queue->jobs);
queue->jobs = new_jobs;
queue->max = new_max;
}
Full_Job_Data full_job;
full_job.job = job;
full_job.running_thread = THREAD_NOT_ASSIGNED;
full_job.id = result;
queue->jobs[queue->count++] = full_job;
Work_Queue *direct_queue = win32vars.queues + group_id;
flush_to_direct_queue(queue, direct_queue, group->count);
return(result);
}
internal
Sys_Cancel_Job_Sig(system_cancel_job){
Thread_Group *group = win32vars.groups + group_id;
Unbounded_Work_Queue *source_queue = &group->queue;
b32 handled_in_unbounded = false;
if (source_queue->skip < source_queue->count){
Full_Job_Data *first_job = source_queue->jobs + source_queue->skip;
if (first_job->id <= job_id){
u32 index = source_queue->skip + (job_id - first_job->id);
Full_Job_Data *job = source_queue->jobs + index;
job->running_thread = 0;
handled_in_unbounded = true;
}
}
if (!handled_in_unbounded){
Work_Queue *queue = win32vars.queues + group_id;
Full_Job_Data *job = queue->jobs + (job_id % QUEUE_WRAP);
Assert(job->id == job_id);
u32 thread_id =
InterlockedCompareExchange(&job->running_thread,
0, THREAD_NOT_ASSIGNED);
if (thread_id != THREAD_NOT_ASSIGNED && thread_id != 0){
i32 thread_index = thread_id - 1;
i32 cancel_lock = group->cancel_lock0 + thread_index;
i32 cancel_cv = group->cancel_cv0 + thread_index;
Thread_Context *thread = group->threads + thread_index;
system_acquire_lock(cancel_lock);
thread->cancel = 1;
system_release_lock(FRAME_LOCK);
do{
system_wait_cv(cancel_lock, cancel_cv);
}while (thread->cancel == 1);
system_acquire_lock(FRAME_LOCK);
system_release_lock(cancel_lock);
}
}
}
internal
Sys_Check_Cancel_Sig(system_check_cancel){
b32 result = 0;
Thread_Group *group = win32vars.groups + thread->group_id;
i32 thread_index = thread->id - 1;
i32 cancel_lock = group->cancel_lock0 + thread_index;
system_acquire_lock(cancel_lock);
if (thread->cancel){
result = 1;
}
system_release_lock(cancel_lock);
return(result);
}
internal
Sys_Grow_Thread_Memory_Sig(system_grow_thread_memory){
system_acquire_lock(CANCEL_LOCK0 + memory->id - 1);
void *old_data = memory->data;
i32 old_size = memory->size;
i32 new_size = l_round_up_i32(memory->size*2, KB(4));
memory->data = system_get_memory(new_size);
memory->size = new_size;
if (old_data){
memcpy(memory->data, old_data, old_size);
system_free_memory(old_data);
}
system_release_lock(CANCEL_LOCK0 + memory->id - 1);
}
#if FRED_INTERNAL
internal void
INTERNAL_get_thread_states(Thread_Group_ID id, b8 *running, i32 *pending){
Thread_Group *group = win32vars.groups + id;
Unbounded_Work_Queue *source_queue = &group->queue;
Work_Queue *queue = win32vars.queues + id;
u32 write = queue->write_position;
u32 read = queue->read_position;
if (write < read){
write += QUEUE_WRAP;
}
*pending = (i32)(write - read) + source_queue->count - source_queue->skip;
for (i32 i = 0; i < group->count; ++i){
running[i] = (group->threads[i].running != 0);
}
}
#endif
//
// Coroutines
//
internal Win32_Coroutine*
Win32AllocCoroutine(){
Win32_Coroutine *result = win32vars.coroutine_free;
Assert(result != 0);
win32vars.coroutine_free = result->next;
return(result);
}
internal void
Win32FreeCoroutine(Win32_Coroutine *data){
data->next = win32vars.coroutine_free;
win32vars.coroutine_free = data;
}
internal void CALL_CONVENTION
Win32CoroutineMain(void *arg_){
Win32_Coroutine *c = (Win32_Coroutine*)arg_;
c->coroutine.func(&c->coroutine);
c->done = 1;
Win32FreeCoroutine(c);
SwitchToFiber(c->coroutine.yield_handle);
}
internal
Sys_Create_Coroutine_Sig(system_create_coroutine){
Win32_Coroutine *c;
Coroutine *coroutine;
void *fiber;
c = Win32AllocCoroutine();
c->done = 0;
coroutine = &c->coroutine;
fiber = CreateFiber(0, Win32CoroutineMain, coroutine);
coroutine->plat_handle = Win32Handle(fiber);
coroutine->func = func;
return(coroutine);
}
internal
Sys_Launch_Coroutine_Sig(system_launch_coroutine){
Win32_Coroutine *c = (Win32_Coroutine*)coroutine;
void *fiber;
fiber = Win32Handle(coroutine->plat_handle);
coroutine->yield_handle = GetCurrentFiber();
coroutine->in = in;
coroutine->out = out;
SwitchToFiber(fiber);
if (c->done){
DeleteFiber(fiber);
Win32FreeCoroutine(c);
coroutine = 0;
}
return(coroutine);
}
Sys_Resume_Coroutine_Sig(system_resume_coroutine){
Win32_Coroutine *c = (Win32_Coroutine*)coroutine;
void *fiber;
Assert(c->done == 0);
coroutine->yield_handle = GetCurrentFiber();
coroutine->in = in;
coroutine->out = out;
fiber = Win32Ptr(coroutine->plat_handle);
SwitchToFiber(fiber);
if (c->done){
DeleteFiber(fiber);
Win32FreeCoroutine(c);
coroutine = 0;
}
return(coroutine);
}
Sys_Yield_Coroutine_Sig(system_yield_coroutine){
SwitchToFiber(coroutine->yield_handle);
}
//
// Files
//
internal
Sys_File_Can_Be_Made_Sig(system_file_can_be_made){
HANDLE file = CreateFile((char*)filename, FILE_APPEND_DATA, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (!file || file == INVALID_HANDLE_VALUE){
return 0;
}
CloseHandle(file);
return(1);
}
internal
Sys_Set_File_List_Sig(system_set_file_list){
b32 clear_list = true;
if (directory != 0){
char dir_space[MAX_PATH + 32];
String dir = make_string_cap(dir_space, 0, MAX_PATH + 32);
append_sc(&dir, directory);
terminate_with_null(&dir);
HANDLE dir_handle = CreateFile(dir.str, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
if (dir_handle != INVALID_HANDLE_VALUE){
DWORD final_length = GetFinalPathNameByHandle(dir_handle, dir_space, sizeof(dir_space), 0);
CloseHandle(dir_handle);
if (final_length < sizeof(dir_space)){
char *c_str_dir = dir_space;
final_length -= 4;
memmove(c_str_dir, c_str_dir+4, final_length);
c_str_dir[final_length] = '\\';
c_str_dir[final_length+1] = '*';
c_str_dir[final_length+2] = 0;
if (canon_directory_out != 0){
if (final_length+1 < canon_directory_max){
memcpy(canon_directory_out, c_str_dir, final_length);
if (canon_directory_out[final_length-1] != '\\'){
canon_directory_out[final_length++] = '\\';
}
canon_directory_out[final_length] = 0;
*canon_directory_size_out = final_length;
}
else{
u32 length = copy_fast_unsafe_cc(canon_directory_out, directory);
canon_directory_out[length] = 0;
*canon_directory_size_out = length;
}
}
WIN32_FIND_DATA find_data;
HANDLE search = FindFirstFile(c_str_dir, &find_data);
if (search != INVALID_HANDLE_VALUE){
i32 character_count = 0;
i32 file_count = 0;
BOOL more_files = true;
do{
if (!match_cs(find_data.cFileName, make_lit_string(".")) &&
!match_cs(find_data.cFileName, make_lit_string(".."))){
++file_count;
i32 size = 0;
for(;find_data.cFileName[size];++size);
character_count += size + 1;
}
more_files = FindNextFile(search, &find_data);
}while(more_files);
FindClose(search);
i32 required_size = character_count + file_count * sizeof(File_Info);
if (file_list->block_size < required_size){
system_free_memory(file_list->block);
file_list->block = system_get_memory(required_size);
file_list->block_size = required_size;
}
file_list->infos = (File_Info*)file_list->block;
char *name = (char*)(file_list->infos + file_count);
if (file_list->block != 0){
search = FindFirstFile(c_str_dir, &find_data);
if (search != INVALID_HANDLE_VALUE){
File_Info *info = file_list->infos;
more_files = true;
do{
if (!match_cs(find_data.cFileName, make_lit_string(".")) &&
!match_cs(find_data.cFileName, make_lit_string(".."))){
info->folder = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info->filename = name;
i32 length = copy_fast_unsafe_cc(name, find_data.cFileName);
name += length;
info->filename_len = length;
*name++ = 0;
String fname = make_string_cap(info->filename, info->filename_len, info->filename_len+1);
replace_char(&fname, '\\', '/');
++info;
}
more_files = FindNextFile(search, &find_data);
}while(more_files);
FindClose(search);
file_list->count = file_count;
clear_list = false;
}
}
}
}
}
}
if (clear_list){
Win32FreeMemory(file_list->block);
file_list->block = 0;
file_list->block_size = 0;
file_list->infos = 0;
file_list->count = 0;
}
}
internal u32
win32_canonical_ascii_name(char *src, u32 len, char *dst, u32 max){
u32 result = 0;
char src_space[MAX_PATH + 32];
if (len < sizeof(src_space) && len >= 2 && ((src[0] >= 'a' && src[0] <= 'z') || (src[0] >= 'A' && src[0] <= 'Z')) && src[1] == ':'){
memcpy(src_space, src, len);
src_space[len] = 0;
HANDLE file = CreateFile(src_space, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file != INVALID_HANDLE_VALUE){
DWORD final_length = GetFinalPathNameByHandle(file, dst, max, 0);
if (final_length < max && final_length >= 4){
if (dst[final_length-1] == 0){
--final_length;
}
final_length -= 4;
memmove(dst, dst+4, final_length);
dst[final_length] = 0;
result = final_length;
}
CloseHandle(file);
}
else{
String src_str = make_string(src, len);
String path_str = path_of_directory(src_str);
String front_str = front_of_directory(src_str);
memcpy(src_space, path_str.str, path_str.size);
src_space[path_str.size] = 0;
HANDLE dir = CreateFile(src_space, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
if (dir != INVALID_HANDLE_VALUE){
DWORD final_length = GetFinalPathNameByHandle(dir, dst, max, 0);
if (final_length < max && final_length >= 4){
if (dst[final_length-1] == 0){
--final_length;
}
final_length -= 4;
memmove(dst, dst+4, final_length);
dst[final_length++] = '\\';
memcpy(dst + final_length, front_str.str, front_str.size);
final_length += front_str.size;
dst[final_length] = 0;
result = final_length;
}
CloseHandle(dir);
}
}
}
return(result);
}
internal
Sys_Get_Canonical_Sig(system_get_canonical){
u32 result = win32_canonical_ascii_name(filename, len, buffer, max);
return(result);
}
internal
Sys_Load_Handle_Sig(system_load_handle){
b32 result = 0;
HANDLE file = CreateFile(filename, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file != INVALID_HANDLE_VALUE){
*(HANDLE*)handle_out = file;
result = 1;
}
return(result);
}
internal
Sys_Load_Size_Sig(system_load_size){
u32 result = 0;
HANDLE file = *(HANDLE*)(&handle);
DWORD hi = 0;
DWORD lo = GetFileSize(file, &hi);
if (hi == 0){
result = lo;
}
return(result);
}
internal
Sys_Load_File_Sig(system_load_file){
b32 result = 0;
HANDLE file = *(HANDLE*)(&handle);
DWORD read_size = 0;
if (ReadFile(file, buffer, size, &read_size, 0)){
if (read_size == size){
result = 1;
}
}
return(result);
}
internal
Sys_Load_Close_Sig(system_load_close){
b32 result = 0;
HANDLE file = *(HANDLE*)(&handle);
if (CloseHandle(file)){
result = 1;
}
return(result);
}
internal
Sys_Save_File_Sig(system_save_file){
b32 result = 0;
HANDLE file = CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (file != INVALID_HANDLE_VALUE){
DWORD written_total = 0;
DWORD written_size = 0;
result = 1;
while (written_total < size){
if (!WriteFile(file, buffer + written_total, size - written_total, &written_size, 0)){
result = 0;
break;
}
written_total += written_size;
}
CloseHandle(file);
}
return(result);
}
internal
Sys_Now_Time_Sig(system_now_time){
u64 result = __rdtsc();
return(result);
}
internal b32
Win32DirectoryExists(char *path){
DWORD attrib = GetFileAttributes(path);
return (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY));
}
internal
Sys_Get_Binary_Path_Sig(system_get_binary_path){
i32 result = 0;
i32 size = GetModuleFileName(0, out->str, out->memory_size);
if (size < out->memory_size-1){
out->size = size;
remove_last_folder(out);
terminate_with_null(out);
result = out->size;
}
return(result);
}
internal
Sys_File_Exists_Sig(system_file_exists){
char full_filename_space[1024];
String full_filename;
HANDLE file;
b32 result = 0;
if (len < sizeof(full_filename_space)){
full_filename = make_fixed_width_string(full_filename_space);
copy_ss(&full_filename, make_string(filename, len));
terminate_with_null(&full_filename);
file = CreateFile(full_filename.str, GENERIC_READ, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file != INVALID_HANDLE_VALUE){
CloseHandle(file);
result = 1;
}
}
return(result);
}
internal
Sys_Directory_CD_Sig(system_directory_cd){
String directory = make_string_cap(dir, *len, cap);
b32 result = 0;
i32 old_size;
char rel_path_space[1024];
String rel_path_string = make_fixed_width_string(rel_path_space);
copy_ss(&rel_path_string, make_string(rel_path, rel_len));
terminate_with_null(&rel_path_string);
if (rel_path[0] != 0){
if (rel_path[0] == '.' && rel_path[1] == 0){
result = 1;
}
else if (rel_path[0] == '.' && rel_path[1] == '.' && rel_path[2] == 0){
result = remove_last_folder(&directory);
terminate_with_null(&directory);
}
else{
if (directory.size + rel_len + 1 > directory.memory_size){
old_size = directory.size;
append_partial_sc(&directory, rel_path);
append_s_char(&directory, '\\');
if (Win32DirectoryExists(directory.str)){
result = 1;
}
else{
directory.size = old_size;
}
}
}
}
*len = directory.size;
return(result);
}
internal
Sys_Get_4ed_Path_Sig(system_get_4ed_path){
String str = make_string_cap(out, 0, capacity);
int32_t size = system_get_binary_path(&str);
return(size);
}
/*
NOTE(casey): This follows Raymond Chen's prescription
for fullscreen toggling, see:
http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx
*/
internal void
Win32ToggleFullscreen(void){
HWND Window = win32vars.window_handle;
LONG_PTR Style = GetWindowLongPtr(Window, GWL_STYLE);
if (Style & WS_OVERLAPPEDWINDOW){
MONITORINFO MonitorInfo = {sizeof(MonitorInfo)};
if(GetWindowPlacement(Window, &win32vars.GlobalWindowPosition) &&
GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo))
{
SetWindowLongPtr(Window, GWL_STYLE, Style & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(Window, HWND_TOP,
MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top,
MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left,
MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
win32vars.full_screen = 1;
}
}
else{
SetWindowLongPtr(Window, GWL_STYLE, Style | WS_OVERLAPPEDWINDOW);
SetWindowPlacement(Window, &win32vars.GlobalWindowPosition);
SetWindowPos(Window, 0, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
win32vars.full_screen = 0;
}
}
//
// Clipboard
//
internal
Sys_Post_Clipboard_Sig(system_post_clipboard){
if (OpenClipboard(win32vars.window_handle)){
EmptyClipboard();
HANDLE memory_handle;
memory_handle = GlobalAlloc(GMEM_MOVEABLE, str.size+1);
if (memory_handle){
char *dest = (char*)GlobalLock(memory_handle);
copy_fast_unsafe_cs(dest, str);
GlobalUnlock(memory_handle);
SetClipboardData(CF_TEXT, memory_handle);
win32vars.next_clipboard_is_self = 1;
}
CloseClipboard();
}
}
internal b32
Win32ReadClipboardContents(){
b32 result = 0;
if (IsClipboardFormatAvailable(CF_TEXT)){
result = 1;
if (OpenClipboard(win32vars.window_handle)){
HANDLE clip_data;
clip_data = GetClipboardData(CF_TEXT);
if (clip_data){
win32vars.clipboard_contents.str = (char*)GlobalLock(clip_data);
if (win32vars.clipboard_contents.str){
win32vars.clipboard_contents.size = str_size((char*)win32vars.clipboard_contents.str);
GlobalUnlock(clip_data);
}
}
CloseClipboard();
}
}
return(result);
}
//
// Command Line Exectuion
//
internal
Sys_CLI_Call_Sig(system_cli_call){
char cmd[] = "c:\\windows\\system32\\cmd.exe";
char *env_variables = 0;
char command_line[2048];
String s = make_fixed_width_string(command_line);
copy_ss(&s, make_lit_string("/C "));
append_partial_sc(&s, script_name);
b32 success = terminate_with_null(&s);
if (success){
success = 0;
SECURITY_ATTRIBUTES sec_attributes = {};
HANDLE out_read;
HANDLE out_write;
sec_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attributes.bInheritHandle = TRUE;
if (CreatePipe(&out_read, &out_write, &sec_attributes, 0)){
if (SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)){
STARTUPINFO startup = {};
startup.cb = sizeof(STARTUPINFO);
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
startup.hStdError = out_write;
startup.hStdOutput = out_write;
startup.wShowWindow = SW_HIDE;
PROCESS_INFORMATION info = {};
Assert(sizeof(Plat_Handle) >= sizeof(HANDLE));
if (CreateProcess(cmd, command_line,
0, 0, TRUE, 0,
env_variables, path,
&startup, &info)){
success = 1;
CloseHandle(info.hThread);
*(HANDLE*)&cli_out->proc = info.hProcess;
*(HANDLE*)&cli_out->out_read = out_read;
*(HANDLE*)&cli_out->out_write = out_write;
++win32vars.running_cli;
}
else{
CloseHandle(out_read);
CloseHandle(out_write);
*(HANDLE*)&cli_out->proc = INVALID_HANDLE_VALUE;
*(HANDLE*)&cli_out->out_read = INVALID_HANDLE_VALUE;
*(HANDLE*)&cli_out->out_write = INVALID_HANDLE_VALUE;
}
}
else{
// TODO(allen): failed SetHandleInformation
CloseHandle(out_read);
CloseHandle(out_write);
*(HANDLE*)&cli_out->proc = INVALID_HANDLE_VALUE;
*(HANDLE*)&cli_out->out_read = INVALID_HANDLE_VALUE;
*(HANDLE*)&cli_out->out_write = INVALID_HANDLE_VALUE;
}
}
else{
// TODO(allen): failed CreatePipe
}
}
return success;
}
struct CLI_Loop_Control{
u32 remaining_amount;
};
internal
Sys_CLI_Begin_Update_Sig(system_cli_begin_update){
Assert(sizeof(cli->scratch_space) >= sizeof(CLI_Loop_Control));
CLI_Loop_Control *loop = (CLI_Loop_Control*)cli->scratch_space;
loop->remaining_amount = 0;
}
internal
Sys_CLI_Update_Step_Sig(system_cli_update_step){
HANDLE handle = *(HANDLE*)&cli->out_read;
CLI_Loop_Control *loop = (CLI_Loop_Control*)cli->scratch_space;
b32 has_more = 0;
DWORD remaining = loop->remaining_amount;
u32 pos = 0;
DWORD read_amount = 0;
for (;;){
if (remaining == 0){
if (!PeekNamedPipe(handle, 0, 0, 0, &remaining, 0)) break;
if (remaining == 0) break;
}
if (remaining + pos < max){
has_more = 1;
ReadFile(handle, dest + pos, remaining, &read_amount, 0);
TentativeAssert(remaining == read_amount);
pos += remaining;
remaining = 0;
}
else{
has_more = 1;
ReadFile(handle, dest + pos, max - pos, &read_amount, 0);
TentativeAssert(max - pos == read_amount);
loop->remaining_amount = remaining - (max - pos);
pos = max;
break;
}
}
*amount = pos;
return(has_more);
}
internal
Sys_CLI_End_Update_Sig(system_cli_end_update){
b32 close_me = 0;
HANDLE proc = *(HANDLE*)&cli->proc;
DWORD result = 0;
if (WaitForSingleObject(proc, 0) == WAIT_OBJECT_0){
if (GetExitCodeProcess(proc, &result) == 0){
cli->exit = -1;
}
else{
cli->exit = (i32)result;
}
close_me = 1;
CloseHandle(*(HANDLE*)&cli->proc);
CloseHandle(*(HANDLE*)&cli->out_read);
CloseHandle(*(HANDLE*)&cli->out_write);
--win32vars.running_cli;
}
return(close_me);
}
//
// Appearence Settings
//
// TODO(allen): add a "shown but auto-hides on timer" setting here.
internal
Sys_Show_Mouse_Cursor_Sig(system_show_mouse_cursor){
switch (show){
case MouseCursorShow_Never:
ShowCursor(false);
break;
case MouseCursorShow_Always:
ShowCursor(true);
break;
// TODO(allen): MouseCursor_HideWhenStill
}
}
internal
Sys_Toggle_Fullscreen_Sig(system_toggle_fullscreen){
/* NOTE(allen): Don't actually change window size now!
Tell the platform layer to do the toggle (or to cancel the toggle)
later when the app.step function isn't running. If the size changes
mid step, it messes up the rendering rules and stuff. */
b32 success = false;
// NOTE(allen): On windows we must be in stream mode to go fullscreen.
if (win32vars.settings.stream_mode){
win32vars.do_toggle = !win32vars.do_toggle;
success = true;
}
return(success);
}
internal
Sys_Is_Fullscreen_Sig(system_is_fullscreen){
/* NOTE(allen): This is a fancy way to say 'full_screen XOR do_toggle'
This way this function can always report the state the fullscreen
will have when the next frame runs, given the number of toggles
that have occurred this frame and the original value. */
bool32 result = (win32vars.full_screen + win32vars.do_toggle) & 1;
return(result);
}
internal
Sys_Send_Exit_Signal_Sig(system_send_exit_signal){
win32vars.send_exit_signal = 1;
}
#include "4ed_system_shared.cpp"
//
// Linkage to Custom and Application
//
internal b32
Win32LoadAppCode(){
b32 result = 0;
App_Get_Functions *get_funcs = 0;
win32vars.app_code = LoadLibraryA("4ed_app.dll");
if (win32vars.app_code){
get_funcs = (App_Get_Functions*)
GetProcAddress(win32vars.app_code, "app_get_functions");
}
if (get_funcs){
result = 1;
win32vars.app = get_funcs();
}
return result;
}
internal void
Win32LoadSystemCode(){
win32vars.system.set_file_list = system_set_file_list;
win32vars.system.get_canonical = system_get_canonical;
win32vars.system.add_listener = system_add_listener;
win32vars.system.remove_listener = system_remove_listener;
win32vars.system.get_file_change = system_get_file_change;
win32vars.system.load_handle = system_load_handle;
win32vars.system.load_size = system_load_size;
win32vars.system.load_file = system_load_file;
win32vars.system.load_close = system_load_close;
win32vars.system.save_file = system_save_file;
win32vars.system.now_time = system_now_time;
win32vars.system.post_clipboard = system_post_clipboard;
win32vars.system.create_coroutine = system_create_coroutine;
win32vars.system.launch_coroutine = system_launch_coroutine;
win32vars.system.resume_coroutine = system_resume_coroutine;
win32vars.system.yield_coroutine = system_yield_coroutine;
win32vars.system.cli_call = system_cli_call;
win32vars.system.cli_begin_update = system_cli_begin_update;
win32vars.system.cli_update_step = system_cli_update_step;
win32vars.system.cli_end_update = system_cli_end_update;
win32vars.system.post_job = system_post_job;
win32vars.system.cancel_job = system_cancel_job;
win32vars.system.check_cancel = system_check_cancel;
win32vars.system.grow_thread_memory = system_grow_thread_memory;
win32vars.system.acquire_lock = system_acquire_lock;
win32vars.system.release_lock = system_release_lock;
win32vars.system.memory_allocate = system_memory_allocate;
win32vars.system.memory_set_protection = system_memory_set_protection;
win32vars.system.memory_free = system_memory_free;
win32vars.system.file_exists = system_file_exists;
win32vars.system.directory_cd = system_directory_cd;
win32vars.system.get_4ed_path = system_get_4ed_path;
win32vars.system.toggle_fullscreen = system_toggle_fullscreen;
win32vars.system.is_fullscreen = system_is_fullscreen;win32vars.system.show_mouse_cursor = system_show_mouse_cursor;
win32vars.system.send_exit_signal = system_send_exit_signal;
#if FRED_INTERNAL
win32vars.system.internal_get_thread_states = INTERNAL_get_thread_states;
#endif
}
internal void
Win32LoadRenderCode(){
win32vars.target.push_clip = draw_push_clip;
win32vars.target.pop_clip = draw_pop_clip;
win32vars.target.push_piece = draw_push_piece;
#if 0
win32vars.target.font_set.font_load = font_load;
win32vars.target.font_set.release_font = draw_release_font;
#endif
}
//
// Helpers
//
global Key_Code keycode_lookup_table[255];
internal void
Win32KeycodeInit(){
keycode_lookup_table[VK_BACK] = key_back;
keycode_lookup_table[VK_DELETE] = key_del;
keycode_lookup_table[VK_UP] = key_up;
keycode_lookup_table[VK_DOWN] = key_down;
keycode_lookup_table[VK_LEFT] = key_left;
keycode_lookup_table[VK_RIGHT] = key_right;
keycode_lookup_table[VK_INSERT] = key_insert;
keycode_lookup_table[VK_HOME] = key_home;
keycode_lookup_table[VK_END] = key_end;
keycode_lookup_table[VK_PRIOR] = key_page_up;
keycode_lookup_table[VK_NEXT] = key_page_down;
keycode_lookup_table[VK_ESCAPE] = key_esc;
keycode_lookup_table[VK_F1] = key_f1;
keycode_lookup_table[VK_F2] = key_f2;
keycode_lookup_table[VK_F3] = key_f3;
keycode_lookup_table[VK_F4] = key_f4;
keycode_lookup_table[VK_F5] = key_f5;
keycode_lookup_table[VK_F6] = key_f6;
keycode_lookup_table[VK_F7] = key_f7;
keycode_lookup_table[VK_F8] = key_f8;
keycode_lookup_table[VK_F9] = key_f9;
keycode_lookup_table[VK_F10] = key_f10;
keycode_lookup_table[VK_F11] = key_f11;
keycode_lookup_table[VK_F12] = key_f12;
keycode_lookup_table[VK_F13] = key_f13;
keycode_lookup_table[VK_F14] = key_f14;
keycode_lookup_table[VK_F15] = key_f15;
keycode_lookup_table[VK_F16] = key_f16;
}
internal void
Win32RedrawScreen(HDC hdc){
launch_rendering(&win32vars.target);
glFlush();
SwapBuffers(hdc);
}
internal void
Win32Resize(i32 width, i32 height){
if (width > 0 && height > 0){
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, height, 0, -1, 1);
glScissor(0, 0, width, height);
win32vars.target.width = width;
win32vars.target.height = height;
}
}
internal void*
win32_load_gl_always(char *name, HMODULE module){
void *p = (void *)wglGetProcAddress(name), *r = 0;
if(p == 0 ||
(p == (void*)0x1) || (p == (void*)0x2) || (p == (void*)0x3) ||
(p == (void*)-1) ){
r = (void *)GetProcAddress(module, name);
}
else{
r = p;
}
return(r);
}
internal void CALL_CONVENTION
OpenGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char *message, const void *userParam){
OutputDebugStringA(message);
OutputDebugStringA("\n");
}
internal void
Win32InitGL(){
// GL context initialization
{
PIXELFORMATDESCRIPTOR format;
format.nSize = sizeof(format);
format.nVersion = 1;
format.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
format.iPixelType = PFD_TYPE_RGBA;
format.cColorBits = 32;
format.cRedBits = 0;
format.cRedShift = 0;
format.cGreenBits = 0;
format.cGreenShift = 0;
format.cBlueBits = 0;
format.cBlueShift = 0;
format.cAlphaBits = 0;
format.cAlphaShift = 0;
format.cAccumBits = 0;
format.cAccumRedBits = 0;
format.cAccumGreenBits = 0;
format.cAccumBlueBits = 0;
format.cAccumAlphaBits = 0;
format.cDepthBits = 24;
format.cStencilBits = 8;
format.cAuxBuffers = 0;
format.iLayerType = PFD_MAIN_PLANE;
format.bReserved = 0;
format.dwLayerMask = 0;
format.dwVisibleMask = 0;
format.dwDamageMask = 0;
HDC dc = GetDC(win32vars.window_handle);
Assert(dc);
int format_id = ChoosePixelFormat(dc, &format);
Assert(format_id != 0);
BOOL success = SetPixelFormat(dc, format_id, &format);
Assert(success == TRUE); AllowLocal(success);
HGLRC glcontext = wglCreateContext(dc);
wglMakeCurrent(dc, glcontext);
HMODULE module = LoadLibraryA("opengl32.dll");
AllowLocal(module);
wglCreateContextAttribsARB_Function *wglCreateContextAttribsARB = 0;
wglCreateContextAttribsARB = (wglCreateContextAttribsARB_Function*)
win32_load_gl_always("wglCreateContextAttribsARB", module);
wglChoosePixelFormatARB_Function *wglChoosePixelFormatARB = 0;
wglChoosePixelFormatARB = (wglChoosePixelFormatARB_Function*)
win32_load_gl_always("wglChoosePixelFormatARB", module);
if (wglCreateContextAttribsARB != 0 && wglChoosePixelFormatARB != 0){
const int choosePixel_attribList[] =
{
WGL_DRAW_TO_WINDOW_ARB, TRUE,
WGL_SUPPORT_OPENGL_ARB, TRUE,
//WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0,
};
i32 extended_format_id = 0;
u32 num_formats = 0;
BOOL result = wglChoosePixelFormatARB(dc, choosePixel_attribList, 0, 1, &extended_format_id, &num_formats);
if (result != 0 && num_formats > 0){
const int createContext_attribList[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
0
};
if (extended_format_id == format_id){
HGLRC extended_context = wglCreateContextAttribsARB(dc, 0, createContext_attribList);
if (extended_context){
wglMakeCurrent(dc, extended_context);
wglDeleteContext(glcontext);
glcontext = extended_context;
}
}
}
}
#if (defined(BUILD_X64) && 1) || (defined(BUILD_X86) && 0)
#if FRED_INTERNAL
// NOTE(casey): This slows down GL but puts error messages to
// the debug console immediately whenever you do something wrong
glDebugMessageCallback_type *glDebugMessageCallback =
(glDebugMessageCallback_type *)win32_load_gl_always("glDebugMessageCallback", module);
glDebugMessageControl_type *glDebugMessageControl =
(glDebugMessageControl_type *)win32_load_gl_always("glDebugMessageControl", module);
if(glDebugMessageCallback != 0 && glDebugMessageControl != 0)
{
glDebugMessageCallback(OpenGLDebugCallback, 0);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
#endif
#endif
ReleaseDC(win32vars.window_handle, dc);
}
glEnable(GL_TEXTURE_2D);
glEnable(GL_SCISSOR_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
internal void
Win32SetCursorFromUpdate(Application_Mouse_Cursor cursor){
switch (cursor){
case APP_MOUSE_CURSOR_ARROW:
SetCursor(win32vars.cursor_arrow); break;
case APP_MOUSE_CURSOR_IBEAM:
SetCursor(win32vars.cursor_ibeam); break;
case APP_MOUSE_CURSOR_LEFTRIGHT:
SetCursor(win32vars.cursor_leftright); break;
case APP_MOUSE_CURSOR_UPDOWN:
SetCursor(win32vars.cursor_updown); break;
}
}
internal u64
Win32HighResolutionTime(){
u64 result = 0;
LARGE_INTEGER t;
if (QueryPerformanceCounter(&t)){
result = (u64)t.QuadPart / win32vars.count_per_usecond;
}
return(result);
}
internal LRESULT
Win32Callback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
LRESULT result = 0;
switch (uMsg){
case WM_MENUCHAR:break;
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
{
switch (wParam){
case VK_CONTROL:case VK_LCONTROL:case VK_RCONTROL:
case VK_MENU:case VK_LMENU:case VK_RMENU:
case VK_SHIFT:case VK_LSHIFT:case VK_RSHIFT:
{
Control_Keys *controls = &win32vars.input_chunk.pers.controls;
b8 *control_keys = win32vars.input_chunk.pers.control_keys;
b8 down = ((lParam & Bit_31)?(0):(1));
b8 is_right = ((lParam & Bit_24)?(1):(0));
if (wParam != 255){
switch (wParam){
case VK_SHIFT:
{
control_keys[MDFR_SHIFT_INDEX] = down;
}break;
case VK_CONTROL:
{
if (is_right) controls->r_ctrl = down;
else controls->l_ctrl = down;
}break;
case VK_MENU:
{
if (is_right) controls->r_alt = down;
else controls->l_alt = down;
}break;
}
b8 ctrl = (controls->r_ctrl || (controls->l_ctrl && !controls->r_alt));
b8 alt = (controls->l_alt || (controls->r_alt && !controls->l_ctrl));
if (win32vars.lctrl_lalt_is_altgr){
if (controls->l_alt && controls->l_ctrl){
ctrl = 0;
alt = 0;
}
}
control_keys[MDFR_CONTROL_INDEX] = ctrl;
control_keys[MDFR_ALT_INDEX] = alt;
}
}break;
default:
{
b8 current_state = ((lParam & Bit_31)?(0):(1));
if (current_state){
Key_Code key = keycode_lookup_table[(u8)wParam];
if (key != 0){
i32 *count = &win32vars.input_chunk.trans.key_data.count;
Key_Event_Data *data = win32vars.input_chunk.trans.key_data.keys;
b8 *control_keys = win32vars.input_chunk.pers.control_keys;
i32 control_keys_size = sizeof(win32vars.input_chunk.pers.control_keys);
Assert(*count < KEY_INPUT_BUFFER_SIZE);
data[*count].character = 0;
data[*count].character_no_caps_lock = 0;
data[*count].keycode = key;
memcpy(data[*count].modifiers, control_keys, control_keys_size);
++(*count);
win32vars.got_useful_event = 1;
}
}
}break;
}/* switch */
}break;
case WM_CHAR: case WM_SYSCHAR: case WM_UNICHAR:
{
u16 character = (u16)wParam;
if (character == '\r'){
character = '\n';
}
else if (character == '\t'){
character = '\t';
}
else if (character < 32 || character == 127){
break;
}
u16 character_no_caps_lock = character;
i32 *count = &win32vars.input_chunk.trans.key_data.count;
Key_Event_Data *data = win32vars.input_chunk.trans.key_data.keys;
b8 *control_keys = win32vars.input_chunk.pers.control_keys;
i32 control_keys_size = sizeof(win32vars.input_chunk.pers.control_keys);
BYTE state[256];
GetKeyboardState(state);
if (state[VK_CAPITAL]){
if (character_no_caps_lock >= 'a' && character_no_caps_lock <= 'z'){
character_no_caps_lock -= (u8)('a' - 'A');
}
else if (character_no_caps_lock >= 'A' && character_no_caps_lock <= 'Z'){
character_no_caps_lock += (u8)('a' - 'A');
}
}
Assert(*count < KEY_INPUT_BUFFER_SIZE);
data[*count].character = character;
data[*count].character_no_caps_lock = character_no_caps_lock;
data[*count].keycode = character_no_caps_lock;
memcpy(data[*count].modifiers, control_keys, control_keys_size);
++(*count);
win32vars.got_useful_event = 1;
}break;
case WM_MOUSEMOVE:
{
i32 new_x = LOWORD(lParam);
i32 new_y = HIWORD(lParam);
if (new_x != win32vars.input_chunk.pers.mouse_x || new_y != win32vars.input_chunk.pers.mouse_y){
win32vars.input_chunk.pers.mouse_x = new_x;
win32vars.input_chunk.pers.mouse_y = new_y;
win32vars.got_useful_event = 1;
}
}break;
case WM_MOUSEWHEEL:
{
win32vars.got_useful_event = 1;
Font_ID rotation = GET_WHEEL_DELTA_WPARAM(wParam);
if (rotation > 0){
win32vars.input_chunk.trans.mouse_wheel = 1;
}
else{
win32vars.input_chunk.trans.mouse_wheel = -1;
}
}break;
case WM_LBUTTONDOWN:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.trans.mouse_l_press = 1;
win32vars.input_chunk.pers.mouse_l = 1;
}break;
case WM_RBUTTONDOWN:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.trans.mouse_r_press = 1;
win32vars.input_chunk.pers.mouse_r = 1;
}break;
case WM_LBUTTONUP:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.trans.mouse_l_release = 1;
win32vars.input_chunk.pers.mouse_l = 0;
}break;
case WM_RBUTTONUP:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.trans.mouse_r_release = 1;
win32vars.input_chunk.pers.mouse_r = 0;
}break;
case WM_KILLFOCUS:
case WM_SETFOCUS:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.pers.mouse_l = 0;
win32vars.input_chunk.pers.mouse_r = 0;
for (i32 i = 0; i < MDFR_INDEX_COUNT; ++i){
win32vars.input_chunk.pers.control_keys[i] = 0;
}
win32vars.input_chunk.pers.controls = null_control_keys;
}break;
case WM_SIZE:
{
win32vars.got_useful_event = 1;
i32 new_width = LOWORD(lParam);
i32 new_height = HIWORD(lParam);
Win32Resize(new_width, new_height);
}break;
case WM_PAINT:
{
win32vars.got_useful_event = 1;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Win32RedrawScreen(hdc);
EndPaint(hwnd, &ps);
}break;
case WM_CLOSE:
case WM_DESTROY:
{
win32vars.got_useful_event = 1;
win32vars.input_chunk.trans.trying_to_kill = 1;
}break;
case WM_4coder_ANIMATE:
{
win32vars.got_useful_event = 1;
}break;
case WM_CANCELMODE:
{
result = DefWindowProc(hwnd, uMsg, wParam, lParam);
}break;
default:
{
result = DefWindowProc(hwnd, uMsg, wParam, lParam);
}break;
}
return(result);
}
int CALL_CONVENTION
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
i32 argc = __argc;
char **argv = __argv;
memset(&win32vars, 0, sizeof(win32vars));
win32vars.GlobalWindowPosition.length = sizeof(win32vars.GlobalWindowPosition);
//
// Threads and Coroutines
//
for (i32 i = 0; i < LOCK_COUNT; ++i){
InitializeCriticalSection(&win32vars.locks[i]);
}
for (i32 i = 0; i < CV_COUNT; ++i){
InitializeConditionVariable(&win32vars.condition_vars[i]);
}
Thread_Context background[4];
memset(background, 0, sizeof(background));
win32vars.groups[BACKGROUND_THREADS].threads = background;
win32vars.groups[BACKGROUND_THREADS].count = ArrayCount(background);
win32vars.groups[BACKGROUND_THREADS].cancel_lock0 = CANCEL_LOCK0;
win32vars.groups[BACKGROUND_THREADS].cancel_cv0 = CANCEL_CV0;
Thread_Memory thread_memory[ArrayCount(background)];
win32vars.thread_memory = thread_memory;
win32vars.queues[BACKGROUND_THREADS].semaphore =
Win32Handle(CreateSemaphore(0, 0,
win32vars.groups[BACKGROUND_THREADS].count,
0));
u32 creation_flag = 0;
for (i32 i = 0; i < win32vars.groups[BACKGROUND_THREADS].count; ++i){
Thread_Context *thread = win32vars.groups[BACKGROUND_THREADS].threads + i;
thread->id = i + 1;
thread->group_id = BACKGROUND_THREADS;
Thread_Memory *memory = win32vars.thread_memory + i;
*memory = thread_memory_zero();
memory->id = thread->id;
thread->queue = &win32vars.queues[BACKGROUND_THREADS];
thread->handle = CreateThread(0, 0, JobThreadProc, thread, creation_flag, (LPDWORD)&thread->windows_id);
}
initialize_unbounded_queue(&win32vars.groups[BACKGROUND_THREADS].queue);
ConvertThreadToFiber(0);
win32vars.coroutine_free = win32vars.coroutine_data;
for (i32 i = 0; i+1 < ArrayCount(win32vars.coroutine_data); ++i){
win32vars.coroutine_data[i].next = win32vars.coroutine_data + i + 1;
}
//
// Memory Initialization
//
LPVOID base;
#if FRED_INTERNAL
#if defined(BUILD_X64)
base = (LPVOID)TB(1);
#elif defined(BUILD_X86)
base = (LPVOID)MB(96);
#endif
#else
base = (LPVOID)0;
#endif
memory_vars.vars_memory_size = MB(2);
memory_vars.vars_memory = VirtualAlloc(base, memory_vars.vars_memory_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#if FRED_INTERNAL
#if defined(BUILD_X64)
base = (LPVOID)TB(2);
#elif defined(BUILD_X86)
base = (LPVOID)MB(98);
#endif
#else
base = (LPVOID)0;
#endif
memory_vars.target_memory_size = MB(512);
memory_vars.target_memory = VirtualAlloc(base, memory_vars.target_memory_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
base = (LPVOID)0;
memory_vars.user_memory_size = MB(2);
memory_vars.user_memory = VirtualAlloc(base, memory_vars.target_memory_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
win32vars.target.max = MB(1);
win32vars.target.push_buffer = (char*)system_get_memory(win32vars.target.max);
if (!memory_vars.vars_memory || !memory_vars.target_memory || !memory_vars.user_memory || !win32vars.target.push_buffer){
exit(1);
}
//
// System and Application Layer Linkage
//
if (!Win32LoadAppCode()){
exit(1);
}
Win32LoadSystemCode();
Win32LoadRenderCode();
//
// Shared Systems Init
//
init_shared_vars();
//
// Read Command Line
//
DWORD required = (GetCurrentDirectory(0, 0)*4) + 1;
char *current_directory_mem = (char*)system_get_memory(required);
DWORD written = GetCurrentDirectory(required, current_directory_mem);
String current_directory = make_string_cap(current_directory_mem, written, required);
terminate_with_null(&current_directory);
replace_char(&current_directory, '\\', '/');
Command_Line_Parameters clparams;
clparams.argv = argv;
clparams.argc = argc;
char **files = 0;
i32 *file_count = 0;
win32vars.app.read_command_line(&win32vars.system, &memory_vars, current_directory, &win32vars.settings, &files, &file_count, clparams);
sysshared_filter_real_files(files, file_count);
//
// Custom Layer Linkage
//
#if defined(FRED_SUPER)
char *custom_file_default = "custom_4coder.dll";
char *custom_file = 0;
if (win32vars.settings.custom_dll) custom_file = win32vars.settings.custom_dll;
else custom_file = custom_file_default;
win32vars.custom = LoadLibraryA(custom_file);
if (!win32vars.custom && custom_file != custom_file_default){
if (!win32vars.settings.custom_dll_is_strict){
win32vars.custom = LoadLibraryA(custom_file_default);
}
}
if (win32vars.custom){
win32vars.custom_api.get_alpha_4coder_version = (_Get_Version_Function*)
GetProcAddress(win32vars.custom, "get_alpha_4coder_version");
if (win32vars.custom_api.get_alpha_4coder_version == 0 ||
win32vars.custom_api.get_alpha_4coder_version(MAJOR, MINOR, PATCH) == 0){
MessageBox(0,"Error: The application and custom version numbers don't match.\n", "Error",0);
exit(1);
}
win32vars.custom_api.get_bindings = (Get_Binding_Data_Function*)
GetProcAddress(win32vars.custom, "get_bindings");
}
if (win32vars.custom_api.get_bindings == 0){
MessageBox(0,"Error: The custom dll is missing.\n", "Error",0);
exit(1);
}
#else
win32vars.custom_api.get_bindings = (Get_Binding_Data_Function*)get_bindings;
#endif
//
// Window and GL Initialization
//
WNDCLASS window_class = {};
window_class.style = CS_HREDRAW|CS_VREDRAW;
window_class.lpfnWndProc = (WNDPROC)(Win32Callback);
window_class.hInstance = hInstance;
window_class.lpszClassName = "4coder-win32-wndclass";
window_class.hIcon = LoadIcon(hInstance, "main");
if (!RegisterClass(&window_class)){
exit(1);
}
RECT window_rect = {};
if (win32vars.settings.set_window_size){
window_rect.right = win32vars.settings.window_w;
window_rect.bottom = win32vars.settings.window_h;
}
else{
window_rect.right = 800;
window_rect.bottom = 600;
}
if (!AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, false)){
// TODO(allen): non-fatal diagnostics
}
#define WINDOW_NAME "4coder-window: " VERSION
i32 window_x = CW_USEDEFAULT;
i32 window_y = CW_USEDEFAULT;
if (win32vars.settings.set_window_pos){
window_x = win32vars.settings.window_x;
window_y = win32vars.settings.window_y;
}
i32 window_style = WS_OVERLAPPEDWINDOW;
if (!win32vars.settings.fullscreen_window && win32vars.settings.maximize_window){
window_style |= WS_MAXIMIZE;
}
win32vars.window_handle = CreateWindow(window_class.lpszClassName, WINDOW_NAME, window_style, window_x, window_y, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, 0, 0, hInstance, 0);
if (win32vars.window_handle == 0){
exit(1);
}
HDC hdc = GetDC(win32vars.window_handle);
#if SUPPORT_DPI
// TODO(allen): not Windows XP compatible, how do I handle that?
SetProcessDPIAware();
win32vars.dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
win32vars.dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
#else
win32vars.dpi_x = 1;
win32vars.dpi_y = 1;
#endif
GetClientRect(win32vars.window_handle, &window_rect);
ReleaseDC(win32vars.window_handle, hdc);
Win32InitGL();
Win32Resize(window_rect.right - window_rect.left, window_rect.bottom - window_rect.top);
//
// Misc System Initializations
//
win32vars.clipboard_sequence = GetClipboardSequenceNumber();
if (win32vars.clipboard_sequence == 0){
system_post_clipboard(make_lit_string(""));
win32vars.clipboard_sequence = GetClipboardSequenceNumber();
win32vars.next_clipboard_is_self = 0;
if (win32vars.clipboard_sequence == 0){
OutputDebugStringA("Failure while initializing clipboard\n");
}
}
else{
Win32ReadClipboardContents();
}
Win32KeycodeInit();
win32vars.cursor_ibeam = LoadCursor(NULL, IDC_IBEAM);
win32vars.cursor_arrow = LoadCursor(NULL, IDC_ARROW);
win32vars.cursor_leftright = LoadCursor(NULL, IDC_SIZEWE);
win32vars.cursor_updown = LoadCursor(NULL, IDC_SIZENS);
LARGE_INTEGER f;
if (QueryPerformanceFrequency(&f)){
win32vars.count_per_usecond = (u64)f.QuadPart / 1000000;
}
else{
// NOTE(allen): Just guess.
win32vars.count_per_usecond = 1;
}
//
// Main Loop
//
win32vars.app.init(&win32vars.system, &win32vars.target, &memory_vars, win32vars.clipboard_contents, current_directory, win32vars.custom_api);
system_free_memory(current_directory.str);
b32 keep_playing = 1;
win32vars.first = 1;
timeBeginPeriod(1);
if (win32vars.settings.fullscreen_window){
Win32ToggleFullscreen();
}
SetForegroundWindow(win32vars.window_handle);
SetActiveWindow(win32vars.window_handle);
ShowWindow(win32vars.window_handle, SW_SHOW);
u64 timer_start = Win32HighResolutionTime();
system_acquire_lock(FRAME_LOCK);
MSG msg;
for (;keep_playing;){
// TODO(allen): Find a good way to wait on a pipe
// without interfering with the reading process
// Looks like we can ReadFile with a size of zero
// in an IOCP for this effect.
// NOTE(allen): When we're in stream mode we don't have
// double buffering so we need to move ahead and call
// the first step right away so it will render into the
// window. With double buffering this is not an issue
// for reasons I cannot at all comprehend.
if (!(win32vars.first && win32vars.settings.stream_mode)){
system_release_lock(FRAME_LOCK);
if (win32vars.running_cli == 0){
win32vars.got_useful_event = false;
}
b32 get_more_messages = true;
do{
if (win32vars.got_useful_event == 0){
get_more_messages = GetMessage(&msg, 0, 0, 0);
}
else{
get_more_messages = PeekMessage(&msg, 0, 0, 0, 1);
}
if (get_more_messages){
if (msg.message == WM_QUIT){
keep_playing = 0;
}else{
b32 treat_normally = true;
if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN){
switch (msg.wParam){
case VK_CONTROL:case VK_LCONTROL:case VK_RCONTROL:
case VK_MENU:case VK_LMENU:case VK_RMENU:
case VK_SHIFT:case VK_LSHIFT:case VK_RSHIFT:break;
default: treat_normally = false; break;
}
}
if (treat_normally){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
Control_Keys *controls = &win32vars.input_chunk.pers.controls;
b8 ctrl = (controls->r_ctrl || (controls->l_ctrl && !controls->r_alt));
b8 alt = (controls->l_alt || (controls->r_alt && !controls->l_ctrl));
if (win32vars.lctrl_lalt_is_altgr){
if (controls->l_alt && controls->l_ctrl){
ctrl = 0;
alt = 0;
}
}
BYTE ctrl_state = 0, alt_state = 0;
BYTE state[256];
if (ctrl || alt){
GetKeyboardState(state);
if (ctrl){
ctrl_state = state[VK_CONTROL];
state[VK_CONTROL] = 0;
}
if (alt){
alt_state = state[VK_MENU];
state[VK_MENU] = 0;
}
SetKeyboardState(state);
TranslateMessage(&msg);
DispatchMessage(&msg);
if (ctrl){
state[VK_CONTROL] = ctrl_state;
}
if (alt){
state[VK_MENU] = alt_state;
}
SetKeyboardState(state);
}
else{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
}while(get_more_messages);
system_acquire_lock(FRAME_LOCK);
}
POINT mouse_point;
if (GetCursorPos(&mouse_point) &&
ScreenToClient(win32vars.window_handle, &mouse_point)){
i32_Rect screen;
screen.x0 = 0;
screen.y0 = 0;
screen.x1 = win32vars.target.width;
screen.y1 = win32vars.target.height;
i32 mx = mouse_point.x;
i32 my = mouse_point.y;
b32 is_hit = false;
if (mx >= screen.x0 && mx < screen.x1 && my >= screen.y0 && my < screen.y1){
is_hit = true;
}
if (!is_hit){
win32vars.input_chunk.trans.out_of_window = 1;
}
win32vars.input_chunk.pers.mouse_x = mouse_point.x;
win32vars.input_chunk.pers.mouse_y = mouse_point.y;
}
else{
win32vars.input_chunk.trans.out_of_window = 1;
}
Win32_Input_Chunk input_chunk = win32vars.input_chunk;
win32vars.input_chunk.trans = null_input_chunk_transient;
input_chunk.pers.control_keys[MDFR_CAPS_INDEX] = GetKeyState(VK_CAPITAL) & 0x1;
win32vars.clipboard_contents = null_string;
if (win32vars.clipboard_sequence != 0){
DWORD new_number = GetClipboardSequenceNumber();
if (new_number != win32vars.clipboard_sequence){
win32vars.clipboard_sequence = new_number;
if (win32vars.next_clipboard_is_self){
win32vars.next_clipboard_is_self = 0;
}
else{
Win32ReadClipboardContents();
}
}
}
Application_Step_Input input = {0};
input.first_step = win32vars.first;
// NOTE(allen): The expected dt given the frame limit in seconds.
input.dt = frame_useconds / 1000000.f;
input.keys = input_chunk.trans.key_data;
input.mouse.out_of_window = input_chunk.trans.out_of_window;
input.mouse.l = input_chunk.pers.mouse_l;
input.mouse.press_l = input_chunk.trans.mouse_l_press;
input.mouse.release_l = input_chunk.trans.mouse_l_release;
input.mouse.r = input_chunk.pers.mouse_r;
input.mouse.press_r = input_chunk.trans.mouse_r_press;
input.mouse.release_r = input_chunk.trans.mouse_r_release;
input.mouse.wheel = input_chunk.trans.mouse_wheel;
input.mouse.x = input_chunk.pers.mouse_x;
input.mouse.y = input_chunk.pers.mouse_y;
input.clipboard = win32vars.clipboard_contents;
Application_Step_Result result = {(Application_Mouse_Cursor)0};
result.mouse_cursor_type = APP_MOUSE_CURSOR_DEFAULT;
result.lctrl_lalt_is_altgr = win32vars.lctrl_lalt_is_altgr;
result.trying_to_kill = input_chunk.trans.trying_to_kill;
result.perform_kill = 0;
if (win32vars.send_exit_signal){
result.trying_to_kill = 1;
win32vars.send_exit_signal = 0;
}
win32vars.app.step(&win32vars.system, &win32vars.target, &memory_vars, &input, &result, clparams);
if (result.perform_kill){
keep_playing = 0;
}
if (win32vars.do_toggle){
Win32ToggleFullscreen();
win32vars.do_toggle = 0;
}
Win32SetCursorFromUpdate(result.mouse_cursor_type);
win32vars.lctrl_lalt_is_altgr = result.lctrl_lalt_is_altgr;
HDC hdc = GetDC(win32vars.window_handle);
Win32RedrawScreen(hdc);
ReleaseDC(win32vars.window_handle, hdc);
win32vars.first = 0;
if (result.animating){
PostMessage(win32vars.window_handle, WM_4coder_ANIMATE, 0, 0);
}
flush_thread_group(BACKGROUND_THREADS);
u64 timer_end = Win32HighResolutionTime();
u64 end_target = timer_start + frame_useconds;
system_release_lock(FRAME_LOCK);
while (timer_end < end_target){
DWORD samount = (DWORD)((end_target - timer_end) / 1000);
if (samount > 0) Sleep(samount);
timer_end = Win32HighResolutionTime();
}
system_acquire_lock(FRAME_LOCK);
timer_start = Win32HighResolutionTime();
}
return(0);
}
#include "font/4coder_font_static_functions.cpp"
#if 0
// NOTE(allen): In case I want to switch back to a console
// application at some point.
int main(int argc, char **argv){
HINSTANCE hInstance = GetModuleHandle(0);
}
#endif
// BOTTOM