diff --git a/thirdparty/sokol/sokol_app.h b/thirdparty/sokol/sokol_app.h index ef4ac700b1..bab03cc02c 100644 --- a/thirdparty/sokol/sokol_app.h +++ b/thirdparty/sokol/sokol_app.h @@ -2,6 +2,13 @@ #define SOKOL_APP_IMPL #endif #ifndef SOKOL_APP_INCLUDED +/* + V language IMPORTANT NOTE: + all the V patch in this code are marked with: + // __v_ start + // __v_ end +*/ + /* sokol_app.h -- cross-platform application wrapper @@ -54,8 +61,8 @@ For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp - Portions of the Windows and Linux GL initialization and event code have been - taken from GLFW (http://www.glfw.org/) + Portions of the Windows and Linux GL initialization, event-, icon- etc... code + have been taken from GLFW (http://www.glfw.org/) iOS onscreen keyboard support 'inspired' by libgdx. @@ -71,7 +78,7 @@ - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined - link with the following libs: -lkernel32 -luser32 -lshell32 - additionally with the GL backend: -lgdi32 - - additionally with the D3D11 backend: -ld3d11 -ldxgi -dxguid + - additionally with the D3D11 backend: -ld3d11 -ldxgi On Linux, you also need to use the -pthread compiler and linker option, otherwise weird things will happen, see here for details: https://github.com/floooh/sokol/issues/376 @@ -119,6 +126,8 @@ RESIZED | YES | YES | YES | YES | YES | YES | --- | YES ICONIFIED | YES | YES | YES | --- | --- | YES | --- | --- RESTORED | YES | YES | YES | --- | --- | YES | --- | --- + FOCUSED | YES | YES | YES | --- | --- | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | --- | --- | YES SUSPENDED | --- | --- | --- | YES | YES | YES | --- | TODO RESUMED | --- | --- | --- | YES | YES | YES | --- | TODO QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | TODO | YES @@ -135,14 +144,9 @@ clipboard | YES | YES | TODO | --- | --- | TODO | --- | YES MSAA | YES | YES | YES | YES | YES | TODO | TODO | YES drag'n'drop | YES | YES | YES | --- | --- | TODO | TODO | YES + window icon | YES | YES(1)| YES | --- | --- | TODO | TODO | YES - TODO - ==== - - Linux: - - clipboard support - - UWP: - - clipboard, mouselock - - sapp_consume_event() on non-web platforms? + (1) macOS has no regular window icons, instead the dock icon is changed STEP BY STEP ============ @@ -214,8 +218,8 @@ .init_userdata_cb (void (*)(void* user_data)) .frame_userdata_cb (void (*)(void* user_data)) .cleanup_userdata_cb (void (*)(void* user_data)) - .event_cb (void(*)(const sapp_event* event, void* user_data)) - .fail_cb (void(*)(const char* msg, void* user_data)) + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) + .fail_userdata_cb (void(*)(const char* msg, void* user_data)) These are the user-data versions of the callback functions. You can mix those with the standard callbacks that don't have the user_data argument. @@ -776,6 +780,113 @@ To check if the application window is currently in fullscreen mode, call sapp_is_fullscreen(). + WINDOW ICON SUPPORT + =================== + Some sokol_app.h backends allow to change the window icon programmatically: + + - on Win32: the small icon in the window's title bar, and the + bigger icon in the task bar + - on Linux: highly dependent on the used window manager, but usually + the window's title bar icon and/or the task bar icon + - on HTML5: the favicon shown in the page's browser tab + + NOTE that it is not possible to set the actual application icon which is + displayed by the operating system on the desktop or 'home screen'. Those + icons must be provided 'traditionally' through operating-system-specific + resources which are associated with the application (sokol_app.h might + later support setting the window icon from platform specific resource data + though). + + There are two ways to set the window icon: + + - at application start in the sokol_main() function by initializing + the sapp_desc.icon nested struct + - or later by calling the function sapp_set_icon() + + As a convenient shortcut, sokol_app.h comes with a builtin default-icon + (a rainbow-colored 'S', which at least looks a bit better than the Windows + default icon for applications), which can be activated like this: + + At startup in sokol_main(): + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon.sokol_default = true + }; + } + + Or later by calling: + + sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true }); + + NOTE that a completely zero-initialized sapp_icon_desc struct will not + update the window icon in any way. This is an 'escape hatch' so that you + can handle the window icon update yourself (or if you do this already, + sokol_app.h won't get in your way, in this case just leave the + sapp_desc.icon struct zero-initialized). + + Providing your own icon images works exactly like in GLFW (down to the + data format): + + You provide one or more 'candidate images' in different sizes, and the + sokol_app.h platform backends pick the best match for the specific backend + and icon type. + + For each candidate image, you need to provide: + + - the width in pixels + - the height in pixels + - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844 + on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44) + + For instance, if you have 3 candidate images (small, medium, big) of + sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup + like this: + + // the actual pixel data (RGBA8, origin top-left) + const uint32_t small[16][16] = { ... }; + const uint32_t medium[32][32] = { ... }; + const uint32_t big[64][64] = { ... }; + + const sapp_icon_desc icon_desc = { + .images = { + { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) }, + { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) }, + // ...or without the SAPP_RANGE helper macro: + { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } } + } + }; + + An sapp_icon_desc struct initialized like this can then either be applied + at application start in sokol_main: + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon = icon_desc + }; + } + + ...or later by calling sapp_set_icon(): + + sapp_set_icon(&icon_desc); + + Some window icon caveats: + + - once the window icon has been updated, there's no way to go back to + the platform's default icon, this is because some platforms (Linux + and HTML5) don't switch the icon visual back to the default even if + the custom icon is deleted or removed + - on HTML5, if the sokol_app.h icon doesn't show up in the browser + tab, check that there's no traditional favicon 'link' element + is defined in the page's index.html, sokol_app.h will only + append a new favicon link element, but not delete any manually + defined favicon in the page + + For an example and test of the window icon feature, check out the the + 'icon-sapp' sample on the sokol-samples git repository. + ONSCREEN KEYBOARD ================= On some platforms which don't provide a physical keyboard, sokol-app @@ -890,6 +1001,7 @@ distribution. */ #define SOKOL_APP_INCLUDED (1) +#include // size_t #include #include @@ -910,12 +1022,22 @@ extern "C" { #endif +/* misc constants */ enum { SAPP_MAX_TOUCHPOINTS = 8, SAPP_MAX_MOUSEBUTTONS = 3, SAPP_MAX_KEYCODES = 512, + SAPP_MAX_ICONIMAGES = 8, }; +/* + sapp_event_type + + The type of event that's passed to the event handler callback + in the sapp_event.type field. These are not just "traditional" + input events, but also notify the application about state changes + or other user-invoked actions. +*/ typedef enum sapp_event_type { SAPP_EVENTTYPE_INVALID, SAPP_EVENTTYPE_KEY_DOWN, @@ -934,6 +1056,8 @@ typedef enum sapp_event_type { SAPP_EVENTTYPE_RESIZED, SAPP_EVENTTYPE_ICONIFIED, SAPP_EVENTTYPE_RESTORED, + SAPP_EVENTTYPE_FOCUSED, + SAPP_EVENTTYPE_UNFOCUSED, SAPP_EVENTTYPE_SUSPENDED, SAPP_EVENTTYPE_RESUMED, SAPP_EVENTTYPE_UPDATE_CURSOR, @@ -944,7 +1068,14 @@ typedef enum sapp_event_type { _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF } sapp_event_type; -/* key codes are the same names and values as GLFW */ +/* + sapp_keycode + + The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the + struct field sapp_event.key_code. + + Note that the keycode values are identical with GLFW. +*/ typedef enum sapp_keycode { SAPP_KEYCODE_INVALID = 0, SAPP_KEYCODE_SPACE = 32, @@ -1069,6 +1200,15 @@ typedef enum sapp_keycode { SAPP_KEYCODE_MENU = 348, } sapp_keycode; +/* + sapp_touchpoint + + Describes a single touchpoint in a multitouch event (TOUCHES_BEGAN, + TOUCHES_MOVED, TOUCHES_ENDED). + + Touch points are stored in the nested array sapp_event.touches[], + and the number of touches is stored in sapp_event.num_touches. +*/ typedef struct sapp_touchpoint { uintptr_t identifier; float pos_x; @@ -1076,6 +1216,12 @@ typedef struct sapp_touchpoint { bool changed; } sapp_touchpoint; +/* + sapp_mousebutton + + The currently pressed mouse button in the events MOUSE_DOWN + and MOUSE_UP, stored in the struct field sapp_event.mouse_button. +*/ typedef enum sapp_mousebutton { SAPP_MOUSEBUTTON_LEFT = 0x0, SAPP_MOUSEBUTTON_RIGHT = 0x1, @@ -1083,78 +1229,157 @@ typedef enum sapp_mousebutton { SAPP_MOUSEBUTTON_INVALID = 0x100, } sapp_mousebutton; +/* + These are currently pressed modifier keys (and mouse buttons) which are + passed in the event struct field sapp_event.modifiers. +*/ enum { - SAPP_MODIFIER_SHIFT = 0x1, - SAPP_MODIFIER_CTRL = 0x2, - SAPP_MODIFIER_ALT = 0x4, - SAPP_MODIFIER_SUPER = 0x8 + SAPP_MODIFIER_SHIFT = 0x1, // left or right shift key + SAPP_MODIFIER_CTRL = 0x2, // left or right control key + SAPP_MODIFIER_ALT = 0x4, // left or right alt key + SAPP_MODIFIER_SUPER = 0x8, // left or right 'super' key + SAPP_MODIFIER_LMB = 0x100, // left mouse button + SAPP_MODIFIER_RMB = 0x200, // right mouse button + SAPP_MODIFIER_MMB = 0x400, // middle mouse button }; +/* + sapp_event + + This is an all-in-one event struct passed to the event handler + user callback function. Note that it depends on the event + type what struct fields actually contain useful values, so you + should first check the event type before reading other struct + fields. +*/ typedef struct sapp_event { - uint64_t frame_count; - sapp_event_type type; - sapp_keycode key_code; - uint32_t char_code; - bool key_repeat; - uint32_t modifiers; - sapp_mousebutton mouse_button; - float mouse_x; - float mouse_y; - float mouse_dx; - float mouse_dy; - float scroll_x; - float scroll_y; - int num_touches; - sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; - int window_width; + uint64_t frame_count; // current frame counter, always valid, useful for checking if two events were issued in the same frame + sapp_event_type type; // the event type, always valid + sapp_keycode key_code; // the virtual key code, only valid in KEY_UP, KEY_DOWN + uint32_t char_code; // the UTF-32 character code, only valid in CHAR events + bool key_repeat; // true if this is a key-repeat event, valid in KEY_UP, KEY_DOWN and CHAR + uint32_t modifiers; // current modifier keys, valid in all key-, char- and mouse-events + sapp_mousebutton mouse_button; // mouse button that was pressed or released, valid in MOUSE_DOWN, MOUSE_UP + float mouse_x; // current horizontal mouse position in pixels, always valid except during mouse lock + float mouse_y; // current vertical mouse position in pixels, always valid except during mouse lock + float mouse_dx; // relative horizontal mouse movement since last frame, always valid + float mouse_dy; // relative vertical mouse movement since last frame, always valid + float scroll_x; // horizontal mouse wheel scroll distance, valid in MOUSE_SCROLL events + float scroll_y; // vertical mouse wheel scroll distance, valid in MOUSE_SCROLL events + int num_touches; // number of valid items in the touches[] array + sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; // current touch points, valid in TOUCHES_BEGIN, TOUCHES_MOVED, TOUCHES_ENDED + int window_width; // current window- and framebuffer sizes in pixels, always valid int window_height; - int framebuffer_width; - int framebuffer_height; + int framebuffer_width; // = window_width * dpi_scale + int framebuffer_height; // = window_height * dpi_scale } sapp_event; +/* + sg_range + + A general pointer/size-pair struct and constructor macros for passing binary blobs + into sokol_app.h. +*/ +typedef struct sapp_range { + const void* ptr; + size_t size; +} sapp_range; +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#endif +#if defined(__cplusplus) +#define SAPP_RANGE(x) sapp_range{ &x, sizeof(x) } +#else +#define SAPP_RANGE(x) (sapp_range){ &x, sizeof(x) } +#endif + +/* + sapp_image_desc + + This is used to describe image data to sokol_app.h (at first, window + icons, later maybe cursor images). + + Note that the actual image pixel format depends on the use case: + + - window icon pixels are RGBA8 + - cursor images are ??? (FIXME) +*/ +typedef struct sapp_image_desc { + int width; + int height; + sapp_range pixels; +} sapp_image_desc; + +/* + sapp_icon_desc + + An icon description structure for use in sapp_desc.icon and + sapp_set_icon(). + + When setting a custom image, the application can provide a number of + candidates differing in size, and sokol_app.h will pick the image(s) + closest to the size expected by the platform's window system. + + To set sokol-app's default icon, set .sokol_default to true. + + Otherwise provide candidate images of different sizes in the + images[] array. + + If both the sokol_default flag is set to true, any image candidates + will be ignored and the sokol_app.h default icon will be set. +*/ +typedef struct sapp_icon_desc { + bool sokol_default; + sapp_image_desc images[SAPP_MAX_ICONIMAGES]; +} sapp_icon_desc; + + typedef struct sapp_desc { - void (*init_cb)(void); /* these are the user-provided callbacks without user data */ + void (*init_cb)(void); // these are the user-provided callbacks without user data void (*frame_cb)(void); void (*cleanup_cb)(void); void (*event_cb)(const sapp_event*); void (*fail_cb)(const char*); - void* user_data; /* these are the user-provided callbacks with user data */ + void* user_data; // these are the user-provided callbacks with user data void (*init_userdata_cb)(void*); void (*frame_userdata_cb)(void*); void (*cleanup_userdata_cb)(void*); void (*event_userdata_cb)(const sapp_event*, void*); void (*fail_userdata_cb)(const char*, void*); - int width; /* the preferred width of the window / canvas */ - int height; /* the preferred height of the window / canvas */ - int sample_count; /* MSAA sample count */ - int swap_interval; /* the preferred swap interval (ignored on some platforms) */ - bool high_dpi; /* whether the rendering canvas is full-resolution on HighDPI displays */ - bool fullscreen; /* whether the window should be created in fullscreen mode */ - bool alpha; /* whether the framebuffer should have an alpha channel (ignored on some platforms) */ - const char* window_title; /* the window title as UTF-8 encoded string */ - bool user_cursor; /* if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR */ - bool enable_clipboard; /* enable clipboard access, default is false */ - int clipboard_size; /* max size of clipboard content in bytes */ - bool enable_dragndrop; /* enable file dropping (drag'n'drop), default is false */ - int max_dropped_files; /* max number of dropped files to process (default: 1) */ - int max_dropped_file_path_length; /* max length in bytes of a dropped UTF-8 file path (default: 2048) */ + int width; // the preferred width of the window / canvas + int height; // the preferred height of the window / canvas + int sample_count; // MSAA sample count + int swap_interval; // the preferred swap interval (ignored on some platforms) + bool high_dpi; // whether the rendering canvas is full-resolution on HighDPI displays + bool fullscreen; // whether the window should be created in fullscreen mode + bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) + const char* window_title; // the window title as UTF-8 encoded string + bool user_cursor; // if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR + bool enable_clipboard; // enable clipboard access, default is false + int clipboard_size; // max size of clipboard content in bytes + bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false + int max_dropped_files; // max number of dropped files to process (default: 1) + int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048) + sapp_icon_desc icon; // the initial window icon to set /* backend-specific options */ - bool gl_force_gles2; /* if true, setup GLES2/WebGL even if GLES3/WebGL2 is available */ - bool win32_console_utf8; /* if true, set the output console codepage to UTF-8 */ - bool win32_console_create; /* if true, attach stdout/stderr to a new console window */ - bool win32_console_attach; /* if true, attach stdout/stderr to parent process */ - const char* html5_canvas_name; /* the name (id) of the HTML5 canvas element, default is "canvas" */ - bool html5_canvas_resize; /* if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked */ - bool html5_preserve_drawing_buffer; /* HTML5 only: whether to preserve default framebuffer content between frames */ - bool html5_premultiplied_alpha; /* HTML5 only: whether the rendered pixels use premultiplied alpha convention */ - bool html5_ask_leave_site; /* initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) */ - bool ios_keyboard_resizes_canvas; /* if true, showing the iOS keyboard shrinks the canvas */ - - /* V patches */ + bool gl_force_gles2; // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available + bool win32_console_utf8; // if true, set the output console codepage to UTF-8 + bool win32_console_create; // if true, attach stdout/stderr to a new console window + bool win32_console_attach; // if true, attach stdout/stderr to parent process + const char* html5_canvas_name; // the name (id) of the HTML5 canvas element, default is "canvas" + bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked + bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames + bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention + bool html5_ask_leave_site; // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) + bool ios_keyboard_resizes_canvas; // if true, showing the iOS keyboard shrinks the canvas + // __v_ start bool __v_native_render; /* V patch to allow for native rendering */ + // __v_ end } sapp_desc; /* HTML5 specific: request and response structs for @@ -1243,6 +1468,8 @@ SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void); /* set the window title (only on desktop platforms) */ SOKOL_APP_API_DECL void sapp_set_window_title(const char* str); +/* set the window icon (only on Windows and Linux) */ +SOKOL_APP_API_DECL void sapp_set_icon(const sapp_icon_desc* icon_desc); /* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */ SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void); /* gets the dropped file paths */ @@ -1276,6 +1503,8 @@ SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); /* D3D11: get pointer to ID3D11DeviceContext object */ SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void); +/* D3D11: get pointer to IDXGISwapChain object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_swap_chain(void); /* D3D11: get pointer to ID3D11RenderTargetView object */ SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void); /* D3D11: get pointer to ID3D11DepthStencilView */ @@ -1494,13 +1723,10 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #pragma comment (lib, "kernel32") #pragma comment (lib, "user32") #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ + #pragma comment (lib, "gdi32") #if defined(SOKOL_D3D11) #pragma comment (lib, "dxgi") #pragma comment (lib, "d3d11") - #pragma comment (lib, "dxguid") - #endif - #if defined(SOKOL_GLCORE33) - #pragma comment (lib, "gdi32") #endif #if defined(SOKOL_D3D11) @@ -1569,7 +1795,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } /*== MACOS DECLARATIONS ======================================================*/ #if defined(_SAPP_MACOS) -// __v_ +// __v_ start @interface SokolWindow : NSWindow { } @end @@ -1583,7 +1809,7 @@ MyView2* g_view; - (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless - (BOOL)canBecomeMainWindow { return YES; } @end -// __v_ +// __v_ end @interface _sapp_macos_app_delegate : NSObject @end @@ -1603,9 +1829,11 @@ MyView2* g_view; typedef struct { uint32_t flags_changed_store; uint8_t mouse_buttons; + // __v_ start // NSWindow* window; // SokolWindow* window; // __v_ _sapp_macos_window* window; // __v_ + // __v_ end NSTrackingArea* tracking_area; _sapp_macos_app_delegate* app_dlg; _sapp_macos_window_delegate* win_dlg; @@ -1674,6 +1902,7 @@ typedef struct { bool wants_show_keyboard; bool wants_hide_keyboard; bool mouse_lock_requested; + uint16_t mouse_buttons; #if defined(SOKOL_WGPU) _sapp_wgpu_t wgpu; #endif @@ -1724,6 +1953,8 @@ typedef struct { typedef struct { HWND hwnd; HDC dc; + HICON big_icon; + HICON small_icon; UINT orig_codepage; LONG mouse_locked_x, mouse_locked_y; bool is_win10_or_greater; @@ -1872,7 +2103,7 @@ typedef struct { #define GLX_RGBA_BIT 0x00000001 #define GLX_WINDOW_BIT 0x00000001 #define GLX_DRAWABLE_TYPE 0x8010 -#define GLX_RENDER_TYPE 0x8011 +#define GLX_RENDER_TYPE 0x8011 #define GLX_DOUBLEBUFFER 5 #define GLX_RED_SIZE 8 #define GLX_GREEN_SIZE 9 @@ -1954,6 +2185,7 @@ typedef struct { Atom WM_STATE; Atom NET_WM_NAME; Atom NET_WM_ICON_NAME; + Atom NET_WM_ICON; Atom NET_WM_STATE; Atom NET_WM_STATE_FULLSCREEN; _sapp_xi_t xi; @@ -2072,6 +2304,8 @@ typedef struct { _sapp_mouse_t mouse; _sapp_clipboard_t clipboard; _sapp_drop_t drop; + sapp_icon_desc default_icon_desc; + uint32_t* default_icon_pixels; #if defined(_SAPP_MACOS) _sapp_macos_t macos; #elif defined(_SAPP_IOS) @@ -2101,8 +2335,9 @@ typedef struct { wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ sapp_keycode keycodes[SAPP_MAX_KEYCODES]; - /* V patches */ + // __v_ start bool __v_native_render; /* V patch to allow for native rendering */ + // __v_ end } _sapp_t; static _sapp_t _sapp; @@ -2131,9 +2366,11 @@ _SOKOL_PRIVATE void _sapp_call_init(void) { } _SOKOL_PRIVATE void _sapp_call_frame(void) { - if (_sapp.__v_native_render) { - return; - } + // __v_ start + if (_sapp.__v_native_render) { + return; + } + // __v_ end if (_sapp.init_called && !_sapp.cleanup_called) { if (_sapp.desc.frame_cb) { _sapp.desc.frame_cb(); @@ -2144,7 +2381,7 @@ _SOKOL_PRIVATE void _sapp_call_frame(void) { } } -// __v_ +// __v_ start _SOKOL_PRIVATE void _sapp_call_frame_native(void) { //puts("_sapp_call_frame_native()"); //printf("init called=%d cleanup_called=%d\n", _sapp.init_called,_sapp.cleanup_called); @@ -2157,7 +2394,7 @@ _SOKOL_PRIVATE void _sapp_call_frame_native(void) { } } } - +// __v_ end _SOKOL_PRIVATE void _sapp_call_cleanup(void) { if (!_sapp.cleanup_called) { @@ -2272,7 +2509,9 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { _sapp.dpi_scale = 1.0f; _sapp.fullscreen = _sapp.desc.fullscreen; _sapp.mouse.shown = true; + // __v_ start _sapp.__v_native_render = _sapp.desc.__v_native_render; + // __v_end } _SOKOL_PRIVATE void _sapp_discard_state(void) { @@ -2284,6 +2523,9 @@ _SOKOL_PRIVATE void _sapp_discard_state(void) { SOKOL_ASSERT(_sapp.drop.buffer); SOKOL_FREE((void*)_sapp.drop.buffer); } + if (_sapp.default_icon_pixels) { + SOKOL_FREE((void*)_sapp.default_icon_pixels); + } _SAPP_CLEAR(_sapp_t, _sapp); } @@ -2332,6 +2574,169 @@ _SOKOL_PRIVATE void _sapp_frame(void) { _sapp.frame_count++; } +_SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) { + SOKOL_ASSERT(desc->width > 0); + SOKOL_ASSERT(desc->height > 0); + SOKOL_ASSERT(desc->pixels.ptr != 0); + SOKOL_ASSERT(desc->pixels.size > 0); + const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t); + if (wh_size != desc->pixels.size) { + SOKOL_LOG("Image data size mismatch (must be width*height*4 bytes)\n"); + return false; + } + return true; +} + +_SOKOL_PRIVATE int _sapp_image_bestmatch(const sapp_image_desc image_descs[], int num_images, int width, int height) { + int least_diff = 0x7FFFFFFF; + int least_index = 0; + for (int i = 0; i < num_images; i++) { + int diff = (image_descs[i].width * image_descs[i].height) - (width * height); + if (diff < 0) { + diff = -diff; + } + if (diff < least_diff) { + least_diff = diff; + least_index = i; + } + } + return least_index; +} + +_SOKOL_PRIVATE int _sapp_icon_num_images(const sapp_icon_desc* desc) { + int index = 0; + for (; index < SAPP_MAX_ICONIMAGES; index++) { + if (0 == desc->images[index].pixels.ptr) { + break; + } + } + return index; +} + +_SOKOL_PRIVATE bool _sapp_validate_icon_desc(const sapp_icon_desc* desc, int num_images) { + SOKOL_ASSERT(num_images <= SAPP_MAX_ICONIMAGES); + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &desc->images[i]; + if (!_sapp_image_validate(img_desc)) { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE void _sapp_setup_default_icon(void) { + SOKOL_ASSERT(0 == _sapp.default_icon_pixels); + + const int num_icons = 3; + const int icon_sizes[3] = { 16, 32, 64 }; // must be multiple of 8! + + // allocate a pixel buffer for all icon pixels + int all_num_pixels = 0; + for (int i = 0; i < num_icons; i++) { + all_num_pixels += icon_sizes[i] * icon_sizes[i]; + } + _sapp.default_icon_pixels = (uint32_t*) SOKOL_CALLOC((size_t)all_num_pixels, sizeof(uint32_t)); + + // initialize default_icon_desc struct + uint32_t* dst = _sapp.default_icon_pixels; + const uint32_t* dst_end = dst + all_num_pixels; + (void)dst_end; // silence unused warning in release mode + for (int i = 0; i < num_icons; i++) { + const int dim = (int) icon_sizes[i]; + const int num_pixels = dim * dim; + sapp_image_desc* img_desc = &_sapp.default_icon_desc.images[i]; + img_desc->width = dim; + img_desc->height = dim; + img_desc->pixels.ptr = dst; + img_desc->pixels.size = (size_t)num_pixels * sizeof(uint32_t); + dst += num_pixels; + } + SOKOL_ASSERT(dst == dst_end); + + // Amstrad CPC font 'S' + const uint8_t tile[8] = { + 0x3C, + 0x66, + 0x60, + 0x3C, + 0x06, + 0x66, + 0x3C, + 0x00, + }; + // rainbow colors + const uint32_t colors[8] = { + 0xFF4370FF, + 0xFF26A7FF, + 0xFF58EEFF, + 0xFF57E1D4, + 0xFF65CC9C, + 0xFF6ABB66, + 0xFFF5A542, + 0xFFC2577E, + }; + dst = _sapp.default_icon_pixels; + const uint32_t blank = 0x00FFFFFF; + const uint32_t shadow = 0xFF000000; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + SOKOL_ASSERT((dim % 8) == 0); + const int scale = dim / 8; + for (int ty = 0, y = 0; ty < 8; ty++) { + const uint32_t color = colors[ty]; + for (int sy = 0; sy < scale; sy++, y++) { + uint8_t bits = tile[ty]; + for (int tx = 0, x = 0; tx < 8; tx++, bits<<=1) { + uint32_t pixel = (0 == (bits & 0x80)) ? blank : color; + for (int sx = 0; sx < scale; sx++, x++) { + SOKOL_ASSERT(dst < dst_end); + *dst++ = pixel; + } + } + } + } + } + SOKOL_ASSERT(dst == dst_end); + + // right shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int y = 0; y < dim; y++) { + uint32_t prev_color = blank; + for (int x = 0; x < dim; x++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); + + // bottom shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int x = 0; x < dim; x++) { + uint32_t prev_color = blank; + for (int y = 0; y < dim; y++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); +} + /*== MacOS/iOS ===============================================================*/ #if defined(_SAPP_APPLE) @@ -2474,10 +2879,11 @@ _SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { _sapp_init_state(desc); _sapp_macos_init_keytable(); [NSApplication sharedApplication]; - NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + // set the application dock icon as early as possible, otherwise + // the dummy icon will be visible for a short time + sapp_set_icon(&_sapp.desc.icon); _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init]; NSApp.delegate = _sapp.macos.app_dlg; - [NSApp activateIgnoringOtherApps:YES]; [NSApp run]; // NOTE: [NSApp run] never returns, instead cleanup code // must be put into applicationWillTerminate @@ -2492,7 +2898,9 @@ int main(int argc, char* argv[]) { } #endif /* SOKOL_NO_ENTRY */ -_SOKOL_PRIVATE uint32_t _sapp_macos_mod(NSEventModifierFlags f) { +_SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) { + const NSEventModifierFlags f = ev.modifierFlags; + const NSUInteger b = NSEvent.pressedMouseButtons; uint32_t m = 0; if (f & NSEventModifierFlagShift) { m |= SAPP_MODIFIER_SHIFT; @@ -2506,6 +2914,15 @@ _SOKOL_PRIVATE uint32_t _sapp_macos_mod(NSEventModifierFlags f) { if (f & NSEventModifierFlagCommand) { m |= SAPP_MODIFIER_SUPER; } + if (0 != (b & (1<<0))) { + m |= SAPP_MODIFIER_LMB; + } + if (0 != (b & (1<<1))) { + m |= SAPP_MODIFIER_RMB; + } + if (0 != (b & (1<<2))) { + m |= SAPP_MODIFIER_MMB; + } return m; } @@ -2668,6 +3085,39 @@ _SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { } } +_SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + NSDockTile* dock_tile = NSApp.dockTile; + const int wanted_width = (int) dock_tile.size.width; + const int wanted_height = (int) dock_tile.size.height; + const int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, wanted_width, wanted_height); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + + CGColorSpaceRef cg_color_space = CGColorSpaceCreateDeviceRGB(); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)img_desc->pixels.ptr, (CFIndex)img_desc->pixels.size); + CGDataProviderRef cg_data_provider = CGDataProviderCreateWithCFData(cf_data); + CGImageRef cg_img = CGImageCreate( + (size_t)img_desc->width, // width + (size_t)img_desc->height, // height + 8, // bitsPerComponent + 32, // bitsPerPixel + (size_t)img_desc->width * 4,// bytesPerRow + cg_color_space, // space + kCGImageAlphaLast | kCGImageByteOrderDefault, // bitmapInfo + cg_data_provider, // provider + NULL, // decode + false, // shouldInterpolate + kCGRenderingIntentDefault); + CFRelease(cf_data); + CGDataProviderRelease(cg_data_provider); + CGColorSpaceRelease(cg_color_space); + + NSImage* ns_image = [[NSImage alloc] initWithCGImage:cg_img size:dock_tile.size]; + dock_tile.contentView = [NSImageView imageViewWithImage:ns_image]; + [dock_tile display]; + _SAPP_OBJC_RELEASE(ns_image); + CGImageRelease(cg_img); +} + _SOKOL_PRIVATE void _sapp_macos_frame(void) { _sapp_frame(); if (_sapp.quit_requested || _sapp.quit_ordered) { @@ -2675,7 +3125,9 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { } } +// __v_ start #include "sokol_app2.h" // __v_ +// __v_ end @implementation _sapp_macos_app_delegate - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { @@ -2694,7 +3146,10 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { _sapp.framebuffer_height = _sapp.window_height; } _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; - const NSUInteger style = _sapp.desc.fullscreen ? NSWindowStyleMaskBorderless : // __v_ + const NSUInteger style = + // __v_ start + _sapp.desc.fullscreen ? NSWindowStyleMaskBorderless : // __v_ + // __v_ end NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | @@ -2710,33 +3165,31 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { _sapp.macos.window.acceptsMouseMovedEvents = YES; _sapp.macos.window.restorable = YES; + // _v__ start + _sapp.macos.window.backgroundColor = [NSColor whiteColor]; + + // Quit menu + NSMenu* menu_bar = [[NSMenu alloc] init]; + NSMenuItem* app_menu_item = [[NSMenuItem alloc] init]; + [menu_bar addItem:app_menu_item]; + NSApp.mainMenu = menu_bar; + NSMenu* app_menu = [[NSMenu alloc] init]; + NSString* window_title_as_nsstring = [NSString stringWithUTF8String:_sapp.window_title]; + // `quit_title` memory will be owned by the NSMenuItem, so no need to release it ourselves + NSString* quit_title = [@"Quit " stringByAppendingString:window_title_as_nsstring]; + NSMenuItem* quit_menu_item = [[NSMenuItem alloc] + initWithTitle:quit_title + action:@selector(terminate:) + keyEquivalent:@"q"]; + [app_menu addItem:quit_menu_item]; + app_menu_item.submenu = app_menu; + _SAPP_OBJC_RELEASE( window_title_as_nsstring ); + _SAPP_OBJC_RELEASE( app_menu ); + _SAPP_OBJC_RELEASE( app_menu_item ); + _SAPP_OBJC_RELEASE( menu_bar ); - // _v__ - _sapp.macos.window.backgroundColor = [NSColor whiteColor]; - - // Quit menu - NSMenu* menu_bar = [[NSMenu alloc] init]; -NSMenuItem* app_menu_item = [[NSMenuItem alloc] init]; -[menu_bar addItem:app_menu_item]; -NSApp.mainMenu = menu_bar; -NSMenu* app_menu = [[NSMenu alloc] init]; -NSString* window_title_as_nsstring = [NSString stringWithUTF8String:_sapp.window_title]; -// `quit_title` memory will be owned by the NSMenuItem, so no need to release it ourselves -NSString* quit_title = [@"Quit " stringByAppendingString:window_title_as_nsstring]; -NSMenuItem* quit_menu_item = [[NSMenuItem alloc] - initWithTitle:quit_title - action:@selector(terminate:) - keyEquivalent:@"q"]; -[app_menu addItem:quit_menu_item]; -app_menu_item.submenu = app_menu; -_SAPP_OBJC_RELEASE( window_title_as_nsstring ); -_SAPP_OBJC_RELEASE( app_menu ); -_SAPP_OBJC_RELEASE( app_menu_item ); -_SAPP_OBJC_RELEASE( menu_bar ); - - - // _v__ + // _v__ end _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; _sapp.macos.window.delegate = _sapp.macos.win_dlg; @@ -2799,48 +3252,55 @@ _SAPP_OBJC_RELEASE( menu_bar ); timer_obj = nil; #endif _sapp.valid = true; - // __v_ - if (!_sapp.__v_native_render) { + // __v_ start + if (!_sapp.__v_native_render) { // __v_ if (_sapp.fullscreen) { - // on GL, this already toggles a rendered frame, so set the valid flag before + /* on GL, this already toggles a rendered frame, so set the valid flag before */ [_sapp.macos.window toggleFullScreen:self]; } else { [_sapp.macos.window center]; } - } - // __v C - /////////////////////////////////////////////////////// - // Create a child view for native rendering - if (_sapp.__v_native_render) { + } // __v_ + // __v_ end + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + [NSApp activateIgnoringOtherApps:YES]; + // __v start + /////////////////////////////////////////////////////// + // Create a child view for native rendering + if (_sapp.__v_native_render) { - CGRect wRect = _sapp.macos.window.frame; - NSView *contentView =_sapp.macos.window.contentView; - CGRect cRect = contentView.frame; + CGRect wRect = _sapp.macos.window.frame; + NSView *contentView =_sapp.macos.window.contentView; + CGRect cRect = contentView.frame; - CGRect rect = CGRectMake(wRect.origin.x, wRect.origin.y, cRect.size.width, cRect.size.height); - NSWindow *overlayWindow = [[NSWindow alloc]initWithContentRect:rect - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:NO]; - //overlayWindow.backgroundColor = [NSColor whiteColor]; + CGRect rect = CGRectMake(wRect.origin.x, wRect.origin.y, cRect.size.width, cRect.size.height); + NSWindow *overlayWindow = [[NSWindow alloc]initWithContentRect:rect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + //overlayWindow.backgroundColor = [NSColor whiteColor]; - //overlayWindow.backgroundColor = [[NSColor whiteColor] colorWithAlphaComponent:0]; - [overlayWindow setOpaque:YES]; - [_sapp.macos.window setIgnoresMouseEvents:NO]; - g_view = [[MyView2 alloc] init]; - overlayWindow.contentView = g_view; + //overlayWindow.backgroundColor = [[NSColor whiteColor] colorWithAlphaComponent:0]; + [overlayWindow setOpaque:YES]; + [_sapp.macos.window setIgnoresMouseEvents:NO]; + g_view = [[MyView2 alloc] init]; + overlayWindow.contentView = g_view; - [ contentView addSubview:g_view]; + [ contentView addSubview:g_view]; //[ _sapp.macos.window addChildWindow:overlayWindow ordered:NSWindowAbove]; [_sapp.macos.window center]; } ////////////////////////////////// +// __v_ end [_sapp.macos.window makeKeyAndOrderFront:nil]; _sapp_macos_update_dimensions(); + +// __v_ start // [NSEvent setMouseCoalescingEnabled:NO]; +// __v_ end } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { @@ -2897,6 +3357,16 @@ _SAPP_OBJC_RELEASE( menu_bar ); _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); } +- (void)windowDidBecomeKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_FOCUSED); +} + +- (void)windowDidResignKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_UNFOCUSED); +} + - (void)windowDidEnterFullScreen:(NSNotification*)notification { _SOKOL_UNUSED(notification); _sapp.fullscreen = true; @@ -2910,10 +3380,10 @@ _SAPP_OBJC_RELEASE( menu_bar ); @implementation _sapp_macos_window -// __v_ +// __v_ start - (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless - (BOOL)canBecomeMainWindow { return YES; } -// __v_ +// __v_ end - (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style @@ -3042,7 +3512,9 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { _SOKOL_UNUSED(rect); /* Catch any last-moment input events */ _sapp_macos_poll_input_events(); - _sapp_macos_frame(); + @autoreleasepool { + _sapp_macos_frame(); + } #if !defined(SOKOL_METAL) [[_sapp.macos.view openGLContext] flushBuffer]; #endif @@ -3057,11 +3529,6 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - (BOOL)acceptsFirstResponder { return YES; } -- (BOOL)acceptsFirstMouse:(NSEvent *)event { - return YES; -} - - - (void)updateTrackingAreas { if (_sapp.macos.tracking_area != nil) { [self removeTrackingArea:_sapp.macos.tracking_area]; @@ -3083,46 +3550,46 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { on Windows while SetCapture is active */ if (0 == _sapp.macos.mouse_buttons) { - _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mod(event.modifierFlags)); + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); } } - (void)mouseExited:(NSEvent*)event { _sapp_macos_update_mouse(event); if (0 == _sapp.macos.mouse_buttons) { - _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mod(event.modifierFlags)); + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); } } - (void)mouseDown:(NSEvent*)event { _sapp_macos_update_mouse(event); - _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mod(event.modifierFlags)); + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event)); _sapp.macos.mouse_buttons |= (1< 0.0f) || (_sapp_absf(dy) > 0.0f)) { _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_macos_mod(event.modifierFlags); + _sapp.event.modifiers = _sapp_macos_mods(event); _sapp.event.scroll_x = dx; _sapp.event.scroll_y = dy; _sapp_call_event(&_sapp.event); @@ -3180,7 +3647,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { } - (void)keyDown:(NSEvent*)event { if (_sapp_events_enabled()) { - const uint32_t mods = _sapp_macos_mod(event.modifierFlags); + const uint32_t mods = _sapp_macos_mods(event); /* NOTE: macOS doesn't send keyUp events while the Cmd key is pressed, as a workaround, to prevent key presses from sticking we'll send a keyup event following right after the keydown if SUPER is also pressed @@ -3216,7 +3683,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, _sapp_translate_key(event.keyCode), event.isARepeat, - _sapp_macos_mod(event.modifierFlags)); + _sapp_macos_mods(event)); } - (void)flagsChanged:(NSEvent*)event { const uint32_t old_f = _sapp.macos.flags_changed_store; @@ -3244,7 +3711,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, key_code, false, - _sapp_macos_mod(event.modifierFlags)); + _sapp_macos_mods(event)); } } - (void)cursorUpdate:(NSEvent*)event { @@ -3564,7 +4031,9 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { @implementation _sapp_ios_view - (void)drawRect:(CGRect)rect { _SOKOL_UNUSED(rect); - _sapp_ios_frame(); + @autoreleasepool { + _sapp_ios_frame(); + } } - (BOOL)isOpaque { return YES; @@ -3931,11 +4400,81 @@ _SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { } } +/* JS helper functions to update browser tab favicon */ +EM_JS(void, sapp_js_clear_favicon, (void), { + var link = document.getElementById('sokol-app-favicon'); + if (link) { + document.head.removeChild(link); + } +}); + +EM_JS(void, sapp_js_set_favicon, (int w, int h, const uint8_t* pixels), { + var canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + var ctx = canvas.getContext('2d'); + var img_data = ctx.createImageData(w, h); + img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); + ctx.putImageData(img_data, 0, 0); + var new_link = document.createElement('link'); + new_link.id = 'sokol-app-favicon'; + new_link.rel = 'shortcut icon'; + new_link.href = canvas.toDataURL(); + document.head.appendChild(new_link); +}); + +_SOKOL_PRIVATE void _sapp_emsc_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + sapp_js_clear_favicon(); + // find the best matching image candidate for 16x16 pixels + int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, 16, 16); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + sapp_js_set_favicon(img_desc->width, img_desc->height, (const uint8_t*) img_desc->pixels.ptr); +} + #if defined(SOKOL_WGPU) _SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void); _SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void); #endif +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_button_mods(uint16_t buttons) { + uint32_t m = 0; + if (0 != (buttons & (1<<0))) { m |= SAPP_MODIFIER_LMB; } + if (0 != (buttons & (1<<1))) { m |= SAPP_MODIFIER_RMB; } // not a bug + if (0 != (buttons & (1<<2))) { m |= SAPP_MODIFIER_MMB; } // not a bug + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_event_mods(const EmscriptenMouseEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_key_event_mods(const EmscriptenKeyboardEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_touch_event_mods(const EmscriptenTouchEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + _SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { _SOKOL_UNUSED(event_type); _SOKOL_UNUSED(user_data); @@ -3994,6 +4533,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenU _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { _SOKOL_UNUSED(user_data); + _sapp.emsc.mouse_buttons = emsc_event->buttons; if (_sapp.mouse.locked) { _sapp.mouse.dx = (float) emsc_event->movementX; _sapp.mouse.dy = (float) emsc_event->movementY; @@ -4036,18 +4576,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE } if (type != SAPP_EVENTTYPE_INVALID) { _sapp_init_event(type); - if (emsc_event->ctrlKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; - } - if (emsc_event->shiftKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; - } - if (emsc_event->altKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_ALT; - } - if (emsc_event->metaKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; - } + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(emsc_event); if (is_button_event) { switch (emsc_event->button) { case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; @@ -4073,20 +4602,10 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE _SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) { _SOKOL_UNUSED(emsc_type); _SOKOL_UNUSED(user_data); + _sapp.emsc.mouse_buttons = emsc_event->mouse.buttons; if (_sapp_events_enabled()) { _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - if (emsc_event->mouse.ctrlKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; - } - if (emsc_event->mouse.shiftKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; - } - if (emsc_event->mouse.altKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_ALT; - } - if (emsc_event->mouse.metaKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; - } + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(&emsc_event->mouse); /* see https://github.com/floooh/sokol/issues/339 */ float scale; switch (emsc_event->deltaMode) { @@ -4104,6 +4623,127 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelE return true; } +static struct { + const char* str; + sapp_keycode code; +} _sapp_emsc_keymap[] = { + { "Backspace", SAPP_KEYCODE_BACKSPACE }, + { "Tab", SAPP_KEYCODE_TAB }, + { "Enter", SAPP_KEYCODE_ENTER }, + { "ShiftLeft", SAPP_KEYCODE_LEFT_SHIFT }, + { "ShiftRight", SAPP_KEYCODE_RIGHT_SHIFT }, + { "ControlLeft", SAPP_KEYCODE_LEFT_CONTROL }, + { "ControlRight", SAPP_KEYCODE_RIGHT_CONTROL }, + { "AltLeft", SAPP_KEYCODE_LEFT_ALT }, + { "AltRight", SAPP_KEYCODE_RIGHT_ALT }, + { "Pause", SAPP_KEYCODE_PAUSE }, + { "CapsLock", SAPP_KEYCODE_CAPS_LOCK }, + { "Escape", SAPP_KEYCODE_ESCAPE }, + { "Space", SAPP_KEYCODE_SPACE }, + { "PageUp", SAPP_KEYCODE_PAGE_UP }, + { "PageDown", SAPP_KEYCODE_PAGE_DOWN }, + { "End", SAPP_KEYCODE_END }, + { "Home", SAPP_KEYCODE_HOME }, + { "ArrowLeft", SAPP_KEYCODE_LEFT }, + { "ArrowUp", SAPP_KEYCODE_UP }, + { "ArrowRight", SAPP_KEYCODE_RIGHT }, + { "ArrowDown", SAPP_KEYCODE_DOWN }, + { "PrintScreen", SAPP_KEYCODE_PRINT_SCREEN }, + { "Insert", SAPP_KEYCODE_INSERT }, + { "Delete", SAPP_KEYCODE_DELETE }, + { "Digit0", SAPP_KEYCODE_0 }, + { "Digit1", SAPP_KEYCODE_1 }, + { "Digit2", SAPP_KEYCODE_2 }, + { "Digit3", SAPP_KEYCODE_3 }, + { "Digit4", SAPP_KEYCODE_4 }, + { "Digit5", SAPP_KEYCODE_5 }, + { "Digit6", SAPP_KEYCODE_6 }, + { "Digit7", SAPP_KEYCODE_7 }, + { "Digit8", SAPP_KEYCODE_8 }, + { "Digit9", SAPP_KEYCODE_9 }, + { "KeyA", SAPP_KEYCODE_A }, + { "KeyB", SAPP_KEYCODE_B }, + { "KeyC", SAPP_KEYCODE_C }, + { "KeyD", SAPP_KEYCODE_D }, + { "KeyE", SAPP_KEYCODE_E }, + { "KeyF", SAPP_KEYCODE_F }, + { "KeyG", SAPP_KEYCODE_G }, + { "KeyH", SAPP_KEYCODE_H }, + { "KeyI", SAPP_KEYCODE_I }, + { "KeyJ", SAPP_KEYCODE_J }, + { "KeyK", SAPP_KEYCODE_K }, + { "KeyL", SAPP_KEYCODE_L }, + { "KeyM", SAPP_KEYCODE_M }, + { "KeyN", SAPP_KEYCODE_N }, + { "KeyO", SAPP_KEYCODE_O }, + { "KeyP", SAPP_KEYCODE_P }, + { "KeyQ", SAPP_KEYCODE_Q }, + { "KeyR", SAPP_KEYCODE_R }, + { "KeyS", SAPP_KEYCODE_S }, + { "KeyT", SAPP_KEYCODE_T }, + { "KeyU", SAPP_KEYCODE_U }, + { "KeyV", SAPP_KEYCODE_V }, + { "KeyW", SAPP_KEYCODE_W }, + { "KeyX", SAPP_KEYCODE_X }, + { "KeyY", SAPP_KEYCODE_Y }, + { "KeyZ", SAPP_KEYCODE_Z }, + { "MetaLeft", SAPP_KEYCODE_LEFT_SUPER }, + { "MetaRight", SAPP_KEYCODE_RIGHT_SUPER }, + { "Numpad0", SAPP_KEYCODE_KP_0 }, + { "Numpad1", SAPP_KEYCODE_KP_1 }, + { "Numpad2", SAPP_KEYCODE_KP_2 }, + { "Numpad3", SAPP_KEYCODE_KP_3 }, + { "Numpad4", SAPP_KEYCODE_KP_4 }, + { "Numpad5", SAPP_KEYCODE_KP_5 }, + { "Numpad6", SAPP_KEYCODE_KP_6 }, + { "Numpad7", SAPP_KEYCODE_KP_7 }, + { "Numpad8", SAPP_KEYCODE_KP_8 }, + { "Numpad9", SAPP_KEYCODE_KP_9 }, + { "NumpadMultiply", SAPP_KEYCODE_KP_MULTIPLY }, + { "NumpadAdd", SAPP_KEYCODE_KP_ADD }, + { "NumpadSubtract", SAPP_KEYCODE_KP_SUBTRACT }, + { "NumpadDecimal", SAPP_KEYCODE_KP_DECIMAL }, + { "NumpadDivide", SAPP_KEYCODE_KP_DIVIDE }, + { "F1", SAPP_KEYCODE_F1 }, + { "F2", SAPP_KEYCODE_F2 }, + { "F3", SAPP_KEYCODE_F3 }, + { "F4", SAPP_KEYCODE_F4 }, + { "F5", SAPP_KEYCODE_F5 }, + { "F6", SAPP_KEYCODE_F6 }, + { "F7", SAPP_KEYCODE_F7 }, + { "F8", SAPP_KEYCODE_F8 }, + { "F9", SAPP_KEYCODE_F9 }, + { "F10", SAPP_KEYCODE_F10 }, + { "F11", SAPP_KEYCODE_F11 }, + { "F12", SAPP_KEYCODE_F12 }, + { "NumLock", SAPP_KEYCODE_NUM_LOCK }, + { "ScrollLock", SAPP_KEYCODE_SCROLL_LOCK }, + { "Semicolon", SAPP_KEYCODE_SEMICOLON }, + { "Equal", SAPP_KEYCODE_EQUAL }, + { "Comma", SAPP_KEYCODE_COMMA }, + { "Minus", SAPP_KEYCODE_MINUS }, + { "Period", SAPP_KEYCODE_PERIOD }, + { "Slash", SAPP_KEYCODE_SLASH }, + { "Backquote", SAPP_KEYCODE_GRAVE_ACCENT }, + { "BracketLeft", SAPP_KEYCODE_LEFT_BRACKET }, + { "Backslash", SAPP_KEYCODE_BACKSLASH }, + { "BracketRight", SAPP_KEYCODE_RIGHT_BRACKET }, + { "Quote", SAPP_KEYCODE_GRAVE_ACCENT }, // FIXME: ??? + { 0, SAPP_KEYCODE_INVALID }, +}; + +_SOKOL_PRIVATE sapp_keycode _sapp_emsc_translate_key(const char* str) { + int i = 0; + const char* keystr; + while (( keystr = _sapp_emsc_keymap[i].str )) { + if (0 == strcmp(str, keystr)) { + return _sapp_emsc_keymap[i].code; + } + i += 1; + } + return SAPP_KEYCODE_INVALID; +} + _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) { _SOKOL_UNUSED(user_data); bool retval = true; @@ -4127,18 +4767,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard bool send_keyup_followup = false; _sapp_init_event(type); _sapp.event.key_repeat = emsc_event->repeat; - if (emsc_event->ctrlKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; - } - if (emsc_event->shiftKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; - } - if (emsc_event->altKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_ALT; - } - if (emsc_event->metaKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; - } + _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); if (type == SAPP_EVENTTYPE_CHAR) { _sapp.event.char_code = emsc_event->charCode; /* workaround to make Cmd+V work on Safari */ @@ -4147,7 +4776,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard } } else { - _sapp.event.key_code = _sapp_translate_key((int)emsc_event->keyCode); + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); /* Special hack for macOS: if the Super key is pressed, macOS doesn't send keyUp events. As a workaround, to prevent keys from "sticking", we'll send a keyup event following a keydown @@ -4267,18 +4896,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchE } if (type != SAPP_EVENTTYPE_INVALID) { _sapp_init_event(type); - if (emsc_event->ctrlKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; - } - if (emsc_event->shiftKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; - } - if (emsc_event->altKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_ALT; - } - if (emsc_event->metaKey) { - _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; - } + _sapp.event.modifiers = _sapp_emsc_touch_event_mods(emsc_event); _sapp.event.num_touches = emsc_event->numTouches; if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; @@ -4298,108 +4916,26 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchE return retval; } -_SOKOL_PRIVATE void _sapp_emsc_keytable_init(void) { - _sapp.keycodes[8] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[9] = SAPP_KEYCODE_TAB; - _sapp.keycodes[13] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[16] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[17] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[18] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[19] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[27] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[32] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[33] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[34] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[35] = SAPP_KEYCODE_END; - _sapp.keycodes[36] = SAPP_KEYCODE_HOME; - _sapp.keycodes[37] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[38] = SAPP_KEYCODE_UP; - _sapp.keycodes[39] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[40] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[45] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[46] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[48] = SAPP_KEYCODE_0; - _sapp.keycodes[49] = SAPP_KEYCODE_1; - _sapp.keycodes[50] = SAPP_KEYCODE_2; - _sapp.keycodes[51] = SAPP_KEYCODE_3; - _sapp.keycodes[52] = SAPP_KEYCODE_4; - _sapp.keycodes[53] = SAPP_KEYCODE_5; - _sapp.keycodes[54] = SAPP_KEYCODE_6; - _sapp.keycodes[55] = SAPP_KEYCODE_7; - _sapp.keycodes[56] = SAPP_KEYCODE_8; - _sapp.keycodes[57] = SAPP_KEYCODE_9; - _sapp.keycodes[59] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[64] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[65] = SAPP_KEYCODE_A; - _sapp.keycodes[66] = SAPP_KEYCODE_B; - _sapp.keycodes[67] = SAPP_KEYCODE_C; - _sapp.keycodes[68] = SAPP_KEYCODE_D; - _sapp.keycodes[69] = SAPP_KEYCODE_E; - _sapp.keycodes[70] = SAPP_KEYCODE_F; - _sapp.keycodes[71] = SAPP_KEYCODE_G; - _sapp.keycodes[72] = SAPP_KEYCODE_H; - _sapp.keycodes[73] = SAPP_KEYCODE_I; - _sapp.keycodes[74] = SAPP_KEYCODE_J; - _sapp.keycodes[75] = SAPP_KEYCODE_K; - _sapp.keycodes[76] = SAPP_KEYCODE_L; - _sapp.keycodes[77] = SAPP_KEYCODE_M; - _sapp.keycodes[78] = SAPP_KEYCODE_N; - _sapp.keycodes[79] = SAPP_KEYCODE_O; - _sapp.keycodes[80] = SAPP_KEYCODE_P; - _sapp.keycodes[81] = SAPP_KEYCODE_Q; - _sapp.keycodes[82] = SAPP_KEYCODE_R; - _sapp.keycodes[83] = SAPP_KEYCODE_S; - _sapp.keycodes[84] = SAPP_KEYCODE_T; - _sapp.keycodes[85] = SAPP_KEYCODE_U; - _sapp.keycodes[86] = SAPP_KEYCODE_V; - _sapp.keycodes[87] = SAPP_KEYCODE_W; - _sapp.keycodes[88] = SAPP_KEYCODE_X; - _sapp.keycodes[89] = SAPP_KEYCODE_Y; - _sapp.keycodes[90] = SAPP_KEYCODE_Z; - _sapp.keycodes[91] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[93] = SAPP_KEYCODE_MENU; - _sapp.keycodes[96] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[97] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[98] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[99] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[100] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[101] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[102] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[103] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[104] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[105] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[106] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[107] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[109] = SAPP_KEYCODE_KP_SUBTRACT; - _sapp.keycodes[110] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[111] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[112] = SAPP_KEYCODE_F1; - _sapp.keycodes[113] = SAPP_KEYCODE_F2; - _sapp.keycodes[114] = SAPP_KEYCODE_F3; - _sapp.keycodes[115] = SAPP_KEYCODE_F4; - _sapp.keycodes[116] = SAPP_KEYCODE_F5; - _sapp.keycodes[117] = SAPP_KEYCODE_F6; - _sapp.keycodes[118] = SAPP_KEYCODE_F7; - _sapp.keycodes[119] = SAPP_KEYCODE_F8; - _sapp.keycodes[120] = SAPP_KEYCODE_F9; - _sapp.keycodes[121] = SAPP_KEYCODE_F10; - _sapp.keycodes[122] = SAPP_KEYCODE_F11; - _sapp.keycodes[123] = SAPP_KEYCODE_F12; - _sapp.keycodes[144] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[145] = SAPP_KEYCODE_SCROLL_LOCK; - _sapp.keycodes[173] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[186] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[187] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[188] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[189] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[190] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[191] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[192] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[219] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[220] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[221] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[222] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[224] = SAPP_KEYCODE_LEFT_SUPER; +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_focus_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_blur_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_UNFOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; } #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) @@ -4577,6 +5113,8 @@ _SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb); emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_focus_cb); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_blur_cb); sapp_js_add_beforeunload_listener(); if (_sapp.clipboard.enabled) { sapp_js_add_clipboard_listener(); @@ -4606,6 +5144,8 @@ _SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0); emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); sapp_js_remove_beforeunload_listener(); if (_sapp.clipboard.enabled) { sapp_js_remove_clipboard_listener(); @@ -4669,7 +5209,6 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { _SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { _sapp_init_state(desc); sapp_js_pointer_init(&_sapp.html5_canvas_selector[1]); - _sapp_emsc_keytable_init(); double w, h; if (_sapp.desc.html5_canvas_resize) { w = (double) _sapp.desc.width; @@ -4694,6 +5233,7 @@ _SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { #endif _sapp.valid = true; _sapp_emsc_register_eventhandlers(); + sapp_set_icon(&desc->icon); /* start the frame loop */ emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0); @@ -4981,6 +5521,8 @@ _SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { #define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } +static const IID _sapp_IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89,0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c }; + static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { #if defined(__cplusplus) return self->GetBuffer(Buffer, riid, ppSurface); @@ -5097,9 +5639,9 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { /* view for the swapchain-created framebuffer */ #ifdef __cplusplus - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); #else - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &_sapp_IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); #endif SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); @@ -5552,18 +6094,18 @@ _SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { if (GetClientRect(_sapp.win32.hwnd, &rect)) { _sapp.window_width = (int)((float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale); _sapp.window_height = (int)((float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale); - const int fb_width = (int)((float)_sapp.window_width * _sapp.win32.dpi.content_scale); - const int fb_height = (int)((float)_sapp.window_height * _sapp.win32.dpi.content_scale); + int fb_width = (int)((float)_sapp.window_width * _sapp.win32.dpi.content_scale); + int fb_height = (int)((float)_sapp.window_height * _sapp.win32.dpi.content_scale); + /* prevent a framebuffer size of 0 when window is minimized */ + if (0 == fb_width) { + fb_width = 1; + } + if (0 == fb_height) { + fb_height = 1; + } if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { _sapp.framebuffer_width = fb_width; _sapp.framebuffer_height = fb_height; - /* prevent a framebuffer size of 0 when window is minimized */ - if (_sapp.framebuffer_width == 0) { - _sapp.framebuffer_width = 1; - } - if (_sapp.framebuffer_height == 0) { - _sapp.framebuffer_height = 1; - } return true; } } @@ -5588,6 +6130,16 @@ _SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { mods |= SAPP_MODIFIER_SUPER; } + const bool swapped = (TRUE == GetSystemMetrics(SM_SWAPBUTTON)); + if (GetAsyncKeyState(VK_LBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_RMB : SAPP_MODIFIER_LMB; + } + if (GetAsyncKeyState(VK_RBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_LMB : SAPP_MODIFIER_RMB; + } + if (GetAsyncKeyState(VK_MBUTTON)) { + mods |= SAPP_MODIFIER_MMB; + } return mods; } @@ -5720,6 +6272,16 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM } } break; + case WM_SETFOCUS: + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_FOCUSED); + break; + case WM_KILLFOCUS: + /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ + if (_sapp.mouse.locked) { + _sapp_win32_lock_mouse(false); + } + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UNFOCUSED); + break; case WM_SETCURSOR: if (_sapp.desc.user_cursor) { if (LOWORD(lParam) == HTCLIENT) { @@ -5926,6 +6488,17 @@ _SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); } +_SOKOL_PRIVATE void _sapp_win32_destroy_icons(void) { + if (_sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + _sapp.win32.big_icon = 0; + } + if (_sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + _sapp.win32.small_icon = 0; + } +} + _SOKOL_PRIVATE void _sapp_win32_init_console(void) { if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { BOOL con_valid = FALSE; @@ -6088,6 +6661,85 @@ _SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); } +_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { + BITMAPV5HEADER bi; + memset(&bi, 0, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = desc->width; + bi.bV5Height = -desc->height; // NOTE the '-' here to indicate that origin is top-left + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + uint8_t* target = 0; + const uint8_t* source = (const uint8_t*)desc->pixels.ptr; + + HDC dc = GetDC(NULL); + HBITMAP color = CreateDIBSection(dc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&target, NULL, (DWORD)0); + ReleaseDC(NULL, dc); + if (0 == color) { + return NULL; + } + SOKOL_ASSERT(target); + + HBITMAP mask = CreateBitmap(desc->width, desc->height, 1, 1, NULL); + if (0 == mask) { + DeleteObject(color); + return NULL; + } + + for (int i = 0; i < (desc->width*desc->height); i++) { + target[0] = source[2]; + target[1] = source[1]; + target[2] = source[0]; + target[3] = source[3]; + target += 4; + source += 4; + } + + ICONINFO icon_info; + memset(&icon_info, 0, sizeof(icon_info)); + icon_info.fIcon = true; + icon_info.xHotspot = 0; + icon_info.yHotspot = 0; + icon_info.hbmMask = mask; + icon_info.hbmColor = color; + HICON icon_handle = CreateIconIndirect(&icon_info); + DeleteObject(color); + DeleteObject(mask); + + return icon_handle; +} + +_SOKOL_PRIVATE void _sapp_win32_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + + int big_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + int sml_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + HICON big_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[big_img_index]); + HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index]); + + // if icon creation or lookup has failed for some reason, leave the currently set icon untouched + if (0 != big_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_BIG, (LPARAM) big_icon); + if (0 != _sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + } + _sapp.win32.big_icon = big_icon; + } + if (0 != sml_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_SMALL, (LPARAM) sml_icon); + if (0 != _sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + } + _sapp.win32.small_icon = sml_icon; + } +} + /* don't laugh, but this seems to be the easiest and most robust way to check if we're running on Win10 @@ -6111,6 +6763,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); _sapp_win32_init_dpi(); _sapp_win32_create_window(); + sapp_set_icon(&desc->icon); #if defined(SOKOL_D3D11) _sapp_d3d11_create_device_and_swapchain(); _sapp_d3d11_create_default_render_target(); @@ -6166,6 +6819,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { _sapp_wgl_shutdown(); #endif _sapp_win32_destroy_window(); + _sapp_win32_destroy_icons(); _sapp_win32_restore_console(); _sapp_discard_state(); } @@ -6274,6 +6928,15 @@ _SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow cons { mods |= SAPP_MODIFIER_SUPER; } + if (0 != (_sapp.uwp.mouse_buttons & (1< 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + int long_count = 0; + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &icon_desc->images[i]; + long_count += 2 + (img_desc->width * img_desc->height); + } + long* icon_data = (long*) SOKOL_CALLOC((size_t)long_count, sizeof(long)); + SOKOL_ASSERT(icon_data); + long* dst = icon_data; + for (int img_index = 0; img_index < num_images; img_index++) { + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + const uint8_t* src = (const uint8_t*) img_desc->pixels.ptr; + *dst++ = img_desc->width; + *dst++ = img_desc->height; + const int num_pixels = img_desc->width * img_desc->height; + for (int pixel_index = 0; pixel_index < num_pixels; pixel_index++) { + *dst++ = ((long)(src[pixel_index * 4 + 0]) << 16) | + ((long)(src[pixel_index * 4 + 1]) << 8) | + ((long)(src[pixel_index * 4 + 2]) << 0) | + ((long)(src[pixel_index * 4 + 3]) << 24); + } + } + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON, + XA_CARDINAL, 32, + PropModeReplace, + (unsigned char*)icon_data, + long_count); + SOKOL_FREE(icon_data); + XFlush(_sapp.x11.display); +} + _SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone); XSetWindowAttributes wa; @@ -9309,7 +10006,35 @@ _SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { return result; } -_SOKOL_PRIVATE uint32_t _sapp_x11_mod(uint32_t x11_mods) { +_SOKOL_PRIVATE uint32_t _sapp_x11_key_modifier_bit(sapp_keycode key) { + switch (key) { + case SAPP_KEYCODE_LEFT_SHIFT: + case SAPP_KEYCODE_RIGHT_SHIFT: + return SAPP_MODIFIER_SHIFT; + case SAPP_KEYCODE_LEFT_CONTROL: + case SAPP_KEYCODE_RIGHT_CONTROL: + return SAPP_MODIFIER_CTRL; + case SAPP_KEYCODE_LEFT_ALT: + case SAPP_KEYCODE_RIGHT_ALT: + return SAPP_MODIFIER_ALT; + case SAPP_KEYCODE_LEFT_SUPER: + case SAPP_KEYCODE_RIGHT_SUPER: + return SAPP_MODIFIER_SUPER; + default: + return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_button_modifier_bit(sapp_mousebutton btn) { + switch (btn) { + case SAPP_MOUSEBUTTON_LEFT: return SAPP_MODIFIER_LMB; + case SAPP_MOUSEBUTTON_RIGHT: return SAPP_MODIFIER_RMB; + case SAPP_MOUSEBUTTON_MIDDLE: return SAPP_MODIFIER_MMB; + default: return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_mods(uint32_t x11_mods) { uint32_t mods = 0; if (x11_mods & ShiftMask) { mods |= SAPP_MODIFIER_SHIFT; @@ -9323,6 +10048,15 @@ _SOKOL_PRIVATE uint32_t _sapp_x11_mod(uint32_t x11_mods) { if (x11_mods & Mod4Mask) { mods |= SAPP_MODIFIER_SUPER; } + if (x11_mods & Button1Mask) { + mods |= SAPP_MODIFIER_LMB; + } + if (x11_mods & Button2Mask) { + mods |= SAPP_MODIFIER_MMB; + } + if (x11_mods & Button3Mask) { + mods |= SAPP_MODIFIER_RMB; + } return mods; } @@ -9663,7 +10397,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { if (XIMaskIsSet(re->valuators.mask, 1)) { _sapp.mouse.dy = (float) *values; } - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xmotion.state)); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); } } XFreeEventData(_sapp.x11.display, &event->xcookie); @@ -9671,11 +10405,21 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { } } break; + case FocusIn: + // NOTE: ingnoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_FOCUSED); + } + break; case FocusOut: /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ if (_sapp.mouse.locked) { _sapp_x11_lock_mouse(false); } + // NOTE: ingnoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_UNFOCUSED); + } break; case KeyPress: { @@ -9683,7 +10427,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { const sapp_keycode key = _sapp_x11_translate_key(keycode); bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; _sapp_x11_keycodes[keycode & 0xFF] = true; - const uint32_t mods = _sapp_x11_mod(event->xkey.state); + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't set modifier bit on key down, so emulate that + mods |= _sapp_x11_key_modifier_bit(key); if (key != SAPP_KEYCODE_INVALID) { _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); } @@ -9701,7 +10447,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { const sapp_keycode key = _sapp_x11_translate_key(keycode); _sapp_x11_keycodes[keycode & 0xFF] = false; if (key != SAPP_KEYCODE_INVALID) { - const uint32_t mods = _sapp_x11_mod(event->xkey.state); + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't clear modifier bit on key up, so emulate that + mods &= ~_sapp_x11_key_modifier_bit(key); _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); } } @@ -9709,7 +10457,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { case ButtonPress: { const sapp_mousebutton btn = _sapp_x11_translate_button(event); - const uint32_t mods = _sapp_x11_mod(event->xbutton.state); + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't set modifier bit on button down, so emulate that + mods |= _sapp_x11_button_modifier_bit(btn); if (btn != SAPP_MOUSEBUTTON_INVALID) { _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); _sapp.x11.mouse_buttons |= (1 << btn); @@ -9729,7 +10479,10 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { { const sapp_mousebutton btn = _sapp_x11_translate_button(event); if (btn != SAPP_MOUSEBUTTON_INVALID) { - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, _sapp_x11_mod(event->xbutton.state)); + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't clear modifier bit on button up, so emulate that + mods &= ~_sapp_x11_button_modifier_bit(btn); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, mods); _sapp.x11.mouse_buttons &= ~(1 << btn); } } @@ -9737,12 +10490,12 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { case EnterNotify: /* don't send enter/leave events while mouse button held down */ if (0 == _sapp.x11.mouse_buttons) { - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xcrossing.state)); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); } break; case LeaveNotify: if (0 == _sapp.x11.mouse_buttons) { - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xcrossing.state)); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); } break; case MotionNotify: @@ -9756,7 +10509,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { _sapp.mouse.x = new_x; _sapp.mouse.y = new_y; _sapp.mouse.pos_valid = true; - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xmotion.state)); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); } break; case ConfigureNotify: @@ -9943,6 +10696,7 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { _sapp_glx_choose_visual(&visual, &depth); _sapp_x11_create_window(visual, depth); _sapp_glx_create_context(); + sapp_set_icon(&desc->icon); _sapp.valid = true; _sapp_x11_show_window(); if (_sapp.fullscreen) { @@ -10111,11 +10865,11 @@ SOKOL_API_IMPL bool sapp_keyboard_shown(void) { return _sapp.onscreen_keyboard_shown; } -SOKOL_APP_API_DECL bool sapp_is_fullscreen(void) { +SOKOL_API_IMPL bool sapp_is_fullscreen(void) { return _sapp.fullscreen; } -SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void) { +SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { #if defined(_SAPP_MACOS) _sapp_macos_toggle_fullscreen(); #elif defined(_SAPP_WIN32) @@ -10229,6 +10983,34 @@ SOKOL_API_IMPL void sapp_set_window_title(const char* title) { #endif } +SOKOL_API_IMPL void sapp_set_icon(const sapp_icon_desc* desc) { + SOKOL_ASSERT(desc); + if (desc->sokol_default) { + if (0 == _sapp.default_icon_pixels) { + _sapp_setup_default_icon(); + } + SOKOL_ASSERT(0 != _sapp.default_icon_pixels); + desc = &_sapp.default_icon_desc; + } + const int num_images = _sapp_icon_num_images(desc); + if (num_images == 0) { + return; + } + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + if (!_sapp_validate_icon_desc(desc, num_images)) { + return; + } + #if defined(_SAPP_MACOS) + _sapp_macos_set_icon(desc, num_images); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_icon(desc, num_images); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_icon(desc, num_images); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_icon(desc, num_images); + #endif +} + SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { SOKOL_ASSERT(_sapp.drop.enabled); return _sapp.drop.num_files; @@ -10381,6 +11163,15 @@ SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { #endif } +SOKOL_API_IMPL const void* sapp_d3d11_get_swap_chain(void) { + SOKOL_ASSERT(_sapp.valid); +#if defined(SOKOL_D3D11) + return _sapp.d3d11.swap_chain; +#else + return 0; +#endif +} + SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) { SOKOL_ASSERT(_sapp.valid); #if defined(SOKOL_D3D11) @@ -10472,4 +11263,4 @@ SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { _sapp.html5_ask_leave_site = ask; } -#endif /* SOKOL_APP_IMPL */ +#endif /* SOKOL_APP_IMPL */ \ No newline at end of file diff --git a/thirdparty/sokol/sokol_gfx.h b/thirdparty/sokol/sokol_gfx.h index 9049ae27de..6938a945d1 100644 --- a/thirdparty/sokol/sokol_gfx.h +++ b/thirdparty/sokol/sokol_gfx.h @@ -871,7 +871,8 @@ typedef struct sg_limits { int max_image_size_3d; // max width/height/depth of SG_IMAGETYPE_3D images int max_image_size_array; // max width/height of SG_IMAGETYPE_ARRAY images int max_image_array_layers; // max number of layers in SG_IMAGETYPE_ARRAY images - int max_vertex_attrs; // <= SG_MAX_VERTEX_ATTRIBUTES (only on some GLES2 impls) + int max_vertex_attrs; // <= SG_MAX_VERTEX_ATTRIBUTES or less (on some GLES2 impls) + int gl_max_vertex_uniform_vectors; // <= GL_MAX_VERTEX_UNIFORM_VECTORS (only on GL backends) } sg_limits; /* @@ -2529,7 +2530,6 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #pragma comment (lib, "user32") #pragma comment (lib, "dxgi") #pragma comment (lib, "d3d11") - #pragma comment (lib, "dxguid") #endif #endif #elif defined(SOKOL_METAL) @@ -2831,6 +2831,7 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #define GL_CLAMP_TO_BORDER 0x812D #define GL_TEXTURE_BORDER_COLOR 0x1004 #define GL_CURRENT_PROGRAM 0x8B8D + #define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB #endif #ifndef GL_UNSIGNED_INT_2_10_10_10_REV @@ -3109,7 +3110,7 @@ typedef struct { } _sg_pipeline_common_t; _SOKOL_PRIVATE void _sg_pipeline_common_init(_sg_pipeline_common_t* cmn, const sg_pipeline_desc* desc) { - SOKOL_ASSERT(desc->color_count < SG_MAX_COLOR_ATTACHMENTS); + SOKOL_ASSERT((desc->color_count >= 1) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS)); cmn->shader_id = desc->shader; cmn->index_type = desc->index_type; for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { @@ -3336,7 +3337,7 @@ typedef _sg_gl_image_t _sg_image_t; typedef struct { GLint gl_loc; sg_uniform_type type; - uint8_t count; + uint16_t count; uint16_t offset; } _sg_gl_uniform_t; @@ -3596,14 +3597,6 @@ typedef struct { HINSTANCE d3dcompiler_dll; bool d3dcompiler_dll_load_failed; pD3DCompile D3DCompile_func; - /* the following arrays are used for unbinding resources, they will always contain zeroes */ - ID3D11RenderTargetView* zero_rtvs[SG_MAX_COLOR_ATTACHMENTS]; - ID3D11Buffer* zero_vbs[SG_MAX_SHADERSTAGE_BUFFERS]; - UINT zero_vb_offsets[SG_MAX_SHADERSTAGE_BUFFERS]; - UINT zero_vb_strides[SG_MAX_SHADERSTAGE_BUFFERS]; - ID3D11Buffer* zero_cbs[SG_MAX_SHADERSTAGE_UBS]; - ID3D11ShaderResourceView* zero_srvs[SG_MAX_SHADERSTAGE_IMAGES]; - ID3D11SamplerState* zero_smps[SG_MAX_SHADERSTAGE_IMAGES]; /* global subresourcedata array for texture updates */ D3D11_SUBRESOURCE_DATA subres_data[SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS]; } _sg_d3d11_backend_t; @@ -5517,6 +5510,9 @@ _SOKOL_PRIVATE void _sg_gl_init_limits(void) { gl_int = SG_MAX_VERTEX_ATTRIBUTES; } _sg.limits.max_vertex_attrs = gl_int; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_vertex_uniform_vectors = gl_int; #if !defined(SOKOL_GLES2) if (!_sg.gl.gles2) { glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gl_int); @@ -5976,6 +5972,14 @@ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { } } +/* called from _sg_gl_destroy_pipeline() */ +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_pipeline(_sg_pipeline_t* pip) { + if (pip == _sg.gl.cache.cur_pipeline) { + _sg.gl.cache.cur_pipeline = 0; + _sg.gl.cache.cur_pipeline_id.id = SG_INVALID_ID; + } +} + _SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { if (_sg.gl.cur_context) { _SG_GL_CHECK_ERROR(); @@ -6151,6 +6155,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const s } else { glGenBuffers(1, &gl_buf); + SOKOL_ASSERT(gl_buf); _sg_gl_cache_store_buffer_binding(gl_target); _sg_gl_cache_bind_buffer(gl_target, gl_buf); glBufferData(gl_target, buf->cmn.size, 0, gl_usage); @@ -6475,7 +6480,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s } _sg_gl_uniform_t* u = &ub->uniforms[u_index]; u->type = u_desc->type; - u->count = (uint8_t) u_desc->array_count; + u->count = (uint16_t) u_desc->array_count; u->offset = (uint16_t) cur_uniform_offset; cur_uniform_offset += _sg_uniform_size(u->type, u->count); if (u_desc->name) { @@ -6598,8 +6603,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg _SOKOL_PRIVATE void _sg_gl_destroy_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); - _SOKOL_UNUSED(pip); - /* empty */ + _sg_gl_cache_invalidate_pipeline(pip); } /* @@ -6765,6 +6769,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ _SOKOL_PRIVATE void _sg_gl_destroy_pass(_sg_pass_t* pass) { SOKOL_ASSERT(pass); + SOKOL_ASSERT(pass != _sg.gl.cur_pass); _SG_GL_CHECK_ERROR(); if (0 != pass->gl.fb) { glDeleteFramebuffers(1, &pass->gl.fb); @@ -7840,6 +7845,14 @@ static inline void _sg_d3d11_Unmap(ID3D11DeviceContext* self, ID3D11Resource* pR #endif } +static inline void _sg_d3d11_ClearState(ID3D11DeviceContext* self) { + #if defined(__cplusplus) + self->ClearState(); + #else + self->lpVtbl->ClearState(self); + #endif +} + /*-- enum translation functions ----------------------------------------------*/ _SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_usage(sg_usage usg) { switch (usg) { @@ -8178,21 +8191,7 @@ _SOKOL_PRIVATE void _sg_d3d11_discard_backend(void) { _SOKOL_PRIVATE void _sg_d3d11_clear_state(void) { /* clear all the device context state, so that resource refs don't keep stuck in the d3d device context */ - _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, _sg.d3d11.zero_rtvs, NULL); - _sg_d3d11_RSSetState(_sg.d3d11.ctx, NULL); - _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, NULL, 0); - _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, NULL, NULL, 0xFFFFFFFF); - _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_BUFFERS, _sg.d3d11.zero_vbs, _sg.d3d11.zero_vb_strides, _sg.d3d11.zero_vb_offsets); - _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, NULL, DXGI_FORMAT_UNKNOWN, 0); - _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, NULL); - _sg_d3d11_VSSetShader(_sg.d3d11.ctx, NULL, NULL, 0); - _sg_d3d11_PSSetShader(_sg.d3d11.ctx, NULL, NULL, 0); - _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, _sg.d3d11.zero_cbs); - _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, _sg.d3d11.zero_cbs); - _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_srvs); - _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_srvs); - _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_smps); - _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_smps); + _sg_d3d11_ClearState(_sg.d3d11.ctx); } _SOKOL_PRIVATE void _sg_d3d11_reset_state_cache(void) { @@ -8839,6 +8838,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, _SOKOL_PRIVATE void _sg_d3d11_destroy_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); + if (pip == _sg.d3d11.cur_pipeline) { + _sg.d3d11.cur_pipeline = 0; + _sg.d3d11.cur_pipeline_id.id = SG_INVALID_ID; + } if (pip->d3d11.il) { _sg_d3d11_Release(pip->d3d11.il); } @@ -8945,6 +8948,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima _SOKOL_PRIVATE void _sg_d3d11_destroy_pass(_sg_pass_t* pass) { SOKOL_ASSERT(pass); + SOKOL_ASSERT(pass != _sg.d3d11.cur_pass); for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { if (pass->d3d11.color_atts[i].rtv) { _sg_d3d11_Release(pass->d3d11.color_atts[i].rtv); @@ -9329,13 +9333,17 @@ _SOKOL_PRIVATE MTLLoadAction _sg_mtl_load_action(sg_action a) { _SOKOL_PRIVATE MTLResourceOptions _sg_mtl_buffer_resource_options(sg_usage usg) { switch (usg) { case SG_USAGE_IMMUTABLE: + #if defined(_SG_TARGET_MACOS) + return MTLResourceStorageModeManaged; + #else return MTLResourceStorageModeShared; + #endif case SG_USAGE_DYNAMIC: case SG_USAGE_STREAM: #if defined(_SG_TARGET_MACOS) - return MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; + return MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; #else - return MTLCPUCacheModeWriteCombined; + return MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared; #endif default: SOKOL_UNREACHABLE; @@ -9981,14 +9989,10 @@ _SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) { _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES); _sg.mtl.device = (__bridge id) desc->context.metal.device; _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue]; - MTLResourceOptions res_opts = MTLResourceCPUCacheModeWriteCombined; - #if defined(_SG_TARGET_MACOS) - res_opts |= MTLResourceStorageModeManaged; - #endif for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { _sg.mtl.uniform_buffers[i] = [_sg.mtl.device newBufferWithLength:(NSUInteger)_sg.mtl.ub_size - options:res_opts + options:MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared ]; } _sg_mtl_init_caps(); @@ -10175,18 +10179,18 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, mtl_desc.arrayLength = 1; } mtl_desc.usage = MTLTextureUsageShaderRead; + MTLResourceOptions res_options = 0; if (img->cmn.usage != SG_USAGE_IMMUTABLE) { - mtl_desc.cpuCacheMode = MTLCPUCacheModeWriteCombined; + res_options |= MTLResourceCPUCacheModeWriteCombined; } #if defined(_SG_TARGET_MACOS) /* macOS: use managed textures */ - mtl_desc.resourceOptions = MTLResourceStorageModeManaged; - mtl_desc.storageMode = MTLStorageModeManaged; + res_options |= MTLResourceStorageModeManaged; #else /* iOS: use CPU/GPU shared memory */ - mtl_desc.resourceOptions = MTLResourceStorageModeShared; - mtl_desc.storageMode = MTLStorageModeShared; + res_options |= MTLResourceStorageModeShared; #endif + mtl_desc.resourceOptions = res_options; return true; } @@ -10194,11 +10198,8 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, _SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { SOKOL_ASSERT(img->cmn.render_target); _SOKOL_UNUSED(img); - /* reset the cpuCacheMode to 'default' */ - mtl_desc.cpuCacheMode = MTLCPUCacheModeDefaultCache; /* render targets are only visible to the GPU */ mtl_desc.resourceOptions = MTLResourceStorageModePrivate; - mtl_desc.storageMode = MTLStorageModePrivate; /* non-MSAA render targets are shader-readable */ mtl_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; } @@ -10206,11 +10207,8 @@ _SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt(MTLTextureDescriptor* mtl_desc, _sg_ /* initialize MTLTextureDescritor with MSAA attributes */ _SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt_msaa(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { SOKOL_ASSERT(img->cmn.sample_count > 1); - /* reset the cpuCacheMode to 'default' */ - mtl_desc.cpuCacheMode = MTLCPUCacheModeDefaultCache; /* render targets are only visible to the GPU */ mtl_desc.resourceOptions = MTLResourceStorageModePrivate; - mtl_desc.storageMode = MTLStorageModePrivate; /* MSAA render targets are not shader-readable (instead they are resolved) */ mtl_desc.usage = MTLTextureUsageRenderTarget; mtl_desc.textureType = MTLTextureType2DMultisample; @@ -10578,6 +10576,11 @@ _SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* a /* block until the oldest frame in flight has finished */ dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences]; + [_sg.mtl.cmd_buffer addCompletedHandler:^(id cmd_buffer) { + // NOTE: this code is called on a different thread! + _SOKOL_UNUSED(cmd_buffer); + dispatch_semaphore_signal(_sg.mtl.sem); + }]; } /* if this is first pass in frame, get uniform buffer base pointer */ @@ -10712,10 +10715,6 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { SOKOL_ASSERT(nil == _sg.mtl.cmd_encoder); SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); - #if defined(_SG_TARGET_MACOS) - [_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] didModifyRange:NSMakeRange(0, (NSUInteger)_sg.mtl.cur_ub_offset)]; - #endif - /* present, commit and signal semaphore when done */ id cur_drawable = nil; if (_sg.mtl.drawable_cb) { @@ -10724,11 +10723,9 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { else { cur_drawable = (__bridge id) _sg.mtl.drawable_userdata_cb(_sg.mtl.user_data); } - [_sg.mtl.cmd_buffer presentDrawable:cur_drawable]; - [_sg.mtl.cmd_buffer addCompletedHandler:^(id cmd_buffer) { - _SOKOL_UNUSED(cmd_buffer); - dispatch_semaphore_signal(_sg.mtl.sem); - }]; + if (nil != cur_drawable) { + [_sg.mtl.cmd_buffer presentDrawable:cur_drawable]; + } [_sg.mtl.cmd_buffer commit]; /* garbage-collect resources pending for release */ @@ -12288,6 +12285,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ _SOKOL_PRIVATE void _sg_wgpu_destroy_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); + if (pip == _sg.wgpu.cur_pipeline) { + _sg.wgpu.cur_pipeline = 0; + _Sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + } if (pip->wgpu.pip) { wgpuRenderPipelineRelease(pip->wgpu.pip); pip->wgpu.pip = 0; @@ -15371,6 +15372,7 @@ SOKOL_API_IMPL void sg_apply_uniforms(sg_shader_stage stage, int ub_index, const } if (!_sg.next_draw_valid) { _SG_TRACE_NOARGS(err_draw_invalid); + return; } _sg_apply_uniforms(stage, ub_index, data); _SG_TRACE_ARGS(apply_uniforms, stage, ub_index, data); @@ -15554,6 +15556,7 @@ SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { info.slot.state = img->slot.state; info.slot.res_id = img->slot.id; info.slot.ctx_id = img->slot.ctx_id; + info.upd_frame_index = img->cmn.upd_frame_index; #if defined(SOKOL_D3D11) info.num_slots = 1; info.active_slot = 0; diff --git a/thirdparty/sokol/util/sokol_fontstash.h b/thirdparty/sokol/util/sokol_fontstash.h index 9bb915fb02..fd1766cee2 100644 --- a/thirdparty/sokol/util/sokol_fontstash.h +++ b/thirdparty/sokol/util/sokol_fontstash.h @@ -112,7 +112,7 @@ sgl_load_pipeline(...); sgl_begin_triangles(); for each vertex: - sg_v2f_t2f_c1i(...); + sgl_v2f_t2f_c1i(...); sgl_end(); sgl_pop_pipeline(); sgl_disable_texture(); @@ -1645,8 +1645,8 @@ static int _sfons_render_create(void* user_ptr, int width, int height) { shd_desc.fs.byte_code = _sfons_fs_bytecode_wgpu; shd_desc.fs.byte_code_size = sizeof(_sfons_fs_bytecode_wgpu); #else - shd_desc.vs.source = _sfons_vs_src_dummy; - shd_desc.fs.source = _sfons_fs_src_dummy; + shd_desc.vs.source = _sfons_vs_source_dummy; + shd_desc.fs.source = _sfons_fs_source_dummy; #endif shd_desc.label = "sfons-shader"; sfons->shd = sg_make_shader(&shd_desc); @@ -1782,4 +1782,4 @@ SOKOL_API_IMPL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return ((uint32_t)r) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); } -#endif /* SOKOL_FONTSTASH_IMPL */ +#endif /* SOKOL_FONTSTASH_IMPL */ \ No newline at end of file diff --git a/thirdparty/sokol/util/sokol_gl.h b/thirdparty/sokol/util/sokol_gl.h index e41fc06bb1..01f1f8291a 100644 --- a/thirdparty/sokol/util/sokol_gl.h +++ b/thirdparty/sokol/util/sokol_gl.h @@ -53,8 +53,7 @@ ================= sokol_gl.h implements a subset of the OpenGLES 1.x feature set useful for when you just want to quickly render a bunch of colored triangles or - lines without having to mess with buffers and - shaders. + lines without having to mess with buffers and shaders. The current feature set is mostly useful for debug visualizations and simple UI-style 2D rendering: @@ -104,41 +103,62 @@ (via sg_setup). This is because sgl_setup() needs to create sokol-gfx resource objects. - sgl_setup() needs to know the attributes of the sokol-gfx render pass - where sokol-gl rendering will happen through the passed-in sgl_desc_t - struct: + If you're intending to render to the default pass, and also don't + want to tweak memory usage, you can just keep sgl_desc_t zero-initialized: - sg_pixel_format color_format - color pixel format of render pass - sg_pixel_format depth_format - depth pixel format of render pass - int sample_count - MSAA sample count of render pass + sgl_setup(&(sgl_desc_t*){ 0 }); - These values have the same defaults as sokol_gfx.h and sokol_app.h, - to use the default values, leave them zero-initialized. + In this case, sokol-gl will create internal sg_pipeline objects that + are compatible with the sokol-app default framebuffer. If you want + to render into a framebuffer with different pixel-format and MSAA + attributes you need to provide the matching attributes in the + sgl_setup() call: - You can adjust the maximum number of vertices and drawing commands - per frame through the members: + sgl_setup(&(sgl_desc_t*){ + .color_format = SG_PIXELFORMAT_..., + .depth_format = SG_PIXELFORMAT_..., + .sample_count = ..., + }); - int max_vertices - default is 65536 - int max_commands - default is 16384 + To reduce memory usage, or if you need to create more then the default number of + contexts, pipelines, vertices or draw commands, set the following sgl_desc_t + members: - You can adjust the size of the internal pipeline state object pool - with: - - int pipeline_pool_size - default is 64 + .context_pool_size (default: 4) + .pipeline_pool_size (default: 64) + .max_vertices (default: 64k) + .max_commands (default: 16k) Finally you can change the face winding for front-facing triangles and quads: - sg_face_winding face_winding - default is SG_FACEWINDING_CCW + .face_winding - default is SG_FACEWINDING_CCW The default winding for front faces is counter-clock-wise. This is the same as OpenGL's default, but different from sokol-gfx. + --- Optionally create additional context objects if you want to render into + multiple sokol-gfx render passes (or generally if you want to + use multiple independent sokol-gl "state buckets") + + sgl_context ctx = sgl_make_context(const sgl_context_desc_t* desc) + + For details on rendering with sokol-gl contexts, search below for + WORKING WITH CONTEXTS. + --- Optionally create pipeline-state-objects if you need render state that differs from sokol-gl's default state: sgl_pipeline pip = sgl_make_pipeline(const sg_pipeline_desc* desc) + ...this creates a pipeline object that's compatible with the currently + active context, alternatively call: + + sgl_pipeline_pip = sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc) + + ...to create a pipeline object that's compatible with an explicitly + provided context. + The similarity with sokol_gfx.h's sg_pipeline type and sg_make_pipeline() function is intended. sgl_make_pipeline() also takes a standard sokol-gfx sg_pipeline_desc object to describe the render state, but @@ -152,6 +172,11 @@ call to sgl_make_pipeline() needs to create several sokol-gfx pipeline objects (one for each primitive type). + 'depth.write_enabled' will be forced to 'false' if the context this + pipeline object is intended for has its depth pixel format set to + SG_PIXELFORMAT_NONE (which means the framebuffer this context is used + with doesn't have a depth-stencil surface). + --- if you need to destroy sgl_pipeline objects before sgl_shutdown(): sgl_destroy_pipeline(sgl_pipeline pip) @@ -185,7 +210,7 @@ ...load the default pipeline state on the top of the pipeline stack: - sgl_default_pipeline() + sgl_load_default_pipeline() ...load a specific pipeline on the top of the pipeline stack: @@ -314,18 +339,29 @@ list, or it will extend the previous draw command if no relevant state has changed since the last sgl_begin/end pair. - --- inside a sokol-gfx rendering pass, call: + --- inside a sokol-gfx rendering pass, call the sgl_draw() function + to render the currently active context: sgl_draw() - This will render everything that has been recorded since the last - call to sgl_draw() through sokol-gfx, and will 'rewind' the internal + ...or alternatively call: + + sgl_context_draw(ctx) + + ...to render an explicitly provided context. + + This will render everything that has been recorded in the context since + the last call to sgl_draw() through sokol-gfx, and will 'rewind' the internal vertex-, uniform- and command-buffers. - --- sokol-gl tracks a single internal error code which can be - queried with + --- each sokol-gl context tracks an internal error code, to query the + current error code for the currently active context call: - sgl_error_t sgl_error(void) + sgl_error_t sgl_error() + + ...alternatively with an explicit context argument: + + sgl_error_t sgl_context_error(ctx); ...which can return the following error codes: @@ -335,10 +371,87 @@ SGL_ERROR_COMMANDS_FULL - the internal command buffer is full (checked in sgl_end()) SGL_ERROR_STACK_OVERFLOW - matrix- or pipeline-stack overflow SGL_ERROR_STACK_UNDERFLOW - matrix- or pipeline-stack underflow + SGL_ERROR_NO_CONTEXT - the active context no longer exists ...if sokol-gl is in an error-state, sgl_draw() will skip any rendering, and reset the error code to SGL_NO_ERROR. + + WORKING WITH CONTEXTS: + ====================== + If you want to render to more than one sokol-gfx render pass you need to + work with additional sokol-gl context objects (one context object for + each offscreen rendering pass, in addition to the implicitly created + 'default context'. + + All sokol-gl state is tracked per context, and there is always a "current + context" (with the notable exception that the currently set context is + destroyed, more on that later). + + Using multiple contexts can also be useful if you only render in + a single pass, but want to maintain multiple independent "state buckets". + + To create new context object, call: + + sgl_context ctx = sgl_make_context(&(sgl_context_desc){ + .max_vertices = ..., // default: 64k + .max_commands = ..., // default: 16k + .color_format = ..., + .depth_format = ..., + .sample_count = ..., + }); + + The color_format, depth_format and sample_count items must be compatible + with the render pass the sgl_draw() or sgL_context_draw() function + will be called in. + + Creating a context does *not* make the context current. To do this, call: + + sgl_set_context(ctx); + + The currently active context will implicitely be used by most sokol-gl functions + which don't take an explicit context handle as argument. + + To switch back to the default context, pass the global constant SGL_DEFAULT_CONTEXT: + + sgl_set_context(SGL_DEFAULT_CONTEXT); + + ...or alternatively use the function sgl_default_context() instead of the + global constant: + + sgl_set_context(sgl_default_context()); + + To get the currently active context, call: + + sgl_context cur_ctx = sgl_get_context(); + + The following functions exist in two variants, one which use the currently + active context (set with sgl_set_context()), and another version which + takes an explicit context handle instead: + + sgl_make_pipeline() vs sgl_context_make_pipeline() + sgl_error() vs sgl_context_error(); + sgl_draw() vs sgl_context_draw(); + + Except for using the currently active context versus a provided context + handle, the two variants are exactlyidentical, e.g. the following + code sequences do the same thing: + + sgl_set_context(ctx); + sgl_pipeline pip = sgl_make_pipeline(...); + sgl_error_t err = sgl_error(); + sgl_draw(); + + vs + + sgl_pipeline pip = sgl_context_make_pipeline(ctx, ...); + sgl_error_t err = sgl_context_error(ctx); + sgl_context_draw(ctx); + + Destroying the currently active context is a 'soft error'. All following + calls which require a currently active context will silently fail, + and sgl_error() will return SGL_ERROR_NO_CONTEXT. + UNDER THE HOOD: =============== sokol_gl.h works by recording vertex data and rendering commands into @@ -359,11 +472,15 @@ What happens in: sgl_setup(): - - 3 memory buffers are allocated, one for vertex data, - one for uniform data, and one for commands - - sokol-gfx resources are created: a (dynamic) vertex buffer, - a shader object (using embedded shader source or byte code), - and an 8x8 all-white default texture + Unique resources shared by all contexts are created: + - a shader object (using embedded shader source or byte code) + - an 8x8 white default texture + The default context is created, which involves: + - 3 memory buffers are created, one for vertex data, + one for uniform data, and one for commands + - a dynamic vertex buffer is created + - the default sgl_pipeline object is created, which involves + creating 5 sg_pipeline objects One vertex is 24 bytes: - float3 position @@ -477,6 +594,9 @@ extern "C" { /* sokol_gl pipeline handle (created with sgl_make_pipeline()) */ typedef struct sgl_pipeline { uint32_t id; } sgl_pipeline; +/* a context handle (created with sgl_make_context()) */ +typedef struct sgl_context { uint32_t id; } sgl_context; + /* sgl_error_t @@ -490,31 +610,60 @@ typedef enum sgl_error_t { SGL_ERROR_COMMANDS_FULL, SGL_ERROR_STACK_OVERFLOW, SGL_ERROR_STACK_UNDERFLOW, + SGL_ERROR_NO_CONTEXT, } sgl_error_t; -typedef struct sgl_desc_t { - int max_vertices; /* size for vertex buffer */ - int max_commands; /* size of uniform- and command-buffers */ - int pipeline_pool_size; /* size of the internal pipeline pool, default is 64 */ +/* + sgl_context_desc_t + + Describes the initialization parameters of a rendering context. + Creating additional contexts is useful if you want to render + in separate sokol-gfx passes. +*/ +typedef struct sgl_context_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k sg_pixel_format color_format; sg_pixel_format depth_format; int sample_count; - sg_face_winding face_winding; /* default front face winding is CCW */ +} sgl_context_desc_t; + +typedef struct sgl_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k + int context_pool_size; // max number of contexts (including default context), default: 4 + int pipeline_pool_size; // size of internal pipeline pool, default: 64 + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_face_winding face_winding; // default: SG_FACEWINDING_CCW } sgl_desc_t; +/* the default context handle */ +static const sgl_context SGL_DEFAULT_CONTEXT = { 0x00010001 }; + /* setup/shutdown/misc */ SOKOL_GL_API_DECL void sgl_setup(const sgl_desc_t* desc); SOKOL_GL_API_DECL void sgl_shutdown(void); -SOKOL_GL_API_DECL sgl_error_t sgl_error(void); -SOKOL_GL_API_DECL void sgl_defaults(void); SOKOL_GL_API_DECL float sgl_rad(float deg); SOKOL_GL_API_DECL float sgl_deg(float rad); +SOKOL_GL_API_DECL sgl_error_t sgl_error(void); +SOKOL_GL_API_DECL sgl_error_t sgl_context_error(sgl_context ctx); + +/* context functions */ +SOKOL_GL_API_DECL sgl_context sgl_make_context(const sgl_context_desc_t* desc); +SOKOL_GL_API_DECL void sgl_destroy_context(sgl_context ctx); +SOKOL_GL_API_DECL void sgl_set_context(sgl_context ctx); +SOKOL_GL_API_DECL sgl_context sgl_get_context(void); +SOKOL_GL_API_DECL sgl_context sgl_default_context(void); /* create and destroy pipeline objects */ SOKOL_GL_API_DECL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc); SOKOL_GL_API_DECL void sgl_destroy_pipeline(sgl_pipeline pip); /* render state functions */ +SOKOL_GL_API_DECL void sgl_defaults(void); SOKOL_GL_API_DECL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left); SOKOL_GL_API_DECL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left); SOKOL_GL_API_DECL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left); @@ -524,7 +673,7 @@ SOKOL_GL_API_DECL void sgl_disable_texture(void); SOKOL_GL_API_DECL void sgl_texture(sg_image img); /* pipeline stack functions */ -SOKOL_GL_API_DECL void sgl_default_pipeline(void); +SOKOL_GL_API_DECL void sgl_load_default_pipeline(void); SOKOL_GL_API_DECL void sgl_load_pipeline(sgl_pipeline pip); SOKOL_GL_API_DECL void sgl_push_pipeline(void); SOKOL_GL_API_DECL void sgl_pop_pipeline(void); @@ -589,15 +738,18 @@ SOKOL_GL_API_DECL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float SOKOL_GL_API_DECL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba); SOKOL_GL_API_DECL void sgl_end(void); -/* render everything */ -SOKOL_GL_API_DECL void sgl_draw(void); +/* render recorded commands */ +SOKOL_GL_API_DECL void sgl_draw(); +SOKOL_GL_API_DECL void sgl_context_draw(sgl_context ctx); #ifdef __cplusplus } /* extern "C" */ /* reference-based equivalents for C++ */ inline void sgl_setup(const sgl_desc_t& desc) { return sgl_setup(&desc); } +inline sgl_context sgl_make_context(const sgl_context_desc_t& desc) { return sgl_make_context(&desc); } inline sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc& desc) { return sgl_make_pipeline(&desc); } +inline sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc& desc) { return sgl_context_make_pipeline(ctx, &desc); } #endif #endif /* SOKOL_GL_INCLUDED */ @@ -2027,6 +2179,7 @@ typedef struct { #define _SGL_INVALID_SLOT_INDEX (0) #define _SGL_MAX_STACK_DEPTH (64) +#define _SGL_DEFAULT_CONTEXT_POOL_SIZE (4) #define _SGL_DEFAULT_PIPELINE_POOL_SIZE (64) #define _SGL_DEFAULT_MAX_VERTICES (1<<16) #define _SGL_DEFAULT_MAX_COMMANDS (1<<14) @@ -2035,8 +2188,8 @@ typedef struct { #define _SGL_SLOT_MASK (_SGL_MAX_POOL_SIZE-1) typedef struct { - uint32_t init_cookie; - sgl_desc_t desc; + _sgl_slot_t slot; + sgl_context_desc_t desc; int num_vertices; int num_uniforms; @@ -2062,11 +2215,8 @@ typedef struct { /* sokol-gfx resources */ sg_buffer vbuf; - sg_image def_img; /* a default white texture */ - sg_shader shd; - sg_bindings bind; sgl_pipeline def_pip; - _sgl_pipeline_pool_t pip_pool; + sg_bindings bind; /* pipeline stack */ int pip_tos; @@ -2076,6 +2226,23 @@ typedef struct { _sgl_matrix_mode_t cur_matrix_mode; int matrix_tos[SGL_NUM_MATRIXMODES]; _sgl_matrix_t matrix_stack[SGL_NUM_MATRIXMODES][_SGL_MAX_STACK_DEPTH]; +} _sgl_context_t; + +typedef struct { + _sgl_pool_t pool; + _sgl_context_t* contexts; +} _sgl_context_pool_t; + +typedef struct { + uint32_t init_cookie; + sgl_desc_t desc; + sg_image def_img; // a default white texture + sg_shader shd; // same shader for all contexts + sgl_context def_ctx_id; + sgl_context cur_ctx_id; + _sgl_context_t* cur_ctx; // may be 0! + _sgl_pipeline_pool_t pip_pool; + _sgl_context_pool_t context_pool; } _sgl_t; static _sgl_t _sgl; @@ -2141,16 +2308,39 @@ static void _sgl_pool_free_index(_sgl_pool_t* pool, int slot_index) { SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); } +static void _sgl_reset_context(_sgl_context_t* ctx) { + SOKOL_ASSERT(ctx); + SOKOL_ASSERT(0 == ctx->vertices); + SOKOL_ASSERT(0 == ctx->uniforms); + SOKOL_ASSERT(0 == ctx->commands); + memset(ctx, 0, sizeof(_sgl_context_t)); +} + +static void _sgl_setup_context_pool(int pool_size) { + /* note: the pools here will have an additional item, since slot 0 is reserved */ + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.context_pool.pool, pool_size); + size_t pool_byte_size = sizeof(_sgl_context_t) * (size_t)_sgl.context_pool.pool.size; + _sgl.context_pool.contexts = (_sgl_context_t*) SOKOL_MALLOC(pool_byte_size); + SOKOL_ASSERT(_sgl.context_pool.contexts); + memset(_sgl.context_pool.contexts, 0, pool_byte_size); +} + +static void _sgl_discard_context_pool(void) { + SOKOL_ASSERT(0 != _sgl.context_pool.contexts); + SOKOL_FREE(_sgl.context_pool.contexts); _sgl.context_pool.contexts = 0; + _sgl_discard_pool(&_sgl.context_pool.pool); +} + static void _sgl_reset_pipeline(_sgl_pipeline_t* pip) { SOKOL_ASSERT(pip); memset(pip, 0, sizeof(_sgl_pipeline_t)); } -static void _sgl_setup_pipeline_pool(const sgl_desc_t* desc) { - SOKOL_ASSERT(desc); +static void _sgl_setup_pipeline_pool(int pool_size) { /* note: the pools here will have an additional item, since slot 0 is reserved */ - SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SGL_MAX_POOL_SIZE)); - _sgl_init_pool(&_sgl.pip_pool.pool, desc->pipeline_pool_size); + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.pip_pool.pool, pool_size); size_t pool_byte_size = sizeof(_sgl_pipeline_t) * (size_t)_sgl.pip_pool.pool.size; _sgl.pip_pool.pips = (_sgl_pipeline_t*) SOKOL_MALLOC(pool_byte_size); SOKOL_ASSERT(_sgl.pip_pool.pips); @@ -2158,6 +2348,7 @@ static void _sgl_setup_pipeline_pool(const sgl_desc_t* desc) { } static void _sgl_discard_pipeline_pool(void) { + SOKOL_ASSERT(0 != _sgl.pip_pool.pips); SOKOL_FREE(_sgl.pip_pool.pips); _sgl.pip_pool.pips = 0; _sgl_discard_pool(&_sgl.pip_pool.pool); } @@ -2211,8 +2402,7 @@ static _sgl_pipeline_t* _sgl_lookup_pipeline(uint32_t pip_id) { /* make pipeline id from uint32_t id */ static sgl_pipeline _sgl_make_pip_id(uint32_t pip_id) { - sgl_pipeline pip; - pip.id = pip_id; + sgl_pipeline pip = { pip_id }; return pip; } @@ -2229,8 +2419,8 @@ static sgl_pipeline _sgl_alloc_pipeline(void) { return res; } -static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_desc) { - SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc); +static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc && ctx_desc); /* create a new desc with 'patched' shader and pixel format state */ sg_pipeline_desc desc = *in_desc; @@ -2254,12 +2444,15 @@ static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_d desc.shader = _sgl.shd; } desc.index_type = SG_INDEXTYPE_NONE; - desc.sample_count = _sgl.desc.sample_count; + desc.sample_count = ctx_desc->sample_count; if (desc.face_winding == _SG_FACEWINDING_DEFAULT) { desc.face_winding = _sgl.desc.face_winding; } - desc.depth.pixel_format = _sgl.desc.depth_format; - desc.colors[0].pixel_format = _sgl.desc.color_format; + desc.depth.pixel_format = ctx_desc->depth_format; + if (ctx_desc->depth_format == SG_PIXELFORMAT_NONE) { + desc.depth.write_enabled = false; + } + desc.colors[0].pixel_format = ctx_desc->color_format; if (desc.colors[0].write_mask == _SG_COLORMASK_DEFAULT) { desc.colors[0].write_mask = SG_COLORMASK_RGB; } @@ -2300,11 +2493,11 @@ static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_d } } -static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc) { - SOKOL_ASSERT(desc); +static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT(desc && ctx_desc); sgl_pipeline pip_id = _sgl_alloc_pipeline(); if (pip_id.id != SG_INVALID_ID) { - _sgl_init_pipeline(pip_id, desc); + _sgl_init_pipeline(pip_id, desc, ctx_desc); } else { SOKOL_LOG("sokol_gl.h: pipeline pool exhausted!"); @@ -2315,11 +2508,13 @@ static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc) { static void _sgl_destroy_pipeline(sgl_pipeline pip_id) { _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); if (pip) { + sg_push_debug_group("sokol-gl"); for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { if (i != SGL_PRIMITIVETYPE_QUADS) { sg_destroy_pipeline(pip->pip[i]); } } + sg_pop_debug_group(); _sgl_reset_pipeline(pip); _sgl_pool_free_index(&_sgl.pip_pool.pool, _sgl_slot_index(pip_id.id)); } @@ -2336,57 +2531,185 @@ static sg_pipeline _sgl_get_pipeline(sgl_pipeline pip_id, _sgl_primitive_type_t } } -static inline void _sgl_begin(_sgl_primitive_type_t mode) { - _sgl.in_begin = true; - _sgl.base_vertex = _sgl.cur_vertex; - _sgl.vtx_count = 0; - _sgl.cur_prim_type = mode; +// get context pointer without id-check +static _sgl_context_t* _sgl_context_at(uint32_t ctx_id) { + SOKOL_ASSERT(SG_INVALID_ID != ctx_id); + int slot_index = _sgl_slot_index(ctx_id); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.context_pool.pool.size)); + return &_sgl.context_pool.contexts[slot_index]; } -static void _sgl_rewind(void) { - _sgl.base_vertex = 0; - _sgl.cur_vertex = 0; - _sgl.cur_uniform = 0; - _sgl.cur_command = 0; - _sgl.error = SGL_NO_ERROR; - _sgl.matrix_dirty = true; +// get context pointer with id-check, returns 0 if no match +static _sgl_context_t* _sgl_lookup_context(uint32_t ctx_id) { + if (SG_INVALID_ID != ctx_id) { + _sgl_context_t* ctx = _sgl_context_at(ctx_id); + if (ctx->slot.id == ctx_id) { + return ctx; + } + } + return 0; } -static inline _sgl_vertex_t* _sgl_next_vertex(void) { - if (_sgl.cur_vertex < _sgl.num_vertices) { - return &_sgl.vertices[_sgl.cur_vertex++]; +// make context id from uint32_t id +static sgl_context _sgl_make_ctx_id(uint32_t ctx_id) { + sgl_context ctx = { ctx_id }; + return ctx; +} + +static sgl_context _sgl_alloc_context(void) { + sgl_context res; + int slot_index = _sgl_pool_alloc_index(&_sgl.context_pool.pool); + if (_SGL_INVALID_SLOT_INDEX != slot_index) { + res = _sgl_make_ctx_id(_sgl_slot_alloc(&_sgl.context_pool.pool, &_sgl.context_pool.contexts[slot_index].slot, slot_index)); } else { - _sgl.error = SGL_ERROR_VERTICES_FULL; + // pool is exhausted + res = _sgl_make_ctx_id(SG_INVALID_ID); + } + return res; +} + +// return sgl_context_desc_t with patched defaults +static sgl_context_desc_t _sgl_context_desc_defaults(const sgl_context_desc_t* desc) { + sgl_context_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + return res; +} + +static void _sgl_identity(_sgl_matrix_t*); +static void _sgl_init_context(sgl_context ctx_id, const sgl_context_desc_t* in_desc) { + SOKOL_ASSERT((ctx_id.id != SG_INVALID_ID) && in_desc); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + SOKOL_ASSERT(ctx); + ctx->desc = _sgl_context_desc_defaults(in_desc); + ctx->cur_img = _sgl.def_img; + + // allocate buffers and pools + ctx->num_vertices = ctx->desc.max_vertices; + ctx->num_commands = ctx->num_uniforms = ctx->desc.max_commands; + ctx->vertices = (_sgl_vertex_t*) SOKOL_MALLOC((size_t)ctx->num_vertices * sizeof(_sgl_vertex_t)); + SOKOL_ASSERT(ctx->vertices); + ctx->uniforms = (_sgl_uniform_t*) SOKOL_MALLOC((size_t)ctx->num_uniforms * sizeof(_sgl_uniform_t)); + SOKOL_ASSERT(ctx->uniforms); + ctx->commands = (_sgl_command_t*) SOKOL_MALLOC((size_t)ctx->num_commands * sizeof(_sgl_command_t)); + SOKOL_ASSERT(ctx->commands); + + // create sokol-gfx resource objects + sg_push_debug_group("sokol-gl"); + + sg_buffer_desc vbuf_desc; + memset(&vbuf_desc, 0, sizeof(vbuf_desc)); + vbuf_desc.size = (size_t)ctx->num_vertices * sizeof(_sgl_vertex_t); + vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vbuf_desc.usage = SG_USAGE_STREAM; + vbuf_desc.label = "sgl-vertex-buffer"; + ctx->vbuf = sg_make_buffer(&vbuf_desc); + SOKOL_ASSERT(SG_INVALID_ID != ctx->vbuf.id); + + sg_pipeline_desc def_pip_desc; + memset(&def_pip_desc, 0, sizeof(def_pip_desc)); + def_pip_desc.depth.write_enabled = true; + ctx->def_pip = _sgl_make_pipeline(&def_pip_desc, &ctx->desc); + sg_pop_debug_group(); + + // default state + ctx->rgba = 0xFFFFFFFF; + for (int i = 0; i < SGL_NUM_MATRIXMODES; i++) { + _sgl_identity(&ctx->matrix_stack[i][0]); + } + ctx->pip_stack[0] = ctx->def_pip; + ctx->matrix_dirty = true; +} + +static sgl_context _sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(desc); + sgl_context ctx_id = _sgl_alloc_context(); + if (ctx_id.id != SG_INVALID_ID) { + _sgl_init_context(ctx_id, desc); + } + else { + SOKOL_LOG("sokol_gl.h: context pool exhausted!"); + } + return ctx_id; +} + +static void _sgl_destroy_context(sgl_context ctx_id) { + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + SOKOL_ASSERT(ctx->vertices); + SOKOL_ASSERT(ctx->uniforms); + SOKOL_ASSERT(ctx->commands); + + SOKOL_FREE(ctx->vertices); + SOKOL_FREE(ctx->uniforms); + SOKOL_FREE(ctx->commands); + + ctx->vertices = 0; + ctx->uniforms = 0; + ctx->commands = 0; + + sg_push_debug_group("sokol-gl"); + sg_destroy_buffer(ctx->vbuf); + _sgl_destroy_pipeline(ctx->def_pip); + sg_pop_debug_group(); + + _sgl_reset_context(ctx); + _sgl_pool_free_index(&_sgl.context_pool.pool, _sgl_slot_index(ctx_id.id)); + } +} + +static inline void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) { + ctx->in_begin = true; + ctx->base_vertex = ctx->cur_vertex; + ctx->vtx_count = 0; + ctx->cur_prim_type = mode; +} + +static void _sgl_rewind(_sgl_context_t* ctx) { + ctx->base_vertex = 0; + ctx->cur_vertex = 0; + ctx->cur_uniform = 0; + ctx->cur_command = 0; + ctx->error = SGL_NO_ERROR; + ctx->matrix_dirty = true; +} + +static inline _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) { + if (ctx->cur_vertex < ctx->num_vertices) { + return &ctx->vertices[ctx->cur_vertex++]; + } + else { + ctx->error = SGL_ERROR_VERTICES_FULL; return 0; } } -static inline _sgl_uniform_t* _sgl_next_uniform(void) { - if (_sgl.cur_uniform < _sgl.num_uniforms) { - return &_sgl.uniforms[_sgl.cur_uniform++]; +static inline _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) { + if (ctx->cur_uniform < ctx->num_uniforms) { + return &ctx->uniforms[ctx->cur_uniform++]; } else { - _sgl.error = SGL_ERROR_UNIFORMS_FULL; + ctx->error = SGL_ERROR_UNIFORMS_FULL; return 0; } } -static inline _sgl_command_t* _sgl_prev_command(void) { - if (_sgl.cur_command > 0) { - return &_sgl.commands[_sgl.cur_command - 1]; +static inline _sgl_command_t* _sgl_prev_command(_sgl_context_t* ctx) { + if (ctx->cur_command > 0) { + return &ctx->commands[ctx->cur_command - 1]; } else { return 0; } } -static inline _sgl_command_t* _sgl_next_command(void) { - if (_sgl.cur_command < _sgl.num_commands) { - return &_sgl.commands[_sgl.cur_command++]; +static inline _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) { + if (ctx->cur_command < ctx->num_commands) { + return &ctx->commands[ctx->cur_command++]; } else { - _sgl.error = SGL_ERROR_COMMANDS_FULL; + ctx->error = SGL_ERROR_COMMANDS_FULL; return 0; } } @@ -2409,26 +2732,26 @@ static inline uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) { return _sgl_pack_rgbab(r_u8, g_u8, b_u8, a_u8); } -static inline void _sgl_vtx(float x, float y, float z, float u, float v, uint32_t rgba) { - SOKOL_ASSERT(_sgl.in_begin); +static inline void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, float v, uint32_t rgba) { + SOKOL_ASSERT(ctx->in_begin); _sgl_vertex_t* vtx; /* handle non-native primitive types */ - if ((_sgl.cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((_sgl.vtx_count & 3) == 3)) { + if ((ctx->cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((ctx->vtx_count & 3) == 3)) { /* for quads, before writing the last quad vertex, reuse the first and third vertex to start the second triangle in the quad */ - vtx = _sgl_next_vertex(); + vtx = _sgl_next_vertex(ctx); if (vtx) { *vtx = *(vtx - 3); } - vtx = _sgl_next_vertex(); + vtx = _sgl_next_vertex(ctx); if (vtx) { *vtx = *(vtx - 2); } } - vtx = _sgl_next_vertex(); + vtx = _sgl_next_vertex(ctx); if (vtx) { vtx->pos[0] = x; vtx->pos[1] = y; vtx->pos[2] = z; vtx->uv[0] = u; vtx->uv[1] = v; vtx->rgba = rgba; } - _sgl.vtx_count++; + ctx->vtx_count++; } static void _sgl_identity(_sgl_matrix_t* m) { @@ -2622,60 +2945,40 @@ static void _sgl_lookat(_sgl_matrix_t* dst, } /* current top-of-stack projection matrix */ -static inline _sgl_matrix_t* _sgl_matrix_projection(void) { - return &_sgl.matrix_stack[SGL_MATRIXMODE_PROJECTION][_sgl.matrix_tos[SGL_MATRIXMODE_PROJECTION]]; +static inline _sgl_matrix_t* _sgl_matrix_projection(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_PROJECTION][ctx->matrix_tos[SGL_MATRIXMODE_PROJECTION]]; } /* get top-of-stack modelview matrix */ -static inline _sgl_matrix_t* _sgl_matrix_modelview(void) { - return &_sgl.matrix_stack[SGL_MATRIXMODE_MODELVIEW][_sgl.matrix_tos[SGL_MATRIXMODE_MODELVIEW]]; +static inline _sgl_matrix_t* _sgl_matrix_modelview(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_MODELVIEW][ctx->matrix_tos[SGL_MATRIXMODE_MODELVIEW]]; } /* get top-of-stack texture matrix */ -static inline _sgl_matrix_t* _sgl_matrix_texture(void) { - return &_sgl.matrix_stack[SGL_MATRIXMODE_TEXTURE][_sgl.matrix_tos[SGL_MATRIXMODE_TEXTURE]]; +static inline _sgl_matrix_t* _sgl_matrix_texture(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_TEXTURE][ctx->matrix_tos[SGL_MATRIXMODE_TEXTURE]]; } /* get pointer to current top-of-stack of current matrix mode */ -static inline _sgl_matrix_t* _sgl_matrix(void) { - return &_sgl.matrix_stack[_sgl.cur_matrix_mode][_sgl.matrix_tos[_sgl.cur_matrix_mode]]; +static inline _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) { + return &ctx->matrix_stack[ctx->cur_matrix_mode][ctx->matrix_tos[ctx->cur_matrix_mode]]; } -/*== PUBLIC FUNCTIONS ========================================================*/ -SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { - SOKOL_ASSERT(desc); - memset(&_sgl, 0, sizeof(_sgl)); - _sgl.init_cookie = _SGL_INIT_COOKIE; - _sgl.desc = *desc; - _sgl.desc.pipeline_pool_size = _sgl_def(_sgl.desc.pipeline_pool_size, _SGL_DEFAULT_PIPELINE_POOL_SIZE); - _sgl.desc.max_vertices = _sgl_def(_sgl.desc.max_vertices, _SGL_DEFAULT_MAX_VERTICES); - _sgl.desc.max_commands = _sgl_def(_sgl.desc.max_commands, _SGL_DEFAULT_MAX_COMMANDS); - _sgl.desc.face_winding = _sgl_def(_sgl.desc.face_winding, SG_FACEWINDING_CCW); +// return sg_context_desc_t with patched defaults +static sgl_desc_t _sgl_desc_defaults(const sgl_desc_t* desc) { + sgl_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + res.context_pool_size = _sgl_def(desc->context_pool_size, _SGL_DEFAULT_CONTEXT_POOL_SIZE); + res.pipeline_pool_size = _sgl_def(desc->pipeline_pool_size, _SGL_DEFAULT_PIPELINE_POOL_SIZE); + res.face_winding = _sgl_def(desc->face_winding, SG_FACEWINDING_CCW); + return res; +} - /* allocate buffers and pools */ - _sgl.num_vertices = _sgl.desc.max_vertices; - _sgl.num_uniforms = _sgl.desc.max_commands; - _sgl.num_commands = _sgl.num_uniforms; - _sgl.vertices = (_sgl_vertex_t*) SOKOL_MALLOC((size_t)_sgl.num_vertices * sizeof(_sgl_vertex_t)); - SOKOL_ASSERT(_sgl.vertices); - _sgl.uniforms = (_sgl_uniform_t*) SOKOL_MALLOC((size_t)_sgl.num_uniforms * sizeof(_sgl_uniform_t)); - SOKOL_ASSERT(_sgl.uniforms); - _sgl.commands = (_sgl_command_t*) SOKOL_MALLOC((size_t)_sgl.num_commands * sizeof(_sgl_command_t)); - SOKOL_ASSERT(_sgl.commands); - _sgl_setup_pipeline_pool(&_sgl.desc); - - /* create sokol-gfx resource objects */ +// create resources which are shared between all contexts +static void _sgl_setup_common(void) { sg_push_debug_group("sokol-gl"); - sg_buffer_desc vbuf_desc; - memset(&vbuf_desc, 0, sizeof(vbuf_desc)); - vbuf_desc.size = (size_t)_sgl.num_vertices * sizeof(_sgl_vertex_t); - vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; - vbuf_desc.usage = SG_USAGE_STREAM; - vbuf_desc.label = "sgl-vertex-buffer"; - _sgl.vbuf = sg_make_buffer(&vbuf_desc); - SOKOL_ASSERT(SG_INVALID_ID != _sgl.vbuf.id); - uint32_t pixels[64]; for (int i = 0; i < 64; i++) { pixels[i] = 0xFFFFFFFF; @@ -2693,8 +2996,8 @@ SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { img_desc.label = "sgl-default-texture"; _sgl.def_img = sg_make_image(&img_desc); SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_img.id); - _sgl.cur_img = _sgl.def_img; + // one shader for all contexts sg_shader_desc shd_desc; memset(&shd_desc, 0, sizeof(shd_desc)); shd_desc.attrs[0].name = "position"; @@ -2745,510 +3048,38 @@ SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_wgpu); shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_wgpu); #else - shd_desc.vs.source = _sgl_vs_src_dummy; - shd_desc.fs.source = _sgl_fs_src_dummy; + shd_desc.vs.source = _sgl_vs_source_dummy; + shd_desc.fs.source = _sgl_fs_source_dummy; #endif _sgl.shd = sg_make_shader(&shd_desc); SOKOL_ASSERT(SG_INVALID_ID != _sgl.shd.id); - - /* create default pipeline object */ - sg_pipeline_desc def_pip_desc; - memset(&def_pip_desc, 0, sizeof(def_pip_desc)); - def_pip_desc.depth.write_enabled = true; - _sgl.def_pip = _sgl_make_pipeline(&def_pip_desc); sg_pop_debug_group(); - - /* default state */ - _sgl.rgba = 0xFFFFFFFF; - for (int i = 0; i < SGL_NUM_MATRIXMODES; i++) { - _sgl_identity(&_sgl.matrix_stack[i][0]); - } - _sgl.pip_stack[0] = _sgl.def_pip; - _sgl.matrix_dirty = true; } -SOKOL_API_IMPL void sgl_shutdown(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_FREE(_sgl.vertices); _sgl.vertices = 0; - SOKOL_FREE(_sgl.uniforms); _sgl.uniforms = 0; - SOKOL_FREE(_sgl.commands); _sgl.commands = 0; +// discard resources which are shared between all contexts +static void _sgl_discard_common(void) { sg_push_debug_group("sokol-gl"); - sg_destroy_buffer(_sgl.vbuf); sg_destroy_image(_sgl.def_img); sg_destroy_shader(_sgl.shd); - for (int i = 0; i < _sgl.pip_pool.pool.size; i++) { - _sgl_pipeline_t* pip = &_sgl.pip_pool.pips[i]; - _sgl_destroy_pipeline(_sgl_make_pip_id(pip->slot.id)); - } sg_pop_debug_group(); - _sgl_discard_pipeline_pool(); - _sgl.init_cookie = 0; } -SOKOL_API_IMPL sgl_error_t sgl_error(void) { - return _sgl.error; +static bool _sgl_is_default_context(sgl_context ctx_id) { + return ctx_id.id == SGL_DEFAULT_CONTEXT.id; } -SOKOL_API_IMPL float sgl_rad(float deg) { - return (deg * (float)M_PI) / 180.0f; -} - -SOKOL_API_IMPL float sgl_deg(float rad) { - return (rad * 180.0f) / (float)M_PI; -} - -SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - return _sgl_make_pipeline(desc); -} - -SOKOL_API_IMPL void sgl_destroy_pipeline(sgl_pipeline pip_id) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl_destroy_pipeline(pip_id); -} - -SOKOL_API_IMPL void sgl_load_pipeline(sgl_pipeline pip_id) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT((_sgl.pip_tos >= 0) && (_sgl.pip_tos < _SGL_MAX_STACK_DEPTH)); - _sgl.pip_stack[_sgl.pip_tos] = pip_id; -} - -SOKOL_API_IMPL void sgl_default_pipeline(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT((_sgl.pip_tos >= 0) && (_sgl.pip_tos < _SGL_MAX_STACK_DEPTH)); - _sgl.pip_stack[_sgl.pip_tos] = _sgl.def_pip; -} - -SOKOL_API_IMPL void sgl_push_pipeline(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - if (_sgl.pip_tos < (_SGL_MAX_STACK_DEPTH - 1)) { - _sgl.pip_tos++; - _sgl.pip_stack[_sgl.pip_tos] = _sgl.pip_stack[_sgl.pip_tos-1]; - } - else { - _sgl.error = SGL_ERROR_STACK_OVERFLOW; - } -} - -SOKOL_API_IMPL void sgl_pop_pipeline(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - if (_sgl.pip_tos > 0) { - _sgl.pip_tos--; - } - else { - _sgl.error = SGL_ERROR_STACK_UNDERFLOW; - } -} - -SOKOL_API_IMPL void sgl_defaults(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl.u = 0.0f; _sgl.v = 0.0f; - _sgl.rgba = 0xFFFFFFFF; - _sgl.texturing_enabled = false; - _sgl.cur_img = _sgl.def_img; - sgl_default_pipeline(); - _sgl_identity(_sgl_matrix_texture()); - _sgl_identity(_sgl_matrix_modelview()); - _sgl_identity(_sgl_matrix_projection()); - _sgl.cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; - _sgl.matrix_dirty = true; -} - -SOKOL_API_IMPL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_command_t* cmd = _sgl_next_command(); - if (cmd) { - cmd->cmd = SGL_COMMAND_VIEWPORT; - cmd->args.viewport.x = x; - cmd->args.viewport.y = y; - cmd->args.viewport.w = w; - cmd->args.viewport.h = h; - cmd->args.viewport.origin_top_left = origin_top_left; - } -} - -SOKOL_API_IMPL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) { - sgl_viewport((int)x, (int)y, (int)w, (int)h, origin_top_left); -} - -SOKOL_API_IMPL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_command_t* cmd = _sgl_next_command(); - if (cmd) { - cmd->cmd = SGL_COMMAND_SCISSOR_RECT; - cmd->args.scissor_rect.x = x; - cmd->args.scissor_rect.y = y; - cmd->args.scissor_rect.w = w; - cmd->args.scissor_rect.h = h; - cmd->args.scissor_rect.origin_top_left = origin_top_left; - } -} - -SOKOL_API_IMPL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) { - sgl_scissor_rect((int)x, (int)y, (int)w, (int)h, origin_top_left); -} - -SOKOL_API_IMPL void sgl_enable_texture(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl.texturing_enabled = true; -} - -SOKOL_API_IMPL void sgl_disable_texture(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl.texturing_enabled = false; -} - -SOKOL_API_IMPL void sgl_texture(sg_image img) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - if (SG_INVALID_ID != img.id) { - _sgl.cur_img = img; - } - else { - _sgl.cur_img = _sgl.def_img; - } -} - -SOKOL_API_IMPL void sgl_begin_points(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_POINTS); -} - -SOKOL_API_IMPL void sgl_begin_lines(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_LINES); -} - -SOKOL_API_IMPL void sgl_begin_line_strip(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_LINE_STRIP); -} - -SOKOL_API_IMPL void sgl_begin_triangles(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_TRIANGLES); -} - -SOKOL_API_IMPL void sgl_begin_triangle_strip(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_TRIANGLE_STRIP); -} - -SOKOL_API_IMPL void sgl_begin_quads(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(!_sgl.in_begin); - _sgl_begin(SGL_PRIMITIVETYPE_QUADS); -} - -SOKOL_API_IMPL void sgl_end(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT(_sgl.in_begin); - SOKOL_ASSERT(_sgl.cur_vertex >= _sgl.base_vertex); - _sgl.in_begin = false; - bool matrix_dirty = _sgl.matrix_dirty; - if (matrix_dirty) { - _sgl.matrix_dirty = false; - _sgl_uniform_t* uni = _sgl_next_uniform(); - if (uni) { - _sgl_matmul4(&uni->mvp, _sgl_matrix_projection(), _sgl_matrix_modelview()); - uni->tm = *_sgl_matrix_texture(); - } - } - /* check if command can be merged with previous command */ - sg_pipeline pip = _sgl_get_pipeline(_sgl.pip_stack[_sgl.pip_tos], _sgl.cur_prim_type); - sg_image img = _sgl.texturing_enabled ? _sgl.cur_img : _sgl.def_img; - _sgl_command_t* prev_cmd = _sgl_prev_command(); - bool merge_cmd = false; - if (prev_cmd) { - if ((prev_cmd->cmd == SGL_COMMAND_DRAW) && - (_sgl.cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) && - (_sgl.cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) && - !matrix_dirty && - (prev_cmd->args.draw.img.id == img.id) && - (prev_cmd->args.draw.pip.id == pip.id)) - { - merge_cmd = true; - } - } - if (merge_cmd) { - /* draw command can be merged with the previous command */ - prev_cmd->args.draw.num_vertices += _sgl.cur_vertex - _sgl.base_vertex; - } - else { - /* append a new draw command */ - _sgl_command_t* cmd = _sgl_next_command(); - if (cmd) { - SOKOL_ASSERT(_sgl.cur_uniform > 0); - cmd->cmd = SGL_COMMAND_DRAW; - cmd->args.draw.img = img; - cmd->args.draw.pip = _sgl_get_pipeline(_sgl.pip_stack[_sgl.pip_tos], _sgl.cur_prim_type); - cmd->args.draw.base_vertex = _sgl.base_vertex; - cmd->args.draw.num_vertices = _sgl.cur_vertex - _sgl.base_vertex; - cmd->args.draw.uniform_index = _sgl.cur_uniform - 1; - } - } -} - -SOKOL_API_IMPL void sgl_t2f(float u, float v) { - _sgl.u = u; _sgl.v = v; -} - -SOKOL_API_IMPL void sgl_c3f(float r, float g, float b) { - _sgl.rgba = _sgl_pack_rgbaf(r, g, b, 1.0f); -} - -SOKOL_API_IMPL void sgl_c4f(float r, float g, float b, float a) { - _sgl.rgba = _sgl_pack_rgbaf(r, g, b, a); -} - -SOKOL_API_IMPL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b) { - _sgl.rgba = _sgl_pack_rgbab(r, g, b, 255); -} - -SOKOL_API_IMPL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - _sgl.rgba = _sgl_pack_rgbab(r, g, b, a); -} - -SOKOL_API_IMPL void sgl_c1i(uint32_t rgba) { - _sgl.rgba = rgba; -} - -SOKOL_API_IMPL void sgl_v2f(float x, float y) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl.rgba); -} - -SOKOL_API_IMPL void sgl_v3f(float x, float y, float z) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl.rgba); -} - -SOKOL_API_IMPL void sgl_v2f_t2f(float x, float y, float u, float v) { - _sgl_vtx(x, y, 0.0f, u, v, _sgl.rgba); -} - -SOKOL_API_IMPL void sgl_v3f_t2f(float x, float y, float z, float u, float v) { - _sgl_vtx(x, y, z, u, v, _sgl.rgba); -} - -SOKOL_API_IMPL void sgl_v2f_c3f(float x, float y, float r, float g, float b) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, 1.0f)); -} - -SOKOL_API_IMPL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, 255)); -} - -SOKOL_API_IMPL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v2f_c1i(float x, float y, uint32_t rgba) { - _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, rgba); -} - -SOKOL_API_IMPL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, 1.0f)); -} - -SOKOL_API_IMPL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, 255)); -} - -SOKOL_API_IMPL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba) { - _sgl_vtx(x, y, z, _sgl.u, _sgl.v, rgba); -} - -SOKOL_API_IMPL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b) { - _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); -} - -SOKOL_API_IMPL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b) { - _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, 255)); -} - -SOKOL_API_IMPL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a) { - _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba) { - _sgl_vtx(x, y, 0.0f, u, v, rgba); -} - -SOKOL_API_IMPL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b) { - _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); -} - -SOKOL_API_IMPL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b) { - _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbab(r, g, b, 255)); -} - -SOKOL_API_IMPL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a) { - _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbab(r, g, b, a)); -} - -SOKOL_API_IMPL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba) { - _sgl_vtx(x, y, z, u, v, rgba); -} - -SOKOL_API_IMPL void sgl_matrix_mode_modelview(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; -} - -SOKOL_API_IMPL void sgl_matrix_mode_projection(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.cur_matrix_mode = SGL_MATRIXMODE_PROJECTION; -} - -SOKOL_API_IMPL void sgl_matrix_mode_texture(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.cur_matrix_mode = SGL_MATRIXMODE_TEXTURE; -} - -SOKOL_API_IMPL void sgl_load_identity(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_identity(_sgl_matrix()); -} - -SOKOL_API_IMPL void sgl_load_matrix(const float m[16]) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - memcpy(&_sgl_matrix()->v[0][0], &m[0], 64); -} - -SOKOL_API_IMPL void sgl_load_transpose_matrix(const float m[16]) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_transpose(_sgl_matrix(), (const _sgl_matrix_t*) &m[0]); -} - -SOKOL_API_IMPL void sgl_mult_matrix(const float m[16]) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - const _sgl_matrix_t* m0 = (const _sgl_matrix_t*) &m[0]; - _sgl_mul(_sgl_matrix(), m0); -} - -SOKOL_API_IMPL void sgl_mult_transpose_matrix(const float m[16]) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_matrix_t m0; - _sgl_transpose(&m0, (const _sgl_matrix_t*) &m[0]); - _sgl_mul(_sgl_matrix(), &m0); -} - -SOKOL_API_IMPL void sgl_rotate(float angle_rad, float x, float y, float z) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_rotate(_sgl_matrix(), angle_rad, x, y, z); -} - -SOKOL_API_IMPL void sgl_scale(float x, float y, float z) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_scale(_sgl_matrix(), x, y, z); -} - -SOKOL_API_IMPL void sgl_translate(float x, float y, float z) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_translate(_sgl_matrix(), x, y, z); -} - -SOKOL_API_IMPL void sgl_frustum(float l, float r, float b, float t, float n, float f) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_frustum(_sgl_matrix(), l, r, b, t, n, f); -} - -SOKOL_API_IMPL void sgl_ortho(float l, float r, float b, float t, float n, float f) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_ortho(_sgl_matrix(), l, r, b, t, n, f); -} - -SOKOL_API_IMPL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_perspective(_sgl_matrix(), fov_y, aspect, z_near, z_far); -} - -SOKOL_API_IMPL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - _sgl.matrix_dirty = true; - _sgl_lookat(_sgl_matrix(), eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z); -} - -SOKOL_GL_API_DECL void sgl_push_matrix(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT((_sgl.cur_matrix_mode >= 0) && (_sgl.cur_matrix_mode < SGL_NUM_MATRIXMODES)); - _sgl.matrix_dirty = true; - if (_sgl.matrix_tos[_sgl.cur_matrix_mode] < (_SGL_MAX_STACK_DEPTH - 1)) { - const _sgl_matrix_t* src = _sgl_matrix(); - _sgl.matrix_tos[_sgl.cur_matrix_mode]++; - _sgl_matrix_t* dst = _sgl_matrix(); - *dst = *src; - } - else { - _sgl.error = SGL_ERROR_STACK_OVERFLOW; - } -} - -SOKOL_GL_API_DECL void sgl_pop_matrix(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - SOKOL_ASSERT((_sgl.cur_matrix_mode >= 0) && (_sgl.cur_matrix_mode < SGL_NUM_MATRIXMODES)); - _sgl.matrix_dirty = true; - if (_sgl.matrix_tos[_sgl.cur_matrix_mode] > 0) { - _sgl.matrix_tos[_sgl.cur_matrix_mode]--; - } - else { - _sgl.error = SGL_ERROR_STACK_UNDERFLOW; - } -} - -/* this renders the accumulated draw commands via sokol-gfx */ -SOKOL_API_IMPL void sgl_draw(void) { - SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); - if ((_sgl.error == SGL_NO_ERROR) && (_sgl.cur_vertex > 0) && (_sgl.cur_command > 0)) { +static void _sgl_draw(_sgl_context_t* ctx) { + SOKOL_ASSERT(ctx); + if ((ctx->error == SGL_NO_ERROR) && (ctx->cur_vertex > 0) && (ctx->cur_command > 0)) { uint32_t cur_pip_id = SG_INVALID_ID; uint32_t cur_img_id = SG_INVALID_ID; int cur_uniform_index = -1; sg_push_debug_group("sokol-gl"); - const sg_range range = { _sgl.vertices, (size_t)_sgl.cur_vertex * sizeof(_sgl_vertex_t) }; - sg_update_buffer(_sgl.vbuf, &range); - _sgl.bind.vertex_buffers[0] = _sgl.vbuf; - for (int i = 0; i < _sgl.cur_command; i++) { - const _sgl_command_t* cmd = &_sgl.commands[i]; + const sg_range range = { ctx->vertices, (size_t)ctx->cur_vertex * sizeof(_sgl_vertex_t) }; + sg_update_buffer(ctx->vbuf, &range); + ctx->bind.vertex_buffers[0] = ctx->vbuf; + for (int i = 0; i < ctx->cur_command; i++) { + const _sgl_command_t* cmd = &ctx->commands[i]; switch (cmd->cmd) { case SGL_COMMAND_VIEWPORT: { @@ -3273,12 +3104,12 @@ SOKOL_API_IMPL void sgl_draw(void) { cur_uniform_index = -1; } if (cur_img_id != args->img.id) { - _sgl.bind.fs_images[0] = args->img; - sg_apply_bindings(&_sgl.bind); + ctx->bind.fs_images[0] = args->img; + sg_apply_bindings(&ctx->bind); cur_img_id = args->img.id; } if (cur_uniform_index != args->uniform_index) { - const sg_range ub_range = { &_sgl.uniforms[args->uniform_index], sizeof(_sgl_uniform_t) }; + const sg_range ub_range = { &ctx->uniforms[args->uniform_index], sizeof(_sgl_uniform_t) }; sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &ub_range); cur_uniform_index = args->uniform_index; } @@ -3292,6 +3123,812 @@ SOKOL_API_IMPL void sgl_draw(void) { } sg_pop_debug_group(); } - _sgl_rewind(); + _sgl_rewind(ctx); } -#endif /* SOKOL_GL_IMPL */ + +static sgl_context_desc_t _sgl_as_context_desc(const sgl_desc_t* desc) { + sgl_context_desc_t ctx_desc; + memset(&ctx_desc, 0, sizeof(ctx_desc)); + ctx_desc.max_vertices = desc->max_vertices; + ctx_desc.max_commands = desc->max_commands; + ctx_desc.color_format = desc->color_format; + ctx_desc.depth_format = desc->depth_format; + ctx_desc.sample_count = desc->sample_count; + return ctx_desc; +} + +/*== PUBLIC FUNCTIONS ========================================================*/ +SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { + SOKOL_ASSERT(desc); + memset(&_sgl, 0, sizeof(_sgl)); + _sgl.init_cookie = _SGL_INIT_COOKIE; + _sgl.desc = _sgl_desc_defaults(desc); + _sgl_setup_pipeline_pool(_sgl.desc.pipeline_pool_size); + _sgl_setup_context_pool(_sgl.desc.context_pool_size); + _sgl_setup_common(); + const sgl_context_desc_t ctx_desc = _sgl_as_context_desc(&_sgl.desc); + _sgl.def_ctx_id = sgl_make_context(&ctx_desc); + SOKOL_ASSERT(SGL_DEFAULT_CONTEXT.id == _sgl.def_ctx_id.id); + sgl_set_context(_sgl.def_ctx_id); +} + +SOKOL_API_IMPL void sgl_shutdown(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + // contexts own a pipeline, so destroy contexts before pipelines + for (int i = 0; i < _sgl.context_pool.pool.size; i++) { + _sgl_context_t* ctx = &_sgl.context_pool.contexts[i]; + _sgl_destroy_context(_sgl_make_ctx_id(ctx->slot.id)); + } + for (int i = 0; i < _sgl.pip_pool.pool.size; i++) { + _sgl_pipeline_t* pip = &_sgl.pip_pool.pips[i]; + _sgl_destroy_pipeline(_sgl_make_pip_id(pip->slot.id)); + } + _sgl_discard_context_pool(); + _sgl_discard_pipeline_pool(); + _sgl_discard_common(); + _sgl.init_cookie = 0; +} + +SOKOL_API_IMPL sgl_error_t sgl_error(void) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return ctx->error; + } + else { + return SGL_ERROR_NO_CONTEXT; + } +} + +SOKOL_API_IMPL sgl_error_t sgl_context_error(sgl_context ctx_id) { + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return ctx->error; + } + else { + return SGL_ERROR_NO_CONTEXT; + } +} + +SOKOL_API_IMPL float sgl_rad(float deg) { + return (deg * (float)M_PI) / 180.0f; +} + +SOKOL_API_IMPL float sgl_deg(float rad) { + return (rad * 180.0f) / (float)M_PI; +} + +SOKOL_API_IMPL sgl_context sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl_make_context(desc); +} + +SOKOL_API_IMPL void sgl_destroy_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + SOKOL_LOG("sokol_gl.h: cannot destroy default context"); + return; + } + _sgl_destroy_context(ctx_id); + // re-validate the current context pointer (this will return a nullptr + // if we just destroyed the current context) + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL void sgl_set_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + _sgl.cur_ctx_id = _sgl.def_ctx_id; + } + else { + _sgl.cur_ctx_id = ctx_id; + } + // this will return null if the handle isn't valid + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL sgl_context sgl_get_context(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl.cur_ctx_id; +} + +SOKOL_API_IMPL sgl_context sgl_default_context(void) { + return SGL_DEFAULT_CONTEXT; +} + +SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } + else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } + else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL void sgl_destroy_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_destroy_pipeline(pip_id); +} + +SOKOL_API_IMPL void sgl_load_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = pip_id; +} + +SOKOL_API_IMPL void sgl_load_default_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = ctx->def_pip; +} + +SOKOL_API_IMPL void sgl_push_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos < (_SGL_MAX_STACK_DEPTH - 1)) { + ctx->pip_tos++; + ctx->pip_stack[ctx->pip_tos] = ctx->pip_stack[ctx->pip_tos-1]; + } + else { + ctx->error = SGL_ERROR_STACK_OVERFLOW; + } +} + +SOKOL_API_IMPL void sgl_pop_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos > 0) { + ctx->pip_tos--; + } + else { + ctx->error = SGL_ERROR_STACK_UNDERFLOW; + } +} + +SOKOL_API_IMPL void sgl_defaults(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->u = 0.0f; ctx->v = 0.0f; + ctx->rgba = 0xFFFFFFFF; + ctx->texturing_enabled = false; + ctx->cur_img = _sgl.def_img; + sgl_load_default_pipeline(); + _sgl_identity(_sgl_matrix_texture(ctx)); + _sgl_identity(_sgl_matrix_modelview(ctx)); + _sgl_identity(_sgl_matrix_projection(ctx)); + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + ctx->matrix_dirty = true; +} + +SOKOL_API_IMPL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_VIEWPORT; + cmd->args.viewport.x = x; + cmd->args.viewport.y = y; + cmd->args.viewport.w = w; + cmd->args.viewport.h = h; + cmd->args.viewport.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) { + sgl_viewport((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_SCISSOR_RECT; + cmd->args.scissor_rect.x = x; + cmd->args.scissor_rect.y = y; + cmd->args.scissor_rect.w = w; + cmd->args.scissor_rect.h = h; + cmd->args.scissor_rect.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) { + sgl_scissor_rect((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_enable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = true; +} + +SOKOL_API_IMPL void sgl_disable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = false; +} + +SOKOL_API_IMPL void sgl_texture(sg_image img) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + if (SG_INVALID_ID != img.id) { + ctx->cur_img = img; + } + else { + ctx->cur_img = _sgl.def_img; + } +} + +SOKOL_API_IMPL void sgl_begin_points(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_POINTS); +} + +SOKOL_API_IMPL void sgl_begin_lines(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINES); +} + +SOKOL_API_IMPL void sgl_begin_line_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_triangles(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLES); +} + +SOKOL_API_IMPL void sgl_begin_triangle_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_quads(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_QUADS); +} + +SOKOL_API_IMPL void sgl_end(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(ctx->in_begin); + SOKOL_ASSERT(ctx->cur_vertex >= ctx->base_vertex); + ctx->in_begin = false; + bool matrix_dirty = ctx->matrix_dirty; + if (matrix_dirty) { + ctx->matrix_dirty = false; + _sgl_uniform_t* uni = _sgl_next_uniform(ctx); + if (uni) { + _sgl_matmul4(&uni->mvp, _sgl_matrix_projection(ctx), _sgl_matrix_modelview(ctx)); + uni->tm = *_sgl_matrix_texture(ctx); + } + } + /* check if command can be merged with previous command */ + sg_pipeline pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + sg_image img = ctx->texturing_enabled ? ctx->cur_img : _sgl.def_img; + _sgl_command_t* prev_cmd = _sgl_prev_command(ctx); + bool merge_cmd = false; + if (prev_cmd) { + if ((prev_cmd->cmd == SGL_COMMAND_DRAW) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) && + !matrix_dirty && + (prev_cmd->args.draw.img.id == img.id) && + (prev_cmd->args.draw.pip.id == pip.id)) + { + merge_cmd = true; + } + } + if (merge_cmd) { + /* draw command can be merged with the previous command */ + prev_cmd->args.draw.num_vertices += ctx->cur_vertex - ctx->base_vertex; + } + else { + /* append a new draw command */ + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + SOKOL_ASSERT(ctx->cur_uniform > 0); + cmd->cmd = SGL_COMMAND_DRAW; + cmd->args.draw.img = img; + cmd->args.draw.pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + cmd->args.draw.base_vertex = ctx->base_vertex; + cmd->args.draw.num_vertices = ctx->cur_vertex - ctx->base_vertex; + cmd->args.draw.uniform_index = ctx->cur_uniform - 1; + } + } +} + +SOKOL_API_IMPL void sgl_t2f(float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->u = u; + ctx->v = v; + } +} + +SOKOL_API_IMPL void sgl_c3f(float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, 1.0f); + } +} + +SOKOL_API_IMPL void sgl_c4f(float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, 255); + } +} + +SOKOL_API_IMPL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c1i(uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = rgba; + } +} + +SOKOL_API_IMPL void sgl_v2f(float x, float y) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f(float x, float y, float z) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f(float x, float y, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f(float x, float y, float z, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3f(float x, float y, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c1i(float x, float y, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx,x, y, z, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_modelview(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_projection(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_PROJECTION; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_TEXTURE; + } +} + +SOKOL_API_IMPL void sgl_load_identity(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_identity(_sgl_matrix(ctx)); +} + +SOKOL_API_IMPL void sgl_load_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + memcpy(&_sgl_matrix(ctx)->v[0][0], &m[0], 64); +} + +SOKOL_API_IMPL void sgl_load_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_transpose(_sgl_matrix(ctx), (const _sgl_matrix_t*) &m[0]); +} + +SOKOL_API_IMPL void sgl_mult_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + const _sgl_matrix_t* m0 = (const _sgl_matrix_t*) &m[0]; + _sgl_mul(_sgl_matrix(ctx), m0); +} + +SOKOL_API_IMPL void sgl_mult_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_matrix_t m0; + _sgl_transpose(&m0, (const _sgl_matrix_t*) &m[0]); + _sgl_mul(_sgl_matrix(ctx), &m0); +} + +SOKOL_API_IMPL void sgl_rotate(float angle_rad, float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_rotate(_sgl_matrix(ctx), angle_rad, x, y, z); +} + +SOKOL_API_IMPL void sgl_scale(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_scale(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_translate(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_translate(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_frustum(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_frustum(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_ortho(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_ortho(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_perspective(_sgl_matrix(ctx), fov_y, aspect, z_near, z_far); +} + +SOKOL_API_IMPL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_lookat(_sgl_matrix(ctx), eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z); +} + +SOKOL_GL_API_DECL void sgl_push_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] < (_SGL_MAX_STACK_DEPTH - 1)) { + const _sgl_matrix_t* src = _sgl_matrix(ctx); + ctx->matrix_tos[ctx->cur_matrix_mode]++; + _sgl_matrix_t* dst = _sgl_matrix(ctx); + *dst = *src; + } + else { + ctx->error = SGL_ERROR_STACK_OVERFLOW; + } +} + +SOKOL_GL_API_DECL void sgl_pop_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] > 0) { + ctx->matrix_tos[ctx->cur_matrix_mode]--; + } + else { + ctx->error = SGL_ERROR_STACK_UNDERFLOW; + } +} + +SOKOL_API_IMPL void sgl_draw(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_draw(ctx); + } +} + +SOKOL_API_IMPL void sgl_context_draw(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + _sgl_draw(ctx); + } +} + +#endif /* SOKOL_GL_IMPL */ \ No newline at end of file diff --git a/vlib/sokol/sapp/enums.v b/vlib/sokol/sapp/enums.v index 14f2d4c6f3..ac885a4f15 100644 --- a/vlib/sokol/sapp/enums.v +++ b/vlib/sokol/sapp/enums.v @@ -18,6 +18,8 @@ pub enum EventType { resized iconified restored + focused + unfocused suspended resumed update_cursor @@ -39,6 +41,9 @@ pub enum Modifier { ctrl = 2 //(1<<1) alt = 4 //(1<<2) super = 8 //(1<<3) + lmb = 0x100 + rmb = 0x200 + mmb = 0x400 } pub enum KeyCode { diff --git a/vlib/sokol/sapp/sapp_structs.c.v b/vlib/sokol/sapp/sapp_structs.c.v index a8ff8b1af7..bcd5fcd319 100644 --- a/vlib/sokol/sapp/sapp_structs.c.v +++ b/vlib/sokol/sapp/sapp_structs.c.v @@ -1,5 +1,30 @@ module sapp +const ( + sapp_max_touchpoints = 8 + sapp_max_mousebuttons = 3 + sapp_max_keycodes = 512 + sapp_max_iconimages = 8 +) + +pub struct C.sapp_range { +pub: + ptr voidptr + size usize +} + +pub struct C.sapp_image_desc { +pub: + width int + height int + pixels C.spp_range +} + +pub struct C.sapp_icon_desc { + sokol_default bool + images [sapp_max_iconimages]C.sapp_image_desc +} + pub struct C.sapp_desc { pub: init_cb fn () // these are the user-provided callbacks without user data @@ -29,6 +54,7 @@ pub: enable_dragndrop bool // enable file dropping (drag'n'drop), default is false max_dropped_files int // max number of dropped files to process (default: 1) max_dropped_file_path_length int // max length in bytes of a dropped UTF-8 file path (default: 2048) + icon C.sapp_icon_desc // backend-specific options gl_force_gles2 bool // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available win32_console_utf8 bool // if true, set the output console codepage to UTF-8 @@ -60,7 +86,7 @@ pub: scroll_x f32 scroll_y f32 num_touches int - touches [8]C.sapp_touchpoint + touches [sapp_max_touchpoints]C.sapp_touchpoint window_width int window_height int framebuffer_width int @@ -83,7 +109,7 @@ pub: scroll_x f32 scroll_y f32 num_touches int - touches [8]C.sapp_touchpoint + touches [sapp_max_touchpoints]C.sapp_touchpoint window_width int window_height int framebuffer_width int diff --git a/vlib/sokol/sgl/sgl.c.v b/vlib/sokol/sgl/sgl.c.v index 0129cbfb24..439bb0322e 100644 --- a/vlib/sokol/sgl/sgl.c.v +++ b/vlib/sokol/sgl/sgl.c.v @@ -75,9 +75,14 @@ pub fn texture(img C.sg_image) { } // pipeline stack functions +[inline] +pub fn load_default_pipeline() { + C.sgl_load_default_pipeline() +} + [inline] pub fn default_pipeline() { - C.sgl_default_pipeline() + C.sgl_load_default_pipeline() } [inline] diff --git a/vlib/sokol/sgl/sgl_funcs.c.v b/vlib/sokol/sgl/sgl_funcs.c.v index 16e980dd60..842edfc267 100644 --- a/vlib/sokol/sgl/sgl_funcs.c.v +++ b/vlib/sokol/sgl/sgl_funcs.c.v @@ -22,7 +22,7 @@ fn C.sgl_disable_texture() fn C.sgl_texture(img C.sg_image) // pipeline stack functions -fn C.sgl_default_pipeline() +fn C.sgl_load_default_pipeline() fn C.sgl_load_pipeline(pip C.sgl_pipeline) fn C.sgl_push_pipeline() fn C.sgl_pop_pipeline()