diff --git a/doom.c b/doom.c index f63defa..a0f72a2 100644 --- a/doom.c +++ b/doom.c @@ -33,8 +33,6 @@ #include "linuxdoom-1.10/info.c" #include "linuxdoom-1.10/i_main.c" #include "linuxdoom-1.10/i_sound.c" -#include "linuxdoom-1.10/i_system.c" -#include "linuxdoom-1.10/i_video.c" #include "linuxdoom-1.10/m_argv.c" #include "linuxdoom-1.10/m_bbox.c" #include "linuxdoom-1.10/m_cheat.c" @@ -97,3 +95,40 @@ #undef strupr #include "linuxdoom-1.10/i_net.c" + +#undef MAXCHAR +#undef MAXSHORT +#undef MAXINT +#undef MAXLONG +#undef MINCHAR +#undef MINSHORT +#undef MININT +#undef MINLONG + +#define APP_WINDOWS +#define APP_LOG( ctx, level, message ) +#define boolean HACK_TO_MAKE_BOOLEAN_NOT_BE_DEFINED +#define APP_IMPLEMENTATION +#include "libs_win32/app.h" +#undef APP_IMPLEMENTATION + +#define FRAMETIMER_IMPLEMENTATION +#include "libs_win32/frametimer.h" + +#define CRTEMU_IMPLEMENTATION +#include "libs_win32/crtemu.h" + +#define THREAD_IMPLEMENTATION +#if defined( __TINYC__ ) + typedef struct _RTL_CONDITION_VARIABLE { PVOID Ptr; } RTL_CONDITION_VARIABLE, *PRTL_CONDITION_VARIABLE; + typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE; + static VOID (*InitializeConditionVariable)( PCONDITION_VARIABLE ); + static VOID (*WakeConditionVariable)( PCONDITION_VARIABLE ); + static BOOL (*SleepConditionVariableCS)( PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD ); +#endif +#include "libs_win32/thread.h" +#undef THREAD_IMPLEMENTATION +#undef boolean + +#include "linuxdoom-1.10/i_video.c" +#include "linuxdoom-1.10/i_system.c" \ No newline at end of file diff --git a/libs_win32/app.h b/libs_win32/app.h new file mode 100644 index 0000000..c83a523 --- /dev/null +++ b/libs_win32/app.h @@ -0,0 +1,4816 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +app.h - v0.5 - Small cross-platform base framework for graphical apps. + +Do this: + #define APP_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + + +#ifndef app_h +#define app_h + +#ifndef APP_S16 + #define APP_S16 short +#endif +#ifndef APP_U32 + #define APP_U32 unsigned int +#endif +#ifndef APP_U64 + #define APP_U64 unsigned long long +#endif + + +typedef struct app_t app_t; + +int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ); + +typedef enum app_state_t { APP_STATE_EXIT_REQUESTED, APP_STATE_NORMAL, } app_state_t; +app_state_t app_yield( app_t* app ); +void app_cancel_exit( app_t* app ); + +void app_title( app_t* app, char const* title ); + +char const* app_cmdline( app_t* app ); +char const* app_filename( app_t* app ); +char const* app_userdata( app_t* app ); +char const* app_appdata( app_t* app ); + +APP_U64 app_time_count( app_t* app ); +APP_U64 app_time_freq( app_t* app ); + +typedef enum app_log_level_t { APP_LOG_LEVEL_INFO, APP_LOG_LEVEL_WARNING, APP_LOG_LEVEL_ERROR, } app_log_level_t; +void app_log( app_t* app, app_log_level_t level, char const* message ); +void app_fatal_error( app_t* app, char const* message ); + +void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ); +void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ); + +void app_pointer_pos( app_t* app, int x, int y ); +int app_pointer_x( app_t* app ); +int app_pointer_y( app_t* app ); + +void app_pointer_limit( app_t* app, int x, int y, int width, int height ); +void app_pointer_limit_off( app_t* app ); + +typedef enum app_interpolation_t { APP_INTERPOLATION_NONE, APP_INTERPOLATION_LINEAR, } app_interpolation_t; +void app_interpolation( app_t* app, app_interpolation_t interpolation ); + +typedef enum app_screenmode_t { APP_SCREENMODE_WINDOW, APP_SCREENMODE_FULLSCREEN, } app_screenmode_t; +void app_screenmode( app_t* app, app_screenmode_t screenmode ); + +void app_window_size( app_t* app, int width, int height ); +int app_window_width( app_t* app ); +int app_window_height( app_t* app ); + +void app_window_pos( app_t* app, int x, int y ); +int app_window_x( app_t* app ); +int app_window_y( app_t* app ); + +typedef struct app_display_t + { + char id[ 64 ]; + int x; + int y; + int width; + int height; + } app_display_t ; + +typedef struct app_displays_t { app_display_t* displays; int count; } app_displays_t; +app_displays_t app_displays( app_t* app ); + +void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ); + +void app_sound( app_t* app, int sample_pairs_count, + void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ), void* user_data ); +void app_sound_volume( app_t* app, float volume ); + +typedef enum app_key_t { APP_KEY_INVALID, APP_KEY_LBUTTON, APP_KEY_RBUTTON, APP_KEY_CANCEL, APP_KEY_MBUTTON, + APP_KEY_XBUTTON1, APP_KEY_XBUTTON2, APP_KEY_BACK, APP_KEY_TAB, APP_KEY_CLEAR, APP_KEY_RETURN, APP_KEY_SHIFT, + APP_KEY_CONTROL, APP_KEY_MENU, APP_KEY_PAUSE, APP_KEY_CAPITAL, APP_KEY_KANA, APP_KEY_HANGUL = APP_KEY_KANA, + APP_KEY_JUNJA, APP_KEY_FINAL, APP_KEY_HANJA, APP_KEY_KANJI = APP_KEY_HANJA, APP_KEY_ESCAPE, APP_KEY_CONVERT, + APP_KEY_NONCONVERT, APP_KEY_ACCEPT, APP_KEY_MODECHANGE, APP_KEY_SPACE, APP_KEY_PRIOR, APP_KEY_NEXT, APP_KEY_END, + APP_KEY_HOME, APP_KEY_LEFT, APP_KEY_UP, APP_KEY_RIGHT, APP_KEY_DOWN, APP_KEY_SELECT, APP_KEY_PRINT, APP_KEY_EXEC, + APP_KEY_SNAPSHOT, APP_KEY_INSERT, APP_KEY_DELETE, APP_KEY_HELP, APP_KEY_0, APP_KEY_1, APP_KEY_2, APP_KEY_3, + APP_KEY_4, APP_KEY_5, APP_KEY_6, APP_KEY_7, APP_KEY_8, APP_KEY_9, APP_KEY_A, APP_KEY_B, APP_KEY_C, APP_KEY_D, + APP_KEY_E, APP_KEY_F, APP_KEY_G, APP_KEY_H, APP_KEY_I, APP_KEY_J, APP_KEY_K, APP_KEY_L, APP_KEY_M, APP_KEY_N, + APP_KEY_O, APP_KEY_P, APP_KEY_Q, APP_KEY_R, APP_KEY_S, APP_KEY_T, APP_KEY_U, APP_KEY_V, APP_KEY_W, APP_KEY_X, + APP_KEY_Y, APP_KEY_Z, APP_KEY_LWIN, APP_KEY_RWIN, APP_KEY_APPS, APP_KEY_SLEEP, APP_KEY_NUMPAD0, APP_KEY_NUMPAD1, + APP_KEY_NUMPAD2, APP_KEY_NUMPAD3, APP_KEY_NUMPAD4, APP_KEY_NUMPAD5, APP_KEY_NUMPAD6, APP_KEY_NUMPAD7, + APP_KEY_NUMPAD8, APP_KEY_NUMPAD9, APP_KEY_MULTIPLY, APP_KEY_ADD, APP_KEY_SEPARATOR, APP_KEY_SUBTRACT, + APP_KEY_DECIMAL, APP_KEY_DIVIDE, APP_KEY_F1, APP_KEY_F2, APP_KEY_F3, APP_KEY_F4, APP_KEY_F5, APP_KEY_F6, APP_KEY_F7, + APP_KEY_F8, APP_KEY_F9, APP_KEY_F10, APP_KEY_F11, APP_KEY_F12, APP_KEY_F13, APP_KEY_F14, APP_KEY_F15, APP_KEY_F16, + APP_KEY_F17, APP_KEY_F18, APP_KEY_F19, APP_KEY_F20, APP_KEY_F21, APP_KEY_F22, APP_KEY_F23, APP_KEY_F24, + APP_KEY_NUMLOCK, APP_KEY_SCROLL, APP_KEY_LSHIFT, APP_KEY_RSHIFT, APP_KEY_LCONTROL, APP_KEY_RCONTROL, APP_KEY_LMENU, + APP_KEY_RMENU, APP_KEY_BROWSER_BACK, APP_KEY_BROWSER_FORWARD, APP_KEY_BROWSER_REFRESH, APP_KEY_BROWSER_STOP, + APP_KEY_BROWSER_SEARCH, APP_KEY_BROWSER_FAVORITES, APP_KEY_BROWSER_HOME, APP_KEY_VOLUME_MUTE, APP_KEY_VOLUME_DOWN, + APP_KEY_VOLUME_UP, APP_KEY_MEDIA_NEXT_TRACK, APP_KEY_MEDIA_PREV_TRACK, APP_KEY_MEDIA_STOP, APP_KEY_MEDIA_PLAY_PAUSE, + APP_KEY_LAUNCH_MAIL, APP_KEY_LAUNCH_MEDIA_SELECT, APP_KEY_LAUNCH_APP1, APP_KEY_LAUNCH_APP2, APP_KEY_OEM_1, + APP_KEY_OEM_PLUS, APP_KEY_OEM_COMMA, APP_KEY_OEM_MINUS, APP_KEY_OEM_PERIOD, APP_KEY_OEM_2, APP_KEY_OEM_3, + APP_KEY_OEM_4, APP_KEY_OEM_5, APP_KEY_OEM_6, APP_KEY_OEM_7, APP_KEY_OEM_8, APP_KEY_OEM_102, APP_KEY_PROCESSKEY, + APP_KEY_ATTN, APP_KEY_CRSEL, APP_KEY_EXSEL, APP_KEY_EREOF, APP_KEY_PLAY, APP_KEY_ZOOM, APP_KEY_NONAME, APP_KEY_PA1, + APP_KEY_OEM_CLEAR, APP_KEYCOUNT } app_key_t; + +typedef enum app_input_type_t { APP_INPUT_KEY_DOWN, APP_INPUT_KEY_UP, APP_INPUT_DOUBLE_CLICK, APP_INPUT_CHAR, + APP_INPUT_MOUSE_MOVE, APP_INPUT_MOUSE_DELTA, APP_INPUT_SCROLL_WHEEL, APP_INPUT_TABLET } app_input_type_t; + +typedef enum app_pressed_t { APP_NOT_PRESSED, APP_PRESSED, } app_pressed_t; + +typedef struct app_input_event_t + { + app_input_type_t type; + union data_t + { + app_key_t key; + char char_code; + struct { int x; int y; } mouse_pos; + struct { float x; float y; } mouse_delta; + float wheel_delta; + struct { int x; int y; float pressure; app_pressed_t tip; app_pressed_t lower; app_pressed_t upper; } tablet; + } data; + } app_input_event_t; + +typedef struct app_input_t { app_input_event_t* events; int count; } app_input_t; +app_input_t app_input( app_t* app ); + +void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ); +void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ); + +#endif /* app_h */ + + +/** + +app.h +===== + +Small cross-platform base framework for graphical apps. + + +Example +------- + +Here's a basic sample program which starts a windowed app and plots random pixels. + + #define APP_IMPLEMENTATION + #define APP_WINDOWS + #include "app.h" + + #include // for rand and __argc/__argv + #include // for memset + + int app_proc( app_t* app, void* user_data ) { + APP_U32 canvas[ 320 * 200 ]; // a place for us to draw stuff + memset( canvas, 0xC0, sizeof( canvas ) ); // clear to grey + app_screenmode( app, APP_SCREENMODE_WINDOW ); + + // keep running until the user close the window + while( app_yield( app ) != APP_STATE_EXIT_REQUESTED ) { + // plot a random pixel on the canvas + int x = rand() % 320; + int y = rand() % 200; + APP_U32 color = rand() | ( (APP_U32) rand() << 16 ); + canvas[ x + y * 320 ] = color; + + // display the canvas + app_present( app, canvas, 320, 200, 0xffffff, 0x000000 ); + } + return 0; + } + + int main( int argc, char** argv ) { + (void) argc, argv; + return app_run( app_proc, NULL, NULL, NULL, NULL ); + } + + // pass-through so the program will build with either /SUBSYSTEM:WINDOWS or /SUBSYSTEN:CONSOLE + extern "C" int __stdcall WinMain( struct HINSTANCE__*, struct HINSTANCE__*, char*, int ) { return main( __argc, __argv ); } + + + +API Documentation +----------------- + +app.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it, +you just include app.h to get the API declarations. To get the definitions, you must include app.h from *one* single +C or C++ file, and #define the symbol `APP_IMPLEMENTATION` before you do. + +As app.h is a cross platform library, you must also define which platform you are running on, like this for Windows: + + #define APP_IMPLEMENTATION + #define APP_WINDOWS + #include "app.h" + +Or like this for other platforms: + #define APP_IMPLEMENTATION + #define APP_SDL + #include "app.h" + +### Customization + +There are a few different things in app.h which are configurable by #defines. Most of the API use the `int` data type, +for integer values where the exact size is not important. However, for some functions, it specifically makes use of 16, +32 and 64 bit data types. These default to using `short`, `unsigned int` and `unsigned long long` by default, but can be +redefined by #defining APP_S16, APP_U32 and APP_U64 respectively, before including app.h. This is useful if you, for +example, use the types from `` in the rest of your program, and you want app.h to use compatible types. In +this case, you would include app.h using the following code: + + #define APP_S16 int16_t + #define APP_U32 uint32_t + #define APP_U64 uint64_t + #include "app.h" + +Note that when customizing the data types, you need to use the same definition in every place where you include app.h, +as they affect the declarations as well as the definitions. + +The rest of the customizations only affect the implementation, so will only need to be defined in the file where you +have the #define APP_IMPLEMENTATION. + + +#### Custom memory allocators + +Even though app.h attempts to minimize the memory use and number of allocations, it still needs to make *some* use of +dynamic allocation by calling `malloc`. Programs might want to keep track of allocations done, or use custom defined +pools to allocate memory from. app.h allows for specifying custom memory allocation functions for `malloc` and `free`. +This is done with the following code: + + #define APP_IMPLEMENTATION + #define APP_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) ) + #define APP_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) ) + #include "app.h" + +where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter +is an optional parameter of type `void*`. When `app_run` is called, you can pass in a `memctx` parameter, which can be a +pointer to anything you like, and which will be passed through as the `ctx` parameter to every APP_MALLOC/APP_FREE call. +For example, if you are doing memory tracking, you can pass a pointer to your tracking data as `memctx`, and in your +custom allocation/deallocation function, you can cast the `ctx` param back to the right type, and access the tracking +data. + +If no custom allocator is defined, app.h will default to `malloc` and `free` from the C runtime library. + + +#### Custom logging function + +There's a bunch of things being logged when app.h runs. It will log an informational entry with the date and time for +when the app is started and stopped, it will log warnings when non-essential initialization fails, and it will log +error messages when things go wrong. By default, logging is done by a simple printf to stdout. As some applications may +need a different behavior, such as writing out a log file, it is possible to override the default logging behavior +through defines like this: + + #define APP_IMPLEMENTATION + #define APP_LOG( ctx, level, message ) ( my_log_func( ctx, level, message ) ) + #include "app.h" + +where `my_log_func` is your own logging function. Just like for the memory allocators, the `ctx` parameter is optional, +and is just a `void*` value which is passed through. But in the case of logging, it will be passed through as the value +off the `logctx` parameter passed into `app_run`. The `level` parameter specifies the severity level of the logging, +and can be used to direct different types of messages to different logging systems, or filter out messages of certain +severity level, e.g. supressing informational messages. + + +#### Custom fatal error function + +As the app.h library works on the lowest level of your program, interfacing directly with the operating system, there +might occur errors which it can not recover from. In these cases, a *fatal error* will be reported. By default, when a +fatal error happens, app.h will print a message to stdout, show a messagebox to the user, and force exit the program. + +It is possible to change this behaviour using the following define: + + #define APP_IMPLEMENTATION + #define APP_FATAL_ERROR( ctx, message ) ( my_custom_fatal_error_func( ctx, message ) ) + #include "app.h" + +where `my_custom_fatal_error_func` is your own error reporting function. The `ctx` parameter fills the same purpose as +for the allocator and logging functions, but here it is the `fatalctx` parameter to `app_run` which is passed through. + + +app_run +------- + + int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ) + +Creates a new app instance, calls the given app_proc and waits for it to return. Then it destroys the app instance. + +* app_proc - function pointer to the user defined starting point of the app. The parameters to that function are: + app_t* a pointer to the app instance. This is an opaque type, and it is passed to all other functions in the API. + void* pointer to the user defined data that was passed as the `user_data` parameter to `app_run`. +* user_data - pointer to user defined data which will be passed through to app_proc. May be NULL. +* memctx - pointer to user defined data which will be passed through to custom APP_MALLOC/APP_FREE calls. May be NULL. +* logctx - pointer to user defined data to be passed through to custom APP_LOG calls. May be NULL. +* fatalctx - pointer to user defined data to be passed through to custom APP_FATAL_ERROR calls. May be NULL. + +When app_run is called, it will perform all the initialization needed by app.h, and create an `app_t*` instance. It will +then call the user-specified `app_proc`, and wait for it to return. The `app_t*` instance will be passed to `app_proc`, +and can be used to call other functions in the API. When returning from `app_proc`, a return value is specified, and +`app_run` will perform termination and cleanup, and destroy the `app_t*` instance, and then return the same value it got +from the `app_proc`. After `app_run` returns, the `app_t*` value is no longer valid for use in any API calls. + + +app_yield +--------- + + app_state_t app_yield( app_t* app ) + +Allows for app.h and the operating system to perform internal house keeping and updates. It should be called on each +iteration of your main loop. + +The return value can be either `APP_STATE_NORMAL` or `APP_STATE_EXIT_REQUESTED`. `APP_STATE_EXIT_REQUESTED` means that +the user have requested the app to terminate, e.g. by pressing the *close* button on the window, and the user defined +`app_proc` needs to handle this, by either returning (to signal that the app should terminate) or by calling +`app_cancel_exit` to ignore the request. A typical pattern is to display a message to the user to confirm that the app +should exit. In the case of `APP_STATE_NORMAL`, there is no need to do anything. + + +app_cancel_exit +--------------- + + void app_cancel_exit( app_t* app ) + +Used to reset the `APP_STATE_EXIT_REQUESTED` state. See `app_yield` for details. + + +app_title +--------- + + void app_title( app_t* app, char const* title ) + +Sets the name of the application, which is displayed in the task switcher and in the title bar of the window. + + +app_cmdline +----------- + + char const* app_cmdline( app_t* app ) + +Returns the command line string used to launch the executable. This can be parsed to get command line arguments. + + +app_filename +------------ + + char const* app_filename( app_t* app ) + +Returns the full filename and path of the executable. The first part of `app_cmdline` usually contains the name of the +executable, but not necessarily the full path, depending on how it was launched. `app_filename`, however, always returns +the full path. + + +app_userdata +------------ + + char const* app_userdata( app_t* app ) + +Returns the full path to a directory where a users personal files can be stored. Depending on the access rights of the +user, it may or may not be possible to write data to the same location as the executable, and instead it must be stored +in a specific area designated by the operating system. `app_userdata` returns the path to the root if that directory. +A typical use for this is to store the users savegame files, by creating a subfolder corresponding to your app, and save +the data there. + + +app_appdata +----------- + + char const* app_appdata( app_t* app ) + +Returns the full path to a directory where application specific files can be stored. Similar to the location returned by +`app_userdata`, but suitable for application data shared between users. Typical use for this is to store the result of +cached calculations or temporary files. + + +app_time_count +-------------- + + APP_U64 app_time_count( app_t* app ) + +Returns the current value of the high precision clock. The epoch is undefined, and the resolution can vary between +systems. Use `app_time_freq` to convert to seconds. Typical use is to make two calls to `app_time_count` and calculate +the difference, to measure the time elapsed between the two calls. + + +app_time_freq +------------- + + APP_U64 app_time_freq( app_t* app ) + +Returns the number of clock ticks per second of the high precision clock. An example use case could be: + + APP_U64 current_count = app_time_count( app ); + APP_U64 delta_count = current_count - previous_count; + double delta_time = ( (double) delta_count ) / ( (double) app_time_freq( app ) ); + previous_count = current_count; + +to measure the time between two iterations through your main loop. + + +app_log +------- + + void app_log( app_t* app, app_log_level_t level, char const* message ) + +app.h will do logging on certain events, e.q when the app starts and ends or when something goes wrong. As the logging +can be customized (see section on customization), it might be desirable for the program to do its own logging the same +way as app.h does it. By calling `app_log`, logging will be done the same way as it is done inside app.h, whether custom +logging or default logging is being used. + + +app_fatal_error +--------------- + + void app_fatal_error( app_t* app, char const* message ) + +Same as with app_log, but for reporting fatal errors, `app_fatal_error` will report an error the same way as is done +internally in app.h, whether custom or default fatal error reporting is being used. + + +app_pointer +----------- + + void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) + +Sets the appearence current mouse pointer. `app_pointer` is called with the following parameters: + +* width, height - the horizontal and vertical dimensions of the mouse pointer bitmap. +* pixels_abgr - width x height number of pixels making up the pointer bitmap, each pixel being a 32-bit unsigned integer + where the highest 8 bits are the alpha channel, and the following 8-bit groups are blue, green and red channels. +* hotspot_x, hotspot_y - offset into the bitmap of the pointer origin, the center point it will be drawn at. + + +app_pointer_default +------------------- + + void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ) + +Retrieves the width, height, pixel data and hotspot for the default mouse pointer. Useful for restoring the default +pointer after using `app_pointer`, or for doing software rendered pointers. Called with the following parameters: + +* width, height - pointers to integer values that are to receive the width and height of the pointer. May be NULL. +* pixels_abgr - width x height number of pixels to receive the pointer bitmap. May be NULL +* hotspot_x, hotspot_y - pointers to integer values that are to receive the hotspot coordinates. May be NULL. + +A typical pattern for calling `app_pointer_default` is to first call it with `pixels_abgr` as NULL, to query the bitmaps +dimensions, and then call it again after preparing a large enough memory area. + + +app_pointer_pos +--------------- + + void app_pointer_pos( app_t* app, int x, int y ) + +Set the position of the mouse pointer, in window coordinates. The function `app_coordinates_bitmap_to_window` can be +used to convert between the coordinate system of the currently displayed bitmap and that of the window. + + +app_pointer_limit +----------------- + + void app_pointer_limit( app_t* app, int x, int y, int width, int height ) + +Locks the mouse pointer movements to stay within the specified area, in window coordinates. The function +`app_coordinates_bitmap_to_window` can be used to convert between the coordinate system of the currently displayed +bitmap and that of the window. + + +app_pointer_limit_off +--------------------- + + void app_pointer_limit_off( app_t* app ) + +Turns of the mouse pointer movement restriction, allowing the pointer to be moved freely again. + + +app_interpolation +----------------- + + void app_interpolation( app_t* app, app_interpolation_t interpolation ) + +app.h supports two different modes of displaying a bitmap. When using `APP_INTERPOLATION_LINEAR`, the bitmap will be +drawn with bilinear interpolations, stretching it to fill the window (maintaining aspect ratio), giving it a smooth, if +somwhat blurry, look. With `APP_INTERPOLATION_NONE`, scaling will only be done on whole pixel ratios, using no +interpolation, which is particularly suitable to maintain the clean, precise look of pixel art. +`APP_INTERPOLATION_LINEAR` is the default setting. + + +app_screenmode +-------------- + + void app_screenmode( app_t* app, app_screenmode_t screenmode ) + +Switch between windowed mode and fullscreen mode. `APP_SCREENMODE_WINDOW` is used to select windowed mode, and +`APP_SCREENMODE_FULLSCREEN` is used to switch to fullscreen mode. `APP_SCREENMODE_FULLSCREEN` is the default. Note that +the app.h fullscreenmode is of the "borderless windowed" type, meaning that fullscreen mode just means that the window +is set to cover the entire screen, and its borders are hidden. It does not imply any exclusive locking of GPU resources. +When switching from windowed to fullscreen mode on a multi-display system, the app will go fullscreen on the display +that the window is currently on. + + +app_window_size +--------------- + + void app_window_size( app_t* app, int width, int height ) + +Sets the size of the window. If currently in `APP_SCREENMODE_FULLSCREEN` screen mode, the setting will not take effect +until switching to `APP_SCREENMODE_WINDOW`. `width` and `height` specifies the size of the windows client area, not +counting borders, title bar or decorations. + + +app_window_width/app_window_height +---------------------------------- + + int app_window_width( app_t* app ) + int app_window_height( app_t* app ) + +Returns the current dimensions of the window (which might have been resized by the user). Regardless of whether the app +is currently in fullscreen or windowed mode, `app_window_width` and `app_window_height` returns the dimension the window +*would* have in windowed mode. Width and height specifies the size of the windows client area, not counting borders, +title bar or decorations. + + +app_window_pos +-------------- + + void app_window_pos( app_t* app, int x, int y ) + +Sets the position of the top left corner of the window. If currently in `APP_SCREENMODE_FULLSCREEN` screen mode, the +setting will not take effect until switching to `APP_SCREENMODE_WINDOW`. + + +app_window_x/app_window_y +------------------------- + + int app_window_x( app_t* app ) + int app_window_y( app_t* app ) + +Returns the current position of the windows top left corner. Regardless of whether the app is currently in fullscreen or +windowed mode, `app_window_x` and `app_window_y` returns the position the window *would* have in windowed mode. + + +app_displays +------------ + + app_displays_t app_displays( app_t* app ) + +Returns a list of all displays connected to the system. For each display, the following fields are reported: +* id - a platform specific string used to identify the display. Useful for saving which display was in use. +* x, y - position of the top left corner of the display, relative to the primary dispay which is always at 0,0. +* width, height - size of the display, in pixels. + + +app_present +----------- + + void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) + +app.h provides a very minimal API for drawing - the only thing you can really do, is provide it with a bitmap for it to +display on the screen. It is then up to the rest of your program to implement code for drawing shapes or sprites onto +that bitmap. When all your drawing is done, you call `app_present` passing it the bitmap, and it will be displayed on +the screen. `app_present` takes the following parameters: +* pixels_xbgr - width x height number of pixels making up the bitmap to be presented, each pixel being a 32-bit unsigned + integer where the highest 8 bits are not used, and the following 8-bit groups are blue, green and red channels. This + parameter may be NULL, in which case no bitmap is drawn, to allow for custom rendering. See below for details. +* width, height - the horizontal and vertical dimensions of the bitmap +* mod_xbgr - an rgb color value which will be automatically multiplied with each pixel, component by component, before + it is displayed. Can be used to for example fade the bitmap to/from black. Set to 0xffffff for no effect. +* border_xbgr - an rgb color value to be used as *border color*. The borders are the areas outside of the bitmap, which + are visible when the window aspect ratio does not match that of the bitmap, so you get bars above or below it. + +Since app.h uses opengl, you can also opt to not pass a bitmap to `app_present`, by passing NULL as the `pixels_xbgr` +parameter (in which case the rest of the parameters are ignored). When doing this, it is up to your program to perform +drawing using opengl calls. In this case `app_present` will work as a call to SwapBuffers only. Note that glSetViewPort +will be automatically called whenever the window is resized. + + +app_sound_buffer_size +--------------------- + + void app_sound_buffer_size( app_t* app, int sample_pairs_count ) + +The api for playing sound samples is just as minimal as that for drawing. app.h provides a single, looping sound stream, +and it is up to your program to handle sound formats, voices and mixing. By calling `app_sound_buffer_size`, a sound +stream of the specified size is initialized, and playback is started. If a `sample_pairs_count` of 0 is given, sound +playback will be stopped. + +The sound buffer is in 44100hz, signed 16-bit stereo format, and the `sample_pairs_count` specified how many left/right +pairs of 16-bit samples the buffer will contain. As an example, to specify a 1 second stream buffer, you would give the +value 44100 for the `sample_pairs_count` parameter, which would internally create a sound buffer of 176,400 bytes, from +44100 samples x 2 channels x 2 bytes per sample. However, since the bits per sample and number of channels are fixed, +the exact byte size of the sound buffer is not important in the app.h API. + + +app_sound_position +------------------ + + int app_sound_position( app_t* app ) + +Returns the current playback position of the sound stream, given in the number of sample pairs from the start of the +buffer. Typical use of a streaming sound buffer is to fill the buffer with data, wait for the playback position to get +passed the mid point of the buffer, and then write the next bit of data over the part that has already been played, and +so on. + + +app_sound_write +--------------- + + void app_sound_write( app_t* app, int sample_pairs_offset, int sample_pairs_count, APP_S16 const* sample_pairs ) + +Writes sample data to the sound buffer. It takes the following parameters: + +* sample_pairs_offset - the offset into the buffer of where to start writing, in number of sample pairs from the start. +* sample_pairs_count - the number of sample pairs to write. Must be less than the buffer size. +* sample_pairs - an array of sound samples, as signed 16-bit integers. Must be at least `sample_pairs_count` in length. + +The `sample_pairs` parameter can be NULL, in which case the corresponding part of the buffer is cleared. + + +app_sound_volume +---------------- + + void app_sound_volume( app_t* app, float volume ) + +Sets the output volume level of the sound stream, as a normalized linear value in the range 0.0f to 1.0f, inclusive. + + +app_input +--------- + + app_input_t app_input( app_t* app ) + +Returns a list of input events which occured since the last call to `app_input`. Each input event can be of one of a +list of types, and the `type` field of the `app_input_event_t` struct specifies which type the event is. The `data` +struct is a union of fields, where only one of them is valid, depending on the value of `type`: +* APP_INPUT_KEY_DOWN, APP_INPUT_KEY_UP, APP_INPUT_DOUBLE_CLICK - use the `key` field of the `data` union, which contains + one of the keyboard key identifiers from the `app_key_t` enumeration. `APP_INPUT_KEY_DOWN` means a key was pressed, + or that it was held long enough for the key repeat to kick in. `APP_INPUT_KEY_UP` means a key was released, and is + not sent on key repeats. For both these events, a `key` may also mean a mouse button, as those are listed in the + `app_key_t` enum. `APP_INPUT_DOUBLE_CLICK` means a mouse button have been double clicked, and is not sent for + keyboard keys. +* APP_INPUT_CHAR - use the `char_code` field of the `data` union, which contains the ASCII value of the key that was + pressed. This is used to read text input, and will handle things like upper/lower case, and characters which + requires multiple keys to be pressed in sequence to generate one input. This means that generally, when a key is + pressed, you will get both an `APP_INPUT_KEY_DOWN` event and an `APP_INPUT_CHAR` event - just use the one you are + interested in, and ignore the other. +* APP_INPUT_MOUSE_MOVE - use the `mouse_pos` field of the `data` union, which contains the x and y position of the + mouse pointer, in window coordinates. The function `app_coordinates_window_to_bitmap` can be used to convert between + the coordinate system of the window and that of the currently displayed bitmap. The `APP_INPUT_MOUSE_MOVE` event is + sent whenever the user moves the mouse, as long as the window has focus and the pointer is inside its client area. +* APP_INPUT_MOUSE_DELTA - use the `mouse_delta` field of the `data` union, which contains the horizontal and vertical + offset which the mouse has been moved by. Ideally, these values should be in normalized -1.0f to 1.0f range, but as + there is no standardisation on the hardware and os level for this, it is not possible to do, so instead the value + have been scaled to give roughly normalized -1.0f to 1.0f values on a typical setup. For serious use, sensitivity + settings and/or user calibration is recommended. The `APP_INPUT_MOUSE_DELTA` event is sent whenever the user moves + the mouse, regardless of whether the window has focus or whether the pointer is inside the window or not. The + `APP_INPUT_MOUSE_DELTA` event is a better option for reading relative mouse movements than using the + `APP_INPUT_MOUSE_MOVE` event together with `app_pointer_pos` to re-center the pointer on every update. +* APP_INPUT_SCROLL_WHEEL - use the `wheel_delta` field of the `data` union, which contains the number of clicks by which + the scroll wheel on the mouse was turned, where positive values indicate that the wheel have been rotated away from + the user, and negative values means it has turned towards the user. The `APP_INPUT_SCROLL_WHEEL` is sent every time + the user turns the scroll wheel, as long as the window has focus. +* APP_INPUT_TABLET - use the `tablet` field of the `data` union, which contains details about the pen used with a + graphical tablet, if connected and installed. The `x` and `y` fields are the horizontal and vertical positions of + the pen on the tablet, scaled to the coordinate system of the window. The function `app_coordinates_window_to_bitmap` + can be used to convert between the coordinate system of the window and that of the currently displayed bitmap. The + `pressure` field is the current pressure of the pen against the tablet, in normalized 0.0f to 1.0f range, inclusive, + where 0.0f means no pressure and 1.0f means full pressure. The `tip` field is set to `APP_PRESSED` if the tip of the + pen is touching the tablet at all, and to `APP_NOT_PRESSED` otherwise. The `upper` and `lower` fields indicate the + current state of the buttons on the side of the pen. The "eraser" part of the pen is not currently supported. + + +app_coordinates_window_to_bitmap +-------------------------------- + + void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ) + +Functions in the `app.h` API expects and returns coordinates in the windows coordinate system, where 0, 0 is the top +left corner of the windows client area (the area inside of the window borders, excluding title bar and decorations), and +width, height is the dimension, in pixels, of the client area. It is often desirable to translate a position given in +window coordinates into a position on the bitmap that is being displayed - taking into account whether the window is in +fullscreen or windowed mode, and how the bitmap is stretched or padded depending on which interpolation mode is being +used: `APP_INTERPOLATION_NONE` or `APP_INTERPOLATION_LINEAR`. `app_coordinates_window_to_bitmap` performs this +translation, and is called with the following parameters: +* width, height - dimensions of the bitmap being presented, the same as the ones passed to `app_present`. +* x, y - pointers to integer values containing the coordinate, in the coordinate system of the window, to be translated. + When the function returns, their values will have been updated with the corresponding position in the coordinate + system of the bitmap. + + +app_coordinates_bitmap_to_window +-------------------------------- + + void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ) + +This performs the opposite translation to `app_coordinates_window_to_bitmap` - it converts a position given in the +coordinate system of the bitmap into the coordinate system of the window. See `app_coordinates_window_to_bitmap` for +details. + +*/ + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ +#ifdef APP_IMPLEMENTATION +#undef APP_IMPLEMENTATION + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// OPENGL CODE - Shared between platform implementations +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef APP_NULL + +#if defined( APP_WINDOWS ) + + #define _CRT_NONSTDC_NO_DEPRECATE + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #include + #define APP_GLCALLTYPE __stdcall + typedef unsigned int APP_GLuint; + typedef int APP_GLsizei; + typedef unsigned int APP_GLenum; + typedef int APP_GLint; + typedef float APP_GLfloat; + typedef char APP_GLchar; + typedef unsigned char APP_GLboolean; + typedef size_t APP_GLsizeiptr; + typedef unsigned int APP_GLbitfield; + + #define APP_GL_FLOAT 0x1406 + #define APP_GL_FALSE 0 + #define APP_GL_FRAGMENT_SHADER 0x8b30 + #define APP_GL_VERTEX_SHADER 0x8b31 + #define APP_GL_COMPILE_STATUS 0x8b81 + #define APP_GL_LINK_STATUS 0x8b82 + #define APP_GL_INFO_LOG_LENGTH 0x8b84 + #define APP_GL_ARRAY_BUFFER 0x8892 + #define APP_GL_TEXTURE_2D 0x0de1 + #define APP_GL_TEXTURE0 0x84c0 + #define APP_GL_CLAMP 0x2900 + #define APP_GL_TEXTURE_WRAP_S 0x2802 + #define APP_GL_TEXTURE_WRAP_T 0x2803 + #define APP_GL_TEXTURE_MIN_FILTER 0x2801 + #define APP_GL_TEXTURE_MAG_FILTER 0x2800 + #define APP_GL_NEAREST 0x2600 + #define APP_GL_LINEAR 0x2601 + #define APP_GL_STATIC_DRAW 0x88e4 + #define APP_GL_RGBA 0x1908 + #define APP_GL_UNSIGNED_BYTE 0x1401 + #define APP_GL_COLOR_BUFFER_BIT 0x00004000 + #define APP_GL_TRIANGLE_FAN 0x0006 + +#elif defined( APP_SDL ) || defined( APP_WASM ) + + #if defined( APP_WASM ) + #include + #define WA_CORO_IMPLEMENT_NANOSLEEP + #include + #else + #include + #include "SDL_opengl.h" + #endif + #define APP_GLCALLTYPE GLAPIENTRY + typedef GLuint APP_GLuint; + typedef GLsizei APP_GLsizei; + typedef GLenum APP_GLenum; + typedef GLint APP_GLint; + typedef GLfloat APP_GLfloat; + typedef GLchar APP_GLchar; + typedef GLboolean APP_GLboolean; + typedef GLsizeiptr APP_GLsizeiptr; + typedef GLbitfield APP_GLbitfield; + + #define APP_GL_FLOAT GL_FLOAT + #define APP_GL_FALSE GL_FALSE + #define APP_GL_FRAGMENT_SHADER GL_FRAGMENT_SHADER + #define APP_GL_VERTEX_SHADER GL_VERTEX_SHADER + #define APP_GL_COMPILE_STATUS GL_COMPILE_STATUS + #define APP_GL_LINK_STATUS GL_LINK_STATUS + #define APP_GL_INFO_LOG_LENGTH GL_INFO_LOG_LENGTH + #define APP_GL_ARRAY_BUFFER GL_ARRAY_BUFFER + #define APP_GL_TEXTURE_2D GL_TEXTURE_2D + #define APP_GL_TEXTURE0 GL_TEXTURE0 + #if defined( APP_WASM ) + #define APP_GL_CLAMP GL_CLAMP_TO_EDGE + #else + #define APP_GL_CLAMP GL_CLAMP + #endif + #define APP_GL_TEXTURE_WRAP_S GL_TEXTURE_WRAP_S + #define APP_GL_TEXTURE_WRAP_T GL_TEXTURE_WRAP_T + #define APP_GL_TEXTURE_MIN_FILTER GL_TEXTURE_MIN_FILTER + #define APP_GL_TEXTURE_MAG_FILTER GL_TEXTURE_MAG_FILTER + #define APP_GL_NEAREST GL_NEAREST + #define APP_GL_LINEAR GL_LINEAR + #define APP_GL_STATIC_DRAW GL_STATIC_DRAW + #define APP_GL_RGBA GL_RGBA + #define APP_GL_UNSIGNED_BYTE GL_UNSIGNED_BYTE + #define APP_GL_COLOR_BUFFER_BIT GL_COLOR_BUFFER_BIT + #define APP_GL_TRIANGLE_FAN GL_TRIANGLE_FAN + +#else + + #error Undefined platform. Define APP_WINDOWS, APP_SDL, APP_WASM or APP_NULL. + #define APP_GLCALLTYPE + typedef int APP_GLuint; + typedef int APP_GLsizei; + typedef int APP_GLenum; + typedef int APP_GLint; + typedef int APP_GLfloat; + typedef int APP_GLchar; + typedef int APP_GLboolean; + typedef int APP_GLsizeiptr; + typedef int APP_GLbitfield; + +#endif + + +#ifdef APP_REPORT_SHADER_ERRORS + #include +#endif + +struct app_internal_opengl_t + { + + APP_GLuint (APP_GLCALLTYPE* CreateShader) (APP_GLenum type); + void (APP_GLCALLTYPE* ShaderSource) (APP_GLuint shader, APP_GLsizei count, APP_GLchar const* const* string, APP_GLint const* length); + void (APP_GLCALLTYPE* CompileShader) (APP_GLuint shader); + void (APP_GLCALLTYPE* GetShaderiv) (APP_GLuint shader, APP_GLenum pname, APP_GLint *params); + APP_GLuint (APP_GLCALLTYPE* CreateProgram) (void); + void (APP_GLCALLTYPE* AttachShader) (APP_GLuint program, APP_GLuint shader); + void (APP_GLCALLTYPE* BindAttribLocation) (APP_GLuint program, APP_GLuint index, APP_GLchar const* name); + void (APP_GLCALLTYPE* LinkProgram) (APP_GLuint program); + void (APP_GLCALLTYPE* GetProgramiv) (APP_GLuint program, APP_GLenum pname, APP_GLint *params); + void (APP_GLCALLTYPE* GenBuffers) (APP_GLsizei n, APP_GLuint *buffers); + void (APP_GLCALLTYPE* BindBuffer) (APP_GLenum target, APP_GLuint buffer); + void (APP_GLCALLTYPE* EnableVertexAttribArray) (APP_GLuint index); + void (APP_GLCALLTYPE* VertexAttribPointer) (APP_GLuint index, APP_GLint size, APP_GLenum type, APP_GLboolean normalized, APP_GLsizei stride, void const* pointer); + void (APP_GLCALLTYPE* GenTextures) (APP_GLsizei n, APP_GLuint* textures); + void (APP_GLCALLTYPE* Enable) (APP_GLenum cap); + void (APP_GLCALLTYPE* ActiveTexture) (APP_GLenum texture); + void (APP_GLCALLTYPE* BindTexture) (APP_GLenum target, APP_GLuint texture); + void (APP_GLCALLTYPE* TexParameteri) (APP_GLenum target, APP_GLenum pname, APP_GLint param); + void (APP_GLCALLTYPE* DeleteBuffers) (APP_GLsizei n, APP_GLuint const* buffers); + void (APP_GLCALLTYPE* DeleteTextures) (APP_GLsizei n, APP_GLuint const* textures); + void (APP_GLCALLTYPE* BufferData) (APP_GLenum target, APP_GLsizeiptr size, void const *data, APP_GLenum usage); + void (APP_GLCALLTYPE* UseProgram) (APP_GLuint program); + void (APP_GLCALLTYPE* Uniform1i) (APP_GLint location, APP_GLint v0); + void (APP_GLCALLTYPE* Uniform3f) (APP_GLint location, APP_GLfloat v0, APP_GLfloat v1, APP_GLfloat v2); + APP_GLint (APP_GLCALLTYPE* GetUniformLocation) (APP_GLuint program, APP_GLchar const* name); + void (APP_GLCALLTYPE* TexImage2D) (APP_GLenum target, APP_GLint level, APP_GLint internalformat, APP_GLsizei width, APP_GLsizei height, APP_GLint border, APP_GLenum format, APP_GLenum type, void const* pixels); + void (APP_GLCALLTYPE* ClearColor) (APP_GLfloat red, APP_GLfloat green, APP_GLfloat blue, APP_GLfloat alpha); + void (APP_GLCALLTYPE* Clear) (APP_GLbitfield mask); + void (APP_GLCALLTYPE* DrawArrays) (APP_GLenum mode, APP_GLint first, APP_GLsizei count); + void (APP_GLCALLTYPE* Viewport) (APP_GLint x, APP_GLint y, APP_GLsizei width, APP_GLsizei height); + void (APP_GLCALLTYPE* DeleteShader) (APP_GLuint shader); + void (APP_GLCALLTYPE* DeleteProgram) (APP_GLuint program); + #ifdef APP_REPORT_SHADER_ERRORS + void (APP_GLCALLTYPE* GetShaderInfoLog) (APP_GLuint shader, APP_GLsizei bufSize, APP_GLsizei *length, APP_GLchar *infoLog); + #endif + + app_interpolation_t interpolation; + int window_width; + int window_height; + + APP_GLuint vertexbuffer; + APP_GLuint texture; + APP_GLuint shader; + }; + + +static int app_internal_opengl_init( app_t* app, struct app_internal_opengl_t* gl, app_interpolation_t interpolation, + int window_width, int window_height ) + { + (void) app; + gl->interpolation = interpolation; + gl->window_width = window_width; + gl->window_height = window_height; + + char const* vs_source = + #ifdef APP_WASM + "precision highp float;\n" + #else + "#version 120\n" + #endif + "attribute vec4 pos;" + "varying vec2 uv;" + "" + "void main( void )" + " {" + " gl_Position = vec4( pos.xy, 0.0, 1.0 );" + " uv = pos.zw;" + " }" + ; + + char const* fs_source = + #ifdef APP_WASM + "precision highp float;\n" + #else + "#version 120\n" + #endif + "varying vec2 uv;" + "" + "uniform sampler2D texture;" + "uniform vec3 modulate;" + "" + "void main(void)" + " {" + " gl_FragColor = texture2D( texture, uv ) * vec4( modulate, 1.0 );" + " }" + ; + + #ifdef APP_REPORT_SHADER_ERRORS + char error_message[ 1024 ]; + #endif + + APP_GLuint vs = gl->CreateShader( APP_GL_VERTEX_SHADER ); + gl->ShaderSource( vs, 1, (char const**) &vs_source, NULL ); + gl->CompileShader( vs ); + APP_GLint vs_compiled; + gl->GetShaderiv( vs, APP_GL_COMPILE_STATUS, &vs_compiled ); + if( !vs_compiled ) + { + #ifdef APP_REPORT_SHADER_ERRORS + char const* prefix = "Vertex Shader Error: "; + memcpy( error_message, prefix, strlen( prefix ) + 1 ); + int len = 0, written = 0; + gl->GetShaderiv( vs, APP_GL_INFO_LOG_LENGTH, &len ); + gl->GetShaderInfoLog( vs, (APP_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + app_fatal_error( app, error_message ); + #endif + return 0; + } + + APP_GLuint fs = gl->CreateShader( APP_GL_FRAGMENT_SHADER ); + gl->ShaderSource( fs, 1, (char const**) &fs_source, NULL ); + gl->CompileShader( fs ); + APP_GLint fs_compiled; + gl->GetShaderiv( fs, APP_GL_COMPILE_STATUS, &fs_compiled ); + if( !fs_compiled ) + { + #ifdef APP_REPORT_SHADER_ERRORS + char const* prefix = "Fragment Shader Error: "; + memcpy( error_message, prefix, strlen( prefix ) + 1 ); + int len = 0, written = 0; + gl->GetShaderiv( vs, APP_GL_INFO_LOG_LENGTH, &len ); + gl->GetShaderInfoLog( fs, (APP_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + app_fatal_error( app, error_message ); + #endif + return 0; + } + + + APP_GLuint prg = gl->CreateProgram(); + gl->AttachShader( prg, fs ); + gl->AttachShader( prg, vs ); + gl->BindAttribLocation( prg, 0, "pos" ); + gl->LinkProgram( prg ); + + APP_GLint linked; + gl->GetProgramiv( prg, APP_GL_LINK_STATUS, &linked ); + if( !linked ) + { + #ifdef APP_REPORT_SHADER_ERRORS + char const* prefix = "Shader Link Error: "; + memcpy( error_message, prefix, strlen( prefix ) + 1 ); + int len = 0, written = 0; + gl->GetShaderiv( vs, APP_GL_INFO_LOG_LENGTH, &len ); + gl->GetShaderInfoLog( prg, (APP_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + app_fatal_error( app, error_message ); + #endif + return 0; + } + + gl->shader = prg; + gl->DeleteShader( fs ); + gl->DeleteShader( vs ); + + gl->GenBuffers( 1, &gl->vertexbuffer ); + gl->BindBuffer( APP_GL_ARRAY_BUFFER, gl->vertexbuffer ); + gl->EnableVertexAttribArray( 0 ); + gl->VertexAttribPointer( 0, 4, APP_GL_FLOAT, APP_GL_FALSE, 4 * sizeof( APP_GLfloat ), 0 ); + + gl->GenTextures( 1, &gl->texture ); + #ifndef APP_WASM + // This enable call is not necessary when using fragment shaders, avoid logged warnings in WebGL + gl->Enable( APP_GL_TEXTURE_2D ); + #endif + gl->ActiveTexture( APP_GL_TEXTURE0 ); + gl->BindTexture( APP_GL_TEXTURE_2D, gl->texture ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MIN_FILTER, APP_GL_NEAREST ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MAG_FILTER, APP_GL_NEAREST ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_WRAP_S, APP_GL_CLAMP ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_WRAP_T, APP_GL_CLAMP ); + + return 1; + } + + +static int app_internal_opengl_term( struct app_internal_opengl_t* gl ) + { + gl->DeleteProgram( gl->shader ); + gl->DeleteBuffers( 1, &gl->vertexbuffer); + gl->DeleteTextures( 1, &gl->texture ); + return 1; + } + + +static int app_internal_opengl_present( struct app_internal_opengl_t* gl, APP_U32 const* pixels_xbgr, int width, + int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) + { + float x1 = 0.0f, y1 = 0.0f, x2 = (float) gl->window_width, y2 = (float) gl->window_height; + + if( gl->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = gl->window_width / (float) width; + float vscale = gl->window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + + float hborder = ( gl->window_width - pixel_scale * width ) / 2.0f; + float vborder = ( gl->window_height - pixel_scale * height ) / 2.0f; + x1 = hborder; + y1 = vborder; + x2 = x1 + pixel_scale * width; + y2 = y1 + pixel_scale * height; + } + else + { + int hscale = gl->window_width / width; + int vscale = gl->window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + + int hborder = ( gl->window_width - pixel_scale * width ) / 2; + int vborder = ( gl->window_height - pixel_scale * height ) / 2; + x1 = (float) hborder; + y1 = (float) vborder; + x2 = x1 + (float) ( pixel_scale * width ); + y2 = y1 + (float) ( pixel_scale * height ); + } + + x1 = ( x1 / gl->window_width ) * 2.0f - 1.0f; + x2 = ( x2 / gl->window_width ) * 2.0f - 1.0f; + y1 = ( y1 / gl->window_height ) * 2.0f - 1.0f; + y2 = ( y2 / gl->window_height ) * 2.0f - 1.0f; + + APP_GLfloat vertices[ 16 ]; + vertices[ 0 ] = x1; + vertices[ 1 ] = y1; + vertices[ 2 ] = 0.0f; + vertices[ 3 ] = 1.0f; + + vertices[ 4 ] = x2; + vertices[ 5 ] = y1; + vertices[ 6 ] = 1.0f; + vertices[ 7 ] = 1.0f; + + vertices[ 8 ] = x2; + vertices[ 9 ] = y2; + vertices[ 10 ] = 1.0f; + vertices[ 11 ] = 0.0f; + + vertices[ 12 ] = x1; + vertices[ 13 ] = y2; + vertices[ 14 ] = 0.0f; + vertices[ 15 ] = 0.0f; + + gl->BindBuffer( APP_GL_ARRAY_BUFFER, gl->vertexbuffer ); + gl->BufferData( APP_GL_ARRAY_BUFFER, 4 * 4 * sizeof( APP_GLfloat ), vertices, APP_GL_STATIC_DRAW ); + gl->VertexAttribPointer( 0, 4, APP_GL_FLOAT, APP_GL_FALSE, 4 * sizeof( APP_GLfloat ), 0 ); + + float mod_r = ( ( mod_xbgr >> 16 ) & 0xff ) / 255.0f; + float mod_g = ( ( mod_xbgr >> 8 ) & 0xff ) / 255.0f; + float mod_b = ( ( mod_xbgr ) & 0xff ) / 255.0f; + + gl->UseProgram( gl->shader ); + gl->Uniform1i( gl->GetUniformLocation( gl->shader, "texture" ), 0 ); + gl->Uniform3f( gl->GetUniformLocation( gl->shader, "modulate" ), mod_r, mod_g, mod_b ); + + gl->ActiveTexture( APP_GL_TEXTURE0 ); + gl->BindTexture( APP_GL_TEXTURE_2D, gl->texture ); + gl->TexImage2D( APP_GL_TEXTURE_2D, 0, APP_GL_RGBA, width, height, 0, APP_GL_RGBA, APP_GL_UNSIGNED_BYTE, pixels_xbgr ); + + if( gl->interpolation == APP_INTERPOLATION_LINEAR ) + { + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MIN_FILTER, APP_GL_LINEAR ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MAG_FILTER, APP_GL_LINEAR ); + } + else + { + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MIN_FILTER, APP_GL_NEAREST ); + gl->TexParameteri( APP_GL_TEXTURE_2D, APP_GL_TEXTURE_MAG_FILTER, APP_GL_NEAREST ); + } + + float b = ( ( border_xbgr >> 16 ) & 0xff ) / 255.0f; + float g = ( ( border_xbgr >> 8 ) & 0xff ) / 255.0f; + float r = ( ( border_xbgr ) & 0xff ) / 255.0f; + gl->ClearColor( r, g, b, 1.0f ); + gl->Clear( APP_GL_COLOR_BUFFER_BIT ); + gl->DrawArrays( APP_GL_TRIANGLE_FAN, 0, 4 ); + + return 1; + } + + +static void app_internal_opengl_resize( struct app_internal_opengl_t* gl, int width, int height ) + { + gl->Viewport( 0, 0, width, height ); + gl->window_width = width; + gl->window_height = height; + } + + +static void app_internal_opengl_interpolation( struct app_internal_opengl_t* gl, app_interpolation_t interpolation ) + { + gl->interpolation = interpolation; + } + + +#endif // #ifndef APP_NULL + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// NULL +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if defined( APP_NULL ) + + +struct app_t { void* dummy; }; +int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ) { app_t app; return app_proc( &app, user_data ); } +app_state_t app_yield( app_t* app ) { return APP_STATE_NORMAL; } +void app_cancel_exit( app_t* app ) { } +void app_title( app_t* app, char const* title ) { } +char const* app_cmdline( app_t* app ) { return ""; } +char const* app_filename( app_t* app ) { return ""; } +char const* app_userdata( app_t* app ) { return ""; } +char const* app_appdata( app_t* app ) { return ""; } +APP_U64 app_time_count( app_t* app ) { return 0ULL; } +APP_U64 app_time_freq( app_t* app ) { return 0ULL; } +void app_log( app_t* app, app_log_level_t level, char const* message ) { } +void app_fatal_error( app_t* app, char const* message ) { } +void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) { } +void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ) { } +void app_pointer_pos( app_t* app, int x, int y ) { } +int app_pointer_x( app_t* app ) { return 0; } +int app_pointer_y( app_t* app ) { return 0;} +void app_pointer_limit( app_t* app, int x, int y, int width, int height ) { } +void app_pointer_limit_off( app_t* app ) { } +void app_interpolation( app_t* app, app_interpolation_t interpolation ) { } +void app_screenmode( app_t* app, app_screenmode_t screenmode ) { } +void app_window_size( app_t* app, int width, int height ) { } +int app_window_width( app_t* app ) { return 0; } +int app_window_height( app_t* app ) { return 0; } +void app_window_pos( app_t* app, int x, int y ) { } +int app_window_x( app_t* app ) { return 0; } +int app_window_y( app_t* app ) { return 0; } +app_displays_t app_displays( app_t* app ) { app_displays_t ret = { 0 }; return ret; } +void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) { } +void app_sound( app_t* app, int sample_pairs_count, void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ), void* user_data ) { } +void app_sound_volume( app_t* app, float volume ) { } +app_input_t app_input( app_t* app ) { app_input_t ret = { 0 }; return ret; } +void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ) { } +void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ); + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WINDOWS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#elif defined( APP_WINDOWS ) + +#define _CRT_NONSTDC_NO_DEPRECATE +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#if !defined( _WIN32_WINNT ) || _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601// requires Windows 7 minimum +#endif +// 0x0400=Windows NT 4.0, 0x0500=Windows 2000, 0x0501=Windows XP, 0x0502=Windows Server 2003, 0x0600=Windows Vista, +// 0x0601=Windows 7, 0x0602=Windows 8, 0x0603=Windows 8.1, 0x0A00=Windows 10, +#define _WINSOCKAPI_ +#pragma warning( push ) +#pragma warning( disable: 4619 ) // #pragma warning: there is no warning number 'nnnn' +#pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +#pragma warning( disable: 4768 ) // __declspec attributes before linkage specification are ignored +#pragma warning( disable: 4255 ) // 'function' : no function prototype given: converting '()' to '(void)' +#pragma warning( disable: 4917 ) // 'declarator' : a GUID can only be associated with a class, interface or namespace +#define _NTDDSCM_H_ /* Fixes the error of mismatched pragma warning push/pop in Windows SDK 10.0.17763.0 */ +#include +//#include +#pragma warning( pop ) +#ifndef __TINYC__ +#pragma comment( lib, "user32.lib" ) +#pragma comment( lib, "gdi32.lib" ) +#pragma comment( lib, "winmm.lib" ) +#pragma comment( lib, "shell32.lib" ) +#else +#pragma comment( lib, "user32") +#pragma comment( lib, "gdi32") +#endif + +#include +#include + +#pragma warning( push ) +#pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +#include +#pragma warning( pop ) + +#ifndef APP_MALLOC + #include + #if defined(__cplusplus) + #define APP_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define APP_MALLOC( ctx, size ) ( malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif + +#ifndef APP_LOG + #if defined(__cplusplus) + #define APP_LOG( ctx, level, message ) ::printf( "%s\n", message ) + #else + #define APP_LOG( ctx, level, message ) printf( "%s\n", message ) + #endif +#endif + +#ifndef APP_FATAL_ERROR + #if defined(__cplusplus) + #define APP_FATAL_ERROR( ctx, message ) { ::printf( "FATAL ERROR: %s\n", message ); \ + ::MessageBoxA( 0, message, "Fatal Error!", MB_OK | MB_ICONSTOP ); ::_flushall(); ::_exit( 0xff ); } + #else + #define APP_FATAL_ERROR( ctx, message ) { printf( "FATAL ERROR: %s\n", message ); \ + MessageBoxA( 0, message, "Fatal Error!", MB_OK | MB_ICONSTOP ); _flushall(); _exit( 0xff ); } + #endif +#endif + + +#ifndef APP_WINDOWED_WS_STYLE + #define APP_WINDOWED_WS_STYLE WS_OVERLAPPEDWINDOW +#endif + +#ifndef APP_WINDOWED_WS_EX_STYLE + #define APP_WINDOWED_WS_EX_STYLE 0 +#endif + + +typedef struct APP_LOGCONTEXTA + { + char lcName[ 40 ]; UINT lcOptions; UINT lcStatus; UINT lcLocks; UINT lcMsgBase; UINT lcDevice; UINT lcPktRate; + DWORD lcPktData; DWORD lcPktMode; DWORD lcMoveMask; DWORD lcBtnDnMask; DWORD lcBtnUpMask; LONG lcInOrgX; + LONG lcInOrgY; LONG lcInOrgZ; LONG lcInExtX; LONG lcInExtY; LONG lcInExtZ; LONG lcOutOrgX; LONG lcOutOrgY; + LONG lcOutOrgZ; LONG lcOutExtX; LONG lcOutExtY; LONG lcOutExtZ; DWORD lcSensX; DWORD lcSensY; DWORD lcSensZ; + BOOL lcSysMode; int lcSysOrgX; int lcSysOrgY; int lcSysExtX; int lcSysExtY; DWORD lcSysSensX; DWORD lcSysSensY; + } APP_LOGCONTEXTA; +typedef struct APP_AXIS { LONG axMin; LONG axMax; UINT axUnits; DWORD axResolution; } APP_AXIS; +typedef struct APP_PACKET { DWORD pkButtons; LONG pkX; LONG pkY; UINT pkNormalPressure; } APP_PACKET; +DECLARE_HANDLE( APP_HMGR ); +DECLARE_HANDLE( APP_HCTX ); +#define APP_WTI_DEVICES 100 +#define APP_WTI_DDCTXS 400 /* 1.1 */ +#define APP_CXO_MESSAGES 0x0004 +#define APP_DVC_NPRESSURE 15 +#define APP_PK_BUTTONS 0x0040 // button information +#define APP_PK_X 0x0080 // x axis +#define APP_PK_Y 0x0100 // y axis +#define APP_PK_NORMAL_PRESSURE 0x0400 // normal or tip pressure +#define APP_PACKETDATA APP_PK_X | APP_PK_Y | APP_PK_BUTTONS | APP_PK_NORMAL_PRESSURE +#define APP_PACKETMODE 0 +#define APP_WT_PACKET 0x7FF0 + + + +////// DSOUND DEFINITIONS //////// + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +typedef struct _DSOUND_WAVEFORMATEX + { + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + WORD wBitsPerSample; + WORD cbSize; + } DSOUND_WAVEFORMATEX; + +typedef struct _DSBUFFERDESC + { + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + DSOUND_WAVEFORMATEX* lpwfxFormat; + } DSBUFFERDESC; + +typedef struct _DSBPOSITIONNOTIFY + { + DWORD dwOffset; + HANDLE hEventNotify; + } DSBPOSITIONNOTIFY; + + +typedef struct _DSCAPS DSCAPS; +typedef struct _DSBCAPS DSBCAPS; +typedef struct _DSEFFECTDESC DSEFFECTDESC; +struct IDirectSound8; + +typedef struct IDirectSoundBuffer8 { struct IDirectSoundBuffer8Vtbl* lpVtbl; } IDirectSoundBuffer8; +typedef struct IDirectSoundBuffer8Vtbl IDirectSoundBuffer8Vtbl; + +struct IDirectSoundBuffer8Vtbl +{ + // IUnknown methods + HRESULT (STDMETHODCALLTYPE *QueryInterface) (IDirectSoundBuffer8*, REFIID, LPVOID*); + ULONG (STDMETHODCALLTYPE *AddRef) (IDirectSoundBuffer8*); + ULONG (STDMETHODCALLTYPE *Release) (IDirectSoundBuffer8*); + + // IDirectSoundBuffer methods + HRESULT (STDMETHODCALLTYPE *GetCaps) (IDirectSoundBuffer8*, DSBCAPS* pDSBufferCaps); + HRESULT (STDMETHODCALLTYPE *GetCurrentPosition) (IDirectSoundBuffer8*, LPDWORD pdwCurrentPlayCursor, LPDWORD pdwCurrentWriteCursor); + HRESULT (STDMETHODCALLTYPE *GetFormat) (IDirectSoundBuffer8*, DSOUND_WAVEFORMATEX* pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten); + HRESULT (STDMETHODCALLTYPE *GetVolume) (IDirectSoundBuffer8*, LPLONG plVolume); + HRESULT (STDMETHODCALLTYPE *GetPan) (IDirectSoundBuffer8*, LPLONG plPan); + HRESULT (STDMETHODCALLTYPE *GetFrequency) (IDirectSoundBuffer8*, LPDWORD pdwFrequency); + HRESULT (STDMETHODCALLTYPE *GetStatus) (IDirectSoundBuffer8*, LPDWORD pdwStatus); + HRESULT (STDMETHODCALLTYPE *Initialize) (IDirectSoundBuffer8*, struct IDirectSound8* pDirectSound, DSBUFFERDESC* pcDSBufferDesc); + HRESULT (STDMETHODCALLTYPE *Lock) (IDirectSoundBuffer8*, DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE *Play) (IDirectSoundBuffer8*, DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE *SetCurrentPosition) (IDirectSoundBuffer8*, DWORD dwNewPosition); + HRESULT (STDMETHODCALLTYPE *SetFormat) (IDirectSoundBuffer8*, DSOUND_WAVEFORMATEX* pcfxFormat); + HRESULT (STDMETHODCALLTYPE *SetVolume) (IDirectSoundBuffer8*, LONG lVolume); + HRESULT (STDMETHODCALLTYPE *SetPan) (IDirectSoundBuffer8*, LONG lPan); + HRESULT (STDMETHODCALLTYPE *SetFrequency) (IDirectSoundBuffer8*, DWORD dwFrequency); + HRESULT (STDMETHODCALLTYPE *Stop) (IDirectSoundBuffer8*); + HRESULT (STDMETHODCALLTYPE *Unlock) (IDirectSoundBuffer8*, LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2); + HRESULT (STDMETHODCALLTYPE *Restore) (IDirectSoundBuffer8*); + + // IDirectSoundBuffer8 methods + HRESULT (STDMETHODCALLTYPE *SetFX) (IDirectSoundBuffer8*, DWORD dwEffectsCount, DSEFFECTDESC* pDSFXDesc, LPDWORD pdwResultCodes); + HRESULT (STDMETHODCALLTYPE *AcquireResources) (IDirectSoundBuffer8*, DWORD dwFlags, DWORD dwEffectsCount, LPDWORD pdwResultCodes); + HRESULT (STDMETHODCALLTYPE *GetObjectInPath) (IDirectSoundBuffer8*, REFGUID rguidObject, DWORD dwIndex, REFGUID rguidInterface, LPVOID *ppObject); +}; + +#define IDirectSoundBuffer8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectSoundBuffer8_Lock(p,a,b,c,d,e,f,g) (p)->lpVtbl->Lock(p,a,b,c,d,e,f,g) +#define IDirectSoundBuffer8_Unlock(p,a,b,c,d) (p)->lpVtbl->Unlock(p,a,b,c,d) +#define IDirectSoundBuffer8_Restore(p) (p)->lpVtbl->Restore(p) +#define IDirectSoundBuffer8_GetCurrentPosition(p,a,b) (p)->lpVtbl->GetCurrentPosition(p,a,b) +#define IDirectSoundBuffer8_Play(p,a,b,c) (p)->lpVtbl->Play(p,a,b,c) +#define IDirectSoundBuffer8_SetVolume(p,a) (p)->lpVtbl->SetVolume(p,a) +#define IDirectSoundBuffer8_Release(p) (p)->lpVtbl->Release(p) + + +typedef struct IDirectSound8 { struct IDirectSound8Vtbl* lpVtbl; } IDirectSound8; +typedef struct IDirectSound8Vtbl IDirectSound8Vtbl; + +struct IDirectSound8Vtbl +{ + // IUnknown methods + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IDirectSound8*, REFIID, LPVOID*); + ULONG (STDMETHODCALLTYPE *AddRef) (IDirectSound8*); + ULONG (STDMETHODCALLTYPE *Release) (IDirectSound8*); + + // IDirectSound methods + HRESULT (STDMETHODCALLTYPE *CreateSoundBuffer) (IDirectSound8*, DSBUFFERDESC* pcDSBufferDesc, struct IDirectSoundBuffer8** ppDSBuffer, void* pUnkOuter); + HRESULT (STDMETHODCALLTYPE *GetCaps) (IDirectSound8*, DSCAPS* pDSCaps); + HRESULT (STDMETHODCALLTYPE *DuplicateSoundBuffer) (IDirectSound8*, struct IDirectSoundBuffer8* pDSBufferOriginal, struct IDirectSoundBuffer8* *ppDSBufferDuplicate); + HRESULT (STDMETHODCALLTYPE *SetCooperativeLevel) (IDirectSound8*, HWND hwnd, DWORD dwLevel); + HRESULT (STDMETHODCALLTYPE *Compact) (IDirectSound8*); + HRESULT (STDMETHODCALLTYPE *GetSpeakerConfig) (IDirectSound8*, LPDWORD pdwSpeakerConfig); + HRESULT (STDMETHODCALLTYPE *SetSpeakerConfig) (IDirectSound8*, DWORD dwSpeakerConfig); + HRESULT (STDMETHODCALLTYPE *Initialize) (IDirectSound8*, LPCGUID pcGuidDevice); + + // IDirectSound8 methods + HRESULT (STDMETHODCALLTYPE *VerifyCertification) (IDirectSound8*, LPDWORD pdwCertified); +}; + +#define IDirectSound8_Release(p) (p)->lpVtbl->Release(p) +#define IDirectSound8_CreateSoundBuffer(p,a,b,c) (p)->lpVtbl->CreateSoundBuffer(p,a,b,c) +#define IDirectSound8_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) + + +typedef struct IDirectSoundNotify { struct IDirectSoundNotifyVtbl* lpVtbl; } IDirectSoundNotify; +typedef struct IDirectSoundNotifyVtbl IDirectSoundNotifyVtbl; + +struct IDirectSoundNotifyVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IDirectSoundNotify*, REFIID, LPVOID*); + ULONG (STDMETHODCALLTYPE *AddRef) (IDirectSoundNotify*); + ULONG (STDMETHODCALLTYPE *Release) (IDirectSoundNotify*); + HRESULT (STDMETHODCALLTYPE *SetNotificationPositions) (IDirectSoundNotify*, DWORD dwPositionNotifies, DSBPOSITIONNOTIFY* pcPositionNotifies); +}; + +#define IDirectSoundNotify_Release(p) (p)->lpVtbl->Release(p) +#define IDirectSoundNotify_SetNotificationPositions(p,a,b) (p)->lpVtbl->SetNotificationPositions(p,a,b) + + +#define DS_OK S_OK +#define DSERR_BUFFERLOST MAKE_HRESULT(1, 0x878, 150) + +#define DSSCL_NORMAL 0x00000001 +#define DSBCAPS_CTRLVOLUME 0x00000080 +#define DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 +#define DSBCAPS_GLOBALFOCUS 0x00008000 +#define DSBCAPS_GETCURRENTPOSITION2 0x00010000 +#define DSBPLAY_LOOPING 0x00000001 +#define DSBVOLUME_MIN -10000 + + +#ifdef __cplusplus +}; +#endif // __cplusplus + + +///// END DSOUND DEFINITIONS ////// + + +struct app_t + { + void* memctx; + void* logctx; + void* fatalctx; + app_interpolation_t interpolation; + app_screenmode_t screenmode; + + BOOL initialized; + BOOL closed; + + char exe_path[ 260 ]; + char userdata_path[ 260 ]; + char appdata_path[ 260 ]; + char const* cmdline; + + HINSTANCE hinstance; + HWND hwnd; + LRESULT (CALLBACK *user_wndproc)( app_t*, HWND, UINT, WPARAM, LPARAM ); + + HDC hdc; + HICON icon; + BOOL has_focus; + BOOL is_minimized; + + struct app_internal_opengl_t gl; + HMODULE gl_dll; + HGLRC gl_context; + PROC (APP_GLCALLTYPE* wglGetProcAddress) (LPCSTR); + HGLRC (APP_GLCALLTYPE* wglCreateContext) (HDC); + BOOL (APP_GLCALLTYPE* wglDeleteContext) (HGLRC); + BOOL (APP_GLCALLTYPE* wglMakeCurrent) (HDC, HGLRC); + BOOL (APP_GLCALLTYPE* wglSwapIntervalEXT) (int); + + UINT (WINAPI *GetRawInputDataPtr)( HRAWINPUT, UINT, LPVOID, PUINT, UINT ); + + HANDLE sound_notifications[ 2 ]; + HMODULE dsound_dll; + struct IDirectSound8* dsound; + struct IDirectSoundBuffer8* dsoundbuf; + HANDLE sound_thread_handle; + volatile LONG exit_sound_thread; + int sample_pairs_count; + int sound_level; + void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ); + void* sound_user_data; + + HCURSOR current_pointer; + + BOOL clip_cursor; + RECT clip_rect; + + app_input_event_t input_events[ 1024 ]; + int input_count; + + int windowed_x; + int windowed_y; + int windowed_h; + int windowed_w; + int fullscreen_width; + int fullscreen_height; + + int display_count; + app_display_t displays[ 16 ]; + HMONITOR displays_hmonitor[ 16 ]; + + struct + { + HMODULE wintab_dll; + APP_HCTX context; + int max_pressure; + + UINT (WINAPI *WTInfo)( UINT, UINT, LPVOID ); + APP_HCTX (WINAPI *WTOpen)( HWND, APP_LOGCONTEXTA*, BOOL ); + BOOL (WINAPI *WTClose)( APP_HCTX ); + BOOL (WINAPI *WTEnable)( APP_HCTX, BOOL ); + BOOL (WINAPI *WTPacket)( APP_HCTX, UINT, LPVOID ); + } tablet; + }; + + +static app_key_t app_internal_vkcode_to_appkey( app_t* app, int vkcode ) + { + int map[ 256 * 2 ] = { APP_KEY_INVALID, 0x00, APP_KEY_LBUTTON, 0x01, APP_KEY_RBUTTON, 0x02, APP_KEY_CANCEL, 0x03, APP_KEY_MBUTTON, 0x04, + APP_KEY_XBUTTON1, 0x05, APP_KEY_XBUTTON2, 0x06, -1, 0x07, APP_KEY_BACK, 0x08, APP_KEY_TAB, 0x09, -1, 0x0A, -1, 0x0B, APP_KEY_CLEAR, 0x0C, + APP_KEY_RETURN, 0x0D, -1, 0x0E, -1, 0x0F, APP_KEY_SHIFT, 0x10, APP_KEY_CONTROL, 0x11, APP_KEY_MENU, 0x12, APP_KEY_PAUSE, 0x13, + APP_KEY_CAPITAL, 0x14, APP_KEY_KANA, 0x15, -1, 0x16, APP_KEY_JUNJA, 0x17, APP_KEY_FINAL, 0x18, APP_KEY_HANJA, 0x19, -1, 0x1A, + APP_KEY_ESCAPE, 0x1B, APP_KEY_CONVERT, 0x1C, APP_KEY_NONCONVERT, 0x1D, APP_KEY_ACCEPT, 0x1E, APP_KEY_MODECHANGE, 0x1F, APP_KEY_SPACE, 0x20, + APP_KEY_PRIOR, 0x21, APP_KEY_NEXT, 0x22, APP_KEY_END, 0x23, APP_KEY_HOME, 0x24, APP_KEY_LEFT, 0x25, APP_KEY_UP, 0x26, APP_KEY_RIGHT, 0x27, + APP_KEY_DOWN, 0x28, APP_KEY_SELECT, 0x29, APP_KEY_PRINT, 0x2A, APP_KEY_EXEC, 0x2B, APP_KEY_SNAPSHOT, 0x2C, APP_KEY_INSERT, 0x2D, + APP_KEY_DELETE, 0x2E, APP_KEY_HELP, 0x2F, APP_KEY_0, 0x30, APP_KEY_1, 0x31, APP_KEY_2, 0x32, APP_KEY_3, 0x33, APP_KEY_4, 0x34, + APP_KEY_5, 0x35, APP_KEY_6, 0x36, APP_KEY_7, 0x37, APP_KEY_8, 0x38, APP_KEY_9, 0x39, -1, 0x3A, -1, 0x3B, -1, 0x3C, -1, 0x3D, -1, 0x3E, + -1, 0x3F, -1, 0x40, APP_KEY_A, 0x41, APP_KEY_B, 0x42, APP_KEY_C, 0x43, APP_KEY_D, 0x44, APP_KEY_E, 0x45, APP_KEY_F, 0x46, APP_KEY_G, 0x47, + APP_KEY_H, 0x48, APP_KEY_I, 0x49, APP_KEY_J, 0x4A, APP_KEY_K, 0x4B, APP_KEY_L, 0x4C, APP_KEY_M, 0x4D, APP_KEY_N, 0x4E, APP_KEY_O, 0x4F, + APP_KEY_P, 0x50, APP_KEY_Q, 0x51, APP_KEY_R, 0x52, APP_KEY_S, 0x53, APP_KEY_T, 0x54, APP_KEY_U, 0x55, APP_KEY_V, 0x56, APP_KEY_W, 0x57, + APP_KEY_X, 0x58, APP_KEY_Y, 0x59, APP_KEY_Z, 0x5A, APP_KEY_LWIN, 0x5B, APP_KEY_RWIN, 0x5C, APP_KEY_APPS, 0x5D, -1, 0x5E, APP_KEY_SLEEP, 0x5F, + APP_KEY_NUMPAD0, 0x60, APP_KEY_NUMPAD1, 0x61, APP_KEY_NUMPAD2, 0x62, APP_KEY_NUMPAD3, 0x63, APP_KEY_NUMPAD4, 0x64, APP_KEY_NUMPAD5, 0x65, + APP_KEY_NUMPAD6, 0x66, APP_KEY_NUMPAD7, 0x67, APP_KEY_NUMPAD8, 0x68, APP_KEY_NUMPAD9, 0x69, APP_KEY_MULTIPLY, 0x6A, APP_KEY_ADD, 0x6B, + APP_KEY_SEPARATOR, 0x6C, APP_KEY_SUBTRACT, 0x6D, APP_KEY_DECIMAL, 0x6E, APP_KEY_DIVIDE, 0x6F, APP_KEY_F1, 0x70, APP_KEY_F2, 0x71, + APP_KEY_F3, 0x72, APP_KEY_F4, 0x73, APP_KEY_F5, 0x74, APP_KEY_F6, 0x75, APP_KEY_F7, 0x76, APP_KEY_F8, 0x77, APP_KEY_F9, 0x78, + APP_KEY_F10, 0x79, APP_KEY_F11, 0x7A, APP_KEY_F12, 0x7B, APP_KEY_F13, 0x7C, APP_KEY_F14, 0x7D, APP_KEY_F15, 0x7E, APP_KEY_F16, 0x7F, + APP_KEY_F17, 0x80, APP_KEY_F18, 0x81, APP_KEY_F19, 0x82, APP_KEY_F20, 0x83, APP_KEY_F21, 0x84, APP_KEY_F22, 0x85, APP_KEY_F23, 0x86, + APP_KEY_F24, 0x87, -1, 0x88, -1, 0x89, -1, 0x8A, -1, 0x8B, -1, 0x8C, -1, 0x8D, -1, 0x8E, -1, 0x8F, APP_KEY_NUMLOCK, 0x90, + APP_KEY_SCROLL, 0x91, -1, 0x92, -1, 0x93, -1, 0x94, -1, 0x95, -1, 0x96, -1, 0x97, -1, 0x98, -1, 0x99, -1, 0x9A, -1, 0x9B, -1, 0x9C, -1, 0x9D, + -1, 0x9E, -1, 0x9F, APP_KEY_LSHIFT, 0xA0, APP_KEY_RSHIFT, 0xA1, APP_KEY_LCONTROL, 0xA2, APP_KEY_RCONTROL, 0xA3, APP_KEY_LMENU, 0xA4, + APP_KEY_RMENU, 0xA5, APP_KEY_BROWSER_BACK, 0xA6, APP_KEY_BROWSER_FORWARD, 0xA7, APP_KEY_BROWSER_REFRESH, 0xA8, APP_KEY_BROWSER_STOP, 0xA9, + APP_KEY_BROWSER_SEARCH, 0xAA, APP_KEY_BROWSER_FAVORITES, 0xAB, APP_KEY_BROWSER_HOME, 0xAC, APP_KEY_VOLUME_MUTE, 0xAD, + APP_KEY_VOLUME_DOWN, 0xAE, APP_KEY_VOLUME_UP, 0xAF, APP_KEY_MEDIA_NEXT_TRACK, 0xB0, APP_KEY_MEDIA_PREV_TRACK, 0xB1, APP_KEY_MEDIA_STOP, 0xB2, + APP_KEY_MEDIA_PLAY_PAUSE, 0xB3, APP_KEY_LAUNCH_MAIL, 0xB4, APP_KEY_LAUNCH_MEDIA_SELECT, 0xB5, APP_KEY_LAUNCH_APP1, 0xB6, + APP_KEY_LAUNCH_APP2, 0xB7, -1, 0xB8, -1, 0xB9, APP_KEY_OEM_1, 0xBA, APP_KEY_OEM_PLUS, 0xBB, APP_KEY_OEM_COMMA, 0xBC, APP_KEY_OEM_MINUS, 0xBD, + APP_KEY_OEM_PERIOD, 0xBE, APP_KEY_OEM_2, 0xBF, APP_KEY_OEM_3, 0xC0, -1, 0xC1, -1, 0xC2, -1, 0xC3, -1, 0xC4, -1, 0xC5, -1, 0xC6, -1, 0xC7, + -1, 0xC8, -1, 0xC9, -1, 0xCA, -1, 0xCB, -1, 0xCC, -1, 0xCD, -1, 0xCE, -1, 0xCF, -1, 0xD0, -1, 0xD1, -1, 0xD2, -1, 0xD3, -1, 0xD4, -1, 0xD5, + -1, 0xD6, -1, 0xD7, -1, 0xD8, -1, 0xD9, -1, 0xDA, APP_KEY_OEM_4, 0xDB, APP_KEY_OEM_5, 0xDC, APP_KEY_OEM_6, 0xDD, APP_KEY_OEM_7, 0xDE, + APP_KEY_OEM_8, 0xDF, -1, 0xE0, -1, 0xE1, APP_KEY_OEM_102, 0xE2, -1, 0xE3, -1, 0xE4, APP_KEY_PROCESSKEY, 0xE5, -1, 0xE6, -1, 0xE7, -1, 0xE8, + -1, 0xE9, -1, 0xEA, -1, 0xEB, -1, 0xEC, -1, 0xED, -1, 0xEE, -1, 0xEF, -1, 0xF0, -1, 0xF1, -1, 0xF2, -1, 0xF3, -1, 0xF4, -1, 0xF5, + APP_KEY_ATTN, 0xF6, APP_KEY_CRSEL, 0xF7, APP_KEY_EXSEL, 0xF8, APP_KEY_EREOF, 0xF9, APP_KEY_PLAY, 0xFA, APP_KEY_ZOOM, 0xFB, + APP_KEY_NONAME, 0xFC, APP_KEY_PA1, 0xFD, APP_KEY_OEM_CLEAR, 0xFE, -1, 0xFF, }; + if( vkcode < 0 || vkcode >= sizeof( map ) / ( 2 * sizeof( *map ) ) ) return APP_KEY_INVALID; + if( map[ vkcode * 2 + 1 ] != vkcode ) + { + app_log( app, APP_LOG_LEVEL_ERROR, "Keymap definition error" ); + return APP_KEY_INVALID; + } + return (app_key_t) map[ vkcode * 2 ]; + } + + +static void app_internal_add_input_event( app_t* app, app_input_event_t* event ) + { + if( app->has_focus ) + { + if( app->input_count < sizeof( app->input_events ) / sizeof( *app->input_events ) ) + app->input_events[ app->input_count++ ] = *event; + } + } + + +static RECT app_internal_rect( int left, int top, int right, int bottom ) + { + RECT r; r.left = left; r.top = top; r.right = right; r.bottom = bottom; return r; + } + + +static BOOL app_internal_tablet_init( app_t* app ) + { + app->tablet.wintab_dll = LoadLibraryA( "Wintab32.dll" ); + if( !app->tablet.wintab_dll ) return FALSE; + + app->tablet.WTInfo = ( UINT (WINAPI*)( UINT, UINT, LPVOID ) ) + (uintptr_t ) GetProcAddress( app->tablet.wintab_dll, "WTInfoA" ); + app->tablet.WTOpen = ( APP_HCTX (WINAPI*)( HWND, APP_LOGCONTEXTA*, BOOL ) ) + (uintptr_t ) GetProcAddress( app->tablet.wintab_dll, "WTOpenA" ); + app->tablet.WTClose = ( BOOL (WINAPI*)( APP_HCTX ) ) + (uintptr_t ) GetProcAddress( app->tablet.wintab_dll, "WTClose" ); + app->tablet.WTEnable = ( BOOL (WINAPI*)( APP_HCTX, BOOL ) ) + (uintptr_t ) GetProcAddress( app->tablet.wintab_dll, "WTEnable" ); + app->tablet.WTPacket = ( BOOL (WINAPI*)( APP_HCTX, UINT, LPVOID ) ) + (uintptr_t ) GetProcAddress( app->tablet.wintab_dll, "WTPacket" ); + + if( !app->tablet.WTInfo( 0 ,0, NULL ) ) return FALSE; // checks if tablet is present + + APP_LOGCONTEXTA log_context; + memset( &log_context, 0, sizeof( log_context ) ); + app->tablet.WTInfo( APP_WTI_DDCTXS, 0, &log_context ); + + APP_AXIS pressure; + memset( &pressure, 0, sizeof( pressure ) ); + app->tablet.WTInfo( APP_WTI_DEVICES, APP_DVC_NPRESSURE, &pressure ); + app->tablet.max_pressure = pressure.axMax; + + log_context.lcPktData = APP_PACKETDATA; + log_context.lcOptions |= APP_CXO_MESSAGES; + log_context.lcPktMode = APP_PACKETMODE; + log_context.lcMoveMask = APP_PACKETDATA; + log_context.lcBtnUpMask = log_context.lcBtnDnMask; + log_context.lcOutOrgX = 0; + log_context.lcOutOrgY = 0; + log_context.lcOutExtX = GetSystemMetrics( SM_CXSCREEN) ; + log_context.lcOutExtY = -GetSystemMetrics( SM_CYSCREEN ); + + app->tablet.context = app->tablet.WTOpen( app->hwnd, &log_context, FALSE ); + if( !app->tablet.context ) return FALSE; + return TRUE; +} + + +static BOOL app_internal_tablet_term( app_t* app ) + { + if( app->tablet.context ) app->tablet.WTClose( app->tablet.context ); + if( app->tablet.wintab_dll ) FreeLibrary( app->tablet.wintab_dll ); + return TRUE; + } + + + +static LRESULT CALLBACK app_internal_wndproc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) + { + app_t* app = (app_t*)(uintptr_t) GetWindowLongPtr( hwnd, GWLP_USERDATA ); + if( !app ) return DefWindowProc( hwnd, message, wparam, lparam); + + app_input_event_t input_event; + + switch( message ) + { + case WM_CHAR: + input_event.type = APP_INPUT_CHAR; input_event.data.char_code = (char) wparam; + app_internal_add_input_event( app, &input_event ); + break; + case WM_LBUTTONDOWN: + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_LBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_LBUTTONUP: + input_event.type = APP_INPUT_KEY_UP; input_event.data.key = APP_KEY_LBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_LBUTTONDBLCLK: + input_event.type = APP_INPUT_DOUBLE_CLICK; input_event.data.key = APP_KEY_LBUTTON; + app_internal_add_input_event( app, &input_event ); + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_LBUTTON; + app_internal_add_input_event(app, &input_event); + break; + case WM_RBUTTONDOWN: + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_RBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_RBUTTONUP: + input_event.type = APP_INPUT_KEY_UP; input_event.data.key = APP_KEY_RBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_RBUTTONDBLCLK: + input_event.type = APP_INPUT_DOUBLE_CLICK; input_event.data.key = APP_KEY_RBUTTON; + app_internal_add_input_event( app, &input_event ); + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_RBUTTON; + app_internal_add_input_event(app, &input_event); + break; + case WM_MBUTTONDOWN: + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_MBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_MBUTTONUP: + input_event.type = APP_INPUT_KEY_UP; input_event.data.key = APP_KEY_MBUTTON; + app_internal_add_input_event( app, &input_event ); + break; + case WM_MBUTTONDBLCLK: + input_event.type = APP_INPUT_DOUBLE_CLICK; input_event.data.key = APP_KEY_MBUTTON; + app_internal_add_input_event( app, &input_event ); + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = APP_KEY_MBUTTON; + app_internal_add_input_event(app, &input_event); + break; + case WM_XBUTTONDOWN: + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = HIWORD( wparam ) == 1 ? APP_KEY_XBUTTON1 :APP_KEY_XBUTTON2; + app_internal_add_input_event( app, &input_event ); + break; + case WM_XBUTTONUP: + input_event.type = APP_INPUT_KEY_UP; input_event.data.key = HIWORD( wparam ) == 1 ? APP_KEY_XBUTTON1 :APP_KEY_XBUTTON2; + app_internal_add_input_event( app, &input_event ); + break; + case WM_XBUTTONDBLCLK: + input_event.type = APP_INPUT_DOUBLE_CLICK; input_event.data.key = HIWORD( wparam ) == 1 ? APP_KEY_XBUTTON1 :APP_KEY_XBUTTON2; + app_internal_add_input_event( app, &input_event ); + input_event.type = APP_INPUT_KEY_DOWN; input_event.data.key = HIWORD( wparam ) == 1 ? APP_KEY_XBUTTON1 :APP_KEY_XBUTTON2; + app_internal_add_input_event(app, &input_event); + break; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + input_event.type = APP_INPUT_KEY_DOWN; + WPARAM vkcode = wparam; + UINT scancode = (UINT)( ( lparam & 0x00ff0000 ) >> 16 ); + int extended = ( lparam & 0x01000000 ) != 0; + UINT const maptype = 3; //MAPVK_VSC_TO_VK_EX + switch( vkcode ) + { + case VK_SHIFT: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) MapVirtualKey( scancode, maptype ) ); + app_internal_add_input_event( app, &input_event ); + break; + case VK_CONTROL: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, extended ? VK_RCONTROL : VK_LCONTROL ); + app_internal_add_input_event( app, &input_event ); + break; + case VK_MENU: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, extended ? VK_RMENU : VK_LMENU ); + app_internal_add_input_event( app, &input_event ); + break; + default: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + break; + } + } break; + + case WM_HOTKEY: + { + input_event.type = APP_INPUT_KEY_DOWN; + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + if( app->input_count < sizeof( app->input_events ) / sizeof( *app->input_events ) ) + app->input_events[ app->input_count++ ] = input_event; + input_event.type = APP_INPUT_KEY_UP; + if( app->input_count < sizeof( app->input_events ) / sizeof( *app->input_events ) ) + app->input_events[ app->input_count++ ] = input_event; + } break; + + case WM_SYSKEYUP: + case WM_KEYUP: + { + input_event.type = APP_INPUT_KEY_UP; + WPARAM vkcode = wparam; + UINT scancode = (UINT)( ( lparam & 0x00ff0000 ) >> 16 ); + int extended = ( lparam & 0x01000000 ) != 0; + UINT const maptype = 3; //MAPVK_VSC_TO_VK_EX + switch( vkcode ) + { + case VK_SHIFT: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) MapVirtualKey( scancode, maptype ) ); + app_internal_add_input_event( app, &input_event ); + break; + case VK_CONTROL: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, extended ? VK_RCONTROL : VK_LCONTROL ); + app_internal_add_input_event( app, &input_event ); + break; + case VK_MENU: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + + input_event.data.key = app_internal_vkcode_to_appkey( app, extended ? VK_RMENU : VK_LMENU ); + app_internal_add_input_event( app, &input_event ); + break; + default: + input_event.data.key = app_internal_vkcode_to_appkey( app, (int) wparam ); + app_internal_add_input_event( app, &input_event ); + break; + } + } break; + + /* + case WM_GESTURE: + printf( "Gesture\n" ); + if( app->has_focus ) + { + GESTUREINFO gesture_info = { sizeof( gesture_info ) }; + if( GetGestureInfo( (HGESTUREINFO) lparam, &gesture_info ) ) + { + printf( "Gesture Info\n" ); + static int prev = 0; + if( gesture_info.dwID == GID_PAN ) + { + if( gesture_info.dwFlags & GF_BEGIN ) + { + prev = gesture_info.ptsLocation.y; + } + else + { + int dist = gesture_info.ptsLocation.y - prev; + prev = gesture_info.ptsLocation.y; + float wheel_delta = (float)( dist / 200.0f ); + printf( "Pan/Inertia: %d\n", dist ); + if( app->input_count > 0 && app->input_events[ app->input_count - 1 ].type == APP_INPUT_SCROLL_WHEEL ) + { + app_input_event_t* event = &app->input_events[ app->input_count - 1 ]; + event->data.wheel_delta += wheel_delta; + } + else + { + input_event.type = APP_INPUT_SCROLL_WHEEL; + input_event.data.wheel_delta = wheel_delta; + app_internal_add_input_event( app, &input_event ); + } + } + } + } + } + break; + */ + case WM_MOUSEWHEEL: + if( app->has_focus ) + { + float const microsoft_mouse_wheel_constant = 120.0f; + float wheel_delta = ( (float) GET_WHEEL_DELTA_WPARAM( wparam ) ) / microsoft_mouse_wheel_constant; + if( app->input_count > 0 && app->input_events[ app->input_count - 1 ].type == APP_INPUT_SCROLL_WHEEL ) + { + app_input_event_t* event = &app->input_events[ app->input_count - 1 ]; + event->data.wheel_delta += wheel_delta; + } + else + { + input_event.type = APP_INPUT_SCROLL_WHEEL; + input_event.data.wheel_delta = wheel_delta; + app_internal_add_input_event( app, &input_event ); + } + } + break; + + case WM_MOUSEMOVE: + if( app->has_focus ) + { + POINT p; + GetCursorPos( &p ); + ScreenToClient( app->hwnd, &p ); + int mouse_x = p.x; + int mouse_y = p.y; + + input_event.type = APP_INPUT_MOUSE_MOVE; + input_event.data.mouse_pos.x = mouse_x; + input_event.data.mouse_pos.y = mouse_y; + app_internal_add_input_event( app, &input_event ); + } + break; + + case WM_INPUT: + { + if( app->GetRawInputDataPtr ) + { + RAWINPUT raw; + UINT size = sizeof( raw ); + app->GetRawInputDataPtr( (HRAWINPUT) lparam, RID_INPUT, &raw, &size, sizeof( RAWINPUTHEADER ) ); + if( raw.header.dwType == RIM_TYPEMOUSE ) + { + if( ( raw.data.mouse.usFlags & 1 ) == MOUSE_MOVE_RELATIVE) + { + float dx = (float) raw.data.mouse.lLastX; + float dy = (float) raw.data.mouse.lLastY; + input_event.type = APP_INPUT_MOUSE_DELTA; + input_event.data.mouse_delta.x = dx; + input_event.data.mouse_delta.y = dy; + app_internal_add_input_event( app, &input_event ); + } + } + } + break; + } + + case APP_WT_PACKET: + { + APP_PACKET packet; + memset( &packet, 0, sizeof( packet ) ); + if( (APP_HCTX) lparam == app->tablet.context && + app->tablet.WTPacket( app->tablet.context, (UINT) wparam, &packet ) ) + { + POINT p; + p.x = packet.pkX; + p.y = packet.pkY; + ScreenToClient( app->hwnd, &p ); + int pen_x = p.x; + int pen_y = p.y; + + input_event.type = APP_INPUT_TABLET; + input_event.data.tablet.x = pen_x; + input_event.data.tablet.y = pen_y; + input_event.data.tablet.pressure = (float) packet.pkNormalPressure / (float) app->tablet.max_pressure; + input_event.data.tablet.tip = ( packet.pkButtons & 1 ) ? APP_PRESSED : APP_NOT_PRESSED; + input_event.data.tablet.lower = ( packet.pkButtons & 2 ) ? APP_PRESSED : APP_NOT_PRESSED; + input_event.data.tablet.upper = ( packet.pkButtons & 4 ) ? APP_PRESSED : APP_NOT_PRESSED; + app_internal_add_input_event( app, &input_event ); + } + } break; + + case WM_SETCURSOR: + if( LOWORD( lparam ) == HTCLIENT ) + { + SetCursor( app->current_pointer ); + return 0; + } + break; + + + case WM_WINDOWPOSCHANGED: + { + if( app->screenmode == APP_SCREENMODE_FULLSCREEN ) + { + RECT wr, cr; + GetWindowRect( app->hwnd, &wr ); + GetClientRect( app->hwnd, &cr ); + if( wr.right - wr.left == cr.right - cr.left && wr.bottom - wr.top == cr.bottom - cr.top ) + { + if( cr.right - cr.left != app->fullscreen_width || cr.bottom - cr.top != app->fullscreen_height ) + app_screenmode( app, APP_SCREENMODE_WINDOW ); + } + } + + if( app->clip_cursor ) + { + RECT r = app->clip_rect; + ClientToScreen( app->hwnd, (POINT*)&r ); + ClientToScreen( app->hwnd, ( (POINT*)&r ) + 1 ); + ClipCursor( &r ); + } + + } break; + + case WM_SIZE: + { + if( wparam == SIZE_MAXIMIZED ) + { + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + app->windowed_x = placement.rcNormalPosition.left; + app->windowed_y = placement.rcNormalPosition.top; + app->windowed_w = placement.rcNormalPosition.right - placement.rcNormalPosition.left; + app->windowed_h = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top; + } + + RECT r; + GetClientRect( app->hwnd, &r ); + app_internal_opengl_resize( &app->gl, r.right - r.left, r.bottom - r.top ); + } break; + + + case WM_ACTIVATEAPP: + app->has_focus = (BOOL) wparam; + if( app->has_focus ) + { + app->is_minimized = FALSE; + if( app->clip_cursor ) + { + RECT r = app->clip_rect; + ClientToScreen( app->hwnd, (POINT*)&r ); + ClientToScreen( app->hwnd, ( (POINT*)&r ) + 1 ); + ClipCursor( &r ); + } + } + else + { + ClipCursor( NULL ); + } + + break; + + case WM_SYSCOMMAND: + if( ( wparam & 0xFFF0 ) == SC_MINIMIZE ) app->is_minimized = TRUE; + break; + + case WM_CLOSE: + app->closed = TRUE; + return 0; + break; + + } + + if( app->user_wndproc ) + return app->user_wndproc( app, hwnd, message, wparam, lparam ); + + return DefWindowProc( hwnd, message, wparam, lparam); + } + + +static BOOL CALLBACK app_internal_monitorenumproc( HMONITOR hmonitor, HDC dc, LPRECT rect, LPARAM data ) + { + (void) dc; + app_t* app = (app_t*) data; + + if( app->display_count >= sizeof( app->displays ) / sizeof( *app->displays ) ) return FALSE; + app->displays_hmonitor[ app->display_count ] = hmonitor; + app_display_t* display = &app->displays[ app->display_count++ ]; + + display->x = rect->left; + display->y = rect->top; + display->width = rect->right - rect->left; + display->height = rect->bottom - rect->top; + + #ifdef __cplusplus + MONITORINFOEXA mi = {}; + mi.cbSize = sizeof( MONITORINFOEXA ); + BOOL res = GetMonitorInfoA( hmonitor, &mi ); + if( res && strlen( mi.szDevice ) >= sizeof( display->id ) ) res = FALSE; + strcpy( display->id, res ? mi.szDevice : "" ) ; + #else + MONITORINFOEXA mi = { sizeof( MONITORINFOEXA ) }; + BOOL res = GetMonitorInfoA( hmonitor, (LPMONITORINFO)&mi ); + if( res && strlen( mi.szDevice ) >= sizeof( display->id ) ) res = FALSE; + strcpy( display->id, res ? mi.szDevice : "" ) ; + #endif + + return TRUE; + } + + +static void app_internal_app_default_cursor( app_t* app ) + { + APP_U32 pointer_pixels[ 256 * 256 ]; + int pointer_width, pointer_height, pointer_hotspot_x, pointer_hotspot_y; + app_pointer_default( app, &pointer_width, &pointer_height, pointer_pixels, &pointer_hotspot_x, &pointer_hotspot_y ); + app_pointer( app, pointer_width, pointer_height, pointer_pixels, pointer_hotspot_x, pointer_hotspot_y ); + } + + +#pragma warning( push ) +#pragma warning( disable: 4533 ) // initialization of 'wc' is skipped by 'goto init_failed' + +int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ) + { + int result = 0xff; + //SetProcessDPIAware(); + + // Init app instance + app_t* app = (app_t*) APP_MALLOC( memctx, sizeof( app_t ) ); + memset( app, 0, sizeof( *app ) ); + app->memctx = memctx; + app->logctx = logctx; + app->fatalctx = fatalctx; + app->interpolation = APP_INTERPOLATION_LINEAR; + app->screenmode = APP_SCREENMODE_FULLSCREEN; + + // Log start message + char msg[ 64 ]; + time_t t = time( NULL ); + struct tm* start = localtime( &t ); + sprintf( msg, "Application started %02d:%02d:%02d %04d-%02d-%02d.", + start->tm_hour, start->tm_min, start->tm_sec, start->tm_year + 1900, start->tm_mon + 1, start->tm_mday ); + app_log( app, APP_LOG_LEVEL_INFO, msg ); + + // Increase timing precision + #ifndef __TINYC__ + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeBeginPeriod( tc.wPeriodMin ); + #endif + + // Get instance handle + app->hinstance = GetModuleHandle( NULL ); + + // Retrieve the path of our executable + GetModuleFileNameA( 0, app->exe_path, sizeof( app->exe_path ) ); + + HMODULE shell32 = LoadLibraryA( "shell32.dll" ); + if( shell32 ) + { + HRESULT (__stdcall *SHGetFolderPathAPtr)(HWND, int, HANDLE, DWORD, LPSTR ) = + (HRESULT (__stdcall*)(HWND, int, HANDLE, DWORD, LPSTR ) ) (uintptr_t) + GetProcAddress( shell32, "SHGetFolderPathA" ); + + if( SHGetFolderPathAPtr ) + { + #define APP_CSIDL_PERSONAL 0x0005 // My Documents + #define APP_CSIDL_COMMON_APPDATA 0x0023 // All Users\Application Data + #define APP_CSIDL_FLAG_CREATE 0x8000 + + // Retrieve user data path + SHGetFolderPathAPtr( NULL, APP_CSIDL_PERSONAL | APP_CSIDL_FLAG_CREATE, NULL, 0, app->userdata_path ); + + // Retrieve app data path + SHGetFolderPathAPtr( NULL, APP_CSIDL_COMMON_APPDATA | APP_CSIDL_FLAG_CREATE, NULL, 0, app->appdata_path ); + + #undef APP_CSIDL_PERSONAL + #undef APP_CSIDL_COMMON_APPDATA + #undef APP_CSIDL_FLAG_CREATE + } + + FreeLibrary( shell32 ); + } + + // Get command line string + app->cmdline = GetCommandLineA(); + + // Load a default Arrow cursor + app_internal_app_default_cursor( app ); + + // Load first icon in the exe and use as app icon + app->icon = LoadIconA( app->hinstance , MAKEINTRESOURCEA( 1 ) ); + + // List all displays + app->display_count = 0; + EnumDisplayMonitors( NULL, NULL, app_internal_monitorenumproc, (LPARAM) app ); + if( app->display_count <= 0 ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to get display info" ); goto init_failed; } + + // Setup the main application window + app->windowed_w = app->displays[ 0 ].width - app->displays[ 0 ].width / 6; + app->windowed_h = app->displays[ 0 ].height - app->displays[ 0 ].height / 6; + app->windowed_x = ( app->displays[ 0 ].width - app->windowed_w ) / 2; + app->windowed_y = ( app->displays[ 0 ].height - app->windowed_h ) / 2; + + app->fullscreen_width = app->displays[ 0 ].width; + app->fullscreen_height = app->displays[ 0 ].height; + + RECT winrect = app_internal_rect( app->windowed_x, app->windowed_y, + app->windowed_x + app->windowed_w, app->windowed_y + app->windowed_h ); + AdjustWindowRect( &winrect, APP_WINDOWED_WS_STYLE | WS_VISIBLE, FALSE ); + + WNDCLASSEX wc = { sizeof( WNDCLASSEX ), CS_DBLCLKS | CS_OWNDC , + (WNDPROC) app_internal_wndproc, 0, 0, 0, 0, 0, 0, 0, TEXT( "app_wc" ), 0 }; + wc.hInstance = app->hinstance; wc.hIcon = app->icon; wc.hCursor = app->current_pointer; + wc.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH ); wc.hIconSm = app->icon; + RegisterClassEx( &wc ); + app->hwnd = CreateWindowEx( APP_WINDOWED_WS_EX_STYLE, wc.lpszClassName, 0, APP_WINDOWED_WS_STYLE, app->windowed_x, app->windowed_y, + winrect.right - winrect.left, winrect.bottom - winrect.top, (HWND) 0, (HMENU) 0, app->hinstance, 0 ); + if( !app->hwnd ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to create window." ); goto init_failed; } + app->hdc = GetDC( app->hwnd ); + app->has_focus = TRUE; + app->is_minimized = FALSE; + + // Store app pointer with window + #pragma warning( push ) + #pragma warning( disable: 4244 ) // conversion from 'LONG_PTR' to 'LONG', possible loss of data + SetWindowLongPtr( app->hwnd, GWLP_USERDATA, (LONG_PTR) app ); + #pragma warning( pop ) + + + #ifdef APP_ENABLE_MEDIA_KEYS + int const APP_MOD_NOREPEAT = 0x4000; + RegisterHotKey( app->hwnd, VK_VOLUME_MUTE, APP_MOD_NOREPEAT, VK_VOLUME_MUTE ); + RegisterHotKey( app->hwnd, VK_VOLUME_DOWN, APP_MOD_NOREPEAT, VK_VOLUME_DOWN ); + RegisterHotKey( app->hwnd, VK_VOLUME_UP, APP_MOD_NOREPEAT, VK_VOLUME_UP ); + RegisterHotKey( app->hwnd, VK_MEDIA_NEXT_TRACK, APP_MOD_NOREPEAT, VK_MEDIA_NEXT_TRACK ); + RegisterHotKey( app->hwnd, VK_MEDIA_PREV_TRACK, APP_MOD_NOREPEAT, VK_MEDIA_PREV_TRACK ); + RegisterHotKey( app->hwnd, VK_MEDIA_STOP, APP_MOD_NOREPEAT, VK_MEDIA_STOP ); + RegisterHotKey( app->hwnd, VK_MEDIA_PLAY_PAUSE, APP_MOD_NOREPEAT, VK_MEDIA_PLAY_PAUSE ); + #endif + + ShowWindow( app->hwnd, SW_HIDE ); + // Windows specific OpenGL initialization + app->gl_dll = LoadLibraryA( "opengl32.dll" ); + if( !app->gl_dll ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to load opengl32.dll" ); goto init_failed; } + app->wglGetProcAddress = (PROC(APP_GLCALLTYPE*)(LPCSTR)) (uintptr_t) GetProcAddress( app->gl_dll, "wglGetProcAddress" ); + if( !app->wglGetProcAddress ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to find wglGetProcAddress" ); goto init_failed; } + app->wglCreateContext = (HGLRC(APP_GLCALLTYPE*)(HDC)) (uintptr_t) GetProcAddress( app->gl_dll, "wglCreateContext" ); + if( !app->wglCreateContext ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to find wglCreateContext" ); goto init_failed; } + app->wglDeleteContext = (BOOL(APP_GLCALLTYPE*)(HGLRC)) (uintptr_t) GetProcAddress( app->gl_dll, "wglDeleteContext" ); + if( !app->wglDeleteContext ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to find wglDeleteContext" ); goto init_failed; } + app->wglMakeCurrent = (BOOL(APP_GLCALLTYPE*)(HDC, HGLRC)) (uintptr_t) GetProcAddress( app->gl_dll, "wglMakeCurrent" ); + if( !app->wglMakeCurrent ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to find wglMakeCurrent" ); goto init_failed; } + + PIXELFORMATDESCRIPTOR pfd; + memset( &pfd, 0, sizeof( pfd ) ); + pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR ); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + BOOL res = SetPixelFormat( app->hdc, ChoosePixelFormat( app->hdc, &pfd ), &pfd ); + if( !res ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to set pixel format" ); goto init_failed; } + + app->gl_context = app->wglCreateContext( app->hdc ); + if( !app->gl_context ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to create OpenGL context" ); goto init_failed; } + res = app->wglMakeCurrent( app->hdc, app->gl_context ); + if( !res ) { app_log( app, APP_LOG_LEVEL_ERROR, "Failed to activate OpenGl Context" ); goto init_failed; } + + app->wglSwapIntervalEXT = (BOOL (APP_GLCALLTYPE*)(int)) (uintptr_t) app->wglGetProcAddress( "wglSwapIntervalEXT" ); + if( app->wglSwapIntervalEXT ) app->wglSwapIntervalEXT( 1 ); + + // Attempt to bind opengl functions using GetProcAddress + app->gl.CreateShader = ( APP_GLuint (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) GetProcAddress( app->gl_dll, "glCreateShader" ); + app->gl.ShaderSource = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLsizei, APP_GLchar const* const*, APP_GLint const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glShaderSource" ); + app->gl.CompileShader = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glCompileShader" ); + app->gl.GetShaderiv = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLenum, APP_GLint*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGetShaderiv" ); + app->gl.CreateProgram = ( APP_GLuint (APP_GLCALLTYPE*) (void) ) (uintptr_t) GetProcAddress( app->gl_dll, "glCreateProgram" ); + app->gl.AttachShader = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glAttachShader" ); + app->gl.BindAttribLocation = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLuint, APP_GLchar const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glBindAttribLocation" ); + app->gl.LinkProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glLinkProgram" ); + app->gl.GetProgramiv = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLenum, APP_GLint*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGetProgramiv" ); + app->gl.GenBuffers = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGenBuffers" ); + app->gl.BindBuffer = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glBindBuffer" ); + app->gl.EnableVertexAttribArray = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glEnableVertexAttribArray" ); + app->gl.VertexAttribPointer = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLint, APP_GLenum, APP_GLboolean, APP_GLsizei, void const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glVertexAttribPointer" ); + app->gl.GenTextures = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGenTextures" ); + app->gl.Enable = ( void (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) GetProcAddress( app->gl_dll, "glEnable" ); + app->gl.ActiveTexture = ( void (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) GetProcAddress( app->gl_dll, "glActiveTexture" ); + app->gl.BindTexture = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glBindTexture" ); + app->gl.TexParameteri = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLenum, APP_GLint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glTexParameteri" ); + app->gl.DeleteBuffers = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glDeleteBuffers" ); + app->gl.DeleteTextures = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glDeleteTextures" ); + app->gl.BufferData = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLsizeiptr, void const *, APP_GLenum) ) (uintptr_t) GetProcAddress( app->gl_dll, "glBufferData" ); + app->gl.UseProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glUseProgram" ); + app->gl.Uniform1i = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glUniform1i" ); + app->gl.Uniform3f = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLfloat, APP_GLfloat, APP_GLfloat) ) (uintptr_t) GetProcAddress( app->gl_dll, "glUniform3f" ); + app->gl.GetUniformLocation = ( APP_GLint (APP_GLCALLTYPE*) (APP_GLuint, APP_GLchar const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGetUniformLocation" ); + app->gl.TexImage2D = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLint, APP_GLint, APP_GLsizei, APP_GLsizei, APP_GLint, APP_GLenum, APP_GLenum, void const*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glTexImage2D" ); + app->gl.ClearColor = ( void (APP_GLCALLTYPE*) (APP_GLfloat, APP_GLfloat, APP_GLfloat, APP_GLfloat) ) (uintptr_t) GetProcAddress( app->gl_dll, "glClearColor" ); + app->gl.Clear = ( void (APP_GLCALLTYPE*) (APP_GLbitfield) ) (uintptr_t) GetProcAddress( app->gl_dll, "glClear" ); + app->gl.DrawArrays = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLint, APP_GLsizei) ) (uintptr_t) GetProcAddress( app->gl_dll, "glDrawArrays" ); + app->gl.Viewport = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLint, APP_GLsizei, APP_GLsizei) ) (uintptr_t) GetProcAddress( app->gl_dll, "glViewport" ); + app->gl.DeleteShader = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glDeleteShader" ); + app->gl.DeleteProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) GetProcAddress( app->gl_dll, "glDeleteProgram" ); + #ifdef APP_REPORT_SHADER_ERRORS + app->gl.GetShaderInfoLog = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLsizei, APP_GLsizei*, APP_GLchar*) ) (uintptr_t) GetProcAddress( app->gl_dll, "glGetShaderInfoLog" ); + #endif + + // Any opengl functions which didn't bind, try binding them using wglGetProcAddrss + if( !app->gl.CreateShader ) app->gl.CreateShader = ( APP_GLuint (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) app->wglGetProcAddress( "glCreateShader" ); + if( !app->gl.ShaderSource ) app->gl.ShaderSource = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLsizei, APP_GLchar const* const*, APP_GLint const*) ) (uintptr_t) app->wglGetProcAddress( "glShaderSource" ); + if( !app->gl.CompileShader ) app->gl.CompileShader = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glCompileShader" ); + if( !app->gl.GetShaderiv ) app->gl.GetShaderiv = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLenum, APP_GLint*) ) (uintptr_t) app->wglGetProcAddress( "glGetShaderiv" ); + if( !app->gl.CreateProgram ) app->gl.CreateProgram = ( APP_GLuint (APP_GLCALLTYPE*) (void) ) (uintptr_t) app->wglGetProcAddress( "glCreateProgram" ); + if( !app->gl.AttachShader ) app->gl.AttachShader = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glAttachShader" ); + if( !app->gl.BindAttribLocation ) app->gl.BindAttribLocation = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLuint, APP_GLchar const*) ) (uintptr_t) app->wglGetProcAddress( "glBindAttribLocation" ); + if( !app->gl.LinkProgram ) app->gl.LinkProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glLinkProgram" ); + if( !app->gl.GetProgramiv ) app->gl.GetProgramiv = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLenum, APP_GLint*) ) (uintptr_t) app->wglGetProcAddress( "glGetProgramiv" ); + if( !app->gl.GenBuffers ) app->gl.GenBuffers = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint*) ) (uintptr_t) app->wglGetProcAddress( "glGenBuffers" ); + if( !app->gl.BindBuffer ) app->gl.BindBuffer = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glBindBuffer" ); + if( !app->gl.EnableVertexAttribArray ) app->gl.EnableVertexAttribArray = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glEnableVertexAttribArray" ); + if( !app->gl.VertexAttribPointer ) app->gl.VertexAttribPointer = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLint, APP_GLenum, APP_GLboolean, APP_GLsizei, void const*) ) (uintptr_t) app->wglGetProcAddress( "glVertexAttribPointer" ); + if( !app->gl.GenTextures ) app->gl.GenTextures = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint*) ) (uintptr_t) app->wglGetProcAddress( "glGenTextures" ); + if( !app->gl.Enable ) app->gl.Enable = ( void (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) app->wglGetProcAddress( "glEnable" ); + if( !app->gl.ActiveTexture ) app->gl.ActiveTexture = ( void (APP_GLCALLTYPE*) (APP_GLenum) ) (uintptr_t) app->wglGetProcAddress( "glActiveTexture" ); + if( !app->gl.BindTexture ) app->gl.BindTexture = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glBindTexture" ); + if( !app->gl.TexParameteri ) app->gl.TexParameteri = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLenum, APP_GLint) ) (uintptr_t) app->wglGetProcAddress( "glTexParameteri" ); + if( !app->gl.DeleteBuffers ) app->gl.DeleteBuffers = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint const*) ) (uintptr_t) app->wglGetProcAddress( "glDeleteBuffers" ); + if( !app->gl.DeleteTextures ) app->gl.DeleteTextures = ( void (APP_GLCALLTYPE*) (APP_GLsizei, APP_GLuint const*) ) (uintptr_t) app->wglGetProcAddress( "glDeleteTextures" ); + if( !app->gl.BufferData ) app->gl.BufferData = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLsizeiptr, void const *, APP_GLenum) ) (uintptr_t) app->wglGetProcAddress( "glBufferData" ); + if( !app->gl.UseProgram ) app->gl.UseProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glUseProgram" ); + if( !app->gl.Uniform1i ) app->gl.Uniform1i = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLint) ) (uintptr_t) app->wglGetProcAddress( "glUniform1i" ); + if( !app->gl.Uniform3f ) app->gl.Uniform3f = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLfloat, APP_GLfloat, APP_GLfloat) ) (uintptr_t) app->wglGetProcAddress( "glUniform3f" ); + if( !app->gl.GetUniformLocation ) app->gl.GetUniformLocation = ( APP_GLint (APP_GLCALLTYPE*) (APP_GLuint, APP_GLchar const*) ) (uintptr_t) app->wglGetProcAddress( "glGetUniformLocation" ); + if( !app->gl.TexImage2D ) app->gl.TexImage2D = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLint, APP_GLint, APP_GLsizei, APP_GLsizei, APP_GLint, APP_GLenum, APP_GLenum, void const*) ) (uintptr_t) app->wglGetProcAddress( "glTexImage2D" ); + if( !app->gl.ClearColor ) app->gl.ClearColor = ( void (APP_GLCALLTYPE*) (APP_GLfloat, APP_GLfloat, APP_GLfloat, APP_GLfloat) ) (uintptr_t) app->wglGetProcAddress( "glClearColor" ); + if( !app->gl.Clear ) app->gl.Clear = ( void (APP_GLCALLTYPE*) (APP_GLbitfield) ) (uintptr_t) app->wglGetProcAddress( "glClear" ); + if( !app->gl.DrawArrays ) app->gl.DrawArrays = ( void (APP_GLCALLTYPE*) (APP_GLenum, APP_GLint, APP_GLsizei) ) (uintptr_t) app->wglGetProcAddress( "glDrawArrays" ); + if( !app->gl.Viewport ) app->gl.Viewport = ( void (APP_GLCALLTYPE*) (APP_GLint, APP_GLint, APP_GLsizei, APP_GLsizei) ) (uintptr_t) app->wglGetProcAddress( "glViewport" ); + if( !app->gl.DeleteShader ) app->gl.DeleteShader = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glDeleteShader" ); + if( !app->gl.DeleteProgram ) app->gl.DeleteProgram = ( void (APP_GLCALLTYPE*) (APP_GLuint) ) (uintptr_t) app->wglGetProcAddress( "glDeleteProgram" ); + #ifdef APP_REPORT_SHADER_ERRORS + if( !app->gl.GetShaderInfoLog ) app->gl.GetShaderInfoLog = ( void (APP_GLCALLTYPE*) (APP_GLuint, APP_GLsizei, APP_GLsizei*, APP_GLchar*) ) (uintptr_t) app->wglGetProcAddress( "glGetShaderInfoLog" ); + #endif + + // Report error if any gl function was not found. + if( !app->gl.CreateShader ) { app_fatal_error( app, "Could not find function CreateShader." ); goto init_failed; } + if( !app->gl.ShaderSource ) { app_fatal_error( app, "Could not find function ShaderSource." ); goto init_failed; } + if( !app->gl.CompileShader ) { app_fatal_error( app, "Could not find function CompileShader." ); goto init_failed; } + if( !app->gl.GetShaderiv ) { app_fatal_error( app, "Could not find function GetShaderiv." ); goto init_failed; } + if( !app->gl.CreateProgram ) { app_fatal_error( app, "Could not find function CreateProgram." ); goto init_failed; } + if( !app->gl.AttachShader ) { app_fatal_error( app, "Could not find function AttachShader." ); goto init_failed; } + if( !app->gl.BindAttribLocation ) { app_fatal_error( app, "Could not find function BindAttribLocation." ); goto init_failed; } + if( !app->gl.LinkProgram ) { app_fatal_error( app, "Could not find function LinkProgram." ); goto init_failed; } + if( !app->gl.GetProgramiv ) { app_fatal_error( app, "Could not find function GetProgramiv." ); goto init_failed; } + if( !app->gl.GenBuffers ) { app_fatal_error( app, "Could not find function GenBuffers." ); goto init_failed; } + if( !app->gl.BindBuffer ) { app_fatal_error( app, "Could not find function BindBuffer." ); goto init_failed; } + if( !app->gl.EnableVertexAttribArray ) { app_fatal_error( app, "Could not find function EnableVertexAttribArray." ); goto init_failed; } + if( !app->gl.VertexAttribPointer ) { app_fatal_error( app, "Could not find function VertexAttribPointer." ); goto init_failed; } + if( !app->gl.GenTextures ) { app_fatal_error( app, "Could not find function GenTextures." ); goto init_failed; } + if( !app->gl.Enable ) { app_fatal_error( app, "Could not find function Enable." ); goto init_failed; } + if( !app->gl.ActiveTexture ) { app_fatal_error( app, "Could not find function ActiveTexture." ); goto init_failed; } + if( !app->gl.BindTexture ) { app_fatal_error( app, "Could not find function BindTexture." ); goto init_failed; } + if( !app->gl.TexParameteri ) { app_fatal_error( app, "Could not find function TexParameteri." ); goto init_failed; } + if( !app->gl.DeleteBuffers ) { app_fatal_error( app, "Could not find function DeleteBuffers." ); goto init_failed; } + if( !app->gl.DeleteTextures ) { app_fatal_error( app, "Could not find function DeleteTextures." ); goto init_failed; } + if( !app->gl.BufferData ) { app_fatal_error( app, "Could not find function BufferData." ); goto init_failed; } + if( !app->gl.UseProgram ) { app_fatal_error( app, "Could not find function UseProgram." ); goto init_failed; } + if( !app->gl.Uniform1i ) { app_fatal_error( app, "Could not find function Uniform1i." ); goto init_failed; } + if( !app->gl.Uniform3f ) { app_fatal_error( app, "Could not find function Uniform3f." ); goto init_failed; } + if( !app->gl.GetUniformLocation ) { app_fatal_error( app, "Could not find function GetUniformLocation." ); goto init_failed; } + if( !app->gl.TexImage2D ) { app_fatal_error( app, "Could not find function TexImage2D." ); goto init_failed; } + if( !app->gl.ClearColor ) { app_fatal_error( app, "Could not find function ClearColor." ); goto init_failed; } + if( !app->gl.Clear ) { app_fatal_error( app, "Could not find function Clear." ); goto init_failed; } + if( !app->gl.DrawArrays ) { app_fatal_error( app, "Could not find function DrawArrays." ); goto init_failed; } + if( !app->gl.Viewport ) { app_fatal_error( app, "Could not find function Viewport." ); goto init_failed; } + if( !app->gl.DeleteShader ) { app_fatal_error( app, "Could not find function DeleteShader." ); goto init_failed; } + if( !app->gl.DeleteProgram ) { app_fatal_error( app, "Could not find function DeleteProgram." ); goto init_failed; } + #ifdef APP_REPORT_SHADER_ERRORS + if( !app->gl.GetShaderInfoLog ) { app_fatal_error( app, "Could not find function GetShaderInfoLog." ); goto init_failed; } + #endif + + // Platform independent OpenGL initialization + int width = app->screenmode == APP_SCREENMODE_FULLSCREEN ? app->fullscreen_width : app->windowed_w; + int height = app->screenmode == APP_SCREENMODE_FULLSCREEN ? app->fullscreen_height: app->windowed_h; + if( !app_internal_opengl_init( app, &app->gl, app->interpolation, width, height ) ) + { + app_log( app, APP_LOG_LEVEL_ERROR, "Failed to initialize OpenGL" ); + goto init_failed; + } + + app->sound_notifications[ 0 ] = CreateEventA( NULL, FALSE, FALSE, NULL ); + app->sound_notifications[ 1 ] = CreateEventA( NULL, FALSE, FALSE, NULL ); + + + app->dsound_dll = LoadLibraryA( "dsound.dll" ); + if( !app->dsound_dll ) app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't load dsound.dll. Sound disabled." ); + + if( app->dsound_dll ) + { + HRESULT (WINAPI *DirectSoundCreate8Ptr)(LPCGUID,struct IDirectSound8**,void*) = ( HRESULT (WINAPI*)(LPCGUID,struct IDirectSound8**,void*) ) + (uintptr_t) GetProcAddress( (HMODULE) app->dsound_dll, "DirectSoundCreate8" ); + if( !DirectSoundCreate8Ptr ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't find DirectSoundCreate. Sound disabled." ); + FreeLibrary( app->dsound_dll ); + app->dsound_dll = 0; + } + if( DirectSoundCreate8Ptr ) + { + HRESULT hr = DirectSoundCreate8Ptr( NULL, &app->dsound, NULL ); + if( FAILED( hr ) || !app->dsound ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't create DirectSound object. Sound disabled." ); + DirectSoundCreate8Ptr = 0; + FreeLibrary( app->dsound_dll ); + app->dsound_dll = 0; + } + else + { + hr = IDirectSound8_SetCooperativeLevel( app->dsound, app->hwnd, DSSCL_NORMAL); + if( FAILED( hr ) ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't set cooperative level for DirectSound object. Sound disabled." ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + DirectSoundCreate8Ptr = 0; + FreeLibrary( app->dsound_dll ); + app->dsound_dll = 0; + } + } + } + } + app->sound_thread_handle = INVALID_HANDLE_VALUE; + + HMODULE user32 = LoadLibraryA( "user32.dll" ); + if( user32 ) + { + BOOL (WINAPI *RegisterRawInputDevicesPtr)( PCRAWINPUTDEVICE, UINT, UINT ) = + (BOOL (WINAPI*)( PCRAWINPUTDEVICE, UINT, UINT ) )(uintptr_t) GetProcAddress( user32, "RegisterRawInputDevices" ); + + app->GetRawInputDataPtr = (UINT (WINAPI*)( HRAWINPUT, UINT, LPVOID, PUINT, UINT)) + (uintptr_t) GetProcAddress( user32, "GetRawInputData" ); + + USHORT const USAGE_PAGE_GENERIC = ((USHORT) 0x01); + USHORT const USAGE_GENERIC_MOUSE = ((USHORT) 0x02); + + RAWINPUTDEVICE rid[ 1 ]; + rid[ 0 ].usUsagePage = USAGE_PAGE_GENERIC; + rid[ 0 ].usUsage = USAGE_GENERIC_MOUSE; + rid[ 0 ].dwFlags = RIDEV_INPUTSINK; + rid[ 0 ].hwndTarget = app->hwnd; + RegisterRawInputDevicesPtr( rid, 1, sizeof( *rid ) ); + + FreeLibrary( user32 ); + } + + if( !app_internal_tablet_init( app ) ) app_log( app, APP_LOG_LEVEL_WARNING, "WinTab initialization failed - tablet not available" ); + + result = app_proc( app, user_data ); + +init_failed: + if( !app_internal_tablet_term( app ) ) app_log( app, APP_LOG_LEVEL_WARNING, "WinTab termination failed" ); + if( app->sound_thread_handle != INVALID_HANDLE_VALUE ) + { + InterlockedExchange( &app->exit_sound_thread, 1 ); + WaitForSingleObject( app->sound_thread_handle, INFINITE ); + CloseHandle( app->sound_thread_handle ); + } + if( app->dsoundbuf ) IDirectSoundBuffer8_Release( app->dsoundbuf ); + if( app->dsound ) IDirectSound8_Release( app->dsound ); + if( app->dsound_dll ) FreeLibrary( app->dsound_dll ); + if( app->sound_notifications[ 0 ] ) CloseHandle( app->sound_notifications[ 0 ] ); + if( app->sound_notifications[ 1 ] ) CloseHandle( app->sound_notifications[ 1 ] ); + if( !app_internal_opengl_term( &app->gl ) ) app_log( app, APP_LOG_LEVEL_WARNING, "Failed to terminate OpenGL" ); + if( app->gl_context ) app->wglMakeCurrent( 0, 0 ); + if( app->gl_context ) app->wglDeleteContext( app->gl_context ); + if( app->gl_dll ) FreeLibrary( app->gl_dll ); + if( app->icon ) DestroyIcon( app->icon ); + if( app->current_pointer ) DestroyIcon( app->current_pointer ); + if( app->hdc ) ReleaseDC( app->hwnd, app->hdc ); + if( app->hwnd ) DestroyWindow( app->hwnd ); + UnregisterClass( TEXT( "app_wc" ), app->hinstance ); + + #ifndef __TINYC__ + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeEndPeriod( tc.wPeriodMin ); + #endif + + t = time( NULL ); + struct tm* end = localtime( &t ); + sprintf( msg, "Application terminated %02d:%02d:%02d %04d-%02d-%02d.", + end->tm_hour, end->tm_min, end->tm_sec, end->tm_year + 1900, end->tm_mon + 1, end->tm_mday ); + app_log( app, APP_LOG_LEVEL_INFO, msg ); + + APP_FREE( memctx, app ); + return result; + } + +#pragma warning( pop ) + + +app_state_t app_yield( app_t* app ) + { + if( !app->initialized ) + { + if( app->screenmode == APP_SCREENMODE_WINDOW ) + { + app->screenmode = APP_SCREENMODE_FULLSCREEN; + app_screenmode( app, APP_SCREENMODE_WINDOW ); + } + else + { + app->screenmode = APP_SCREENMODE_WINDOW; + app_screenmode( app, APP_SCREENMODE_FULLSCREEN ); + } + STARTUPINFOA startup_info = { sizeof( STARTUPINFOA ) }; + GetStartupInfoA( &startup_info ); + if( startup_info.dwFlags & STARTF_USESHOWWINDOW ) { + ShowWindow( app->hwnd, startup_info.wShowWindow ); + } else { + ShowWindow( app->hwnd, SW_SHOWDEFAULT ); + } + SetActiveWindow( app->hwnd ); + BringWindowToTop( app->hwnd ); + SwitchToThisWindow( app->hwnd, TRUE ); + if( app->tablet.context ) app->tablet.WTEnable( app->tablet.context, TRUE ); + app->initialized = TRUE; + } + + MSG msg; + while( PeekMessage( &msg, app->hwnd, 0,0, PM_REMOVE ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + + SwitchToThread(); // yield to any thread on same processor + + return app->closed == TRUE ? APP_STATE_EXIT_REQUESTED : APP_STATE_NORMAL; + } + + +void app_cancel_exit( app_t* app ) + { + app->closed = FALSE; + } + + +void app_title( app_t* app, char const* title ) + { + #ifdef UNICODE + int len = (int) strlen (title ); + if( len < 256 ) + { + WCHAR unistring[ 256 ]; + MultiByteToWideChar( CP_ACP, 0, title, -1, unistring, len + 1 ); + SetWindowText( app->hwnd, unistring ); + } + #else + SetWindowText( app->hwnd, title ); + #endif + } + + +char const* app_cmdline( app_t* app ) + { + return app->cmdline; + } + + +char const* app_filename( app_t* app ) + { + return app->exe_path; + } + + +char const* app_userdata( app_t* app ) + { + return app->userdata_path; + } + + +char const* app_appdata( app_t* app ) + { + return app->appdata_path; + } + + +APP_U64 app_time_count( app_t* app ) + { + (void) app; + LARGE_INTEGER c; + QueryPerformanceCounter( &c ); + return (APP_U64) c.QuadPart; + } + + +APP_U64 app_time_freq( app_t* app ) + { + (void) app; + LARGE_INTEGER f; + QueryPerformanceFrequency( &f ); + return (APP_U64) f.QuadPart; + } + + +void app_log( app_t* app, app_log_level_t level, char const* message ) + { + (void) app, (void) level, (void) message; + APP_LOG( app->logctx, level, message ); + } + + +void app_fatal_error( app_t* app, char const* message ) + { + (void) app, (void) message; + APP_FATAL_ERROR( app->fatalctx, message ); + } + + +static HCURSOR app_internal_create_cursor( HWND hwnd, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) + { + int size = width > height ? width : height; + BITMAPV5HEADER header; + memset( &header, 0, sizeof( BITMAPV5HEADER ) ); + header.bV5Size = sizeof( BITMAPV5HEADER ); + header.bV5Width = (LONG) size; + header.bV5Height = -(LONG) size; + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5RedMask = 0x00FF0000; + header.bV5GreenMask = 0x0000FF00; + header.bV5BlueMask = 0x000000FF; + header.bV5AlphaMask = 0xFF000000; + + HDC hdc = GetDC( hwnd ); + void* bits = NULL; + HBITMAP bitmap = CreateDIBSection( hdc, (BITMAPINFO*)&header, DIB_RGB_COLORS, (void**) &bits, NULL, (DWORD) 0); + ReleaseDC( NULL, hdc ); + + APP_U32* ptr = (APP_U32*) bits; + for( int y = 0; y < height; ++y ) + { + for( int x = 0; x < width; ++x ) + { + APP_U32 c = pixels_abgr[ x + y * width ]; + APP_U32 a = ( c & 0xff000000 ) >> 24; + APP_U32 b = ( c & 0x00ff0000 ) >> 16; + APP_U32 g = ( c & 0x0000ff00 ) >> 8; + APP_U32 r = ( c & 0x000000ff ); + ptr[ x + y * size ] = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; + } + } + + HBITMAP empty_mask = CreateBitmap( size, size, 1, 1, NULL ); + ICONINFO icon_info; + icon_info.fIcon = FALSE; + icon_info.xHotspot = (DWORD) hotspot_x; + icon_info.yHotspot = (DWORD) hotspot_y; + icon_info.hbmMask = empty_mask; + icon_info.hbmColor = bitmap; + + HCURSOR cursor = CreateIconIndirect( &icon_info ); + DeleteObject( bitmap ); + DeleteObject( empty_mask ); + + return cursor; + } + + +void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) + { + if( app->current_pointer ) DestroyIcon( app->current_pointer ); + app->current_pointer = 0; + + if( pixels_abgr ) + app->current_pointer = app_internal_create_cursor( app->hwnd, width, height, + pixels_abgr, hotspot_x, hotspot_y ); + ShowCursor( FALSE ); + SetCursor( app->current_pointer ); + ShowCursor( TRUE ); + } + + +static BOOL app_internal_extract_default_windows_cursor( int* width, int* height, APP_U32* pixels_abgr, + int* hotspot_x, int* hotspot_y ) + { + HCURSOR cursor = LoadCursor( NULL, IDC_ARROW ); + if( !cursor ) return FALSE; + + ICONINFO info; + if( !GetIconInfo( cursor, &info ) ) { DestroyCursor( cursor ); return FALSE; } + BOOL bw_cursor = ( info.hbmColor == NULL ); + + BITMAP bmpinfo; + memset( &bmpinfo, 0, sizeof( bmpinfo ) ); + if( bw_cursor && GetObject( info.hbmMask, sizeof( BITMAP ), &bmpinfo ) == 0 ) + { + DestroyCursor( cursor ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + return FALSE; + } + if( !bw_cursor && GetObject( info.hbmColor, sizeof( BITMAP ), &bmpinfo ) == 0 ) + { + DestroyCursor( cursor ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + return FALSE; + } + + if( bmpinfo.bmWidth > 256 || bmpinfo.bmHeight > 256 ) + { + DestroyCursor( cursor ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + return FALSE; + } + int pointer_width = bmpinfo.bmWidth; + int pointer_height = ( bmpinfo.bmHeight >= 0 ? bmpinfo.bmHeight : -bmpinfo.bmHeight ) / ( bw_cursor ? 2 : 1 ); + + if( width ) *width = pointer_width; + if( height ) *height = pointer_height; + if( hotspot_x ) *hotspot_x = (int) info.xHotspot; + if( hotspot_y ) *hotspot_y = (int) info.yHotspot; + if( !pixels_abgr ) + { + DestroyCursor( cursor ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + return TRUE; + } + + BITMAPINFOHEADER bmi; + bmi.biSize = sizeof( BITMAPINFOHEADER ); + bmi.biPlanes = 1; + bmi.biBitCount = 32; + bmi.biWidth = bmpinfo.bmWidth; + bmi.biHeight = -bmpinfo.bmHeight; + bmi.biCompression = BI_RGB; + bmi.biSizeImage = 0; + HDC hdc = GetDC( NULL ); + if( GetDIBits( hdc, bw_cursor ? info.hbmMask : info.hbmColor, 0, (UINT) bmpinfo.bmHeight, pixels_abgr, + (BITMAPINFO*) &bmi, DIB_RGB_COLORS ) != bmpinfo.bmHeight ) + { + DestroyCursor( cursor ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + ReleaseDC( NULL, hdc ); + return FALSE; + } + ReleaseDC( NULL, hdc ); + + if( bw_cursor ) + { + for( int y = 0; y < pointer_height; ++y ) + { + for( int x = 0; x < pointer_width; ++x ) + { + APP_U32 c = pixels_abgr[ x + pointer_width * y ]; + APP_U32 m = pixels_abgr[ x + pointer_width * ( pointer_height + y ) ]; + APP_U32 a = 255 - ( c & 0xff ); + APP_U32 g = m & 0xff; + pixels_abgr[ x + pointer_width * y ] = ( a << 24 ) | ( g << 16 ) | ( g << 8 ) | g; + } + } + } + else + { + for( int y = 0; y < pointer_height; ++y ) + { + for( int x = 0; x < pointer_width; ++x ) + { + APP_U32 c = pixels_abgr[ x + pointer_width * y ]; + APP_U32 a = ( c >> 24 ) & 0xff; + APP_U32 r = ( c >> 16 ) & 0xff; + APP_U32 g = ( c >> 8 ) & 0xff; + APP_U32 b = ( c ) & 0xff; + pixels_abgr[ x + pointer_width * y ] = ( a << 24 ) | ( b << 16 ) | ( g << 8 ) | r; + } + } + } + + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + DestroyCursor( cursor ); + return TRUE; + } + + +void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ) + { + (void) app; + + APP_U32 default_pointer_data[ 11 * 16 ] = + { + 0xFF000000,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0xFF000000,0xFF000000,0xFF000000,0xFF000000, + 0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFFFFFFFF,0xFF000000,0x00000000,0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0xFF000000,0x00000000,0x00000000,0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000,0x00000000, + 0xFF000000,0x00000000,0x00000000,0x00000000,0x00000000,0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xFF000000,0xFFFFFFFF,0xFFFFFFFF,0xFF000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xFF000000,0xFF000000,0xFF000000,0x00000000,0x00000000, + }; + + if( !app_internal_extract_default_windows_cursor( width, height, pixels_abgr, hotspot_x, hotspot_y ) ) + { + if( width ) *width = 11; + if( height ) *height = 16; + if( hotspot_x ) *hotspot_x = 0; + if( hotspot_y ) *hotspot_y = 0; + if( pixels_abgr ) memcpy( pixels_abgr, default_pointer_data, sizeof( APP_U32 ) * 11 * 16 ); + } + } + + + +void app_pointer_pos( app_t* app, int x, int y ) + { + POINT p; + p.x = x; + p.y = y; + ClientToScreen( app->hwnd, &p ); + SetCursorPos( p.x, p.y ); + } + + +int app_pointer_x( app_t* app ) + { + POINT p; + GetCursorPos( &p ); + ScreenToClient( app->hwnd, &p ); + return (int) p.x; + } + + +int app_pointer_y( app_t* app ) + { + POINT p; + GetCursorPos( &p ); + ScreenToClient( app->hwnd, &p ); + return (int) p.y; + } + + +void app_pointer_limit( app_t* app, int x, int y, int width, int height ) + { + app->clip_cursor = TRUE; + app->clip_rect.left= x; + app->clip_rect.top = y; + app->clip_rect.right = x + width; + app->clip_rect.bottom = y + height; + + RECT r = app->clip_rect; + ClientToScreen( app->hwnd, (POINT*)&r ); + ClientToScreen( app->hwnd, ( (POINT*)&r ) + 1 ); + ClipCursor( &r ); + } + + +void app_pointer_limit_off( app_t* app ) + { + app->clip_cursor = FALSE; + ClipCursor( 0 ); + } + + +void app_interpolation( app_t* app, app_interpolation_t interpolation ) + { + if( interpolation == app->interpolation ) return; + app->interpolation = interpolation; + + POINT p; + GetCursorPos( &p ); + ScreenToClient( app->hwnd, &p ); + int mouse_x = p.x; + int mouse_y = p.y; + + app_input_event_t input_event; + input_event.type = APP_INPUT_MOUSE_MOVE; + input_event.data.mouse_pos.x = mouse_x; + input_event.data.mouse_pos.y = mouse_y; + app_internal_add_input_event( app, &input_event ); + + app_internal_opengl_interpolation( &app->gl, interpolation ); + } + + +void app_screenmode( app_t* app, app_screenmode_t screenmode ) + { + if( screenmode == app->screenmode ) return; + app->screenmode = screenmode; + BOOL visible = IsWindowVisible( app->hwnd ); + if( screenmode == APP_SCREENMODE_WINDOW ) + { + SetWindowLong( app->hwnd, GWL_STYLE, APP_WINDOWED_WS_STYLE | ( visible ? WS_VISIBLE : 0 ) ); + + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + placement.showCmd = (UINT)( visible ? SW_SHOW : SW_HIDE ); + + placement.rcNormalPosition.left = app->windowed_x; + placement.rcNormalPosition.top = app->windowed_y; + placement.rcNormalPosition.right = app->windowed_x + app->windowed_w; + placement.rcNormalPosition.bottom = app->windowed_y + app->windowed_h; + SetWindowPlacement( app->hwnd, &placement ); + } + else + { + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + + if( visible ) + { + if( placement.showCmd != SW_SHOWMAXIMIZED ) + { + app->windowed_x = placement.rcNormalPosition.left; + app->windowed_y = placement.rcNormalPosition.top; + app->windowed_w = placement.rcNormalPosition.right - placement.rcNormalPosition.left; + app->windowed_h = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top; + } + else + { + ShowWindow( app->hwnd, SW_RESTORE ); + } + } + + HMONITOR hmonitor = MonitorFromWindow( app->hwnd, MONITOR_DEFAULTTOPRIMARY ); + int display_index = 0; + for( int i = 0; i < app->display_count; ++i ) + { + if( app->displays_hmonitor[ i ] == hmonitor ) + { + display_index = i; + break; + } + } + + + RECT r = app_internal_rect( app->displays[ display_index ].x, app->displays[ display_index ].y, + app->displays[ display_index ].x + app->displays[ display_index ].width, + app->displays[ display_index ].y + app->displays[ display_index ].height ); + app->fullscreen_width = r.right - r.left; + app->fullscreen_height = r.bottom - r.top; + SetWindowPos( app->hwnd, 0, r.left, r.top, app->fullscreen_width, app->fullscreen_height, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED ); + + SetWindowLong( app->hwnd, GWL_STYLE, ( visible ? WS_VISIBLE : 0 ) ); + SetWindowPos( app->hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED ); + } + } + + +void app_window_size( app_t* app, int width, int height ) + { + RECT r; + r = app_internal_rect( 0, 0, width, height ); + AdjustWindowRect( &r, APP_WINDOWED_WS_STYLE | WS_VISIBLE, FALSE ); + + width = r.right - r.left; + height = r.bottom - r.top; + app->windowed_w = width; + app->windowed_h = height; + + if( app->screenmode == APP_SCREENMODE_WINDOW ) + SetWindowPos( app->hwnd, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED ); + } + + +int app_window_width( app_t* app ) + { + RECT r; + GetClientRect( app->hwnd, &r ); + return r.right - r.left; + } + + +int app_window_height( app_t* app ) + { + RECT r; + GetClientRect( app->hwnd, &r ); + return r.bottom - r.top; + } + + +void app_window_pos( app_t* app, int x, int y ) + { + if( app->screenmode == APP_SCREENMODE_WINDOW ) + { + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + placement.rcNormalPosition.right = x + ( placement.rcNormalPosition.right - placement.rcNormalPosition.left ); + placement.rcNormalPosition.bottom = y + ( placement.rcNormalPosition.bottom - placement.rcNormalPosition.top ); + placement.rcNormalPosition.left = x; + placement.rcNormalPosition.top = y; + SetWindowPlacement( app->hwnd, &placement ); + } + + app->windowed_x = x; + app->windowed_y = y; + } + + +int app_window_x( app_t* app ) + { + if( app->screenmode == APP_SCREENMODE_WINDOW ) + { + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + return placement.rcNormalPosition.left; + } + else + { + return app->windowed_x; + } + } + + +int app_window_y( app_t* app ) + { + if( app->screenmode == APP_SCREENMODE_WINDOW ) + { + WINDOWPLACEMENT placement; + placement.length = sizeof( placement ); + GetWindowPlacement( app->hwnd, &placement ); + return placement.rcNormalPosition.top; + } + else + { + return app->windowed_y; + } + } + + +app_displays_t app_displays( app_t* app ) + { + app_displays_t displays; + displays.count = app->display_count; + displays.displays = app->displays; + return displays; + } + + +void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) + { + if( app->is_minimized ) return; + if( pixels_xbgr ) app_internal_opengl_present( &app->gl, pixels_xbgr, width, height, mod_xbgr, border_xbgr ); + SwapBuffers( app->hdc ); + } + + +static void app_sound_write( app_t* app, int sample_pairs_offset, int sample_pairs_count ) + { + int offset = sample_pairs_offset * 2 * ( 16 / 8 ); + int length = sample_pairs_count * 2 * ( 16 / 8 ); + + // Obtain memory address of write block. This will be in two parts if the block wraps around. + LPVOID lpvPtr1; + DWORD dwBytes1; + LPVOID lpvPtr2; + DWORD dwBytes2; + HRESULT hr = IDirectSoundBuffer8_Lock( app->dsoundbuf, (DWORD) offset, (DWORD) length, &lpvPtr1, &dwBytes1, + &lpvPtr2, &dwBytes2, 0 ); + + // If DSERR_BUFFERLOST is returned, restore and retry lock. + if( hr == DSERR_BUFFERLOST ) + { + IDirectSoundBuffer8_Restore( app->dsoundbuf ); + hr = IDirectSoundBuffer8_Lock( app->dsoundbuf, (DWORD) offset, (DWORD) length, &lpvPtr1, &dwBytes1, + &lpvPtr2, &dwBytes2, 0 ); + } + if( FAILED( hr) ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't lock sound buffer" ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + return; + } + + // Write to pointers. + app->sound_callback( (APP_S16*) lpvPtr1, (int) dwBytes1 / ( 2 * ( 16 / 8 ) ), app->sound_user_data ); + if( lpvPtr2 ) app->sound_callback( (APP_S16*) lpvPtr2, (int) dwBytes2 / ( 2 * ( 16 / 8 ) ), app->sound_user_data ); + + // Release the data back to DirectSound. + hr = IDirectSoundBuffer8_Unlock( app->dsoundbuf, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2 ); + if( FAILED( hr) ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Couldn't unlock sound buffer" ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + return; + } + } + + +static DWORD WINAPI app_sound_thread_proc( LPVOID lpThreadParameter ) + { + app_t* app = (app_t*) lpThreadParameter; + int mid_point = app->sample_pairs_count / 2; + int half_size = mid_point; + int prev_pos = 0; + while( InterlockedCompareExchange( &app->exit_sound_thread, 0, 0 ) == 0 ) + { + WaitForMultipleObjectsEx( 2, app->sound_notifications, FALSE, 100, FALSE ); + DWORD position = 0; + IDirectSoundBuffer8_GetCurrentPosition( app->dsoundbuf, &position, 0 ); + int pos = ( (int) position )/( 2 * ( 16 / 8 ) ); + + if( prev_pos >= mid_point && pos < mid_point ) + app_sound_write( app, mid_point, half_size ); + else if( prev_pos < mid_point && pos >= mid_point ) + app_sound_write( app, 0, half_size ); + + prev_pos = pos; + } + + return 0; + } + + +void app_sound( app_t* app, int sample_pairs_count, void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ), void* user_data ) + { + if( !app->dsound ) return; + + if( !sound_callback || !sample_pairs_count ) + { + if( app->sound_thread_handle != INVALID_HANDLE_VALUE ) + { + InterlockedExchange( &app->exit_sound_thread, 1 ); + WaitForSingleObject( app->sound_thread_handle, INFINITE ); + CloseHandle( app->sound_thread_handle ); + app->sound_thread_handle = INVALID_HANDLE_VALUE; + } + if( app->dsoundbuf ) + { + IDirectSoundBuffer8_Release( app->dsoundbuf ); + app->dsoundbuf = NULL; + } + app->sample_pairs_count = 0; + app->sound_callback = NULL; + app->sound_user_data = NULL; + return; + } + + if( app->sample_pairs_count != sample_pairs_count ) + { + app->sample_pairs_count = sample_pairs_count; + + if( app->dsoundbuf ) + { + IDirectSoundBuffer8_Release( app->dsoundbuf ); + app->dsoundbuf = 0; + } + + if( sample_pairs_count > 0 ) + { + int const channels = 2; + int const frequency = 44100; + int const bits_per_sample = 16; + + WORD const DSOUND_WAVE_FORMAT_PCM = 1; + DSOUND_WAVEFORMATEX format; + memset( &format, 0, sizeof( DSOUND_WAVEFORMATEX ) ); + format.wFormatTag = DSOUND_WAVE_FORMAT_PCM; + format.nChannels = (WORD) channels; + format.nSamplesPerSec = (DWORD) frequency; + format.nBlockAlign = (WORD) ( ( channels * bits_per_sample ) / 8 ); + format.nAvgBytesPerSec = (DWORD) ( frequency * format.nBlockAlign ); + format.wBitsPerSample = (WORD) bits_per_sample; + format.cbSize = 0; + + DSBUFFERDESC dsbdesc; + memset( &dsbdesc, 0, sizeof( DSBUFFERDESC ) ); + dsbdesc.dwSize = sizeof( DSBUFFERDESC ); + + dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY ; + + int size = channels * ( bits_per_sample / 8 ) * sample_pairs_count; + dsbdesc.dwBufferBytes = (DWORD) size; + dsbdesc.lpwfxFormat = &format; + + struct IDirectSoundBuffer8* soundbuf = NULL; + HRESULT hr = IDirectSound8_CreateSoundBuffer( app->dsound, &dsbdesc, &soundbuf, NULL ); + if( FAILED( hr ) || !soundbuf ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Failed to create sound buffer" ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + app->sample_pairs_count = 0; + app->sound_callback = NULL; + app->sound_user_data = NULL; + return; + } + + GUID const GUID_IDirectSoundBuffer8 = { 0x6825a449, 0x7524, 0x4d82, { 0x92, 0x0f, 0x50, 0xe3, 0x6a, 0xb3, 0xab, 0x1e } }; + #ifdef __cplusplus + GUID const& ref_GUID_IDirectSoundBuffer8 = GUID_IDirectSoundBuffer8; + #else + GUID const* ref_GUID_IDirectSoundBuffer8 = &GUID_IDirectSoundBuffer8; + #endif + hr = IDirectSoundBuffer8_QueryInterface( soundbuf, ref_GUID_IDirectSoundBuffer8, (void**) &app->dsoundbuf ); + IDirectSoundBuffer8_Release( soundbuf ); + + if( FAILED( hr ) || !app->dsoundbuf ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Failed to create sound buffer" ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + app->sample_pairs_count = 0; + app->sound_callback = NULL; + app->sound_user_data = NULL; + return; + } + + struct IDirectSoundNotify* notify = NULL; + GUID const GUID_IDirectSoundNotify8 = { 0xb0210783, 0x89cd, 0x11d0, { 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16 } }; + #ifdef __cplusplus + GUID const& ref_GUID_IDirectSoundNotify8 = GUID_IDirectSoundNotify8; + #else + GUID const* ref_GUID_IDirectSoundNotify8 = &GUID_IDirectSoundNotify8; + #endif + hr = IDirectSoundBuffer8_QueryInterface( app->dsoundbuf, ref_GUID_IDirectSoundNotify8, (void**) ¬ify ); + if( FAILED( hr ) || !notify ) + { + app_log( app, APP_LOG_LEVEL_WARNING, "Failed to create sound buffer" ); + IDirectSoundBuffer8_Release( app->dsoundbuf ); + IDirectSound8_Release( app->dsound ); + app->dsound = 0; + app->dsoundbuf = 0; + app->sample_pairs_count = 0; + app->sound_callback = NULL; + app->sound_user_data = NULL; + return; + } + + DSBPOSITIONNOTIFY notify_positions[ 2 ]; + notify_positions[ 0 ].dwOffset = 0; + notify_positions[ 0 ].hEventNotify = app->sound_notifications[ 0 ]; + notify_positions[ 1 ].dwOffset = (DWORD)( size / 2 ); + notify_positions[ 1 ].hEventNotify = app->sound_notifications[ 1 ]; + + IDirectSoundNotify_SetNotificationPositions( notify, 2, notify_positions ); + IDirectSoundNotify_Release( notify ); + + InterlockedExchange( &app->exit_sound_thread, 0 ); + app->sound_thread_handle = CreateThread( NULL, 0U, app_sound_thread_proc, app, 0, NULL ); + SetThreadPriority( app->sound_thread_handle, THREAD_PRIORITY_HIGHEST ); + + IDirectSoundBuffer8_Play( app->dsoundbuf, 0, 0, DSBPLAY_LOOPING ); + } + } + + app->sound_callback = sound_callback; + app->sound_user_data = user_data; + } + + +void app_sound_volume( app_t* app, float volume ) + { + if( !app->dsound ) return; + if( !app->dsoundbuf ) return; + + int level = volume < 0.000015f ? DSBVOLUME_MIN : (int) ( 2000.0f * (float) log10( (double ) volume ) ); + if( app->sound_level == level ) return; + app->sound_level = level; + + IDirectSoundBuffer8_SetVolume( app->dsoundbuf, level ); + } + + +app_input_t app_input( app_t* app ) + { + app_input_t input; + input.events = app->input_events; + input.count = app->input_count; + app->input_count = 0; + return input; + } + + +void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ) + { + if( width == 0 || height == 0 ) return; + RECT r; + GetClientRect( app->hwnd, &r ); + int window_width = ( app->screenmode == APP_SCREENMODE_FULLSCREEN ) ? app->fullscreen_width : r.right - r.left; + int window_height = ( app->screenmode == APP_SCREENMODE_FULLSCREEN ) ? app->fullscreen_height : r.bottom - r.top; + + + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = window_width / (float) width; + float vscale = window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( window_width - pixel_scale * width ) / 2.0f; + float vborder = ( window_height - pixel_scale * height ) / 2.0f; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = window_width / width; + int vscale = window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( window_width - pixel_scale * width ) / 2; + int vborder = ( window_height - pixel_scale * height ) / 2; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + } + + +void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ) + { + RECT r; + GetClientRect( app->hwnd, &r ); + int window_width = ( app->screenmode == APP_SCREENMODE_FULLSCREEN ) ? app->fullscreen_width : r.right - r.left; + int window_height = ( app->screenmode == APP_SCREENMODE_FULLSCREEN ) ? app->fullscreen_height : r.bottom - r.top; + + + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = window_width / (float) width; + float vscale = window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( window_width - pixel_scale * width ) / 2.0f; + float vborder = ( window_height - pixel_scale * height ) / 2.0f; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = window_width / width; + int vscale = window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( window_width - pixel_scale * width ) / 2; + int vborder = ( window_height - pixel_scale * height ) / 2; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + } + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SDL +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#elif defined( APP_SDL ) + +#ifndef APP_MALLOC + #include + #if defined(__cplusplus) + #define APP_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define APP_MALLOC( ctx, size ) ( malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif + +#include +#include + +#include "SDL.h" + +#ifndef APP_FATAL_ERROR + #define APP_FATAL_ERROR( ctx, message ) { \ + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Fatal Error!", message, NULL ); exit( 0xff ); } +#endif + +struct app_t + { + void* memctx; + void* logctx; + void* fatalctx; + struct app_internal_opengl_t gl; + int initialized; + int exit_requested; + int has_focus; + app_interpolation_t interpolation; + app_screenmode_t screenmode; + + SDL_Window* window; + SDL_Cursor* cursor; + + SDL_AudioDeviceID sound_device; + void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ); + void* sound_user_data; + int volume; + + app_input_event_t input_events[ 1024 ]; + int input_count; + + int display_count; + app_display_t displays[ 16 ]; + + }; + + +int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ) + { + app_t* app = (app_t*) APP_MALLOC( memctx, sizeof( app_t ) ); + memset( app, 0, (int)sizeof( app_t ) ); + app->memctx = memctx; + app->logctx = logctx; + app->fatalctx = fatalctx; + app->interpolation = APP_INTERPOLATION_LINEAR; + app->screenmode = APP_SCREENMODE_FULLSCREEN; + + int result = 0xff; + int display_count; + int glres; + + if( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) + { +// printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); + goto init_failed; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + + app->window = SDL_CreateWindow( "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 400, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN); + if( !app->window ) + { +// printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); + goto init_failed; + } + + app->has_focus = 1; + app->volume = 256; + + display_count = SDL_GetNumVideoDisplays(); + for( int i = 0; i < display_count; ++i ) + { + SDL_Rect r; + SDL_GetDisplayBounds( i, &r ); + app_display_t d; + sprintf( d.id, "DISPLAY%d", i ); + d.x = r.x; + d.y = r.y; + d.width = r.w; + d.height = r.h; + app->displays[ i ] = d; + } + app->display_count = display_count; + + SDL_GL_CreateContext( app->window ); + glewInit(); + + SDL_GL_SetSwapInterval( 1 ); + + app->gl.CreateShader = glCreateShader; + app->gl.ShaderSource = glShaderSource; + app->gl.CompileShader = glCompileShader; + app->gl.GetShaderiv = glGetShaderiv; + app->gl.CreateProgram = glCreateProgram; + app->gl.AttachShader = glAttachShader; + app->gl.BindAttribLocation = glBindAttribLocation; + app->gl.LinkProgram = glLinkProgram; + app->gl.GetProgramiv = glGetProgramiv; + app->gl.GenBuffers = glGenBuffers; + app->gl.BindBuffer = glBindBuffer; + app->gl.EnableVertexAttribArray = glEnableVertexAttribArray; + app->gl.VertexAttribPointer = glVertexAttribPointer; + app->gl.GenTextures = glGenTextures; + app->gl.Enable = glEnable; + app->gl.ActiveTexture = glActiveTexture; + app->gl.BindTexture = glBindTexture; + app->gl.TexParameteri = glTexParameteri; + app->gl.DeleteBuffers = glDeleteBuffers; + app->gl.DeleteTextures = glDeleteTextures; + app->gl.BufferData = glBufferData; + app->gl.UseProgram = glUseProgram; + app->gl.Uniform1i = glUniform1i; + app->gl.Uniform3f = glUniform3f; + app->gl.GetUniformLocation = glGetUniformLocation; + app->gl.TexImage2D = glTexImage2D; + app->gl.ClearColor = glClearColor; + app->gl.Clear = glClear; + app->gl.DrawArrays = glDrawArrays; + app->gl.Viewport = glViewport; + app->gl.DeleteShader = glDeleteShader; + app->gl.DeleteProgram = glDeleteProgram; + #ifdef APP_REPORT_SHADER_ERRORS + app->gl.GetShaderInfoLog = glGetShaderInfoLog; + #endif + + glres = app_internal_opengl_init( app, &app->gl, app->interpolation, 640, 400 ); + if( !glres ) + { + app_fatal_error( app, "OpenGL init fail" ); + goto init_failed; + } + + result = app_proc( app, user_data ); + +init_failed: + if( app->sound_device ) + { + SDL_PauseAudioDevice( app->sound_device, 1 ); + SDL_CloseAudioDevice( app->sound_device ); + app->sound_device = 0; + app->sound_callback = NULL; + app->sound_user_data = NULL; + } + + if( app->cursor ) SDL_FreeCursor( app->cursor ); + + //Destroy window + SDL_DestroyWindow( app->window ); + + + //Quit SDL subsystems + SDL_Quit(); + + APP_FREE( memctx, app ); + return result; + } + + +static app_key_t app_internal_scancode_to_appkey( app_t* app, SDL_Scancode scancode ) + + { + int map[ 287 * 2 ] = { APP_KEY_INVALID, SDL_SCANCODE_UNKNOWN, APP_KEY_INVALID, 1, APP_KEY_INVALID, 2, APP_KEY_INVALID, 3, APP_KEY_A, SDL_SCANCODE_A, + APP_KEY_B, SDL_SCANCODE_B, APP_KEY_C, SDL_SCANCODE_C, APP_KEY_D, SDL_SCANCODE_D, APP_KEY_E, SDL_SCANCODE_E, APP_KEY_F, SDL_SCANCODE_F, APP_KEY_G, + SDL_SCANCODE_G, APP_KEY_H, SDL_SCANCODE_H, APP_KEY_I, SDL_SCANCODE_I, APP_KEY_J, SDL_SCANCODE_J, APP_KEY_K, SDL_SCANCODE_K, APP_KEY_L, + SDL_SCANCODE_L, APP_KEY_M, SDL_SCANCODE_M, APP_KEY_N, SDL_SCANCODE_N, APP_KEY_O, SDL_SCANCODE_O, APP_KEY_P, SDL_SCANCODE_P, APP_KEY_Q, + SDL_SCANCODE_Q, APP_KEY_R, SDL_SCANCODE_R, APP_KEY_S, SDL_SCANCODE_S, APP_KEY_T, SDL_SCANCODE_T, APP_KEY_U, SDL_SCANCODE_U, APP_KEY_V, + SDL_SCANCODE_V, APP_KEY_W, SDL_SCANCODE_W, APP_KEY_X, SDL_SCANCODE_X, APP_KEY_Y, SDL_SCANCODE_Y, APP_KEY_Z, SDL_SCANCODE_Z, APP_KEY_1, + SDL_SCANCODE_1, APP_KEY_2, SDL_SCANCODE_2, APP_KEY_3, SDL_SCANCODE_3, APP_KEY_4, SDL_SCANCODE_4, APP_KEY_5, SDL_SCANCODE_5, APP_KEY_6, + SDL_SCANCODE_6, APP_KEY_7, SDL_SCANCODE_7, APP_KEY_8, SDL_SCANCODE_8, APP_KEY_9, SDL_SCANCODE_9, APP_KEY_0, SDL_SCANCODE_0, APP_KEY_RETURN, + SDL_SCANCODE_RETURN, APP_KEY_ESCAPE, SDL_SCANCODE_ESCAPE, APP_KEY_BACK, SDL_SCANCODE_BACKSPACE, APP_KEY_TAB, SDL_SCANCODE_TAB, APP_KEY_SPACE, + SDL_SCANCODE_SPACE, APP_KEY_OEM_MINUS, SDL_SCANCODE_MINUS, APP_KEY_INVALID, SDL_SCANCODE_EQUALS, APP_KEY_OEM_4, SDL_SCANCODE_LEFTBRACKET, + APP_KEY_OEM_6, SDL_SCANCODE_RIGHTBRACKET, APP_KEY_OEM_5, SDL_SCANCODE_BACKSLASH, APP_KEY_INVALID, SDL_SCANCODE_NONUSHASH, APP_KEY_OEM_1, + SDL_SCANCODE_SEMICOLON, APP_KEY_OEM_7, SDL_SCANCODE_APOSTROPHE, APP_KEY_INVALID, SDL_SCANCODE_GRAVE, APP_KEY_OEM_COMMA, SDL_SCANCODE_COMMA, + APP_KEY_OEM_PERIOD, SDL_SCANCODE_PERIOD, APP_KEY_OEM_2, SDL_SCANCODE_SLASH, APP_KEY_CAPITAL, SDL_SCANCODE_CAPSLOCK, APP_KEY_F1, SDL_SCANCODE_F1, + APP_KEY_F2, SDL_SCANCODE_F2, APP_KEY_F3, SDL_SCANCODE_F3, APP_KEY_F4, SDL_SCANCODE_F4, APP_KEY_F5, SDL_SCANCODE_F5, APP_KEY_F6, SDL_SCANCODE_F6, + APP_KEY_F7, SDL_SCANCODE_F7, APP_KEY_F8, SDL_SCANCODE_F8, APP_KEY_F9, SDL_SCANCODE_F9, APP_KEY_F10, SDL_SCANCODE_F10, APP_KEY_F11, + SDL_SCANCODE_F11, APP_KEY_F12, SDL_SCANCODE_F12, APP_KEY_SNAPSHOT, SDL_SCANCODE_PRINTSCREEN, APP_KEY_SCROLL, SDL_SCANCODE_SCROLLLOCK, + APP_KEY_PAUSE, SDL_SCANCODE_PAUSE, APP_KEY_INSERT, SDL_SCANCODE_INSERT, APP_KEY_HOME, SDL_SCANCODE_HOME, APP_KEY_PRIOR, SDL_SCANCODE_PAGEUP, + APP_KEY_DELETE, SDL_SCANCODE_DELETE, APP_KEY_END, SDL_SCANCODE_END, APP_KEY_NEXT, SDL_SCANCODE_PAGEDOWN, APP_KEY_RIGHT, SDL_SCANCODE_RIGHT, + APP_KEY_LEFT, SDL_SCANCODE_LEFT, APP_KEY_DOWN, SDL_SCANCODE_DOWN, APP_KEY_UP, SDL_SCANCODE_UP, APP_KEY_NUMLOCK, SDL_SCANCODE_NUMLOCKCLEAR, + APP_KEY_DIVIDE, SDL_SCANCODE_KP_DIVIDE, APP_KEY_MULTIPLY, SDL_SCANCODE_KP_MULTIPLY, APP_KEY_SUBTRACT, SDL_SCANCODE_KP_MINUS, APP_KEY_ADD, + SDL_SCANCODE_KP_PLUS, APP_KEY_RETURN, SDL_SCANCODE_KP_ENTER, APP_KEY_NUMPAD1,SDL_SCANCODE_KP_1, APP_KEY_NUMPAD2,SDL_SCANCODE_KP_2, APP_KEY_NUMPAD3, + SDL_SCANCODE_KP_3, APP_KEY_NUMPAD4,SDL_SCANCODE_KP_4, APP_KEY_NUMPAD5,SDL_SCANCODE_KP_5, APP_KEY_NUMPAD6,SDL_SCANCODE_KP_6, APP_KEY_NUMPAD7, + SDL_SCANCODE_KP_7, APP_KEY_NUMPAD8,SDL_SCANCODE_KP_8, APP_KEY_NUMPAD9,SDL_SCANCODE_KP_9, APP_KEY_NUMPAD0,SDL_SCANCODE_KP_0, APP_KEY_DECIMAL, + SDL_SCANCODE_KP_PERIOD, APP_KEY_INVALID, SDL_SCANCODE_NONUSBACKSLASH, APP_KEY_APPS, SDL_SCANCODE_APPLICATION, APP_KEY_INVALID, SDL_SCANCODE_POWER, + APP_KEY_RETURN, SDL_SCANCODE_KP_EQUALS, APP_KEY_F13, SDL_SCANCODE_F13, APP_KEY_F14, SDL_SCANCODE_F14, APP_KEY_F15, SDL_SCANCODE_F15, APP_KEY_F16, + SDL_SCANCODE_F16, APP_KEY_F17, SDL_SCANCODE_F17, APP_KEY_F18, SDL_SCANCODE_F18, APP_KEY_F19, SDL_SCANCODE_F19, APP_KEY_F20, SDL_SCANCODE_F20, + APP_KEY_F21, SDL_SCANCODE_F21, APP_KEY_F22, SDL_SCANCODE_F22, APP_KEY_F23, SDL_SCANCODE_F23, APP_KEY_F24, SDL_SCANCODE_F24, APP_KEY_EXEC, + SDL_SCANCODE_EXECUTE, APP_KEY_HELP, SDL_SCANCODE_HELP, APP_KEY_MENU, SDL_SCANCODE_MENU, APP_KEY_SELECT, SDL_SCANCODE_SELECT, APP_KEY_MEDIA_STOP, + SDL_SCANCODE_STOP, APP_KEY_INVALID, SDL_SCANCODE_AGAIN, APP_KEY_INVALID, SDL_SCANCODE_UNDO, APP_KEY_INVALID, SDL_SCANCODE_CUT, APP_KEY_INVALID, + SDL_SCANCODE_COPY, APP_KEY_INVALID, SDL_SCANCODE_PASTE, APP_KEY_INVALID, SDL_SCANCODE_FIND, APP_KEY_VOLUME_MUTE, SDL_SCANCODE_MUTE, + APP_KEY_VOLUME_UP, SDL_SCANCODE_VOLUMEUP, APP_KEY_VOLUME_DOWN, SDL_SCANCODE_VOLUMEDOWN, APP_KEY_INVALID, 130, APP_KEY_INVALID, 131, + APP_KEY_INVALID, 132, APP_KEY_SEPARATOR, SDL_SCANCODE_KP_COMMA, APP_KEY_INVALID, SDL_SCANCODE_KP_EQUALSAS400, APP_KEY_INVALID, + SDL_SCANCODE_INTERNATIONAL1, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL2, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL3, APP_KEY_INVALID, + SDL_SCANCODE_INTERNATIONAL4, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL5, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL6, APP_KEY_INVALID, + SDL_SCANCODE_INTERNATIONAL7, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL8, APP_KEY_INVALID, SDL_SCANCODE_INTERNATIONAL9, APP_KEY_HANGUL, + SDL_SCANCODE_LANG1, APP_KEY_HANJA, SDL_SCANCODE_LANG2, APP_KEY_INVALID, SDL_SCANCODE_LANG3, APP_KEY_INVALID, SDL_SCANCODE_LANG4, APP_KEY_INVALID, + SDL_SCANCODE_LANG5, APP_KEY_INVALID, SDL_SCANCODE_LANG6, APP_KEY_INVALID, SDL_SCANCODE_LANG7, APP_KEY_INVALID, SDL_SCANCODE_LANG8, APP_KEY_INVALID, + SDL_SCANCODE_LANG9, APP_KEY_INVALID, SDL_SCANCODE_ALTERASE, APP_KEY_INVALID, SDL_SCANCODE_SYSREQ, APP_KEY_CANCEL, SDL_SCANCODE_CANCEL, APP_KEY_CLEAR, + SDL_SCANCODE_CLEAR, APP_KEY_PRIOR, SDL_SCANCODE_PRIOR, APP_KEY_RETURN, SDL_SCANCODE_RETURN2, APP_KEY_OEM_COMMA, SDL_SCANCODE_SEPARATOR, + APP_KEY_INVALID, SDL_SCANCODE_OUT, APP_KEY_INVALID, SDL_SCANCODE_OPER, APP_KEY_INVALID, SDL_SCANCODE_CLEARAGAIN, APP_KEY_CRSEL, SDL_SCANCODE_CRSEL, + APP_KEY_EXSEL, SDL_SCANCODE_EXSEL, APP_KEY_INVALID, 165, APP_KEY_INVALID, 166, APP_KEY_INVALID, 167, APP_KEY_INVALID, 168, APP_KEY_INVALID, 169, + APP_KEY_INVALID, 170, APP_KEY_INVALID, 171, APP_KEY_INVALID, 172, APP_KEY_INVALID, 173, APP_KEY_INVALID, 174, APP_KEY_INVALID, 175, APP_KEY_INVALID, + SDL_SCANCODE_KP_00, APP_KEY_INVALID, SDL_SCANCODE_KP_000, APP_KEY_INVALID, SDL_SCANCODE_THOUSANDSSEPARATOR, APP_KEY_INVALID, + SDL_SCANCODE_DECIMALSEPARATOR, APP_KEY_INVALID, SDL_SCANCODE_CURRENCYUNIT, APP_KEY_INVALID, SDL_SCANCODE_CURRENCYSUBUNIT, APP_KEY_INVALID, + SDL_SCANCODE_KP_LEFTPAREN, APP_KEY_INVALID, SDL_SCANCODE_KP_RIGHTPAREN, APP_KEY_INVALID, SDL_SCANCODE_KP_LEFTBRACE, APP_KEY_INVALID, + SDL_SCANCODE_KP_RIGHTBRACE, APP_KEY_INVALID, SDL_SCANCODE_KP_TAB, APP_KEY_INVALID, SDL_SCANCODE_KP_BACKSPACE, APP_KEY_INVALID, SDL_SCANCODE_KP_A, + APP_KEY_INVALID, SDL_SCANCODE_KP_B, APP_KEY_INVALID, SDL_SCANCODE_KP_C, APP_KEY_INVALID, SDL_SCANCODE_KP_D, APP_KEY_INVALID, SDL_SCANCODE_KP_E, + APP_KEY_INVALID, SDL_SCANCODE_KP_F, APP_KEY_INVALID, SDL_SCANCODE_KP_XOR, APP_KEY_INVALID, SDL_SCANCODE_KP_POWER, APP_KEY_INVALID, + SDL_SCANCODE_KP_PERCENT, APP_KEY_INVALID, SDL_SCANCODE_KP_LESS, APP_KEY_INVALID, SDL_SCANCODE_KP_GREATER, APP_KEY_INVALID, SDL_SCANCODE_KP_AMPERSAND, + APP_KEY_INVALID, SDL_SCANCODE_KP_DBLAMPERSAND, APP_KEY_INVALID, SDL_SCANCODE_KP_VERTICALBAR, APP_KEY_INVALID, SDL_SCANCODE_KP_DBLVERTICALBAR, + APP_KEY_INVALID, SDL_SCANCODE_KP_COLON, APP_KEY_INVALID, SDL_SCANCODE_KP_HASH, APP_KEY_INVALID, SDL_SCANCODE_KP_SPACE, APP_KEY_INVALID, + SDL_SCANCODE_KP_AT, APP_KEY_INVALID, SDL_SCANCODE_KP_EXCLAM, APP_KEY_INVALID, SDL_SCANCODE_KP_MEMSTORE, APP_KEY_INVALID, SDL_SCANCODE_KP_MEMRECALL, + APP_KEY_INVALID, SDL_SCANCODE_KP_MEMCLEAR, APP_KEY_INVALID, SDL_SCANCODE_KP_MEMADD, APP_KEY_INVALID, SDL_SCANCODE_KP_MEMSUBTRACT, APP_KEY_INVALID, + SDL_SCANCODE_KP_MEMMULTIPLY, APP_KEY_INVALID, SDL_SCANCODE_KP_MEMDIVIDE, APP_KEY_INVALID, SDL_SCANCODE_KP_PLUSMINUS, APP_KEY_INVALID, + SDL_SCANCODE_KP_CLEAR, APP_KEY_INVALID, SDL_SCANCODE_KP_CLEARENTRY, APP_KEY_INVALID, SDL_SCANCODE_KP_BINARY, APP_KEY_INVALID, SDL_SCANCODE_KP_OCTAL, + APP_KEY_INVALID, SDL_SCANCODE_KP_DECIMAL, APP_KEY_INVALID, SDL_SCANCODE_KP_HEXADECIMAL, APP_KEY_INVALID, 222, APP_KEY_INVALID, 223, APP_KEY_LCONTROL, + SDL_SCANCODE_LCTRL, APP_KEY_LSHIFT, SDL_SCANCODE_LSHIFT, APP_KEY_LMENU, SDL_SCANCODE_LALT, APP_KEY_LWIN, SDL_SCANCODE_LGUI, APP_KEY_RCONTROL, + SDL_SCANCODE_RCTRL, APP_KEY_RSHIFT, SDL_SCANCODE_RSHIFT, APP_KEY_RMENU, SDL_SCANCODE_RALT, APP_KEY_RWIN, SDL_SCANCODE_RGUI, APP_KEY_INVALID, 232, + APP_KEY_INVALID, 233, APP_KEY_INVALID, 234, APP_KEY_INVALID, 235, APP_KEY_INVALID, 236, APP_KEY_INVALID, 237, APP_KEY_INVALID, 238, APP_KEY_INVALID, + 239, APP_KEY_INVALID, 240, APP_KEY_INVALID, 241, APP_KEY_INVALID, 242, APP_KEY_INVALID, 243, APP_KEY_INVALID, 244, APP_KEY_INVALID, 245, + APP_KEY_INVALID, 246, APP_KEY_INVALID, 247, APP_KEY_INVALID, 248, APP_KEY_INVALID, 249, APP_KEY_INVALID, 250, APP_KEY_INVALID, 251, APP_KEY_INVALID, + 252, APP_KEY_INVALID, 253, APP_KEY_INVALID, 254, APP_KEY_INVALID, 255, APP_KEY_INVALID, 256, APP_KEY_MODECHANGE, SDL_SCANCODE_MODE, + APP_KEY_MEDIA_NEXT_TRACK, SDL_SCANCODE_AUDIONEXT, APP_KEY_MEDIA_PREV_TRACK, SDL_SCANCODE_AUDIOPREV, APP_KEY_MEDIA_PLAY_PAUSE, SDL_SCANCODE_AUDIOSTOP, + APP_KEY_PLAY, SDL_SCANCODE_AUDIOPLAY, APP_KEY_VOLUME_MUTE, SDL_SCANCODE_AUDIOMUTE, APP_KEY_LAUNCH_MEDIA_SELECT, SDL_SCANCODE_MEDIASELECT, + APP_KEY_INVALID, SDL_SCANCODE_WWW, APP_KEY_LAUNCH_MAIL, SDL_SCANCODE_MAIL, APP_KEY_INVALID, SDL_SCANCODE_CALCULATOR, APP_KEY_INVALID, + SDL_SCANCODE_COMPUTER, APP_KEY_BROWSER_SEARCH, SDL_SCANCODE_AC_SEARCH, APP_KEY_BROWSER_HOME, SDL_SCANCODE_AC_HOME, APP_KEY_BROWSER_BACK, + SDL_SCANCODE_AC_BACK, APP_KEY_BROWSER_FORWARD, SDL_SCANCODE_AC_FORWARD, APP_KEY_BROWSER_STOP, SDL_SCANCODE_AC_STOP, APP_KEY_BROWSER_REFRESH, + SDL_SCANCODE_AC_REFRESH, APP_KEY_BROWSER_FAVORITES, SDL_SCANCODE_AC_BOOKMARKS, APP_KEY_INVALID, SDL_SCANCODE_BRIGHTNESSDOWN, APP_KEY_INVALID, + SDL_SCANCODE_BRIGHTNESSUP, APP_KEY_INVALID, SDL_SCANCODE_DISPLAYSWITCH, APP_KEY_INVALID, SDL_SCANCODE_KBDILLUMTOGGLE, APP_KEY_INVALID, + SDL_SCANCODE_KBDILLUMDOWN, APP_KEY_INVALID, SDL_SCANCODE_KBDILLUMUP, APP_KEY_INVALID, SDL_SCANCODE_EJECT, APP_KEY_SLEEP, SDL_SCANCODE_SLEEP, + APP_KEY_LAUNCH_APP1, SDL_SCANCODE_APP1, APP_KEY_LAUNCH_APP2, SDL_SCANCODE_APP2, APP_KEY_INVALID, SDL_SCANCODE_AUDIOREWIND, APP_KEY_INVALID, + SDL_SCANCODE_AUDIOFASTFORWARD, }; + + if( scancode < 0 || scancode >= sizeof( map ) / ( 2 * sizeof( *map ) ) ) return APP_KEY_INVALID; + if( map[ scancode * 2 + 1 ] != scancode ) + { + app_log( app, APP_LOG_LEVEL_ERROR, "Keymap definition error" ); + return APP_KEY_INVALID; + } + return (app_key_t) map[ scancode * 2 ]; + } + + +static void app_internal_add_input_event( app_t* app, app_input_event_t* event ) + { + if( app->has_focus ) + { + if( app->input_count < sizeof( app->input_events ) / sizeof( *app->input_events ) ) + app->input_events[ app->input_count++ ] = *event; + } + } + + +app_state_t app_yield( app_t* app ) + { + if( !app->initialized ) + { + app->initialized = 1; + if( app->screenmode == APP_SCREENMODE_FULLSCREEN ) SDL_SetWindowFullscreen( app->window, SDL_WINDOW_FULLSCREEN_DESKTOP ); + SDL_ShowWindow( app->window ); + int w = app->gl.window_width; + int h = app->gl.window_height; + SDL_GL_GetDrawableSize( app->window, &w, &h ); + app_internal_opengl_resize( &app->gl, w, h ); + } + + SDL_Event e; + while( SDL_PollEvent( &e ) ) + { + if( e.type == SDL_WINDOWEVENT ) + { + if( e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ) + { + int w = app->gl.window_width; + int h = app->gl.window_height; + SDL_GL_GetDrawableSize( app->window, &w, &h ); + if( w != app->gl.window_width || h != app->gl.window_height ) + { + app_internal_opengl_resize( &app->gl, w, h ); + } + } + else if( e.window.event == SDL_WINDOWEVENT_CLOSE ) + { + app->exit_requested = 1; + } + else if( e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ) + { + app->has_focus = 1; + } + else if( e.window.event == SDL_WINDOWEVENT_FOCUS_LOST ) + { + app->has_focus = 0; + } + } + else if( e.type == SDL_KEYDOWN ) + { + app_input_event_t input_event; + input_event.type = APP_INPUT_KEY_DOWN; + input_event.data.key = app_internal_scancode_to_appkey( app, e.key.keysym.scancode ); + app_internal_add_input_event( app, &input_event ); + } + else if( e.type == SDL_KEYUP ) + { + app_input_event_t input_event; + input_event.type = APP_INPUT_KEY_UP; + input_event.data.key = app_internal_scancode_to_appkey( app, e.key.keysym.scancode ); + app_internal_add_input_event( app, &input_event ); + } + else if( e.type == SDL_TEXTINPUT ) + { + app_input_event_t input_event; + char *c; + input_event.type = APP_INPUT_CHAR; + for ( c = e.text.text; *c; c++ ) + { + input_event.data.char_code = *c; + app_internal_add_input_event( app, &input_event ); + } + } + else if( e.type == SDL_MOUSEMOTION ) + { + app_input_event_t input_event; + input_event.type = APP_INPUT_MOUSE_MOVE; + input_event.data.mouse_pos.x = e.motion.x; + input_event.data.mouse_pos.y = e.motion.y; + app_internal_add_input_event( app, &input_event ); + + input_event.type = APP_INPUT_MOUSE_DELTA; + input_event.data.mouse_pos.x = e.motion.xrel; + input_event.data.mouse_pos.y = e.motion.yrel; + app_internal_add_input_event( app, &input_event ); + } + else if( e.type == SDL_MOUSEBUTTONDOWN ) + { + app_input_event_t input_event; + input_event.type = APP_INPUT_KEY_DOWN; + if( e.button.button == SDL_BUTTON_LEFT ) + input_event.data.key = APP_KEY_LBUTTON; + else if( e.button.button == SDL_BUTTON_RIGHT ) + input_event.data.key = APP_KEY_RBUTTON; + else if( e.button.button == SDL_BUTTON_MIDDLE ) + input_event.data.key = APP_KEY_MBUTTON; + else if( e.button.button == SDL_BUTTON_X1 ) + input_event.data.key = APP_KEY_XBUTTON1; + else if( e.button.button == SDL_BUTTON_X2 ) + input_event.data.key = APP_KEY_XBUTTON2; + app_internal_add_input_event( app, &input_event ); + } + else if( e.type == SDL_MOUSEBUTTONUP ) + { + app_input_event_t input_event; + input_event.type = APP_INPUT_KEY_UP; + if( e.button.button == SDL_BUTTON_LEFT ) + input_event.data.key = APP_KEY_LBUTTON; + else if( e.button.button == SDL_BUTTON_RIGHT ) + input_event.data.key = APP_KEY_RBUTTON; + else if( e.button.button == SDL_BUTTON_MIDDLE ) + input_event.data.key = APP_KEY_MBUTTON; + else if( e.button.button == SDL_BUTTON_X1 ) + input_event.data.key = APP_KEY_XBUTTON1; + else if( e.button.button == SDL_BUTTON_X2 ) + input_event.data.key = APP_KEY_XBUTTON2; + app_internal_add_input_event( app, &input_event ); + } + else if( e.type == SDL_MOUSEWHEEL ) + { + float const microsoft_mouse_wheel_constant = 120.0f; + float wheel_delta = ( (float) e.wheel.y ) / microsoft_mouse_wheel_constant; + if( app->input_count > 0 && app->input_events[ app->input_count - 1 ].type == APP_INPUT_SCROLL_WHEEL ) + { + app_input_event_t* event = &app->input_events[ app->input_count - 1 ]; + event->data.wheel_delta += wheel_delta; + } + else + { + app_input_event_t input_event; + input_event.type = APP_INPUT_SCROLL_WHEEL; + input_event.data.wheel_delta = wheel_delta; + app_internal_add_input_event( app, &input_event ); + } + } + + } + + return app->exit_requested ? APP_STATE_EXIT_REQUESTED : APP_STATE_NORMAL; + } + + +void app_cancel_exit( app_t* app ) + { + app->exit_requested = 0; + } + + +void app_title( app_t* app, char const* title ) + { + SDL_SetWindowTitle( app->window, title ); + } + + +char const* app_cmdline( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_filename( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_userdata( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_appdata( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } + + +APP_U64 app_time_count( app_t* app ) + { + return SDL_GetPerformanceCounter(); + } + + +APP_U64 app_time_freq( app_t* app ) + { + return SDL_GetPerformanceFrequency(); + } + + +void app_log( app_t* app, app_log_level_t level, char const* message ) { /* NOT IMPLEMENTED */ } + + +void app_fatal_error( app_t* app, char const* message ) + { + APP_FATAL_ERROR( app->fatalctx, message ); + } + + +void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) + { + SDL_Surface* surf = SDL_CreateRGBSurfaceFrom( (void*)pixels_abgr, width, height, 32, 4 * width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); + if( app->cursor ) SDL_FreeCursor( app->cursor ); + app->cursor = SDL_CreateColorCursor( surf, hotspot_x, hotspot_y ); + SDL_SetCursor( app->cursor ); + SDL_FreeSurface( surf ); + } + + +void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ) { /* NOT IMPLEMENTED */ } + + +void app_pointer_pos( app_t* app, int x, int y ) + { + SDL_WarpMouseInWindow( app->window, x, y ); + } + + +int app_pointer_x( app_t* app ) + { + int x = 0; + SDL_GetMouseState( &x, NULL ); + return x; + } + + +int app_pointer_y( app_t* app ) + { + int y = 0; + SDL_GetMouseState( NULL, &y ); + return y; + } + + +void app_pointer_limit( app_t* app, int x, int y, int width, int height ) { /* NOT IMPLEMENTED */ } +void app_pointer_limit_off( app_t* app ) { /* NOT IMPLEMENTED */ } + +void app_interpolation( app_t* app, app_interpolation_t interpolation ) + { + if( interpolation == app->interpolation ) return; + app->interpolation = interpolation; + + int mouse_x; + int mouse_y; + SDL_GetMouseState( &mouse_x, &mouse_y ); + + app_input_event_t input_event; + input_event.type = APP_INPUT_MOUSE_MOVE; + input_event.data.mouse_pos.x = mouse_x; + input_event.data.mouse_pos.y = mouse_y; + app_internal_add_input_event( app, &input_event ); + + app_internal_opengl_interpolation( &app->gl, interpolation ); + } + + +void app_screenmode( app_t* app, app_screenmode_t screenmode ) + { + if( screenmode != app->screenmode ) + { + app->screenmode = screenmode; + SDL_SetWindowFullscreen( app->window, + screenmode == APP_SCREENMODE_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 ); + } + } + + +void app_window_size( app_t* app, int width, int height ) + { + SDL_SetWindowSize( app->window, width, height ); + } + + +int app_window_width( app_t* app ) + { + int width = 0; + SDL_GetWindowSize( app->window, &width, NULL ); + return width; + } + + +int app_window_height( app_t* app ) + { + int height = 0; + SDL_GetWindowSize( app->window, NULL, &height ); + return height; + } + + +void app_window_pos( app_t* app, int x, int y ) + { + SDL_SetWindowPosition( app->window, x, y ); + } + + +int app_window_x( app_t* app ) + { + int x = 0; + SDL_GetWindowPosition( app->window, &x, NULL ); + return x; + } + + +int app_window_y( app_t* app ) + { + int y = 0; + SDL_GetWindowPosition( app->window, NULL, &y ); + return y; + } + + +app_displays_t app_displays( app_t* app ) + { + app_displays_t displays; + displays.count = app->display_count; + displays.displays = app->displays; + return displays; + } + + +void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) + { + if( pixels_xbgr ) app_internal_opengl_present( &app->gl, pixels_xbgr, width, height, mod_xbgr, border_xbgr ); + SDL_GL_SwapWindow( app->window ); + } + + +static void app_internal_sdl_sound_callback( void* userdata, Uint8* stream, int len ) + { + app_t* app = (app_t*) userdata; + if( app->sound_callback ) + { + app->sound_callback( (APP_S16*) stream, len / ( 2 * sizeof( APP_S16 ) ), app->sound_user_data ); + if( app->volume < 256 ) + { + APP_S16* samples = (APP_S16*) stream; + for( int i = 0; i < len / sizeof( APP_S16 ); ++i ) + { + int s = (int)(*samples); + s = ( s * app->volume ) >> 8; + *samples++ = (APP_S16) s; + } + } + } + } + + +void app_sound( app_t* app, int sample_pairs_count, void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ), void* user_data ) + { + if( app->sound_device ) + { + SDL_PauseAudioDevice( app->sound_device, 1 ); + SDL_CloseAudioDevice( app->sound_device ); + app->sound_callback = NULL; + app->sound_user_data = NULL; + app->sound_device = 0; + } + if( sample_pairs_count > 0 && sound_callback ) + { + SDL_AudioSpec spec; + spec.freq = 44100; + spec.format = AUDIO_S16; + spec.channels = 2; + spec.silence = 0; + spec.samples = sample_pairs_count * 2; + spec.padding = 0; + spec.size = 0; + spec.callback = app_internal_sdl_sound_callback; + spec.userdata = app; + + app->sound_device = SDL_OpenAudioDevice( NULL, 0, &spec, NULL, 0 ); + if( !app->sound_device ) return; + + app->sound_callback = sound_callback; + app->sound_user_data = user_data; + SDL_PauseAudioDevice( app->sound_device, 0 ); + } + } + + +void app_sound_volume( app_t* app, float volume ) + { + int v = (int) ( volume * 256.0f ); + app->volume = v < 0 ? 0 : v > 256 ? 256 : v; + } + + +app_input_t app_input( app_t* app ) + { + app_input_t input; + input.events = app->input_events; + input.count = app->input_count; + app->input_count = 0; + return input; + } + + +void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ) + { + if( width == 0 || height == 0 ) return; + int window_width; + int window_height; + SDL_GL_GetDrawableSize( app->window, &window_width, &window_height ); + + + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = window_width / (float) width; + float vscale = window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( window_width - pixel_scale * width ) / 2.0f; + float vborder = ( window_height - pixel_scale * height ) / 2.0f; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = window_width / width; + int vscale = window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( window_width - pixel_scale * width ) / 2; + int vborder = ( window_height - pixel_scale * height ) / 2; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + } + + +void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ) + { + int window_width; + int window_height; + SDL_GL_GetDrawableSize( app->window, &window_width, &window_height ); + + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = window_width / (float) width; + float vscale = window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( window_width - pixel_scale * width ) / 2.0f; + float vborder = ( window_height - pixel_scale * height ) / 2.0f; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = window_width / width; + int vscale = window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( window_width - pixel_scale * width ) / 2; + int vborder = ( window_height - pixel_scale * height ) / 2; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + } + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WEBASSEMBLY +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#elif defined( APP_WASM ) + +#ifndef APP_MALLOC + #include + #if defined(__cplusplus) + #define APP_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define APP_MALLOC( ctx, size ) ( malloc( size ) ) + #define APP_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif + +#include +#include +#include + +WAJIC(void, app_js_print, (const char* msg), + { + WA.print(MStrGet(msg) + "\n"); + }) + +#ifndef APP_FATAL_ERROR + #define APP_FATAL_ERROR( ctx, message ) { app_js_print(message); abort(); } +#endif + +struct app_t + { + void* memctx; + void* logctx; + void* fatalctx; + struct app_internal_opengl_t gl; + int has_focus; + app_interpolation_t interpolation; + + void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ); + void* sound_user_data; + int sound_buffer_size; + APP_S16* sound_buffer; + int volume; + + app_input_event_t input_events[ 1024 ]; + int input_count; + int pointer_x; + int pointer_y; + }; + + +// The javascript event handling keeps a simple buffer of events with 3 ints per event +// The first int is the event id, followed by two arguments (not all events use both arguments) +// EVENT ID ARG 1 ARG 2 +// 1 WINDOW_SIZE_CHANGED w h +// 2 WINDOW_FOCUS gained/lost +// 3 KEY down/up scancode +// 4 CHAR charcode +// 5 MOUSE_MOTION x y +// 6 MOUSE_BUTTON down/up buttonnum +// 7 MOUSE_WHEEL wheel + +WAJIC_WITH_INIT( +( + var evts = [], evtcursor = 0, dpr = window.devicePixelRatio||1, canvas, aspect_ratio, fullscreen; + var update_canvas_size = (w)=> + { + var h = (w *= dpr)/aspect_ratio|0; + if (w<32 || h<32 || (w == canvas.width && h == canvas.height) || fullscreen) return; + canvas.width = w; + canvas.height = h; + evts.push(1, canvas.width, canvas.height); + }; + var alias = (el, a, b, c)=> + { + return el[a+c] || el['moz'+b+c] || el['webkit'+b+c] || el['ms'+b+c]; + }; +), +void, app_js_setup_canvas, (int* out_width, int* out_height), +{ + canvas = WA.canvas; + if (!canvas.height) { canvas.width = 1024; canvas.height = 576; } + MU32[out_width>>2] = canvas.clientWidth; + MU32[out_height>>2] = canvas.clientHeight; + aspect_ratio = canvas.width/canvas.height; + + var cancelEvent = (e)=>{ if (e.preventDefault) e.preventDefault(true); else if (e.stopPropagation) e.stopPropagation(true); else e.stopped = true; }; + var documtEvent = (t, f, a)=>{ document.addEventListener(t, f); if (!a) { documtEvent('moz'+t, f, 1); documtEvent('webkit'+t, f, 1); documtEvent('ms'+t, f, 1); } }; + var windowEvent = (t, f)=>{ window.addEventListener(t, f, true); }; + var canvasEvent = (t, f)=>{ canvas.addEventListener(t, f, {capture:true,passive:false}); }; + windowEvent('resize', ()=>{ update_canvas_size(canvas.clientWidth); }); + windowEvent('focus', ()=>{ evts.push(2, 1, 0); }); + windowEvent('blur', ()=>{ evts.push(2, (fullscreen?1:0), 0); }); + windowEvent('keydown', (e)=> + { + evts.push(3, 1, e.keyCode); + if (e.key.length == 1 && e.key.charCodeAt() < 128 && !e.ctrlKey) evts.push(4, e.key.charCodeAt(), 0); + cancelEvent(e); + }); + windowEvent('keyup', (e)=> + { + evts.push(3, 0, e.keyCode); + cancelEvent(e); + }); + canvasEvent('mousemove', (e)=> + { + evts.push(5, + (e.offsetX * canvas.width / canvas.clientWidth )|0, + (e.offsetY * canvas.height / canvas.clientHeight)|0); + cancelEvent(e); + }); + var buttons = 0; + canvasEvent('mousedown', (e)=> + { + var btn = (1< + { + var btn = (1<{ evts.push(7, e.deltaY); cancelEvent(e); }); + documtEvent('fullscreenchange', ()=> + { + fullscreen = alias(document,'f','F','ullscreenElement') || alias(document,'f','F','ullScreenElement'); + if (fullscreen) + { + canvas.orgS = canvas.style.cssText; + canvas.orgW = canvas.clientWidth; + canvas.style.cssText = 'background:black'; + canvas.height = screen.height * dpr; + canvas.width = screen.width * dpr; + evts.push(1, canvas.width, canvas.height); + } + else if (canvas.orgS) + { + canvas.style.cssText = canvas.orgS; + update_canvas_size(canvas.orgW); + } + }); + WA.SetFullscreen = (f)=> + { + if (!f == !fullscreen) return; + var el = (f ? WA.canvas : document); + var fn = (f ? (alias(el,'r','R','equestFullscreen') || alias(el,'r','R','equestFullScreen')) : (alias(el,'e','E','xitFullscreen') || alias(el,'c','C','ancelFullScreen'))); + if (fn) fn.apply(el, []); + }; +}) + +WAJIC(void, app_js_screenmode, (int fullscreen), +{ + WA.SetFullscreen(fullscreen); +}) + +WAJIC(void, app_js_set_aspect_ratio, (int* width, int* height), +{ + var new_aspect_ratio = MU32[width>>2]/MU32[height>>2]; + if (Math.abs(new_aspect_ratio - aspect_ratio) > 0.01) + { + aspect_ratio = new_aspect_ratio; + update_canvas_size(canvas.clientWidth); + } + MU32[width>>2] = canvas.width; + MU32[height>>2] = canvas.height; +}) + +WAJIC(int, app_js_get_event, (int evt[3]), +{ + if (evtcursor >= evts.length) + { + evts.length = evtcursor = 0; + return 0; + } + MU32[(evt>>2)+0] = evts[evtcursor++]; + MU32[(evt>>2)+1] = evts[evtcursor++]; + MU32[(evt>>2)+2] = evts[evtcursor++]; + return 1; +}) + + +int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx ) + { + app_t* app = (app_t*) APP_MALLOC( memctx, sizeof( app_t ) ); + memset( app, 0, (int)sizeof( app_t ) ); + app->memctx = memctx; + app->logctx = logctx; + app->fatalctx = fatalctx; + app->interpolation = APP_INTERPOLATION_LINEAR; + + app->gl.CreateShader = glCreateShader; + app->gl.ShaderSource = glShaderSource; + app->gl.CompileShader = glCompileShader; + app->gl.GetShaderiv = glGetShaderiv; + app->gl.CreateProgram = glCreateProgram; + app->gl.AttachShader = glAttachShader; + app->gl.BindAttribLocation = glBindAttribLocation; + app->gl.LinkProgram = glLinkProgram; + app->gl.GetProgramiv = glGetProgramiv; + app->gl.GenBuffers = glGenBuffers; + app->gl.BindBuffer = glBindBuffer; + app->gl.EnableVertexAttribArray = glEnableVertexAttribArray; + app->gl.VertexAttribPointer = glVertexAttribPointer; + app->gl.GenTextures = glGenTextures; + app->gl.Enable = glEnable; + app->gl.ActiveTexture = glActiveTexture; + app->gl.BindTexture = glBindTexture; + app->gl.TexParameteri = glTexParameteri; + app->gl.DeleteBuffers = glDeleteBuffers; + app->gl.DeleteTextures = glDeleteTextures; + app->gl.BufferData = glBufferData; + app->gl.UseProgram = glUseProgram; + app->gl.Uniform1i = glUniform1i; + app->gl.Uniform3f = glUniform3f; + app->gl.GetUniformLocation = glGetUniformLocation; + app->gl.TexImage2D = glTexImage2D; + app->gl.ClearColor = glClearColor; + app->gl.Clear = glClear; + app->gl.DrawArrays = glDrawArrays; + app->gl.Viewport = glViewport; + app->gl.DeleteShader = glDeleteShader; + app->gl.DeleteProgram = glDeleteProgram; + #ifdef APP_REPORT_SHADER_ERRORS + app->gl.GetShaderInfoLog = glGetShaderInfoLog; + #endif + + int result = 0xff; + + app_js_setup_canvas( &app->gl.window_width, &app->gl.window_height ); + glSetupCanvasContext( 1, 0, 0, 0 ); + glViewport( 0, 0, app->gl.window_width, app->gl.window_height ); + + app->has_focus = 1; + app->volume = 256; + + int glres = app_internal_opengl_init( app, &app->gl, app->interpolation, app->gl.window_width, app->gl.window_height ); + if( !glres ) + { + app_fatal_error( app, "OpenGL init fail" ); + goto init_failed; + } + WaCoroInitNew( NULL, NULL, NULL, 0 ); + result = app_proc( app, user_data ); + +init_failed: + + APP_FREE( memctx, app ); + return result; + } + + +static void app_internal_add_input_event( app_t* app, app_input_event_t* event ) + { + if( app->has_focus ) + { + if( app->input_count < sizeof( app->input_events ) / sizeof( *app->input_events ) ) + app->input_events[ app->input_count++ ] = *event; + } + } + + +static app_key_t app_internal_scancode_to_appkey( app_t* app, int scancode ) + { + static const app_key_t map[] = { + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_CANCEL, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_HELP, APP_KEY_INVALID, APP_KEY_BACK, APP_KEY_TAB, + APP_KEY_RETURN, APP_KEY_INVALID, APP_KEY_CLEAR, APP_KEY_RETURN, APP_KEY_RETURN, APP_KEY_INVALID, APP_KEY_LSHIFT, APP_KEY_LCONTROL, APP_KEY_LMENU, APP_KEY_PAUSE, + APP_KEY_CAPITAL, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_HANJA, APP_KEY_INVALID, APP_KEY_ESCAPE, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_MODECHANGE, APP_KEY_SPACE, APP_KEY_PRIOR, APP_KEY_NEXT, APP_KEY_END, APP_KEY_HOME, APP_KEY_LEFT, APP_KEY_UP, APP_KEY_RIGHT, + APP_KEY_DOWN, APP_KEY_SELECT, APP_KEY_SNAPSHOT, APP_KEY_EXEC, APP_KEY_SNAPSHOT, APP_KEY_INSERT, APP_KEY_DELETE, APP_KEY_HELP, APP_KEY_0, APP_KEY_1, + APP_KEY_2, APP_KEY_3, APP_KEY_4, APP_KEY_5, APP_KEY_6, APP_KEY_7, APP_KEY_8, APP_KEY_9, APP_KEY_INVALID, APP_KEY_OEM_1, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_A, APP_KEY_B, APP_KEY_C, APP_KEY_D, APP_KEY_E, + APP_KEY_F, APP_KEY_G, APP_KEY_H, APP_KEY_I, APP_KEY_J, APP_KEY_K, APP_KEY_L, APP_KEY_M, APP_KEY_N, APP_KEY_O, + APP_KEY_P, APP_KEY_Q, APP_KEY_R, APP_KEY_S, APP_KEY_T, APP_KEY_U, APP_KEY_V, APP_KEY_W, APP_KEY_X, APP_KEY_Y, + APP_KEY_Z, APP_KEY_LWIN, APP_KEY_RWIN, APP_KEY_APPS, APP_KEY_INVALID, APP_KEY_SLEEP, APP_KEY_NUMPAD0, APP_KEY_NUMPAD1, APP_KEY_NUMPAD2, APP_KEY_NUMPAD3, + APP_KEY_NUMPAD4, APP_KEY_NUMPAD5, APP_KEY_NUMPAD6, APP_KEY_NUMPAD7, APP_KEY_NUMPAD8, APP_KEY_NUMPAD9, APP_KEY_MULTIPLY, APP_KEY_ADD, APP_KEY_OEM_COMMA, APP_KEY_SUBTRACT, + APP_KEY_INVALID, APP_KEY_DIVIDE, APP_KEY_F1, APP_KEY_F2, APP_KEY_F3, APP_KEY_F4, APP_KEY_F5, APP_KEY_F6, APP_KEY_F7, APP_KEY_F8, + APP_KEY_F9, APP_KEY_F10, APP_KEY_F11, APP_KEY_F12, APP_KEY_F13, APP_KEY_F14, APP_KEY_F15, APP_KEY_F16, APP_KEY_F17, APP_KEY_F18, + APP_KEY_F19, APP_KEY_F20, APP_KEY_F21, APP_KEY_F22, APP_KEY_F23, APP_KEY_F24, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_NUMLOCK, APP_KEY_SCROLL, APP_KEY_RETURN, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_LSHIFT, APP_KEY_RSHIFT, APP_KEY_LCONTROL, APP_KEY_RCONTROL, APP_KEY_LMENU, APP_KEY_RMENU, APP_KEY_BROWSER_BACK, APP_KEY_BROWSER_FORWARD, APP_KEY_BROWSER_REFRESH, APP_KEY_BROWSER_STOP, + APP_KEY_BROWSER_SEARCH, APP_KEY_BROWSER_FAVORITES, APP_KEY_BROWSER_HOME, APP_KEY_VOLUME_MUTE, APP_KEY_VOLUME_DOWN, APP_KEY_VOLUME_UP, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_RETURN, APP_KEY_INVALID, + APP_KEY_LAUNCH_MAIL, APP_KEY_LAUNCH_MEDIA_SELECT, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_OEM_1, APP_KEY_INVALID, APP_KEY_OEM_COMMA, APP_KEY_OEM_MINUS, + APP_KEY_OEM_PERIOD, APP_KEY_OEM_2, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_OEM_4, + APP_KEY_OEM_5, APP_KEY_OEM_6, APP_KEY_OEM_7, APP_KEY_INVALID, APP_KEY_LWIN, APP_KEY_RMENU, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_HANGUL, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_CRSEL, APP_KEY_EXSEL, APP_KEY_INVALID, + APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_INVALID, APP_KEY_CLEAR, APP_KEY_INVALID, }; + + if( scancode < 0 || scancode >= sizeof( map ) / sizeof( *map ) ) return APP_KEY_INVALID; + return (app_key_t) map[ scancode ]; + } + + +app_state_t app_yield( app_t* app ) + { + int evt[3]; + app_input_event_t input_event; + app_key_t key; + while (app_js_get_event( evt )) + { + switch (evt[0]) + { + // EVENT ID ARG 1 ARG 2 + case 1: // WINDOW_SIZE_CHANGED w h + if( evt[1] != app->gl.window_width || evt[2] != app->gl.window_height ) + { + app_internal_opengl_resize( &app->gl, evt[1], evt[2] ); + } + break; + case 2: // WINDOW_FOCUS gained/lost + app->has_focus = evt[1]; + break; + case 3: // KEY down/up scancode + input_event.type = (evt[1] ? APP_INPUT_KEY_DOWN : APP_INPUT_KEY_UP); + key = app_internal_scancode_to_appkey( app, evt[2] ); + if( key == APP_KEY_LCONTROL || key == APP_KEY_RCONTROL ) + { + input_event.data.key = APP_KEY_CONTROL; + app_internal_add_input_event( app, &input_event ); + } + else if( key == APP_KEY_LSHIFT || key == APP_KEY_RSHIFT ) + { + input_event.data.key = APP_KEY_SHIFT; + app_internal_add_input_event( app, &input_event ); + } + else if( key == APP_KEY_LMENU || key == APP_KEY_RMENU ) + { + input_event.data.key = APP_KEY_MENU; + app_internal_add_input_event( app, &input_event ); + } + input_event.data.key = key; + app_internal_add_input_event( app, &input_event ); + break; + case 4: // CHAR charcode + input_event.type = APP_INPUT_CHAR; + input_event.data.char_code = (char) evt[1]; + app_internal_add_input_event( app, &input_event ); + break; + case 5: // MOUSE_MOTION x y + input_event.type = APP_INPUT_MOUSE_MOVE; + app->pointer_x = input_event.data.mouse_pos.x = evt[1]; + app->pointer_y = input_event.data.mouse_pos.y = evt[2]; + app_internal_add_input_event( app, &input_event ); + break; + case 6: // MOUSE_BUTTON down/up buttonnum + input_event.type = (evt[1] ? APP_INPUT_KEY_DOWN : APP_INPUT_KEY_UP); + if( evt[2] == 0 ) + input_event.data.key = APP_KEY_LBUTTON; + else if( evt[2] == 1 ) + input_event.data.key = APP_KEY_RBUTTON; + else if( evt[2] == 2 ) + input_event.data.key = APP_KEY_MBUTTON; + else if( evt[2] == 3 ) + input_event.data.key = APP_KEY_XBUTTON1; + else if( evt[2] == 4 ) + input_event.data.key = APP_KEY_XBUTTON2; + else + break; + app_internal_add_input_event( app, &input_event ); + break; + case 7: // MOUSE_WHEEL wheel + { + float const microsoft_mouse_wheel_constant = 120.0f; + float wheel_delta = ( (float) evt[1] ) / microsoft_mouse_wheel_constant; + if( app->input_count > 0 && app->input_events[ app->input_count - 1 ].type == APP_INPUT_SCROLL_WHEEL ) + { + app_input_event_t* event = &app->input_events[ app->input_count - 1 ]; + event->data.wheel_delta += wheel_delta; + } + else + { + input_event.type = APP_INPUT_SCROLL_WHEEL; + input_event.data.wheel_delta = wheel_delta; + app_internal_add_input_event( app, &input_event ); + } + } break; + } + } + return APP_STATE_NORMAL; + } + + +void app_cancel_exit( app_t* app ) { /* NOT IMPLEMENTED */ } +void app_title( app_t* app, char const* title ) { /* NOT IMPLEMENTED */ } +char const* app_cmdline( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_filename( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_userdata( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } +char const* app_appdata( app_t* app ) { /* NOT IMPLEMENTED */ return NULL; } + + +WAJIC_WITH_INIT( +( + var start_time = Date.now(); +), +APP_U32, app_js_get_ticks, (), +{ + return Date.now() - start_time; +}) + +APP_U64 app_time_count( app_t* app ) + { + return (APP_U64)app_js_get_ticks()*1000; + } + + +APP_U64 app_time_freq( app_t* app ) + { + return (APP_U64)1000*1000; + } + + +void app_log( app_t* app, app_log_level_t level, char const* message ) + { + printf("[APP] [%d] %s\n", (int)level, message); + } + + +void app_fatal_error( app_t* app, char const* message ) + { + APP_FATAL_ERROR( app->fatalctx, message ); + } + + +void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y ) { /* NOT IMPLEMENTED */ } +void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y ) { /* NOT IMPLEMENTED */ } +void app_pointer_pos( app_t* app, int x, int y ) { /* NOT IMPLEMENTED */ } + + +int app_pointer_x( app_t* app ) + { + return app->pointer_x; + } + + +int app_pointer_y( app_t* app ) + { + return app->pointer_y; + } + + +void app_pointer_limit( app_t* app, int x, int y, int width, int height ) { /* NOT IMPLEMENTED */ } +void app_pointer_limit_off( app_t* app ) { /* NOT IMPLEMENTED */ } + +void app_interpolation( app_t* app, app_interpolation_t interpolation ) + { + if( interpolation == app->interpolation ) return; + app->interpolation = interpolation; + + app_internal_opengl_interpolation( &app->gl, interpolation ); + } + + +void app_screenmode( app_t* app, app_screenmode_t screenmode ) + { + app_js_screenmode( (int) ( screenmode == APP_SCREENMODE_FULLSCREEN ) ); + } + + +void app_window_size( app_t* app, int width, int height ) + { + // view size is controlled by the browser, we only control the display aspect ratio + app_js_set_aspect_ratio( &width, &height ); + if( width != app->gl.window_width || height != app->gl.window_height ) + { + app_internal_opengl_resize( &app->gl, width, height ); + } + } + + +int app_window_width( app_t* app ) + { + return app->gl.window_width; + } + + +int app_window_height( app_t* app ) + { + return app->gl.window_height; + } + + +void app_window_pos( app_t* app, int x, int y ) { /* NOT IMPLEMENTED */ } + + +int app_window_x( app_t* app ) + { + return 0; + } + + +int app_window_y( app_t* app ) + { + return 0; + } + + +app_displays_t app_displays( app_t* app ) + { + // Fixed display for web + static app_display_t display; + display.id[0] = '\0'; + display.x = 0; + display.y = 0; + display.width = 1920; + display.height = 1080; + app_displays_t displays; + displays.count = 1; + displays.displays = &display; + return displays; + } + + +WAJIC_WITH_INIT( +( + var audio_ctx, audio_done = 0, audio_latency = 2048, audio_bufs = [], audio_bufidx = 0, audio_miss = 0; + var start_audio = ()=> + { + if (audio_ctx.state == 'running') return 1; + audio_done = audio_ctx.currentTime; + audio_ctx.resume(); + }; + var set_start_audio_event = (name)=>document.addEventListener(name, start_audio, {once:true}); +), +int, app_js_audio_needed, (bool has_focus), +{ + if (!audio_ctx) + { + if (audio_ctx === false) return 0; + try { (audio_ctx = new (alias(window,"","",'AudioContext'))()).createBuffer(1,1,44100).getChannelData(0); } catch (e) { } + if (!audio_ctx) { audio_ctx = false; WA.print('Warning: WebAudio not supported\n'); return 0; } + for (var i = 0; i != 10; i++) audio_bufs[i] = audio_ctx.createBuffer(2, 2048, 44100); + if (!start_audio()) { set_start_audio_event('click'); set_start_audio_event('touchstart'); set_start_audio_event('keydown'); } + } + if (!start_audio() && !start_audio()) return 0; + var ct = audio_ctx.currentTime; + if (audio_done < ct) + { + if (has_focus && (audio_miss += 2) > 7) + { + audio_latency += 2048; + audio_miss = 0; + } + audio_done = ct; + } + else if (audio_miss > 1) audio_miss--; + return ((ct - audio_done) * 44100 + .5 + audio_latency * (has_focus ? 1 : 2) + 2047)>>11; +}) + +WAJIC(int, app_js_audio_push, (APP_S16* sample_pairs, int volume), +{ + sample_pairs = new Int16Array(MU8.buffer).subarray(sample_pairs>>1); + var buf = audio_bufs[audio_bufidx = ((audio_bufidx + 1) % 10)]; + var left = buf.getChannelData(0), right = buf.getChannelData(1); + var f = (1 / 32768) * (volume / 255); + for (var i = 0; i != 2048; i++) + { + left[i] = sample_pairs[i*2] * f; + right[i] = sample_pairs[i*2+1] * f; + } + var source = audio_ctx.createBufferSource(); + source.connect(audio_ctx.destination); + source.buffer = buf; + source[source.start ? 'start' : 'noteOn'](0.005+audio_done); + audio_done += 2048/44100; +}) + + +void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr ) + { + if( pixels_xbgr ) app_internal_opengl_present( &app->gl, pixels_xbgr, width, height, mod_xbgr, border_xbgr ); + for( int needed = app_js_audio_needed(app->has_focus); app->sound_callback && needed--;) + { + app->sound_callback(app->sound_buffer, 2048, app->sound_user_data); + app_js_audio_push(app->sound_buffer, app->volume); + } + if( app->has_focus ) + WaCoroWaitAnimFrame(); + else + WaCoroSleep(50); + } + + +void app_sound( app_t* app, int sample_pairs_count, void (*sound_callback)( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ), void* user_data ) + { + app->sound_callback = sound_callback; + app->sound_user_data = user_data; + if( sound_callback && !app->sound_buffer ) + app->sound_buffer = (APP_S16*) APP_MALLOC( app->memctx, sizeof(APP_S16) * 2048 * 2 ); + else if( !sound_callback && app->sound_buffer ) + APP_FREE( app->memctx, app->sound_buffer ); + } + + +void app_sound_volume( app_t* app, float volume ) + { + int v = (int) ( volume * 256.0f ); + app->volume = v < 0 ? 0 : v > 256 ? 256 : v; + } + + +app_input_t app_input( app_t* app ) + { + app_input_t input; + input.events = app->input_events; + input.count = app->input_count; + app->input_count = 0; + return input; + } + + +void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y ) + { + if( width == 0 || height == 0 ) return; + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = app->gl.window_width / (float) width; + float vscale = app->gl.window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( app->gl.window_width - pixel_scale * width ) / 2.0f; + float vborder = ( app->gl.window_height - pixel_scale * height ) / 2.0f; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = app->gl.window_width / width; + int vscale = app->gl.window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( app->gl.window_width - pixel_scale * width ) / 2; + int vborder = ( app->gl.window_height - pixel_scale * height ) / 2; + *x -= (int)( hborder ); + *y -= (int)( vborder ); + *x = (int)( *x / pixel_scale ); + *y = (int)( *y / pixel_scale ); + } + } + + +void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y ) + { + if( app->interpolation == APP_INTERPOLATION_LINEAR ) + { + float hscale = app->gl.window_width / (float) width; + float vscale = app->gl.window_height / (float) height; + float pixel_scale = hscale < vscale ? hscale : vscale; + if( pixel_scale > 0.0f ) + { + float hborder = ( app->gl.window_width - pixel_scale * width ) / 2.0f; + float vborder = ( app->gl.window_height - pixel_scale * height ) / 2.0f; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + else + { + *x = 0; + *y = 0; + } + } + else + { + int hscale = app->gl.window_width / width; + int vscale = app->gl.window_height / height; + int pixel_scale = pixel_scale = hscale < vscale ? hscale : vscale; + pixel_scale = pixel_scale < 1 ? 1 : pixel_scale; + int hborder = ( app->gl.window_width - pixel_scale * width ) / 2; + int vborder = ( app->gl.window_height - pixel_scale * height ) / 2; + *x = (int)( *x * pixel_scale ); + *y = (int)( *y * pixel_scale ); + *x += (int)( hborder ); + *y += (int)( vborder ); + } + } + + +#else + #error Undefined platform. Define APP_WINDOWS, APP_SDL, APP_WASM or APP_NULL. +#endif + + +#endif /* APP_IMPLEMENTATION */ + +/* +revision history: + 0.4 pointer x/y, callback for sound, modifier keys fix, gl binding fix, cursor fix + 0.3 added API documentation + 0.2 first publicly released version +*/ + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2016 Mattias Gustavsson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/ diff --git a/libs_win32/crtemu.h b/libs_win32/crtemu.h new file mode 100644 index 0000000..ed5246f --- /dev/null +++ b/libs_win32/crtemu.h @@ -0,0 +1,1928 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +crtemu.h - v0.1 - Cathode ray tube emulation shader for C/C++. + +Do this: + #define CRTEMU_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + + +#ifndef crtemu_h +#define crtemu_h + +#ifndef CRTEMU_U32 + #define CRTEMU_U32 unsigned int +#endif +#ifndef CRTEMU_U64 + #define CRTEMU_U64 unsigned long long +#endif + +typedef enum crtemu_type_t { + CRTEMU_TYPE_TV, + CRTEMU_TYPE_PC, + CRTEMU_TYPE_LITE, +} crtemu_type_t; + +typedef struct crtemu_t crtemu_t; + +crtemu_t* crtemu_create( crtemu_type_t type, void* memctx ); + +void crtemu_destroy( crtemu_t* crtemu ); + +void crtemu_frame( crtemu_t* crtemu, CRTEMU_U32* frame_abgr, int frame_width, int frame_height ); + +void crtemu_present( crtemu_t* crtemu, CRTEMU_U64 time_us, CRTEMU_U32 const* pixels_xbgr, int width, int height, + CRTEMU_U32 mod_xbgr, CRTEMU_U32 border_xbgr ); + +void crtemu_coordinates_window_to_bitmap( crtemu_t* crtemu, int width, int height, int* x, int* y ); + +#endif /* crtemu_h */ + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ +#ifdef CRTEMU_IMPLEMENTATION +#undef CRTEMU_IMPLEMENTATION + +#define _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_SECURE_NO_WARNINGS +#include +#include + +#ifndef CRTEMU_MALLOC + #include + #if defined(__cplusplus) + #define CRTEMU_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define CRTEMU_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define CRTEMU_MALLOC( ctx, size ) ( malloc( size ) ) + #define CRTEMU_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif + +#ifdef CRTEMU_REPORT_SHADER_ERRORS + #ifndef CRTEMU_REPORT_ERROR + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define CRTEMU_REPORT_ERROR( str ) printf( "%s", str ) + #endif +#endif + +#ifndef _WIN32 + #define CRTEMU_SDL +#endif + +#ifdef __wasm__ + #define CRTEMU_WEBGL +#endif + +#ifndef CRTEMU_SDL + + #ifdef __cplusplus + extern "C" { + #endif + + __declspec(dllimport) struct HINSTANCE__* __stdcall LoadLibraryA( char const* lpLibFileName ); + __declspec(dllimport) int __stdcall FreeLibrary( struct HINSTANCE__* hModule ); + #if defined(_WIN64) + typedef __int64 (__stdcall* CRTEMU_PROC)( void ); + __declspec(dllimport) CRTEMU_PROC __stdcall GetProcAddress( struct HINSTANCE__* hModule, char const* lpLibFileName ); + #else + typedef __int32 (__stdcall* CRTEMU_PROC)( void ); + __declspec(dllimport) CRTEMU_PROC __stdcall GetProcAddress( struct HINSTANCE__* hModule, char const* lpLibFileName ); + #endif + + #ifdef __cplusplus + } + #endif + + #define CRTEMU_GLCALLTYPE __stdcall + typedef unsigned int CRTEMU_GLuint; + typedef int CRTEMU_GLsizei; + typedef unsigned int CRTEMU_GLenum; + typedef int CRTEMU_GLint; + typedef float CRTEMU_GLfloat; + typedef char CRTEMU_GLchar; + typedef unsigned char CRTEMU_GLboolean; + typedef size_t CRTEMU_GLsizeiptr; + typedef unsigned int CRTEMU_GLbitfield; + + #define CRTEMU_GL_FLOAT 0x1406 + #define CRTEMU_GL_FALSE 0 + #define CRTEMU_GL_FRAGMENT_SHADER 0x8b30 + #define CRTEMU_GL_VERTEX_SHADER 0x8b31 + #define CRTEMU_GL_COMPILE_STATUS 0x8b81 + #define CRTEMU_GL_LINK_STATUS 0x8b82 + #define CRTEMU_GL_INFO_LOG_LENGTH 0x8b84 + #define CRTEMU_GL_ARRAY_BUFFER 0x8892 + #define CRTEMU_GL_TEXTURE_2D 0x0de1 + #define CRTEMU_GL_TEXTURE0 0x84c0 + #define CRTEMU_GL_TEXTURE1 0x84c1 + #define CRTEMU_GL_TEXTURE2 0x84c2 + #define CRTEMU_GL_TEXTURE3 0x84c3 + #define CRTEMU_GL_TEXTURE_MIN_FILTER 0x2801 + #define CRTEMU_GL_TEXTURE_MAG_FILTER 0x2800 + #define CRTEMU_GL_NEAREST 0x2600 + #define CRTEMU_GL_LINEAR 0x2601 + #define CRTEMU_GL_STATIC_DRAW 0x88e4 + #define CRTEMU_GL_RGBA 0x1908 + #define CRTEMU_GL_UNSIGNED_BYTE 0x1401 + #define CRTEMU_GL_COLOR_BUFFER_BIT 0x00004000 + #define CRTEMU_GL_TRIANGLE_FAN 0x0006 + #define CRTEMU_GL_FRAMEBUFFER 0x8d40 + #define CRTEMU_GL_VIEWPORT 0x0ba2 + #define CRTEMU_GL_RGB 0x1907 + #define CRTEMU_GL_COLOR_ATTACHMENT0 0x8ce0 + #define CRTEMU_GL_TEXTURE_WRAP_S 0x2802 + #define CRTEMU_GL_TEXTURE_WRAP_T 0x2803 + #define CRTEMU_GL_CLAMP_TO_BORDER 0x812D + #define CRTEMU_GL_TEXTURE_BORDER_COLOR 0x1004 + +#else + + #ifndef CRTEMU_WEBGL + #include + #include "SDL_opengl.h" + #else + #include + #endif + #define CRTEMU_GLCALLTYPE GLAPIENTRY + + typedef GLuint CRTEMU_GLuint; + typedef GLsizei CRTEMU_GLsizei; + typedef GLenum CRTEMU_GLenum; + typedef GLint CRTEMU_GLint; + typedef GLfloat CRTEMU_GLfloat; + typedef GLchar CRTEMU_GLchar; + typedef GLboolean CRTEMU_GLboolean; + typedef GLsizeiptr CRTEMU_GLsizeiptr; + typedef GLbitfield CRTEMU_GLbitfield; + + #define CRTEMU_GL_FLOAT GL_FLOAT + #define CRTEMU_GL_FALSE GL_FALSE + #define CRTEMU_GL_FRAGMENT_SHADER GL_FRAGMENT_SHADER + #define CRTEMU_GL_VERTEX_SHADER GL_VERTEX_SHADER + #define CRTEMU_GL_COMPILE_STATUS GL_COMPILE_STATUS + #define CRTEMU_GL_LINK_STATUS GL_LINK_STATUS + #define CRTEMU_GL_INFO_LOG_LENGTH GL_INFO_LOG_LENGTH + #define CRTEMU_GL_ARRAY_BUFFER GL_ARRAY_BUFFER + #define CRTEMU_GL_TEXTURE_2D GL_TEXTURE_2D + #define CRTEMU_GL_TEXTURE0 GL_TEXTURE0 + #define CRTEMU_GL_TEXTURE1 GL_TEXTURE1 + #define CRTEMU_GL_TEXTURE2 GL_TEXTURE2 + #define CRTEMU_GL_TEXTURE3 GL_TEXTURE3 + #define CRTEMU_GL_TEXTURE_MIN_FILTER GL_TEXTURE_MIN_FILTER + #define CRTEMU_GL_TEXTURE_MAG_FILTER GL_TEXTURE_MAG_FILTER + #define CRTEMU_GL_NEAREST GL_NEAREST + #define CRTEMU_GL_LINEAR GL_LINEAR + #define CRTEMU_GL_STATIC_DRAW GL_STATIC_DRAW + #define CRTEMU_GL_RGBA GL_RGBA + #define CRTEMU_GL_UNSIGNED_BYTE GL_UNSIGNED_BYTE + #define CRTEMU_GL_COLOR_BUFFER_BIT GL_COLOR_BUFFER_BIT + #define CRTEMU_GL_TRIANGLE_FAN GL_TRIANGLE_FAN + #define CRTEMU_GL_FRAMEBUFFER GL_FRAMEBUFFER + #define CRTEMU_GL_VIEWPORT GL_VIEWPORT + #define CRTEMU_GL_RGB GL_RGB + #define CRTEMU_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0 + #define CRTEMU_GL_TEXTURE_WRAP_S GL_TEXTURE_WRAP_S + #define CRTEMU_GL_TEXTURE_WRAP_T GL_TEXTURE_WRAP_T + #ifndef CRTEMU_WEBGL + #define CRTEMU_GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER + #define CRTEMU_GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR + #else + // WebGL does not support GL_CLAMP_TO_BORDER, we have to emulate + // this behavior with code in the fragment shader + #define CRTEMU_GL_CLAMP_TO_BORDER GL_CLAMP_TO_EDGE + #endif +#endif + + +struct crtemu_t { + crtemu_type_t type; + void* memctx; + + CRTEMU_GLuint vertexbuffer; + CRTEMU_GLuint backbuffer; + CRTEMU_GLuint fbo_backbuffer; + + CRTEMU_GLuint accumulatetexture_a; + CRTEMU_GLuint accumulatetexture_b; + CRTEMU_GLuint accumulatebuffer_a; + CRTEMU_GLuint accumulatebuffer_b; + + CRTEMU_GLuint blurtexture_a; + CRTEMU_GLuint blurtexture_b; + CRTEMU_GLuint blurbuffer_a; + CRTEMU_GLuint blurbuffer_b; + + CRTEMU_GLuint frametexture; + float use_frame; + + CRTEMU_GLuint crt_shader; + CRTEMU_GLuint blur_shader; + CRTEMU_GLuint accumulate_shader; + CRTEMU_GLuint blend_shader; + CRTEMU_GLuint copy_shader; + + int last_present_width; + int last_present_height; + + + #ifndef CRTEMU_SDL + struct HINSTANCE__* gl_dll; + CRTEMU_PROC (CRTEMU_GLCALLTYPE *wglGetProcAddress) (char const* ); + #endif + + void (CRTEMU_GLCALLTYPE* TexParameterfv) (CRTEMU_GLenum target, CRTEMU_GLenum pname, CRTEMU_GLfloat const* params); + void (CRTEMU_GLCALLTYPE* DeleteFramebuffers) (CRTEMU_GLsizei n, CRTEMU_GLuint const* framebuffers); + void (CRTEMU_GLCALLTYPE* GetIntegerv) (CRTEMU_GLenum pname, CRTEMU_GLint *data); + void (CRTEMU_GLCALLTYPE* GenFramebuffers) (CRTEMU_GLsizei n, CRTEMU_GLuint *framebuffers); + void (CRTEMU_GLCALLTYPE* BindFramebuffer) (CRTEMU_GLenum target, CRTEMU_GLuint framebuffer); + void (CRTEMU_GLCALLTYPE* Uniform1f) (CRTEMU_GLint location, CRTEMU_GLfloat v0); + void (CRTEMU_GLCALLTYPE* Uniform2f) (CRTEMU_GLint location, CRTEMU_GLfloat v0, CRTEMU_GLfloat v1); + void (CRTEMU_GLCALLTYPE* FramebufferTexture2D) (CRTEMU_GLenum target, CRTEMU_GLenum attachment, CRTEMU_GLenum textarget, CRTEMU_GLuint texture, CRTEMU_GLint level); + CRTEMU_GLuint (CRTEMU_GLCALLTYPE* CreateShader) (CRTEMU_GLenum type); + void (CRTEMU_GLCALLTYPE* ShaderSource) (CRTEMU_GLuint shader, CRTEMU_GLsizei count, CRTEMU_GLchar const* const* string, CRTEMU_GLint const* length); + void (CRTEMU_GLCALLTYPE* CompileShader) (CRTEMU_GLuint shader); + void (CRTEMU_GLCALLTYPE* GetShaderiv) (CRTEMU_GLuint shader, CRTEMU_GLenum pname, CRTEMU_GLint *params); + CRTEMU_GLuint (CRTEMU_GLCALLTYPE* CreateProgram) (void); + void (CRTEMU_GLCALLTYPE* AttachShader) (CRTEMU_GLuint program, CRTEMU_GLuint shader); + void (CRTEMU_GLCALLTYPE* BindAttribLocation) (CRTEMU_GLuint program, CRTEMU_GLuint index, CRTEMU_GLchar const* name); + void (CRTEMU_GLCALLTYPE* LinkProgram) (CRTEMU_GLuint program); + void (CRTEMU_GLCALLTYPE* GetProgramiv) (CRTEMU_GLuint program, CRTEMU_GLenum pname, CRTEMU_GLint *params); + void (CRTEMU_GLCALLTYPE* GenBuffers) (CRTEMU_GLsizei n, CRTEMU_GLuint *buffers); + void (CRTEMU_GLCALLTYPE* BindBuffer) (CRTEMU_GLenum target, CRTEMU_GLuint buffer); + void (CRTEMU_GLCALLTYPE* EnableVertexAttribArray) (CRTEMU_GLuint index); + void (CRTEMU_GLCALLTYPE* VertexAttribPointer) (CRTEMU_GLuint index, CRTEMU_GLint size, CRTEMU_GLenum type, CRTEMU_GLboolean normalized, CRTEMU_GLsizei stride, void const* pointer); + void (CRTEMU_GLCALLTYPE* GenTextures) (CRTEMU_GLsizei n, CRTEMU_GLuint* textures); + void (CRTEMU_GLCALLTYPE* Enable) (CRTEMU_GLenum cap); + void (CRTEMU_GLCALLTYPE* ActiveTexture) (CRTEMU_GLenum texture); + void (CRTEMU_GLCALLTYPE* BindTexture) (CRTEMU_GLenum target, CRTEMU_GLuint texture); + void (CRTEMU_GLCALLTYPE* TexParameteri) (CRTEMU_GLenum target, CRTEMU_GLenum pname, CRTEMU_GLint param); + void (CRTEMU_GLCALLTYPE* DeleteBuffers) (CRTEMU_GLsizei n, CRTEMU_GLuint const* buffers); + void (CRTEMU_GLCALLTYPE* DeleteTextures) (CRTEMU_GLsizei n, CRTEMU_GLuint const* textures); + void (CRTEMU_GLCALLTYPE* BufferData) (CRTEMU_GLenum target, CRTEMU_GLsizeiptr size, void const *data, CRTEMU_GLenum usage); + void (CRTEMU_GLCALLTYPE* UseProgram) (CRTEMU_GLuint program); + void (CRTEMU_GLCALLTYPE* Uniform1i) (CRTEMU_GLint location, CRTEMU_GLint v0); + void (CRTEMU_GLCALLTYPE* Uniform3f) (CRTEMU_GLint location, CRTEMU_GLfloat v0, CRTEMU_GLfloat v1, CRTEMU_GLfloat v2); + CRTEMU_GLint (CRTEMU_GLCALLTYPE* GetUniformLocation) (CRTEMU_GLuint program, CRTEMU_GLchar const* name); + void (CRTEMU_GLCALLTYPE* TexImage2D) (CRTEMU_GLenum target, CRTEMU_GLint level, CRTEMU_GLint internalformat, CRTEMU_GLsizei width, CRTEMU_GLsizei height, CRTEMU_GLint border, CRTEMU_GLenum format, CRTEMU_GLenum type, void const* pixels); + void (CRTEMU_GLCALLTYPE* ClearColor) (CRTEMU_GLfloat red, CRTEMU_GLfloat green, CRTEMU_GLfloat blue, CRTEMU_GLfloat alpha); + void (CRTEMU_GLCALLTYPE* Clear) (CRTEMU_GLbitfield mask); + void (CRTEMU_GLCALLTYPE* DrawArrays) (CRTEMU_GLenum mode, CRTEMU_GLint first, CRTEMU_GLsizei count); + void (CRTEMU_GLCALLTYPE* Viewport) (CRTEMU_GLint x, CRTEMU_GLint y, CRTEMU_GLsizei width, CRTEMU_GLsizei height); + void (CRTEMU_GLCALLTYPE* DeleteShader) (CRTEMU_GLuint shader); + void (CRTEMU_GLCALLTYPE* DeleteProgram) (CRTEMU_GLuint program); + #ifdef CRTEMU_REPORT_SHADER_ERRORS + void (CRTEMU_GLCALLTYPE* GetShaderInfoLog) (CRTEMU_GLuint shader, CRTEMU_GLsizei bufSize, CRTEMU_GLsizei *length, CRTEMU_GLchar *infoLog); + #endif +}; + + +static CRTEMU_GLuint crtemu_internal_build_shader( crtemu_t* crtemu, char const* vs_source, char const* fs_source ) { + #ifdef CRTEMU_REPORT_SHADER_ERRORS + char error_message[ 1024 ]; + #endif + + CRTEMU_GLuint vs = crtemu->CreateShader( CRTEMU_GL_VERTEX_SHADER ); + crtemu->ShaderSource( vs, 1, (char const**) &vs_source, NULL ); + crtemu->CompileShader( vs ); + CRTEMU_GLint vs_compiled; + crtemu->GetShaderiv( vs, CRTEMU_GL_COMPILE_STATUS, &vs_compiled ); + if( !vs_compiled ) { + #ifdef CRTEMU_REPORT_SHADER_ERRORS + char const* prefix = "Vertex Shader Error: "; + strcpy( error_message, prefix ); + int len = 0, written = 0; + crtemu->GetShaderiv( vs, CRTEMU_GL_INFO_LOG_LENGTH, &len ); + crtemu->GetShaderInfoLog( vs, (CRTEMU_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + CRTEMU_REPORT_ERROR( error_message ); + #endif + return 0; + } + + CRTEMU_GLuint fs = crtemu->CreateShader( CRTEMU_GL_FRAGMENT_SHADER ); + crtemu->ShaderSource( fs, 1, (char const**) &fs_source, NULL ); + crtemu->CompileShader( fs ); + CRTEMU_GLint fs_compiled; + crtemu->GetShaderiv( fs, CRTEMU_GL_COMPILE_STATUS, &fs_compiled ); + if( !fs_compiled ) { + #ifdef CRTEMU_REPORT_SHADER_ERRORS + char const* prefix = "Fragment Shader Error: "; + strcpy( error_message, prefix ); + int len = 0, written = 0; + crtemu->GetShaderiv( vs, CRTEMU_GL_INFO_LOG_LENGTH, &len ); + crtemu->GetShaderInfoLog( fs, (CRTEMU_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + CRTEMU_REPORT_ERROR( error_message ); + #endif + return 0; + } + + + CRTEMU_GLuint prg = crtemu->CreateProgram(); + crtemu->AttachShader( prg, fs ); + crtemu->AttachShader( prg, vs ); + crtemu->BindAttribLocation( prg, 0, "pos" ); + crtemu->LinkProgram( prg ); + + CRTEMU_GLint linked; + crtemu->GetProgramiv( prg, CRTEMU_GL_LINK_STATUS, &linked ); + if( !linked ) { + #ifdef CRTEMU_REPORT_SHADER_ERRORS + char const* prefix = "Shader Link Error: "; + strcpy( error_message, prefix ); + int len = 0, written = 0; + crtemu->GetShaderiv( vs, CRTEMU_GL_INFO_LOG_LENGTH, &len ); + crtemu->GetShaderInfoLog( prg, (CRTEMU_GLsizei)( sizeof( error_message ) - strlen( prefix ) ), &written, + error_message + strlen( prefix ) ); + CRTEMU_REPORT_ERROR( error_message ); + #endif + return 0; + } + + return prg; +} + + +int crtemu_shaders_tv( crtemu_t* crtemu ) { + char const* vs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "attribute vec4 pos;" + "varying vec2 uv;" + "" + "void main( void )" + " {" + " gl_Position = vec4( pos.xy, 0.0, 1.0 );" + " uv = pos.zw;" + " }"; + + char const* crt_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "\n" + "varying vec2 uv;\n" + "\n" + "uniform vec3 modulate;\n" + "uniform vec2 resolution;\n" + "uniform vec2 size;\n" + "uniform float time;\n" + "uniform sampler2D backbuffer;\n" + "uniform sampler2D blurbuffer;\n" + "uniform sampler2D frametexture;\n" + "uniform float use_frame;\n" + "\n" + #ifdef CRTEMU_WEBGL + // WebGL does not support GL_CLAMP_TO_BORDER so we overwrite texture2D + // with this function which emulates the clamp-to-border behavior + "vec4 texture2Dborder(sampler2D samp, vec2 tc)\n" + " {\n" + " float borderdist = .502-max(abs(.5-tc.x), abs(.5-tc.y));\n" + " float borderfade = clamp(borderdist * 400.0, 0.0, 1.0);\n" + " return texture2D( samp, tc ) * borderfade;\n" + " }\n" + "#define texture2D texture2Dborder\n" + #endif + "vec3 tsample( sampler2D samp, vec2 tc, float offs, vec2 resolution )\n" + " {\n" + " tc = tc * vec2(1.025, 0.92) + vec2(-0.0125, 0.04);\n" + " vec3 s = pow( abs( texture2D( samp, vec2( tc.x, 1.0-tc.y ) ).rgb), vec3( 2.2 ) );\n" + " return s*vec3(1.25);\n" + " }" + "\n" + "vec3 filmic( vec3 LinearColor )\n" + " {\n" + " vec3 x = max( vec3(0.0), LinearColor-vec3(0.004));\n" + " return (x*(6.2*x+0.5))/(x*(6.2*x+1.7)+0.06);\n" + " }\n" + "\n" + "vec2 curve( vec2 uv )\n" + " {\n" + " uv = (uv - 0.5) * 2.0;\n" + " uv *= 1.1; \n" + " uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);\n" + " uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);\n" + " uv = (uv / 2.0) + 0.5;\n" + " uv = uv *0.92 + 0.04;\n" + " return uv;\n" + " }\n" + "\n" + "float rand(vec2 co)\n" + " {\n" + " return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n" + " }\n" + " \n" + "void main(void)\n" + " {\n" + " /* Curve */\n" + " vec2 curved_uv = mix( curve( uv ), uv, 0.4 );\n" + " float scale = 0.04;\n" + " vec2 scuv = curved_uv*(1.0-scale)+scale/2.0+vec2(0.003, -0.001);\n" + "\n" + " /* Main color, Bleed */\n" + " vec3 col;\n" + " float x = sin(0.1*time+curved_uv.y*13.0)*sin(0.23*time+curved_uv.y*19.0)*sin(0.3+0.11*time+curved_uv.y*23.0)*0.0012;\n" + " float o =sin(gl_FragCoord.y*1.5)/resolution.x;\n" + " x=x*0.25+o*0.25;\n" + " col.r = tsample(backbuffer,vec2(x+scuv.x+0.0009,scuv.y+0.0009),resolution.y/800.0, resolution ).x+0.02;\n" + " col.g = tsample(backbuffer,vec2(x+scuv.x+0.0000,scuv.y-0.0011),resolution.y/800.0, resolution ).y+0.02;\n" + " col.b = tsample(backbuffer,vec2(x+scuv.x-0.0015,scuv.y+0.0000),resolution.y/800.0, resolution ).z+0.02;\n" + " float i = clamp(col.r*0.299 + col.g*0.587 + col.b*0.114, 0.0, 1.0 ); \n" + " i = pow( 1.0 - pow(i,2.0), 1.0 );\n" + " i = (1.0-i) * 0.85 + 0.15; \n" + "\n" + " /* Ghosting */\n" + " float ghs = 0.15;\n" + " vec3 r = tsample(blurbuffer, vec2(x-0.014*1.0, -0.027)*0.85+0.007*vec2( 0.35*sin(1.0/7.0 + 15.0*curved_uv.y + 0.9*time), \n" + " 0.35*sin( 2.0/7.0 + 10.0*curved_uv.y + 1.37*time) )+vec2(scuv.x+0.001,scuv.y+0.001),\n" + " 5.5+1.3*sin( 3.0/9.0 + 31.0*curved_uv.x + 1.70*time),resolution).xyz*vec3(0.5,0.25,0.25);\n" + " vec3 g = tsample(blurbuffer, vec2(x-0.019*1.0, -0.020)*0.85+0.007*vec2( 0.35*cos(1.0/9.0 + 15.0*curved_uv.y + 0.5*time), \n" + " 0.35*sin( 2.0/9.0 + 10.0*curved_uv.y + 1.50*time) )+vec2(scuv.x+0.000,scuv.y-0.002),\n" + " 5.4+1.3*sin( 3.0/3.0 + 71.0*curved_uv.x + 1.90*time),resolution).xyz*vec3(0.25,0.5,0.25);\n" + " vec3 b = tsample(blurbuffer, vec2(x-0.017*1.0, -0.003)*0.85+0.007*vec2( 0.35*sin(2.0/3.0 + 15.0*curved_uv.y + 0.7*time), \n" + " 0.35*cos( 2.0/3.0 + 10.0*curved_uv.y + 1.63*time) )+vec2(scuv.x-0.002,scuv.y+0.000),\n" + " 5.3+1.3*sin( 3.0/7.0 + 91.0*curved_uv.x + 1.65*time),resolution).xyz*vec3(0.25,0.25,0.5);\n" + "\n" + " col += vec3(ghs*(1.0-0.299))*pow(clamp(vec3(3.0)*r,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + " col += vec3(ghs*(1.0-0.587))*pow(clamp(vec3(3.0)*g,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + " col += vec3(ghs*(1.0-0.114))*pow(clamp(vec3(3.0)*b,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + "\n" + " /* Level adjustment (curves) */\n" + " col *= vec3(0.95,1.05,0.95);\n" + " col = clamp(col*1.3 + 0.75*col*col + 1.25*col*col*col*col*col,vec3(0.0),vec3(10.0));\n" + "\n" + " /* Vignette */\n" + " float vig = (0.1 + 1.0*16.0*curved_uv.x*curved_uv.y*(1.0-curved_uv.x)*(1.0-curved_uv.y));\n" + " vig = 1.3*pow(vig,0.5);\n" + " col *= vig;\n" + "\n" + " /* Scanlines */\n" + " float scans = clamp( 0.35+0.18*sin(4.0*time+curved_uv.y*size.y*1.5), 0.0, 1.0);\n" + " float s = pow(scans,0.9);\n" + " col = col * vec3(s);\n" + "\n" + " /* Vertical lines (shadow mask) */\n" + " col*=1.0-0.23*(clamp((mod(gl_FragCoord.xy.x, 3.0))/2.0,0.0,1.0));\n" + "\n" + " /* Tone map */\n" + " col = filmic( col );\n" + "\n" + " /* Noise */\n" + " /*vec2 seed = floor(curved_uv*resolution.xy*vec2(0.5))/resolution.xy;*/\n" + " vec2 seed = curved_uv*resolution.xy;;\n" + " /* seed = curved_uv; */\n" + " col -= 0.015*pow(vec3(rand( seed +time ), rand( seed +time*2.0 ), rand( seed +time * 3.0 ) ), vec3(1.5) );\n" + "\n" + " /* Flicker */\n" + " col *= (1.0-0.004*(sin(50.0*time+curved_uv.y*2.0)*0.5+0.5));\n" + "\n" + " /* Clamp */\n" + " if (curved_uv.x < 0.0 || curved_uv.x > 1.0)\n" + " col *= 0.0;\n" + " if (curved_uv.y < 0.0 || curved_uv.y > 1.0)\n" + " col *= 0.0;\n" + " col *= modulate;\n" + " /* Frame */\n" + " vec2 fuv=vec2( uv.x, 1.0 - uv.y);\n" + " vec4 f=texture2D(frametexture,fuv);\n" + " vec3 fr = mix( max( col, 0.0), f.xyz, f.w);\n" + " col = mix( col, fr, vec3( use_frame ) );\n" + " \n" + " gl_FragColor = vec4( col, 1.0 );\n" + " }\n" + "\n"; + + char const* blur_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform vec2 blur;" + "uniform sampler2D texture;" + "" + "void main( void )" + " {" + " vec4 sum = texture2D( texture, uv ) * 0.2270270270;" + " sum += texture2D(texture, vec2( uv.x - 4.0 * blur.x, uv.y - 4.0 * blur.y ) ) * 0.0162162162;" + " sum += texture2D(texture, vec2( uv.x - 3.0 * blur.x, uv.y - 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x - 2.0 * blur.x, uv.y - 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x - 1.0 * blur.x, uv.y - 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 1.0 * blur.x, uv.y + 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 2.0 * blur.x, uv.y + 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x + 3.0 * blur.x, uv.y + 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x + 4.0 * blur.x, uv.y + 4.0 * blur.y ) ) * 0.0162162162;" + " gl_FragColor = sum;" + " } " + ""; + + + char const* accumulate_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.96 );" + " } " + ""; + + char const* blend_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.32 );" + " } " + ""; + + char const* copy_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "" + "void main( void )" + " {" + " gl_FragColor = texture2D( tex0, uv );" + " } " + ""; + + crtemu->crt_shader = crtemu_internal_build_shader( crtemu, vs_source, crt_fs_source ); + if( crtemu->crt_shader == 0 ) return 0; + + crtemu->blur_shader = crtemu_internal_build_shader( crtemu, vs_source, blur_fs_source ); + if( crtemu->blur_shader == 0 ) return 0; + + crtemu->accumulate_shader = crtemu_internal_build_shader( crtemu, vs_source, accumulate_fs_source ); + if( crtemu->accumulate_shader == 0 ) return 0; + + crtemu->blend_shader = crtemu_internal_build_shader( crtemu, vs_source, blend_fs_source ); + if( crtemu->blend_shader == 0 ) return 0; + + crtemu->copy_shader = crtemu_internal_build_shader( crtemu, vs_source, copy_fs_source ); + if( crtemu->copy_shader == 0 ) return 0; + + return 1; +} + + +int crtemu_shaders_pc( crtemu_t* crtemu ) { + char const* vs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "attribute vec4 pos;" + "varying vec2 uv;" + "" + "void main( void )" + " {" + " gl_Position = vec4( pos.xy, 0.0, 1.0 );" + " uv = pos.zw;" + " }"; + + char const* crt_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "\n" + "varying vec2 uv;\n" + "\n" + "uniform vec3 modulate;\n" + "uniform vec2 resolution;\n" + "uniform vec2 size;\n" + "uniform float time;\n" + "uniform sampler2D backbuffer;\n" + "uniform sampler2D blurbuffer;\n" + "uniform sampler2D frametexture;\n" + "uniform float use_frame;\n" + "\n" + #ifdef CRTEMU_WEBGL + // WebGL does not support GL_CLAMP_TO_BORDER so we overwrite texture2D + // with this function which emulates the clamp-to-border behavior + "vec4 texture2Dborder(sampler2D samp, vec2 tc)\n" + " {\n" + " float borderdist = .502-max(abs(.5-tc.x), abs(.5-tc.y));\n" + " float borderfade = clamp(borderdist * 400.0, 0.0, 1.0);\n" + " return texture2D( samp, tc ) * borderfade;\n" + " }\n" + "#define texture2D texture2Dborder\n" + #endif + "vec3 tsample( sampler2D samp, vec2 tc, float offs, vec2 resolution )\n" + " {\n" + " tc = tc * vec2(1.156, 1.156) - vec2( 0.078 + 0.003, 0.078 );\n" + " vec3 s = pow( abs( texture2D( samp, vec2( tc.x, 1.0-tc.y ) ).rgb), vec3( 2.2 ) );\n" + " return s*vec3(1.25);\n" + " }\n" + "\n" + "vec3 filmic( vec3 LinearColor )\n" + " {\n" + " vec3 x = max( vec3(0.0), LinearColor-vec3(0.004));\n" + " return (x*(6.2*x+0.5))/(x*(6.2*x+1.7)+0.06);\n" + " }\n" + "\n" + "vec2 curve( vec2 uv )\n" + " {\n" + " uv = (uv - 0.5) * 2.0;\n" + " uv *= 1.1; \n" + " uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);\n" + " uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);\n" + " uv = (uv / 2.0) + 0.5;\n" + " uv = uv *0.92 + 0.04;\n" + " return uv;\n" + " }\n" + "\n" + "float rand(vec2 co)\n" + " {\n" + " return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n" + " }\n" + " \n" + "void main(void)\n" + " {\n" + " /* Curve */\n" + " vec2 curved_uv = mix( curve( uv ), uv, 0.8 );\n" + " float scale = 0.04;\n" + " vec2 scuv = curved_uv*(1.0-scale)+scale/2.0+vec2(0.003, -0.001);\n" + "\n" + " /* Main color, Bleed */\n" + " vec3 col;\n" + " float x = sin(0.1*time+curved_uv.y*13.0)*sin(0.23*time+curved_uv.y*19.0)*sin(0.3+0.11*time+curved_uv.y*23.0)*0.0012;\n" + " float o =sin(gl_FragCoord.y*1.5)/resolution.x;\n" + " x+=o*0.25;\n" + " x *= 0.2;\n" + " col.r = tsample(backbuffer,vec2(x+scuv.x+0.0009*0.25,scuv.y+0.0009*0.25),resolution.y/800.0, resolution ).x+0.02;\n" + " col.g = tsample(backbuffer,vec2(x+scuv.x+0.0000*0.25,scuv.y-0.0011*0.25),resolution.y/800.0, resolution ).y+0.02;\n" + " col.b = tsample(backbuffer,vec2(x+scuv.x-0.0015*0.25,scuv.y+0.0000*0.25),resolution.y/800.0, resolution ).z+0.02;\n" + " float i = clamp(col.r*0.299 + col.g*0.587 + col.b*0.114, 0.0, 1.0 ); \n" + " i = pow( 1.0 - pow(i,2.0), 1.0 );\n" + " i = (1.0-i) * 0.85 + 0.15; \n" + "\n" + " /* Ghosting */\n" + " float ghs = 0.05;\n" + " vec3 r = tsample(blurbuffer, vec2(x-0.014*1.0, -0.027)*0.45+0.007*vec2( 0.35*sin(1.0/7.0 + 15.0*curved_uv.y + 0.9*time), \n" + " 0.35*sin( 2.0/7.0 + 10.0*curved_uv.y + 1.37*time) )+vec2(scuv.x+0.001,scuv.y+0.001),\n" + " 5.5+1.3*sin( 3.0/9.0 + 31.0*curved_uv.x + 1.70*time),resolution).xyz*vec3(0.5,0.25,0.25);\n" + " vec3 g = tsample(blurbuffer, vec2(x-0.019*1.0, -0.020)*0.45+0.007*vec2( 0.35*cos(1.0/9.0 + 15.0*curved_uv.y + 0.5*time), \n" + " 0.35*sin( 2.0/9.0 + 10.0*curved_uv.y + 1.50*time) )+vec2(scuv.x+0.000,scuv.y-0.002),\n" + " 5.4+1.3*sin( 3.0/3.0 + 71.0*curved_uv.x + 1.90*time),resolution).xyz*vec3(0.25,0.5,0.25);\n" + " vec3 b = tsample(blurbuffer, vec2(x-0.017*1.0, -0.003)*0.35+0.007*vec2( 0.35*sin(2.0/3.0 + 15.0*curved_uv.y + 0.7*time), \n" + " 0.35*cos( 2.0/3.0 + 10.0*curved_uv.y + 1.63*time) )+vec2(scuv.x-0.002,scuv.y+0.000),\n" + " 5.3+1.3*sin( 3.0/7.0 + 91.0*curved_uv.x + 1.65*time),resolution).xyz*vec3(0.25,0.25,0.5);\n" + "\n" + " col += vec3(ghs*(1.0-0.299))*pow(clamp(vec3(3.0)*r,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + " col += vec3(ghs*(1.0-0.587))*pow(clamp(vec3(3.0)*g,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + " col += vec3(ghs*(1.0-0.114))*pow(clamp(vec3(3.0)*b,vec3(0.0),vec3(1.0)),vec3(2.0))*vec3(i);\n" + "\n" + " /* Level adjustment (curves) */\n" + " col *= vec3(0.95,0.95,0.95);\n" + " col = clamp(col*1.3 + 0.75*col*col + 1.25*col*col*col*col*col,vec3(0.0),vec3(10.0));\n" + "\n" + " /* Vignette */\n" + " float vig = (0.1 + 1.0*16.0*curved_uv.x*curved_uv.y*(1.0-curved_uv.x)*(1.0-curved_uv.y));\n" + " vig = 1.3*pow(vig,0.5);\n" + " col *= vig;\n" + "\n" + " /* Scanlines */\n" + " float scans = clamp( 0.5+0.2*sin(cos(20.0*time)*0.32+curved_uv.y*size.y*1.75), 0.0, 1.0);\n" + " float s = pow(scans,0.9);\n" + " col = col * vec3(s);\n" + "\n" + " /* Vertical lines (shadow mask) */\n" + " col*=1.0-0.23*(clamp((mod(gl_FragCoord.xy.x, 3.0))/2.0,0.0,1.0));\n" + "\n" + " /* Tone map */\n" + " col = filmic( col );\n" + "\n" + " /* Noise */\n" + " //vec2 seed = floor(curved_uv*resolution.xy*vec2(0.5))/resolution.xy;\n" + " vec2 seed = curved_uv*resolution.xy;;\n" + " /* seed = curved_uv; */\n" + " col -= 0.015*pow(vec3(rand( seed +time ), rand( seed +time*2.0 ), rand( seed +time * 3.0 ) ), vec3(1.5) );\n" + "\n" + " /* Flicker */\n" + " col *= (1.0-0.004*(sin(50.0*time+curved_uv.y*2.0)*0.5+0.5));\n" + "\n" + " /* Clamp */\n" + " if (curved_uv.x < 0.0 || curved_uv.x > 1.0)\n" + " col *= 0.0;\n" + " if (curved_uv.y < 0.0 || curved_uv.y > 1.0)\n" + " col *= 0.0;\n" + " col*=modulate; \n" + " /* Frame */\n" + " vec2 fuv=vec2( uv.x, 1.0 - uv.y);\n" + " vec4 f=texture2D(frametexture, fuv);\n" + " col = mix( col, mix( max( col, 0.0), f.xyz, f.w), vec3( use_frame) );\n" + " \n" + " gl_FragColor = vec4( col, 1.0 );\n" + " }\n" + " \n" + ""; + + char const* blur_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform vec2 blur;" + "uniform sampler2D texture;" + "" + "void main( void )" + " {" + " vec4 sum = texture2D( texture, uv ) * 0.2270270270;" + " sum += texture2D(texture, vec2( uv.x - 4.0 * blur.x, uv.y - 4.0 * blur.y ) ) * 0.0162162162;" + " sum += texture2D(texture, vec2( uv.x - 3.0 * blur.x, uv.y - 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x - 2.0 * blur.x, uv.y - 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x - 1.0 * blur.x, uv.y - 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 1.0 * blur.x, uv.y + 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 2.0 * blur.x, uv.y + 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x + 3.0 * blur.x, uv.y + 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x + 4.0 * blur.x, uv.y + 4.0 * blur.y ) ) * 0.0162162162;" + " gl_FragColor = sum;" + " } " + ""; + + + char const* accumulate_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.96 );" + " } " + ""; + + char const* blend_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.24 );" + " } " + ""; + + char const* copy_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "" + "void main( void )" + " {" + " gl_FragColor = texture2D( tex0, uv );" + " } " + ""; + + crtemu->crt_shader = crtemu_internal_build_shader( crtemu, vs_source, crt_fs_source ); + if( crtemu->crt_shader == 0 ) return 0; + + crtemu->blur_shader = crtemu_internal_build_shader( crtemu, vs_source, blur_fs_source ); + if( crtemu->blur_shader == 0 ) return 0; + + crtemu->accumulate_shader = crtemu_internal_build_shader( crtemu, vs_source, accumulate_fs_source ); + if( crtemu->accumulate_shader == 0 ) return 0; + + crtemu->blend_shader = crtemu_internal_build_shader( crtemu, vs_source, blend_fs_source ); + if( crtemu->blend_shader == 0 ) return 0; + + crtemu->copy_shader = crtemu_internal_build_shader( crtemu, vs_source, copy_fs_source ); + if( crtemu->copy_shader == 0 ) return 0; + + return 1; +} + + +int crtemu_shaders_lite( crtemu_t* crtemu ) { + char const* vs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "attribute vec4 pos;" + "varying vec2 uv;" + "" + "void main( void )" + " {" + " gl_Position = vec4( pos.xy, 0.0, 1.0 );" + " uv = pos.zw;" + " }"; + + char const* crt_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "\n" + "varying vec2 uv;\n" + "\n" + "uniform vec3 modulate;\n" + "uniform vec2 resolution;\n" + "uniform vec2 size;\n" + "uniform float time;\n" + "uniform sampler2D backbuffer;\n" + "uniform sampler2D blurbuffer;\n" + "uniform sampler2D frametexture;\n" + "uniform float use_frame;\n" + "\n" + /* #ifdef CRTEMU_WEBGL + // WebGL does not support GL_CLAMP_TO_BORDER so we overwrite texture2D + // with this function which emulates the clamp-to-border behavior + "vec4 texture2Dborder(sampler2D samp, vec2 tc)\n" + " {\n" + " float borderdist = .502-max(abs(.5-tc.x), abs(.5-tc.y));\n" + " float borderfade = clamp(borderdist * 400.0, 0.0, 1.0);\n" + " return texture2D( samp, tc ) * borderfade;\n" + " }\n" + "#define texture2D texture2Dborder\n" + #endif*/ + "vec3 tsample( sampler2D samp, vec2 tc )\n" + " {\n" + " vec3 s = pow( abs( texture2D( samp, vec2( tc.x, 1.0-tc.y ) ).rgb), vec3( 2.2 ) );\n" + " return s;\n" + " }\n" + "\n" + "vec3 filmic( vec3 LinearColor )\n" + " {\n" + " vec3 x = max( vec3(0.0), LinearColor-vec3(0.004));\n" + " return (x*(6.2*x+0.5))/(x*(6.2*x+1.7)+0.06);\n" + " }\n" + "\n" + "void main(void)\n" + " {\n" + " // Main color\n" + " vec3 col;\n" + " col = mix( tsample(backbuffer,uv ), tsample(frametexture,uv ), 0.45 );\n" + " col = 3.0*col + pow( col, vec3( 3.0 ) );\n" + " col += tsample(blurbuffer,uv )*0.3;\n" + "\n" + " // Scanlines\n" + " float scans = clamp( 0.5-0.5*cos( uv.y * 6.28319 * (size.y ) ), 0.0, 1.0);\n" + " float s = pow(scans,1.3);\n" + " col = mix( col, col * vec3(s), 0.7 );\n" + "\n" + " // Vertical lines (shadow mask)\n" + " col*=1.0-0.23*(clamp((mod(gl_FragCoord.xy.x, 3.0))/2.0,0.0,1.0));\n" + "\n" + " // Vignette\n" + " float vig = (0.1 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));\n" + " vig = 1.3*pow(vig,0.5);\n" + " col = mix( col, col*vig, 0.2 );\n" + "\n" + " // Tone map\n" + " col = mix( pow( col, vec3(1.0 / 2.2) ), filmic( col ), 0.5 );\n" + "\n" + " col*=modulate; \n" + " gl_FragColor = vec4( col, 1.0 );\n" + " }\n" + " \n" + ""; + + char const* blur_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform vec2 blur;" + "uniform sampler2D texture;" + "" + "void main( void )" + " {" + " vec4 sum = texture2D( texture, uv ) * 0.2270270270;" + " sum += texture2D(texture, vec2( uv.x - 4.0 * blur.x, uv.y - 4.0 * blur.y ) ) * 0.0162162162;" + " sum += texture2D(texture, vec2( uv.x - 3.0 * blur.x, uv.y - 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x - 2.0 * blur.x, uv.y - 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x - 1.0 * blur.x, uv.y - 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 1.0 * blur.x, uv.y + 1.0 * blur.y ) ) * 0.1945945946;" + " sum += texture2D(texture, vec2( uv.x + 2.0 * blur.x, uv.y + 2.0 * blur.y ) ) * 0.1216216216;" + " sum += texture2D(texture, vec2( uv.x + 3.0 * blur.x, uv.y + 3.0 * blur.y ) ) * 0.0540540541;" + " sum += texture2D(texture, vec2( uv.x + 4.0 * blur.x, uv.y + 4.0 * blur.y ) ) * 0.0162162162;" + " gl_FragColor = sum;" + " } " + ""; + + + char const* accumulate_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.96 );" + " } " + ""; + + char const* blend_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "uniform sampler2D tex1;" + "uniform float modulate;" + "" + "void main( void )" + " {" + " vec4 a = texture2D( tex0, uv ) * vec4( modulate );" + " vec4 b = texture2D( tex1, uv );" + "" + " gl_FragColor = max( a, b * 0.2 );" + " } " + ""; + + char const* copy_fs_source = + #ifdef CRTEMU_WEBGL + "precision highp float;\n\n" + #else + "#version 120\n\n" + #endif + "" + "varying vec2 uv;" + "" + "uniform sampler2D tex0;" + "" + "void main( void )" + " {" + " gl_FragColor = texture2D( tex0, uv );" + " } " + ""; + + crtemu->crt_shader = crtemu_internal_build_shader( crtemu, vs_source, crt_fs_source ); + if( crtemu->crt_shader == 0 ) return 0; + + crtemu->blur_shader = crtemu_internal_build_shader( crtemu, vs_source, blur_fs_source ); + if( crtemu->blur_shader == 0 ) return 0; + + crtemu->accumulate_shader = crtemu_internal_build_shader( crtemu, vs_source, accumulate_fs_source ); + if( crtemu->accumulate_shader == 0 ) return 0; + + crtemu->blend_shader = crtemu_internal_build_shader( crtemu, vs_source, blend_fs_source ); + if( crtemu->blend_shader == 0 ) return 0; + + crtemu->copy_shader = crtemu_internal_build_shader( crtemu, vs_source, copy_fs_source ); + if( crtemu->copy_shader == 0 ) return 0; + + return 1; + +} + + +crtemu_t* crtemu_create( crtemu_type_t type, void* memctx ) { + crtemu_t* crtemu = (crtemu_t*) CRTEMU_MALLOC( memctx, sizeof( crtemu_t ) ); + memset( crtemu, 0, sizeof( crtemu_t ) ); + crtemu->type = type; + crtemu->memctx = memctx; + + crtemu->use_frame = 0.0f; + + crtemu->last_present_width = 0; + crtemu->last_present_height = 0; + + #ifndef CRTEMU_SDL + + crtemu->gl_dll = LoadLibraryA( "opengl32.dll" ); + if( !crtemu->gl_dll ) goto failed; + + crtemu->wglGetProcAddress = (CRTEMU_PROC (CRTEMU_GLCALLTYPE*)(char const*)) (uintptr_t) GetProcAddress( crtemu->gl_dll, "wglGetProcAddress" ); + if( !crtemu->gl_dll ) goto failed; + + // Attempt to bind opengl functions using GetProcAddress + crtemu->TexParameterfv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLfloat const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glTexParameterfv" ); + crtemu->DeleteFramebuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDeleteFramebuffers" ); + crtemu->GetIntegerv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint *) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGetIntegerv" ); + crtemu->GenFramebuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint *) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGenFramebuffers" ); + crtemu->BindFramebuffer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glBindFramebuffer" ); + crtemu->Uniform1f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glUniform1f" ); + crtemu->Uniform2f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glUniform2f" ); + crtemu->FramebufferTexture2D = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLuint, CRTEMU_GLint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glFramebufferTexture2D" ); + crtemu->CreateShader = ( CRTEMU_GLuint (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glCreateShader" ); + crtemu->ShaderSource = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLsizei, CRTEMU_GLchar const* const*, CRTEMU_GLint const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glShaderSource" ); + crtemu->CompileShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glCompileShader" ); + crtemu->GetShaderiv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLenum, CRTEMU_GLint*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGetShaderiv" ); + crtemu->CreateProgram = ( CRTEMU_GLuint (CRTEMU_GLCALLTYPE*) (void) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glCreateProgram" ); + crtemu->AttachShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glAttachShader" ); + crtemu->BindAttribLocation = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLuint, CRTEMU_GLchar const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glBindAttribLocation" ); + crtemu->LinkProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glLinkProgram" ); + crtemu->GetProgramiv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLenum, CRTEMU_GLint*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGetProgramiv" ); + crtemu->GenBuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGenBuffers" ); + crtemu->BindBuffer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glBindBuffer" ); + crtemu->EnableVertexAttribArray = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glEnableVertexAttribArray" ); + crtemu->VertexAttribPointer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLint, CRTEMU_GLenum, CRTEMU_GLboolean, CRTEMU_GLsizei, void const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glVertexAttribPointer" ); + crtemu->GenTextures = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGenTextures" ); + crtemu->Enable = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glEnable" ); + crtemu->ActiveTexture = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glActiveTexture" ); + crtemu->BindTexture = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glBindTexture" ); + crtemu->TexParameteri = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glTexParameteri" ); + crtemu->DeleteBuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDeleteBuffers" ); + crtemu->DeleteTextures = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDeleteTextures" ); + crtemu->BufferData = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLsizeiptr, void const *, CRTEMU_GLenum) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glBufferData" ); + crtemu->UseProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glUseProgram" ); + crtemu->Uniform1i = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glUniform1i" ); + crtemu->Uniform3f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glUniform3f" ); + crtemu->GetUniformLocation = ( CRTEMU_GLint (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLchar const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGetUniformLocation" ); + crtemu->TexImage2D = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint, CRTEMU_GLint, CRTEMU_GLsizei, CRTEMU_GLsizei, CRTEMU_GLint, CRTEMU_GLenum, CRTEMU_GLenum, void const*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glTexImage2D" ); + crtemu->ClearColor = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glClearColor" ); + crtemu->Clear = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLbitfield) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glClear" ); + crtemu->DrawArrays = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint, CRTEMU_GLsizei) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDrawArrays" ); + crtemu->Viewport = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLint, CRTEMU_GLsizei, CRTEMU_GLsizei) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glViewport" ); + crtemu->DeleteShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDeleteShader" ); + crtemu->DeleteProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glDeleteProgram" ); + #ifdef CRTEMU_REPORT_SHADER_ERRORS + crtemu->GetShaderInfoLog = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLsizei, CRTEMU_GLsizei*, CRTEMU_GLchar*) ) (uintptr_t) GetProcAddress( crtemu->gl_dll, "glGetShaderInfoLog" ); + #endif + + // Any opengl functions which didn't bind, try binding them using wglGetProcAddrss + if( !crtemu->TexParameterfv ) crtemu->TexParameterfv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLfloat const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glTexParameterfv" ); + if( !crtemu->DeleteFramebuffers ) crtemu->DeleteFramebuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glDeleteFramebuffers" ); + if( !crtemu->GetIntegerv ) crtemu->GetIntegerv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint *) ) (uintptr_t) crtemu->wglGetProcAddress( "glGetIntegerv" ); + if( !crtemu->GenFramebuffers ) crtemu->GenFramebuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint *) ) (uintptr_t) crtemu->wglGetProcAddress( "glGenFramebuffers" ); + if( !crtemu->BindFramebuffer ) crtemu->BindFramebuffer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glBindFramebuffer" ); + if( !crtemu->Uniform1f ) crtemu->Uniform1f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat) ) (uintptr_t) crtemu->wglGetProcAddress( "glUniform1f" ); + if( !crtemu->Uniform2f ) crtemu->Uniform2f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) crtemu->wglGetProcAddress( "glUniform2f" ); + if( !crtemu->FramebufferTexture2D ) crtemu->FramebufferTexture2D = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLuint, CRTEMU_GLint) ) (uintptr_t) crtemu->wglGetProcAddress( "glFramebufferTexture2D" ); + if( !crtemu->CreateShader ) crtemu->CreateShader = ( CRTEMU_GLuint (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) crtemu->wglGetProcAddress( "glCreateShader" ); + if( !crtemu->ShaderSource ) crtemu->ShaderSource = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLsizei, CRTEMU_GLchar const* const*, CRTEMU_GLint const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glShaderSource" ); + if( !crtemu->CompileShader ) crtemu->CompileShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glCompileShader" ); + if( !crtemu->GetShaderiv ) crtemu->GetShaderiv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLenum, CRTEMU_GLint*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGetShaderiv" ); + if( !crtemu->CreateProgram ) crtemu->CreateProgram = ( CRTEMU_GLuint (CRTEMU_GLCALLTYPE*) (void) ) (uintptr_t) crtemu->wglGetProcAddress( "glCreateProgram" ); + if( !crtemu->AttachShader ) crtemu->AttachShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glAttachShader" ); + if( !crtemu->BindAttribLocation ) crtemu->BindAttribLocation = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLuint, CRTEMU_GLchar const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glBindAttribLocation" ); + if( !crtemu->LinkProgram ) crtemu->LinkProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glLinkProgram" ); + if( !crtemu->GetProgramiv ) crtemu->GetProgramiv = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLenum, CRTEMU_GLint*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGetProgramiv" ); + if( !crtemu->GenBuffers ) crtemu->GenBuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGenBuffers" ); + if( !crtemu->BindBuffer ) crtemu->BindBuffer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glBindBuffer" ); + if( !crtemu->EnableVertexAttribArray ) crtemu->EnableVertexAttribArray = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glEnableVertexAttribArray" ); + if( !crtemu->VertexAttribPointer ) crtemu->VertexAttribPointer = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLint, CRTEMU_GLenum, CRTEMU_GLboolean, CRTEMU_GLsizei, void const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glVertexAttribPointer" ); + if( !crtemu->GenTextures ) crtemu->GenTextures = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGenTextures" ); + if( !crtemu->Enable ) crtemu->Enable = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) crtemu->wglGetProcAddress( "glEnable" ); + if( !crtemu->ActiveTexture ) crtemu->ActiveTexture = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum) ) (uintptr_t) crtemu->wglGetProcAddress( "glActiveTexture" ); + if( !crtemu->BindTexture ) crtemu->BindTexture = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glBindTexture" ); + if( !crtemu->TexParameteri ) crtemu->TexParameteri = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLenum, CRTEMU_GLint) ) (uintptr_t) crtemu->wglGetProcAddress( "glTexParameteri" ); + if( !crtemu->DeleteBuffers ) crtemu->DeleteBuffers = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glDeleteBuffers" ); + if( !crtemu->DeleteTextures ) crtemu->DeleteTextures = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLsizei, CRTEMU_GLuint const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glDeleteTextures" ); + if( !crtemu->BufferData ) crtemu->BufferData = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLsizeiptr, void const *, CRTEMU_GLenum) ) (uintptr_t) crtemu->wglGetProcAddress( "glBufferData" ); + if( !crtemu->UseProgram ) crtemu->UseProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glUseProgram" ); + if( !crtemu->Uniform1i ) crtemu->Uniform1i = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLint) ) (uintptr_t) crtemu->wglGetProcAddress( "glUniform1i" ); + if( !crtemu->Uniform3f ) crtemu->Uniform3f = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) crtemu->wglGetProcAddress( "glUniform3f" ); + if( !crtemu->GetUniformLocation ) crtemu->GetUniformLocation = ( CRTEMU_GLint (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLchar const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGetUniformLocation" ); + if( !crtemu->TexImage2D ) crtemu->TexImage2D = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint, CRTEMU_GLint, CRTEMU_GLsizei, CRTEMU_GLsizei, CRTEMU_GLint, CRTEMU_GLenum, CRTEMU_GLenum, void const*) ) (uintptr_t) crtemu->wglGetProcAddress( "glTexImage2D" ); + if( !crtemu->ClearColor ) crtemu->ClearColor = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat, CRTEMU_GLfloat) ) (uintptr_t) crtemu->wglGetProcAddress( "glClearColor" ); + if( !crtemu->Clear ) crtemu->Clear = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLbitfield) ) (uintptr_t) crtemu->wglGetProcAddress( "glClear" ); + if( !crtemu->DrawArrays ) crtemu->DrawArrays = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLenum, CRTEMU_GLint, CRTEMU_GLsizei) ) (uintptr_t) crtemu->wglGetProcAddress( "glDrawArrays" ); + if( !crtemu->Viewport ) crtemu->Viewport = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLint, CRTEMU_GLint, CRTEMU_GLsizei, CRTEMU_GLsizei) ) (uintptr_t) crtemu->wglGetProcAddress( "glViewport" ); + if( !crtemu->DeleteShader ) crtemu->DeleteShader = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glDeleteShader" ); + if( !crtemu->DeleteProgram ) crtemu->DeleteProgram = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint) ) (uintptr_t) crtemu->wglGetProcAddress( "glDeleteProgram" ); + #ifdef CRTEMU_REPORT_SHADER_ERRORS + if( !crtemu->GetShaderInfoLog ) crtemu->GetShaderInfoLog = ( void (CRTEMU_GLCALLTYPE*) (CRTEMU_GLuint, CRTEMU_GLsizei, CRTEMU_GLsizei*, CRTEMU_GLchar*) ) (uintptr_t) crtemu->wglGetProcAddress( "glGetShaderInfoLog" ); + #endif + + #else + + crtemu->TexParameterfv = glTexParameterfv; + crtemu->DeleteFramebuffers = glDeleteFramebuffers; + crtemu->GetIntegerv = glGetIntegerv; + crtemu->GenFramebuffers = glGenFramebuffers; + crtemu->BindFramebuffer = glBindFramebuffer; + crtemu->Uniform1f = glUniform1f; + crtemu->Uniform2f = glUniform2f; + crtemu->FramebufferTexture2D = glFramebufferTexture2D; + crtemu->CreateShader = glCreateShader; + crtemu->ShaderSource = glShaderSource; + crtemu->CompileShader = glCompileShader; + crtemu->GetShaderiv = glGetShaderiv; + crtemu->CreateProgram = glCreateProgram; + crtemu->AttachShader = glAttachShader; + crtemu->BindAttribLocation = glBindAttribLocation; + crtemu->LinkProgram = glLinkProgram; + crtemu->GetProgramiv = glGetProgramiv; + crtemu->GenBuffers = glGenBuffers; + crtemu->BindBuffer = glBindBuffer; + crtemu->EnableVertexAttribArray = glEnableVertexAttribArray; + crtemu->VertexAttribPointer = glVertexAttribPointer; + crtemu->GenTextures = glGenTextures; + crtemu->Enable = glEnable; + crtemu->ActiveTexture = glActiveTexture; + crtemu->BindTexture = glBindTexture; + crtemu->TexParameteri = glTexParameteri; + crtemu->DeleteBuffers = glDeleteBuffers; + crtemu->DeleteTextures = glDeleteTextures; + crtemu->BufferData = glBufferData; + crtemu->UseProgram = glUseProgram; + crtemu->Uniform1i = glUniform1i; + crtemu->Uniform3f = glUniform3f; + crtemu->GetUniformLocation = glGetUniformLocation; + crtemu->TexImage2D = glTexImage2D; + crtemu->ClearColor = glClearColor; + crtemu->Clear = glClear; + crtemu->DrawArrays = glDrawArrays; + crtemu->Viewport = glViewport; + crtemu->DeleteShader = glDeleteShader; + crtemu->DeleteProgram = glDeleteProgram; + #ifdef CRTEMU_REPORT_SHADER_ERRORS + crtemu->GetShaderInfoLog = glGetShaderInfoLog; + #endif + + #endif + + // Report error if any gl function was not found. + if( !crtemu->TexParameterfv ) goto failed; + if( !crtemu->DeleteFramebuffers ) goto failed; + if( !crtemu->GetIntegerv ) goto failed; + if( !crtemu->GenFramebuffers ) goto failed; + if( !crtemu->BindFramebuffer ) goto failed; + if( !crtemu->Uniform1f ) goto failed; + if( !crtemu->Uniform2f ) goto failed; + if( !crtemu->FramebufferTexture2D ) goto failed; + if( !crtemu->CreateShader ) goto failed; + if( !crtemu->ShaderSource ) goto failed; + if( !crtemu->CompileShader ) goto failed; + if( !crtemu->GetShaderiv ) goto failed; + if( !crtemu->CreateProgram ) goto failed; + if( !crtemu->AttachShader ) goto failed; + if( !crtemu->BindAttribLocation ) goto failed; + if( !crtemu->LinkProgram ) goto failed; + if( !crtemu->GetProgramiv ) goto failed; + if( !crtemu->GenBuffers ) goto failed; + if( !crtemu->BindBuffer ) goto failed; + if( !crtemu->EnableVertexAttribArray ) goto failed; + if( !crtemu->VertexAttribPointer ) goto failed; + if( !crtemu->GenTextures ) goto failed; + if( !crtemu->Enable ) goto failed; + if( !crtemu->ActiveTexture ) goto failed; + if( !crtemu->BindTexture ) goto failed; + if( !crtemu->TexParameteri ) goto failed; + if( !crtemu->DeleteBuffers ) goto failed; + if( !crtemu->DeleteTextures ) goto failed; + if( !crtemu->BufferData ) goto failed; + if( !crtemu->UseProgram ) goto failed; + if( !crtemu->Uniform1i ) goto failed; + if( !crtemu->Uniform3f ) goto failed; + if( !crtemu->GetUniformLocation ) goto failed; + if( !crtemu->TexImage2D ) goto failed; + if( !crtemu->ClearColor ) goto failed; + if( !crtemu->Clear ) goto failed; + if( !crtemu->DrawArrays ) goto failed; + if( !crtemu->Viewport ) goto failed; + if( !crtemu->DeleteShader ) goto failed; + if( !crtemu->DeleteProgram ) goto failed; + #ifdef CRTEMU_REPORT_SHADER_ERRORS + if( !crtemu->GetShaderInfoLog ) goto failed; + #endif + + switch( type ) { + case CRTEMU_TYPE_TV: { + if( !crtemu_shaders_tv( crtemu ) ) goto failed; + } break; + case CRTEMU_TYPE_PC: { + if( !crtemu_shaders_pc( crtemu ) ) goto failed; + } break; + case CRTEMU_TYPE_LITE: { + if( !crtemu_shaders_lite( crtemu ) ) goto failed; + } break; + } + + crtemu->GenTextures( 1, &crtemu->accumulatetexture_a ); + crtemu->GenFramebuffers( 1, &crtemu->accumulatebuffer_a ); + + crtemu->GenTextures( 1, &crtemu->accumulatetexture_b ); + crtemu->GenFramebuffers( 1, &crtemu->accumulatebuffer_b ); + + crtemu->GenTextures( 1, &crtemu->blurtexture_a ); + crtemu->GenFramebuffers( 1, &crtemu->blurbuffer_a ); + + crtemu->GenTextures( 1, &crtemu->blurtexture_b ); + crtemu->GenFramebuffers( 1, &crtemu->blurbuffer_b ); + + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->GenTextures( 1, &crtemu->frametexture ); + #ifndef CRTEMU_WEBGL + // This enable call is not necessary when using fragment shaders, avoid logged warnings in WebGL + crtemu->Enable( CRTEMU_GL_TEXTURE_2D ); + #endif + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE2 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->frametexture ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + + crtemu->GenTextures( 1, &crtemu->backbuffer ); + crtemu->GenFramebuffers( 1, &crtemu->fbo_backbuffer ); + #ifndef CRTEMU_WEBGL + // This enable call is not necessary when using fragment shaders, avoid logged warnings in WebGL + crtemu->Enable( CRTEMU_GL_TEXTURE_2D ); + #endif + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_NEAREST ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_NEAREST ); + + crtemu->GenBuffers( 1, &crtemu->vertexbuffer ); + crtemu->BindBuffer( CRTEMU_GL_ARRAY_BUFFER, crtemu->vertexbuffer ); + crtemu->EnableVertexAttribArray( 0 ); + crtemu->VertexAttribPointer( 0, 4, CRTEMU_GL_FLOAT, CRTEMU_GL_FALSE, 4 * sizeof( CRTEMU_GLfloat ), 0 ); + + #ifdef CRTEMU_WEBGL + // Avoid WebGL error "TEXTURE_2D at unit 0 is incomplete: Non-power-of-two textures must have a wrap mode of CLAMP_TO_EDGE." + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_b ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_b ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->frametexture ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + #endif + + return crtemu; + +failed: + if( crtemu->accumulatetexture_a ) crtemu->DeleteTextures( 1, &crtemu->accumulatetexture_a ); + if( crtemu->accumulatebuffer_a ) crtemu->DeleteFramebuffers( 1, &crtemu->accumulatebuffer_a ); + if( crtemu->accumulatetexture_b ) crtemu->DeleteTextures( 1, &crtemu->accumulatetexture_b ); + if( crtemu->accumulatebuffer_b ) crtemu->DeleteFramebuffers( 1, &crtemu->accumulatebuffer_b ); + if( crtemu->blurtexture_a ) crtemu->DeleteTextures( 1, &crtemu->blurtexture_a ); + if( crtemu->blurbuffer_a ) crtemu->DeleteFramebuffers( 1, &crtemu->blurbuffer_a ); + if( crtemu->blurtexture_b ) crtemu->DeleteTextures( 1, &crtemu->blurtexture_b ); + if( crtemu->blurbuffer_b ) crtemu->DeleteFramebuffers( 1, &crtemu->blurbuffer_b ); + if( crtemu->frametexture ) crtemu->DeleteTextures( 1, &crtemu->frametexture ); + if( crtemu->backbuffer ) crtemu->DeleteTextures( 1, &crtemu->backbuffer ); + if( crtemu->vertexbuffer ) crtemu->DeleteBuffers( 1, &crtemu->vertexbuffer ); + + #ifndef CRTEMU_SDL + if( crtemu->gl_dll ) FreeLibrary( crtemu->gl_dll ); + #endif + CRTEMU_FREE( crtemu->memctx, crtemu ); + return 0; +} + + +void crtemu_destroy( crtemu_t* crtemu ) { + crtemu->DeleteTextures( 1, &crtemu->accumulatetexture_a ); + crtemu->DeleteFramebuffers( 1, &crtemu->accumulatebuffer_a ); + crtemu->DeleteTextures( 1, &crtemu->accumulatetexture_b ); + crtemu->DeleteFramebuffers( 1, &crtemu->accumulatebuffer_b ); + crtemu->DeleteTextures( 1, &crtemu->blurtexture_a ); + crtemu->DeleteFramebuffers( 1, &crtemu->blurbuffer_a ); + crtemu->DeleteTextures( 1, &crtemu->blurtexture_b ); + crtemu->DeleteFramebuffers( 1, &crtemu->blurbuffer_b ); + crtemu->DeleteTextures( 1, &crtemu->frametexture ); + crtemu->DeleteTextures( 1, &crtemu->backbuffer ); + crtemu->DeleteBuffers( 1, &crtemu->vertexbuffer ); + #ifndef CRTEMU_SDL + FreeLibrary( crtemu->gl_dll ); + #endif + CRTEMU_FREE( crtemu->memctx, crtemu ); +} + + +void crtemu_frame( crtemu_t* crtemu, CRTEMU_U32* frame_abgr, int frame_width, int frame_height ) { + if( crtemu->type != CRTEMU_TYPE_LITE ) { + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE3 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->frametexture ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGBA, frame_width, frame_height, 0, CRTEMU_GL_RGBA, CRTEMU_GL_UNSIGNED_BYTE, frame_abgr ); + if( frame_abgr ) { + crtemu->use_frame = 1.0f; + } else { + crtemu->use_frame = 0.0f; + } + } +} + + +static void crtemu_internal_blur( crtemu_t* crtemu, CRTEMU_GLuint source, CRTEMU_GLuint blurbuffer_a, CRTEMU_GLuint blurbuffer_b, + CRTEMU_GLuint blurtexture_b, float r, int width, int height ) { + + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, blurbuffer_b ); + crtemu->UseProgram( crtemu->blur_shader ); + crtemu->Uniform2f( crtemu->GetUniformLocation( crtemu->blur_shader, "blur" ), r / (float) width, 0 ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->blur_shader, "texture" ), 0 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, source ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, blurbuffer_a ); + crtemu->UseProgram( crtemu->blur_shader ); + crtemu->Uniform2f( crtemu->GetUniformLocation( crtemu->blur_shader, "blur" ), 0, r / (float) height ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->blur_shader, "texture" ), 0 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, blurtexture_b ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); +} + + +void crtemu_present( crtemu_t* crtemu, CRTEMU_U64 time_us, CRTEMU_U32 const* pixels_xbgr, int width, int height, + CRTEMU_U32 mod_xbgr, CRTEMU_U32 border_xbgr ) { + + int viewport[ 4 ]; + crtemu->GetIntegerv( CRTEMU_GL_VIEWPORT, viewport ); + + // Copy to backbuffer + if( pixels_xbgr ) { + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGBA, width, height, 0, CRTEMU_GL_RGBA, CRTEMU_GL_UNSIGNED_BYTE, pixels_xbgr ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + } else { + if( width != crtemu->last_present_width || height != crtemu->last_present_height ) { + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGB, width, height, 0, CRTEMU_GL_RGB, CRTEMU_GL_UNSIGNED_BYTE, 0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->fbo_backbuffer ); + crtemu->FramebufferTexture2D( CRTEMU_GL_FRAMEBUFFER, CRTEMU_GL_COLOR_ATTACHMENT0, CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer, 0 ); + } + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->fbo_backbuffer ); + crtemu->Viewport( 0, 0, width, height ); + crtemu->UseProgram( crtemu->copy_shader ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->copy_shader, "tex0" ), 0 ); + CRTEMU_GLfloat vertices[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + }; + crtemu->BufferData( CRTEMU_GL_ARRAY_BUFFER, 4 * 4 * sizeof( CRTEMU_GLfloat ), vertices, CRTEMU_GL_STATIC_DRAW ); + crtemu->BindBuffer( CRTEMU_GL_ARRAY_BUFFER, crtemu->vertexbuffer ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + crtemu->Viewport( viewport[ 0 ], viewport[ 1 ], viewport[ 2 ], viewport[ 3 ] ); + } + + if( width != crtemu->last_present_width || height != crtemu->last_present_height ) { + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_a ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGB, width, height, 0, CRTEMU_GL_RGB, CRTEMU_GL_UNSIGNED_BYTE, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->accumulatebuffer_a ); + crtemu->FramebufferTexture2D( CRTEMU_GL_FRAMEBUFFER, CRTEMU_GL_COLOR_ATTACHMENT0, CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_a, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_b ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGB, width, height, 0, CRTEMU_GL_RGB, CRTEMU_GL_UNSIGNED_BYTE, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->accumulatebuffer_b ); + crtemu->FramebufferTexture2D( CRTEMU_GL_FRAMEBUFFER, CRTEMU_GL_COLOR_ATTACHMENT0, CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_b, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_a ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGB, width, height, 0, CRTEMU_GL_RGB, CRTEMU_GL_UNSIGNED_BYTE, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->blurbuffer_a ); + crtemu->FramebufferTexture2D( CRTEMU_GL_FRAMEBUFFER, CRTEMU_GL_COLOR_ATTACHMENT0, CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_a, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_b ); + crtemu->TexImage2D( CRTEMU_GL_TEXTURE_2D, 0, CRTEMU_GL_RGB, width, height, 0, CRTEMU_GL_RGB, CRTEMU_GL_UNSIGNED_BYTE, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->blurbuffer_b ); + crtemu->FramebufferTexture2D( CRTEMU_GL_FRAMEBUFFER, CRTEMU_GL_COLOR_ATTACHMENT0, CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_b, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + } + + + crtemu->last_present_width = width; + crtemu->last_present_height = height; + + CRTEMU_GLfloat vertices[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + }; + crtemu->BufferData( CRTEMU_GL_ARRAY_BUFFER, 4 * 4 * sizeof( CRTEMU_GLfloat ), vertices, CRTEMU_GL_STATIC_DRAW ); + crtemu->BindBuffer( CRTEMU_GL_ARRAY_BUFFER, crtemu->vertexbuffer ); + + crtemu->Viewport( 0, 0, width, height ); + + // Blur the previous accumulation buffer + crtemu_internal_blur( crtemu, crtemu->accumulatetexture_b, crtemu->blurbuffer_a, crtemu->blurbuffer_b, crtemu->blurtexture_b, 1.0f, width, height ); + + // Update accumulation buffer + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->accumulatebuffer_a ); + crtemu->UseProgram( crtemu->accumulate_shader ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->accumulate_shader, "tex0" ), 0 ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->accumulate_shader, "tex1" ), 1 ); + crtemu->Uniform1f( crtemu->GetUniformLocation( crtemu->accumulate_shader, "modulate" ), 1.0f ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + + // Store a copy of the accumulation buffer + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->accumulatebuffer_b ); + crtemu->UseProgram( crtemu->copy_shader ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->copy_shader, "tex0" ), 0 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + // Blend accumulation and backbuffer + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, crtemu->accumulatebuffer_a ); + crtemu->UseProgram( crtemu->blend_shader ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->blend_shader, "tex0" ), 0 ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->blend_shader, "tex1" ), 1 ); + crtemu->Uniform1f( crtemu->GetUniformLocation( crtemu->blend_shader, "modulate" ), 1.0f ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_b ); + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + + // Add slight blur to backbuffer + float backbuffer_blur = crtemu->type == CRTEMU_TYPE_TV ? 0.17f : 0.0f; + crtemu_internal_blur( crtemu, crtemu->accumulatetexture_a, crtemu->accumulatebuffer_a, crtemu->blurbuffer_b, crtemu->blurtexture_b, backbuffer_blur, width, height ); + + // Create fully blurred version of backbuffer + crtemu_internal_blur( crtemu, crtemu->accumulatetexture_a, crtemu->blurbuffer_a, crtemu->blurbuffer_b, crtemu->blurtexture_b, 1.0f, width, height ); + + + // Present to screen with CRT shader + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); + + crtemu->Viewport( viewport[ 0 ], viewport[ 1 ], viewport[ 2 ], viewport[ 3 ] ); + + int window_width = viewport[ 2 ] - viewport[ 0 ]; + int window_height = viewport[ 3 ] - viewport[ 1 ]; + + int aspect_width = (int)( ( window_height * 4 ) / 3 ); + int aspect_height= (int)( ( window_width * 3 ) / 4 ); + int target_width, target_height; + if( aspect_height <= window_height ) { + target_width = window_width; + target_height = aspect_height; + } else { + target_width = aspect_width; + target_height = window_height; + } + + float hscale = target_width / (float) width; + float vscale = target_height / (float) height; + + float hborder = ( window_width - hscale * width ) / 2.0f; + float vborder = ( window_height - vscale * height ) / 2.0f; + float x1 = hborder; + float y1 = vborder; + float x2 = x1 + hscale * width; + float y2 = y1 + vscale * height; + + x1 = ( x1 / window_width ) * 2.0f - 1.0f; + x2 = ( x2 / window_width ) * 2.0f - 1.0f; + y1 = ( y1 / window_height ) * 2.0f - 1.0f; + y2 = ( y2 / window_height ) * 2.0f - 1.0f; + + CRTEMU_GLfloat screen_vertices[] = { + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + screen_vertices[ 0 ] = x1; + screen_vertices[ 1 ] = y1; + screen_vertices[ 4 ] = x2; + screen_vertices[ 5 ] = y1; + screen_vertices[ 8 ] = x2; + screen_vertices[ 9 ] = y2; + screen_vertices[ 12 ] = x1; + screen_vertices[ 13 ] = y2; + + crtemu->BindBuffer( CRTEMU_GL_ARRAY_BUFFER, crtemu->vertexbuffer ); + crtemu->EnableVertexAttribArray( 0 ); + crtemu->VertexAttribPointer( 0, 4, CRTEMU_GL_FLOAT, CRTEMU_GL_FALSE, 4 * sizeof( CRTEMU_GLfloat ), 0 ); + crtemu->BufferData( CRTEMU_GL_ARRAY_BUFFER, 4 * 4 * sizeof( CRTEMU_GLfloat ), screen_vertices, CRTEMU_GL_STATIC_DRAW ); + + float b = ( ( border_xbgr >> 16 ) & 0xff ) / 255.0f; + float g = ( ( border_xbgr >> 8 ) & 0xff ) / 255.0f; + float r = ( ( border_xbgr ) & 0xff ) / 255.0f; + crtemu->ClearColor( r, g, b, 1.0f ); + crtemu->Clear( CRTEMU_GL_COLOR_BUFFER_BIT ); + + crtemu->UseProgram( crtemu->crt_shader ); + + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->crt_shader, "backbuffer" ), 0 ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->crt_shader, "blurbuffer" ), 1 ); + crtemu->Uniform1i( crtemu->GetUniformLocation( crtemu->crt_shader, "frametexture" ), 2 ); + crtemu->Uniform1f( crtemu->GetUniformLocation( crtemu->crt_shader, "use_frame" ), crtemu->use_frame ); + crtemu->Uniform1f( crtemu->GetUniformLocation( crtemu->crt_shader, "time" ), 1.5f * (CRTEMU_GLfloat)( ( (double) time_us ) / 1000000.0 ) ); + crtemu->Uniform2f( crtemu->GetUniformLocation( crtemu->crt_shader, "resolution" ), (float) window_width, (float) window_height ); + if( crtemu->type == CRTEMU_TYPE_LITE ) { + crtemu->Uniform2f( crtemu->GetUniformLocation( crtemu->crt_shader, "size" ), (float)( target_width / 2 >= width ? width : target_width / 2 ), (float) ( target_height / 2 >= height ? height : target_height / 2 ) ); + } else { + crtemu->Uniform2f( crtemu->GetUniformLocation( crtemu->crt_shader, "size" ), (float) target_width, (float) target_height ); + } + + float mod_r = ( ( mod_xbgr >> 16 ) & 0xff ) / 255.0f; + float mod_g = ( ( mod_xbgr >> 8 ) & 0xff ) / 255.0f; + float mod_b = ( ( mod_xbgr ) & 0xff ) / 255.0f; + crtemu->Uniform3f( crtemu->GetUniformLocation( crtemu->crt_shader, "modulate" ), mod_r, mod_g, mod_b ); + + float color[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->accumulatetexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, CRTEMU_GL_CLAMP_TO_BORDER ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, CRTEMU_GL_CLAMP_TO_BORDER ); + #ifndef CRTEMU_WEBGL + crtemu->TexParameterfv( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_BORDER_COLOR, color ); + #endif + + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->blurtexture_a ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, CRTEMU_GL_CLAMP_TO_BORDER ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, CRTEMU_GL_CLAMP_TO_BORDER ); + #ifndef CRTEMU_WEBGL + crtemu->TexParameterfv( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_BORDER_COLOR, color ); + #endif + + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE3 ); + if( crtemu->type == CRTEMU_TYPE_LITE ) { + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->backbuffer ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_NEAREST ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_NEAREST ); + } else { + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, crtemu->frametexture ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MIN_FILTER, CRTEMU_GL_LINEAR ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_MAG_FILTER, CRTEMU_GL_LINEAR ); + } + + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_S, CRTEMU_GL_CLAMP_TO_BORDER ); + crtemu->TexParameteri( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_WRAP_T, CRTEMU_GL_CLAMP_TO_BORDER ); + #ifndef CRTEMU_WEBGL + crtemu->TexParameterfv( CRTEMU_GL_TEXTURE_2D, CRTEMU_GL_TEXTURE_BORDER_COLOR, color ); + #endif + + crtemu->DrawArrays( CRTEMU_GL_TRIANGLE_FAN, 0, 4 ); + + crtemu->ActiveTexture( CRTEMU_GL_TEXTURE0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 0 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 1 ); + crtemu->BindTexture( CRTEMU_GL_TEXTURE_2D, 2 ); + crtemu->BindFramebuffer( CRTEMU_GL_FRAMEBUFFER, 0 ); +} + + +void crtemu_coordinates_window_to_bitmap( crtemu_t* crtemu, int width, int height, int* x, int* y ) { + switch( crtemu->type ) { + case CRTEMU_TYPE_TV: { + CRTEMU_GLint viewport[ 4 ]; + crtemu->GetIntegerv( CRTEMU_GL_VIEWPORT, viewport ); + + int window_width = viewport[ 2 ] - viewport[ 0 ]; + int window_height = viewport[ 3 ] - viewport[ 1 ]; + + int aspect_width = (int)( ( window_height * 4 ) / 3 ); + int aspect_height= (int)( ( window_width * 3 ) / 4 ); + int target_width, target_height; + if( aspect_height <= window_height ) { + target_width = window_width; + target_height = aspect_height; + } else { + target_width = aspect_width; + target_height = window_height; + } + + float hscale = target_width / (float) width; + float vscale = target_height / (float) height; + + float hborder = ( window_width - hscale * width ) / 2.0f; + float vborder = ( window_height - vscale * height ) / 2.0f; + + float xp = ( ( *x - hborder ) / hscale ) / (float) width; + float yp = ( ( *y - vborder ) / vscale ) / (float) height; + + /* TODO: Common params for shader and this */ + float xc = ( xp - 0.5f ) * 2.0f; + float yc = ( yp - 0.5f ) * 2.0f; + xc *= 1.1f; + yc *= 1.1f; + //xc *= 1.0f + powf( ( fabsf( yc ) / 5.0f ), 2.0f); + //yc *= 1.0f + powf( ( fabsf( xc ) / 4.0f ), 2.0f); + float yt = ( yc >= 0.0f ? yc : -yc ) / 5.0f; + float xt = ( xc >= 0.0f ? xc : -xc ) / 4.0f; + xc *= 1.0f + ( yt * yt ); + yc *= 1.0f + ( xt * xt ); + xc = ( xc / 2.0f ) + 0.5f; + yc = ( yc / 2.0f ) + 0.5f; + xc = xc * 0.92f + 0.04f; + yc = yc * 0.92f + 0.04f; + xp = xc * 0.6f + xp * 0.4f; + yp = yc * 0.6f + yp * 0.4f; + + xp = xp * ( 1.0f - 0.04f ) + 0.04f / 2.0f + 0.003f; + yp = yp * ( 1.0f - 0.04f ) + 0.04f / 2.0f - 0.001f; + + xp = xp * 1.025f - 0.0125f; + yp = yp * 0.92f + 0.04f; + + xp *= width; + yp *= height; + + *x = (int) ( xp ); + *y = (int) ( yp ); + } break; + case CRTEMU_TYPE_PC: { + CRTEMU_GLint viewport[ 4 ]; + crtemu->GetIntegerv( CRTEMU_GL_VIEWPORT, viewport ); + + int window_width = viewport[ 2 ] - viewport[ 0 ]; + int window_height = viewport[ 3 ] - viewport[ 1 ]; + + int aspect_width = (int)( ( window_height * 4 ) / 3 ); + int aspect_height= (int)( ( window_width * 3 ) / 4 ); + int target_width, target_height; + if( aspect_height <= window_height ) { + target_width = window_width; + target_height = aspect_height; + } else { + target_width = aspect_width; + target_height = window_height; + } + + float hscale = target_width / (float) width; + float vscale = target_height / (float) height; + + float hborder = ( window_width - hscale * width ) / 2.0f; + float vborder = ( window_height - vscale * height ) / 2.0f; + + float xp = ( ( *x - hborder ) / hscale ) / (float) width; + float yp = ( ( *y - vborder ) / vscale ) / (float) height; + + /* TODO: Common params for shader and this */ + float xc = ( xp - 0.5f ) * 2.0f; + float yc = ( yp - 0.5f ) * 2.0f; + xc *= 1.1f; + yc *= 1.1f; + //xc *= 1.0f + powf( ( fabsf( yc ) / 5.0f ), 2.0f); + //yc *= 1.0f + powf( ( fabsf( xc ) / 4.0f ), 2.0f); + float yt = ( yc >= 0.0f ? yc : -yc ) / 5.0f; + float xt = ( xc >= 0.0f ? xc : -xc ) / 4.0f; + xc *= 1.0f + ( yt * yt ); + yc *= 1.0f + ( xt * xt ); + xc = ( xc / 2.0f ) + 0.5f; + yc = ( yc / 2.0f ) + 0.5f; + xc = xc * 0.92f + 0.04f; + yc = yc * 0.92f + 0.04f; + xp = xc * 0.2f + xp * 0.8f; + yp = yc * 0.2f + yp * 0.8f; + + xp = xp * ( 1.0f - 0.04f ) + 0.04f / 2.0f + 0.003f; + yp = yp * ( 1.0f - 0.04f ) + 0.04f / 2.0f - 0.001f; + + xp = xp * 1.156f - ( 0.078f + 0.003f ); + yp = yp * 1.156f - 0.078f; + + xp *= width; + yp *= height; + + *x = (int) ( xp ); + *y = (int) ( yp ); + } break; + case CRTEMU_TYPE_LITE: { + CRTEMU_GLint viewport[ 4 ]; + crtemu->GetIntegerv( CRTEMU_GL_VIEWPORT, viewport ); + + int window_width = viewport[ 2 ] - viewport[ 0 ]; + int window_height = viewport[ 3 ] - viewport[ 1 ]; + + int aspect_width = (int)( ( window_height * 4 ) / 3 ); + int aspect_height= (int)( ( window_width * 3 ) / 4 ); + int target_width, target_height; + if( aspect_height <= window_height ) { + target_width = window_width; + target_height = aspect_height; + } else { + target_width = aspect_width; + target_height = window_height; + } + + float hscale = target_width / (float) width; + float vscale = target_height / (float) height; + + float hborder = ( window_width - hscale * width ) / 2.0f; + float vborder = ( window_height - vscale * height ) / 2.0f; + + float xp = ( ( *x - hborder ) / hscale ) / (float) width; + float yp = ( ( *y - vborder ) / vscale ) / (float) height; + + xp *= width; + yp *= height; + + *x = (int) ( xp ); + *y = (int) ( yp ); + } break; + } +} + + + +#endif /* CRTEMU_IMPLEMENTATION */ + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2016 Mattias Gustavsson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/ diff --git a/libs_win32/frametimer.h b/libs_win32/frametimer.h new file mode 100644 index 0000000..4d63b3e --- /dev/null +++ b/libs_win32/frametimer.h @@ -0,0 +1,317 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +frametimer.h - v0.1 - Framerate timer functionality. + +Do this: + #define FRAMETIMER_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + +#ifndef frametimer_h +#define frametimer_h + +typedef struct frametimer_t frametimer_t; + +frametimer_t* frametimer_create( void* memxtx ); + +void frametimer_destroy( frametimer_t* frametimer ); + +void frametimer_lock_rate( frametimer_t* frametimer, int fps ); + +float frametimer_update( frametimer_t* frametimer ); + +float frametimer_delta_time( frametimer_t* frametimer ); + +int frametimer_frame_counter( frametimer_t* frametimer ); + +#endif /* frametimer_h */ + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ + +#ifdef FRAMETIMER_IMPLEMENTATION +#undef FRAMETIMER_IMPLEMENTATION + +#define _CRT_NONSTDC_NO_DEPRECATE +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif +#ifdef _WIN32 + #if !defined( _WIN32_WINNT ) || _WIN32_WINNT < 0x0501 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 // requires Windows XP minimum + #endif + // 0x0400=Windows NT 4.0, 0x0500=Windows 2000, 0x0501=Windows XP, 0x0502=Windows Server 2003, 0x0600=Windows Vista, + // 0x0601=Windows 7, 0x0602=Windows 8, 0x0603=Windows 8.1, 0x0A00=Windows 10, + #define _WINSOCKAPI_ + #pragma warning( push ) + #pragma warning( disable: 4619 ) + #pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' + #pragma warning( disable: 4768 ) // __declspec attributes before linkage specification are ignored + #pragma warning( disable: 4255 ) // 'function' : no function prototype given: converting '()' to '(void)' + #include + #pragma warning( pop ) + #ifndef __TINYC__ + #pragma comment(lib, "winmm.lib") + #else + typedef struct timecaps_tag { UINT wPeriodMin; UINT wPeriodMax; } TIMECAPS, *PTIMECAPS, NEAR *NPTIMECAPS, FAR *LPTIMECAPS; + typedef UINT MMRESULT; + #define TIMERR_NOERROR (0) + static MMRESULT (*timeGetDevCaps)( LPTIMECAPS ptc, UINT cbtc ); + static MMRESULT (*timeBeginPeriod)( UINT uPeriod ); + static MMRESULT (*timeEndPeriod)( UINT uPeriod ); + #endif +#elif defined( __APPLE__ ) + #include +#elif defined( __wasm__ ) + #define WA_CORO_IMPLEMENT_NANOSLEEP + #include +#else + #include +#endif + +#ifndef FRAMETIMER_MALLOC + #include + #if defined(__cplusplus) + #define FRAMETIMER_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define FRAMETIMER_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define FRAMETIMER_MALLOC( ctx, size ) ( malloc( size ) ) + #define FRAMETIMER_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif + +#ifndef FRAMETIMER_U64 + #define FRAMETIMER_U64 unsigned long long +#endif + +struct frametimer_t + { + FRAMETIMER_U64 clock_freq; + FRAMETIMER_U64 prev_clock; + void* memctx; + float delta_time; + int initialized; + int frame_counter; + int frame_rate_lock; + #ifdef _WIN32 + HANDLE waitable_timer; + #endif + }; + + +frametimer_t* frametimer_create( void* memctx ) + { + frametimer_t* frametimer = (frametimer_t*) FRAMETIMER_MALLOC( memctx, sizeof( frametimer_t ) ); + #ifdef _WIN32 + #ifdef __TINYC__ + HMODULE winmm = LoadLibrary( "winmm" ); + timeGetDevCaps = GetProcAddress( winmm, "timeGetDevCaps"); + timeBeginPeriod = GetProcAddress( winmm, "timeBeginPeriod"); + timeEndPeriod = GetProcAddress( winmm, "timeEndPeriod"); + #endif + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeBeginPeriod( tc.wPeriodMin ); + frametimer->waitable_timer = CreateWaitableTimer(NULL, TRUE, NULL); + #endif + + frametimer->memctx = memctx; + frametimer->initialized = 0; + + #ifdef _WIN32 + LARGE_INTEGER f; + QueryPerformanceFrequency( &f ); + frametimer->clock_freq = (FRAMETIMER_U64) f.QuadPart; + #else + frametimer->clock_freq = 1000000000ull; + #endif + + frametimer->prev_clock = 0; + frametimer->delta_time = 0.0f; + frametimer->frame_counter = 0; + frametimer->frame_rate_lock = 0; + return frametimer; + } + + +void frametimer_destroy( frametimer_t* frametimer ) + { + #ifdef _WIN32 + CloseHandle( frametimer->waitable_timer ); + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeEndPeriod( tc.wPeriodMin ); + #endif + FRAMETIMER_FREE( frametimer->memctx, frametimer ); + } + + +void frametimer_lock_rate( frametimer_t* frametimer, int fps ) + { + frametimer->frame_rate_lock = ( fps > 0 && fps < 5 ) ? 5 : fps < 0 ? 0 : fps; + } + + +float frametimer_update( frametimer_t* frametimer ) + { + if( !frametimer->initialized ) + { + #ifdef _WIN32 + LARGE_INTEGER c; + QueryPerformanceCounter( &c ); + frametimer->prev_clock = (FRAMETIMER_U64) c.QuadPart; + #elif defined( __APPLE__ ) + frametimer->prev_clock = clock_gettime_nsec_np( CLOCK_UPTIME_RAW ); + #else + struct timespec t; + clock_gettime( CLOCK_MONOTONIC_RAW, &t ); + frametimer->prev_clock = (FRAMETIMER_U64)t.tv_sec; + frametimer->prev_clock *= 1000000000ull; + frametimer->prev_clock += (FRAMETIMER_U64)t.tv_nsec; + #endif + frametimer->initialized = 1; + } + + ++frametimer->frame_counter; + + + FRAMETIMER_U64 curr_clock = 0ULL; + #ifdef _WIN32 + LARGE_INTEGER c; + QueryPerformanceCounter( &c ); + curr_clock = (FRAMETIMER_U64) c.QuadPart; + #elif defined( __APPLE__ ) + curr_clock = (FRAMETIMER_U64) clock_gettime_nsec_np( CLOCK_UPTIME_RAW ); + #else + struct timespec t; + clock_gettime( CLOCK_MONOTONIC_RAW, &t ); + curr_clock = (FRAMETIMER_U64)t.tv_sec; + curr_clock *= 1000000000ull; + curr_clock += (FRAMETIMER_U64)t.tv_nsec; + #endif + + if( frametimer->frame_rate_lock > 0 ) + { + FRAMETIMER_U64 delta = 0ULL; + if( curr_clock > frametimer->prev_clock ) + delta = curr_clock - frametimer->prev_clock - 1ULL; + if( delta < frametimer->clock_freq / frametimer->frame_rate_lock ) + { + FRAMETIMER_U64 wait = ( frametimer->clock_freq / frametimer->frame_rate_lock ) - delta; + #ifdef _WIN32 + if( wait > 0 ) + { + LARGE_INTEGER due_time; + due_time.QuadPart = - (LONGLONG) ( ( 10000000ULL * wait ) / frametimer->clock_freq ) ; + + SetWaitableTimer( frametimer->waitable_timer, &due_time, 0, 0, 0, FALSE ); + WaitForSingleObject( frametimer->waitable_timer, 200 ); // wait long enough for timer to trigger ( 200ms == 5fps ) + CancelWaitableTimer( frametimer->waitable_timer ); // in case we timed out + } + #else + struct timespec t = { 0, 0 }; + t.tv_nsec = wait; + while( t.tv_nsec > 0 ) + { + struct timespec r = { 0, 0 }; + if( nanosleep( &t, &r ) >= 0 ) break; + t = r; + } + #endif + curr_clock += wait; + } + } + + FRAMETIMER_U64 delta_clock = 0ULL; + if( curr_clock > frametimer->prev_clock ) delta_clock = curr_clock - frametimer->prev_clock; + + // Cap delta time if it is too high (running at less than 5 fps ) or things will jump + // like crazy on occasional long stalls. + if( delta_clock > frametimer->clock_freq / 5ULL ) delta_clock = frametimer->clock_freq / 5ULL; // Cap to 5 fps + + float delta_time = (float) ( ( (double) delta_clock ) / ( (double) frametimer->clock_freq ) ); + + frametimer->delta_time = delta_time; + frametimer->prev_clock = curr_clock; + + return delta_time; + } + + +float frametimer_delta_time( frametimer_t* frametimer ) + { + return frametimer->delta_time; + } + + +int frametimer_frame_counter( frametimer_t* frametimer ) + { + return frametimer->frame_counter; + } + + +#endif /* FRAMETIMER_IMPLEMENTATION */ + + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2015 Mattias Gustavsson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/ diff --git a/libs_win32/thread.h b/libs_win32/thread.h new file mode 100644 index 0000000..b5ab4b6 --- /dev/null +++ b/libs_win32/thread.h @@ -0,0 +1,1478 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +thread.h - v0.2 - Cross platform threading functions for C/C++. + +Do this: + #define THREAD_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + +#ifndef thread_h +#define thread_h + +#ifndef THREAD_U64 + #define THREAD_U64 unsigned long long +#endif + +#define THREAD_STACK_SIZE_DEFAULT ( 0 ) +#define THREAD_SIGNAL_WAIT_INFINITE ( -1 ) + +typedef void* thread_id_t; +thread_id_t thread_current_thread_id( void ); +void thread_yield( void ); +void thread_exit( int return_code ); + +typedef void* thread_ptr_t; +thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size ); +void thread_destroy( thread_ptr_t thread ); +int thread_join( thread_ptr_t thread ); +void thread_set_high_priority( thread_ptr_t thread ); + +typedef union thread_mutex_t thread_mutex_t; +void thread_mutex_init( thread_mutex_t* mutex ); +void thread_mutex_term( thread_mutex_t* mutex ); +void thread_mutex_lock( thread_mutex_t* mutex ); +void thread_mutex_unlock( thread_mutex_t* mutex ); + +typedef union thread_signal_t thread_signal_t; +void thread_signal_init( thread_signal_t* signal ); +void thread_signal_term( thread_signal_t* signal ); +void thread_signal_raise( thread_signal_t* signal ); +int thread_signal_wait( thread_signal_t* signal, int timeout_ms ); + +typedef union thread_atomic_int_t thread_atomic_int_t; +int thread_atomic_int_load( thread_atomic_int_t* atomic ); +void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ); +int thread_atomic_int_inc( thread_atomic_int_t* atomic ); +int thread_atomic_int_dec( thread_atomic_int_t* atomic ); +int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ); +int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ); +int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ); +int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ); + +typedef union thread_atomic_ptr_t thread_atomic_ptr_t; +void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ); +void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ); +void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ); +void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ); + +typedef union thread_timer_t thread_timer_t; +void thread_timer_init( thread_timer_t* timer ); +void thread_timer_term( thread_timer_t* timer ); +void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ); + +typedef void* thread_tls_t; +thread_tls_t thread_tls_create( void ); +void thread_tls_destroy( thread_tls_t tls ); +void thread_tls_set( thread_tls_t tls, void* value ); +void* thread_tls_get( thread_tls_t tls ); + +typedef struct thread_queue_t thread_queue_t; +void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ); +void thread_queue_term( thread_queue_t* queue ); +void thread_queue_produce( thread_queue_t* queue, void* value ); +void* thread_queue_consume( thread_queue_t* queue ); +int thread_queue_count( thread_queue_t* queue ); + +#endif /* thread_h */ + + +/** + +Example +======= + +Here's a basic sample program which starts a second thread which just waits and prints a message. + + #define THREAD_IMPLEMENTATION + #include "thread.h" + + #include // for printf + + int thread_proc( void* user_data) + { + thread_timer_t timer; + thread_timer_init( &timer ); + + int count = 0; + thread_atomic_int_t* exit_flag = (thread_atomic_int_t*) user_data; + while( thread_atomic_int_load( exit_flag ) == 0 ) + { + printf( "Thread... " ); + thread_timer_wait( &timer, 1000000000 ); // sleep for a second + ++count; + } + + thread_timer_term( &timer ); + printf( "Done\n" ); + return count; + } + + int main( int argc, char** argv ) + { + (void) argc, argv; + + thread_atomic_int_t exit_flag; + thread_atomic_int_store( &exit_flag, 0 ); + + thread_ptr_t thread = thread_create( thread_proc, &exit_flag, THREAD_STACK_SIZE_DEFAULT ); + + thread_timer_t timer; + thread_timer_init( &timer ); + for( int i = 0; i < 5; ++i ) + { + printf( "Main... " ); + thread_timer_wait( &timer, 2000000000 ); // sleep for two seconds + } + thread_timer_term( &timer ); + + thread_atomic_int_store( &exit_flag, 1 ); // signal thread to exit + int retval = thread_join( thread ); + + printf( "Count: %d\n", retval ); + + thread_destroy( thread ); + return retval; + } + + +API Documentation +================= + +thread.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it, +you just include thread.h to get the API declarations. To get the definitions, you must include thread.h from *one* +single C or C++ file, and #define the symbol `THREAD_IMPLEMENTATION` before you do. + + +Customization +------------- +thread.h allows for specifying the exact type of 64-bit unsigned integer to be used in its API. By default, it is +defined as `unsigned long long`, but as this is not a standard type on all compilers, you can redefine it by #defining +THREAD_U64 before including thread.h. This is useful if you, for example, use the types from `` in the rest of +your program, and you want thread.h to use compatible types. In this case, you would include thread.h using the +following code: + + #define THREAD_U64 uint64_t + #include "thread.h" + +Note that when customizing this data type, you need to use the same definition in every place where you include +thread.h, as it affect the declarations as well as the definitions. + + +thread_current_thread_id +------------------------ + + thread_id_t thread_current_thread_id( void ) + +Returns a unique identifier for the calling thread. After the thread terminates, the id might be reused for new threads. + + +thread_yield +------------ + + void thread_yield( void ) + +Makes the calling thread yield execution to another thread. The operating system controls which thread is switched to. + + +thread_exit +----------- + + void thread_exit( int return_code ) + +Exits the calling thread, as if you had done `return return_code;` from the main body of the thread function. + + +thread_create +------------- + + thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size ) + +Creates a new thread running the `thread_proc` function, passing the `user_data` through to it. The thread will have +the stack size specified in the `stack_size` parameter. To get the operating system default stack size, use the +defined constant `THREAD_STACK_SIZE_DEFAULT`. When returning from the thread_proc function, the value you return can +be received in another thread by calling thread_join. `thread_create` returns a pointer to the thread instance, which +can be used as a parameter to the functions `thread_destroy`, `thread_join` and `thread_set_high_priority`. + + +thread_destroy +-------------- + + void thread_destroy( thread_ptr_t thread ) + +Destroys a thread that was created by calling `thread_create`. Make sure the thread has exited before you attempt to +destroy it. This can be accomplished by calling `thread_join`. It is not possible for force termination of a thread by +calling `thread_destroy`. + + +thread_join +----------- + + int thread_join( thread_ptr_t thread ) + +Waits for the specified thread to exit. Returns the value which the thread returned when exiting. + + +thread_set_high_priority +------------------------ + + void thread_set_high_priority( thread_ptr_t thread ) + +When created, threads are set to run at normal priority. In some rare cases, such as a sound buffer update loop, it can +be necessary to have one thread of your application run on a higher priority than the rest. Calling +`thread_set_high_priority` will raise the priority of the specified thread, giving it a chance to be run more often. +Do not increase the priority of a thread unless you absolutely have to, as it can negatively affect performance if used +without care. + + +thread_mutex_init +----------------- + + void thread_mutex_init( thread_mutex_t* mutex ) + +Initializes the specified mutex instance, preparing it for use. A mutex can be used to lock sections of code, such that +it can only be run by one thread at a time. + + +thread_mutex_term +----------------- + + void thread_mutex_term( thread_mutex_t* mutex ) + +Terminates the specified mutex instance, releasing any system resources held by it. + + +thread_mutex_lock +----------------- + + void thread_mutex_lock( thread_mutex_t* mutex ) + +Takes an exclusive lock on a mutex. If the lock is already taken by another thread, `thread_mutex_lock` will yield the +calling thread and wait for the lock to become available before returning. The mutex must be initialized by calling +`thread_mutex_init` before it can be locked. + + +thread_mutex_unlock +------------------- + + void thread_mutex_unlock( thread_mutex_t* mutex ) + +Releases a lock taken by calling `thread_mutex_lock`. + + +thread_signal_init +------------------ + + void thread_signal_init( thread_signal_t* signal ) + +Initializes the specified signal instance, preparing it for use. A signal works like a flag, which can be waited on by +one thread, until it is raised from another thread. + + +thread_signal_term +------------------ + + void thread_signal_term( thread_signal_t* signal ) + +Terminates the specified signal instance, releasing any system resources held by it. + + +thread_signal_raise +------------------- + + void thread_signal_raise( thread_signal_t* signal ) + +Raise the specified signal. Other threads waiting for the signal will proceed. + + +thread_signal_wait +------------------ + + int thread_signal_wait( thread_signal_t* signal, int timeout_ms ) + +Waits for a signal to be raised, or until `timeout_ms` milliseconds have passed. If the wait timed out, a value of 0 is +returned, otherwise a non-zero value is returned. If the `timeout_ms` parameter is 0, `thread_signal_wait` waits +indefinitely. + + +thread_atomic_int_load +---------------------- + + int thread_atomic_int_load( thread_atomic_int_t* atomic ) + +Returns the value of `atomic` as an atomic operation. + + +thread_atomic_int_store +----------------------- + + void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ) + +Sets the value of `atomic` as an atomic operation. + + +thread_atomic_int_inc +--------------------- + + int thread_atomic_int_inc( thread_atomic_int_t* atomic ) + +Increments the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_dec +--------------------- + + int thread_atomic_int_dec( thread_atomic_int_t* atomic ) + +Decrements the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_add +--------------------- + + int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ) + +Adds the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_sub +--------------------- + + int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ) + +Subtracts the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_swap +---------------------- + + int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ) + +Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_compare_and_swap +---------------------------------- + + int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ) + +Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`, +all as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_ptr_load +---------------------- + + void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ) + +Returns the value of `atomic` as an atomic operation. + + +thread_atomic_ptr_store +----------------------- + + void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ) + +Sets the value of `atomic` as an atomic operation. + + +thread_atomic_ptr_swap +---------------------- + + void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ) + +Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_ptr_compare_and_swap +---------------------------------- + + void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ) + +Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`, +all as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_timer_init +----------------- + + void thread_timer_init( thread_timer_t* timer ) + +Initializes the specified timer instance, preparing it for use. A timer can be used to sleep a thread for a high +precision duration. + + +thread_timer_term +----------------- + + void thread_timer_term( thread_timer_t* timer ) + +Terminates the specified timer instance, releasing any system resources held by it. + + +thread_timer_wait +----------------- + + void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ) + +Waits until `nanoseconds` amount of time have passed, before returning. + + +thread_tls_create +----------------- + + thread_tls_t thread_tls_create( void ) + +Creates a thread local storage (TLS) index. Once created, each thread has its own value for that TLS index, which can +be set or retrieved individually. + + +thread_tls_destroy +------------------ + + void thread_tls_destroy( thread_tls_t tls ) + +Destroys the specified TLS index. No further calls to `thread_tls_set` or `thread_tls_get` are valid after this. + + +thread_tls_set +-------------- + + void thread_tls_set( thread_tls_t tls, void* value ) + +Stores a value in the calling thread's slot for the specified TLS index. Each thread has its own value for each TLS +index. + + +thread_tls_get +-------------- + + void* thread_tls_get( thread_tls_t tls ) + +Retrieves the value from the calling thread's slot for the specified TLS index. Each thread has its own value for each +TLS index. + + +thread_queue_init +----------------- + + void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ) + +Initializes the specified queue instance, preparing it for use. The queue is a lock-free (but not wait-free) +single-producer/single-consumer queue - it will not acquire any locks as long as there is space for adding or items to +be consume, but will lock and wait when there is not. The `size` parameter specifies the number of elements in the +queue. The `values` parameter is an array of queue slots (`size` elements in length), each being of type `void*`. If +the queue is initially empty, the `count` parameter should be 0, otherwise it indicates the number of entires, from the +start of the `values` array, that the queue is initialized with. The `values` array is not copied, and must remain valid +until `thread_queue_term` is called. + + +thread_queue_term +----------------- + + void thread_queue_term( thread_queue_t* queue ) + +Terminates the specified queue instance, releasing any system resources held by it. + + +thread_queue_produce +-------------------- + + void thread_queue_produce( thread_queue_t* queue, void* value ) + +Adds an element to a single-producer/single-consumer queue. If there is space in the queue to add another element, no +lock will be taken. If the queue is full, calling thread will sleep until an element is consumed from another thread, +before adding the element and returning. + + +thread_queue_consume +-------------------- + + void* thread_queue_consume( thread_queue_t* queue ) + +Removes an element from a single-producer/single-consumer queue. If the queue contains at least one element, no lock +will be taken. If the queue is empty, the calling thread will sleep until an element is added from another thread, +before returning. `thread_queue_consume` returns the value that was removed from the queue. + + +thread_queue_count +------------------ + + int thread_queue_count( thread_queue_t* queue ) + +Returns the number of elements currently held in a single-producer/single-consumer queue. Be aware that by the time you +get the count, it might have changed by another thread calling consume or produce, so use with care. + +**/ + + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ + +#ifndef thread_impl +#define thread_impl + +union thread_mutex_t + { + void* align; + char data[ 64 ]; + }; + +union thread_signal_t + { + void* align; + char data[ 116 ]; + }; + +union thread_atomic_int_t + { + void* align; + #ifdef _WIN32 + long i; + #else + int i; + #endif + }; + +union thread_atomic_ptr_t + { + void* ptr; + }; + +union thread_timer_t + { + void* data; + char d[ 8 ]; + }; + +struct thread_queue_t + { + thread_signal_t data_ready; + thread_signal_t space_open; + thread_atomic_int_t count; + thread_atomic_int_t head; + thread_atomic_int_t tail; + void** values; + int size; + #ifndef NDEBUG + thread_atomic_int_t id_produce_is_set; + thread_id_t id_produce; + thread_atomic_int_t id_consume_is_set; + thread_id_t id_consume; + #endif + }; + +#endif /* thread_impl */ + + + +#ifdef THREAD_IMPLEMENTATION +#undef THREAD_IMPLEMENTATION + + +#if defined( _WIN32 ) + + #ifndef __TINYC__ + #pragma comment( lib, "winmm.lib" ) + #endif + + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + + #if !defined( _WIN32_WINNT ) || _WIN32_WINNT < 0x0501 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x501// requires Windows XP minimum + #endif + + #define _WINSOCKAPI_ + #pragma warning( push ) + #pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' + #pragma warning( disable: 4255 ) + #include + #pragma warning( pop ) + + // To set thread name + const DWORD MS_VC_EXCEPTION = 0x406D1388; + #pragma pack( push, 8 ) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + } THREADNAME_INFO; + #pragma pack(pop) + +#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + #include + #include + +#else + #error Unknown platform. +#endif + + +#ifndef NDEBUG + #include +#endif + + +thread_id_t thread_current_thread_id( void ) + { + #if defined( _WIN32 ) + + return (void*) (uintptr_t)GetCurrentThreadId(); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return (void*) pthread_self(); + + #else + #error Unknown platform. + #endif + } + + +void thread_yield( void ) + { + #if defined( _WIN32 ) + + SwitchToThread(); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + sched_yield(); + + #else + #error Unknown platform. + #endif + } + + +void thread_exit( int return_code ) + { + #if defined( _WIN32 ) + + ExitThread( (DWORD) return_code ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_exit( (void*)(uintptr_t) return_code ); + + #else + #error Unknown platform. + #endif + } + + +thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size ) + { + #if defined( _WIN32 ) + + DWORD thread_id; + HANDLE handle = CreateThread( NULL, stack_size > 0 ? (size_t)stack_size : 0U, + (LPTHREAD_START_ROUTINE)(uintptr_t) thread_proc, user_data, 0, &thread_id ); + if( !handle ) return NULL; + + return (thread_ptr_t) handle; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_t thread; + if( 0 != pthread_create( &thread, NULL, ( void* (*)( void * ) ) thread_proc, user_data ) ) + return NULL; + + return (thread_ptr_t) thread; + + #else + #error Unknown platform. + #endif + } + + +void thread_destroy( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + WaitForSingleObject( (HANDLE) thread, INFINITE ); + CloseHandle( (HANDLE) thread ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_join( (pthread_t) thread, NULL ); + + #else + #error Unknown platform. + #endif + } + + +int thread_join( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + WaitForSingleObject( (HANDLE) thread, INFINITE ); + DWORD retval; + GetExitCodeThread( (HANDLE) thread, &retval ); + return (int) retval; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + void* retval; + pthread_join( (pthread_t) thread, &retval ); + return (int)(uintptr_t) retval; + + #else + #error Unknown platform. + #endif + } + + +void thread_set_high_priority( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + SetThreadPriority( (HANDLE) thread, THREAD_PRIORITY_HIGHEST ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + struct sched_param sp; + memset( &sp, 0, sizeof( sp ) ); + sp.sched_priority = sched_get_priority_min( SCHED_RR ); + pthread_setschedparam( pthread_self(), SCHED_RR, &sp); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_init( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + // Compile-time size check + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( CRITICAL_SECTION ) ? 0 : 1 ); }; + #pragma warning( pop ) + + InitializeCriticalSectionAndSpinCount( (CRITICAL_SECTION*) mutex, 32 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + // Compile-time size check + struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( pthread_mutex_t ) ? 0 : 1 ); }; + + pthread_mutex_init( (pthread_mutex_t*) mutex, NULL ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_term( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + DeleteCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_destroy( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_lock( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + EnterCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_lock( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_unlock( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + LeaveCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_unlock( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +struct thread_internal_signal_t + { + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + CRITICAL_SECTION mutex; + CONDITION_VARIABLE condition; + int value; + #else + HANDLE event; + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_t mutex; + pthread_cond_t condition; + int value; + + #else + #error Unknown platform. + #endif + }; + + +void thread_signal_init( thread_signal_t* signal ) + { + // Compile-time size check + #ifdef _WIN32 + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + #endif + struct x { char thread_signal_type_too_small : ( sizeof( thread_signal_t ) < sizeof( struct thread_internal_signal_t ) ? 0 : 1 ); }; + #ifdef _WIN32 + #pragma warning( pop ) + #endif + + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + InitializeCriticalSectionAndSpinCount( &internal->mutex, 32 ); + InitializeConditionVariable( &internal->condition ); + internal->value = 0; + #else + internal->event = CreateEvent( NULL, FALSE, FALSE, NULL ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_init( &internal->mutex, NULL ); + pthread_cond_init( &internal->condition, NULL ); + internal->value = 0; + + #else + #error Unknown platform. + #endif + } + + + void thread_signal_term( thread_signal_t* signal ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + DeleteCriticalSection( &internal->mutex ); + #else + CloseHandle( internal->event ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_destroy( &internal->mutex ); + pthread_cond_destroy( &internal->condition ); + + #else + #error Unknown platform. + #endif + } + + +void thread_signal_raise( thread_signal_t* signal ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + EnterCriticalSection( &internal->mutex ); + internal->value = 1; + LeaveCriticalSection( &internal->mutex ); + WakeConditionVariable( &internal->condition ); + #else + SetEvent( internal->event ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_mutex_lock( &internal->mutex ); + internal->value = 1; + pthread_mutex_unlock( &internal->mutex ); + pthread_cond_signal( &internal->condition ); + + #else + #error Unknown platform. + #endif + } + + +int thread_signal_wait( thread_signal_t* signal, int timeout_ms ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + int failed = 0; + EnterCriticalSection( &internal->mutex ); + while( !internal->value && !failed ) + { + failed = ( 0 == SleepConditionVariableCS( &internal->condition, &internal->mutex, timeout_ms < 0 ? INFINITE : timeout_ms ) ); + } + if( !failed ) internal->value = 0; + LeaveCriticalSection( &internal->mutex ); + return !failed; + #else + int failed = WAIT_OBJECT_0 != WaitForSingleObject( internal->event, timeout_ms < 0 ? INFINITE : timeout_ms ); + return !failed; + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + struct timespec ts; + if( timeout_ms >= 0 ) + { + struct timeval tv; + gettimeofday( &tv, NULL ); + ts.tv_sec = time( NULL ) + timeout_ms / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * ( timeout_ms % 1000 ); + ts.tv_sec += ts.tv_nsec / ( 1000 * 1000 * 1000 ); + ts.tv_nsec %= ( 1000 * 1000 * 1000 ); + } + + int failed = 0; + pthread_mutex_lock( &internal->mutex ); + while( !internal->value && !failed ) + { + if( timeout_ms < 0 ) + failed = pthread_cond_wait( &internal->condition, &internal->mutex ); + else + failed = pthread_cond_timedwait( &internal->condition, &internal->mutex, &ts ); + } + if( !failed ) internal->value = 0; + pthread_mutex_unlock( &internal->mutex ); + return !failed; + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_load( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchange( &atomic->i, 0, 0 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + int ret; + __atomic_load( &atomic->i, &ret, __ATOMIC_SEQ_CST ); + return ret; + + #else + #error Unknown platform. + #endif + } + + +void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ) + { + #if defined( _WIN32 ) + + InterlockedExchange( &atomic->i, desired ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + __atomic_store( &atomic->i, &desired, __ATOMIC_SEQ_CST ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_inc( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedIncrement( &atomic->i ) - 1; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return (int)__atomic_fetch_add( &atomic->i, 1, __ATOMIC_SEQ_CST ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_dec( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedDecrement( &atomic->i ) + 1; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return (int)__atomic_fetch_sub( &atomic->i, 1, __ATOMIC_SEQ_CST ); + + #else + #error Unknown platform. + #endif + } + +#ifndef __TINYC__ + +int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ) + { + #if defined( _WIN32 ) + + return InterlockedExchangeAdd ( &atomic->i, value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return (int)__atomic_fetch_add( &atomic->i, value, __ATOMIC_SEQ_CST ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ) + { + #if defined( _WIN32 ) + + return InterlockedExchangeAdd( &atomic->i, -value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return (int)__atomic_fetch_sub( &atomic->i, value, __ATOMIC_SEQ_CST ); + + #else + #error Unknown platform. + #endif + } + +#endif + +int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ) + { + #if defined( _WIN32 ) + + return InterlockedExchange( &atomic->i, desired ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + int old; + __atomic_exchange( &atomic->i, &desired, &old, __ATOMIC_SEQ_CST ); + return old; + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchange( &atomic->i, desired, expected ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + __atomic_compare_exchange( &atomic->i, &expected, &desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); + return expected; + + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchangePointer( &atomic->ptr, 0, 0 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + void* ret; + __atomic_load( &atomic->ptr, &ret, __ATOMIC_SEQ_CST ); + return ret; + + #else + #error Unknown platform. + #endif + } + + +void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ) + { + #if defined( _WIN32 ) + + #pragma warning( push ) + #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG' + #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG' + #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size + InterlockedExchangePointer( &atomic->ptr, desired ); + #pragma warning( pop ) + + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + __atomic_store( &atomic->ptr, &desired, 0 ); + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ) + { + #if defined( _WIN32 ) + + #pragma warning( push ) + #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG' + #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG' + #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size + return InterlockedExchangePointer( &atomic->ptr, desired ); + #pragma warning( pop ) + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + void* old; + __atomic_exchange( &atomic->ptr, &desired, &old, __ATOMIC_SEQ_CST ); + return old; + + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchangePointer( &atomic->ptr, desired, expected ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + __atomic_compare_exchange( &atomic->ptr, &expected, &desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); + return expected; + + #else + #error Unknown platform. + #endif + } + + +void thread_timer_init( thread_timer_t* timer ) + { + #if defined( _WIN32 ) + + // Compile-time size check + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + struct x { char thread_timer_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( HANDLE ) ? 0 : 1 ); }; + #pragma warning( pop ) + + #ifndef __TINYC__ + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeBeginPeriod( tc.wPeriodMin ); + #endif + + *(HANDLE*)timer = CreateWaitableTimer( NULL, TRUE, NULL ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + // Nothing + + #else + #error Unknown platform. + #endif + } + + +void thread_timer_term( thread_timer_t* timer ) + { + #if defined( _WIN32 ) + + CloseHandle( *(HANDLE*)timer ); + + #ifndef __TINYC__ + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeEndPeriod( tc.wPeriodMin ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + // Nothing + + #else + #error Unknown platform. + #endif + } + + +void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ) + { + #if defined( _WIN32 ) + + LARGE_INTEGER due_time; + due_time.QuadPart = - (LONGLONG) ( nanoseconds / 100 ); + BOOL b = SetWaitableTimer( *(HANDLE*)timer, &due_time, 0, 0, 0, FALSE ); + (void) b; + WaitForSingleObject( *(HANDLE*)timer, INFINITE ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + struct timespec rem; + struct timespec req; + req.tv_sec = nanoseconds / 1000000000ULL; + req.tv_nsec = nanoseconds - req.tv_sec * 1000000000ULL; + while( nanosleep( &req, &rem ) ) + req = rem; + + #else + #error Unknown platform. + #endif + } + + +thread_tls_t thread_tls_create( void ) + { + #if defined( _WIN32 ) + + DWORD tls = TlsAlloc(); + if( tls == TLS_OUT_OF_INDEXES ) + return NULL; + else + return (thread_tls_t) (uintptr_t) tls; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_key_t tls; + if( pthread_key_create( &tls, NULL ) == 0 ) + return (thread_tls_t) (uintptr_t) tls; + else + return NULL; + + #else + #error Unknown platform. + #endif + } + + +void thread_tls_destroy( thread_tls_t tls ) + { + #if defined( _WIN32 ) + + TlsFree( (DWORD) (uintptr_t) tls ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_key_delete( (pthread_key_t) (uintptr_t) tls ); + + #else + #error Unknown platform. + #endif + } + + +void thread_tls_set( thread_tls_t tls, void* value ) + { + #if defined( _WIN32 ) + + TlsSetValue( (DWORD) (uintptr_t) tls, value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + pthread_setspecific( (pthread_key_t) (uintptr_t) tls, value ); + + #else + #error Unknown platform. + #endif + } + + +void* thread_tls_get( thread_tls_t tls ) + { + #if defined( _WIN32 ) + + return TlsGetValue( (DWORD) (uintptr_t) tls ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) + + return pthread_getspecific( (pthread_key_t) (uintptr_t) tls ); + + #else + #error Unknown platform. + #endif + } + + +void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ) + { + queue->values = values; + thread_signal_init( &queue->data_ready ); + thread_signal_init( &queue->space_open ); + thread_atomic_int_store( &queue->head, 0 ); + thread_atomic_int_store( &queue->tail, ( count > size ? size : count ) % size ); + thread_atomic_int_store( &queue->count, count > size ? size : count ); + queue->size = size; + #ifndef NDEBUG + thread_atomic_int_store( &queue->id_produce_is_set, 0 ); + thread_atomic_int_store( &queue->id_consume_is_set, 0 ); + #endif + } + + +void thread_queue_term( thread_queue_t* queue ) + { + thread_signal_term( &queue->space_open ); + thread_signal_term( &queue->data_ready ); + } + + +void thread_queue_produce( thread_queue_t* queue, void* value ) + { + #ifndef NDEBUG + if( thread_atomic_int_compare_and_swap( &queue->id_produce_is_set, 0, 1 ) == 0 ) + queue->id_produce = thread_current_thread_id(); + assert( thread_current_thread_id() == queue->id_produce ); + #endif + if( thread_atomic_int_load( &queue->count ) == queue->size ) + thread_signal_wait( &queue->space_open, THREAD_SIGNAL_WAIT_INFINITE ); + int tail = thread_atomic_int_inc( &queue->tail ); + queue->values[ tail % queue->size ] = value; + if( thread_atomic_int_inc( &queue->count ) == 0 ) + thread_signal_raise( &queue->data_ready ); + } + + +void* thread_queue_consume( thread_queue_t* queue ) + { + #ifndef NDEBUG + if( thread_atomic_int_compare_and_swap( &queue->id_consume_is_set, 0, 1 ) == 0 ) + queue->id_consume = thread_current_thread_id(); + assert( thread_current_thread_id() == queue->id_consume ); + #endif + if( thread_atomic_int_load( &queue->count ) == 0 ) + thread_signal_wait( &queue->data_ready, THREAD_SIGNAL_WAIT_INFINITE ); + int head = thread_atomic_int_inc( &queue->head ); + void* retval = queue->values[ head % queue->size ]; + if( thread_atomic_int_dec( &queue->count ) == queue->size ) + thread_signal_raise( &queue->space_open ); + return retval; + } + + +int thread_queue_count( thread_queue_t* queue ) + { + return thread_atomic_int_load( &queue->count ); + } + + +#endif /* THREAD_IMPLEMENTATION */ + +/* +revision history: + 0.2 first publicly released version +*/ + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2015 Mattias Gustavsson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/linuxdoom-1.10/i_system.c b/linuxdoom-1.10/i_system.c index bacd67a..5f27b41 100644 --- a/linuxdoom-1.10/i_system.c +++ b/linuxdoom-1.10/i_system.c @@ -20,18 +20,17 @@ // //----------------------------------------------------------------------------- +#if _WIN32 #include "doomtype.h" #include "doomdef.h" #include "doomstat.h" #include "d_ticcmd.h" -#include -#include void I_Init (void) { I_InitSound(); - I_InitGraphics(); + // I_InitGraphics(); } @@ -52,7 +51,12 @@ byte* I_ZoneBase (int *size) // returns current time in tics. int I_GetTime (void) { - return clock()/(CLOCKS_PER_SEC/TICRATE); + LARGE_INTEGER large; + LARGE_INTEGER freq; + QueryPerformanceCounter( &large ); + QueryPerformanceFrequency( &freq ); + + return large.QuadPart * TICRATE / freq.QuadPart; } @@ -75,9 +79,9 @@ void I_StartFrame (void) // called before processing each tic in a frame. // Quick syncronous operations are performed here. // Can call D_PostEvent. -void I_StartTic (void) -{ -} +//void I_StartTic (void) +//{ +//} // Asynchronous interrupt functions should maintain private queues // that are read by the synchronous functions @@ -151,3 +155,191 @@ void I_Error (char *error, ...) exit(-1); } + +#else +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// +//----------------------------------------------------------------------------- + +static const char +rcsid[] = "$Id: m_bbox.c,v 1.1 1997/02/03 22:45:10 b1 Exp $"; + + +#include +#include +#include + +#include +#include +#include + + +#include "doomdef.h" +#include "m_misc.h" +#include "i_video.h" +#include "i_sound.h" + +#include "d_net.h" +#include "g_game.h" + +#ifdef __GNUG__ +#pragma implementation "i_system.h" +#endif +#include "i_system.h" + + + + +int mb_used = 6; + + +void +I_Tactile +( int on, + int off, + int total ) +{ + // UNUSED. + on = off = total = 0; +} + +ticcmd_t emptycmd; +ticcmd_t* I_BaseTiccmd(void) +{ + return &emptycmd; +} + + +int I_GetHeapSize (void) +{ + return mb_used*1024*1024; +} + +byte* I_ZoneBase (int* size) +{ + *size = mb_used*1024*1024; + return (byte *) malloc (*size); +} + + + +// +// I_GetTime +// returns time in 1/70th second tics +// +int I_GetTime (void) +{ + struct timeval tp; + struct timezone tzp; + int newtics; + static int basetime=0; + + gettimeofday(&tp, &tzp); + if (!basetime) + basetime = tp.tv_sec; + newtics = (tp.tv_sec-basetime)*TICRATE + tp.tv_usec*TICRATE/1000000; + return newtics; +} + + + +// +// I_Init +// +void I_Init (void) +{ + I_InitSound(); + // I_InitGraphics(); +} + +// +// I_Quit +// +void I_Quit (void) +{ + D_QuitNetGame (); + I_ShutdownSound(); + I_ShutdownMusic(); + M_SaveDefaults (); + I_ShutdownGraphics(); + exit(0); +} + +void I_WaitVBL(int count) +{ +#ifdef SGI + sginap(1); +#else +#ifdef SUN + sleep(0); +#else + usleep (count * (1000000/70) ); +#endif +#endif +} + +void I_BeginRead(void) +{ +} + +void I_EndRead(void) +{ +} + +byte* I_AllocLow(int length) +{ + byte* mem; + + mem = (byte *)malloc (length); + memset (mem,0,length); + return mem; +} + + +// +// I_Error +// +extern boolean demorecording; + +void I_Error (char *error, ...) +{ + va_list argptr; + + // Message first. + va_start (argptr,error); + fprintf (stderr, "Error: "); + vfprintf (stderr,error,argptr); + fprintf (stderr, "\n"); + va_end (argptr); + + fflush( stderr ); + + // Shutdown. Here might be other errors. + if (demorecording) + G_CheckDemoStatus(); + + D_QuitNetGame (); + I_ShutdownGraphics(); + + exit(-1); +} + +#endif \ No newline at end of file diff --git a/linuxdoom-1.10/i_video.c b/linuxdoom-1.10/i_video.c index c5e87e0..d142d51 100644 --- a/linuxdoom-1.10/i_video.c +++ b/linuxdoom-1.10/i_video.c @@ -21,40 +21,1295 @@ // //----------------------------------------------------------------------------- +#ifdef _WIN32 +#include "doomtype.h" + +#include + +#include "doomstat.h" +#include "i_system.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_main.h" + +#include "doomdef.h" + +uint8_t app_palette[256 * 3]; +uint8_t* app_screen; +thread_atomic_int_t app_running; + +event_t event_queue[ 256 ] = {0}; +int events_count = 0; +thread_mutex_t event_mutex; + +thread_signal_t vblank_signal; + +int counter = 0; +int app_proc( app_t* app, void* user_data ) +{ + app_screenmode( app, APP_SCREENMODE_FULLSCREEN ); + app_interpolation( app, APP_INTERPOLATION_NONE ); + + uint8_t* screen_buffer_xbgr = (uint8_t*)malloc( SCREENWIDTH * SCREENHEIGHT * 4 ); + + frametimer_t* frametimer = frametimer_create( 0); + frametimer_lock_rate( frametimer, 70 ); + + crtemu_t* crtemu = crtemu_create( CRTEMU_TYPE_LITE,0 ); + + APP_U32 empty = 0; + app_pointer( app, 1, 1, &empty, 0, 0 ); + + while( thread_atomic_int_load(&app_running) && app_yield( app ) != APP_STATE_EXIT_REQUESTED ) + { + if( app_screen ) + { + for( int i = 0; i < SCREENWIDTH * SCREENHEIGHT; ++i ) + { + uint8_t scr = app_screen[ i ]; + screen_buffer_xbgr[ i * 4 + 3] = 0xff; + screen_buffer_xbgr[ i * 4 ] = app_palette[ scr * 3 ]; + screen_buffer_xbgr[ i * 4 + 1] = app_palette[ scr * 3 + 1 ]; + screen_buffer_xbgr[ i * 4 + 2] = app_palette[ scr * 3 + 2 ]; + } + } + + frametimer_update( frametimer ); + ++counter; + thread_signal_raise( &vblank_signal ); + + crtemu_present( crtemu, ( counter * 1000000ULL) / 70, (APP_U32*)screen_buffer_xbgr, SCREENWIDTH, SCREENHEIGHT, 0xffffff, 0x000000 ); + app_present( app, 0, 0, 0, 0xffffffff, 0x00000000); + + } + free( screen_buffer_xbgr ); + return 0; +} + +int app_proc_thread() +{ + return app_run( app_proc, 0, 0, 0, 0 ); +} + +void I_InitGraphics (void) +{ + #if defined( __TINYC__ ) + HMODULE kernel = LoadLibrary( "kernel32" ); + InitializeConditionVariable = GetProcAddress( kernel, "InitializeConditionVariable"); + WakeConditionVariable = GetProcAddress( kernel, "WakeConditionVariable"); + SleepConditionVariableCS = GetProcAddress( kernel, "SleepConditionVariableCS"); + #endif + thread_mutex_init( &event_mutex ); + thread_signal_init( &vblank_signal ); + + app_screen = (uint8_t*)malloc( SCREENWIDTH * SCREENHEIGHT ); + thread_atomic_int_store( &app_running, 1 ); + + thread_create( app_proc_thread, 0, THREAD_STACK_SIZE_DEFAULT ); +} + +void I_ShutdownGraphics(void) +{ + thread_atomic_int_store( &app_running, 0 ); + + free( app_screen ); + app_screen = 0; + + thread_signal_term( &vblank_signal ); +} + +// Takes full 8 bit values. +void I_SetPalette (byte* palette) +{ + int i; + int c; + // set the X colormap entries + for (i=0 ; i<256 ; i++) + { + c = gammatable[usegamma][*palette++]; + app_palette[i*3+0] = (c<<8) + c; + c = gammatable[usegamma][*palette++]; + app_palette[i*3+1] = (c<<8) + c; + c = gammatable[usegamma][*palette++]; + app_palette[i*3+2] = (c<<8) + c; + } +} + + +void I_UpdateNoBlit (void) +{ +} + +void I_FinishUpdate (void) +{ + if( app_screen ) + { + memcpy( app_screen, screens[ 0 ], SCREENWIDTH*SCREENHEIGHT ); + } +} + +// +// Called by D_DoomLoop, +// called before processing each tic in a frame. +// Quick syncronous operations are performed here. +// Can call D_PostEvent. +void I_StartTic (void) +{ + static int prev[ 256 ] = { 0 }; + int keys[ 256 ]; + for( int i = 0; i < 256; ++i ) keys[ i ] = ( GetAsyncKeyState( i ) & 0x8000 ) != 0; + + for( int i = 0; i < 255; ++i ) + { + int key = 0; + + switch( i ) + { + case VK_RSHIFT: key = KEY_RSHIFT; break; + case VK_RCONTROL: key = KEY_RCTRL; break; + case VK_MENU: key = KEY_RALT; break; + case VK_BACK: key = KEY_BACKSPACE; break; + case VK_PAUSE: key = KEY_PAUSE; break; + case VK_TAB: key = KEY_TAB; break; + case VK_F1: key = KEY_F1; break; + case VK_F2: key = KEY_F2; break; + case VK_F3: key = KEY_F3; break; + case VK_F4: key = KEY_F4; break; + case VK_F5: key = KEY_F5; break; + case VK_F6: key = KEY_F6; break; + case VK_F7: key = KEY_F7; break; + case VK_F8: key = KEY_F8; break; + case VK_F9: key = KEY_F9; break; + case VK_F10: key = KEY_F10; break; + case VK_F11: key = KEY_F11; break; + case VK_F12: key = KEY_F12; break; + case VK_UP: key = KEY_UPARROW; break; + case VK_DOWN: key = KEY_DOWNARROW; break; + case VK_LEFT: key = KEY_LEFTARROW; break; + case VK_RIGHT: key = KEY_RIGHTARROW; break; + case VK_ESCAPE: key = KEY_ESCAPE; break; + case VK_SHIFT: key = KEY_RSHIFT; break; + case VK_CONTROL: key = KEY_RCTRL; break; + case VK_RMENU: key = KEY_RALT; break; + case VK_LMENU: key = KEY_LALT; break; + case VK_RETURN: key = KEY_ENTER; break; + case VK_SPACE: key = ' '; break; + case VK_OEM_PLUS: key = KEY_EQUALS; break; + case VK_OEM_MINUS: key = KEY_MINUS; break; + default: + key = tolower( i ); + } + + if( keys[ i ] && !prev[ i ] ) + { + event_t ev; + ev.type = ev_keydown; + ev.data1 = key; + D_PostEvent( &ev ); + } + else if( !keys[ i ] && prev[ i ] ) + { + event_t ev; + ev.type = ev_keyup; + ev.data1 = key; + D_PostEvent( &ev ); + } + prev[ i ] = keys[ i ]; + } + + RECT r; + GetClientRect( GetDesktopWindow(), &r ); + r.bottom -= r.top; + r.right -= r.left; + POINT pos; + GetCursorPos( &pos ); + pos.x -= r.right / 2; + pos.y -= r.bottom / 2; + + event_t ev; + ev.type = ev_mouse; + ev.data1 = 0; + ev.data2 = pos.x << 2; + ev.data3 = -pos.y << 2; + D_PostEvent( &ev ); + SetCursorPos(r.right / 2,r.bottom / 2); +} + +// Wait for vertical retrace or pause a bit. +void I_WaitVBL(int count) +{ + int c = counter; + while ( counter - c < count ) + thread_signal_wait( &vblank_signal, 1000 ); +} + +void I_ReadScreen (byte* scr) +{ + memcpy( scr, screens[0], SCREENWIDTH * SCREENHEIGHT ); +} + +void I_BeginRead (void) +{ + +} + +void I_EndRead (void) +{ + +} + + +#else +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// DOOM graphics stuff for X11, UNIX. +// +//----------------------------------------------------------------------------- + static const char rcsid[] = "$Id: i_x.c,v 1.6 1997/02/03 22:45:10 b1 Exp $"; +#include +#include +#include +#include + +#include +#include +#include + +#include +// Had to dig up XShm.c for this one. +// It is in the libXext, but not in the XFree86 headers. +#ifdef LINUX +int XShmGetEventBase( Display* dpy ); // problems with g++? +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "doomstat.h" +#include "i_system.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_main.h" + #include "doomdef.h" -#include "doomtype.h" -void I_InitGraphics (void) + +#define POINTER_WARP_COUNTDOWN 1 + +Display* X_display=0; +Window X_mainWindow; +Colormap X_cmap; +Visual* X_visual; +GC X_gc; +XEvent X_event; +int X_screen; +XVisualInfo X_visualinfo; +XImage* image; +int X_width; +int X_height; + +// MIT SHared Memory extension. +boolean doShm; + +XShmSegmentInfo X_shminfo; +int X_shmeventtype; + +// Fake mouse handling. +// This cannot work properly w/o DGA. +// Needs an invisible mouse cursor at least. +boolean grabMouse; +int doPointerWarp = POINTER_WARP_COUNTDOWN; + +// Blocky mode, +// replace each 320x200 pixel with multiply*multiply pixels. +// According to Dave Taylor, it still is a bonehead thing +// to use .... +static int multiply=1; + + +// +// Translates the key currently in X_event +// + +int xlatekey(void) { -} + int rc; + + switch(rc = XKeycodeToKeysym(X_display, X_event.xkey.keycode, 0)) + { + case XK_Left: rc = KEY_LEFTARROW; break; + case XK_Right: rc = KEY_RIGHTARROW; break; + case XK_Down: rc = KEY_DOWNARROW; break; + case XK_Up: rc = KEY_UPARROW; break; + case XK_Escape: rc = KEY_ESCAPE; break; + case XK_Return: rc = KEY_ENTER; break; + case XK_Tab: rc = KEY_TAB; break; + case XK_F1: rc = KEY_F1; break; + case XK_F2: rc = KEY_F2; break; + case XK_F3: rc = KEY_F3; break; + case XK_F4: rc = KEY_F4; break; + case XK_F5: rc = KEY_F5; break; + case XK_F6: rc = KEY_F6; break; + case XK_F7: rc = KEY_F7; break; + case XK_F8: rc = KEY_F8; break; + case XK_F9: rc = KEY_F9; break; + case XK_F10: rc = KEY_F10; break; + case XK_F11: rc = KEY_F11; break; + case XK_F12: rc = KEY_F12; break; + + case XK_BackSpace: + case XK_Delete: rc = KEY_BACKSPACE; break; + + case XK_Pause: rc = KEY_PAUSE; break; + + case XK_KP_Equal: + case XK_equal: rc = KEY_EQUALS; break; + + case XK_KP_Subtract: + case XK_minus: rc = KEY_MINUS; break; + + case XK_Shift_L: + case XK_Shift_R: + rc = KEY_RSHIFT; + break; + + case XK_Control_L: + case XK_Control_R: + rc = KEY_RCTRL; + break; + + case XK_Alt_L: + case XK_Meta_L: + case XK_Alt_R: + case XK_Meta_R: + rc = KEY_RALT; + break; + + default: + if (rc >= XK_space && rc <= XK_asciitilde) + rc = rc - XK_space + ' '; + if (rc >= 'A' && rc <= 'Z') + rc = rc - 'A' + 'a'; + break; + } + + return rc; + +} void I_ShutdownGraphics(void) { + // Detach from X server + if (!XShmDetach(X_display, &X_shminfo)) + I_Error("XShmDetach() failed in I_ShutdownGraphics()"); + + // Release shared memory. + shmdt(X_shminfo.shmaddr); + shmctl(X_shminfo.shmid, IPC_RMID, 0); + + // Paranoia. + image->data = NULL; } -// Takes full 8 bit values. -void I_SetPalette (byte* palette) + + +// +// I_StartFrame +// +void I_StartFrame (void) { + // er? + +} + +static int lastmousex = 0; +static int lastmousey = 0; +boolean mousemoved = false; +boolean shmFinished; + +void I_GetEvent(void) +{ + + event_t event; + + // put event-grabbing stuff in here + XNextEvent(X_display, &X_event); + switch (X_event.type) + { + case KeyPress: + event.type = ev_keydown; + event.data1 = xlatekey(); + D_PostEvent(&event); + // fprintf(stderr, "k"); + break; + case KeyRelease: + event.type = ev_keyup; + event.data1 = xlatekey(); + D_PostEvent(&event); + // fprintf(stderr, "ku"); + break; + case ButtonPress: + event.type = ev_mouse; + event.data1 = + (X_event.xbutton.state & Button1Mask) + | (X_event.xbutton.state & Button2Mask ? 2 : 0) + | (X_event.xbutton.state & Button3Mask ? 4 : 0) + | (X_event.xbutton.button == Button1) + | (X_event.xbutton.button == Button2 ? 2 : 0) + | (X_event.xbutton.button == Button3 ? 4 : 0); + event.data2 = event.data3 = 0; + D_PostEvent(&event); + // fprintf(stderr, "b"); + break; + case ButtonRelease: + event.type = ev_mouse; + event.data1 = + (X_event.xbutton.state & Button1Mask) + | (X_event.xbutton.state & Button2Mask ? 2 : 0) + | (X_event.xbutton.state & Button3Mask ? 4 : 0); + // suggest parentheses around arithmetic in operand of | + event.data1 = + event.data1 + ^ (X_event.xbutton.button == Button1 ? 1 : 0) + ^ (X_event.xbutton.button == Button2 ? 2 : 0) + ^ (X_event.xbutton.button == Button3 ? 4 : 0); + event.data2 = event.data3 = 0; + D_PostEvent(&event); + // fprintf(stderr, "bu"); + break; + case MotionNotify: + event.type = ev_mouse; + event.data1 = + (X_event.xmotion.state & Button1Mask) + | (X_event.xmotion.state & Button2Mask ? 2 : 0) + | (X_event.xmotion.state & Button3Mask ? 4 : 0); + event.data2 = (X_event.xmotion.x - lastmousex) << 2; + event.data3 = (lastmousey - X_event.xmotion.y) << 2; + + if (event.data2 || event.data3) + { + lastmousex = X_event.xmotion.x; + lastmousey = X_event.xmotion.y; + if (X_event.xmotion.x != X_width/2 && + X_event.xmotion.y != X_height/2) + { + D_PostEvent(&event); + // fprintf(stderr, "m"); + mousemoved = false; + } else + { + mousemoved = true; + } + } + break; + + case Expose: + case ConfigureNotify: + break; + + default: + if (doShm && X_event.type == X_shmeventtype) shmFinished = true; + break; + } + +} + +Cursor +createnullcursor +( Display* display, + Window root ) +{ + Pixmap cursormask; + XGCValues xgc; + GC gc; + XColor dummycolour; + Cursor cursor; + + cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/); + xgc.function = GXclear; + gc = XCreateGC(display, cursormask, GCFunction, &xgc); + XFillRectangle(display, cursormask, gc, 0, 0, 1, 1); + dummycolour.pixel = 0; + dummycolour.red = 0; + dummycolour.flags = 04; + cursor = XCreatePixmapCursor(display, cursormask, cursormask, + &dummycolour,&dummycolour, 0,0); + XFreePixmap(display,cursormask); + XFreeGC(display,gc); + return cursor; +} + +// +// I_StartTic +// +void I_StartTic (void) +{ + + if (!X_display) + return; + + while (XPending(X_display)) + I_GetEvent(); + + // Warp the pointer back to the middle of the window + // or it will wander off - that is, the game will + // loose input focus within X11. + if (grabMouse) + { + if (!--doPointerWarp) + { + XWarpPointer( X_display, + None, + X_mainWindow, + 0, 0, + 0, 0, + X_width/2, X_height/2); + + doPointerWarp = POINTER_WARP_COUNTDOWN; + } + } + + mousemoved = false; + } +// +// I_UpdateNoBlit +// void I_UpdateNoBlit (void) { + // what is this? } +// +// I_FinishUpdate +// void I_FinishUpdate (void) { + + static int lasttic; + int tics; + int i; + // UNUSED static unsigned char *bigscreen=0; + + // draws little dots on the bottom of the screen + if (devparm) + { + + i = I_GetTime(); + tics = i - lasttic; + lasttic = i; + if (tics > 20) tics = 20; + + for (i=0 ; idata[i*X_width]; + + y = SCREENHEIGHT; + while (y--) + { + x = SCREENWIDTH; + do + { + fouripixels = *ilineptr++; + twoopixels = (fouripixels & 0xff000000) + | ((fouripixels>>8) & 0xffff00) + | ((fouripixels>>16) & 0xff); + twomoreopixels = ((fouripixels<<16) & 0xff000000) + | ((fouripixels<<8) & 0xffff00) + | (fouripixels & 0xff); +#ifdef __BIG_ENDIAN__ + *olineptrs[0]++ = twoopixels; + *olineptrs[1]++ = twoopixels; + *olineptrs[0]++ = twomoreopixels; + *olineptrs[1]++ = twomoreopixels; +#else + *olineptrs[0]++ = twomoreopixels; + *olineptrs[1]++ = twomoreopixels; + *olineptrs[0]++ = twoopixels; + *olineptrs[1]++ = twoopixels; +#endif + } while (x-=4); + olineptrs[0] += X_width/4; + olineptrs[1] += X_width/4; + } + + } + else if (multiply == 3) + { + unsigned int *olineptrs[3]; + unsigned int *ilineptr; + int x, y, i; + unsigned int fouropixels[3]; + unsigned int fouripixels; + + ilineptr = (unsigned int *) (screens[0]); + for (i=0 ; i<3 ; i++) + olineptrs[i] = (unsigned int *) &image->data[i*X_width]; + + y = SCREENHEIGHT; + while (y--) + { + x = SCREENWIDTH; + do + { + fouripixels = *ilineptr++; + fouropixels[0] = (fouripixels & 0xff000000) + | ((fouripixels>>8) & 0xff0000) + | ((fouripixels>>16) & 0xffff); + fouropixels[1] = ((fouripixels<<8) & 0xff000000) + | (fouripixels & 0xffff00) + | ((fouripixels>>8) & 0xff); + fouropixels[2] = ((fouripixels<<16) & 0xffff0000) + | ((fouripixels<<8) & 0xff00) + | (fouripixels & 0xff); +#ifdef __BIG_ENDIAN__ + *olineptrs[0]++ = fouropixels[0]; + *olineptrs[1]++ = fouropixels[0]; + *olineptrs[2]++ = fouropixels[0]; + *olineptrs[0]++ = fouropixels[1]; + *olineptrs[1]++ = fouropixels[1]; + *olineptrs[2]++ = fouropixels[1]; + *olineptrs[0]++ = fouropixels[2]; + *olineptrs[1]++ = fouropixels[2]; + *olineptrs[2]++ = fouropixels[2]; +#else + *olineptrs[0]++ = fouropixels[2]; + *olineptrs[1]++ = fouropixels[2]; + *olineptrs[2]++ = fouropixels[2]; + *olineptrs[0]++ = fouropixels[1]; + *olineptrs[1]++ = fouropixels[1]; + *olineptrs[2]++ = fouropixels[1]; + *olineptrs[0]++ = fouropixels[0]; + *olineptrs[1]++ = fouropixels[0]; + *olineptrs[2]++ = fouropixels[0]; +#endif + } while (x-=4); + olineptrs[0] += 2*X_width/4; + olineptrs[1] += 2*X_width/4; + olineptrs[2] += 2*X_width/4; + } + + } + else if (multiply == 4) + { + // Broken. Gotta fix this some day. + void Expand4(unsigned *, double *); + Expand4 ((unsigned *)(screens[0]), (double *) (image->data)); + } + + if (doShm) + { + + if (!XShmPutImage( X_display, + X_mainWindow, + X_gc, + image, + 0, 0, + 0, 0, + X_width, X_height, + True )) + I_Error("XShmPutImage() failed\n"); + + // wait for it to finish and processes all input events + shmFinished = false; + do + { + I_GetEvent(); + } while (!shmFinished); + + } + else + { + + // draw the image + XPutImage( X_display, + X_mainWindow, + X_gc, + image, + 0, 0, + 0, 0, + X_width, X_height ); + + // sync up with server + XSync(X_display, False); + + } + } -void I_WaitVBL(int count) -{ -} +// +// I_ReadScreen +// void I_ReadScreen (byte* scr) { + memcpy (scr, screens[0], SCREENWIDTH*SCREENHEIGHT); } + +// +// Palette stuff. +// +static XColor colors[256]; + +void UploadNewPalette(Colormap cmap, byte *palette) +{ + + register int i; + register int c; + static boolean firstcall = true; + +#ifdef __cplusplus + if (X_visualinfo.c_class == PseudoColor && X_visualinfo.depth == 8) +#else + if (X_visualinfo.class == PseudoColor && X_visualinfo.depth == 8) +#endif + { + // initialize the colormap + if (firstcall) + { + firstcall = false; + for (i=0 ; i<256 ; i++) + { + colors[i].pixel = i; + colors[i].flags = DoRed|DoGreen|DoBlue; + } + } + + // set the X colormap entries + for (i=0 ; i<256 ; i++) + { + c = gammatable[usegamma][*palette++]; + colors[i].red = (c<<8) + c; + c = gammatable[usegamma][*palette++]; + colors[i].green = (c<<8) + c; + c = gammatable[usegamma][*palette++]; + colors[i].blue = (c<<8) + c; + } + + // store the colors to the current colormap + XStoreColors(X_display, cmap, colors, 256); + + } +} + +// +// I_SetPalette +// +void I_SetPalette (byte* palette) +{ + UploadNewPalette(X_cmap, palette); +} + + +// +// This function is probably redundant, +// if XShmDetach works properly. +// ddt never detached the XShm memory, +// thus there might have been stale +// handles accumulating. +// +void grabsharedmemory(int size) +{ + + int key = ('d'<<24) | ('o'<<16) | ('o'<<8) | 'm'; + struct shmid_ds shminfo; + int minsize = 320*200; + int id; + int rc; + // UNUSED int done=0; + int pollution=5; + + // try to use what was here before + do + { + id = shmget((key_t) key, minsize, 0777); // just get the id + if (id != -1) + { + rc=shmctl(id, IPC_STAT, &shminfo); // get stats on it + if (!rc) + { + if (shminfo.shm_nattch) + { + fprintf(stderr, "User %d appears to be running " + "DOOM. Is that wise?\n", shminfo.shm_cpid); + key++; + } + else + { + if (getuid() == shminfo.shm_perm.cuid) + { + rc = shmctl(id, IPC_RMID, 0); + if (!rc) + fprintf(stderr, + "Was able to kill my old shared memory\n"); + else + I_Error("Was NOT able to kill my old shared memory"); + + id = shmget((key_t)key, size, IPC_CREAT|0777); + if (id==-1) + I_Error("Could not get shared memory"); + + rc=shmctl(id, IPC_STAT, &shminfo); + + break; + + } + if (size >= shminfo.shm_segsz) + { + fprintf(stderr, + "will use %d's stale shared memory\n", + shminfo.shm_cpid); + break; + } + else + { + fprintf(stderr, + "warning: can't use stale " + "shared memory belonging to id %d, " + "key=0x%x\n", + shminfo.shm_cpid, key); + key++; + } + } + } + else + { + I_Error("could not get stats on key=%d", key); + } + } + else + { + id = shmget((key_t)key, size, IPC_CREAT|0777); + if (id==-1) + { + extern int errno; + fprintf(stderr, "errno=%d\n", errno); + I_Error("Could not get any shared memory"); + } + break; + } + } while (--pollution); + + if (!pollution) + { + I_Error("Sorry, system too polluted with stale " + "shared memory segments.\n"); + } + + X_shminfo.shmid = id; + + // attach to the shared memory segment + image->data = X_shminfo.shmaddr = shmat(id, 0, 0); + + fprintf(stderr, "shared memory id=%d, addr=0x%x\n", id, + (int) (image->data)); +} + +void I_InitGraphics(void) +{ + + char* displayname; + char* d; + int n; + int pnum; + int x=0; + int y=0; + + // warning: char format, different type arg + char xsign=' '; + char ysign=' '; + + int oktodraw; + unsigned long attribmask; + XSetWindowAttributes attribs; + XGCValues xgcvalues; + int valuemask; + static int firsttime=1; + + if (!firsttime) + return; + firsttime = 0; + + signal(SIGINT, (void (*)(int)) I_Quit); + + if (M_CheckParm("-2")) + multiply = 2; + + if (M_CheckParm("-3")) + multiply = 3; + + if (M_CheckParm("-4")) + multiply = 4; + + X_width = SCREENWIDTH * multiply; + X_height = SCREENHEIGHT * multiply; + + // check for command-line display name + if ( (pnum=M_CheckParm("-disp")) ) // suggest parentheses around assignment + displayname = myargv[pnum+1]; + else + displayname = 0; + + // check if the user wants to grab the mouse (quite unnice) + grabMouse = !!M_CheckParm("-grabmouse"); + + // check for command-line geometry + if ( (pnum=M_CheckParm("-geom")) ) // suggest parentheses around assignment + { + // warning: char format, different type arg 3,5 + n = sscanf(myargv[pnum+1], "%c%d%c%d", &xsign, &x, &ysign, &y); + + if (n==2) + x = y = 0; + else if (n==6) + { + if (xsign == '-') + x = -x; + if (ysign == '-') + y = -y; + } + else + I_Error("bad -geom parameter"); + } + + // open the display + X_display = XOpenDisplay(displayname); + if (!X_display) + { + if (displayname) + I_Error("Could not open display [%s]", displayname); + else + I_Error("Could not open display (DISPLAY=[%s])", getenv("DISPLAY")); + } + + // use the default visual + X_screen = DefaultScreen(X_display); + if (!XMatchVisualInfo(X_display, X_screen, 8, PseudoColor, &X_visualinfo)) + I_Error("xdoom currently only supports 256-color PseudoColor screens"); + X_visual = X_visualinfo.visual; + + // check for the MITSHM extension + doShm = XShmQueryExtension(X_display); + + // even if it's available, make sure it's a local connection + if (doShm) + { + if (!displayname) displayname = (char *) getenv("DISPLAY"); + if (displayname) + { + d = displayname; + while (*d && (*d != ':')) d++; + if (*d) *d = 0; + if (!(!strcasecmp(displayname, "unix") || !*displayname)) doShm = false; + } + } + + fprintf(stderr, "Using MITSHM extension\n"); + + // create the colormap + X_cmap = XCreateColormap(X_display, RootWindow(X_display, + X_screen), X_visual, AllocAll); + + // setup attributes for main window + attribmask = CWEventMask | CWColormap | CWBorderPixel; + attribs.event_mask = + KeyPressMask + | KeyReleaseMask + // | PointerMotionMask | ButtonPressMask | ButtonReleaseMask + | ExposureMask; + + attribs.colormap = X_cmap; + attribs.border_pixel = 0; + + // create the main window + X_mainWindow = XCreateWindow( X_display, + RootWindow(X_display, X_screen), + x, y, + X_width, X_height, + 0, // borderwidth + 8, // depth + InputOutput, + X_visual, + attribmask, + &attribs ); + + XDefineCursor(X_display, X_mainWindow, + createnullcursor( X_display, X_mainWindow ) ); + + // create the GC + valuemask = GCGraphicsExposures; + xgcvalues.graphics_exposures = False; + X_gc = XCreateGC( X_display, + X_mainWindow, + valuemask, + &xgcvalues ); + + // map the window + XMapWindow(X_display, X_mainWindow); + + // wait until it is OK to draw + oktodraw = 0; + while (!oktodraw) + { + XNextEvent(X_display, &X_event); + if (X_event.type == Expose + && !X_event.xexpose.count) + { + oktodraw = 1; + } + } + + // grabs the pointer so it is restricted to this window + if (grabMouse) + XGrabPointer(X_display, X_mainWindow, True, + ButtonPressMask|ButtonReleaseMask|PointerMotionMask, + GrabModeAsync, GrabModeAsync, + X_mainWindow, None, CurrentTime); + + if (doShm) + { + + X_shmeventtype = XShmGetEventBase(X_display) + ShmCompletion; + + // create the image + image = XShmCreateImage( X_display, + X_visual, + 8, + ZPixmap, + 0, + &X_shminfo, + X_width, + X_height ); + + grabsharedmemory(image->bytes_per_line * image->height); + + + // UNUSED + // create the shared memory segment + // X_shminfo.shmid = shmget (IPC_PRIVATE, + // image->bytes_per_line * image->height, IPC_CREAT | 0777); + // if (X_shminfo.shmid < 0) + // { + // perror(""); + // I_Error("shmget() failed in InitGraphics()"); + // } + // fprintf(stderr, "shared memory id=%d\n", X_shminfo.shmid); + // attach to the shared memory segment + // image->data = X_shminfo.shmaddr = shmat(X_shminfo.shmid, 0, 0); + + + if (!image->data) + { + perror(""); + I_Error("shmat() failed in InitGraphics()"); + } + + // get the X server to attach to it + if (!XShmAttach(X_display, &X_shminfo)) + I_Error("XShmAttach() failed in InitGraphics()"); + + } + else + { + image = XCreateImage( X_display, + X_visual, + 8, + ZPixmap, + 0, + (char*)malloc(X_width * X_height), + X_width, X_height, + 8, + X_width ); + + } + + if (multiply == 1) + screens[0] = (unsigned char *) (image->data); + else + screens[0] = (unsigned char *) malloc (SCREENWIDTH * SCREENHEIGHT); + +} + + +unsigned exptable[256]; + +void InitExpand (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + exptable[i] = i | (i<<8) | (i<<16) | (i<<24); +} + +double exptable2[256*256]; + +void InitExpand2 (void) +{ + int i; + int j; + // UNUSED unsigned iexp, jexp; + double* exp; + union + { + double d; + unsigned u[2]; + } pixel; + + printf ("building exptable2...\n"); + exp = exptable2; + for (i=0 ; i<256 ; i++) + { + pixel.u[0] = i | (i<<8) | (i<<16) | (i<<24); + for (j=0 ; j<256 ; j++) + { + pixel.u[1] = j | (j<<8) | (j<<16) | (j<<24); + *exp++ = pixel.d; + } + } + printf ("done.\n"); +} + +int inited; + +void +Expand4 +( unsigned* lineptr, + double* xline ) +{ + double dpixel; + unsigned x; + unsigned y; + unsigned fourpixels; + unsigned step; + double* exp; + + exp = exptable2; + if (!inited) + { + inited = 1; + InitExpand2 (); + } + + + step = 3*SCREENWIDTH/2; + + y = SCREENHEIGHT-1; + do + { + x = SCREENWIDTH; + + do + { + fourpixels = lineptr[0]; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff0000)>>13) ); + xline[0] = dpixel; + xline[160] = dpixel; + xline[320] = dpixel; + xline[480] = dpixel; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff)<<3 ) ); + xline[1] = dpixel; + xline[161] = dpixel; + xline[321] = dpixel; + xline[481] = dpixel; + + fourpixels = lineptr[1]; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff0000)>>13) ); + xline[2] = dpixel; + xline[162] = dpixel; + xline[322] = dpixel; + xline[482] = dpixel; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff)<<3 ) ); + xline[3] = dpixel; + xline[163] = dpixel; + xline[323] = dpixel; + xline[483] = dpixel; + + fourpixels = lineptr[2]; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff0000)>>13) ); + xline[4] = dpixel; + xline[164] = dpixel; + xline[324] = dpixel; + xline[484] = dpixel; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff)<<3 ) ); + xline[5] = dpixel; + xline[165] = dpixel; + xline[325] = dpixel; + xline[485] = dpixel; + + fourpixels = lineptr[3]; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff0000)>>13) ); + xline[6] = dpixel; + xline[166] = dpixel; + xline[326] = dpixel; + xline[486] = dpixel; + + dpixel = *(double *)( (int)exp + ( (fourpixels&0xffff)<<3 ) ); + xline[7] = dpixel; + xline[167] = dpixel; + xline[327] = dpixel; + xline[487] = dpixel; + + lineptr+=4; + xline+=8; + } while (x-=16); + xline += step; + } while (y--); +} + + + +#endif diff --git a/linuxdoom-1.10/m_misc.c b/linuxdoom-1.10/m_misc.c index 47c0c38..2fe599a 100644 --- a/linuxdoom-1.10/m_misc.c +++ b/linuxdoom-1.10/m_misc.c @@ -237,6 +237,21 @@ default_t defaults[] = {"show_messages",&showMessages, 1}, +#ifdef _WIN32 + {"key_right",&key_right, KEY_RIGHTARROW}, + {"key_left",&key_left, KEY_LEFTARROW}, + {"key_up",&key_up, KEY_UPARROW}, + {"key_down",&key_down, KEY_DOWNARROW}, + {"key_strafeleft",&key_strafeleft, ','}, + {"key_straferight",&key_straferight, '.'}, + + {"key_fire",&key_fire, KEY_RCTRL}, + {"key_use",&key_use, ' '}, + {"key_strafe",&key_strafe, KEY_RALT}, + {"key_speed",&key_speed, KEY_RSHIFT}, + +#endif + #ifdef NORMALUNIX {"key_right",&key_right, KEY_RIGHTARROW}, {"key_left",&key_left, KEY_LEFTARROW},