4819 lines
210 KiB
C++
4819 lines
210 KiB
C++
/*
|
|
------------------------------------------------------------------------------
|
|
Licensing information can be found at the end of the file.
|
|
------------------------------------------------------------------------------
|
|
|
|
app.h - v0.4 - 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 <stdlib.h> // for rand and __argc/__argv
|
|
#include <string.h> // 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 `<stdint.h>` 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 <stddef.h>
|
|
#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 <wajic_gl.h>
|
|
#include <wajic_coro.h>
|
|
#else
|
|
#include <GL/glew.h>
|
|
#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 <string.h>
|
|
#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 r = ( ( border_xbgr >> 16 ) & 0xff ) / 255.0f;
|
|
float g = ( ( border_xbgr >> 8 ) & 0xff ) / 255.0f;
|
|
float b = ( ( 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 1ULL; }
|
|
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 <windows.h>
|
|
//#include <shlobj.h>
|
|
#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" )
|
|
#endif
|
|
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives'
|
|
#include <math.h>
|
|
#pragma warning( pop )
|
|
|
|
#ifndef APP_MALLOC
|
|
#include <stdlib.h>
|
|
#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 );
|
|
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 );
|
|
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 );
|
|
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 );
|
|
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 )
|
|
{
|
|
int width = app->windowed_w;
|
|
if( app->screenmode == APP_SCREENMODE_WINDOW )
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof( placement );
|
|
GetWindowPlacement( app->hwnd, &placement );
|
|
width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
|
|
}
|
|
RECT r = app_internal_rect( 0, 0, 0, 0 );
|
|
AdjustWindowRect( &r, APP_WINDOWED_WS_STYLE | WS_VISIBLE, FALSE );
|
|
return width - ( r.right - r.left );
|
|
}
|
|
|
|
|
|
int app_window_height( app_t* app )
|
|
{
|
|
int height = app->windowed_h;
|
|
if( app->screenmode == APP_SCREENMODE_WINDOW )
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof( placement );
|
|
GetWindowPlacement( app->hwnd, &placement );
|
|
height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
|
|
}
|
|
RECT r = app_internal_rect( 0, 0, 0, 0 );
|
|
AdjustWindowRect( &r, APP_WINDOWED_WS_STYLE | WS_VISIBLE, FALSE );
|
|
return height - ( 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 <stdlib.h>
|
|
#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 <string.h>
|
|
#include <stdio.h>
|
|
|
|
#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 <stdlib.h>
|
|
#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 <string.h>
|
|
#include <stdio.h>
|
|
|
|
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<<e.button);
|
|
if (buttons & btn) return;
|
|
buttons |= btn;
|
|
evts.push(6, 1, e.button);
|
|
cancelEvent(e);
|
|
});
|
|
windowEvent('mouseup', (e)=>
|
|
{
|
|
var btn = (1<<e.button);
|
|
if (!(buttons & btn)) return;
|
|
buttons &= ~btn;
|
|
evts.push(6, 0, e.button);
|
|
cancelEvent(e);
|
|
});
|
|
canvasEvent('wheel', (e)=>{ 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;
|
|
}
|
|
|
|
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, 512, 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 += 512;
|
|
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) + 511)>>9;
|
|
})
|
|
|
|
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 != 512; 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 += 512/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, 512, 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) * 512 * 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.
|
|
|
|
------------------------------------------------------------------------------
|
|
*/
|