From ce126c13b43813a9fb2f93538bd727857c5204c0 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 15:18:15 +0100 Subject: [PATCH 01/20] Add support for Raspberry Pi builds Uses OpenGLES 3 or 2 with EGL Updated sokol from https://github.com/floooh/sokol/tree/gles2 --- CMakeLists.txt | 83 +- src/sokol/sokol.c | 4 + src/sokol/sokol_app.h | 5872 ++++++++++++++++++++++------------------- src/sokol/sokol_gfx.h | 4736 +++++++++++++++++++++++---------- 4 files changed, 6565 insertions(+), 4130 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbcb91c84..2db44446a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,41 @@ else() project(SkyEmu C CXX) endif() +if(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7l|aarch64|arm") + set(IS_ARM TRUE) +else() + set(IS_ARM FALSE) +endif() + +# Function to check if a command exists +function(command_exists CMD RESULT_VAR) + execute_process(COMMAND which ${CMD} OUTPUT_VARIABLE CMD_PATH RESULT_VARIABLE CMD_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) + if(CMD_RESULT EQUAL 0) + set(${RESULT_VAR} TRUE PARENT_SCOPE) + else() + set(${RESULT_VAR} FALSE PARENT_SCOPE) + endif() +endfunction() + +command_exists("vcgencmd" IS_VIDEOCORE) + +function(check_gles3_support SUPPORTS_GLES3) + # Initialize variable + set(${SUPPORTS_GLES3} OFF PARENT_SCOPE) + + # Run the Bash one-liner to get the SoC version as a decimal number + execute_process( + COMMAND bash -c "awk '/Revision/ { rev = \"0x\" substr(\$3, length(\$3)-3, 4); printf \"%d\n\", (rev / 4096) % 16 }' /proc/cpuinfo" + OUTPUT_VARIABLE SOC_VERSION_DEC + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Determine GLES3 support based on the SoC version + if(SOC_VERSION_DEC GREATER_EQUAL 3) + # BCM2711 or newer + set(${SUPPORTS_GLES3} ON PARENT_SCOPE) + endif() +endfunction() set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) @@ -49,7 +84,7 @@ if (MSVC) # No secure development lifecycle features # Exception handling model: Catch C++ exceptions only, assume that "extern C" functions will never throw a C++ exception. # Disable all forms of MSVC debug iterator checking in new and old Visual Studios. - # Causes the application to use the multithread, static version of the run-time library. + # Causes the application to use the multithread, static version of the run-time library. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT /Ox /Ob2 /Oi /Ot /GT /GF /GS- /fp:fast /fp:except- /MP /sdl- /EHsc /D_SECURE_SCL=0 /D_SCL_SECURE_NO_WARNINGS /D_ITERATOR_DEBUG_LEVEL=0 /D_HAS_ITERATOR_DEBUGGING=0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT /Ox /Ob2 /Oi /Ot /GT /GF /GS- /fp:fast /fp:except- /MP /sdl- /EHsc /D_SECURE_SCL=0 /D_SCL_SECURE_NO_WARNINGS /D_ITERATOR_DEBUG_LEVEL=0 /D_HAS_ITERATOR_DEBUGGING=0 ") @@ -64,9 +99,9 @@ if (NOT EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 ") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 ") set(UNICODE_GUI 1) -else () +else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") endif () if(IOS) set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum iOS deployment version" FORCE) @@ -79,7 +114,7 @@ if(WIN32) endif() if(ANDROID) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() if(NOT IOS) @@ -93,6 +128,18 @@ if ((CMAKE_SYSTEM_NAME STREQUAL Linux) AND NOT EMSCRIPTEN AND NOT ANDROID) find_package(Threads REQUIRED) find_package(ALSA REQUIRED) set(SE_PLATFORM_LINUX TRUE) + + # Check for GLES3 support on the Raspberry Pi + if (IS_ARM AND IS_VIDEOCORE) + check_gles3_support(SUPPORTS_GLES3) + if(SUPPORTS_GLES3) + set(SE_FORCE_GLES3 TRUE) + add_definitions(-DSE_FORCE_GLES3=1) + else() + set(SE_FORCE_GLES2 TRUE) + add_definitions(-DSE_FORCE_GLES2=1) + endif() + endif() add_definitions(-DSE_PLATFORM_LINUX=1) endif() if (UNIX AND NOT APPLE AND NOT SE_PLATFORM_LINUX AND NOT EMSCRIPTEN AND NOT ANDROID) @@ -148,8 +195,8 @@ endif() # TinyFileDialogs if ((NOT IOS) AND (NOT EMSCRIPTEN) AND (NOT ANDROID) AND (NOT SE_PLATFORM_LINUX) AND (NOT SE_PLATFORM_FREEBSD)) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_TINY_FILE_DIALOGS") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_TINY_FILE_DIALOGS") - add_library(tinyfd STATIC + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_TINY_FILE_DIALOGS") + add_library(tinyfd STATIC src/tinyfiledialogs/tinyfiledialogs.h src/tinyfiledialogs/tinyfiledialogs.c ) @@ -163,13 +210,13 @@ if(NOT EMSCRIPTEN) endif() #=== LIBRARY: cimgui + Dear ImGui -add_library(cimgui STATIC - src/cimgui/cimgui.cpp - src/cimgui/cimgui.h +add_library(cimgui STATIC + src/cimgui/cimgui.cpp + src/cimgui/cimgui.h src/cimgui/imgui/imgui.cpp src/cimgui/imgui/imgui.h - src/cimgui/imgui/imgui_widgets.cpp - src/cimgui/imgui/imgui_draw.cpp + src/cimgui/imgui/imgui_widgets.cpp + src/cimgui/imgui/imgui_draw.cpp src/cimgui/imgui/imgui_tables.cpp src/cimgui/imgui/imgui_demo.cpp ) @@ -186,12 +233,12 @@ if(USE_SDL) set(SDL_SHARED OFF CACHE BOOL "Build Shared" FORCE) set(SDL_STATIC ON CACHE BOOL "Build Static" FORCE) set(SDL_FORCE_STATIC_VCRT ON CACHE BOOL "USE LIBC" FORCE) - set(SDL_LIBC ON CACHE BOOL "USE LIBC" FORCE) + set(SDL_LIBC ON CACHE BOOL "USE LIBC" FORCE) set(SDL_STATIC_PIC ON CACHE BOOL "Build Static PIC" FORCE) # Allow some projects to be built conditionally. set(SDL2_DISABLE_SDL2MAIN ON CACHE BOOL "Disable building/installation of SDL2main" FORCE) set(SDL2_DISABLE_INSTALL ON CACHE BOOL "Disable installation of SDL2" FORCE) - set(SDL2_DISABLE_UNINSTALL ON CACHE BOOL "Disable uninstallation of SDL2" FORCE) + set(SDL2_DISABLE_UNINSTALL ON CACHE BOOL "Disable uninstallation of SDL2" FORCE) set(SDL_LIBC ON CACHE BOOL "Use the system C library" ${OPT_DEF_LIBC} FORCE) set(SDL_ATOMIC OFF CACHE BOOL "SDL Video support" FORCE) @@ -199,7 +246,7 @@ if(USE_SDL) set(SDL_FILE OFF CACHE BOOL "SDL Video support" FORCE) set(SDL_THREAD OFF CACHE BOOL "SDL Video support" FORCE) set(SDL_RENDER OFF CACHE BOOL "SDL Video support" FORCE) - set(SDL_LOCALE OFF CACHE BOOL "SDL Video support" FORCE) + set(SDL_LOCALE OFF CACHE BOOL "SDL Video support" FORCE) add_definitions(-DSDL_LEAN_AND_MEAN=1) add_subdirectory(src/SDL2) @@ -252,7 +299,13 @@ else() set_property(TARGET sokol PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") if (SE_PLATFORM_LINUX OR SE_PLATFORM_FREEBSD) target_include_directories(sokol PRIVATE "${X11_INCLUDE_DIR}") - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::OpenGL dl) + if(SE_FORCE_GLES2) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::GLES2 EGL dl) + elseif(SE_FORCE_GLES3) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::GLES3 EGL dl) + else() + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::OpenGL dl) + endif() endif() if (CMAKE_SYSTEM_NAME STREQUAL Linux) target_link_libraries(sokol INTERFACE m) diff --git a/src/sokol/sokol.c b/src/sokol/sokol.c index a1487f9a5..a8ebe9984 100644 --- a/src/sokol/sokol.c +++ b/src/sokol/sokol.c @@ -9,6 +9,10 @@ #define SOKOL_GLES3 #elif defined(__APPLE__) #error "Must use sokol.m on macOS" +#elif defined(SE_FORCE_GLES2) +#define SOKOL_GLES2 +#elif defined(SE_FORCE_GLES3) +#define SOKOL_GLES3 #else #define SOKOL_GLCORE33 #endif diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index 5af91c241..4b3d62cc3 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -28,16 +28,12 @@ Optionally provide the following defines with your own implementations: SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_LOG(msg) - your own logging function (default: puts(msg)) SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) - SOKOL_ABORT() - called after an unrecoverable error (default: abort()) SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function SOKOL_APP_API_DECL - public function declaration prefix (default: extern) SOKOL_API_DECL - same as SOKOL_APP_API_DECL SOKOL_API_IMPL - public function implementation prefix (default: -) - SOKOL_CALLOC - your own calloc function (default: calloc(n, s)) - SOKOL_FREE - your own free function (default: free(p)) Optionally define the following to force debug checks and validations even in release mode: @@ -52,23 +48,13 @@ On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) or __declspec(dllimport) as needed. - If you use sokol_app.h together with sokol_gfx.h, include both headers - in the implementation source file, and include sokol_app.h before - sokol_gfx.h since sokol_app.h will also include the required 3D-API - headers. - - On Windows, a minimal 'GL header' and function loader is integrated which - contains just enough of GL for sokol_gfx.h. If you want to use your own - GL header-generator/loader instead, define SOKOL_WIN32_NO_GL_LOADER - before including the implementation part of sokol_app.h. - - To make use of the integrated GL loader, simply include the sokol_app.h - implementation before the sokol_gfx.h implementation. + On Linux, SOKOL_GLCORE33 can use either GLX or EGL. + GLX is default, set SOKOL_FORCE_EGL to override. 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. @@ -78,17 +64,18 @@ - on macOS with GL: Cocoa, QuartzCore, OpenGL - on iOS with Metal: Foundation, UIKit, Metal, MetalKit - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit - - on Linux: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) + - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) - on Android: GLESv3, EGL, log, android - - on Windows: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib + - 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 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 - Building for UWP requires a recent Visual Studio toolchain and Windows SDK - (at least VS2019 and Windows SDK 10.0.19041.0). When the UWP backend is - selected, the sokol_app.h implementation must be compiled as C++17. - On macOS and iOS, the implementation must be compiled as Objective-C. FEATURE OVERVIEW @@ -100,58 +87,56 @@ - creates a window and 3D-API context/device with a 'default framebuffer' - makes the rendered frame visible - provides keyboard-, mouse- and low-level touch-events - - platforms: MacOS, iOS, HTML5, Win32, Linux, Android (TODO: RaspberryPi) + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 FEATURE/PLATFORM MATRIX ======================= - | Windows | macOS | Linux | iOS | Android | UWP | Raspi | HTML5 - --------------------+---------+-------+-------+-------+---------+------+-------+------- - gl 3.x | YES | YES | YES | --- | --- | --- | --- | --- - gles2/webgl | --- | --- | --- | YES | YES | --- | TODO | YES - gles3/webgl2 | --- | --- | --- | YES | YES | --- | --- | YES - metal | --- | YES | --- | YES | --- | --- | --- | --- - d3d11 | YES | --- | --- | --- | --- | YES | --- | --- - KEY_DOWN | YES | YES | YES | SOME | TODO | YES | TODO | YES - KEY_UP | YES | YES | YES | SOME | TODO | YES | TODO | YES - CHAR | YES | YES | YES | YES | TODO | YES | TODO | YES - MOUSE_DOWN | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_UP | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_SCROLL | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_MOVE | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_ENTER | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_LEAVE | YES | YES | YES | --- | --- | YES | TODO | YES - TOUCHES_BEGAN | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_MOVED | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_ENDED | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_CANCELLED | --- | --- | --- | YES | YES | TODO | --- | YES - RESIZED | YES | YES | YES | YES | YES | YES | --- | YES - ICONIFIED | YES | YES | YES | --- | --- | YES | --- | --- - RESTORED | YES | YES | YES | --- | --- | YES | --- | --- - SUSPENDED | --- | --- | --- | YES | YES | YES | --- | TODO - RESUMED | --- | --- | --- | YES | YES | YES | --- | TODO - QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | TODO | YES - UPDATE_CURSOR | YES | YES | TODO | --- | --- | TODO | --- | TODO - IME | TODO | TODO? | TODO | ??? | TODO | --- | ??? | ??? - key repeat flag | YES | YES | YES | --- | --- | YES | TODO | YES - windowed | YES | YES | YES | --- | --- | YES | TODO | YES - fullscreen | YES | YES | YES | YES | YES | YES | TODO | --- - mouse hide | YES | YES | YES | --- | --- | YES | TODO | TODO - mouse lock | YES | YES | YES | --- | --- | TODO | TODO | YES - screen keyboard | --- | --- | --- | YES | TODO | TODO | --- | YES - swap interval | YES | YES | YES | YES | TODO | --- | TODO | YES - high-dpi | YES | YES | TODO | YES | YES | YES | TODO | YES - clipboard | YES | YES | TODO | --- | --- | TODO | --- | YES - MSAA | YES | YES | YES | YES | YES | TODO | TODO | YES - drag'n'drop | YES | YES | YES | --- | --- | TODO | TODO | YES - - TODO - ==== - - Linux: - - clipboard support - - UWP: - - clipboard, mouselock - - sapp_consume_event() on non-web platforms? + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 3.x | YES | YES | YES | --- | --- | --- + gles2/webgl | --- | --- | YES(2)| YES | YES | YES + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES + RESIZED | YES | YES | YES | YES | YES | YES + ICONIFIED | YES | YES | YES | --- | --- | --- + RESTORED | YES | YES | YES | --- | --- | --- + FOCUSED | YES | YES | YES | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | YES + SUSPENDED | --- | --- | --- | YES | YES | TODO + RESUMED | --- | --- | --- | YES | YES | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | YES + IME | TODO | TODO? | TODO | ??? | TODO | ??? + key repeat flag | YES | YES | YES | --- | --- | YES + windowed | YES | YES | YES | --- | --- | YES + fullscreen | YES | YES | YES | YES | YES | --- + mouse hide | YES | YES | YES | --- | --- | YES + mouse lock | YES | YES | YES | --- | --- | YES + set cursor type | YES | YES | YES | --- | --- | YES + screen keyboard | --- | --- | --- | YES | TODO | YES + swap interval | YES | YES | YES | YES | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES + clipboard | YES | YES | TODO | --- | --- | YES + MSAA | YES | YES | YES | YES | YES | YES + drag'n'drop | YES | YES | YES | --- | --- | YES + window icon | YES | YES(1)| YES | --- | --- | YES + + (1) macOS has no regular window icons, instead the dock icon is changed + (2) supported with EGL only (not GLX) STEP BY STEP ============ @@ -174,6 +159,18 @@ }; } + To get any logging output in case of errors you need to provide a log + callback. The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + There are many more setup parameters, but these are the most important. For a complete list search for the sapp_desc structure declaration below. @@ -185,7 +182,10 @@ rendering canvas. The actual size may differ from this depending on platform and other circumstances. Also the canvas size may change at any time (for instance when the user resizes the application window, - or rotates the mobile device). + or rotates the mobile device). You can just keep .width and .height + zero-initialized to open a default-sized window (what "default-size" + exactly means is platform-specific, but usually it's a size that covers + most of, but not all, of the display). All provided function callbacks will be called from the same thread, but this may be different from the thread where sokol_main() was called. @@ -206,11 +206,6 @@ used to communicate other types of events to the application. Keep the event_cb struct member zero-initialized if your application doesn't require event handling. - .fail_cb (void (*)(const char* msg)) - The fail callback is called when a fatal error is encountered - during start which doesn't allow the program to continue. - Providing a callback here gives you a chance to show an error message - to the user. The default behaviour is SOKOL_LOG(msg) As you can see, those 'standard callbacks' don't have a user_data argument, so any data that needs to be preserved between callbacks @@ -223,11 +218,7 @@ .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)) - 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. + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) The function sapp_userdata() can be used to query the user_data pointer provided in the sapp_desc struct. @@ -257,6 +248,10 @@ may help to prevent casting back and forth between int and float in more strongly typed languages than C and C++. + double sapp_frame_duration(void) + Returns the frame duration in seconds averaged over a number of + frames to smooth out any jittering spikes. + int sapp_color_format(void) int sapp_depth_format(void) The color and depth-stencil pixelformats of the default framebuffer, @@ -265,9 +260,9 @@ where sg_pixel_format is expected). Possible values are: 23 == SG_PIXELFORMAT_RGBA8 - 27 == SG_PIXELFORMAT_BGRA8 - 41 == SG_PIXELFORMAT_DEPTH - 42 == SG_PIXELFORMAT_DEPTH_STENCIL + 28 == SG_PIXELFORMAT_BGRA8 + 42 == SG_PIXELFORMAT_DEPTH + 43 == SG_PIXELFORMAT_DEPTH_STENCIL int sapp_sample_count(void) Return the MSAA sample count of the default framebuffer. @@ -370,6 +365,32 @@ "Really Quit?" dialog box). Note that the cleanup-callback isn't guaranteed to be called on the web and mobile platforms. + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + + void sapp_show_mouse(bool show) + + And to get the current shown status: + + bool sapp_mouse_shown(void) + + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + + To change the mouse cursor to one of several predefined types, call + the function: + + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + + To get the currently active mouse cursor type, call: + + sapp_mouse_cursor sapp_get_mouse_cursor(void) + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) ================================================ In normal mouse mode, no mouse movement events are reported when the @@ -416,7 +437,7 @@ - SAPP_EVENTTYPE_MOUSE_DOWN - SAPP_EVENTTYPE_MOUSE_UP - SAPP_EVENTTYPE_MOUSE_SCROLL - - SAPP_EVENTYTPE_KEY_UP + - SAPP_EVENTTYPE_KEY_UP - SAPP_EVENTTYPE_KEY_DOWN - The mouse lock/unlock action on the web platform is asynchronous, this means that sapp_mouse_locked() won't immediately return @@ -610,8 +631,10 @@ sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ .dropped_file_index = 0, .callback = fetch_cb - .buffer_ptr = buf, - .buffer_size = buf_size, + .buffer = { + .ptr = buf, + .size = sizeof(buf) + }, .user_data = ... }); @@ -625,9 +648,9 @@ // IMPORTANT: check if the loading operation actually succeeded: if (response->succeeded) { // the size of the loaded file: - const uint32_t num_bytes = response->fetched_size; + const size_t num_bytes = response->data.size; // and the pointer to the data (same as 'buf' in the fetch-call): - const void* ptr = response->buffer_ptr; + const void* ptr = response->data.ptr; } else { // on error check the error code: @@ -658,10 +681,18 @@ In a HighDPI scenario, you still request the same window size during sokol_main(), but the framebuffer sizes returned by sapp_width() and sapp_height() will be scaled up according to the DPI scaling - ratio. You can also get a DPI scaling factor with the function - sapp_dpi_scale(). + ratio. + + Note that on some platforms the DPI scaling factor may change at any + time (for instance when a window is moved from a high-dpi display + to a low-dpi display). + + To query the current DPI scaling factor, call the function: - Here's an example on a Mac with Retina display: + float sapp_dpi_scale(void); + + For instance on a Retina Mac, returning the following sapp_desc + struct from sokol_main(): sapp_desc sokol_main() { return (sapp_desc) { @@ -672,19 +703,32 @@ }; } - The functions sapp_width(), sapp_height() and sapp_dpi_scale() will - return the following values: + ...the functions the functions sapp_width(), sapp_height() + and sapp_dpi_scale() will return the following values: - sapp_width -> 1280 - sapp_height -> 960 - sapp_dpi_scale -> 2.0 + sapp_width: 1280 + sapp_height: 960 + sapp_dpi_scale: 2.0 If the high_dpi flag is false, or you're not running on a Retina display, the values would be: - sapp_width -> 640 - sapp_height -> 480 - sapp_dpi_scale -> 1.0 + sapp_width: 640 + sapp_height: 480 + sapp_dpi_scale: 1.0 + + If the window is moved from the Retina display to a low-dpi external display, + the values would change as follows: + + sapp_width: 1280 => 640 + sapp_height: 960 => 480 + sapp_dpi_scale: 2.0 => 1.0 + + Currently there is no event associated with a DPI change, but an + SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the + framebuffer size changing. + + Per-monitor DPI is currently supported on macOS and Windows. APPLICATION QUIT ================ @@ -736,7 +780,7 @@ programmatically close the browser tab). On the web it's also not possible to run custom code when the user - closes a brower tab, so it's not possible to prevent this with a + closes a browser tab, so it's not possible to prevent this with a fancy custom dialog box. Instead the standard "Leave Site?" dialog box can be activated (or @@ -785,6 +829,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 @@ -858,15 +1009,89 @@ doesn't matter if the application is started from the command line or via double-click. + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + // ... + .allocator = { + .alloc = my_alloc, + .free = my_free, + .user_data = ..., + } + }; + } + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_app.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sapp' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-app like this: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }; + } + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + TEMP NOTE DUMP ============== - onscreen keyboard support on Android requires Java :(, should we even bother? - sapp_desc needs a bool whether to initialize depth-stencil surface - GL context initialization needs more control (at least what GL version to initialize) - application icon - - the UPDATE_CURSOR event currently behaves differently between Win32 and OSX - (Win32 sends the event each frame when the mouse moves and is inside the window - client area, OSX sends it only once when the mouse enters the client area) - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy at the latest but should do it earlier, in onStop, as an app is "killable" after onStop on Android Honeycomb and later (it can't be done at the moment as the app may be started @@ -899,6 +1124,7 @@ distribution. */ #define SOKOL_APP_INCLUDED (1) +#include // size_t #include #include @@ -919,12 +1145,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, @@ -943,9 +1179,10 @@ 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, SAPP_EVENTTYPE_QUIT_REQUESTED, SAPP_EVENTTYPE_CLIPBOARD_PASTED, SAPP_EVENTTYPE_FILES_DROPPED, @@ -953,7 +1190,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, @@ -1076,16 +1320,48 @@ typedef enum sapp_keycode { SAPP_KEYCODE_RIGHT_ALT = 346, SAPP_KEYCODE_RIGHT_SUPER = 347, SAPP_KEYCODE_MENU = 348, - SAPP_KEYCODE_BACK = 349, } sapp_keycode; +/* + Android specific 'tool type' enum for touch events. This lets the + application check what type of input device was used for + touch events. + + NOTE: the values must remain in sync with the corresponding + Android SDK type, so don't change those. + + See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN +*/ +typedef enum sapp_android_tooltype { + SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN + SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER + SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS + SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE +} sapp_android_tooltype; + +/* + 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; float pos_y; + sapp_android_tooltype android_tooltype; // only valid on Android 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, @@ -1093,75 +1369,290 @@ 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; + +/* + sapp_allocator + + Used in sapp_desc to provide custom memory-alloc and -free functions + to sokol_app.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sapp_allocator { + void* (*alloc)(size_t size, void* user_data); + void (*free)(void* ptr, void* user_data); + void* user_data; +} sapp_allocator; + +/* + sapp_log_item + + Log items are defined via X-Macros and expanded to an enum + 'sapp_log_item', and in debug mode to corresponding + human readable error messages. +*/ +#define _SAPP_LOG_ITEMS \ + _SAPP_LOGITEM_XMACRO(OK, "Ok") \ + _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0, 3.2 and 4.1)") \ + _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ + _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED, "OpenGL 3.2 not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ + _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity sucessfully created") \ + _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ + _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ + _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ + +#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, +typedef enum sapp_log_item { + _SAPP_LOG_ITEMS +} sapp_log_item; +#undef _SAPP_LOGITEM_XMACRO + +/* + sapp_logger + + Used in sapp_desc to provide a logging function. Please be aware that + without logging function, sokol-app will be completely silent, e.g. it will + not report errors or warnings. For maximum error verbosity, compile in + debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance + the standard logging function from sokol_log.h). +*/ +typedef struct sapp_logger { + void (*func)( + const char* tag, // always "sapp" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sapp_logger; + 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 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 + sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) + sapp_logger logger; // logging callback override (default: NO LOGGING!) /* 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 */ + bool gl_force_gles2; // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available + int gl_major_version; // override GL major and minor version (the default GL version is 3.2) + int gl_minor_version; + 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 } sapp_desc; /* HTML5 specific: request and response structs for @@ -1174,23 +1665,41 @@ typedef enum sapp_html5_fetch_error { } sapp_html5_fetch_error; typedef struct sapp_html5_fetch_response { - bool succeeded; /* true if the loading operation has succeeded */ + bool succeeded; // true if the loading operation has succeeded sapp_html5_fetch_error error_code; - int file_index; /* index of the dropped file (0..sapp_get_num_dropped_filed()-1) */ - uint32_t fetched_size; /* size in bytes of loaded data */ - void* buffer_ptr; /* pointer to user-provided buffer which contains the loaded data */ - uint32_t buffer_size; /* size of user-provided buffer (buffer_size >= fetched_size) */ - void* user_data; /* user-provided user data pointer */ + int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) + sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) + sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) + void* user_data; // user-provided user data pointer } sapp_html5_fetch_response; typedef struct sapp_html5_fetch_request { - int dropped_file_index; /* 0..sapp_get_num_dropped_files()-1 */ - void (*callback)(const sapp_html5_fetch_response*); /* response callback function pointer (required) */ - void* buffer_ptr; /* pointer to buffer to load data into */ - uint32_t buffer_size; /* size in bytes of buffer */ - void* user_data; /* optional userdata pointer */ + int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 + void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) + sapp_range buffer; // ptr/size of a memory buffer to load the data into + void* user_data; // optional userdata pointer } sapp_html5_fetch_request; +/* + sapp_mouse_cursor + + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +typedef enum sapp_mouse_cursor { + SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor + SAPP_MOUSECURSOR_ARROW, + SAPP_MOUSECURSOR_IBEAM, + SAPP_MOUSECURSOR_CROSSHAIR, + SAPP_MOUSECURSOR_POINTING_HAND, + SAPP_MOUSECURSOR_RESIZE_EW, + SAPP_MOUSECURSOR_RESIZE_NS, + SAPP_MOUSECURSOR_RESIZE_NWSE, + SAPP_MOUSECURSOR_RESIZE_NESW, + SAPP_MOUSECURSOR_RESIZE_ALL, + SAPP_MOUSECURSOR_NOT_ALLOWED, + _SAPP_MOUSECURSOR_NUM, +} sapp_mouse_cursor; + /* user-provided functions */ extern sapp_desc sokol_main(int argc, char* argv[]); @@ -1225,11 +1734,15 @@ SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); /* show or hide the mouse cursor */ SOKOL_APP_API_DECL void sapp_show_mouse(bool show); /* show or hide the mouse cursor */ -SOKOL_APP_API_DECL bool sapp_mouse_shown(); +SOKOL_APP_API_DECL bool sapp_mouse_shown(void); /* enable/disable mouse-pointer-lock mode */ SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); /* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* set mouse cursor type */ +SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); +/* get current mouse cursor type */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); /* return the userdata pointer optionally provided in sapp_desc */ SOKOL_APP_API_DECL void* sapp_userdata(void); /* return a copy of the sapp_desc structure */ @@ -1244,12 +1757,16 @@ SOKOL_APP_API_DECL void sapp_quit(void); SOKOL_APP_API_DECL void sapp_consume_event(void); /* get the current frame counter (for comparison with sapp_event.frame_count) */ SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* get an averaged/smoothed frame duration in seconds */ +SOKOL_APP_API_DECL double sapp_frame_duration(void); /* write string into clipboard */ SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); /* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ 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 */ @@ -1258,6 +1775,11 @@ SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); /* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); +/* EGL: get EGLDisplay object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); +/* EGL: get EGLContext object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); + /* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ SOKOL_APP_API_DECL bool sapp_gles2(void); @@ -1278,12 +1800,13 @@ SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); /* iOS: get bridged pointer to iOS UIWindow */ SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); -SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); /* D3D11: get pointer to ID3D11Device object */ 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 */ @@ -1302,7 +1825,6 @@ SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); /* Android: get native activity handle */ SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); -SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); #ifdef __cplusplus } /* extern "C" */ @@ -1322,12 +1844,24 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #endif // SOKOL_APP_INCLUDED -/*-- IMPLEMENTATION ----------------------------------------------------------*/ +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation #ifdef SOKOL_APP_IMPL #define SOKOL_APP_IMPL_INCLUDED (1) +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free #include // memset #include // size_t +#include // roundf /* check if the config defines are alright */ #if defined(__APPLE__) @@ -1360,20 +1894,9 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #endif #elif defined(_WIN32) /* Windows (D3D11 or GL) */ - #include - #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) - #define _SAPP_UWP (1) - #if !defined(SOKOL_D3D11) - #error("sokol_app.h: unknown 3D API selected for UWP, must be SOKOL_D3D11") - #endif - #if !defined(__cplusplus) - #error("sokol_app.h: UWP bindings require C++/17") - #endif - #else - #define _SAPP_WIN32 (1) - #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") - #endif + #define _SAPP_WIN32 (1) + #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") #endif #elif defined(__ANDROID__) /* Android */ @@ -1387,8 +1910,12 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #elif defined(__linux__) || defined(__unix__) /* Linux */ #define _SAPP_LINUX (1) - #if !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33") + #if defined(SOKOL_GLCORE33) + #if !defined(SOKOL_FORCE_EGL) + #define _SAPP_GLX (1) + #endif + #elif !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3 or SOKOL_GLES2") #endif #else #error "sokol_app.h: Unknown platform" @@ -1399,7 +1926,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #endif #ifndef SOKOL_DEBUG #ifndef NDEBUG - #define SOKOL_DEBUG (1) + #define SOKOL_DEBUG #endif #endif #ifndef SOKOL_ASSERT @@ -1409,32 +1936,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #ifndef SOKOL_UNREACHABLE #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) #endif -#if !defined(SOKOL_CALLOC) || !defined(SOKOL_FREE) - #include -#endif -#if !defined(SOKOL_CALLOC) - #define SOKOL_CALLOC(n,s) calloc(n,s) -#endif -#if !defined(SOKOL_FREE) - #define SOKOL_FREE(p) free(p) -#endif -#ifndef SOKOL_LOG - #ifdef SOKOL_DEBUG - #if defined(__ANDROID__) - #include - #define SOKOL_LOG(s) { SOKOL_ASSERT(s); __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s); } - #else - #include - #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } - #endif - #else - #define SOKOL_LOG(s) - #endif -#endif -#ifndef SOKOL_ABORT - #include - #define SOKOL_ABORT() abort() -#endif + #ifndef _SOKOL_PRIVATE #if defined(__GNUC__) || defined(__clang__) #define _SOKOL_PRIVATE __attribute__((unused)) static @@ -1446,7 +1948,6 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #define _SOKOL_UNUSED(x) (void)(x) #endif -/*== PLATFORM SPECIFIC INCLUDES AND DEFINES ==================================*/ #if defined(_SAPP_APPLE) #if defined(SOKOL_METAL) #import @@ -1458,26 +1959,17 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #define GL_SILENCE_DEPRECATION #endif #include - #include #endif #elif defined(_SAPP_IOS) #import #if !defined(SOKOL_METAL) #import - #include - #include #endif #endif + #include + #include #elif defined(_SAPP_EMSCRIPTEN) - #if defined(SOKOL_GLES3) - #include - #elif defined(SOKOL_GLES2) - #ifndef GL_EXT_PROTOTYPES - #define GL_GLEXT_PROTOTYPES - #endif - #include - #include - #elif defined(SOKOL_WGPU) + #if defined(SOKOL_WGPU) #include #endif #include @@ -1491,7 +1983,6 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #pragma warning(disable:4055) /* 'type cast': from data pointer */ #pragma warning(disable:4505) /* unreferenced local function has been removed */ #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ - #pragma warning(disable:4996) /* 'freopen': This function or variable may be unsafe. */ #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN @@ -1509,18 +2000,16 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #pragma comment (linker, "/subsystem:windows") #endif #endif - #include /* freopen() */ + #include /* freopen_s() */ + #include /* wcslen() */ #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) @@ -1535,52 +2024,16 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ #define WM_MOUSEHWHEEL (0x020E) #endif -#elif defined(_SAPP_UWP) - #ifndef NOMINMAX - #define NOMINMAX - #endif - - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4054) /* 'type cast': from function pointer */ - #pragma warning(disable:4055) /* 'type cast': from data pointer */ - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #ifndef WM_DPICHANGED + #define WM_DPICHANGED (0x02E0) #endif - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #include - #include - #include - - #pragma comment (lib, "WindowsApp") - #pragma comment (lib, "dxguid") #elif defined(_SAPP_ANDROID) #include #include + #include #include #include #include - #if defined(SOKOL_GLES3) - #include - #else - #ifndef GL_EXT_PROTOTYPES - #define GL_GLEXT_PROTOTYPES - #endif - #include - #include - #endif #elif defined(_SAPP_LINUX) #define GL_GLEXT_PROTOTYPES #include @@ -1591,66 +2044,294 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #include #include #include + #include /* XC_* font cursors */ #include /* CARD32 */ - #include + #if !defined(_SAPP_GLX) + #include + #endif #include /* dlopen, dlsym, dlclose */ #include /* LONG_MAX */ #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ + #include #endif -/*== MACOS DECLARATIONS ======================================================*/ -#if defined(_SAPP_MACOS) -@interface _sapp_macos_app_delegate : NSObject -@end -@interface _sapp_macos_window : NSWindow -@end -@interface _sapp_macos_window_delegate : NSObject -@end -#if defined(SOKOL_METAL) - @interface _sapp_macos_view : MTKView - @end -#elif defined(SOKOL_GLCORE33) - @interface _sapp_macos_view : NSOpenGLView - - (void)timerFired:(id)sender; - @end -#endif // SOKOL_GLCORE33 - +// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ +// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ +// +// >>frame timing +#define _SAPP_RING_NUM_SLOTS (256) typedef struct { - uint32_t flags_changed_store; - uint8_t mouse_buttons; - NSWindow* window; - NSTrackingArea* tracking_area; - _sapp_macos_app_delegate* app_dlg; - _sapp_macos_window_delegate* win_dlg; - _sapp_macos_view* view; - #if defined(SOKOL_METAL) - id mtl_device; - #endif -} _sapp_macos_t; + int head; + int tail; + double buf[_SAPP_RING_NUM_SLOTS]; +} _sapp_ring_t; -#endif // _SAPP_MACOS +_SOKOL_PRIVATE int _sapp_ring_idx(int i) { + return i % _SAPP_RING_NUM_SLOTS; +} -/*== IOS DECLARATIONS ========================================================*/ -#if defined(_SAPP_IOS) +_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { + ring->head = 0; + ring->tail = 0; +} -@interface _sapp_app_delegate : NSObject -@end -@interface _sapp_textfield_dlg : NSObject -- (void)keyboardWasShown:(NSNotification*)notif; -- (void)keyboardWillBeHidden:(NSNotification*)notif; -- (void)keyboardDidChangeFrame:(NSNotification*)notif; -@end -#if defined(SOKOL_METAL) - @interface _sapp_ios_view : MTKView; - @end -#else - @interface _sapp_ios_view : GLKView - @end -#endif +_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { + return _sapp_ring_idx(ring->head + 1) == ring->tail; +} -typedef struct { - UIWindow* window; - _sapp_ios_view* view; +_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { + return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { + int count; + if (ring->head >= ring->tail) { + count = ring->head - ring->tail; + } + else { + count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; + } + SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); + return count; +} + +_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { + SOKOL_ASSERT(!_sapp_ring_full(ring)); + ring->buf[ring->head] = val; + ring->head = _sapp_ring_idx(ring->head + 1); +} + +_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { + SOKOL_ASSERT(!_sapp_ring_empty(ring)); + double val = ring->buf[ring->tail]; + ring->tail = _sapp_ring_idx(ring->tail + 1); + return val; +} + +/* + NOTE: + + Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? + A: The value appears to be highly unstable during the first few + seconds, sometimes several frames are dropped in sequence, or + switch between 120 and 60 Hz for a few frames. Simply measuring + and averaging the frame time yielded a more stable frame duration. + Maybe switching to CVDisplayLink would yield better results. + Until then just measure the time. +*/ +typedef struct { + #if defined(_SAPP_APPLE) + struct { + mach_timebase_info_data_t timebase; + uint64_t start; + } mach; + #elif defined(_SAPP_EMSCRIPTEN) + // empty + #elif defined(_SAPP_WIN32) + struct { + LARGE_INTEGER freq; + LARGE_INTEGER start; + } win; + #else // Linux, Android, ... + #ifdef CLOCK_MONOTONIC + #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC + #else + // on some embedded platforms, CLOCK_MONOTONIC isn't defined + #define _SAPP_CLOCK_MONOTONIC (1) + #endif + struct { + uint64_t start; + } posix; + #endif +} _sapp_timestamp_t; + +_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} + +_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + mach_timebase_info(&ts->mach.timebase); + ts->mach.start = mach_absolute_time(); + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + #elif defined(_SAPP_WIN32) + QueryPerformanceFrequency(&ts->win.freq); + QueryPerformanceCounter(&ts->win.start); + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; + #endif +} + +_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + const uint64_t traw = mach_absolute_time() - ts->mach.start; + const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); + return (double)now / 1000000000.0; + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + SOKOL_ASSERT(false); + return 0.0; + #elif defined(_SAPP_WIN32) + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); + return (double)now / 1000000000.0; + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; + return (double)now / 1000000000.0; + #endif +} + +typedef struct { + double last; + double accum; + double avg; + int spike_count; + int num; + _sapp_timestamp_t timestamp; + _sapp_ring_t ring; +} _sapp_timing_t; + +_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { + t->last = 0.0; + t->accum = 0.0; + t->spike_count = 0; + t->num = 0; + _sapp_ring_init(&t->ring); +} + +_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { + t->avg = 1.0 / 60.0; // dummy value until first actual value is available + _sapp_timing_reset(t); + _sapp_timestamp_init(&t->timestamp); +} + +_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { + // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) + double min_dur = 0.0; + double max_dur = 0.1; + // if we have enough samples for a useful average, use a much tighter 'valid window' + if (_sapp_ring_full(&t->ring)) { + min_dur = t->avg * 0.8; + max_dur = t->avg * 1.2; + } + if ((dur < min_dur) || (dur > max_dur)) { + t->spike_count++; + // if there have been many spikes in a row, the display refresh rate + // might have changed, so a timing reset is needed + if (t->spike_count > 20) { + _sapp_timing_reset(t); + } + return; + } + if (_sapp_ring_full(&t->ring)) { + double old_val = _sapp_ring_dequeue(&t->ring); + t->accum -= old_val; + t->num -= 1; + } + _sapp_ring_enqueue(&t->ring, dur); + t->accum += dur; + t->num += 1; + SOKOL_ASSERT(t->num > 0); + t->avg = t->accum / t->num; + t->spike_count = 0; +} + +_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { + t->last = 0.0; +} + +_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { + const double now = _sapp_timestamp_now(&t->timestamp); + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { + return t->avg; +} + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >> structs +#if defined(_SAPP_MACOS) +@interface _sapp_macos_app_delegate : NSObject +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject +@end +#if defined(SOKOL_METAL) + @interface _sapp_macos_view : MTKView + @end +#elif defined(SOKOL_GLCORE33) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#endif // SOKOL_GLCORE33 + +typedef struct { + uint32_t flags_changed_store; + uint8_t mouse_buttons; + NSWindow* window; + NSTrackingArea* tracking_area; + id keyup_monitor; + _sapp_macos_app_delegate* app_dlg; + _sapp_macos_window_delegate* win_dlg; + _sapp_macos_view* view; + NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; + #if defined(SOKOL_METAL) + id mtl_device; + #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject +@end +@interface _sapp_textfield_dlg : NSObject +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) + @interface _sapp_ios_view : MTKView; + @end +#else + @interface _sapp_ios_view : GLKView + @end +#endif + +typedef struct { + UIWindow* window; + _sapp_ios_view* view; UITextField* textfield; _sapp_textfield_dlg* textfield_dlg; #if defined(SOKOL_METAL) @@ -1665,7 +2346,6 @@ typedef struct { #endif // _SAPP_IOS -/*== EMSCRIPTEN DECLARATIONS =================================================*/ #if defined(_SAPP_EMSCRIPTEN) #if defined(SOKOL_WGPU) @@ -1687,14 +2367,14 @@ 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 } _sapp_emsc_t; #endif // _SAPP_EMSCRIPTEN -/*== WIN32 DECLARATIONS ======================================================*/ -#if defined(SOKOL_D3D11) && (defined(_SAPP_WIN32) || defined(_SAPP_UWP)) +#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) typedef struct { ID3D11Device* device; ID3D11DeviceContext* device_context; @@ -1706,10 +2386,12 @@ typedef struct { ID3D11DepthStencilView* dsv; DXGI_SWAP_CHAIN_DESC swap_chain_desc; IDXGISwapChain* swap_chain; + IDXGIDevice1* dxgi_device; + bool use_dxgi_frame_stats; + UINT sync_refresh_count; } _sapp_d3d11_t; #endif -/*== WIN32 DECLARATIONS ======================================================*/ #if defined(_SAPP_WIN32) #ifndef DPI_ENUMS_DECLARED @@ -1736,9 +2418,14 @@ typedef struct { typedef struct { HWND hwnd; + HMONITOR hmonitor; HDC dc; + HICON big_icon; + HICON small_icon; + HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; UINT orig_codepage; LONG mouse_locked_x, mouse_locked_y; + RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed bool is_win10_or_greater; bool in_create_window; bool iconified; @@ -1812,25 +2499,6 @@ typedef struct { #endif // _SAPP_WIN32 -/*== UWP DECLARATIONS ======================================================*/ -#if defined(_SAPP_UWP) - -typedef struct { - float content_scale; - float window_scale; - float mouse_scale; -} _sapp_uwp_dpi_t; - -typedef struct { - bool mouse_tracked; - uint8_t mouse_buttons; - _sapp_uwp_dpi_t dpi; -} _sapp_uwp_t; - -#endif // _SAPP_UWP - -/*== ANDROID DECLARATIONS ====================================================*/ - #if defined(_SAPP_ANDROID) typedef enum { _SOKOL_ANDROID_MSG_CREATE, @@ -1876,7 +2544,6 @@ typedef struct { #endif // _SAPP_ANDROID -/*== LINUX DECLARATIONS ======================================================*/ #if defined(_SAPP_LINUX) #define _SAPP_X11_XDND_VERSION (5) @@ -1916,7 +2583,7 @@ typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); -typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const GLubyte *procName); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); @@ -1958,6 +2625,7 @@ typedef struct { Colormap colormap; Window window; Cursor hidden_cursor; + Cursor cursors[_SAPP_MOUSECURSOR_NUM]; int window_state; float dpi; unsigned char error_code; @@ -1967,12 +2635,15 @@ 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; _sapp_xdnd_t xdnd; } _sapp_x11_t; +#if defined(_SAPP_GLX) + typedef struct { void* libgl; int major; @@ -2011,30 +2682,40 @@ typedef struct { bool ARB_create_context_profile; } _sapp_glx_t; -#endif // _SAPP_LINUX +#else + +typedef struct { + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_egl_t; -/*== COMMON DECLARATIONS =====================================================*/ +#endif // _SAPP_GLX + +#endif // _SAPP_LINUX /* helper macros */ #define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) #define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) #define _SAPP_MAX_TITLE_LENGTH (128) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) /* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ #define _SAPP_PIXELFORMAT_RGBA8 (23) -#define _SAPP_PIXELFORMAT_BGRA8 (27) -#define _SAPP_PIXELFORMAT_DEPTH (41) -#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (42) +#define _SAPP_PIXELFORMAT_BGRA8 (28) +#define _SAPP_PIXELFORMAT_DEPTH (42) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (43) #if defined(_SAPP_MACOS) || defined(_SAPP_IOS) // this is ARC compatible #if defined(__cplusplus) - #define _SAPP_CLEAR(type, item) { item = (type) { }; } + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } #else - #define _SAPP_CLEAR(type, item) { item = (type) { 0 }; } + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } #endif #else - #define _SAPP_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } #endif typedef struct { @@ -2058,6 +2739,7 @@ typedef struct { bool shown; bool locked; bool pos_valid; + sapp_mouse_cursor current_cursor; } _sapp_mouse_t; typedef struct { @@ -2081,10 +2763,13 @@ typedef struct { int swap_interval; float dpi_scale; uint64_t frame_count; + _sapp_timing_t timing; sapp_event event; _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) @@ -2098,16 +2783,15 @@ typedef struct { #elif defined(SOKOL_GLCORE33) _sapp_wgl_t wgl; #endif - #elif defined(_SAPP_UWP) - _sapp_uwp_t uwp; - #if defined(SOKOL_D3D11) - _sapp_d3d11_t d3d11; - #endif #elif defined(_SAPP_ANDROID) _sapp_android_t android; #elif defined(_SAPP_LINUX) _sapp_x11_t x11; - _sapp_glx_t glx; + #if defined(_SAPP_GLX) + _sapp_glx_t glx; + #else + _sapp_egl_t egl; + #endif #endif char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ @@ -2116,383 +2800,94 @@ typedef struct { } _sapp_t; static _sapp_t _sapp; -/*=== OPTIONAL MINI GL LOADER FOR WIN32/WGL ==================================*/ -#if defined(_SAPP_WIN32) && defined(SOKOL_GLCORE33) && !defined(SOKOL_WIN32_NO_GL_LOADER) -#define __gl_h_ 1 -#define __gl32_h_ 1 -#define __gl31_h_ 1 -#define __GL_H__ 1 -#define __glext_h_ 1 -#define __GLEXT_H_ 1 -#define __gltypes_h_ 1 -#define __glcorearb_h_ 1 -#define __gl_glcorearb_h_ 1 -#define GL_APIENTRY APIENTRY - -typedef unsigned int GLenum; -typedef unsigned int GLuint; -typedef int GLsizei; -typedef char GLchar; -typedef ptrdiff_t GLintptr; -typedef ptrdiff_t GLsizeiptr; -typedef double GLclampd; -typedef unsigned short GLushort; -typedef unsigned char GLubyte; -typedef unsigned char GLboolean; -typedef uint64_t GLuint64; -typedef double GLdouble; -typedef unsigned short GLhalf; -typedef float GLclampf; -typedef unsigned int GLbitfield; -typedef signed char GLbyte; -typedef short GLshort; -typedef void GLvoid; -typedef int64_t GLint64; -typedef float GLfloat; -typedef struct __GLsync * GLsync; -typedef int GLint; -#define GL_INT_2_10_10_10_REV 0x8D9F -#define GL_R32F 0x822E -#define GL_PROGRAM_POINT_SIZE 0x8642 -#define GL_STENCIL_ATTACHMENT 0x8D20 -#define GL_DEPTH_ATTACHMENT 0x8D00 -#define GL_COLOR_ATTACHMENT2 0x8CE2 -#define GL_COLOR_ATTACHMENT0 0x8CE0 -#define GL_R16F 0x822D -#define GL_COLOR_ATTACHMENT22 0x8CF6 -#define GL_DRAW_FRAMEBUFFER 0x8CA9 -#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 -#define GL_NUM_EXTENSIONS 0x821D -#define GL_INFO_LOG_LENGTH 0x8B84 -#define GL_VERTEX_SHADER 0x8B31 -#define GL_INCR 0x1E02 -#define GL_DYNAMIC_DRAW 0x88E8 -#define GL_STATIC_DRAW 0x88E4 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 -#define GL_TEXTURE_CUBE_MAP 0x8513 -#define GL_FUNC_SUBTRACT 0x800A -#define GL_FUNC_REVERSE_SUBTRACT 0x800B -#define GL_CONSTANT_COLOR 0x8001 -#define GL_DECR_WRAP 0x8508 -#define GL_R8 0x8229 -#define GL_LINEAR_MIPMAP_LINEAR 0x2703 -#define GL_ELEMENT_ARRAY_BUFFER 0x8893 -#define GL_SHORT 0x1402 -#define GL_DEPTH_TEST 0x0B71 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 -#define GL_LINK_STATUS 0x8B82 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 -#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E -#define GL_RGBA16F 0x881A -#define GL_CONSTANT_ALPHA 0x8003 -#define GL_READ_FRAMEBUFFER 0x8CA8 -#define GL_TEXTURE0 0x84C0 -#define GL_TEXTURE_MIN_LOD 0x813A -#define GL_CLAMP_TO_EDGE 0x812F -#define GL_UNSIGNED_SHORT_5_6_5 0x8363 -#define GL_TEXTURE_WRAP_R 0x8072 -#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 -#define GL_NEAREST_MIPMAP_NEAREST 0x2700 -#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#define GL_SRC_ALPHA_SATURATE 0x0308 -#define GL_STREAM_DRAW 0x88E0 -#define GL_ONE 1 -#define GL_NEAREST_MIPMAP_LINEAR 0x2702 -#define GL_RGB10_A2 0x8059 -#define GL_RGBA8 0x8058 -#define GL_COLOR_ATTACHMENT1 0x8CE1 -#define GL_RGBA4 0x8056 -#define GL_RGB8 0x8051 -#define GL_ARRAY_BUFFER 0x8892 -#define GL_STENCIL 0x1802 -#define GL_TEXTURE_2D 0x0DE1 -#define GL_DEPTH 0x1801 -#define GL_FRONT 0x0404 -#define GL_STENCIL_BUFFER_BIT 0x00000400 -#define GL_REPEAT 0x2901 -#define GL_RGBA 0x1908 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 -#define GL_DECR 0x1E03 -#define GL_FRAGMENT_SHADER 0x8B30 -#define GL_FLOAT 0x1406 -#define GL_TEXTURE_MAX_LOD 0x813B -#define GL_DEPTH_COMPONENT 0x1902 -#define GL_ONE_MINUS_DST_ALPHA 0x0305 -#define GL_COLOR 0x1800 -#define GL_TEXTURE_2D_ARRAY 0x8C1A -#define GL_TRIANGLES 0x0004 -#define GL_UNSIGNED_BYTE 0x1401 -#define GL_TEXTURE_MAG_FILTER 0x2800 -#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 -#define GL_NONE 0 -#define GL_SRC_COLOR 0x0300 -#define GL_BYTE 0x1400 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A -#define GL_LINE_STRIP 0x0003 -#define GL_TEXTURE_3D 0x806F -#define GL_CW 0x0900 -#define GL_LINEAR 0x2601 -#define GL_RENDERBUFFER 0x8D41 -#define GL_GEQUAL 0x0206 -#define GL_COLOR_BUFFER_BIT 0x00004000 -#define GL_RGBA32F 0x8814 -#define GL_BLEND 0x0BE2 -#define GL_ONE_MINUS_SRC_ALPHA 0x0303 -#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 -#define GL_TEXTURE_WRAP_T 0x2803 -#define GL_TEXTURE_WRAP_S 0x2802 -#define GL_TEXTURE_MIN_FILTER 0x2801 -#define GL_LINEAR_MIPMAP_NEAREST 0x2701 -#define GL_EXTENSIONS 0x1F03 -#define GL_NO_ERROR 0 -#define GL_REPLACE 0x1E01 -#define GL_KEEP 0x1E00 -#define GL_CCW 0x0901 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 -#define GL_RGB 0x1907 -#define GL_TRIANGLE_STRIP 0x0005 -#define GL_FALSE 0 -#define GL_ZERO 0 -#define GL_CULL_FACE 0x0B44 -#define GL_INVERT 0x150A -#define GL_INT 0x1404 -#define GL_UNSIGNED_INT 0x1405 -#define GL_UNSIGNED_SHORT 0x1403 -#define GL_NEAREST 0x2600 -#define GL_SCISSOR_TEST 0x0C11 -#define GL_LEQUAL 0x0203 -#define GL_STENCIL_TEST 0x0B90 -#define GL_DITHER 0x0BD0 -#define GL_DEPTH_COMPONENT16 0x81A5 -#define GL_EQUAL 0x0202 -#define GL_FRAMEBUFFER 0x8D40 -#define GL_RGB5 0x8050 -#define GL_LINES 0x0001 -#define GL_DEPTH_BUFFER_BIT 0x00000100 -#define GL_SRC_ALPHA 0x0302 -#define GL_INCR_WRAP 0x8507 -#define GL_LESS 0x0201 -#define GL_MULTISAMPLE 0x809D -#define GL_FRAMEBUFFER_BINDING 0x8CA6 -#define GL_BACK 0x0405 -#define GL_ALWAYS 0x0207 -#define GL_FUNC_ADD 0x8006 -#define GL_ONE_MINUS_DST_COLOR 0x0307 -#define GL_NOTEQUAL 0x0205 -#define GL_DST_COLOR 0x0306 -#define GL_COMPILE_STATUS 0x8B81 -#define GL_RED 0x1903 -#define GL_COLOR_ATTACHMENT3 0x8CE3 -#define GL_DST_ALPHA 0x0304 -#define GL_RGB5_A1 0x8057 -#define GL_GREATER 0x0204 -#define GL_POLYGON_OFFSET_FILL 0x8037 -#define GL_TRUE 1 -#define GL_NEVER 0x0200 -#define GL_POINTS 0x0000 -#define GL_ONE_MINUS_SRC_COLOR 0x0301 -#define GL_MIRRORED_REPEAT 0x8370 -#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D -#define GL_R11F_G11F_B10F 0x8C3A -#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B -#define GL_RGBA32UI 0x8D70 -#define GL_RGB32UI 0x8D71 -#define GL_RGBA16UI 0x8D76 -#define GL_RGB16UI 0x8D77 -#define GL_RGBA8UI 0x8D7C -#define GL_RGB8UI 0x8D7D -#define GL_RGBA32I 0x8D82 -#define GL_RGB32I 0x8D83 -#define GL_RGBA16I 0x8D88 -#define GL_RGB16I 0x8D89 -#define GL_RGBA8I 0x8D8E -#define GL_RGB8I 0x8D8F -#define GL_RED_INTEGER 0x8D94 -#define GL_RG 0x8227 -#define GL_RG_INTEGER 0x8228 -#define GL_R8 0x8229 -#define GL_R16 0x822A -#define GL_RG8 0x822B -#define GL_RG16 0x822C -#define GL_R16F 0x822D -#define GL_R32F 0x822E -#define GL_RG16F 0x822F -#define GL_RG32F 0x8230 -#define GL_R8I 0x8231 -#define GL_R8UI 0x8232 -#define GL_R16I 0x8233 -#define GL_R16UI 0x8234 -#define GL_R32I 0x8235 -#define GL_R32UI 0x8236 -#define GL_RG8I 0x8237 -#define GL_RG8UI 0x8238 -#define GL_RG16I 0x8239 -#define GL_RG16UI 0x823A -#define GL_RG32I 0x823B -#define GL_RG32UI 0x823C -#define GL_RGBA_INTEGER 0x8D99 -#define GL_R8_SNORM 0x8F94 -#define GL_RG8_SNORM 0x8F95 -#define GL_RGB8_SNORM 0x8F96 -#define GL_RGBA8_SNORM 0x8F97 -#define GL_R16_SNORM 0x8F98 -#define GL_RG16_SNORM 0x8F99 -#define GL_RGB16_SNORM 0x8F9A -#define GL_RGBA16_SNORM 0x8F9B -#define GL_RGBA16 0x805B -#define GL_MAX_TEXTURE_SIZE 0x0D33 -#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C -#define GL_MAX_3D_TEXTURE_SIZE 0x8073 -#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF -#define GL_MAX_VERTEX_ATTRIBS 0x8869 -#define GL_CLAMP_TO_BORDER 0x812D -#define GL_TEXTURE_BORDER_COLOR 0x1004 -#define GL_CURRENT_PROGRAM 0x8B8D - -// X Macro list of GL function names and signatures -#define _SAPP_GL_FUNCS \ - _SAPP_XMACRO(glBindVertexArray, void, (GLuint array)) \ - _SAPP_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ - _SAPP_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ - _SAPP_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ - _SAPP_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ - _SAPP_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ - _SAPP_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ - _SAPP_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ - _SAPP_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ - _SAPP_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ - _SAPP_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ - _SAPP_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glUseProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ - _SAPP_XMACRO(glLinkProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ - _SAPP_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ - _SAPP_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ - _SAPP_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ - _SAPP_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ - _SAPP_XMACRO(glDeleteShader, void, (GLuint shader)) \ - _SAPP_XMACRO(glDeleteProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glCompileShader, void, (GLuint shader)) \ - _SAPP_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ - _SAPP_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ - _SAPP_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ - _SAPP_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ - _SAPP_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ - _SAPP_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ - _SAPP_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ - _SAPP_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ - _SAPP_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ - _SAPP_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ - _SAPP_XMACRO(glActiveTexture, void, (GLenum texture)) \ - _SAPP_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ - _SAPP_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ - _SAPP_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ - _SAPP_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ - _SAPP_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ - _SAPP_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ - _SAPP_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ - _SAPP_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ - _SAPP_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ - _SAPP_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glCreateShader, GLuint, (GLenum type)) \ - _SAPP_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glClearDepth, void, (GLdouble depth)) \ - _SAPP_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ - _SAPP_XMACRO(glCreateProgram, GLuint, (void)) \ - _SAPP_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ - _SAPP_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ - _SAPP_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ - _SAPP_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ - _SAPP_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ - _SAPP_XMACRO(glDisable, void, (GLenum cap)) \ - _SAPP_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ - _SAPP_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ - _SAPP_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ - _SAPP_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ - _SAPP_XMACRO(glDepthMask, void, (GLboolean flag)) \ - _SAPP_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ - _SAPP_XMACRO(glClearStencil, void, (GLint s)) \ - _SAPP_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ - _SAPP_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ - _SAPP_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ - _SAPP_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ - _SAPP_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ - _SAPP_XMACRO(glEnable, void, (GLenum cap)) \ - _SAPP_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ - _SAPP_XMACRO(glStencilMask, void, (GLuint mask)) \ - _SAPP_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ - _SAPP_XMACRO(glGetError, GLenum, (void)) \ - _SAPP_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ - _SAPP_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ - _SAPP_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ - _SAPP_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, GLfloat* params)) \ - _SAPP_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ - _SAPP_XMACRO(glDepthFunc, void, (GLenum func)) \ - _SAPP_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ - _SAPP_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ - _SAPP_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ - _SAPP_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ - _SAPP_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glReadBuffer, void, (GLenum src)) \ - _SAPP_XMACRO(glClear, void, (GLbitfield mask)) \ - _SAPP_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ - _SAPP_XMACRO(glFrontFace, void, (GLenum mode)) \ - _SAPP_XMACRO(glCullFace, void, (GLenum mode)) - -// generate GL function pointer typedefs -#define _SAPP_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; -_SAPP_GL_FUNCS -#undef _SAPP_XMACRO - -// generate GL function pointers -#define _SAPP_XMACRO(name, ret, args) static PFN_ ## name name; -_SAPP_GL_FUNCS -#undef _SAPP_XMACRO - -// helper function to lookup GL functions in GL DLL -_SOKOL_PRIVATE void* _sapp_win32_glgetprocaddr(const char* name) { - void* proc_addr = (void*) _sapp.wgl.GetProcAddress(name); - if (0 == proc_addr) { - proc_addr = (void*) GetProcAddress(_sapp.wgl.opengl32, name); - } - SOKOL_ASSERT(proc_addr); - return proc_addr; -} - -// populate GL function pointers -_SOKOL_PRIVATE void _sapp_win32_gl_loadfuncs(void) { - SOKOL_ASSERT(_sapp.wgl.GetProcAddress); - SOKOL_ASSERT(_sapp.wgl.opengl32); - #define _SAPP_XMACRO(name, ret, args) name = (PFN_ ## name) _sapp_win32_glgetprocaddr(#name); - _SAPP_GL_FUNCS - #undef _SAPP_XMACRO +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sapp_log_messages[] = { + _SAPP_LOG_ITEMS +}; +#undef _SAPP_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) + +static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sapp.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sapp_log_messages[log_item]; + } + #endif + _sapp.desc.logger.func("sapp", log_level, log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } } -#endif // _SAPP_WIN32 && SOKOL_GLCORE33 && !SOKOL_WIN32_NO_GL_LOADER +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} -/*=== PRIVATE HELPER FUNCTIONS ===============================================*/ -_SOKOL_PRIVATE void _sapp_fail(const char* msg) { - if (_sapp.desc.fail_cb) { - _sapp.desc.fail_cb(msg); +_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sapp.desc.allocator.alloc) { + ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data); + } + else { + ptr = malloc(size); + } + if (0 == ptr) { + _SAPP_PANIC(MALLOC_FAILED); } - else if (_sapp.desc.fail_userdata_cb) { - _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data); + return ptr; +} + +_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { + void* ptr = _sapp_malloc(size); + _sapp_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sapp_free(void* ptr) { + if (_sapp.desc.allocator.free) { + _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data); } else { - SOKOL_LOG(msg); + free(ptr); } - SOKOL_ABORT(); } +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers _SOKOL_PRIVATE void _sapp_call_init(void) { if (_sapp.desc.init_cb) { _sapp.desc.init_cb(); @@ -2582,24 +2977,40 @@ _SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { } } -_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* in_desc) { - sapp_desc desc = *in_desc; - desc.width = _sapp_def(desc.width, 640); - desc.height = _sapp_def(desc.height, 480); - desc.sample_count = _sapp_def(desc.sample_count, 1); - desc.swap_interval = _sapp_def(desc.swap_interval, 1); - desc.html5_canvas_name = _sapp_def(desc.html5_canvas_name, "canvas"); - desc.clipboard_size = _sapp_def(desc.clipboard_size, 8192); - desc.max_dropped_files = _sapp_def(desc.max_dropped_files, 1); - desc.max_dropped_file_path_length = _sapp_def(desc.max_dropped_file_path_length, 2048); - desc.window_title = _sapp_def(desc.window_title, "sokol_app"); - return desc; +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { + SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); + sapp_desc res = *desc; + res.sample_count = _sapp_def(res.sample_count, 1); + res.swap_interval = _sapp_def(res.swap_interval, 1); + // NOTE: can't patch the default for gl_major_version and gl_minor_version + // independently, because a desired version 4.0 would be patched to 4.2 + // (or expressed differently: zero is a valid value for gl_minor_version + // and can't be used to indicate 'default') + if (0 == res.gl_major_version) { + res.gl_major_version = 3; + res.gl_minor_version = 2; + } + res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas"); + res.clipboard_size = _sapp_def(res.clipboard_size, 8192); + res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); + res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); + res.window_title = _sapp_def(res.window_title, "sokol_app"); + return res; } _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { - _SAPP_CLEAR(_sapp_t, _sapp); + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->width >= 0); + SOKOL_ASSERT(desc->height >= 0); + SOKOL_ASSERT(desc->sample_count >= 0); + SOKOL_ASSERT(desc->swap_interval >= 0); + SOKOL_ASSERT(desc->clipboard_size >= 0); + SOKOL_ASSERT(desc->max_dropped_files >= 0); + SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); _sapp.desc = _sapp_desc_defaults(desc); _sapp.first_frame = true; + // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this _sapp.window_width = _sapp.desc.width; _sapp.window_height = _sapp.desc.height; _sapp.framebuffer_width = _sapp.window_width; @@ -2613,36 +3024,40 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; if (_sapp.clipboard.enabled) { _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; - _sapp.clipboard.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.clipboard.buf_size); + _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); } _sapp.drop.enabled = _sapp.desc.enable_dragndrop; if (_sapp.drop.enabled) { _sapp.drop.max_files = _sapp.desc.max_dropped_files; _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; - _sapp.drop.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.drop.buf_size); + _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); } _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); _sapp.desc.window_title = _sapp.window_title; _sapp.dpi_scale = 1.0f; _sapp.fullscreen = _sapp.desc.fullscreen; _sapp.mouse.shown = true; + _sapp_timing_init(&_sapp.timing); } _SOKOL_PRIVATE void _sapp_discard_state(void) { if (_sapp.clipboard.enabled) { SOKOL_ASSERT(_sapp.clipboard.buffer); - SOKOL_FREE((void*)_sapp.clipboard.buffer); + _sapp_free((void*)_sapp.clipboard.buffer); } if (_sapp.drop.enabled) { SOKOL_ASSERT(_sapp.drop.buffer); - SOKOL_FREE((void*)_sapp.drop.buffer); + _sapp_free((void*)_sapp.drop.buffer); + } + if (_sapp.default_icon_pixels) { + _sapp_free((void*)_sapp.default_icon_pixels); } - _SAPP_CLEAR(_sapp_t, _sapp); + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); } _SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { - memset(&_sapp.event, 0, sizeof(_sapp.event)); + _sapp_clear(&_sapp.event, sizeof(_sapp.event)); _sapp.event.type = type; _sapp.event.frame_count = _sapp.frame_count; _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; @@ -2673,7 +3088,7 @@ _SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { _SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { if (_sapp.drop.enabled) { SOKOL_ASSERT(_sapp.drop.buffer); - memset(_sapp.drop.buffer, 0, (size_t)_sapp.drop.buf_size); + _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); } } @@ -2686,7 +3101,176 @@ _SOKOL_PRIVATE void _sapp_frame(void) { _sapp.frame_count++; } -/*== MacOS/iOS ===============================================================*/ +_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) { + _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); + 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*) _sapp_malloc_clear((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); +} + +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>apple #if defined(_SAPP_APPLE) #if __has_feature(objc_arc) @@ -2695,7 +3279,13 @@ _SOKOL_PRIVATE void _sapp_frame(void) { #define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } #endif -/*== MacOS ===================================================================*/ +// ███ ███ █████ ██████ ██████ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ ███████ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██████ ██████ ███████ +// +// >>macos #if defined(_SAPP_MACOS) _SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { @@ -2814,6 +3404,11 @@ _SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { _SOKOL_PRIVATE void _sapp_macos_discard_state(void) { // NOTE: it's safe to call [release] on a nil object + if (_sapp.macos.keyup_monitor != nil) { + [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; + // NOTE: removeMonitor also releases the object + _sapp.macos.keyup_monitor = nil; + } _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); @@ -2824,14 +3419,48 @@ _SOKOL_PRIVATE void _sapp_macos_discard_state(void) { _SAPP_OBJC_RELEASE(_sapp.macos.window); } +// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end + +_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { + _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug + _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; +} + _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]; + + // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: + NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + [[NSApp keyWindow] sendEvent:event]; + } + return event; + }; + _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; + [NSApp run]; // NOTE: [NSApp run] never returns, instead cleanup code // must be put into applicationWillTerminate @@ -2846,7 +3475,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; @@ -2860,6 +3491,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; } @@ -2889,19 +3529,36 @@ _SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { } } +/* NOTE: unlike the iOS version of this function, the macOS version + can dynamically update the DPI scaling factor when a window is moved + between HighDPI / LowDPI screens. +*/ _SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; + } + else { + _sapp.dpi_scale = 1.0f; + } + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = (int)roundf(bounds.size.width); + _sapp.window_height = (int)roundf(bounds.size.height); #if defined(SOKOL_METAL) - const NSRect fb_rect = [_sapp.macos.view bounds]; - _sapp.framebuffer_width = fb_rect.size.width * _sapp.dpi_scale; - _sapp.framebuffer_height = fb_rect.size.height * _sapp.dpi_scale; + _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const CGSize fb_size = _sapp.macos.view.drawableSize; + const int cur_fb_width = (int)roundf(fb_size.width); + const int cur_fb_height = (int)roundf(fb_size.height); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); #elif defined(SOKOL_GLCORE33) - const NSRect fb_rect = [_sapp.macos.view convertRectToBacking:[_sapp.macos.view frame]]; - _sapp.framebuffer_width = fb_rect.size.width; - _sapp.framebuffer_height = fb_rect.size.height; + const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + _sapp.framebuffer_width = cur_fb_width; + _sapp.framebuffer_height = cur_fb_height; #endif - const NSRect bounds = [_sapp.macos.view bounds]; - _sapp.window_width = bounds.size.width; - _sapp.window_height = bounds.size.height; if (_sapp.framebuffer_width == 0) { _sapp.framebuffer_width = 1; } @@ -2914,13 +3571,17 @@ _SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { if (_sapp.window_height == 0) { _sapp.window_height = 1; } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; - - /* also make sure the MTKView drawable size is uptodate */ - #if defined(SOKOL_METAL) - CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.macos.view.drawableSize = drawable_size; - #endif + if (dim_changed) { + #if defined(SOKOL_METAL) + CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + #else + // nothing to do for GL? + #endif + if (!_sapp.first_frame) { + _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); + } + } } _SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { @@ -2961,13 +3622,13 @@ _SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; } -_SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) { +_SOKOL_PRIVATE void _sapp_macos_mouse_update(NSEvent* event) { if (!_sapp.mouse.locked) { const NSPoint mouse_pos = event.locationInWindow; float new_x = mouse_pos.x * _sapp.dpi_scale; float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; - /* don't update dx/dy in the very first update */ if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first update _sapp.mouse.dx = new_x - _sapp.mouse.x; _sapp.mouse.dy = new_y - _sapp.mouse.y; } @@ -3006,14 +3667,67 @@ _SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { */ if (_sapp.mouse.locked) { CGAssociateMouseAndMouseCursorPosition(NO); - CGDisplayHideCursor(kCGDirectMainDisplay); + [NSCursor hide]; } else { - CGDisplayShowCursor(kCGDirectMainDisplay); + [NSCursor unhide]; CGAssociateMouseAndMouseCursorPosition(YES); } } +_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { + // show/hide cursor only if visibility status has changed (required because show/hide stacks) + if (shown != _sapp.mouse.shown) { + if (shown) { + [NSCursor unhide]; + } + else { + [NSCursor hide]; + } + } + // update cursor type + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.macos.cursors[cursor]) { + [_sapp.macos.cursors[cursor] set]; + } + else { + [[NSCursor arrowCursor] set]; + } +} + +_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) { @@ -3024,20 +3738,17 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { @implementation _sapp_macos_app_delegate - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { _SOKOL_UNUSED(aNotification); - if (_sapp.fullscreen) { + _sapp_macos_init_cursors(); + if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { + // use 4/5 of screen size as default size NSRect screen_rect = NSScreen.mainScreen.frame; - _sapp.window_width = screen_rect.size.width; - _sapp.window_height = screen_rect.size.height; - } - if (_sapp.desc.high_dpi) { - _sapp.framebuffer_width = 2 * _sapp.window_width; - _sapp.framebuffer_height = 2 * _sapp.window_height; - } - else { - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; + if (_sapp.window_width == 0) { + _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); + } + if (_sapp.window_height == 0) { + _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f); + } } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; const NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | @@ -3057,10 +3768,16 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; _sapp.macos.window.delegate = _sapp.macos.win_dlg; #if defined(SOKOL_METAL) + NSInteger max_fps = 60; + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + if (@available(macOS 12.0, *)) { + max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; + } + #endif _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); _sapp.macos.view = [[_sapp_macos_view alloc] init]; [_sapp.macos.view updateTrackingAreas]; - _sapp.macos.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; _sapp.macos.view.device = _sapp.macos.mtl_device; _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; @@ -3074,7 +3791,15 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { int i = 0; attrs[i++] = NSOpenGLPFAAccelerated; attrs[i++] = NSOpenGLPFADoubleBuffer; - attrs[i++] = NSOpenGLPFAOpenGLProfile; attrs[i++] = NSOpenGLProfileVersion3_2Core; + attrs[i++] = NSOpenGLPFAOpenGLProfile; + const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; + switch(glVersion) { + case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; + case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; + case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; + default: + _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); + } attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; @@ -3114,14 +3839,14 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; timer_obj = nil; #endif + [_sapp.macos.window center]; _sapp.valid = true; 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]; - } + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + [NSApp activateIgnoringOtherApps:YES]; [_sapp.macos.window makeKeyAndOrderFront:nil]; _sapp_macos_update_dimensions(); [NSEvent setMouseCoalescingEnabled:NO]; @@ -3166,9 +3891,12 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { - (void)windowDidResize:(NSNotification*)notification { _SOKOL_UNUSED(notification); _sapp_macos_update_dimensions(); - if (!_sapp.first_frame) { - _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); - } +} + +- (void)windowDidChangeScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_timing_reset(&_sapp.timing); + _sapp_macos_update_dimensions(); } - (void)windowDidMiniaturize:(NSNotification*)notification { @@ -3181,6 +3909,16 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { _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; @@ -3218,12 +3956,12 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { NSPasteboard *pboard = [sender draggingPasteboard]; if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { _sapp_clear_drop_buffer(); - _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : pboard.pasteboardItems.count; + _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; bool drop_failed = false; for (int i = 0; i < _sapp.drop.num_files; i++) { NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); drop_failed = true; break; } @@ -3274,7 +4012,15 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { #endif _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - const NSEventMask mask = NSEventMaskLeftMouseDown | + /* + + NOTE: late event polling temporarily out-commented to check if this + causes infrequent and almost impossible to reproduce problems with the + window close events, see: + https://github.com/floooh/sokol/pull/483#issuecomment-805148815 + + + const NSEventMask mask = NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp| NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | @@ -3305,13 +4051,17 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { [NSApp sendEvent:event]; } } + */ } - (void)drawRect:(NSRect)rect { _SOKOL_UNUSED(rect); + _sapp_timing_measure(&_sapp.timing); /* 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 @@ -3342,90 +4092,90 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { [super updateTrackingAreas]; } - (void)mouseEntered:(NSEvent*)event { - _sapp_macos_update_mouse(event); + _sapp_macos_mouse_update(event); /* don't send mouse enter/leave while dragging (so that it behaves the same as 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); + _sapp_macos_mouse_update(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_update(event); + _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); @@ -3444,16 +4194,9 @@ _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); - /* 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 - */ + const uint32_t mods = _sapp_macos_mods(event); const sapp_keycode key_code = _sapp_translate_key(event.keyCode); _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); - if (0 != (mods & SAPP_MODIFIER_SUPER)) { - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods); - } const NSString* chars = event.characters; const NSUInteger len = chars.length; if (len > 0) { @@ -3480,11 +4223,11 @@ _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; - const uint32_t new_f = event.modifierFlags; + const uint32_t new_f = (uint32_t)event.modifierFlags; _sapp.macos.flags_changed_store = new_f; sapp_keycode key_code = SAPP_KEYCODE_INVALID; bool down = false; @@ -3508,20 +4251,20 @@ _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)); - } -} -- (void)cursorUpdate:(NSEvent*)event { - _SOKOL_UNUSED(event); - if (_sapp.desc.user_cursor) { - _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); + _sapp_macos_mods(event)); } } @end -#endif /* MacOS */ +#endif // macOS -/*== iOS =====================================================================*/ +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios #if defined(_SAPP_IOS) _SOKOL_PRIVATE void _sapp_ios_discard_state(void) { @@ -3585,26 +4328,31 @@ _SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet _SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.window_width = (int) screen_rect.size.width; - _sapp.window_height = (int) screen_rect.size.height; + _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); int cur_fb_width, cur_fb_height; #if defined(SOKOL_METAL) const CGSize fb_size = _sapp.ios.view.drawableSize; - cur_fb_width = (int) fb_size.width; - cur_fb_height = (int) fb_size.height; + cur_fb_width = (int)roundf(fb_size.width); + cur_fb_height = (int)roundf(fb_size.height); #else - cur_fb_width = (int) _sapp.ios.view.drawableWidth; - cur_fb_height = (int) _sapp.ios.view.drawableHeight; + cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); + cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); #endif - const bool dim_changed = - (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - _sapp.framebuffer_width = cur_fb_width; - _sapp.framebuffer_height = cur_fb_height; - SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; - if (dim_changed && !_sapp.first_frame) { - _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + #if defined(SOKOL_METAL) + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.ios.view.drawableSize = drawable_size; + #else + // nothing to do here, GLKView correctly respects the view's contentScaleFactor + #endif + if (!_sapp.first_frame) { + _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + } } } @@ -3651,31 +4399,30 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { CGRect screen_rect = UIScreen.mainScreen.bounds; _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; - _sapp.window_width = screen_rect.size.width; - _sapp.window_height = screen_rect.size.height; + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); if (_sapp.desc.high_dpi) { - _sapp.framebuffer_width = 2 * _sapp.window_width; - _sapp.framebuffer_height = 2 * _sapp.window_height; + _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; } else { - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; + _sapp.dpi_scale = 1.0f; } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; + _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); + NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; #if defined(SOKOL_METAL) _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); _sapp.ios.view = [[_sapp_ios_view alloc] init]; - _sapp.ios.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; _sapp.ios.view.device = _sapp.ios.mtl_device; _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; - if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = 2.0; - } - else { - _sapp.ios.view.contentScaleFactor = 1.0; - } + /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor + and automatically renders at Retina resolution. We'll disable + autoResize and instead do the resizing in _sapp_ios_update_dimensions() + */ + _sapp.ios.view.autoResizeDrawable = false; _sapp.ios.view.userInteractionEnabled = YES; _sapp.ios.view.multipleTouchEnabled = YES; _sapp.ios.view_ctrl = [[UIViewController alloc] init]; @@ -3698,20 +4445,22 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; - _sapp.ios.view.drawableMultisample = GLKViewDrawableMultisampleNone; /* FIXME */ + GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; + _sapp.ios.view.drawableMultisample = msaa; _sapp.ios.view.context = _sapp.ios.eagl_ctx; _sapp.ios.view.enableSetNeedsDisplay = NO; _sapp.ios.view.userInteractionEnabled = YES; _sapp.ios.view.multipleTouchEnabled = YES; + // on GLKView, contentScaleFactor appears to work just fine! if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = 2.0; + _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; } else { _sapp.ios.view.contentScaleFactor = 1.0; } _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.view_ctrl.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; #endif [_sapp.ios.window makeKeyAndVisible]; @@ -3823,7 +4572,10 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { @implementation _sapp_ios_view - (void)drawRect:(CGRect)rect { _SOKOL_UNUSED(rect); - _sapp_ios_frame(); + _sapp_timing_measure(&_sapp.timing); + @autoreleasepool { + _sapp_ios_frame(); + } } - (BOOL)isOpaque { return YES; @@ -3845,9 +4597,19 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { #endif /* _SAPP_APPLE */ -/*== EMSCRIPTEN ==============================================================*/ +// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ +// +// >>emscripten #if defined(_SAPP_EMSCRIPTEN) +#if defined(EM_JS_DEPS) +EM_JS_DEPS(sokol_app, "$withStackSave,$allocateUTF8OnStack"); +#endif + #ifdef __cplusplus extern "C" { #endif @@ -3903,7 +4665,7 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { return; } if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long!\n"); + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); _sapp.drop.num_files = 0; } } @@ -3930,13 +4692,14 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { sapp_html5_fetch_response response; - memset(&response, 0, sizeof(response)); + _sapp_clear(&response, sizeof(response)); response.succeeded = (0 != success); response.error_code = (sapp_html5_fetch_error) error_code; response.file_index = index; - response.fetched_size = fetched_size; - response.buffer_ptr = buf_ptr; - response.buffer_size = buf_size; + response.data.ptr = buf_ptr; + response.data.size = fetched_size; + response.buffer.ptr = buf_ptr; + response.buffer.size = buf_size; response.user_data = user_data; callback(&response); } @@ -3947,7 +4710,7 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int /* Javascript helper functions for mobile virtual keyboard input */ EM_JS(void, sapp_js_create_textfield, (void), { - var _sapp_inp = document.createElement("input"); + const _sapp_inp = document.createElement("input"); _sapp_inp.type = "text"; _sapp_inp.id = "_sokol_app_input_element"; _sapp_inp.autocapitalize = "none"; @@ -3967,7 +4730,7 @@ EM_JS(void, sapp_js_unfocus_textfield, (void), { }); EM_JS(void, sapp_js_add_beforeunload_listener, (void), { - Module.sokol_beforeunload = function(event) { + Module.sokol_beforeunload = (event) => { if (__sapp_html5_get_ask_leave_site() != 0) { event.preventDefault(); event.returnValue = ' '; @@ -3981,9 +4744,12 @@ EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { }); EM_JS(void, sapp_js_add_clipboard_listener, (void), { - Module.sokol_paste = function(event) { - var pasted_str = event.clipboardData.getData('text'); - ccall('_sapp_emsc_onpaste', 'void', ['string'], [pasted_str]); + Module.sokol_paste = (event) => { + const pasted_str = event.clipboardData.getData('text'); + withStackSave(() => { + const cstr = allocateUTF8OnStack(pasted_str); + __sapp_emsc_onpaste(cstr); + }); }; window.addEventListener('paste', Module.sokol_paste); }); @@ -3993,8 +4759,8 @@ EM_JS(void, sapp_js_remove_clipboard_listener, (void), { }); EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { - var str = UTF8ToString(c_str); - var ta = document.createElement('textarea'); + const str = UTF8ToString(c_str); + const ta = document.createElement('textarea'); ta.setAttribute('autocomplete', 'off'); ta.setAttribute('autocorrect', 'off'); ta.setAttribute('autocapitalize', 'off'); @@ -4016,29 +4782,31 @@ _SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { Module.sokol_drop_files = []; - var canvas_name = UTF8ToString(canvas_name_cstr); - var canvas = document.getElementById(canvas_name); - Module.sokol_dragenter = function(event) { + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + Module.sokol_dragenter = (event) => { event.stopPropagation(); event.preventDefault(); }; - Module.sokol_dragleave = function(event) { + Module.sokol_dragleave = (event) => { event.stopPropagation(); event.preventDefault(); }; - Module.sokol_dragover = function(event) { + Module.sokol_dragover = (event) => { event.stopPropagation(); event.preventDefault(); }; - Module.sokol_drop = function(event) { + Module.sokol_drop = (event) => { event.stopPropagation(); event.preventDefault(); - var files = event.dataTransfer.files; + const files = event.dataTransfer.files; Module.sokol_dropped_files = files; __sapp_emsc_begin_drop(files.length); - var i; - for (i = 0; i < files.length; i++) { - ccall('_sapp_emsc_drop', 'void', ['number', 'string'], [i, files[i].name]); + for (let i = 0; i < files.length; i++) { + withStackSave(() => { + const cstr = allocateUTF8OnStack(files[i].name); + __sapp_emsc_drop(i, cstr); + }); } // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect __sapp_emsc_end_drop(event.clientX, event.clientY); @@ -4050,18 +4818,20 @@ EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { }); EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { - if ((index < 0) || (index >= Module.sokol_dropped_files.length)) { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + if ((index < 0) || (index >= files.length)) { return 0; } else { - return Module.sokol_dropped_files[index].size; + return files[index].size; } }); EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { - var reader = new FileReader(); - reader.onload = function(loadEvent) { - var content = loadEvent.target.result; + const reader = new FileReader(); + reader.onload = (loadEvent) => { + const content = loadEvent.target.result; if (content.byteLength > buf_size) { // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); @@ -4071,16 +4841,18 @@ EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback c __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); } }; - reader.onerror = function() { + reader.onerror = () => { // SAPP_HTML5_FETCH_ERROR_OTHER __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); }; - reader.readAsArrayBuffer(Module.sokol_dropped_files[index]); + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + reader.readAsArrayBuffer(files[index]); }); EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { - var canvas_name = UTF8ToString(canvas_name_cstr); - var canvas = document.getElementById(canvas_name); + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); canvas.removeEventListener('dragenter', Module.sokol_dragenter); canvas.removeEventListener('dragleave', Module.sokol_dragleave); canvas.removeEventListener('dragover', Module.sokol_dragover); @@ -4126,9 +4898,9 @@ _SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { } } -EM_JS(void, sapp_js_pointer_init, (const char* c_str_target), { +EM_JS(void, sapp_js_init, (const char* c_str_target), { // lookup and store canvas object by name - var target_str = UTF8ToString(c_str_target); + const target_str = UTF8ToString(c_str_target); Module.sapp_emsc_target = document.getElementById(target_str); if (!Module.sapp_emsc_target) { console.log("sokol_app.h: invalid target:" + target_str); @@ -4190,11 +4962,111 @@ _SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { } } +// set mouse cursor type +EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { + if (Module.sapp_emsc_target) { + let cursor; + if (shown === 0) { + cursor = "none"; + } + else switch (cursor_type) { + case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT + case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW + case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM + case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR + case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND + case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW + case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS + case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE + case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW + case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL + case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED + default: cursor = "auto"; break; + } + Module.sapp_emsc_target.style.cursor = cursor; + } +}); + +_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + sapp_js_set_cursor((int)cursor, shown ? 1 : 0); +} + +/* JS helper functions to update browser tab favicon */ +EM_JS(void, sapp_js_clear_favicon, (void), { + const 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), { + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + const img_data = ctx.createImageData(w, h); + img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); + ctx.putImageData(img_data, 0, 0); + const 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); @@ -4224,19 +5096,19 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenU w = ui_event->windowInnerWidth; } else { - _sapp.window_width = (int) w; + _sapp.window_width = (int)roundf(w); } if (h < 1.0) { h = ui_event->windowInnerHeight; } else { - _sapp.window_height = (int) h; + _sapp.window_height = (int)roundf(h); } if (_sapp.desc.high_dpi) { _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); } - _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); #if defined(SOKOL_WGPU) @@ -4253,6 +5125,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; @@ -4295,18 +5168,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; @@ -4332,20 +5194,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) { @@ -4363,6 +5215,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; @@ -4386,19 +5359,9 @@ _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) { + // FIXME: this doesn't appear to work on Android Chrome _sapp.event.char_code = emsc_event->charCode; /* workaround to make Cmd+V work on Safari */ if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { @@ -4406,7 +5369,18 @@ _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); + if (0 != emsc_event->code[0]) { + // This code path is for desktop browsers which send untranslated 'physical' key code strings + // (which is what we actually want for key events) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); + } else { + // This code path is for mobile browsers which only send localized key code + // strings. Note that the translation will only work for a small subset + // of localization-agnostic keys (like Enter, arrow keys, etc...), but + // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); + } + /* 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 @@ -4419,7 +5393,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard { send_keyup_followup = true; } - /* only forward a certain key ranges to the browser */ + // only forward keys to the browser (can further be suppressed by sapp_consume_event()) switch (_sapp.event.key_code) { case SAPP_KEYCODE_WORLD_1: case SAPP_KEYCODE_WORLD_2: @@ -4485,7 +5459,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard } } if (_sapp_call_event(&_sapp.event)) { - /* consume event via sapp_consume_event() */ + // event was consumed via sapp_consume_event() retval = true; } if (send_keyup_followup) { @@ -4526,18 +5500,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; @@ -4557,108 +5520,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) @@ -4734,17 +5615,17 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, EM_JS(void, sapp_js_wgpu_init, (), { WebGPU.initManagers(); // FIXME: the extension activation must be more clever here - navigator.gpu.requestAdapter().then(function(adapter) { + navigator.gpu.requestAdapter().then((adapter) => { console.log("wgpu adapter extensions: " + adapter.extensions); - adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then(function(device) { + adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then((device) => { var gpuContext = document.getElementById("canvas").getContext("gpupresent"); console.log("wgpu device extensions: " + adapter.extensions); - gpuContext.getSwapChainPreferredFormat(device).then(function(fmt) { - var swapChainDescriptor = { device: device, format: fmt }; - var swapChain = gpuContext.configureSwapChain(swapChainDescriptor); - var deviceId = WebGPU.mgrDevice.create(device); - var swapChainId = WebGPU.mgrSwapChain.create(swapChain); - var fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); + gpuContext.getSwapChainPreferredFormat(device).then((fmt) => { + const swapChainDescriptor = { device: device, format: fmt }; + const swapChain = gpuContext.configureSwapChain(swapChainDescriptor); + const deviceId = WebGPU.mgrDevice.create(device); + const swapChainId = WebGPU.mgrSwapChain.create(swapChain); + const fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); console.log("wgpu device: " + device); console.log("wgpu swap chain: " + swapChain); console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); @@ -4763,7 +5644,7 @@ _SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); WGPUTextureDescriptor ds_desc; - memset(&ds_desc, 0, sizeof(ds_desc)); + _sapp_clear(&ds_desc, sizeof(ds_desc)); ds_desc.usage = WGPUTextureUsage_OutputAttachment; ds_desc.dimension = WGPUTextureDimension_2D; ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; @@ -4778,7 +5659,7 @@ _SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { if (_sapp.sample_count > 1) { WGPUTextureDescriptor msaa_desc; - memset(&msaa_desc, 0, sizeof(msaa_desc)); + _sapp_clear(&msaa_desc, sizeof(msaa_desc)); msaa_desc.usage = WGPUTextureUsage_OutputAttachment; msaa_desc.dimension = WGPUTextureDimension_2D; msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; @@ -4836,6 +5717,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(); @@ -4865,6 +5748,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(); @@ -4879,8 +5764,8 @@ _SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { } _SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { - _SOKOL_UNUSED(time); _SOKOL_UNUSED(userData); + _sapp_timing_external(&_sapp.timing, time / 1000.0); #if defined(SOKOL_WGPU) /* @@ -4927,12 +5812,11 @@ _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(); + sapp_js_init(&_sapp.html5_canvas_selector[1]); double w, h; if (_sapp.desc.html5_canvas_resize) { - w = (double) _sapp.desc.width; - h = (double) _sapp.desc.height; + w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); + h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); } else { emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); @@ -4941,10 +5825,10 @@ _SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { if (_sapp.desc.high_dpi) { _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); } - _sapp.window_width = (int) w; - _sapp.window_height = (int) h; - _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); + _sapp.window_width = (int)roundf(w); + _sapp.window_height = (int)roundf(h); + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) _sapp_emsc_webgl_init(); @@ -4953,6 +5837,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); @@ -4971,7 +5856,13 @@ int main(int argc, char* argv[]) { #endif /* SOKOL_NO_ENTRY */ #endif /* _SAPP_EMSCRIPTEN */ -/*== MISC GL SUPPORT FUNCTIONS ================================================*/ +// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>gl helpers #if defined(SOKOL_GLCORE33) typedef struct { int red_bits; @@ -4986,7 +5877,7 @@ typedef struct { } _sapp_gl_fbconfig; _SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { - memset(fbconfig, 0, sizeof(_sapp_gl_fbconfig)); + _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); /* -1 means "don't care" */ fbconfig->red_bits = -1; fbconfig->green_bits = -1; @@ -5080,11 +5971,17 @@ _SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_ } #endif -/*== WINDOWS DESKTOP and UWP====================================================*/ -#if defined(_SAPP_WIN32) || defined(_SAPP_UWP) -_SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if defined(_SAPP_WIN32) +_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - memset(dst, 0, (size_t)dst_num_bytes); + _sapp_clear(dst, (size_t)dst_num_bytes); const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); if ((dst_needed > 0) && (dst_needed < dst_chars)) { @@ -5097,14 +5994,14 @@ _SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, } } -_SOKOL_PRIVATE void _sapp_win32_uwp_app_event(sapp_event_type type) { +_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { if (_sapp_events_enabled()) { _sapp_init_event(type); _sapp_call_event(&_sapp.event); } } -_SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { +_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { /* same as GLFW */ _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; _sapp.keycodes[0x002] = SAPP_KEYCODE_1; @@ -5225,21 +6122,27 @@ _SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; } -#endif // _SAPP_WIN32 || _SAPP_UWP +#endif // _SAPP_WIN32 -/*== WINDOWS DESKTOP===========================================================*/ #if defined(_SAPP_WIN32) #if defined(SOKOL_D3D11) #if defined(__cplusplus) #define _sapp_d3d11_Release(self) (self)->Release() +#define _sapp_win32_refiid(iid) iid #else #define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#define _sapp_win32_refiid(iid) &iid #endif #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 const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; +static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; + static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { #if defined(__cplusplus) return self->GetBuffer(Buffer, riid, ppSurface); @@ -5248,6 +6151,14 @@ static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, RE #endif } +static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { + #if defined(__cplusplus) + return self->QueryInterface(riid, ppvObject); + #else + return self->lpVtbl->QueryInterface(self, riid, ppvObject); + #endif +} + static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { #if defined(__cplusplus) return self->CreateRenderTargetView(pResource, pDesc, ppRTView); @@ -5296,6 +6207,46 @@ static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval #endif } +static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { + #if defined(__cplusplus) + return self->GetFrameStatistics(pStats); + #else + return self->lpVtbl->GetFrameStatistics(self, pStats); + #endif +} + +static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { + #if defined(__cplusplus) + return self->SetMaximumFrameLatency(MaxLatency); + #else + return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); + #endif +} + +static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { + #if defined(__cplusplus) + return self->GetAdapter(pAdapter); + #else + return self->lpVtbl->GetAdapter(self, pAdapter); + #endif +} + +static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { + #if defined(__cplusplus) + return self->GetParent(riid, ppParent); + #else + return self->lpVtbl->GetParent(self, riid, ppParent); + #endif +} + +static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { + #if defined(__cplusplus) + return self->MakeWindowAssociation(WindowHandle, Flags); + #else + return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); + #endif +} + _SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; @@ -5308,10 +6259,12 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { if (_sapp.win32.is_win10_or_greater) { sc_desc->BufferCount = 2; sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = true; } else { sc_desc->BufferCount = 1; sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = false; } sc_desc->SampleDesc.Count = 1; sc_desc->SampleDesc.Quality = 0; @@ -5335,11 +6288,66 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { &feature_level, /* pFeatureLevel */ &_sapp.d3d11.device_context); /* ppImmediateContext */ _SOKOL_UNUSED(hr); + #if defined(SOKOL_DEBUG) + if (!SUCCEEDED(hr)) { + // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the + // 'D3D11 debug layer' stopped working, indicated by the error message: + // === + // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. + // These flags must be removed, or the Windows 10 SDK must be installed. + // Flags include: D3D11_CREATE_DEVICE_DEBUG + // === + // + // ...just retry with the DEBUG flag switched off + _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); + create_flags &= ~D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + } + #endif SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); + + // minimize frame latency, disable Alt-Enter + hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); + if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { + _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); + IDXGIAdapter* dxgi_adapter = 0; + hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); + if (SUCCEEDED(hr) && dxgi_adapter) { + IDXGIFactory* dxgi_factory = 0; + hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); + if (SUCCEEDED(hr)) { + _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); + _SAPP_SAFE_RELEASE(dxgi_factory); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); + } + _SAPP_SAFE_RELEASE(dxgi_adapter); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); + } + } + else { + _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); + } } _SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); _SAPP_SAFE_RELEASE(_sapp.d3d11.device); } @@ -5355,18 +6363,14 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { HRESULT hr; /* view for the swapchain-created framebuffer */ - #ifdef __cplusplus - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); - #else - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); - #endif + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); /* common desc for MSAA and depth-stencil texture */ D3D11_TEXTURE2D_DESC tex_desc; - memset(&tex_desc, 0, sizeof(tex_desc)); + _sapp_clear(&tex_desc, sizeof(tex_desc)); tex_desc.Width = (UINT)_sapp.framebuffer_width; tex_desc.Height = (UINT)_sapp.framebuffer_height; tex_desc.MipLevels = 1; @@ -5411,14 +6415,22 @@ _SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { } } -_SOKOL_PRIVATE void _sapp_d3d11_present(void) { +_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { /* do MSAA resolve if needed */ if (_sapp.sample_count > 1) { SOKOL_ASSERT(_sapp.d3d11.rt); SOKOL_ASSERT(_sapp.d3d11.msaa_rt); _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); } - _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, 0); + UINT flags = 0; + if (_sapp.win32.is_win10_or_greater && do_not_wait) { + /* this hack/workaround somewhat improves window-movement and -sizing + responsiveness when rendering is controlled via WM_TIMER during window + move and resize on NVIDIA cards on Win10 with recent drivers. + */ + flags = DXGI_PRESENT_DO_NOT_WAIT; + } + _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); } #endif /* SOKOL_D3D11 */ @@ -5427,7 +6439,7 @@ _SOKOL_PRIVATE void _sapp_d3d11_present(void) { _SOKOL_PRIVATE void _sapp_wgl_init(void) { _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); if (!_sapp.wgl.opengl32) { - _sapp_fail("Failed to load opengl32.dll\n"); + _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); } SOKOL_ASSERT(_sapp.wgl.opengl32); _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); @@ -5450,7 +6462,7 @@ _SOKOL_PRIVATE void _sapp_wgl_init(void) { GetModuleHandleW(NULL), NULL); if (!_sapp.wgl.msg_hwnd) { - _sapp_fail("Win32: failed to create helper window!\n"); + _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); } SOKOL_ASSERT(_sapp.wgl.msg_hwnd); ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); @@ -5461,7 +6473,7 @@ _SOKOL_PRIVATE void _sapp_wgl_init(void) { } _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); if (!_sapp.wgl.msg_dc) { - _sapp_fail("Win32: failed to obtain helper window DC!\n"); + _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); } } @@ -5514,21 +6526,21 @@ _SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { _SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { SOKOL_ASSERT(_sapp.wgl.msg_dc); PIXELFORMATDESCRIPTOR pfd; - memset(&pfd, 0, sizeof(pfd)); + _sapp_clear(&pfd, sizeof(pfd)); pfd.nSize = sizeof(pfd); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { - _sapp_fail("WGL: failed to set pixel format for dummy context\n"); + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); } HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); if (!rc) { - _sapp_fail("WGL: Failed to create dummy context\n"); + _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); } if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { - _sapp_fail("WGL: Failed to make context current\n"); + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); } _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); @@ -5548,7 +6560,7 @@ _SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); int value = 0; if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { - _sapp_fail("WGL: Failed to retrieve pixel format attribute\n"); + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); } return value; } @@ -5559,7 +6571,7 @@ _SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { const _sapp_gl_fbconfig* closest; int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); - _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); + _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); SOKOL_ASSERT(usable_configs); int usable_count = 0; for (int i = 0; i < native_count; i++) { @@ -5606,31 +6618,31 @@ _SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { if (closest) { pixel_format = (int) closest->handle; } - SOKOL_FREE(usable_configs); + _sapp_free(usable_configs); return pixel_format; } _SOKOL_PRIVATE void _sapp_wgl_create_context(void) { int pixel_format = _sapp_wgl_find_pixel_format(); if (0 == pixel_format) { - _sapp_fail("WGL: Didn't find matching pixel format.\n"); + _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); } PIXELFORMATDESCRIPTOR pfd; if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { - _sapp_fail("WGL: Failed to retrieve PFD for selected pixel format!\n"); + _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); } if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { - _sapp_fail("WGL: Failed to set selected pixel format!\n"); + _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); } if (!_sapp.wgl.arb_create_context) { - _sapp_fail("WGL: ARB_create_context required!\n"); + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); } if (!_sapp.wgl.arb_create_context_profile) { - _sapp_fail("WGL: ARB_create_context_profile required!\n"); + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); } const int attrs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0, 0 @@ -5639,16 +6651,16 @@ _SOKOL_PRIVATE void _sapp_wgl_create_context(void) { if (!_sapp.wgl.gl_ctx) { const DWORD err = GetLastError(); if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { - _sapp_fail("WGL: Driver does not support OpenGL version 3.3\n"); + _SAPP_PANIC(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED); } else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { - _sapp_fail("WGL: Driver does not support the requested OpenGL profile"); + _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); } else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { - _sapp_fail("WGL: The share context is not compatible with the requested context"); + _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); } else { - _sapp_fail("WGL: Failed to create OpenGL context"); + _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); } } _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); @@ -5673,7 +6685,7 @@ _SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { _SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - memset(dst, 0, (size_t)dst_num_bytes); + _sapp_clear(dst, (size_t)dst_num_bytes); const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); if (bytes_needed <= dst_num_bytes) { WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); @@ -5684,10 +6696,40 @@ _SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int } } -_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { + RECT rect; + if (GetClientRect(_sapp.win32.hwnd, &rect)) { + float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; + float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; + _sapp.window_width = (int)roundf(window_width); + _sapp.window_height = (int)roundf(window_height); + int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); + int fb_height = (int)roundf(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; + return true; + } + } + else { + _sapp.window_width = _sapp.window_height = 1; + _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + } + return false; +} + +_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO minfo; - memset(&minfo, 0, sizeof(minfo)); + _sapp_clear(&minfo, sizeof(minfo)); minfo.cbSize = sizeof(MONITORINFO); GetMonitorInfo(monitor, &minfo); const RECT mr = minfo.rcMonitor; @@ -5698,32 +6740,99 @@ _SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { DWORD win_style; RECT rect = { 0, 0, 0, 0 }; - _sapp.fullscreen = !_sapp.fullscreen; + _sapp.fullscreen = fullscreen; if (!_sapp.fullscreen) { win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.desc.width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.desc.height * _sapp.win32.dpi.window_scale); + rect = _sapp.win32.stored_window_rect; } else { + GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.right = monitor_w; - rect.bottom = monitor_h; + rect.left = mr.left; + rect.top = mr.top; + rect.right = rect.left + monitor_w; + rect.bottom = rect.top + monitor_h; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + } + const int win_w = rect.right - rect.left; + const int win_h = rect.bottom - rect.top; + const int win_x = rect.left; + const int win_y = rect.top; + SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { + _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined + // before windows.h is included, but we can't guarantee that because + // the sokol_app.h implementation may be included with other implementations + // in the same compilation unit + int id = 0; + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL + case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM + case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS + case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND + case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE + case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS + case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE + case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW + case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL + case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO + default: break; } - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - int win_width = rect.right - rect.left; - int win_height = rect.bottom - rect.top; - if (!_sapp.fullscreen) { - rect.left = (monitor_w - win_width) / 2; - rect.top = (monitor_h - win_height) / 2; + if (id != 0) { + _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); } + // fallback: default cursor + if (0 == _sapp.win32.cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); +} - SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); - SetWindowPos(_sapp.win32.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); +_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp_win32_init_cursor((sapp_mouse_cursor)i); + } } -_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool visible) { - /* NOTE: this function is only called when the mouse visibility actually changes */ - ShowCursor((BOOL)visible); +_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { + POINT pos; + if (!GetCursorPos(&pos)) { + return false; + } + if (WindowFromPoint(pos) != _sapp.win32.hwnd) { + return false; + } + RECT area; + GetClientRect(_sapp.win32.hwnd, &area); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); + return PtInRect(&area, pos) == TRUE; +} + +_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + if (!shown) { + SetCursor(NULL); + } + else { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); + SetCursor(_sapp.win32.cursors[cursor]); + } } _SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { @@ -5760,7 +6869,7 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { /* while the mouse is locked, make the mouse cursor invisible and confine the mouse movement to a small rectangle inside our window - (so that we dont miss any mouse up events) + (so that we don't miss any mouse up events) */ RECT client_rect = { _sapp.win32.mouse_locked_x, @@ -5781,7 +6890,7 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { _sapp.win32.hwnd // hwndTarget }; if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - SOKOL_LOG("RegisterRawInputDevices() failed (on mouse lock).\n"); + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); } /* in case the raw mouse device only supports absolute position reporting, we need to skip the dx/dy compution for the first WM_INPUT event @@ -5792,7 +6901,7 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { /* disable raw input for mouse */ const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - SOKOL_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n"); + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); } /* let the mouse roam freely again */ @@ -5805,32 +6914,15 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { } } -/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ -_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { - RECT rect; - 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); - 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; - } +_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { + const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + if (cur_monitor != _sapp.win32.hmonitor) { + _sapp.win32.hmonitor = cur_monitor; + return true; } else { - _sapp.window_width = _sapp.window_height = 1; - _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + return false; } - return false; } _SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { @@ -5847,9 +6939,34 @@ _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; } +_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { + if (!_sapp.mouse.locked) { + const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first event + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + _SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { if (_sapp_events_enabled()) { _sapp_init_event(type); @@ -5898,6 +7015,34 @@ _SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { } } +_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { + /* called on WM_DPICHANGED, which will only be sent to the application + if sapp_desc.high_dpi is true and the Windows version is recent enough + to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + */ + SOKOL_ASSERT(_sapp.desc.high_dpi); + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (!user32) { + return; + } + typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); + GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); + if (fn_getdpiforwindow) { + UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); + // NOTE: for high-dpi apps, mouse_scale remains one + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.dpi_scale = _sapp.win32.dpi.window_scale; + SetWindowPos(hWnd, 0, + proposed_win_rect->left, + proposed_win_rect->top, + proposed_win_rect->right - proposed_win_rect->left, + proposed_win_rect->bottom - proposed_win_rect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + FreeLibrary(user32); +} + _SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { if (!_sapp.drop.enabled) { return; @@ -5908,13 +7053,13 @@ _SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; - WCHAR* buffer = (WCHAR*) SOKOL_CALLOC(num_chars, sizeof(WCHAR)); + WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); DragQueryFileW(hdrop, i, buffer, num_chars); if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); drop_failed = true; } - SOKOL_FREE(buffer); + _sapp_free(buffer); } DragFinish(hdrop); if (!drop_failed) { @@ -5929,6 +7074,34 @@ _SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { } } +_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { + #if defined(SOKOL_D3D11) + // on D3D11, use the more precise DXGI timestamp + if (_sapp.d3d11.use_dxgi_frame_stats) { + DXGI_FRAME_STATISTICS dxgi_stats; + _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); + HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); + if (SUCCEEDED(hr)) { + if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { + if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { + _sapp_timing_discontinuity(&_sapp.timing); + } + _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; + LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); + _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); + } + return; + } + } + // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_timing_measure(&_sapp.timing); + #endif +} + _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!_sapp.win32.in_create_window) { switch (uMsg) { @@ -5939,7 +7112,7 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM a change to intervene via sapp_cancel_quit() */ _sapp.quit_requested = true; - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); /* if user code hasn't intervened, quit the app */ if (_sapp.quit_requested) { _sapp.quit_ordered = true; @@ -5971,62 +7144,75 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM if (iconified != _sapp.win32.iconified) { _sapp.win32.iconified = iconified; if (iconified) { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_ICONIFIED); + _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); } else { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESTORED); + _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); } } } break; + case WM_SETFOCUS: + _sapp_win32_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_app_event(SAPP_EVENTTYPE_UNFOCUSED); + break; case WM_SETCURSOR: - if (_sapp.desc.user_cursor) { - if (LOWORD(lParam) == HTCLIENT) { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - return 1; - } + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); + return TRUE; } break; + case WM_DPICHANGED: + { + /* Update window's DPI and size if its moved to another monitor with a different DPI + Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. + */ + _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); + break; + } case WM_LBUTTONDOWN: + _sapp_win32_mouse_update(lParam); _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { /* mouse only reports absolute position - NOTE: THIS IS UNTESTED, it's unclear from reading the - Win32 RawInput docs under which circumstances absolute - positions are sent. + NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. + (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). + See: https://github.com/floooh/sokol/issues/806 and + https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) */ + LONG new_x = raw_mouse_data->data.mouse.lLastX; + LONG new_y = raw_mouse_data->data.mouse.lLastY; if (_sapp.win32.raw_input_mousepos_valid) { - LONG new_x = raw_mouse_data->data.mouse.lLastX; - LONG new_y = raw_mouse_data->data.mouse.lLastY; _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); - _sapp.win32.raw_input_mousepos_x = new_x; - _sapp.win32.raw_input_mousepos_y = new_y; - _sapp.win32.raw_input_mousepos_valid = true; } + _sapp.win32.raw_input_mousepos_x = new_x; + _sapp.win32.raw_input_mousepos_y = new_y; + _sapp.win32.raw_input_mousepos_valid = true; } else { /* mouse reports movement delta (this seems to be the common case) */ @@ -6079,9 +7266,11 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM } break; case WM_MOUSEWHEEL: + _sapp_win32_mouse_update(lParam); _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); break; case WM_MOUSEHWHEEL: + _sapp_win32_mouse_update(lParam); _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); break; case WM_CHAR: @@ -6102,9 +7291,11 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM KillTimer(_sapp.win32.hwnd, 1); break; case WM_TIMER: + _sapp_win32_timing_measure(); _sapp_frame(); #if defined(SOKOL_D3D11) - _sapp_d3d11_present(); + // present with DXGI_PRESENT_DO_NOT_WAIT + _sapp_d3d11_present(true); #endif #if defined(SOKOL_GLCORE33) _sapp_wgl_swap_buffers(); @@ -6116,13 +7307,29 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM #if defined(SOKOL_D3D11) _sapp_d3d11_resize_default_render_target(); #endif - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); } */ break; + case WM_NCLBUTTONDOWN: + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT point; + GetCursorPos(&point); + ScreenToClient(_sapp.win32.hwnd, &point); + PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + } + break; case WM_DROPFILES: _sapp_win32_files_dropped((HDROP)wParam); break; + case WM_DISPLAYCHANGE: + // refresh rate might have changed + _sapp_timing_reset(&_sapp.timing); + break; + default: break; } @@ -6132,7 +7339,7 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM _SOKOL_PRIVATE void _sapp_win32_create_window(void) { WNDCLASSW wndclassw; - memset(&wndclassw, 0, sizeof(wndclassw)); + _sapp_clear(&wndclassw, sizeof(wndclassw)); wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; wndclassw.hInstance = GetModuleHandleW(NULL); @@ -6141,42 +7348,50 @@ _SOKOL_PRIVATE void _sapp_win32_create_window(void) { wndclassw.lpszClassName = L"SOKOLAPP"; RegisterClassW(&wndclassw); - DWORD win_style; + /* NOTE: regardless whether fullscreen is requested or not, a regular + windowed-mode window will always be created first (however in hidden + mode, so that no windowed-mode window pops up before the fullscreen window) + */ const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; RECT rect = { 0, 0, 0, 0 }; - if (_sapp.fullscreen) { - win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.right = GetSystemMetrics(SM_CXSCREEN); - rect.bottom = GetSystemMetrics(SM_CYSCREEN); - } - else { - win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); - } + DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); + const bool use_default_width = 0 == _sapp.window_width; + const bool use_default_height = 0 == _sapp.window_height; AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); const int win_width = rect.right - rect.left; const int win_height = rect.bottom - rect.top; _sapp.win32.in_create_window = true; _sapp.win32.hwnd = CreateWindowExW( - win_ex_style, /* dwExStyle */ - L"SOKOLAPP", /* lpClassName */ - _sapp.window_title_wide, /* lpWindowName */ - win_style, /* dwStyle */ - CW_USEDEFAULT, /* X */ - CW_USEDEFAULT, /* Y */ - win_width, /* nWidth */ - win_height, /* nHeight */ - NULL, /* hWndParent */ - NULL, /* hMenu */ - GetModuleHandle(NULL), /* hInstance */ - NULL); /* lParam */ - ShowWindow(_sapp.win32.hwnd, SW_SHOW); + win_ex_style, // dwExStyle + L"SOKOLAPP", // lpClassName + _sapp.window_title_wide, // lpWindowName + win_style, // dwStyle + CW_USEDEFAULT, // X + SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! + use_default_width ? CW_USEDEFAULT : win_width, // nWidth + use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) + NULL, // hWndParent + NULL, // hMenu + GetModuleHandle(NULL), // hInstance + NULL); // lParam _sapp.win32.in_create_window = false; _sapp.win32.dc = GetDC(_sapp.win32.hwnd); + _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); SOKOL_ASSERT(_sapp.win32.dc); - _sapp_win32_update_dimensions(); + /* this will get the actual windowed-mode window size, if fullscreen + is requested, the set_fullscreen function will then capture the + current window rectangle, which then might be used later to + restore the window position when switching back to windowed + */ + _sapp_win32_update_dimensions(); + if (_sapp.fullscreen) { + _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); + _sapp_win32_update_dimensions(); + } + ShowWindow(_sapp.win32.hwnd, SW_SHOW); DragAcceptFiles(_sapp.win32.hwnd, 1); } @@ -6185,6 +7400,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; @@ -6195,8 +7421,12 @@ _SOKOL_PRIVATE void _sapp_win32_init_console(void) { con_valid = AttachConsole(ATTACH_PARENT_PROCESS); } if (con_valid) { - freopen("CON", "w", stdout); - freopen("CON", "w", stderr); + FILE* res_fp = 0; + errno_t err; + err = freopen_s(&res_fp, "CON", "w", stdout); + (void)err; + err = freopen_s(&res_fp, "CON", "w", stderr); + (void)err; } } if (_sapp.desc.win32_console_utf8) { @@ -6213,35 +7443,56 @@ _SOKOL_PRIVATE void _sapp_win32_restore_console(void) { _SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { + DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); + typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; GETDPIFORMONITOR_T fn_getdpiformonitor = 0; + SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; + HINSTANCE user32 = LoadLibraryA("user32.dll"); if (user32) { fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); + fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); } HINSTANCE shcore = LoadLibraryA("shcore.dll"); if (shcore) { fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); } + /* + NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): + + These are different attempts to get DPI handling on Windows right, from oldest + to newest. SetProcessDpiAwarenessContext() is required for the new + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. + */ if (fn_setprocessdpiawareness) { - /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ - PROCESS_DPI_AWARENESS process_dpi_awareness = PROCESS_SYSTEM_DPI_AWARE; - _sapp.win32.dpi.aware = true; - if (!_sapp.desc.high_dpi) { - process_dpi_awareness = PROCESS_DPI_UNAWARE; + if (_sapp.desc.high_dpi) { + /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, + if that fails, fall back to system-dpi-awareness + */ + _sapp.win32.dpi.aware = true; + DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; + if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { + // fallback to system-dpi-aware + fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); + } + } + else { + /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ _sapp.win32.dpi.aware = false; + fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); } - fn_setprocessdpiawareness(process_dpi_awareness); } else if (fn_setprocessdpiaware) { - fn_setprocessdpiaware(); + // fallback for Windows 7 _sapp.win32.dpi.aware = true; + fn_setprocessdpiaware(); } /* get dpi scale factor for main monitor */ if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { @@ -6279,26 +7530,32 @@ _SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { SOKOL_ASSERT(_sapp.win32.hwnd); SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + if (!OpenClipboard(_sapp.win32.hwnd)) { + return false; + } + + HANDLE object = 0; wchar_t* wchar_buf = 0; + const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); - HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); - if (!object) { + object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); + if (NULL == object) { goto error; } wchar_buf = (wchar_t*) GlobalLock(object); - if (!wchar_buf) { + if (NULL == wchar_buf) { goto error; } - if (!_sapp_win32_uwp_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { + if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { goto error; } - GlobalUnlock(wchar_buf); + GlobalUnlock(object); wchar_buf = 0; - if (!OpenClipboard(_sapp.win32.hwnd)) { + EmptyClipboard(); + // NOTE: when successful, SetClipboardData() takes ownership of memory object! + if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { goto error; } - EmptyClipboard(); - SetClipboardData(CF_UNICODETEXT, object); CloseClipboard(); return true; @@ -6309,6 +7566,7 @@ _SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { if (object) { GlobalFree(object); } + CloseClipboard(); return false; } @@ -6332,7 +7590,7 @@ _SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { return _sapp.clipboard.buffer; } if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { - SOKOL_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n"); + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); } GlobalUnlock(object); CloseClipboard(); @@ -6340,10 +7598,89 @@ _SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { } _SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { - _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); } +_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { + BITMAPV5HEADER bi; + _sapp_clear(&bi, 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; + _sapp_clear(&icon_info, 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 @@ -6363,10 +7700,12 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { _sapp_init_state(desc); _sapp_win32_init_console(); _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); - _sapp_win32_uwp_init_keytable(); - _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + _sapp_win32_init_keytable(); + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); _sapp_win32_init_dpi(); + _sapp_win32_init_cursors(); _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(); @@ -6375,14 +7714,12 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { _sapp_wgl_init(); _sapp_wgl_load_extensions(); _sapp_wgl_create_context(); - #if !defined(SOKOL_WIN32_NO_GL_LOADER) - _sapp_win32_gl_loadfuncs(); - #endif #endif _sapp.valid = true; bool done = false; while (!(done || _sapp.quit_ordered)) { + _sapp_win32_timing_measure(); MSG msg; while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { if (WM_QUIT == msg.message) { @@ -6391,12 +7728,12 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { } else { TranslateMessage(&msg); - DispatchMessage(&msg); + DispatchMessageW(&msg); } } _sapp_frame(); #if defined(SOKOL_D3D11) - _sapp_d3d11_present(); + _sapp_d3d11_present(false); if (IsIconic(_sapp.win32.hwnd)) { Sleep((DWORD)(16 * _sapp.swap_interval)); } @@ -6409,7 +7746,13 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { #if defined(SOKOL_D3D11) _sapp_d3d11_resize_default_render_target(); #endif - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + /* check if the window monitor has changed, need to reset timing because + the new monitor might have a different refresh rate + */ + if (_sapp_win32_update_monitor()) { + _sapp_timing_reset(&_sapp.timing); } if (_sapp.quit_requested) { PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); @@ -6425,6 +7768,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(); } @@ -6436,17 +7780,17 @@ _SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_lin LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); if (w_argv == NULL) { - _sapp_fail("Win32: failed to parse command line"); + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! } else { size_t size = wcslen(w_command_line) * 4; - argv = (char**) SOKOL_CALLOC(1, ((size_t)argc + 1) * sizeof(char*) + size); + argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); SOKOL_ASSERT(argv); args = (char*) &argv[argc + 1]; int n; for (int i = 0; i < argc; ++i) { n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); if (n == 0) { - _sapp_fail("Win32: failed to convert all arguments to utf8"); + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! break; } argv[i] = args; @@ -6476,7 +7820,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); sapp_desc desc = sokol_main(argc_utf8, argv_utf8); _sapp_win32_run(&desc); - SOKOL_FREE(argv_utf8); + _sapp_free(argv_utf8); return 0; } #endif /* SOKOL_WIN32_FORCE_MAIN */ @@ -6488,1008 +7832,13 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ #endif /* _SAPP_WIN32 */ -/*== UWP ================================================================*/ -#if defined(_SAPP_UWP) - -// Helper functions -_SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) { - _sapp.uwp.dpi.window_scale = monitor_dpi / 96.0f; - if (_sapp.desc.high_dpi) { - _sapp.uwp.dpi.content_scale = _sapp.uwp.dpi.window_scale; - _sapp.uwp.dpi.mouse_scale = 1.0f * _sapp.uwp.dpi.window_scale; - } - else { - _sapp.uwp.dpi.content_scale = 1.0f; - _sapp.uwp.dpi.mouse_scale = 1.0f; - } - _sapp.dpi_scale = _sapp.uwp.dpi.content_scale; -} - -_SOKOL_PRIVATE void _sapp_uwp_show_mouse(bool visible) { - using namespace winrt::Windows::UI::Core; - - /* NOTE: this function is only called when the mouse visibility actually changes */ - CoreWindow::GetForCurrentThread().PointerCursor(visible ? - CoreCursor(CoreCursorType::Arrow, 0) : - CoreCursor(nullptr)); -} - -_SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) { - using namespace winrt::Windows::System; - using namespace winrt::Windows::UI::Core; - - uint32_t mods = 0; - if ((sender_window.GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_SHIFT; - } - if ((sender_window.GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_CTRL; - } - if ((sender_window.GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_ALT; - } - if (((sender_window.GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) || - ((sender_window.GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)) - { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_uwp_mouse_event(sapp_event_type type, sapp_mousebutton btn, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.mouse_button = btn; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_scroll_event(float delta, bool horizontal, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.scroll_x = horizontal ? (-delta / 30.0f) : 0.0f; - _sapp.event.scroll_y = horizontal ? 0.0f : (delta / 30.0f); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_extract_mouse_button_events(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - - // we need to figure out ourselves what mouse buttons have been pressed and released, - // because UWP doesn't properly send down/up mouse button events when multiple buttons - // are pressed down, so we also need to check the mouse button state in other mouse events - // to track what buttons have been pressed down and released - // - auto properties = args.CurrentPoint().Properties(); - const uint8_t lmb_bit = (1 << SAPP_MOUSEBUTTON_LEFT); - const uint8_t rmb_bit = (1 << SAPP_MOUSEBUTTON_RIGHT); - const uint8_t mmb_bit = (1 << SAPP_MOUSEBUTTON_MIDDLE); - uint8_t new_btns = 0; - if (properties.IsLeftButtonPressed()) { - new_btns |= lmb_bit; - } - if (properties.IsRightButtonPressed()) { - new_btns |= rmb_bit; - } - if (properties.IsMiddleButtonPressed()) { - new_btns |= mmb_bit; - } - const uint8_t old_btns = _sapp.uwp.mouse_buttons; - const uint8_t chg_btns = new_btns ^ old_btns; - - _sapp.uwp.mouse_buttons = new_btns; - - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - sapp_mousebutton btn = SAPP_MOUSEBUTTON_INVALID; - if (chg_btns & lmb_bit) { - btn = SAPP_MOUSEBUTTON_LEFT; - type = (new_btns & lmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (chg_btns & rmb_bit) { - btn = SAPP_MOUSEBUTTON_RIGHT; - type = (new_btns & rmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (chg_btns & mmb_bit) { - btn = SAPP_MOUSEBUTTON_MIDDLE; - type = (new_btns & mmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (type != SAPP_EVENTTYPE_INVALID) { - _sapp_uwp_mouse_event(type, btn, sender); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_key_event(sapp_event_type type, winrt::Windows::UI::Core::CoreWindow const& sender_window, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto key_status = args.KeyStatus(); - uint32_t ext_scan_code = key_status.ScanCode | (key_status.IsExtendedKey ? 0x100 : 0); - if (_sapp_events_enabled() && (ext_scan_code < SAPP_MAX_KEYCODES)) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.key_code = _sapp.keycodes[ext_scan_code]; - _sapp.event.key_repeat = type == SAPP_EVENTTYPE_KEY_UP ? false : key_status.WasKeyDown; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_uwp_char_event(uint32_t c, bool repeat, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled() && (c >= 32)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.char_code = c; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_toggle_fullscreen(void) { - auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); - _sapp.fullscreen = appView.IsFullScreenMode(); - if (!_sapp.fullscreen) { - appView.TryEnterFullScreenMode(); - } - else { - appView.ExitFullScreenMode(); - } - _sapp.fullscreen = appView.IsFullScreenMode(); -} - -namespace {/* Empty namespace to ensure internal linkage (same as _SOKOL_PRIVATE) */ - -// Controls all the DirectX device resources. -class DeviceResources { -public: - // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. - interface IDeviceNotify { - virtual void OnDeviceLost() = 0; - virtual void OnDeviceRestored() = 0; - }; - - DeviceResources(); - ~DeviceResources(); - void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); - void SetLogicalSize(winrt::Windows::Foundation::Size logicalSize); - void SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation); - void SetDpi(float dpi); - void ValidateDevice(); - void HandleDeviceLost(); - void RegisterDeviceNotify(IDeviceNotify* deviceNotify); - void Trim(); - void Present(); - -private: - - // Swapchain Rotation Matrices (Z-rotation) - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation0 = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation90 = { - 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation180 = { - -1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, -1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation270 = { - 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - void CreateDeviceResources(); - void CreateWindowSizeDependentResources(); - void UpdateRenderTargetSize(); - DXGI_MODE_ROTATION ComputeDisplayRotation(); - bool SdkLayersAvailable(); - - // Direct3D objects. - winrt::com_ptr m_d3dDevice; - winrt::com_ptr m_d3dContext; - winrt::com_ptr m_swapChain; - - // Direct3D rendering objects. Required for 3D. - winrt::com_ptr m_d3dRenderTarget; - winrt::com_ptr m_d3dRenderTargetView; - winrt::com_ptr m_d3dMSAARenderTarget; - winrt::com_ptr m_d3dMSAARenderTargetView; - winrt::com_ptr m_d3dDepthStencil; - winrt::com_ptr m_d3dDepthStencilView; - D3D11_VIEWPORT m_screenViewport = { }; - - // Cached reference to the Window. - winrt::agile_ref< winrt::Windows::UI::Core::CoreWindow> m_window; - - // Cached device properties. - D3D_FEATURE_LEVEL m_d3dFeatureLevel = D3D_FEATURE_LEVEL_9_1; - winrt::Windows::Foundation::Size m_d3dRenderTargetSize = { }; - winrt::Windows::Foundation::Size m_outputSize = { }; - winrt::Windows::Foundation::Size m_logicalSize = { }; - winrt::Windows::Graphics::Display::DisplayOrientations m_nativeOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; - winrt::Windows::Graphics::Display::DisplayOrientations m_currentOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; - float m_dpi = -1.0f; - - // Transforms used for display orientation. - DirectX::XMFLOAT4X4 m_orientationTransform3D; - - // The IDeviceNotify can be held directly as it owns the DeviceResources. - IDeviceNotify* m_deviceNotify = nullptr; -}; - -// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events. -struct App : winrt::implements { -public: - // IFrameworkViewSource Methods - winrt::Windows::ApplicationModel::Core::IFrameworkView CreateView() { return *this; } - - // IFrameworkView Methods. - virtual void Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView); - virtual void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); - virtual void Load(winrt::hstring const& entryPoint); - virtual void Run(); - virtual void Uninitialize(); - -protected: - // Application lifecycle event handlers - void OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args); - void OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args); - void OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args); - - // Window event handlers - void OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args); - void OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args); - - // Navigation event handlers - void OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args); - - // Input event handlers - void OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); - void OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); - void OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args); - - // Pointer event handlers - void OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - - // DisplayInformation event handlers. - void OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - void OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - void OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - -private: - std::unique_ptr m_deviceResources; - bool m_windowVisible = true; -}; - -DeviceResources::DeviceResources() { - CreateDeviceResources(); -} - -DeviceResources::~DeviceResources() { - // Cleanup Sokol Context - _sapp.d3d11.device = nullptr; - _sapp.d3d11.device_context = nullptr; -} - -void DeviceResources::CreateDeviceResources() { - // This flag adds support for surfaces with a different color channel ordering - // than the API default. It is required for compatibility with Direct2D. - UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; - - #if defined(_DEBUG) - if (SdkLayersAvailable()) { - // If the project is in a debug build, enable debugging via SDK Layers with this flag. - creationFlags |= D3D11_CREATE_DEVICE_DEBUG; - } - #endif - - // This array defines the set of DirectX hardware feature levels this app will support. - // Note the ordering should be preserved. - // Don't forget to declare your application's minimum required feature level in its - // description. All applications are assumed to support 9.1 unless otherwise stated. - D3D_FEATURE_LEVEL featureLevels[] = { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 - }; - - // Create the Direct3D 11 API device object and a corresponding context. - winrt::com_ptr device; - winrt::com_ptr context; - - HRESULT hr = D3D11CreateDevice( - nullptr, // Specify nullptr to use the default adapter. - D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver. - 0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. - creationFlags, // Set debug and Direct2D compatibility flags. - featureLevels, // List of feature levels this app can support. - ARRAYSIZE(featureLevels), // Size of the list above. - D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. - device.put(), // Returns the Direct3D device created. - &m_d3dFeatureLevel, // Returns feature level of device created. - context.put() // Returns the device immediate context. - ); - - if (FAILED(hr)) { - // If the initialization fails, fall back to the WARP device. - // For more information on WARP, see: - // https://go.microsoft.com/fwlink/?LinkId=286690 - winrt::check_hresult( - D3D11CreateDevice( - nullptr, - D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. - 0, - creationFlags, - featureLevels, - ARRAYSIZE(featureLevels), - D3D11_SDK_VERSION, - device.put(), - &m_d3dFeatureLevel, - context.put() - ) - ); - } - - // Store pointers to the Direct3D 11.3 API device and immediate context. - m_d3dDevice = device.as(); - m_d3dContext = context.as(); - - // Setup Sokol Context - _sapp.d3d11.device = m_d3dDevice.get(); - _sapp.d3d11.device_context = m_d3dContext.get(); -} - -void DeviceResources::CreateWindowSizeDependentResources() { - // Cleanup Sokol Context (these are non-owning raw pointers) - _sapp.d3d11.rt = nullptr; - _sapp.d3d11.rtv = nullptr; - _sapp.d3d11.msaa_rt = nullptr; - _sapp.d3d11.msaa_rtv = nullptr; - _sapp.d3d11.ds = nullptr; - _sapp.d3d11.dsv = nullptr; - - // Clear the previous window size specific context. - ID3D11RenderTargetView* nullViews[] = { nullptr }; - m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr); - // these are smart pointers, setting to nullptr will delete the objects - m_d3dRenderTarget = nullptr; - m_d3dRenderTargetView = nullptr; - m_d3dMSAARenderTarget = nullptr; - m_d3dMSAARenderTargetView = nullptr; - m_d3dDepthStencilView = nullptr; - m_d3dDepthStencil = nullptr; - m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr); - - UpdateRenderTargetSize(); - - // The width and height of the swap chain must be based on the window's - // natively-oriented width and height. If the window is not in the native - // orientation, the dimensions must be reversed. - DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation(); - - bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270; - m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width; - m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height; - - if (m_swapChain != nullptr) { - // If the swap chain already exists, resize it. - HRESULT hr = m_swapChain->ResizeBuffers( - 2, // Double-buffered swap chain. - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - DXGI_FORMAT_B8G8R8A8_UNORM, - 0 - ); - - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - // If the device was removed for any reason, a new device and swap chain will need to be created. - HandleDeviceLost(); - - // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method - // and correctly set up the new device. - return; - } - else { - winrt::check_hresult(hr); - } - } - else { - // Otherwise, create a new one using the same adapter as the existing Direct3D device. - DXGI_SCALING scaling = (_sapp.uwp.dpi.content_scale == _sapp.uwp.dpi.window_scale) ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH; - DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 }; - - swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window. - swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height); - swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format. - swapChainDesc.Stereo = false; - swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling. - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency. - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Microsoft Store apps must use this SwapEffect. - swapChainDesc.Flags = 0; - swapChainDesc.Scaling = scaling; - swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - - // This sequence obtains the DXGI factory that was used to create the Direct3D device above. - winrt::com_ptr dxgiDevice = m_d3dDevice.as(); - winrt::com_ptr dxgiAdapter; - winrt::check_hresult(dxgiDevice->GetAdapter(dxgiAdapter.put())); - winrt::com_ptr dxgiFactory; - winrt::check_hresult(dxgiAdapter->GetParent(__uuidof(IDXGIFactory4), dxgiFactory.put_void())); - winrt::com_ptr swapChain; - winrt::check_hresult(dxgiFactory->CreateSwapChainForCoreWindow(m_d3dDevice.get(), m_window.get().as<::IUnknown>().get(), &swapChainDesc, nullptr, swapChain.put())); - m_swapChain = swapChain.as(); - - // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and - // ensures that the application will only render after each VSync, minimizing power consumption. - winrt::check_hresult(dxgiDevice->SetMaximumFrameLatency(1)); - - // Setup Sokol Context - winrt::check_hresult(swapChain->GetDesc(&_sapp.d3d11.swap_chain_desc)); - _sapp.d3d11.swap_chain = m_swapChain.as().detach(); - } - - // Set the proper orientation for the swap chain, and generate 2D and - // 3D matrix transformations for rendering to the rotated swap chain. - // Note the rotation angle for the 2D and 3D transforms are different. - // This is due to the difference in coordinate spaces. Additionally, - // the 3D matrix is specified explicitly to avoid rounding errors. - switch (displayRotation) { - case DXGI_MODE_ROTATION_IDENTITY: - m_orientationTransform3D = m_rotation0; - break; - - case DXGI_MODE_ROTATION_ROTATE90: - m_orientationTransform3D = m_rotation270; - break; - - case DXGI_MODE_ROTATION_ROTATE180: - m_orientationTransform3D = m_rotation180; - break; - - case DXGI_MODE_ROTATION_ROTATE270: - m_orientationTransform3D = m_rotation90; - break; - } - winrt::check_hresult(m_swapChain->SetRotation(displayRotation)); - - // Create a render target view of the swap chain back buffer. - winrt::check_hresult(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_d3dRenderTarget))); - winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dRenderTarget.get(), nullptr, m_d3dRenderTargetView.put())); - - // Create MSAA texture and view if needed - if (_sapp.sample_count > 1) { - CD3D11_TEXTURE2D_DESC1 msaaTexDesc( - DXGI_FORMAT_B8G8R8A8_UNORM, - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - 1, // arraySize - 1, // mipLevels - D3D11_BIND_RENDER_TARGET, - D3D11_USAGE_DEFAULT, - 0, // cpuAccessFlags - _sapp.sample_count, - _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 - ); - winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&msaaTexDesc, nullptr, m_d3dMSAARenderTarget.put())); - winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dMSAARenderTarget.get(), nullptr, m_d3dMSAARenderTargetView.put())); - } - - // Create a depth stencil view for use with 3D rendering if needed. - CD3D11_TEXTURE2D_DESC1 depthStencilDesc( - DXGI_FORMAT_D24_UNORM_S8_UINT, - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - 1, // This depth stencil view has only one texture. - 1, // Use a single mipmap level. - D3D11_BIND_DEPTH_STENCIL, - D3D11_USAGE_DEFAULT, - 0, // cpuAccessFlag - _sapp.sample_count, - _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 - ); - winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&depthStencilDesc, nullptr, m_d3dDepthStencil.put())); - - CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); - winrt::check_hresult(m_d3dDevice->CreateDepthStencilView(m_d3dDepthStencil.get(), nullptr, m_d3dDepthStencilView.put())); - - // Set sokol window and framebuffer sizes - _sapp.window_width = (int) m_logicalSize.Width; - _sapp.window_height = (int) m_logicalSize.Height; - _sapp.framebuffer_width = lround(m_d3dRenderTargetSize.Width); - _sapp.framebuffer_height = lround(m_d3dRenderTargetSize.Height); - - // Setup Sokol Context - _sapp.d3d11.rt = m_d3dRenderTarget.as().get(); - _sapp.d3d11.rtv = m_d3dRenderTargetView.as().get(); - _sapp.d3d11.ds = m_d3dDepthStencil.as().get(); - _sapp.d3d11.dsv = m_d3dDepthStencilView.get(); - if (_sapp.sample_count > 1) { - _sapp.d3d11.msaa_rt = m_d3dMSAARenderTarget.as().get(); - _sapp.d3d11.msaa_rtv = m_d3dMSAARenderTargetView.as().get(); - } - - // Sokol app is now valid - _sapp.valid = true; -} - -// Determine the dimensions of the render target and whether it will be scaled down. -void DeviceResources::UpdateRenderTargetSize() { - // Calculate the necessary render target size in pixels. - m_outputSize.Width = m_logicalSize.Width * _sapp.uwp.dpi.content_scale; - m_outputSize.Height = m_logicalSize.Height * _sapp.uwp.dpi.content_scale; - - // Prevent zero size DirectX content from being created. - m_outputSize.Width = std::max(m_outputSize.Width, 1.0f); - m_outputSize.Height = std::max(m_outputSize.Height, 1.0f); -} - -// This method is called when the CoreWindow is created (or re-created). -void DeviceResources::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { - auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); - m_window = window; - m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); - m_nativeOrientation = currentDisplayInformation.NativeOrientation(); - m_currentOrientation = currentDisplayInformation.CurrentOrientation(); - m_dpi = currentDisplayInformation.LogicalDpi(); - _sapp_uwp_configure_dpi(m_dpi); - CreateWindowSizeDependentResources(); -} - -// This method is called in the event handler for the SizeChanged event. -void DeviceResources::SetLogicalSize(winrt::Windows::Foundation::Size logicalSize) { - if (m_logicalSize != logicalSize) { - m_logicalSize = logicalSize; - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the DpiChanged event. -void DeviceResources::SetDpi(float dpi) { - if (dpi != m_dpi) { - m_dpi = dpi; - _sapp_uwp_configure_dpi(m_dpi); - // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated. - auto window = m_window.get(); - m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the OrientationChanged event. -void DeviceResources::SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation) { - if (m_currentOrientation != currentOrientation) { - m_currentOrientation = currentOrientation; - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the DisplayContentsInvalidated event. -void DeviceResources::ValidateDevice() { - // The D3D Device is no longer valid if the default adapter changed since the device - // was created or if the device has been removed. - - // First, get the information for the default adapter from when the device was created. - winrt::com_ptr dxgiDevice = m_d3dDevice.as< IDXGIDevice3>(); - winrt::com_ptr deviceAdapter; - winrt::check_hresult(dxgiDevice->GetAdapter(deviceAdapter.put())); - winrt::com_ptr deviceFactory; - winrt::check_hresult(deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory))); - winrt::com_ptr previousDefaultAdapter; - winrt::check_hresult(deviceFactory->EnumAdapters1(0, previousDefaultAdapter.put())); - DXGI_ADAPTER_DESC1 previousDesc; - winrt::check_hresult(previousDefaultAdapter->GetDesc1(&previousDesc)); - - // Next, get the information for the current default adapter. - winrt::com_ptr currentFactory; - winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory))); - winrt::com_ptr currentDefaultAdapter; - winrt::check_hresult(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put())); - DXGI_ADAPTER_DESC1 currentDesc; - winrt::check_hresult(currentDefaultAdapter->GetDesc1(¤tDesc)); - - // If the adapter LUIDs don't match, or if the device reports that it has been removed, - // a new D3D device must be created. - if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart || - previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart || - FAILED(m_d3dDevice->GetDeviceRemovedReason())) - { - // Release references to resources related to the old device. - dxgiDevice = nullptr; - deviceAdapter = nullptr; - deviceFactory = nullptr; - previousDefaultAdapter = nullptr; - - // Create a new device and swap chain. - HandleDeviceLost(); - } -} - -// Recreate all device resources and set them back to the current state. -void DeviceResources::HandleDeviceLost() { - m_swapChain = nullptr; - if (m_deviceNotify != nullptr) { - m_deviceNotify->OnDeviceLost(); - } - CreateDeviceResources(); - CreateWindowSizeDependentResources(); - if (m_deviceNotify != nullptr) { - m_deviceNotify->OnDeviceRestored(); - } -} - -// Register our DeviceNotify to be informed on device lost and creation. -void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) { - m_deviceNotify = deviceNotify; -} - -// Call this method when the app suspends. It provides a hint to the driver that the app -// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. -void DeviceResources::Trim() { - m_d3dDevice.as()->Trim(); -} - -// Present the contents of the swap chain to the screen. -void DeviceResources::Present() { - - // MSAA resolve if needed - if (_sapp.sample_count > 1) { - m_d3dContext->ResolveSubresource(m_d3dRenderTarget.get(), 0, m_d3dMSAARenderTarget.get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM); - m_d3dContext->DiscardView1(m_d3dMSAARenderTargetView.get(), nullptr, 0); - } - - // The first argument instructs DXGI to block until VSync, putting the application - // to sleep until the next VSync. This ensures we don't waste any cycles rendering - // frames that will never be displayed to the screen. - DXGI_PRESENT_PARAMETERS parameters = { 0 }; - HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); - - // Discard the contents of the render target. - // This is a valid operation only when the existing contents will be entirely - // overwritten. If dirty or scroll rects are used, this call should be removed. - m_d3dContext->DiscardView1(m_d3dRenderTargetView.get(), nullptr, 0); - - // Discard the contents of the depth stencil. - m_d3dContext->DiscardView1(m_d3dDepthStencilView.get(), nullptr, 0); - - // If the device was removed either by a disconnection or a driver upgrade, we - // must recreate all device resources. - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - HandleDeviceLost(); - } - else { - winrt::check_hresult(hr); - } -} - -// This method determines the rotation between the display device's native orientation and the -// current display orientation. -DXGI_MODE_ROTATION DeviceResources::ComputeDisplayRotation() { - DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED; - - // Note: NativeOrientation can only be Landscape or Portrait even though - // the DisplayOrientations enum has other values. - switch (m_nativeOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - switch (m_currentOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - rotation = DXGI_MODE_ROTATION_IDENTITY; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - rotation = DXGI_MODE_ROTATION_ROTATE270; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE180; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE90; - break; - } - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - switch (m_currentOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - rotation = DXGI_MODE_ROTATION_ROTATE90; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - rotation = DXGI_MODE_ROTATION_IDENTITY; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE270; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE180; - break; - } - break; - } - return rotation; -} - -// Check for SDK Layer support. -bool DeviceResources::SdkLayersAvailable() { - #if defined(_DEBUG) - HRESULT hr = D3D11CreateDevice( - nullptr, - D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. - 0, - D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. - nullptr, // Any feature level will do. - 0, - D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. - nullptr, // No need to keep the D3D device reference. - nullptr, // No need to know the feature level. - nullptr // No need to keep the D3D device context reference. - ); - return SUCCEEDED(hr); - #else - return false; - #endif -} - -// The first method called when the IFrameworkView is being created. -void App::Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) { - // Register event handlers for app lifecycle. This example includes Activated, so that we - // can make the CoreWindow active and start rendering on the window. - applicationView.Activated({ this, &App::OnActivated }); - - winrt::Windows::ApplicationModel::Core::CoreApplication::Suspending({ this, &App::OnSuspending }); - winrt::Windows::ApplicationModel::Core::CoreApplication::Resuming({ this, &App::OnResuming }); - - // At this point we have access to the device. - // We can create the device-dependent resources. - m_deviceResources = std::make_unique(); -} - -// Called when the CoreWindow object is created (or re-created). -void App::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { - window.SizeChanged({ this, &App::OnWindowSizeChanged }); - window.VisibilityChanged({ this, &App::OnVisibilityChanged }); - - window.KeyDown({ this, &App::OnKeyDown }); - window.KeyUp({ this, &App::OnKeyUp }); - window.CharacterReceived({ this, &App::OnCharacterReceived }); - - window.PointerEntered({ this, &App::OnPointerEntered }); - window.PointerExited({ this, &App::OnPointerExited }); - window.PointerPressed({ this, &App::OnPointerPressed }); - window.PointerReleased({ this, &App::OnPointerReleased }); - window.PointerMoved({ this, &App::OnPointerMoved }); - window.PointerWheelChanged({ this, &App::OnPointerWheelChanged }); - - auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); - - currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged }); - currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged }); - winrt::Windows::Graphics::Display::DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated }); - - winrt::Windows::UI::Core::SystemNavigationManager::GetForCurrentView().BackRequested({ this, &App::OnBackRequested }); - - m_deviceResources->SetWindow(window); -} - -// Initializes scene resources, or loads a previously saved app state. -void App::Load(winrt::hstring const& entryPoint) { - _SOKOL_UNUSED(entryPoint); -} - -// This method is called after the window becomes active. -void App::Run() { - // NOTE: UWP will simply terminate an application, it's not possible to detect when an application is being closed - while (true) { - if (m_windowVisible) { - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent); - _sapp_frame(); - m_deviceResources->Present(); - } - else { - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessOneAndAllPending); - } - } -} - -// Required for IFrameworkView. -// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView -// class is torn down while the app is in the foreground. -void App::Uninitialize() { - // empty -} - -// Application lifecycle event handlers. -void App::OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) { - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(applicationView); - auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); - auto targetSize = winrt::Windows::Foundation::Size((float)_sapp.desc.width, (float)_sapp.desc.height); - appView.SetPreferredMinSize(targetSize); - appView.TryResizeView(targetSize); - - // Disabling this since it can only append the title to the app name (Title - Appname). - // There's no way of just setting a string to be the window title. - //appView.Title(_sapp.window_title_wide); - - // Run() won't start until the CoreWindow is activated. - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Activate(); - if (_sapp.desc.fullscreen) { - appView.TryEnterFullScreenMode(); - } - _sapp.fullscreen = appView.IsFullScreenMode(); -} - -void App::OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args) { - _SOKOL_UNUSED(sender); - _SOKOL_UNUSED(args); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_SUSPENDED); -} - -void App::OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args) { - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(sender); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESUMED); -} - -void App::OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args) { - _SOKOL_UNUSED(args); - m_deviceResources->SetLogicalSize(winrt::Windows::Foundation::Size(sender.Bounds().Width, sender.Bounds().Height)); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) { - _SOKOL_UNUSED(sender); - m_windowVisible = args.Visible(); - _sapp_win32_uwp_app_event(m_windowVisible ? SAPP_EVENTTYPE_RESTORED : SAPP_EVENTTYPE_ICONIFIED); -} - -void App::OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args) { - _SOKOL_UNUSED(sender); - args.Handled(true); -} - -void App::OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto status = args.KeyStatus(); - _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_DOWN, sender, args); -} - -void App::OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto status = args.KeyStatus(); - _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_UP, sender, args); -} - -void App::OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) { - _sapp_uwp_char_event(args.KeyCode(), args.KeyStatus().WasKeyDown, sender); -} - -void App::OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _SOKOL_UNUSED(args); - _sapp.uwp.mouse_tracked = true; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); -} - -void App::OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _SOKOL_UNUSED(args); - _sapp.uwp.mouse_tracked = false; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, sender); -} - -void App::OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -// NOTE: for some reason this event handler is never called?? -void App::OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -void App::OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - auto position = args.CurrentPoint().Position(); - const float new_x = (float)(int)(position.X * _sapp.uwp.dpi.mouse_scale + 0.5f); - const float new_y = (float)(int)(position.Y * _sapp.uwp.dpi.mouse_scale + 0.5f); - // don't update dx/dy in the very first event - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - if (!_sapp.uwp.mouse_tracked) { - _sapp.uwp.mouse_tracked = true; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); - } - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, sender); - - // HACK for detecting multiple mouse button presses - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -void App::OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - auto properties = args.CurrentPoint().Properties(); - _sapp_uwp_scroll_event((float)properties.MouseWheelDelta(), properties.IsHorizontalMouseWheel(), sender); -} - -void App::OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - m_deviceResources->SetDpi(sender.LogicalDpi()); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - m_deviceResources->SetCurrentOrientation(sender.CurrentOrientation()); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(sender); - m_deviceResources->ValidateDevice(); -} - -} /* End empty namespace */ - -_SOKOL_PRIVATE void _sapp_uwp_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_win32_uwp_init_keytable(); - _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - winrt::Windows::ApplicationModel::Core::CoreApplication::Run(winrt::make()); -} - -#if !defined(SOKOL_NO_ENTRY) -#if defined(UNICODE) -int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { -#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { -#endif - _SOKOL_UNUSED(hInstance); - _SOKOL_UNUSED(hPrevInstance); - _SOKOL_UNUSED(lpCmdLine); - _SOKOL_UNUSED(nCmdShow); - sapp_desc desc = sokol_main(0, nullptr); - _sapp_uwp_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_UWP */ - -/*== Android ================================================================*/ +// █████ ███ ██ ██████ ██████ ██████ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ +// +// >>android #if defined(_SAPP_ANDROID) /* android loop thread */ @@ -7504,10 +7853,16 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { return false; } + _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; const EGLint cfg_attributes[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2?EGL_OPENGL_ES2_BIT:EGL_OPENGL_ES3_BIT, + #else + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + #endif EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, @@ -7561,25 +7916,18 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { _sapp.android.context = context; return true; } -SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ - //eglSwapInterval(_sapp.android.display,0); -} - _SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { if (_sapp.android.display != EGL_NO_DISPLAY) { eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (_sapp.android.surface != EGL_NO_SURFACE) { - SOKOL_LOG("Destroying egl surface"); eglDestroySurface(_sapp.android.display, _sapp.android.surface); _sapp.android.surface = EGL_NO_SURFACE; } if (_sapp.android.context != EGL_NO_CONTEXT) { - SOKOL_LOG("Destroying egl context"); eglDestroyContext(_sapp.android.display, _sapp.android.context); _sapp.android.context = EGL_NO_CONTEXT; } - SOKOL_LOG("Terminating egl display"); eglTerminate(_sapp.android.display); _sapp.android.display = EGL_NO_DISPLAY; } @@ -7620,7 +7968,6 @@ _SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { _SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { if (_sapp_events_enabled()) { _sapp_init_event(type); - SOKOL_LOG("event_cb()"); _sapp_call_event(&_sapp.event); } } @@ -7643,14 +7990,14 @@ _SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool const int32_t buf_h = win_h / 2; EGLint format; EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); - SOKOL_ASSERT(egl_result == EGL_TRUE); + SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions as the ANativeWindow size results in weird display artefacts, that's why it's only called when the buffer geometry is different from the window size */ int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); - SOKOL_ASSERT(result == 0); + SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); } } @@ -7658,26 +8005,23 @@ _SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool EGLint fb_w, fb_h; EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); - SOKOL_ASSERT(egl_result_w == EGL_TRUE); - SOKOL_ASSERT(egl_result_h == EGL_TRUE); + SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); + SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); _sapp.framebuffer_width = fb_w; _sapp.framebuffer_height = fb_h; _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; if (win_changed || fb_changed || force_update) { if (!_sapp.first_frame) { - SOKOL_LOG("SAPP_EVENTTYPE_RESIZED"); _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); } } } _SOKOL_PRIVATE void _sapp_android_cleanup(void) { - SOKOL_LOG("Cleaning up"); if (_sapp.android.surface != EGL_NO_SURFACE) { /* egl context is bound, cleanup gracefully */ if (_sapp.init_called && !_sapp.cleanup_called) { - SOKOL_LOG("cleanup_cb()"); _sapp_call_cleanup(); } } @@ -7696,6 +8040,7 @@ _SOKOL_PRIVATE void _sapp_android_frame(void) { SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + _sapp_timing_measure(&_sapp.timing); _sapp_android_update_dimensions(_sapp.android.current.window, false); _sapp_frame(); eglSwapBuffers(_sapp.android.display, _sapp.android.surface); @@ -7708,30 +8053,22 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { if (!_sapp_events_enabled()) { return false; } - if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; - if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; - int32_t action_idx = AMotionEvent_getAction(e); int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; sapp_event_type type = SAPP_EVENTTYPE_INVALID; switch (action) { case AMOTION_EVENT_ACTION_DOWN: - SOKOL_LOG("Touch: down"); case AMOTION_EVENT_ACTION_POINTER_DOWN: - SOKOL_LOG("Touch: ptr down"); type = SAPP_EVENTTYPE_TOUCHES_BEGAN; break; case AMOTION_EVENT_ACTION_MOVE: type = SAPP_EVENTTYPE_TOUCHES_MOVED; break; case AMOTION_EVENT_ACTION_UP: - SOKOL_LOG("Touch: up"); case AMOTION_EVENT_ACTION_POINTER_UP: - SOKOL_LOG("Touch: ptr up"); type = SAPP_EVENTTYPE_TOUCHES_ENDED; break; case AMOTION_EVENT_ACTION_CANCEL: - SOKOL_LOG("Touch: cancel"); type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; break; default: @@ -7749,9 +8086,9 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { for (int32_t i = 0; i < _sapp.event.num_touches; i++) { sapp_touchpoint* dst = &_sapp.event.touches[i]; dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); - dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; - dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - + dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || action == AMOTION_EVENT_ACTION_POINTER_UP) { dst->changed = (i == idx); @@ -7763,230 +8100,26 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { return true; } -_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { - switch (scancode) { - case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; - case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; - case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; - case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; - case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; - case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; - case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; - case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; - case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; - case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; - case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; - case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; - case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; - case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; - case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; - case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; - case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; - case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; - case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; - case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; - case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; - case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; - case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; - case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; - case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; - case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; - case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; - case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; - case AKEYCODE_F1: return SAPP_KEYCODE_F1; - case AKEYCODE_F2: return SAPP_KEYCODE_F2; - case AKEYCODE_F3: return SAPP_KEYCODE_F3; - case AKEYCODE_F4: return SAPP_KEYCODE_F4; - case AKEYCODE_F5: return SAPP_KEYCODE_F5; - case AKEYCODE_F6: return SAPP_KEYCODE_F6; - case AKEYCODE_F7: return SAPP_KEYCODE_F7; - case AKEYCODE_F8: return SAPP_KEYCODE_F8; - case AKEYCODE_F9: return SAPP_KEYCODE_F9; - case AKEYCODE_F10: return SAPP_KEYCODE_F10; - case AKEYCODE_F11: return SAPP_KEYCODE_F11; - case AKEYCODE_F12: return SAPP_KEYCODE_F12; - case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; - case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; - case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; - case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; - case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; - case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; - case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; - case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; - case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; - case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; - case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; - case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; - case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; - case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; - case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; - case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; - case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; - case AKEYCODE_A: return SAPP_KEYCODE_A; - case AKEYCODE_B: return SAPP_KEYCODE_B; - case AKEYCODE_C: return SAPP_KEYCODE_C; - case AKEYCODE_D: return SAPP_KEYCODE_D; - case AKEYCODE_E: return SAPP_KEYCODE_E; - case AKEYCODE_F: return SAPP_KEYCODE_F; - case AKEYCODE_G: return SAPP_KEYCODE_G; - case AKEYCODE_H: return SAPP_KEYCODE_H; - case AKEYCODE_I: return SAPP_KEYCODE_I; - case AKEYCODE_J: return SAPP_KEYCODE_J; - case AKEYCODE_K: return SAPP_KEYCODE_K; - case AKEYCODE_L: return SAPP_KEYCODE_L; - case AKEYCODE_M: return SAPP_KEYCODE_M; - case AKEYCODE_N: return SAPP_KEYCODE_N; - case AKEYCODE_O: return SAPP_KEYCODE_O; - case AKEYCODE_P: return SAPP_KEYCODE_P; - case AKEYCODE_Q: return SAPP_KEYCODE_Q; - case AKEYCODE_R: return SAPP_KEYCODE_R; - case AKEYCODE_S: return SAPP_KEYCODE_S; - case AKEYCODE_T: return SAPP_KEYCODE_T; - case AKEYCODE_U: return SAPP_KEYCODE_U; - case AKEYCODE_V: return SAPP_KEYCODE_V; - case AKEYCODE_W: return SAPP_KEYCODE_W; - case AKEYCODE_X: return SAPP_KEYCODE_X; - case AKEYCODE_Y: return SAPP_KEYCODE_Y; - case AKEYCODE_Z: return SAPP_KEYCODE_Z; - case AKEYCODE_1: return SAPP_KEYCODE_1; - case AKEYCODE_2: return SAPP_KEYCODE_2; - case AKEYCODE_3: return SAPP_KEYCODE_3; - case AKEYCODE_4: return SAPP_KEYCODE_4; - case AKEYCODE_5: return SAPP_KEYCODE_5; - case AKEYCODE_6: return SAPP_KEYCODE_6; - case AKEYCODE_7: return SAPP_KEYCODE_7; - case AKEYCODE_8: return SAPP_KEYCODE_8; - case AKEYCODE_9: return SAPP_KEYCODE_9; - case AKEYCODE_0: return SAPP_KEYCODE_0; - case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; - case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; - case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; - case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; - case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; - case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; - case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; - case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; - case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; - case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; - case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; - case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; - /* Android Buttons */ - case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; - default: return SAPP_KEYCODE_INVALID; - } - return SAPP_KEYCODE_INVALID; -} -_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { - uint32_t meta_state = AKeyEvent_getMetaState(e); - uint32_t mods = 0; - if (meta_state& AMETA_SHIFT_ON) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (meta_state& AMETA_CTRL_ON) { - mods |= SAPP_MODIFIER_CTRL; - } - if (meta_state& AMETA_ALT_ON) { - mods |= SAPP_MODIFIER_ALT; - } - if (meta_state& AMETA_META_ON) { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} -int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) -{ - ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); - // Attaches the current thread to the JVM. - JavaVM *javaVM = activity->vm; - JNIEnv *jniEnv = activity->env; - - jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); - if(result == JNI_ERR){ - return 0; - } - - jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); - int unicodeKey; - - if(metaState == 0){ - jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); - jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); - jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); - unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); - }else{ - jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); - jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); - jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); - unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); - } - - (*javaVM)->DetachCurrentThread(javaVM); - - return unicodeKey; -} -_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { - if (_sapp_events_enabled() ) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_android_mods(e); - _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} _SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { return false; } - if (!_sapp_events_enabled()) { - return false; - } - int32_t action = AKeyEvent_getAction(e); - // Don't process soft keyboard commands as they are not reliable through this interface. - if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; - // Don't relay key press events from joysticks or game pads as Sokol key down events - if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || - (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { - return false; - } - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - switch (action) { - - case AKEY_EVENT_ACTION_DOWN : - type = SAPP_EVENTTYPE_KEY_DOWN; - break; - case AKEY_EVENT_ACTION_UP: - type = SAPP_EVENTTYPE_KEY_UP; - break; - default: - break; - } - if (type == SAPP_EVENTTYPE_INVALID) { - return false; - } - bool repeat = AKeyEvent_getRepeatCount(e)>0; - _sapp_init_event(type); - _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); - _sapp.event.modifiers = _sapp_android_mods(e); - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - if(type==SAPP_EVENTTYPE_KEY_DOWN){ - _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); - } - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); + if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { + /* FIXME: this should be hooked into a "really quit?" mechanism + so the app can ask the user for confirmation, this is currently + generally missing in sokol_app.h + */ + _sapp_android_shutdown(); + return true; } - return _sapp.event.key_code != SAPP_KEYCODE_INVALID; + return false; } _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(fd); + _SOKOL_UNUSED(data); if ((events & ALOOPER_EVENT_INPUT) == 0) { - SOKOL_LOG("_sapp_android_input_cb() encountered unsupported event"); + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); return 1; } SOKOL_ASSERT(_sapp.android.current.input); @@ -8005,14 +8138,15 @@ _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { } _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(data); if ((events & ALOOPER_EVENT_INPUT) == 0) { - SOKOL_LOG("_sapp_android_main_cb() encountered unsupported event"); + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); return 1; } _sapp_android_msg_t msg; if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { - SOKOL_LOG("Could not write to read_from_main_fd"); + _SAPP_ERROR(ANDROID_READ_MSG_FAILED); return 1; } @@ -8020,45 +8154,42 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { switch (msg) { case _SOKOL_ANDROID_MSG_CREATE: { - SOKOL_LOG("MSG_CREATE"); + _SAPP_INFO(ANDROID_MSG_CREATE); SOKOL_ASSERT(!_sapp.valid); bool result = _sapp_android_init_egl(); - SOKOL_ASSERT(result); + SOKOL_ASSERT(result); _SOKOL_UNUSED(result); _sapp.valid = true; _sapp.android.has_created = true; } break; case _SOKOL_ANDROID_MSG_RESUME: - SOKOL_LOG("MSG_RESUME"); + _SAPP_INFO(ANDROID_MSG_RESUME); _sapp.android.has_resumed = true; _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); break; case _SOKOL_ANDROID_MSG_PAUSE: - SOKOL_LOG("MSG_PAUSE"); + _SAPP_INFO(ANDROID_MSG_PAUSE); _sapp.android.has_resumed = false; _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); break; case _SOKOL_ANDROID_MSG_FOCUS: - SOKOL_LOG("MSG_FOCUS"); + _SAPP_INFO(ANDROID_MSG_FOCUS); _sapp.android.has_focus = true; break; case _SOKOL_ANDROID_MSG_NO_FOCUS: - SOKOL_LOG("MSG_NO_FOCUS"); + _SAPP_INFO(ANDROID_MSG_NO_FOCUS); _sapp.android.has_focus = false; break; case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: - SOKOL_LOG("MSG_SET_NATIVE_WINDOW"); + _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); if (_sapp.android.current.window != _sapp.android.pending.window) { if (_sapp.android.current.window != NULL) { _sapp_android_cleanup_egl_surface(); } if (_sapp.android.pending.window != NULL) { - SOKOL_LOG("Creating egl surface ..."); if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { - SOKOL_LOG("... ok!"); _sapp_android_update_dimensions(_sapp.android.pending.window, true); } else { - SOKOL_LOG("... failed!"); _sapp_android_shutdown(); } } @@ -8066,7 +8197,7 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { _sapp.android.current.window = _sapp.android.pending.window; break; case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: - SOKOL_LOG("MSG_SET_INPUT_QUEUE"); + _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); if (_sapp.android.current.input != _sapp.android.pending.input) { if (_sapp.android.current.input != NULL) { AInputQueue_detachLooper(_sapp.android.current.input); @@ -8083,13 +8214,13 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { _sapp.android.current.input = _sapp.android.pending.input; break; case _SOKOL_ANDROID_MSG_DESTROY: - SOKOL_LOG("MSG_DESTROY"); + _SAPP_INFO(ANDROID_MSG_DESTROY); _sapp_android_cleanup(); _sapp.valid = false; _sapp.android.is_thread_stopping = true; break; default: - SOKOL_LOG("Unknown msg type received"); + _SAPP_WARN(ANDROID_UNKNOWN_MSG); break; } pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ @@ -8107,17 +8238,15 @@ _SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { SOKOL_ASSERT(_sapp.valid); /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ if (shown) { - SOKOL_LOG("Showing keyboard"); ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); } else { - SOKOL_LOG("Hiding keyboard"); ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); } } _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { _SOKOL_UNUSED(arg); - SOKOL_LOG("Loop thread started"); + _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); ALooper_addFd(_sapp.android.looper, @@ -8146,16 +8275,6 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; } - /* handle quit-requested, either from window or from sapp_request_quit() */ - if (_sapp.quit_requested && !_sapp.quit_ordered) { - /* give user code a chance to intervene */ - _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if(_sapp.quit_ordered) _sapp_android_shutdown(); } /* cleanup thread */ @@ -8172,34 +8291,39 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { _sapp.android.is_thread_stopped = true; pthread_cond_broadcast(&_sapp.android.pt.cond); pthread_mutex_unlock(&_sapp.android.pt.mutex); - SOKOL_LOG("Loop thread done"); + + _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); return NULL; } /* android main/ui thread */ _SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { - SOKOL_LOG("Could not write to write_from_main_fd"); + _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); } } _SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onStart()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); } _SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onResume()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); } _SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { - SOKOL_LOG("NativeActivity onSaveInstanceState()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); *out_size = 0; return NULL; } _SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { - SOKOL_LOG("NativeActivity onWindowFocusChanged()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); if (has_focus) { _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); } else { @@ -8208,12 +8332,14 @@ _SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activ } _SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onPause()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); } _SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onStop()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); } _SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { @@ -8227,12 +8353,15 @@ _SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { } _SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { - SOKOL_LOG("NativeActivity onNativeWindowCreated()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); _sapp_android_msg_set_native_window(window); } _SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { - SOKOL_LOG("NativeActivity onNativeWindowDestroyed()"); + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(window); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); _sapp_android_msg_set_native_window(NULL); } @@ -8247,22 +8376,27 @@ _SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { } _SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { - SOKOL_LOG("NativeActivity onInputQueueCreated()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); _sapp_android_msg_set_input_queue(queue); } _SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { - SOKOL_LOG("NativeActivity onInputQueueDestroyed()"); + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(queue); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); _sapp_android_msg_set_input_queue(NULL); } _SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onConfigurationChanged()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); /* see android:configChanges in manifest */ } _SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onLowMemory()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); } _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { @@ -8274,7 +8408,8 @@ _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { * However, if ANativeActivity_finish() is explicitly called from for example * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? */ - SOKOL_LOG("NativeActivity onDestroy()"); + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); /* send destroy msg */ pthread_mutex_lock(&_sapp.android.pt.mutex); @@ -8291,7 +8426,7 @@ _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { close(_sapp.android.pt.read_from_main_fd); close(_sapp.android.pt.write_from_main_fd); - SOKOL_LOG("NativeActivity done"); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ exit(0); @@ -8299,17 +8434,23 @@ _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { JNIEXPORT void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { - SOKOL_LOG("NativeActivity onCreate()"); - + _SOKOL_UNUSED(saved_state); + _SOKOL_UNUSED(saved_state_size); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); + + // the NativeActity pointer needs to be available inside sokol_main() + // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() + // will clear the global _sapp_t struct, so we need to initialize the native + // activity pointer twice, once before sokol_main() and once after _sapp_init_state() + _sapp_clear(&_sapp, sizeof(_sapp)); + _sapp.android.activity = activity; sapp_desc desc = sokol_main(0, NULL); _sapp_init_state(&desc); - - /* start loop thread */ _sapp.android.activity = activity; int pipe_fd[2]; if (pipe(pipe_fd) != 0) { - SOKOL_LOG("Could not create thread pipe"); + _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); return; } _sapp.android.pt.read_from_main_fd = pipe_fd[0]; @@ -8357,14 +8498,20 @@ void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; activity->callbacks->onLowMemory = _sapp_android_on_low_memory; - SOKOL_LOG("NativeActivity successfully created"); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); /* NOT A BUG: do NOT call sapp_discard_state() */ } #endif /* _SAPP_ANDROID */ -/*== LINUX ==================================================================*/ +// ██ ██ ███ ██ ██ ██ ██ ██ +// ██ ██ ████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ████ ██████ ██ ██ +// +// >>linux #if defined(_SAPP_LINUX) /* see GLFW's xkb_unicode.c */ @@ -9225,6 +9372,7 @@ _SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); + _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); if (_sapp.drop.enabled) { @@ -9263,6 +9411,7 @@ _SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { consistent user experience (matches Qt, Gtk, etc), although not always the most accurate one */ + bool dpi_ok = false; char* rms = XResourceManagerString(_sapp.x11.display); if (rms) { XrmDatabase db = XrmGetStringDatabase(rms); @@ -9272,13 +9421,21 @@ _SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { if (type && strcmp(type, "String") == 0) { _sapp.x11.dpi = atof(value.addr); + dpi_ok = true; } } XrmDestroyDatabase(db); } } + // fallback if querying DPI had failed: assume the standard DPI 96.0f + if (!dpi_ok) { + _sapp.x11.dpi = 96.0f; + _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); + } } +#if defined(_SAPP_GLX) + _SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { SOKOL_ASSERT(ext); const char* start = extensions; @@ -9310,10 +9467,10 @@ _SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensio _SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) { if (_sapp.glx.GetProcAddress) { - return (void*) _sapp.glx.GetProcAddress((const GLubyte*) procname); + return (void*) _sapp.glx.GetProcAddress(procname); } else if (_sapp.glx.GetProcAddressARB) { - return (void*) _sapp.glx.GetProcAddressARB((const GLubyte*) procname); + return (void*) _sapp.glx.GetProcAddressARB(procname); } else { return dlsym(_sapp.glx.libgl, procname); @@ -9329,7 +9486,7 @@ _SOKOL_PRIVATE void _sapp_glx_init() { } } if (!_sapp.glx.libgl) { - _sapp_fail("GLX: failed to load libGL"); + _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); } _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); @@ -9360,17 +9517,17 @@ _SOKOL_PRIVATE void _sapp_glx_init() { !_sapp.glx.GetProcAddressARB || !_sapp.glx.GetVisualFromFBConfig) { - _sapp_fail("GLX: failed to load required entry points"); + _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); } if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { - _sapp_fail("GLX: GLX extension not found"); + _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); } if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { - _sapp_fail("GLX: Failed to query GLX version"); + _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); } if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { - _sapp_fail("GLX: GLX version 1.3 is required"); + _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); } const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { @@ -9413,10 +9570,10 @@ _SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); if (!native_configs || !native_count) { - _sapp_fail("GLX: No GLXFBConfigs returned"); + _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); } - usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); + usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); usable_count = 0; for (i = 0; i < native_count; i++) { const GLXFBConfig n = native_configs[i]; @@ -9464,18 +9621,18 @@ _SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { result = (GLXFBConfig) closest->handle; } XFree(native_configs); - SOKOL_FREE(usable_configs); + _sapp_free(usable_configs); return result; } _SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { GLXFBConfig native = _sapp_glx_choosefbconfig(); if (0 == native) { - _sapp_fail("GLX: Failed to find a suitable GLXFBConfig"); + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); } XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); if (!result) { - _sapp_fail("GLX: Failed to retrieve Visual for GLXFBConfig"); + _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); } *visual = result->visual; *depth = result->depth; @@ -9485,27 +9642,27 @@ _SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { _SOKOL_PRIVATE void _sapp_glx_create_context(void) { GLXFBConfig native = _sapp_glx_choosefbconfig(); if (0 == native){ - _sapp_fail("GLX: Failed to find a suitable GLXFBConfig (2)"); + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); } if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { - _sapp_fail("GLX: ARB_create_context and ARB_create_context_profile required"); + _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); } _sapp_x11_grab_error_handler(); const int attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, 3, - GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 0, 0 }; _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); if (!_sapp.glx.ctx) { - _sapp_fail("GLX: failed to create GL context"); + _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); } _sapp_x11_release_error_handler(); _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); if (!_sapp.glx.window) { - _sapp_fail("GLX: failed to create window"); + _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); } } @@ -9538,9 +9695,11 @@ _SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { } } +#endif /* _SAPP_GLX */ + _SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { XEvent event; - memset(&event, 0, sizeof(event)); + _sapp_clear(&event, sizeof(event)); event.type = ClientMessage; event.xclient.window = _sapp.x11.window; @@ -9597,24 +9756,77 @@ _SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { img->xhot = 0; img->yhot = 0; const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); - memset(img->pixels, 0, num_bytes); + _sapp_clear(img->pixels, num_bytes); _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); XcursorImageDestroy(img); } + _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.x11.display); + if (theme) { + XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); + if (img) { + _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.cursors[cursor]) { + _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); + const int size = XcursorGetDefaultSize(_sapp.x11.display); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); + _sapp_x11_create_hidden_cursor(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + if (_sapp.x11.hidden_cursor) { + XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); + _sapp.x11.hidden_cursor = 0; + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + if (_sapp.x11.cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); + _sapp.x11.cursors[i] = 0; + } + } +} + _SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { _sapp.fullscreen = !_sapp.fullscreen; _sapp_x11_set_fullscreen(_sapp.fullscreen); _sapp_x11_query_window_size(); } -_SOKOL_PRIVATE void _sapp_x11_show_mouse(bool show) { - if (show) { - XUndefineCursor(_sapp.x11.display, _sapp.x11.window); +_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (shown) { + if (_sapp.x11.cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); + } + else { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } } else { XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); } + XFlush(_sapp.x11.display); } _SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { @@ -9677,10 +9889,43 @@ _SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { XFlush(_sapp.x11.display); } +_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 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*) _sapp_malloc_clear((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); + _sapp_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; - memset(&wa, 0, sizeof(wa)); + _sapp_clear(&wa, sizeof(wa)); const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; wa.colormap = _sapp.x11.colormap; wa.border_pixel = 0; @@ -9688,12 +9933,32 @@ _SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | FocusChangeMask | VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + + int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); + int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); + int window_width = _sapp.window_width; + int window_height = _sapp.window_height; + if (0 == window_width) { + window_width = (display_width * 4) / 5; + } + if (0 == window_height) { + window_height = (display_height * 4) / 5; + } + int window_xpos = (display_width - window_width) / 2; + int window_ypos = (display_height - window_height) / 2; + if (window_xpos < 0) { + window_xpos = 0; + } + if (window_ypos < 0) { + window_ypos = 0; + } _sapp_x11_grab_error_handler(); _sapp.x11.window = XCreateWindow(_sapp.x11.display, _sapp.x11.root, - 0, 0, - (uint32_t)_sapp.window_width, - (uint32_t)_sapp.window_height, + window_xpos, + window_ypos, + (uint32_t)window_width, + (uint32_t)window_height, 0, /* border width */ depth, /* color depth */ InputOutput, @@ -9702,7 +9967,7 @@ _SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { &wa); _sapp_x11_release_error_handler(); if (!_sapp.x11.window) { - _sapp_fail("X11: Failed to create window"); + _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); } Atom protocols[] = { _sapp.x11.WM_DELETE_WINDOW @@ -9710,8 +9975,12 @@ _SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); XSizeHints* hints = XAllocSizeHints(); - hints->flags |= PWinGravity; + hints->flags = (PWinGravity | PPosition | PSize); hints->win_gravity = StaticGravity; + hints->x = window_xpos; + hints->y = window_ypos; + hints->width = window_width; + hints->height = window_height; XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); XFree(hints); @@ -9720,8 +9989,8 @@ _SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { const Atom version = _SAPP_X11_XDND_VERSION; XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); } - _sapp_x11_update_window_title(); + _sapp_x11_query_window_size(); } _SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { @@ -9791,7 +10060,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; @@ -9805,6 +10102,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; } @@ -9824,6 +10130,20 @@ _SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) } } +_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y) { + if (!_sapp.mouse.locked) { + const float new_x = (float) x; + const float new_y = (float) y; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + _SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { if (_sapp_events_enabled()) { _sapp_init_event(type); @@ -10073,7 +10393,7 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { ((src_count == 6) && (src_chr != '/')) || ((src_count == 7) && (src_chr != '/'))) { - SOKOL_LOG("sokol_app.h: dropped file URI doesn't start with file://"); + _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); err = true; break; } @@ -10082,7 +10402,6 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { // skip } else if (src_chr == '\n') { - src_chr = 0; src_count = 0; _sapp.drop.num_files++; // too many files is not an error @@ -10093,7 +10412,7 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); } else if ((src_chr == '%') && src[0] && src[1]) { - // a percent-encoded byte (most like UTF-8 multibyte sequence) + // a percent-encoded byte (most likely UTF-8 multibyte sequence) const char digits[3] = { src[0], src[1], 0 }; src += 2; dst_chr = (char) strtol(digits, 0, 16); @@ -10107,7 +10426,7 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { *dst_ptr++ = dst_chr; } else { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)"); + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); err = true; break; } @@ -10145,7 +10464,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); @@ -10153,11 +10472,21 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { } } break; + case FocusIn: + // NOTE: ignoring 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: ignoring 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: { @@ -10165,7 +10494,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); } @@ -10183,15 +10514,20 @@ _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); } } break; case ButtonPress: { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); 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); @@ -10209,9 +10545,13 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { break; case ButtonRelease: { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); 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); } } @@ -10219,26 +10559,20 @@ _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_update(event->xcrossing.x, event->xcrossing.y); + _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_update(event->xcrossing.x, event->xcrossing.y); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); } break; case MotionNotify: if (!_sapp.mouse.locked) { - const float new_x = (float) event->xmotion.x; - const float new_y = (float) event->xmotion.y; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _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_update(event->xmotion.x, event->xmotion.y); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); } break; case ConfigureNotify: @@ -10321,9 +10655,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { } else if (_sapp.x11.xdnd.version >= 2) { XEvent reply; - memset(&reply, 0, sizeof(reply)); + _sapp_clear(&reply, sizeof(reply)); reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.window; + reply.xclient.window = _sapp.x11.xdnd.source; reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = (long)_sapp.x11.window; @@ -10342,7 +10676,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { return; } XEvent reply; - memset(&reply, 0, sizeof(reply)); + _sapp_clear(&reply, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = _sapp.x11.xdnd.source; reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; @@ -10376,9 +10710,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { } if (_sapp.x11.xdnd.version >= 2) { XEvent reply; - memset(&reply, 0, sizeof(reply)); + _sapp_clear(&reply, sizeof(reply)); reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.window; + reply.xclient.window = _sapp.x11.xdnd.source; reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = (long)_sapp.x11.window; @@ -10394,6 +10728,147 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { } } +#if !defined(_SAPP_GLX) + +_SOKOL_PRIVATE void _sapp_egl_init(void) { +#if defined(SOKOL_GLCORE33) + if (!eglBindAPI(EGL_OPENGL_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); + } +#else + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); + } +#endif + + _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); + if (EGL_NO_DISPLAY == _sapp.egl.display) { + _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); + } + + EGLint major, minor; + if (!eglInitialize(_sapp.egl.display, &major, &minor)) { + _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); + } + + EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint config_attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLCORE33) + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + #elif defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES3_BIT, + #else + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + #endif + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0, + EGL_SAMPLES, sample_count, + EGL_NONE, + }; + + EGLConfig egl_configs[32]; + EGLint config_count; + if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) { + _SAPP_PANIC(LINUX_EGL_NO_CONFIGS); + } + + EGLConfig config = egl_configs[0]; + for (int i = 0; i < config_count; ++i) { + EGLConfig c = egl_configs[i]; + EGLint r, g, b, a, d, s, n; + if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) && + (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) { + config = c; + break; + } + } + + EGLint visual_id; + if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) { + _SAPP_PANIC(LINUX_EGL_NO_NATIVE_VISUAL); + } + + XVisualInfo visual_info_template; + _sapp_clear(&visual_info_template, sizeof(visual_info_template)); + visual_info_template.visualid = (VisualID)visual_id; + + int num_visuals; + XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals); + if (!visual_info) { + _SAPP_PANIC(LINUX_EGL_GET_VISUAL_INFO_FAILED); + } + + _sapp_x11_create_window(visual_info->visual, visual_info->depth); + XFree(visual_info); + + _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL); + if (EGL_NO_SURFACE == _sapp.egl.surface) { + _SAPP_PANIC(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED); + } + + EGLint ctx_attrs[] = { + #if defined(SOKOL_GLCORE33) + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #elif defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, + #else + EGL_CONTEXT_CLIENT_VERSION, 2, + #endif + EGL_NONE, + }; + + _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs); + if (EGL_NO_CONTEXT == _sapp.egl.context) { + _SAPP_PANIC(LINUX_EGL_CREATE_CONTEXT_FAILED); + } + + if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) { + _SAPP_PANIC(LINUX_EGL_MAKE_CURRENT_FAILED); + } + + eglSwapInterval(_sapp.egl.display, _sapp.swap_interval); + +#if defined(SOKOL_GLES3) + _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; +#endif +} + +_SOKOL_PRIVATE void _sapp_egl_destroy(void) { + if (_sapp.egl.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (_sapp.egl.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.egl.display, _sapp.egl.context); + _sapp.egl.context = EGL_NO_CONTEXT; + } + + if (_sapp.egl.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.egl.display, _sapp.egl.surface); + _sapp.egl.surface = EGL_NO_SURFACE; + } + + eglTerminate(_sapp.egl.display); + _sapp.egl.display = EGL_NO_DISPLAY; + } +} + +#endif /* _SAPP_GLX */ + _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { /* The following lines are here to trigger a linker error instead of an obscure runtime error if the user has forgotten to add -pthread to @@ -10410,7 +10885,7 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { XrmInitialize(); _sapp.x11.display = XOpenDisplay(NULL); if (!_sapp.x11.display) { - _sapp_fail("XOpenDisplay() failed!\n"); + _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); } _sapp.x11.screen = DefaultScreen(_sapp.x11.display); _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); @@ -10418,23 +10893,28 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { _sapp_x11_query_system_dpi(); _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; _sapp_x11_init_extensions(); - _sapp_x11_create_hidden_cursor(); + _sapp_x11_create_cursors(); +#if defined(_SAPP_GLX) _sapp_glx_init(); Visual* visual = 0; int depth = 0; _sapp_glx_choose_visual(&visual, &depth); _sapp_x11_create_window(visual, depth); _sapp_glx_create_context(); + _sapp_glx_swapinterval(_sapp.swap_interval); +#else + _sapp_egl_init(); +#endif + sapp_set_icon(&desc->icon); _sapp.valid = true; _sapp_x11_show_window(); if (_sapp.fullscreen) { _sapp_x11_set_fullscreen(true); } - _sapp_x11_query_window_size(); - _sapp_glx_swapinterval(_sapp.swap_interval); + XFlush(_sapp.x11.display); while (!_sapp.quit_ordered) { - _sapp_glx_make_current(); + _sapp_timing_measure(&_sapp.timing); int count = XPending(_sapp.x11.display); while (count--) { XEvent event; @@ -10442,7 +10922,11 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { _sapp_x11_process_event(&event); } _sapp_frame(); +#if defined(_SAPP_GLX) _sapp_glx_swap_buffers(); +#else + eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); +#endif XFlush(_sapp.x11.display); /* handle quit-requested, either from window or from sapp_request_quit() */ if (_sapp.quit_requested && !_sapp.quit_ordered) { @@ -10455,8 +10939,13 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { } } _sapp_call_cleanup(); +#if defined(_SAPP_GLX) _sapp_glx_destroy_context(); +#else + _sapp_egl_destroy(); +#endif _sapp_x11_destroy_window(); + _sapp_x11_destroy_cursors(); XCloseDisplay(_sapp.x11.display); _sapp_discard_state(); } @@ -10470,7 +10959,13 @@ int main(int argc, char* argv[]) { #endif /* SOKOL_NO_ENTRY */ #endif /* _SAPP_LINUX */ -/*== PUBLIC API FUNCTIONS ====================================================*/ +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public #if defined(SOKOL_NO_ENTRY) SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { SOKOL_ASSERT(desc); @@ -10482,13 +10977,10 @@ SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { _sapp_emsc_run(desc); #elif defined(_SAPP_WIN32) _sapp_win32_run(desc); - #elif defined(_SAPP_UWP) - _sapp_uwp_run(desc); #elif defined(_SAPP_LINUX) _sapp_linux_run(desc); #else - // calling sapp_run() directly is not supported on Android) - _sapp_fail("sapp_run() not supported on this platform!"); + #error "sapp_run() not supported on this platform" #endif } @@ -10497,7 +10989,7 @@ sapp_desc sokol_main(int argc, char* argv[]) { _SOKOL_UNUSED(argc); _SOKOL_UNUSED(argv); sapp_desc desc; - memset(&desc, 0, sizeof(desc)); + _sapp_clear(&desc, sizeof(desc)); return desc; } #else @@ -10523,6 +11015,10 @@ SOKOL_API_IMPL uint64_t sapp_frame_count(void) { return _sapp.frame_count; } +SOKOL_API_IMPL double sapp_frame_duration(void) { + return _sapp_timing_get_avg(&_sapp.timing); +} + SOKOL_API_IMPL int sapp_width(void) { return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; } @@ -10573,6 +11069,28 @@ SOKOL_API_IMPL float sapp_dpi_scale(void) { return _sapp.dpi_scale; } +SOKOL_APP_IMPL const void* sapp_egl_get_display(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.display; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.display; + #else + return 0; + #endif +} + +SOKOL_APP_IMPL const void* sapp_egl_get_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.context; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.context; + #else + return 0; + #endif +} + SOKOL_API_IMPL bool sapp_gles2(void) { return _sapp.gles2_fallback; } @@ -10593,17 +11111,15 @@ 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) _sapp_win32_toggle_fullscreen(); - #elif defined(_SAPP_UWP) - _sapp_uwp_toggle_fullscreen(); #elif defined(_SAPP_LINUX) _sapp_x11_toggle_fullscreen(); #endif @@ -10613,13 +11129,13 @@ SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void) { SOKOL_API_IMPL void sapp_show_mouse(bool show) { if (_sapp.mouse.shown != show) { #if defined(_SAPP_MACOS) - _sapp_macos_show_mouse(show); + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); #elif defined(_SAPP_WIN32) - _sapp_win32_show_mouse(show); + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); #elif defined(_SAPP_LINUX) - _sapp_x11_show_mouse(show); - #elif defined(_SAPP_UWP) - _sapp_uwp_show_mouse(show); + _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); #endif _sapp.mouse.shown = show; } @@ -10647,6 +11163,26 @@ SOKOL_API_IMPL bool sapp_mouse_locked(void) { return _sapp.mouse.locked; } +SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.mouse.current_cursor != cursor) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); + #endif + _sapp.mouse.current_cursor = cursor; + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + SOKOL_API_IMPL void sapp_request_quit(void) { _sapp.quit_requested = true; } @@ -10665,7 +11201,6 @@ SOKOL_API_IMPL void sapp_consume_event(void) { /* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { - SOKOL_ASSERT(_sapp.clipboard.enabled); if (!_sapp.clipboard.enabled) { return; } @@ -10683,7 +11218,6 @@ SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { } SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.enabled); if (!_sapp.clipboard.enabled) { return ""; } @@ -10711,6 +11245,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; @@ -10747,15 +11309,15 @@ SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request SOKOL_ASSERT(_sapp.drop.enabled); SOKOL_ASSERT(request); SOKOL_ASSERT(request->callback); - SOKOL_ASSERT(request->buffer_ptr); - SOKOL_ASSERT(request->buffer_size > 0); + SOKOL_ASSERT(request->buffer.ptr); + SOKOL_ASSERT(request->buffer.size > 0); #if defined(_SAPP_EMSCRIPTEN) const int index = request->dropped_file_index; sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; if ((index < 0) || (index >= _sapp.drop.num_files)) { error_code = SAPP_HTML5_FETCH_ERROR_OTHER; } - if (sapp_html5_get_dropped_file_size(index) > request->buffer_size) { + if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; } if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { @@ -10764,15 +11326,15 @@ SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request (int)error_code, request->callback, 0, // fetched_size - request->buffer_ptr, - request->buffer_size, + (void*)request->buffer.ptr, + request->buffer.size, request->user_data); } else { sapp_js_fetch_dropped_file(index, request->callback, - request->buffer_ptr, - request->buffer_size, + (void*)request->buffer.ptr, + request->buffer.size, request->user_data); } #else @@ -10844,16 +11406,6 @@ SOKOL_API_IMPL const void* sapp_ios_get_window(void) { return 0; #endif } -SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ - #if defined(_SAPP_IOS) - const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { SOKOL_ASSERT(_sapp.valid); @@ -10873,6 +11425,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) @@ -10952,7 +11513,8 @@ SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { } SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { - SOKOL_ASSERT(_sapp.valid); + // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() + // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) #if defined(_SAPP_ANDROID) return (void*)_sapp.android.activity; #else diff --git a/src/sokol/sokol_gfx.h b/src/sokol/sokol_gfx.h index 87276666d..ecd79d9bb 100644 --- a/src/sokol/sokol_gfx.h +++ b/src/sokol/sokol_gfx.h @@ -39,15 +39,15 @@ Optionally provide the following defines with your own implementations: - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_MALLOC(s) - your own malloc function (default: malloc(s)) - SOKOL_FREE(p) - your own free function (default: free(p)) - SOKOL_LOG(msg) - your own logging function (default: puts(msg)) - SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) - SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_GFX_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) + SOKOL_EXTERNAL_GL_LOADER - indicates that you're using your own GL loader, in this case + sokol_gfx.h will not include any platform GL headers and disable + the integrated Win32 GL loader If sokol_gfx.h is compiled as a DLL, define the following before including the declaration or implementation: @@ -81,6 +81,7 @@ offline shader cross-compiler, see here: https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + STEP BY STEP ============ --- to initialize sokol_gfx, after creating a window and a 3D-API @@ -88,6 +89,30 @@ sg_setup(const sg_desc*) + Depending on the selected 3D backend, sokol-gfx requires some + information, like a device pointer framebuffer pixel formats + and so on. If you are using sokol_app.h for the window system + glue, you can use a helper function provided in the sokol_glue.h + header: + + #include "sokol_gfx.h" + #include "sokol_app.h" + #include "sokol_glue.h" + //... + sg_setup(&(sg_desc){ + .context = sapp_sgcontext(), + }); + + To get any logging output for errors and from the validation layer, you + need to provide a logging callback. Easiest way is through sokol_log.h: + + #include "sokol_log.h" + //... + sg_setup(&(sg_desc){ + //... + .logger.func = slog_func, + }); + --- create resource objects (at least buffers, shaders and pipelines, and optionally images and passes): @@ -126,7 +151,10 @@ --- optionally update shader uniform data with: - sg_apply_uniforms(sg_shader_stage stage, int ub_index, const void* data, int num_bytes) + sg_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range* data) + + Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout + of the uniform data passed into sg_apply_uniforms(). --- kick off a draw call with: @@ -166,7 +194,7 @@ sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) - ...or if you want to specifiy the viewport rectangle with float values: + ...or if you want to specify the viewport rectangle with float values: sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) @@ -243,6 +271,11 @@ bool sg_query_buffer_overflow(sg_buffer buf) + You can manually check to see if an overflow would occur before adding + any data to a buffer by calling + + bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size) + NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of data will be 4-byte aligned in the destination buffer. This means that there will be gaps in index buffers containing 16-bit indices @@ -268,22 +301,25 @@ by calling sg_query_desc(). This will return an sg_desc struct with the default values patched in instead of any zero-initialized values - --- you can inspect various internal resource attributes via: - - sg_buffer_info sg_query_buffer_info(sg_buffer buf) - sg_image_info sg_query_image_info(sg_image img) - sg_shader_info sg_query_shader_info(sg_shader shd) - sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) - sg_pass_info sg_query_pass_info(sg_pass pass) + --- you can get a desc struct matching the creation attributes of a + specific resource object via: - ...please note that the returned info-structs are tied quite closely - to sokol_gfx.h internals, and may change more often than other - public API functions and structs. + sg_buffer_desc sg_query_buffer_desc(sg_buffer buf) + sg_image_desc sg_query_image_desc(sg_image img) + sg_shader_desc sq_query_shader_desc(sg_shader shd) + sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip) + sg_pass_desc sg_query_pass_desc(sg_pass pass) - --- you can ask at runtime what backend sokol_gfx.h has been compiled - for, or whether the GLES3 backend had to fall back to GLES2 with: + ...but NOTE that the returned desc structs may be incomplete, only + creation attributes that are kept around internally after resource + creation will be filled in, and in some cases (like shaders) that's + very little. Any missing attributes will be set to zero. The returned + desc structs might still be useful as partial blueprint for creating + similar resources if filled up with the missing attributes. - sg_backend sg_query_backend(void) + Calling the query-desc functions on an invalid resource will return + completely zeroed structs (it makes sense to check the resource state + with sg_query_*_state() first) --- you can query the default resource creation parameters through the functions @@ -298,6 +334,24 @@ will be replaced with their concrete values in the returned desc struct. + --- you can inspect various internal resource runtime values via: + + sg_buffer_info sg_query_buffer_info(sg_buffer buf) + sg_image_info sg_query_image_info(sg_image img) + sg_shader_info sg_query_shader_info(sg_shader shd) + sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) + sg_pass_info sg_query_pass_info(sg_pass pass) + + ...please note that the returned info-structs are tied quite closely + to sokol_gfx.h internals, and may change more often than other + public API functions and structs. + + --- you can ask at runtime what backend sokol_gfx.h has been compiled + for, or whether the GLES3 backend had to fall back to GLES2 with: + + sg_backend sg_query_backend(void) + + ON INITIALIZATION: ================== When calling sg_setup(), a pointer to an sg_desc struct must be provided @@ -331,30 +385,129 @@ a convenience function to get a sg_context_desc struct filled out with context information provided by sokol_app.h - See the documention block of the sg_desc struct below for more information. + See the documentation block of the sg_desc struct below for more information. + + + UNIFORM DATA LAYOUT: + ==================== + NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry + about the following details. + + The data that's passed into the sg_apply_uniforms() function must adhere to + specific layout rules so that the GPU shader finds the uniform block + items at the right offset. + + For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform + blocks, but not about the internal layout. The data will just be copied into + a uniform/constant buffer in a single operation and it's up you to arrange the + CPU-side layout so that it matches the GPU side layout. This also means that with + the D3D11 and Metal backends you are not limited to a 'cross-platform' subset + of uniform variable types. + + If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here. + + For the GL backends, the internal layout of uniform blocks matters though, + and you are limited to a small number of uniform variable types. This is + because sokol-gfx must be able to locate the uniform block members in order + to upload them to the GPU with glUniformXXX() calls. + + To describe the uniform block layout to sokol-gfx, the following information + must be passed to the sg_make_shader() call in the sg_shader_desc struct: + + - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or + SG_UNIFORMLAYOUT_STD140) + - a list of the uniform block members types in the correct order they + appear on the CPU side + + For example if the GLSL shader has the following uniform declarations: + + uniform mat4 mvp; + uniform vec2 offset0; + uniform vec2 offset1; + uniform vec2 offset2; + + ...and on the CPU side, there's a similar C struct: + + typedef struct { + float mvp[16]; + float offset0[2]; + float offset1[2]; + float offset2[2]; + } params_t; + + ...the uniform block description in the sg_shader_desc must look like this: + + sg_shader_desc desc = { + .vs.uniform_blocks[0] = { + .size = sizeof(params_t), + .layout = SG_UNIFORMLAYOUT_NATIVE, // this is the default and can be omitted + .uniforms = { + // order must be the same as in 'params_t': + [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 }, + [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 }, + [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 }, + [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 }, + } + } + }; + + With this information sokol-gfx can now compute the correct offsets of the data items + within the uniform block struct. + + The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used, + but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is + described in the next section: + + + CROSS-BACKEND COMMON UNIFORM DATA LAYOUT + ======================================== + For cross-platform / cross-3D-backend code it is important that the same uniform block + layout on the CPU side can be used for all sokol-gfx backends. To achieve this, + a common subset of the std140 layout must be used: + + - The uniform block layout hint in sg_shader_desc must be explicitly set to + SG_UNIFORMLAYOUT_STD140. + - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums): + - float => SG_UNIFORMTYPE_FLOAT + - vec2 => SG_UNIFORMTYPE_FLOAT2 + - vec3 => SG_UNIFORMTYPE_FLOAT3 + - vec4 => SG_UNIFORMTYPE_FLOAT4 + - int => SG_UNIFORMTYPE_INT + - ivec2 => SG_UNIFORMTYPE_INT2 + - ivec3 => SG_UNIFORMTYPE_INT3 + - ivec4 => SG_UNIFORMTYPE_INT4 + - mat4 => SG_UNIFORMTYPE_MAT4 + - Alignment for those types must be as follows (in bytes): + - float => 4 + - vec2 => 8 + - vec3 => 16 + - vec4 => 16 + - int => 4 + - ivec2 => 8 + - ivec3 => 16 + - ivec4 => 16 + - mat4 => 16 + - Arrays are only allowed for the following types: vec4, int4, mat4. + + Note that the HLSL cbuffer layout rules are slightly different from the + std140 layout rules, this means that the cbuffer declarations in HLSL code + must be tweaked so that the layout is compatible with std140. + + The by far easiest way to tacke the common uniform block layout problem is + to use the sokol-shdc shader cross-compiler tool! + BACKEND-SPECIFIC TOPICS: ======================== - --- the GL backends need to know about the internal structure of uniform - blocks, and the texture sampler-name and -type: - - typedef struct { - float mvp[16]; // model-view-projection matrix - float offset0[2]; // some 2D vectors - float offset1[2]; - float offset2[2]; - } params_t; + --- The GL backends need to know about the internal structure of uniform + blocks, and the texture sampler-name and -type. The uniform layout details + are described in the UNIFORM DATA LAYOUT section above. // uniform block structure and texture image definition in sg_shader_desc: sg_shader_desc desc = { // uniform block description (size and internal structure) .vs.uniform_blocks[0] = { - .size = sizeof(params_t), - .uniforms = { - [0] = { .name="mvp", .type=SG_UNIFORMTYPE_MAT4 }, - [1] = { .name="offset0", .type=SG_UNIFORMTYPE_VEC2 }, - ... - } + ... }, // one texture on the fragment-shader-stage, GLES2/WebGL needs name and image type .fs.images[0] = { .name="tex", .type=SG_IMAGETYPE_ARRAY } @@ -431,6 +584,7 @@ } }; + WORKING WITH CONTEXTS ===================== sokol-gfx allows to switch between different rendering contexts and @@ -474,6 +628,7 @@ https://github.com/floooh/sokol-samples/blob/master/glfw/multiwindow-glfw.c + TRACE HOOKS: ============ sokol_gfx.h optionally allows to install "trace hook" callbacks for @@ -503,6 +658,7 @@ imgui/sokol_gfx_imgui.h header which implements a realtime debugging UI for sokol_gfx.h on top of Dear ImGui. + A NOTE ON PORTABLE PACKED VERTEX FORMATS: ========================================= There are two things to consider when using packed @@ -531,9 +687,11 @@ - SG_VERTEXFORMAT_SHORT2 - SG_VERTEXFORMAT_SHORT4 - - WebGL/GLES2 cannot use integer vertex shader inputs (int or ivecn) + - WebGL/GLES2 cannot use integer vertex shader inputs (int or ivecn) or the following: - - SG_VERTEXFORMAT_UINT10_N2 is not supported on WebGL/GLES2 + - SG_VERTEXFORMAT_UINT10_N2 + - SG_VERTEXFORMAT_HALF2, SG_VERTEXFORMAT_HALF4 + (commonly supported extension: OES_vertex_half_float) So for a vertex input layout which works on all platforms, only use the following vertex formats, and if needed "expand" the normalized vertex shader @@ -551,10 +709,278 @@ - SG_VERTEXFORMAT_SHORT4N, - SG_VERTEXFORMAT_USHORT4N - TODO: - ==== - - talk about asynchronous resource creation + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sg_setup(&(sg_desc){ + // ... + .allocator = { + .alloc = my_alloc, + .free = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_gfx.h + itself though, not any allocations in OS libraries. + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sg_setup(&(sg_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sg' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gfx like this: + + sg_setup(&(sg_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + COMMIT LISTENERS + ================ + It's possible to hook callback functions into sokol-gfx which are called from + inside sg_commit() in unspecified order. This is mainly useful for libraries + that build on top of sokol_gfx.h to be notified about the end/start of a frame. + + To add a commit listener, call: + + static void my_commit_listener(void* user_data) { + ... + } + + bool success = sg_add_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + The function returns false if the internal array of commit listeners is full, + or the same commit listener had already been added. + + If the function returns true, my_commit_listener() will be called each frame + from inside sg_commit(). + + By default, 1024 distinct commit listeners can be added, but this number + can be tweaked in the sg_setup() call: + + sg_setup(&(sg_desc){ + .max_commit_listeners = 2048, + }); + + An sg_commit_listener item is equal to another if both the function + pointer and user_data field are equal. + + To remove a commit listener: + + bool success = sg_remove_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + ...where the .func and .user_data field are equal to a previous + sg_add_commit_listener() call. The function returns true if the commit + listener item was found and removed, and false otherwise. + + + RESOURCE CREATION AND DESTRUCTION IN DETAIL + =========================================== + The 'vanilla' way to create resource objects is with the 'make functions': + + sg_buffer sg_make_buffer(const sg_buffer_desc* desc) + sg_image sg_make_image(const sg_image_desc* desc) + sg_shader sg_make_shader(const sg_shader_desc* desc) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) + sg_pass sg_make_pass(const sg_pass_desc* desc) + + This will result in one of three cases: + + 1. The returned handle is invalid. This happens when there are no more + free slots in the resource pool for this resource type. An invalid + handle is associated with the INVALID resource state, for instance: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) { + // buffer pool is exhausted + } + + 2. The returned handle is valid, but creating the underlying resource + has failed for some reason. This results in a resource object in the + FAILED state. The reason *why* resource creation has failed differ + by resource type. Look for log messages with more details. A failed + resource state can be checked with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) { + // creating the resource has failed + } + + 3. And finally, if everything goes right, the returned resource is + in resource state VALID and ready to use. This can be checked + with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) { + // creating the resource has failed + } + + When calling the 'make functions', the created resource goes through a number + of states: + + - INITIAL: the resource slot associated with the new resource is currently + free (technically, there is no resource yet, just an empty pool slot) + - ALLOC: a handle for the new resource has been allocated, this just means + a pool slot has been reserved. + - VALID or FAILED: in VALID state any 3D API backend resource objects have + been successfully created, otherwise if anything went wrong, the resource + will be in FAILED state. + + Sometimes it makes sense to first grab a handle, but initialize the + underlying resource at a later time. For instance when loading data + asynchronously from a slow data source, you may know what buffers and + textures are needed at an early stage of the loading process, but actually + loading the buffer or texture content can only be completed at a later time. + + For such situations, sokol-gfx resource objects can be created in two steps. + You can allocate a handle upfront with one of the 'alloc functions': + + sg_buffer sg_alloc_buffer(void) + sg_image sg_alloc_image(void) + sg_shader sg_alloc_shader(void) + sg_pipeline sg_alloc_pipeline(void) + sg_pass sg_alloc_pass(void) + + This will return a handle with the underlying resource object in the + ALLOC state: + + sg_image img = sg_alloc_image(); + if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) { + // allocating an image handle has succeeded, otherwise + // the image pool is full + } + + Such an 'incomplete' handle can be used in most sokol-gfx rendering functions + without doing any harm, sokol-gfx will simply skip any rendering operation + that involve resources which are not in VALID state. + + At a later time (for instance once the texture has completed loading + asynchronously), the resource creation can be completed by calling one of + the 'init functions', those functions take an existing resource handle and + 'desc struct': + + void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc) + void sg_init_image(sg_image img, const sg_image_desc* desc) + void sg_init_shader(sg_shader shd, const sg_shader_desc* desc) + void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc) + void sg_init_pass(sg_pass pass, const sg_pass_desc* desc) + + The init functions expect a resource in ALLOC state, and after the function + returns, the resource will be either in VALID or FAILED state. Calling + an 'alloc function' followed by the matching 'init function' is fully + equivalent with calling the 'make function' alone. + + Destruction can also happen as a two-step process. The 'uninit functions' + will put a resource object from the VALID or FAILED state back into the + ALLOC state: + + void sg_uninit_buffer(sg_buffer buf) + void sg_uninit_image(sg_image img) + void sg_uninit_shader(sg_shader shd) + void sg_uninit_pipeline(sg_pipeline pip) + void sg_uninit_pass(sg_pass pass) + + Calling the 'uninit functions' with a resource that is not in the VALID or + FAILED state is a no-op. + + To finally free the pool slot for recycling call the 'dealloc functions': + + void sg_dealloc_buffer(sg_buffer buf) + void sg_dealloc_image(sg_image img) + void sg_dealloc_shader(sg_shader shd) + void sg_dealloc_pipeline(sg_pipeline pip) + void sg_dealloc_pass(sg_pass pass) + + Calling the 'dealloc functions' on a resource that's not in ALLOC state is + a no-op, but will generate a warning log message. + + Calling an 'uninit function' and 'dealloc function' in sequence is equivalent + with calling the associated 'destroy function': + + void sg_destroy_buffer(sg_buffer buf) + void sg_destroy_image(sg_image img) + void sg_destroy_shader(sg_shader shd) + void sg_destroy_pipeline(sg_pipeline pip) + void sg_destroy_pass(sg_pass pass) + + The 'destroy functions' can be called on resources in any state and generally + do the right thing (for instance if the resource is in ALLOC state, the destroy + function will be equivalent to the 'dealloc function' and skip the 'uninit part'). + + And finally to close the circle, the 'fail functions' can be called to manually + put a resource in ALLOC state into the FAILED state: + + sg_fail_buffer(sg_buffer buf) + sg_fail_image(sg_image img) + sg_fail_shader(sg_shader shd) + sg_fail_pipeline(sg_pipeline pip) + sg_fail_pass(sg_pass pass) + + This is recommended if anything went wrong outside of sokol-gfx during asynchronous + resource creation (for instance the file loading operation failed). In this case, + the 'fail function' should be called instead of the 'init function'. + + Calling a 'fail function' on a resource that's not in ALLOC state is a no-op, + but will generate a warning log message. + + NOTE: that two-step resource creation usually only makes sense for buffers + and images, but not for shaders, pipelines or passes. Most notably, trying + to create a pipeline object with a shader that's not in VALID state will + trigger a validation layer error, or if the validation layer is disabled, + result in a pipeline object in FAILED state. Same when trying to create + a pass object with image invalid image objects. + + LICENSE + ======= zlib/libpng license Copyright (c) 2018 Andre Weissflog @@ -644,7 +1070,7 @@ typedef struct sg_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:4202) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ #endif #if defined(__cplusplus) #define SG_RANGE(x) sg_range{ &x, sizeof(x) } @@ -777,6 +1203,7 @@ typedef enum sg_pixel_format { SG_PIXELFORMAT_RG16SI, SG_PIXELFORMAT_RG16F, SG_PIXELFORMAT_RGBA8, + SG_PIXELFORMAT_SRGB8A8, SG_PIXELFORMAT_RGBA8SN, SG_PIXELFORMAT_RGBA8UI, SG_PIXELFORMAT_RGBA8SI, @@ -820,6 +1247,8 @@ typedef enum sg_pixel_format { SG_PIXELFORMAT_ETC2_RG11, SG_PIXELFORMAT_ETC2_RG11SN, + SG_PIXELFORMAT_RGB9E5, + _SG_PIXELFORMAT_NUM, _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF } sg_pixel_format; @@ -868,7 +1297,9 @@ 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) + int gl_max_combined_texture_image_units; // <= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (only on GL backends) } sg_limits; /* @@ -1168,6 +1599,8 @@ typedef enum sg_vertex_format { SG_VERTEXFORMAT_SHORT4N, SG_VERTEXFORMAT_USHORT4N, SG_VERTEXFORMAT_UINT10_N2, + SG_VERTEXFORMAT_HALF2, + SG_VERTEXFORMAT_HALF4, _SG_VERTEXFORMAT_NUM, _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF } sg_vertex_format; @@ -1204,11 +1637,57 @@ typedef enum sg_uniform_type { SG_UNIFORMTYPE_FLOAT2, SG_UNIFORMTYPE_FLOAT3, SG_UNIFORMTYPE_FLOAT4, + SG_UNIFORMTYPE_INT, + SG_UNIFORMTYPE_INT2, + SG_UNIFORMTYPE_INT3, + SG_UNIFORMTYPE_INT4, SG_UNIFORMTYPE_MAT4, _SG_UNIFORMTYPE_NUM, _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF } sg_uniform_type; +/* + sg_uniform_layout + + A hint for the interior memory layout of uniform blocks. This is + only really relevant for the GL backend where the internal layout + of uniform blocks must be known to sokol-gfx. For all other backends the + internal memory layout of uniform blocks doesn't matter, sokol-gfx + will just pass uniform data as a single memory blob to the + 3D backend. + + SG_UNIFORMLAYOUT_NATIVE (default) + Native layout means that a 'backend-native' memory layout + is used. For the GL backend this means that uniforms + are packed tightly in memory (e.g. there are no padding + bytes). + + SG_UNIFORMLAYOUT_STD140 + The memory layout is a subset of std140. Arrays are only + allowed for the FLOAT4, INT4 and MAT4. Alignment is as + is as follows: + + FLOAT, INT: 4 byte alignment + FLOAT2, INT2: 8 byte alignment + FLOAT3, INT3: 16 byte alignment(!) + FLOAT4, INT4: 16 byte alignment + MAT4: 16 byte alignment + FLOAT4[], INT4[]: 16 byte alignment + + The overall size of the uniform block must be a multiple + of 16. + + For more information search for 'UNIFORM DATA LAYOUT' in the documentation block + at the start of the header. +*/ +typedef enum sg_uniform_layout { + _SG_UNIFORMLAYOUT_DEFAULT, /* value 0 reserved for default-init */ + SG_UNIFORMLAYOUT_NATIVE, /* default: layout depends on currently active backend */ + SG_UNIFORMLAYOUT_STD140, /* std140: memory layout according to std140 */ + _SG_UNIFORMLAYOUT_NUM, + _SG_UNIFORMLAYOUT_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_layout; + /* sg_cull_mode @@ -1710,6 +2189,7 @@ typedef struct sg_image_desc { defaults are "vs_4_0" and "ps_4_0") - reflection info for each uniform block used by the shader stage: - the size of the uniform block in bytes + - a memory layout hint (native vs std140, only required for GL backends) - reflection info for each uniform block member (only required for GL backends): - member name - member type (SG_UNIFORMTYPE_xxx) @@ -1742,6 +2222,7 @@ typedef struct sg_shader_uniform_desc { typedef struct sg_shader_uniform_block_desc { size_t size; + sg_uniform_layout layout; sg_shader_uniform_desc uniforms[SG_MAX_UB_MEMBERS]; } sg_shader_uniform_block_desc; @@ -1811,9 +2292,9 @@ typedef struct sg_shader_desc { .enabled: false .front/back: .compare: SG_COMPAREFUNC_ALWAYS + .fail_op: SG_STENCILOP_KEEP .depth_fail_op: SG_STENCILOP_KEEP .pass_op: SG_STENCILOP_KEEP - .compare: SG_COMPAREFUNC_ALWAYS .read_mask: 0 .write_mask: 0 .ref: 0 @@ -2075,12 +2556,10 @@ typedef struct sg_image_info { uint32_t upd_frame_index; /* frame index of last sg_update_image() */ int num_slots; /* number of renaming-slots for dynamically updated images */ int active_slot; /* currently active write-slot for dynamically updated images */ - int width; /* image width */ - int height; /* image height */ } sg_image_info; typedef struct sg_shader_info { - sg_slot_info slot; /* resoure pool slot info */ + sg_slot_info slot; /* resource pool slot info */ } sg_shader_info; typedef struct sg_pipeline_info { @@ -2091,6 +2570,198 @@ typedef struct sg_pass_info { sg_slot_info slot; /* resource pool slot info */ } sg_pass_info; +/* + sg_log_item + + An enum with a unique item for each log message, warning, error + and validation layer message. +*/ +#define _SG_LOG_ITEMS \ + _SG_LOGITEM_XMACRO(OK, "Ok") \ + _SG_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SG_LOGITEM_XMACRO(GL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (gl)") \ + _SG_LOGITEM_XMACRO(GL_3D_TEXTURES_NOT_SUPPORTED, "3d textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_ARRAY_TEXTURES_NOT_SUPPORTED, "array textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_COMPILATION_FAILED, "shader compilation failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_INCOMPLETE, "framebuffer completeness check failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_MSAA_FRAMEBUFFER_INCOMPLETE, "completeness check failed for msaa resolve framebuffer (gl)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_FAILED, "CreateTexture2D() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_SRV_FAILED, "CreateShaderResourceView() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 3D texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_FAILED, "CreateTexture3D() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_SRV_FAILED, "CreateShaderResourceView() failed for 3d texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_MSAA_TEXTURE_FAILED, "CreateTexture2D() failed for MSAA render target texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_SAMPLER_STATE_FAILED, "CreateSamplerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED, "loading d3dcompiler_47.dll failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_FAILED, "shader compilation failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_CONSTANT_BUFFER_FAILED, "CreateBuffer() failed for uniform constant buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_INPUT_LAYOUT_FAILED, "CreateInputLayout() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RASTERIZER_STATE_FAILED, "CreateRasterizerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED, "CreateDepthStencilState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BLEND_STATE_FAILED, "CreateBlendState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RTV_FAILED, "CreateRenderTargetView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DSV_FAILED, "CreateDepthStencilView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \ + _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_VERTEX_SHADER_ENTRY_NOT_FOUND, "vertex shader entry function not found (metal)") \ + _SG_LOGITEM_XMACRO(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND, "fragment shader entry not found (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(WGPU_MAP_UNIFORM_BUFFER_FAILED, "mapping uniform buffer failed (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_STAGING_BUFFER_FULL_COPY_TO_BUFFER, "per frame staging buffer full when copying to buffer (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_STAGING_BUFFER_FULL_COPY_TO_TEXTURE, "per frame staging buffer full when copying to texture (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_RESET_STATE_CACHE_FIXME, "_sg_wgpu_reset_state_cache: fixme") \ + _SG_LOGITEM_XMACRO(WGPU_ACTIVATE_CONTEXT_FIXME, "_sg_wgpu_activate_context: fixme") \ + _SG_LOGITEM_XMACRO(UNINIT_BUFFER_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in buffer uninit (must be same as for creation)") \ + _SG_LOGITEM_XMACRO(UNINIT_IMAGE_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in image uninit (must be same as for creation)") \ + _SG_LOGITEM_XMACRO(UNINIT_SHADER_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in shader uninit (must be same as for creation)") \ + _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in pipeline uninit (must be same as for creation)") \ + _SG_LOGITEM_XMACRO(UNINIT_PASS_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in pass uninit (must be same as for creation)") \ + _SG_LOGITEM_XMACRO(IDENTICAL_COMMIT_LISTENER, "attempting to add identical commit listener") \ + _SG_LOGITEM_XMACRO(COMMIT_LISTENER_ARRAY_FULL, "commit listener array full") \ + _SG_LOGITEM_XMACRO(TRACE_HOOKS_NOT_ENABLED, "sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined") \ + _SG_LOGITEM_XMACRO(DEALLOC_BUFFER_INVALID_STATE, "sg_dealloc_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_IMAGE_INVALID_STATE, "sg_dealloc_image(): image must be in alloc state") \ + _SG_LOGITEM_XMACRO(DEALLOC_SHADER_INVALID_STATE, "sg_dealloc_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_PIPELINE_INVALID_STATE, "sg_dealloc_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_PASS_INVALID_STATE, "sg_dealloc_pass(): pass must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_BUFFER_INVALID_STATE, "sg_init_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_IMAGE_INVALID_STATE, "sg_init_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_SHADER_INVALID_STATE, "sg_init_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_PIPELINE_INVALID_STATE, "sg_init_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_PASS_INVALID_STATE, "sg_init_pass(): pass must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_PASS_INVALID_STATE, "sg_uninit_pass(): pass must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(FAIL_BUFFER_INVALID_STATE, "sg_fail_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_IMAGE_INVALID_STATE, "sg_fail_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_SHADER_INVALID_STATE, "sg_fail_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_PIPELINE_INVALID_STATE, "sg_fail_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_PASS_INVALID_STATE, "sg_fail_pass(): pass must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(BUFFER_POOL_EXHAUSTED, "buffer pool exhausted") \ + _SG_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ + _SG_LOGITEM_XMACRO(SHADER_POOL_EXHAUSTED, "shader pool exhausted") \ + _SG_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted") \ + _SG_LOGITEM_XMACRO(PASS_POOL_EXHAUSTED, "pass pool exhausted") \ + _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_CANARY, "sg_buffer_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_SIZE, "sg_buffer_desc.size cannot be 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA, "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA_SIZE, "immutable buffer data size differs from buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_NO_DATA, "dynamic/stream usage buffers cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_NODATA, "sg_image_data: no data (.ptr and/or .size is zero)") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_DATA_SIZE, "sg_image_data: data size doesn't match expected surface size") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_CANARY, "sg_image_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_WIDTH, "sg_image_desc.width must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_HEIGHT, "sg_image_desc.height must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_PIXELFORMAT, "invalid pixel format for render-target image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, "invalid pixel format for non-render-target image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, "non-render-target images cannot be multisampled") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, "MSAA not supported for this pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_IMMUTABLE, "render target images must be SG_USAGE_IMMUTABLE") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_NO_DATA, "render target images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_INJECTED_NO_DATA, "images with injected textures cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA, "dynamic/stream images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE, "compressed images must be immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_CANARY, "sg_shader_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SOURCE, "shader source code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_BYTECODE, "shader byte code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, "shader source or byte code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UBS, "shader uniform blocks must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, "uniform block members must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_UB_MEMBERS, "GL backend requires uniform block member declarations") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_MEMBER_NAME, "uniform block member name missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, "size of uniform block members doesn't match uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_ARRAY_COUNT, "uniform array count must be >= 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_STD140_ARRAY_TYPE, "uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_IMGS, "shader images must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMG_NAME, "GL backend requires uniform block member names") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_NAMES, "GLES2 backend requires vertex attribute names") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_SEMANTICS, "D3D11 backend requires vertex attribute semantics") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, "vertex attribute name/semantic string too long (max len 16)") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_CANARY, "sg_pipeline_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER, "sg_pipeline_desc.shader missing or invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_ATTRS, "sg_pipeline_desc.layout.attrs is empty or not continuous") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_NAME, "GLES2/WebGL missing vertex attribute name in shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_CANARY, "sg_pass_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_NO_COLOR_ATTS, "sg_pass_desc.color_attachments[0] must be valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS, "color attachments must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE, "pass attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_MIPLEVEL, "pass attachment mip level is bigger than image has mipmaps") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_FACE, "pass attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_LAYER, "pass attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_SLICE, "pass attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_NO_RT, "pass attachment image must be render targets") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT, "pass color-attachment images must have a renderable pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT, "pass depth-attachment image must have depth pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_SIZES, "all pass attachments must have the same size") \ + _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS, "all pass attachments must have the same sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_PASS, "sg_begin_pass: pass must be valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_IMAGE, "sg_begin_pass: one or more attachment images are not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID_ID, "sg_apply_pipeline: invalid pipeline id provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_EXISTS, "sg_apply_pipeline: pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID, "sg_apply_pipeline: pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_EXISTS, "sg_apply_pipeline: shader object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_VALID, "sg_apply_pipeline: shader object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATT_COUNT, "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_FORMAT, "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTH_FORMAT, "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE, "sg_apply_bindings: must be called after sg_apply_pipeline") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_EXISTS, "sg_apply_bindings: currently applied pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_VALID, "sg_apply_bindings: currently applied pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBS, "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_EXISTS, "sg_apply_bindings: vertex buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_TYPE, "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_IB, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_EXISTS, "sg_apply_bindings: index buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_TYPE, "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMGS, "sg_apply_bindings: vertex shader image count doesn't match sg_shader_desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_EXISTS, "sg_apply_bindings: vertex shader image no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_TYPES, "sg_apply_bindings: one or more vertex shader image types don't match sg_shader_desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMGS, "sg_apply_bindings: fragment shader image count doesn't match sg_shader_desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_EXISTS, "sg_apply_bindings: fragment shader image no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_TYPES, "sg_apply_bindings: one or more fragment shader image types don't match sg_shader_desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_UB_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size exceeds declared uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_APPEND, "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_USAGE, "sg_append_buffer: cannot append to immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_SIZE, "sg_append_buffer: overall appended size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_UPDATE, "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_USAGE, "sg_update_image: cannot update immutable image") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_ONCE, "sg_update_image: only one update allowed per image and frame") \ + _SG_LOGITEM_XMACRO(VALIDATION_FAILED, "validation layer checks failed") \ + +#define _SG_LOGITEM_XMACRO(item,msg) SG_LOGITEM_##item, +typedef enum sg_log_item { + _SG_LOG_ITEMS +} sg_log_item; +#undef _SG_LOGITEM_XMACRO + /* sg_desc @@ -2112,9 +2783,15 @@ typedef struct sg_pass_info { .pipeline_pool_size 64 .pass_pool_size 16 .context_pool_size 16 - .sampler_cache_size 64 .uniform_buffer_size 4 MB (4*1024*1024) .staging_buffer_size 8 MB (8*1024*1024) + .sampler_cache_size 64 + .max_commit_listeners 1024 + .disable_validation false + + .allocator.alloc 0 (in this case, malloc() will be called) + .allocator.free 0 (in this case, free() will be called) + .allocator.user_data 0 .context.color_format: default value depends on selected backend: all GL backends: SG_PIXELFORMAT_RGBA8 @@ -2171,7 +2848,7 @@ typedef struct sg_pass_info { ID3D11DepthStencilView object of the default framebuffer, this function will be called in sg_begin_pass() when rendering to the default framebuffer - .context.metal.user_data + .context.d3d11.user_data optional user data pointer passed to the userdata versions of callback functions @@ -2192,7 +2869,7 @@ typedef struct sg_pass_info { .context.wgpu.depth_stencil_view_userdata_cb callback to get current default-pass depth-stencil-surface WGPUTextureView the pixel format of the default WGPUTextureView must be WGPUTextureFormat_Depth24Plus8 - .context.metal.user_data + .context.wgpu.user_data optional user data pointer passed to the userdata versions of callback functions @@ -2246,6 +2923,56 @@ typedef struct sg_context_desc { sg_wgpu_context_desc wgpu; } sg_context_desc; +/* + sg_commit_listener + + Used with function sg_add_commit_listener() to add a callback + which will be called in sg_commit(). This is useful for libraries + building on top of sokol-gfx to be notified about when a frame + ends (instead of having to guess, or add a manual 'new-frame' + function. +*/ +typedef struct sg_commit_listener { + void (*func)(void* user_data); + void* user_data; +} sg_commit_listener; + +/* + sg_allocator + + Used in sg_desc to provide custom memory-alloc and -free functions + to sokol_gfx.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sg_allocator { + void* (*alloc)(size_t size, void* user_data); + void (*free)(void* ptr, void* user_data); + void* user_data; +} sg_allocator; + +/* + sg_logger + + Used in sg_desc to provide a logging function. Please be aware + that without logging function, sokol-gfx will be completely + silent, e.g. it will not report errors, warnings and + validation layer messages. For maximum error verbosity, + compile in debug mode (e.g. NDEBUG *not* defined) and install + a logger (for instance the standard logging function from sokol_log.h). +*/ +typedef struct sg_logger { + void (*func)( + const char* tag, // always "sg" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sg_logger; + typedef struct sg_desc { uint32_t _start_canary; int buffer_pool_size; @@ -2257,6 +2984,10 @@ typedef struct sg_desc { int uniform_buffer_size; int staging_buffer_size; int sampler_cache_size; + int max_commit_listeners; + bool disable_validation; // disable validation layer even in debug mode, useful for tests + sg_allocator allocator; + sg_logger logger; // optional log function override sg_context_desc context; uint32_t _end_canary; } sg_desc; @@ -2269,6 +3000,8 @@ SOKOL_GFX_API_DECL void sg_reset_state_cache(void); SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks); SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name); SOKOL_GFX_API_DECL void sg_pop_debug_group(void); +SOKOL_GFX_API_DECL bool sg_add_commit_listener(sg_commit_listener listener); +SOKOL_GFX_API_DECL bool sg_remove_commit_listener(sg_commit_listener listener); /* resource creation, destruction and updating */ SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc); @@ -2285,6 +3018,7 @@ SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data); SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data); SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data); SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf); +SOKOL_GFX_API_DECL bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size); /* rendering functions */ SOKOL_GFX_API_DECL void sg_begin_default_pass(const sg_pass_action* pass_action, int width, int height); @@ -2319,6 +3053,12 @@ SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img); SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd); SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip); SOKOL_GFX_API_DECL sg_pass_info sg_query_pass_info(sg_pass pass); +/* get desc structs matching a specific resource (NOTE that not all creation attributes may be provided) */ +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_desc(sg_image img); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_desc(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_pass_desc sg_query_pass_desc(sg_pass pass); /* get resource creation desc struct with their default values replaced */ SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc); SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc); @@ -2332,26 +3072,26 @@ SOKOL_GFX_API_DECL sg_image sg_alloc_image(void); SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void); SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void); SOKOL_GFX_API_DECL sg_pass sg_alloc_pass(void); -SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf_id); -SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img_id); -SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd_id); -SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip_id); -SOKOL_GFX_API_DECL void sg_dealloc_pass(sg_pass pass_id); -SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc); -SOKOL_GFX_API_DECL void sg_init_image(sg_image img_id, const sg_image_desc* desc); -SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc); -SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc); -SOKOL_GFX_API_DECL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc); -SOKOL_GFX_API_DECL bool sg_uninit_buffer(sg_buffer buf_id); -SOKOL_GFX_API_DECL bool sg_uninit_image(sg_image img_id); -SOKOL_GFX_API_DECL bool sg_uninit_shader(sg_shader shd_id); -SOKOL_GFX_API_DECL bool sg_uninit_pipeline(sg_pipeline pip_id); -SOKOL_GFX_API_DECL bool sg_uninit_pass(sg_pass pass_id); -SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf_id); -SOKOL_GFX_API_DECL void sg_fail_image(sg_image img_id); -SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd_id); -SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip_id); -SOKOL_GFX_API_DECL void sg_fail_pass(sg_pass pass_id); +SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img); +SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_dealloc_pass(sg_pass pass); +SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL void sg_init_image(sg_image img, const sg_image_desc* desc); +SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd, const sg_shader_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pass(sg_pass pass, const sg_pass_desc* desc); +SOKOL_GFX_API_DECL void sg_uninit_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_uninit_image(sg_image img); +SOKOL_GFX_API_DECL void sg_uninit_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_uninit_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_uninit_pass(sg_pass pass); +SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_fail_image(sg_image img); +SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_fail_pass(sg_pass pass); /* rendering contexts (optional) */ SOKOL_GFX_API_DECL sg_context sg_setup_context(void); @@ -2409,53 +3149,42 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #endif #endif // SOKOL_GFX_INCLUDED -/*--- IMPLEMENTATION ---------------------------------------------------------*/ +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation #ifdef SOKOL_GFX_IMPL #define SOKOL_GFX_IMPL_INCLUDED (1) #if !(defined(SOKOL_GLCORE33)||defined(SOKOL_GLES2)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) #error "Please select a backend with SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND" #endif -#include /* memset */ -#include /* FLT_MAX */ +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sg_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free +#include // memset +#include // FLT_MAX #ifndef SOKOL_API_IMPL #define SOKOL_API_IMPL #endif #ifndef SOKOL_DEBUG #ifndef NDEBUG - #define SOKOL_DEBUG (1) + #define SOKOL_DEBUG #endif #endif #ifndef SOKOL_ASSERT #include #define SOKOL_ASSERT(c) assert(c) #endif -#ifndef SOKOL_VALIDATE_BEGIN - #define SOKOL_VALIDATE_BEGIN() _sg_validate_begin() -#endif -#ifndef SOKOL_VALIDATE - #define SOKOL_VALIDATE(cond, err) _sg_validate((cond), err) -#endif -#ifndef SOKOL_VALIDATE_END - #define SOKOL_VALIDATE_END() _sg_validate_end() -#endif #ifndef SOKOL_UNREACHABLE #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) #endif -#ifndef SOKOL_MALLOC - #include - #define SOKOL_MALLOC(s) malloc(s) - #define SOKOL_FREE(p) free(p) -#endif -#ifndef SOKOL_LOG - #ifdef SOKOL_DEBUG - #include - #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } - #else - #define SOKOL_LOG(s) - #endif -#endif #ifndef _SOKOL_PRIVATE #if defined(__GNUC__) || defined(__clang__) @@ -2506,9 +3235,335 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #pragma warning(disable:4055) /* 'type cast': from data pointer */ #endif -#if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +#if defined(SOKOL_D3D11) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #ifdef _MSC_VER + #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) + #pragma comment (lib, "WindowsApp") + #else + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #endif + #endif +#elif defined(SOKOL_METAL) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #define _SG_TARGET_MACOS (1) + #else + #define _SG_TARGET_IOS (1) + #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR + #define _SG_TARGET_IOS_SIMULATOR (1) + #endif + #endif + #import +#elif defined(SOKOL_WGPU) + #if defined(__EMSCRIPTEN__) + #include + #else + #include + #endif +#elif defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) #define _SOKOL_ANY_GL (1) + // include platform specific GL headers (or on Win32: use an embedded GL loader) + #if !defined(SOKOL_EXTERNAL_GL_LOADER) + #if defined(_WIN32) + #if defined(SOKOL_GLCORE33) && !defined(SOKOL_EXTERNAL_GL_LOADER) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #define _SOKOL_USE_WIN32_GL_LOADER (1) + #pragma comment (lib, "kernel32") // GetProcAddress() + #endif + #elif defined(__APPLE__) + #include + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #include + #else + #include + #include + #endif + #elif defined(__EMSCRIPTEN__) || defined(__ANDROID__) + #if defined(SOKOL_GLES3) + #include + #elif defined(SOKOL_GLES2) + #ifndef GL_EXT_PROTOTYPES + #define GL_GLEXT_PROTOTYPES + #endif + #include + #include + #endif + #elif defined(__linux__) || defined(__unix__) + #define GL_GLEXT_PROTOTYPES + #include + #endif + #endif + + // optional GL loader definitions (only on Win32) + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + #define __gl_h_ 1 + #define __gl32_h_ 1 + #define __gl31_h_ 1 + #define __GL_H__ 1 + #define __glext_h_ 1 + #define __GLEXT_H_ 1 + #define __gltypes_h_ 1 + #define __glcorearb_h_ 1 + #define __gl_glcorearb_h_ 1 + #define GL_APIENTRY APIENTRY + + typedef unsigned int GLenum; + typedef unsigned int GLuint; + typedef int GLsizei; + typedef char GLchar; + typedef ptrdiff_t GLintptr; + typedef ptrdiff_t GLsizeiptr; + typedef double GLclampd; + typedef unsigned short GLushort; + typedef unsigned char GLubyte; + typedef unsigned char GLboolean; + typedef uint64_t GLuint64; + typedef double GLdouble; + typedef unsigned short GLhalf; + typedef float GLclampf; + typedef unsigned int GLbitfield; + typedef signed char GLbyte; + typedef short GLshort; + typedef void GLvoid; + typedef int64_t GLint64; + typedef float GLfloat; + typedef struct __GLsync * GLsync; + typedef int GLint; + #define GL_INT_2_10_10_10_REV 0x8D9F + #define GL_R32F 0x822E + #define GL_PROGRAM_POINT_SIZE 0x8642 + #define GL_STENCIL_ATTACHMENT 0x8D20 + #define GL_DEPTH_ATTACHMENT 0x8D00 + #define GL_COLOR_ATTACHMENT2 0x8CE2 + #define GL_COLOR_ATTACHMENT0 0x8CE0 + #define GL_R16F 0x822D + #define GL_COLOR_ATTACHMENT22 0x8CF6 + #define GL_DRAW_FRAMEBUFFER 0x8CA9 + #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 + #define GL_NUM_EXTENSIONS 0x821D + #define GL_INFO_LOG_LENGTH 0x8B84 + #define GL_VERTEX_SHADER 0x8B31 + #define GL_INCR 0x1E02 + #define GL_DYNAMIC_DRAW 0x88E8 + #define GL_STATIC_DRAW 0x88E4 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 + #define GL_TEXTURE_CUBE_MAP 0x8513 + #define GL_FUNC_SUBTRACT 0x800A + #define GL_FUNC_REVERSE_SUBTRACT 0x800B + #define GL_CONSTANT_COLOR 0x8001 + #define GL_DECR_WRAP 0x8508 + #define GL_R8 0x8229 + #define GL_LINEAR_MIPMAP_LINEAR 0x2703 + #define GL_ELEMENT_ARRAY_BUFFER 0x8893 + #define GL_SHORT 0x1402 + #define GL_DEPTH_TEST 0x0B71 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 + #define GL_LINK_STATUS 0x8B82 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 + #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E + #define GL_RGBA16F 0x881A + #define GL_CONSTANT_ALPHA 0x8003 + #define GL_READ_FRAMEBUFFER 0x8CA8 + #define GL_TEXTURE0 0x84C0 + #define GL_TEXTURE_MIN_LOD 0x813A + #define GL_CLAMP_TO_EDGE 0x812F + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_TEXTURE_WRAP_R 0x8072 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_NEAREST_MIPMAP_NEAREST 0x2700 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 + #define GL_SRC_ALPHA_SATURATE 0x0308 + #define GL_STREAM_DRAW 0x88E0 + #define GL_ONE 1 + #define GL_NEAREST_MIPMAP_LINEAR 0x2702 + #define GL_RGB10_A2 0x8059 + #define GL_RGBA8 0x8058 + #define GL_SRGB8_ALPHA8 0x8C43 + #define GL_COLOR_ATTACHMENT1 0x8CE1 + #define GL_RGBA4 0x8056 + #define GL_RGB8 0x8051 + #define GL_ARRAY_BUFFER 0x8892 + #define GL_STENCIL 0x1802 + #define GL_TEXTURE_2D 0x0DE1 + #define GL_DEPTH 0x1801 + #define GL_FRONT 0x0404 + #define GL_STENCIL_BUFFER_BIT 0x00000400 + #define GL_REPEAT 0x2901 + #define GL_RGBA 0x1908 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 + #define GL_DECR 0x1E03 + #define GL_FRAGMENT_SHADER 0x8B30 + #define GL_FLOAT 0x1406 + #define GL_TEXTURE_MAX_LOD 0x813B + #define GL_DEPTH_COMPONENT 0x1902 + #define GL_ONE_MINUS_DST_ALPHA 0x0305 + #define GL_COLOR 0x1800 + #define GL_TEXTURE_2D_ARRAY 0x8C1A + #define GL_TRIANGLES 0x0004 + #define GL_UNSIGNED_BYTE 0x1401 + #define GL_TEXTURE_MAG_FILTER 0x2800 + #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + #define GL_NONE 0 + #define GL_SRC_COLOR 0x0300 + #define GL_BYTE 0x1400 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A + #define GL_LINE_STRIP 0x0003 + #define GL_TEXTURE_3D 0x806F + #define GL_CW 0x0900 + #define GL_LINEAR 0x2601 + #define GL_RENDERBUFFER 0x8D41 + #define GL_GEQUAL 0x0206 + #define GL_COLOR_BUFFER_BIT 0x00004000 + #define GL_RGBA32F 0x8814 + #define GL_BLEND 0x0BE2 + #define GL_ONE_MINUS_SRC_ALPHA 0x0303 + #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 + #define GL_TEXTURE_WRAP_T 0x2803 + #define GL_TEXTURE_WRAP_S 0x2802 + #define GL_TEXTURE_MIN_FILTER 0x2801 + #define GL_LINEAR_MIPMAP_NEAREST 0x2701 + #define GL_EXTENSIONS 0x1F03 + #define GL_NO_ERROR 0 + #define GL_REPLACE 0x1E01 + #define GL_KEEP 0x1E00 + #define GL_CCW 0x0901 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 + #define GL_RGB 0x1907 + #define GL_TRIANGLE_STRIP 0x0005 + #define GL_FALSE 0 + #define GL_ZERO 0 + #define GL_CULL_FACE 0x0B44 + #define GL_INVERT 0x150A + #define GL_INT 0x1404 + #define GL_UNSIGNED_INT 0x1405 + #define GL_UNSIGNED_SHORT 0x1403 + #define GL_NEAREST 0x2600 + #define GL_SCISSOR_TEST 0x0C11 + #define GL_LEQUAL 0x0203 + #define GL_STENCIL_TEST 0x0B90 + #define GL_DITHER 0x0BD0 + #define GL_DEPTH_COMPONENT16 0x81A5 + #define GL_EQUAL 0x0202 + #define GL_FRAMEBUFFER 0x8D40 + #define GL_RGB5 0x8050 + #define GL_LINES 0x0001 + #define GL_DEPTH_BUFFER_BIT 0x00000100 + #define GL_SRC_ALPHA 0x0302 + #define GL_INCR_WRAP 0x8507 + #define GL_LESS 0x0201 + #define GL_MULTISAMPLE 0x809D + #define GL_FRAMEBUFFER_BINDING 0x8CA6 + #define GL_BACK 0x0405 + #define GL_ALWAYS 0x0207 + #define GL_FUNC_ADD 0x8006 + #define GL_ONE_MINUS_DST_COLOR 0x0307 + #define GL_NOTEQUAL 0x0205 + #define GL_DST_COLOR 0x0306 + #define GL_COMPILE_STATUS 0x8B81 + #define GL_RED 0x1903 + #define GL_COLOR_ATTACHMENT3 0x8CE3 + #define GL_DST_ALPHA 0x0304 + #define GL_RGB5_A1 0x8057 + #define GL_GREATER 0x0204 + #define GL_POLYGON_OFFSET_FILL 0x8037 + #define GL_TRUE 1 + #define GL_NEVER 0x0200 + #define GL_POINTS 0x0000 + #define GL_ONE_MINUS_SRC_COLOR 0x0301 + #define GL_MIRRORED_REPEAT 0x8370 + #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D + #define GL_R11F_G11F_B10F 0x8C3A + #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B + #define GL_RGB9_E5 0x8C3D + #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E + #define GL_RGBA32UI 0x8D70 + #define GL_RGB32UI 0x8D71 + #define GL_RGBA16UI 0x8D76 + #define GL_RGB16UI 0x8D77 + #define GL_RGBA8UI 0x8D7C + #define GL_RGB8UI 0x8D7D + #define GL_RGBA32I 0x8D82 + #define GL_RGB32I 0x8D83 + #define GL_RGBA16I 0x8D88 + #define GL_RGB16I 0x8D89 + #define GL_RGBA8I 0x8D8E + #define GL_RGB8I 0x8D8F + #define GL_RED_INTEGER 0x8D94 + #define GL_RG 0x8227 + #define GL_RG_INTEGER 0x8228 + #define GL_R8 0x8229 + #define GL_R16 0x822A + #define GL_RG8 0x822B + #define GL_RG16 0x822C + #define GL_R16F 0x822D + #define GL_R32F 0x822E + #define GL_RG16F 0x822F + #define GL_RG32F 0x8230 + #define GL_R8I 0x8231 + #define GL_R8UI 0x8232 + #define GL_R16I 0x8233 + #define GL_R16UI 0x8234 + #define GL_R32I 0x8235 + #define GL_R32UI 0x8236 + #define GL_RG8I 0x8237 + #define GL_RG8UI 0x8238 + #define GL_RG16I 0x8239 + #define GL_RG16UI 0x823A + #define GL_RG32I 0x823B + #define GL_RG32UI 0x823C + #define GL_RGBA_INTEGER 0x8D99 + #define GL_R8_SNORM 0x8F94 + #define GL_RG8_SNORM 0x8F95 + #define GL_RGB8_SNORM 0x8F96 + #define GL_RGBA8_SNORM 0x8F97 + #define GL_R16_SNORM 0x8F98 + #define GL_RG16_SNORM 0x8F99 + #define GL_RGB16_SNORM 0x8F9A + #define GL_RGBA16_SNORM 0x8F9B + #define GL_RGBA16 0x805B + #define GL_MAX_TEXTURE_SIZE 0x0D33 + #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C + #define GL_MAX_3D_TEXTURE_SIZE 0x8073 + #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF + #define GL_MAX_VERTEX_ATTRIBS 0x8869 + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 + #define GL_CURRENT_PROGRAM 0x8B8D + #define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB + #define GL_UNPACK_ALIGNMENT 0x0CF5 + #define GL_FRAMEBUFFER_SRGB 0x8DB9 + #endif + #ifndef GL_UNSIGNED_INT_2_10_10_10_REV #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 #endif @@ -2595,77 +3650,35 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #endif #ifdef SOKOL_GLES2 - # ifdef GL_ANGLE_instanced_arrays - # define SOKOL_INSTANCING_ENABLED - # define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedANGLE(mode, first, count, instancecount) - # define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedANGLE(mode, count, type, indices, instancecount) - # define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorANGLE(index, divisor) - # elif defined(GL_EXT_draw_instanced) && defined(GL_EXT_instanced_arrays) - # define SOKOL_INSTANCING_ENABLED - # define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedEXT(mode, first, count, instancecount) - # define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedEXT(mode, count, type, indices, instancecount) - # define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorEXT(index, divisor) - # else - # define SOKOL_GLES2_INSTANCING_ERROR "Select GL_ANGLE_instanced_arrays or (GL_EXT_draw_instanced & GL_EXT_instanced_arrays) to enable instancing in GLES2" - # define glDrawArraysInstanced(mode, first, count, instancecount) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) - # define glDrawElementsInstanced(mode, count, type, indices, instancecount) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) - # define glVertexAttribDivisor(index, divisor) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) - # endif - #else - # define SOKOL_INSTANCING_ENABLED - #endif - #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } - -#elif defined(SOKOL_D3D11) - #ifndef D3D11_NO_HELPERS - #define D3D11_NO_HELPERS - #endif - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #ifndef NOMINMAX - #define NOMINMAX - #endif - #include - #include - #ifdef _MSC_VER - #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) - #pragma comment (lib, "WindowsApp") - #else - #pragma comment (lib, "kernel32") - #pragma comment (lib, "user32") - #pragma comment (lib, "dxgi") - #pragma comment (lib, "d3d11") - #pragma comment (lib, "dxguid") - #endif - #endif -#elif defined(SOKOL_METAL) - // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting - #if !defined(__cplusplus) - #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) - #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" - #endif - #endif - #include - #import - #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE - #define _SG_TARGET_MACOS (1) - #else - #define _SG_TARGET_IOS (1) - #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR - #define _SG_TARGET_IOS_SIMULATOR (1) + #ifdef GL_ANGLE_instanced_arrays + #define _SOKOL_GL_INSTANCING_ENABLED + #define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedANGLE(mode, first, count, instancecount) + #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedANGLE(mode, count, type, indices, instancecount) + #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorANGLE(index, divisor) + #elif defined(GL_EXT_draw_instanced) && defined(GL_EXT_instanced_arrays) + #define _SOKOL_GL_INSTANCING_ENABLED + #define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedEXT(mode, first, count, instancecount) + #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedEXT(mode, count, type, indices, instancecount) + #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorEXT(index, divisor) + #else + #define _SOKOL_GLES2_INSTANCING_ERROR "Select GL_ANGLE_instanced_arrays or (GL_EXT_draw_instanced & GL_EXT_instanced_arrays) to enable instancing in GLES2" + #define glDrawArraysInstanced(mode, first, count, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) + #define glDrawElementsInstanced(mode, count, type, indices, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) + #define glVertexAttribDivisor(index, divisor) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) #endif - #endif -#elif defined(SOKOL_WGPU) - #if defined(__EMSCRIPTEN__) - #include #else - #include + #define _SOKOL_GL_INSTANCING_ENABLED #endif + #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } #endif -/*=== COMMON BACKEND STUFF ===================================================*/ - +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs /* resource pool slots */ typedef struct { uint32_t id; @@ -2688,6 +3701,7 @@ enum { _SG_DEFAULT_SAMPLER_CACHE_CAPACITY = 64, _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, _SG_DEFAULT_STAGING_SIZE = 8 * 1024 * 1024, + _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024, }; /* fixed-size string */ @@ -2698,36 +3712,62 @@ typedef struct { /* helper macros */ #define _sg_def(val, def) (((val) == 0) ? (def) : (val)) #define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) -#define _sg_min(a,b) ((ab)?a:b) -#define _sg_clamp(v,v0,v1) ((vv1)?(v1):(v))) -#define _sg_fequal(val,cmp,delta) (((val-cmp)> -delta)&&((val-cmp)(b))?(a):(b)) +#define _sg_clamp(v,v0,v1) (((v)<(v0))?(v0):(((v)>(v1))?(v1):(v))) +#define _sg_fequal(val,cmp,delta) ((((val)-(cmp))> -(delta))&&(((val)-(cmp))<(delta))) + +_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size); +_SOKOL_PRIVATE void _sg_free(void* ptr); +_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size); typedef struct { - int size; - int append_pos; - bool append_overflow; - sg_buffer_type type; - sg_usage usage; + sg_filter min_filter; + sg_filter mag_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + sg_wrap wrap_w; + sg_border_color border_color; + uint32_t max_anisotropy; + int min_lod; /* orig min/max_lod is float, this is int(min/max_lod*1000.0) */ + int max_lod; + uintptr_t sampler_handle; +} _sg_sampler_cache_item_t; + +typedef struct { + int capacity; + int num_items; + _sg_sampler_cache_item_t* items; +} _sg_sampler_cache_t; + +typedef struct { + int size; + int append_pos; + bool append_overflow; uint32_t update_frame_index; uint32_t append_frame_index; int num_slots; int active_slot; + sg_buffer_type type; + sg_usage usage; } _sg_buffer_common_t; _SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { cmn->size = (int)desc->size; cmn->append_pos = 0; cmn->append_overflow = false; - cmn->type = desc->type; - cmn->usage = desc->usage; cmn->update_frame_index = 0; cmn->append_frame_index = 0; - cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; cmn->active_slot = 0; + cmn->type = desc->type; + cmn->usage = desc->usage; } typedef struct { + uint32_t upd_frame_index; + int num_slots; + int active_slot; sg_image_type type; bool render_target; int width; @@ -2744,12 +3784,14 @@ typedef struct { sg_wrap wrap_w; sg_border_color border_color; uint32_t max_anisotropy; - uint32_t upd_frame_index; - int num_slots; - int active_slot; + float min_lod; + float max_lod; } _sg_image_common_t; _SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) { + cmn->upd_frame_index = 0; + cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; cmn->type = desc->type; cmn->render_target = desc->render_target; cmn->width = desc->width; @@ -2766,14 +3808,13 @@ _SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_imag cmn->wrap_w = desc->wrap_w; cmn->border_color = desc->border_color; cmn->max_anisotropy = desc->max_anisotropy; - cmn->upd_frame_index = 0; - cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; - cmn->active_slot = 0; + cmn->min_lod = desc->min_lod; + cmn->max_lod = desc->max_lod; } typedef struct { size_t size; -} _sg_uniform_block_t; +} _sg_shader_uniform_block_t; typedef struct { sg_image_type image_type; @@ -2783,7 +3824,7 @@ typedef struct { typedef struct { int num_uniform_blocks; int num_images; - _sg_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; + _sg_shader_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; _sg_shader_image_t images[SG_MAX_SHADERSTAGE_IMAGES]; } _sg_shader_stage_t; @@ -2818,36 +3859,44 @@ _SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_sh } typedef struct { + bool vertex_layout_valid[SG_MAX_SHADERSTAGE_BUFFERS]; + bool use_instanced_draw; sg_shader shader_id; + sg_layout_desc layout; + sg_depth_state depth; + sg_stencil_state stencil; + int color_count; + sg_color_state colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_primitive_type primitive_type; sg_index_type index_type; - bool vertex_layout_valid[SG_MAX_SHADERSTAGE_BUFFERS]; - int color_attachment_count; - sg_pixel_format color_formats[SG_MAX_COLOR_ATTACHMENTS]; - sg_pixel_format depth_format; + sg_cull_mode cull_mode; + sg_face_winding face_winding; int sample_count; - float depth_bias; - float depth_bias_slope_scale; - float depth_bias_clamp; sg_color blend_color; + bool alpha_to_coverage_enabled; } _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); - cmn->shader_id = desc->shader; - cmn->index_type = desc->index_type; + SOKOL_ASSERT((desc->color_count >= 1) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS)); for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { cmn->vertex_layout_valid[i] = false; } - cmn->color_attachment_count = desc->color_count; - for (int i = 0; i < cmn->color_attachment_count; i++) { - cmn->color_formats[i] = desc->colors[i].pixel_format; - } - cmn->depth_format = desc->depth.pixel_format; + cmn->use_instanced_draw = false; + cmn->shader_id = desc->shader; + cmn->layout = desc->layout; + cmn->depth = desc->depth; + cmn->stencil = desc->stencil; + cmn->color_count = desc->color_count; + for (int i = 0; i < desc->color_count; i++) { + cmn->colors[i] = desc->colors[i]; + } + cmn->primitive_type = desc->primitive_type; + cmn->index_type = desc->index_type; + cmn->cull_mode = desc->cull_mode; + cmn->face_winding = desc->face_winding; cmn->sample_count = desc->sample_count; - cmn->depth_bias = desc->depth.bias; - cmn->depth_bias_slope_scale = desc->depth.bias_slope_scale; - cmn->depth_bias_clamp = desc->depth.bias_clamp; cmn->blend_color = desc->blend_color; + cmn->alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; } typedef struct { @@ -2884,107 +3933,6 @@ _SOKOL_PRIVATE void _sg_pass_common_init(_sg_pass_common_t* cmn, const sg_pass_d } } -/*=== GENERIC SAMPLER CACHE ==================================================*/ - -/* - this is used by the Metal and WGPU backends to reduce the - number of sampler state objects created through the backend API -*/ -typedef struct { - sg_filter min_filter; - sg_filter mag_filter; - sg_wrap wrap_u; - sg_wrap wrap_v; - sg_wrap wrap_w; - sg_border_color border_color; - uint32_t max_anisotropy; - int min_lod; /* orig min/max_lod is float, this is int(min/max_lod*1000.0) */ - int max_lod; - uintptr_t sampler_handle; -} _sg_sampler_cache_item_t; - -typedef struct { - int capacity; - int num_items; - _sg_sampler_cache_item_t* items; -} _sg_sampler_cache_t; - -_SOKOL_PRIVATE void _sg_smpcache_init(_sg_sampler_cache_t* cache, int capacity) { - SOKOL_ASSERT(cache && (capacity > 0)); - memset(cache, 0, sizeof(_sg_sampler_cache_t)); - cache->capacity = capacity; - const size_t size = (size_t)cache->capacity * sizeof(_sg_sampler_cache_item_t); - cache->items = (_sg_sampler_cache_item_t*) SOKOL_MALLOC(size); - SOKOL_ASSERT(cache->items); - memset(cache->items, 0, size); -} - -_SOKOL_PRIVATE void _sg_smpcache_discard(_sg_sampler_cache_t* cache) { - SOKOL_ASSERT(cache && cache->items); - SOKOL_FREE(cache->items); - cache->items = 0; - cache->num_items = 0; - cache->capacity = 0; -} - -_SOKOL_PRIVATE int _sg_smpcache_minlod_int(float min_lod) { - return (int) (min_lod * 1000.0f); -} - -_SOKOL_PRIVATE int _sg_smpcache_maxlod_int(float max_lod) { - return (int) (_sg_clamp(max_lod, 0.0f, 1000.0f) * 1000.0f); -} - -_SOKOL_PRIVATE int _sg_smpcache_find_item(const _sg_sampler_cache_t* cache, const sg_image_desc* img_desc) { - /* return matching sampler cache item index or -1 */ - SOKOL_ASSERT(cache && cache->items); - SOKOL_ASSERT(img_desc); - const int min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); - const int max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); - for (int i = 0; i < cache->num_items; i++) { - const _sg_sampler_cache_item_t* item = &cache->items[i]; - if ((img_desc->min_filter == item->min_filter) && - (img_desc->mag_filter == item->mag_filter) && - (img_desc->wrap_u == item->wrap_u) && - (img_desc->wrap_v == item->wrap_v) && - (img_desc->wrap_w == item->wrap_w) && - (img_desc->max_anisotropy == item->max_anisotropy) && - (img_desc->border_color == item->border_color) && - (min_lod == item->min_lod) && - (max_lod == item->max_lod)) - { - return i; - } - } - /* fallthrough: no matching cache item found */ - return -1; -} - -_SOKOL_PRIVATE void _sg_smpcache_add_item(_sg_sampler_cache_t* cache, const sg_image_desc* img_desc, uintptr_t sampler_handle) { - SOKOL_ASSERT(cache && cache->items); - SOKOL_ASSERT(img_desc); - SOKOL_ASSERT(cache->num_items < cache->capacity); - const int item_index = cache->num_items++; - _sg_sampler_cache_item_t* item = &cache->items[item_index]; - item->min_filter = img_desc->min_filter; - item->mag_filter = img_desc->mag_filter; - item->wrap_u = img_desc->wrap_u; - item->wrap_v = img_desc->wrap_v; - item->wrap_w = img_desc->wrap_w; - item->border_color = img_desc->border_color; - item->max_anisotropy = img_desc->max_anisotropy; - item->min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); - item->max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); - item->sampler_handle = sampler_handle; -} - -_SOKOL_PRIVATE uintptr_t _sg_smpcache_sampler(_sg_sampler_cache_t* cache, int item_index) { - SOKOL_ASSERT(cache && cache->items); - SOKOL_ASSERT(item_index < cache->num_items); - return cache->items[item_index].sampler_handle; -} - -/*=== DUMMY BACKEND DECLARATIONS =============================================*/ #if defined(SOKOL_DUMMY_BACKEND) typedef struct { _sg_slot_t slot; @@ -3031,7 +3979,6 @@ typedef struct { } _sg_dummy_context_t; typedef _sg_dummy_context_t _sg_context_t; -/*== GL BACKEND DECLARATIONS =================================================*/ #elif defined(_SOKOL_ANY_GL) typedef struct { _sg_slot_t slot; @@ -3059,7 +4006,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; @@ -3157,6 +4104,8 @@ typedef struct { GLuint texture; } _sg_gl_texture_bind_slot; +#define _SG_GL_IMAGE_CACHE_SIZE (SG_MAX_SHADERSTAGE_IMAGES * SG_NUM_SHADER_STAGES) + typedef struct { sg_depth_state depth; sg_stencil_state stencil; @@ -3174,7 +4123,7 @@ typedef struct { GLuint stored_vertex_buffer; GLuint stored_index_buffer; GLuint prog; - _sg_gl_texture_bind_slot textures[SG_MAX_SHADERSTAGE_IMAGES]; + _sg_gl_texture_bind_slot textures[_SG_GL_IMAGE_CACHE_SIZE]; _sg_gl_texture_bind_slot stored_texture; int cur_ib_offset; GLenum cur_primitive_type; @@ -3196,10 +4145,11 @@ typedef struct { _sg_gl_state_cache_t cache; bool ext_anisotropic; GLint max_anisotropy; - GLint max_combined_texture_image_units; + #if _SOKOL_USE_WIN32_GL_LOADER + HINSTANCE opengl32_dll; + #endif } _sg_gl_backend_t; -/*== D3D11 BACKEND DECLARATIONS ==============================================*/ #elif defined(SOKOL_D3D11) typedef struct { @@ -3303,6 +4253,7 @@ typedef struct { void* user_data; bool in_pass; bool use_indexed_draw; + bool use_instanced_draw; int cur_width; int cur_height; int num_rtvs; @@ -3316,19 +4267,10 @@ 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; -/*=== METAL BACKEND DECLARATIONS =============================================*/ #elif defined(SOKOL_METAL) #if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR) @@ -3464,11 +4406,11 @@ typedef struct { id device; id cmd_queue; id cmd_buffer; + id present_cmd_buffer; id cmd_encoder; id uniform_buffers[SG_NUM_INFLIGHT_FRAMES]; } _sg_mtl_backend_t; -/*=== WGPU BACKEND DECLARATIONS ==============================================*/ #elif defined(SOKOL_WGPU) #define _SG_WGPU_STAGING_ALIGN (256) @@ -3600,7 +4542,8 @@ typedef struct { } _sg_wgpu_backend_t; #endif -/*=== RESOURCE POOL DECLARATIONS =============================================*/ +// POOL STRUCTS + /* this *MUST* remain 0 */ #define _SG_INVALID_SLOT_INDEX (0) @@ -3627,131 +4570,11 @@ typedef struct { _sg_context_t* contexts; } _sg_pools_t; -/*=== VALIDATION LAYER DECLARATIONS ==========================================*/ -typedef enum { - /* special case 'validation was successful' */ - _SG_VALIDATE_SUCCESS, - - /* buffer creation */ - _SG_VALIDATE_BUFFERDESC_CANARY, - _SG_VALIDATE_BUFFERDESC_SIZE, - _SG_VALIDATE_BUFFERDESC_DATA, - _SG_VALIDATE_BUFFERDESC_DATA_SIZE, - _SG_VALIDATE_BUFFERDESC_NO_DATA, - - /* image creation */ - _SG_VALIDATE_IMAGEDESC_CANARY, - _SG_VALIDATE_IMAGEDESC_WIDTH, - _SG_VALIDATE_IMAGEDESC_HEIGHT, - _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT, - _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, - _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, - _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, - _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE, - _SG_VALIDATE_IMAGEDESC_RT_NO_DATA, - _SG_VALIDATE_IMAGEDESC_DATA, - _SG_VALIDATE_IMAGEDESC_NO_DATA, - - /* shader creation */ - _SG_VALIDATE_SHADERDESC_CANARY, - _SG_VALIDATE_SHADERDESC_SOURCE, - _SG_VALIDATE_SHADERDESC_BYTECODE, - _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, - _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, - _SG_VALIDATE_SHADERDESC_NO_CONT_UBS, - _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS, - _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, - _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS, - _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME, - _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, - _SG_VALIDATE_SHADERDESC_IMG_NAME, - _SG_VALIDATE_SHADERDESC_ATTR_NAMES, - _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS, - _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, - - /* pipeline creation */ - _SG_VALIDATE_PIPELINEDESC_CANARY, - _SG_VALIDATE_PIPELINEDESC_SHADER, - _SG_VALIDATE_PIPELINEDESC_NO_ATTRS, - _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, - _SG_VALIDATE_PIPELINEDESC_ATTR_NAME, - _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, - - /* pass creation */ - _SG_VALIDATE_PASSDESC_CANARY, - _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS, - _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS, - _SG_VALIDATE_PASSDESC_IMAGE, - _SG_VALIDATE_PASSDESC_MIPLEVEL, - _SG_VALIDATE_PASSDESC_FACE, - _SG_VALIDATE_PASSDESC_LAYER, - _SG_VALIDATE_PASSDESC_SLICE, - _SG_VALIDATE_PASSDESC_IMAGE_NO_RT, - _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT, - _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT, - _SG_VALIDATE_PASSDESC_IMAGE_SIZES, - _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS, - - /* sg_begin_pass validation */ - _SG_VALIDATE_BEGINPASS_PASS, - _SG_VALIDATE_BEGINPASS_IMAGE, - - /* sg_apply_pipeline validation */ - _SG_VALIDATE_APIP_PIPELINE_VALID_ID, - _SG_VALIDATE_APIP_PIPELINE_EXISTS, - _SG_VALIDATE_APIP_PIPELINE_VALID, - _SG_VALIDATE_APIP_SHADER_EXISTS, - _SG_VALIDATE_APIP_SHADER_VALID, - _SG_VALIDATE_APIP_ATT_COUNT, - _SG_VALIDATE_APIP_COLOR_FORMAT, - _SG_VALIDATE_APIP_DEPTH_FORMAT, - _SG_VALIDATE_APIP_SAMPLE_COUNT, - - /* sg_apply_bindings validation */ - _SG_VALIDATE_ABND_PIPELINE, - _SG_VALIDATE_ABND_PIPELINE_EXISTS, - _SG_VALIDATE_ABND_PIPELINE_VALID, - _SG_VALIDATE_ABND_VBS, - _SG_VALIDATE_ABND_VB_EXISTS, - _SG_VALIDATE_ABND_VB_TYPE, - _SG_VALIDATE_ABND_VB_OVERFLOW, - _SG_VALIDATE_ABND_NO_IB, - _SG_VALIDATE_ABND_IB, - _SG_VALIDATE_ABND_IB_EXISTS, - _SG_VALIDATE_ABND_IB_TYPE, - _SG_VALIDATE_ABND_IB_OVERFLOW, - _SG_VALIDATE_ABND_VS_IMGS, - _SG_VALIDATE_ABND_VS_IMG_EXISTS, - _SG_VALIDATE_ABND_VS_IMG_TYPES, - _SG_VALIDATE_ABND_FS_IMGS, - _SG_VALIDATE_ABND_FS_IMG_EXISTS, - _SG_VALIDATE_ABND_FS_IMG_TYPES, - - /* sg_apply_uniforms validation */ - _SG_VALIDATE_AUB_NO_PIPELINE, - _SG_VALIDATE_AUB_NO_UB_AT_SLOT, - _SG_VALIDATE_AUB_SIZE, - - /* sg_update_buffer validation */ - _SG_VALIDATE_UPDATEBUF_USAGE, - _SG_VALIDATE_UPDATEBUF_SIZE, - _SG_VALIDATE_UPDATEBUF_ONCE, - _SG_VALIDATE_UPDATEBUF_APPEND, - - /* sg_append_buffer validation */ - _SG_VALIDATE_APPENDBUF_USAGE, - _SG_VALIDATE_APPENDBUF_SIZE, - _SG_VALIDATE_APPENDBUF_UPDATE, - - /* sg_update_image validation */ - _SG_VALIDATE_UPDIMG_USAGE, - _SG_VALIDATE_UPDIMG_NOTENOUGHDATA, - _SG_VALIDATE_UPDIMG_SIZE, - _SG_VALIDATE_UPDIMG_COMPRESSED, - _SG_VALIDATE_UPDIMG_ONCE -} _sg_validate_error_t; - -/*=== GENERIC BACKEND STATE ==================================================*/ +typedef struct { + int num; // number of allocated commit listener items + int upper; // the current upper index (no valid items past this point) + sg_commit_listener* items; +} _sg_commit_listeners_t; typedef struct { bool valid; @@ -3764,7 +4587,7 @@ typedef struct { bool bindings_valid; bool next_draw_valid; #if defined(SOKOL_DEBUG) - _sg_validate_error_t validate_error; + sg_log_item validate_error; #endif _sg_pools_t pools; sg_backend backend; @@ -3783,76 +4606,351 @@ typedef struct { #if defined(SOKOL_TRACE_HOOKS) sg_trace_hooks hooks; #endif + _sg_commit_listeners_t commit_listeners; } _sg_state_t; static _sg_state_t _sg; -/*-- helper functions --------------------------------------------------------*/ +// ███████ █████ ███ ███ ██████ ██ ███████ ██████ ██████ █████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ███████ ██ ████ ██ ██████ ██ █████ ██████ ██ ███████ ██ ███████ █████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ██ ███████ ███████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ +// +// >>sampler cache +/* + this is used by the Metal and WGPU backends to reduce the + number of sampler state objects created through the backend API +*/ +_SOKOL_PRIVATE void _sg_smpcache_init(_sg_sampler_cache_t* cache, int capacity) { + SOKOL_ASSERT(cache && (capacity > 0)); + _sg_clear(cache, sizeof(_sg_sampler_cache_t)); + cache->capacity = capacity; + const size_t size = (size_t)cache->capacity * sizeof(_sg_sampler_cache_item_t); + cache->items = (_sg_sampler_cache_item_t*) _sg_malloc_clear(size); +} -_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { - return 0 == str->buf[0]; +_SOKOL_PRIVATE void _sg_smpcache_discard(_sg_sampler_cache_t* cache) { + SOKOL_ASSERT(cache && cache->items); + _sg_free(cache->items); + cache->items = 0; + cache->num_items = 0; + cache->capacity = 0; } -_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { - return &str->buf[0]; +_SOKOL_PRIVATE int _sg_smpcache_minlod_int(float min_lod) { + return (int) (min_lod * 1000.0f); } -_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { - SOKOL_ASSERT(dst); - if (src) { - #if defined(_MSC_VER) - strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); - #else - strncpy(dst->buf, src, _SG_STRING_SIZE); - #endif - dst->buf[_SG_STRING_SIZE-1] = 0; - } - else { - memset(dst->buf, 0, _SG_STRING_SIZE); - } +_SOKOL_PRIVATE int _sg_smpcache_maxlod_int(float max_lod) { + return (int) (_sg_clamp(max_lod, 0.0f, 1000.0f) * 1000.0f); } -/* return byte size of a vertex format */ -_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { - switch (fmt) { - case SG_VERTEXFORMAT_FLOAT: return 4; - case SG_VERTEXFORMAT_FLOAT2: return 8; - case SG_VERTEXFORMAT_FLOAT3: return 12; - case SG_VERTEXFORMAT_FLOAT4: return 16; - case SG_VERTEXFORMAT_BYTE4: return 4; - case SG_VERTEXFORMAT_BYTE4N: return 4; - case SG_VERTEXFORMAT_UBYTE4: return 4; - case SG_VERTEXFORMAT_UBYTE4N: return 4; - case SG_VERTEXFORMAT_SHORT2: return 4; - case SG_VERTEXFORMAT_SHORT2N: return 4; - case SG_VERTEXFORMAT_USHORT2N: return 4; - case SG_VERTEXFORMAT_SHORT4: return 8; - case SG_VERTEXFORMAT_SHORT4N: return 8; - case SG_VERTEXFORMAT_USHORT4N: return 8; - case SG_VERTEXFORMAT_UINT10_N2: return 4; - case SG_VERTEXFORMAT_INVALID: return 0; - default: - SOKOL_UNREACHABLE; - return -1; +_SOKOL_PRIVATE int _sg_smpcache_find_item(const _sg_sampler_cache_t* cache, const sg_image_desc* img_desc) { + /* return matching sampler cache item index or -1 */ + SOKOL_ASSERT(cache && cache->items); + SOKOL_ASSERT(img_desc); + const int min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); + const int max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); + for (int i = 0; i < cache->num_items; i++) { + const _sg_sampler_cache_item_t* item = &cache->items[i]; + if ((img_desc->min_filter == item->min_filter) && + (img_desc->mag_filter == item->mag_filter) && + (img_desc->wrap_u == item->wrap_u) && + (img_desc->wrap_v == item->wrap_v) && + (img_desc->wrap_w == item->wrap_w) && + (img_desc->max_anisotropy == item->max_anisotropy) && + (img_desc->border_color == item->border_color) && + (min_lod == item->min_lod) && + (max_lod == item->max_lod)) + { + return i; + } } + /* fallthrough: no matching cache item found */ + return -1; } -/* return the byte size of a shader uniform */ -_SOKOL_PRIVATE int _sg_uniform_size(sg_uniform_type type, int count) { - switch (type) { - case SG_UNIFORMTYPE_INVALID: return 0; - case SG_UNIFORMTYPE_FLOAT: return 4 * count; - case SG_UNIFORMTYPE_FLOAT2: return 8 * count; - case SG_UNIFORMTYPE_FLOAT3: return 12 * count; /* FIXME: std140??? */ - case SG_UNIFORMTYPE_FLOAT4: return 16 * count; - case SG_UNIFORMTYPE_MAT4: return 64 * count; - default: - SOKOL_UNREACHABLE; - return -1; - } +_SOKOL_PRIVATE void _sg_smpcache_add_item(_sg_sampler_cache_t* cache, const sg_image_desc* img_desc, uintptr_t sampler_handle) { + SOKOL_ASSERT(cache && cache->items); + SOKOL_ASSERT(img_desc); + SOKOL_ASSERT(cache->num_items < cache->capacity); + const int item_index = cache->num_items++; + _sg_sampler_cache_item_t* item = &cache->items[item_index]; + item->min_filter = img_desc->min_filter; + item->mag_filter = img_desc->mag_filter; + item->wrap_u = img_desc->wrap_u; + item->wrap_v = img_desc->wrap_v; + item->wrap_w = img_desc->wrap_w; + item->border_color = img_desc->border_color; + item->max_anisotropy = img_desc->max_anisotropy; + item->min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); + item->max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); + item->sampler_handle = sampler_handle; } -/* return true if pixel format is a compressed format */ -_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { +_SOKOL_PRIVATE uintptr_t _sg_smpcache_sampler(_sg_sampler_cache_t* cache, int item_index) { + SOKOL_ASSERT(cache && cache->items); + SOKOL_ASSERT(item_index < cache->num_items); + return cache->items[item_index].sampler_handle; +} + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SG_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sg_log_messages[] = { + _SG_LOG_ITEMS +}; +#undef _SG_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SG_PANIC(code) _sg_log(SG_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SG_ERROR(code) _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SG_WARN(code) _sg_log(SG_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SG_INFO(code) _sg_log(SG_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SG_LOGMSG(code,msg) _sg_log(SG_LOGITEM_ ##code, 3, msg, __LINE__) +#define _SG_VALIDATE(cond,code) if (!(cond)){ _sg.validate_error = SG_LOGITEM_ ##code; _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__); } + +static void _sg_log(sg_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sg.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sg_log_messages[log_item]; + } + #endif + _sg.desc.logger.func("sg", log_level, log_item, msg, line_nr, filename, _sg.desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory + +// a helper macro to clear a struct with potentially ARC'ed ObjC references +#if defined(SOKOL_METAL) + #if defined(__cplusplus) + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SG_CLEAR_ARC_STRUCT(type, item) { _sg_clear(&item, sizeof(item)); } +#endif + +_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sg_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sg.desc.allocator.alloc) { + ptr = _sg.desc.allocator.alloc(size, _sg.desc.allocator.user_data); + } + else { + ptr = malloc(size); + } + if (0 == ptr) { + _SG_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) { + void* ptr = _sg_malloc(size); + _sg_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sg_free(void* ptr) { + if (_sg.desc.allocator.free) { + _sg.desc.allocator.free(ptr, _sg.desc.allocator.user_data); + } + else { + free(ptr); + } +} + +_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { + return 0 == str->buf[0]; +} + +_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { + return &str->buf[0]; +} + +_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); + #else + strncpy(dst->buf, src, _SG_STRING_SIZE); + #endif + dst->buf[_SG_STRING_SIZE-1] = 0; + } + else { + _sg_clear(dst->buf, _SG_STRING_SIZE); + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers +_SOKOL_PRIVATE uint32_t _sg_align_u32(uint32_t val, uint32_t align) { + SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0)); + return (val + (align - 1)) & ~(align - 1); +} + +/* return byte size of a vertex format */ +_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return 4; + case SG_VERTEXFORMAT_FLOAT2: return 8; + case SG_VERTEXFORMAT_FLOAT3: return 12; + case SG_VERTEXFORMAT_FLOAT4: return 16; + case SG_VERTEXFORMAT_BYTE4: return 4; + case SG_VERTEXFORMAT_BYTE4N: return 4; + case SG_VERTEXFORMAT_UBYTE4: return 4; + case SG_VERTEXFORMAT_UBYTE4N: return 4; + case SG_VERTEXFORMAT_SHORT2: return 4; + case SG_VERTEXFORMAT_SHORT2N: return 4; + case SG_VERTEXFORMAT_USHORT2N: return 4; + case SG_VERTEXFORMAT_SHORT4: return 8; + case SG_VERTEXFORMAT_SHORT4N: return 8; + case SG_VERTEXFORMAT_USHORT4N: return 8; + case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 4; + case SG_VERTEXFORMAT_HALF4: return 8; + case SG_VERTEXFORMAT_INVALID: return 0; + default: + SOKOL_UNREACHABLE; + return -1; + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_alignment(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + return 1; + } + else { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 16; + default: + SOKOL_UNREACHABLE; + return 1; + } + } + else { + return 16; + } + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_size(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 64; + default: + SOKOL_UNREACHABLE; + return 0; + } + } + else { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } + else { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT: + case SG_UNIFORMTYPE_INT2: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } + } +} + +/* return true if pixel format is a compressed format */ +_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { switch (fmt) { case SG_PIXELFORMAT_BC1_RGBA: case SG_PIXELFORMAT_BC2_RGBA: @@ -3927,12 +5025,14 @@ _SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) { case SG_PIXELFORMAT_RG16SI: case SG_PIXELFORMAT_RG16F: case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: case SG_PIXELFORMAT_RGBA8SN: case SG_PIXELFORMAT_RGBA8UI: case SG_PIXELFORMAT_RGBA8SI: case SG_PIXELFORMAT_BGRA8: case SG_PIXELFORMAT_RGB10A2: case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: return 4; case SG_PIXELFORMAT_RG32UI: @@ -3961,7 +5061,21 @@ _SOKOL_PRIVATE int _sg_roundup(int val, int round_to) { } /* return row pitch for an image + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp + + For the special PVRTC pitch computation, see: + GL extension requirement (https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt) + + Quote: + + 6) How is the imageSize argument calculated for the CompressedTexImage2D + and CompressedTexSubImage2D functions. + + Resolution: For PVRTC 4BPP formats the imageSize is calculated as: + ( max(width, 8) * max(height, 8) * 4 + 7) / 8 + For PVRTC 2BPP formats the imageSize is calculated as: + ( max(width, 16) * max(height, 8) * 2 + 7) / 8 */ _SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) { int pitch; @@ -3989,23 +5103,11 @@ _SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) break; case SG_PIXELFORMAT_PVRTC_RGB_4BPP: case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: - { - const int block_size = 4*4; - const int bpp = 4; - int width_blocks = width / 4; - width_blocks = width_blocks < 2 ? 2 : width_blocks; - pitch = width_blocks * ((block_size * bpp) / 8); - } + pitch = (_sg_max(width, 8) * 4 + 7) / 8; break; case SG_PIXELFORMAT_PVRTC_RGB_2BPP: case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: - { - const int block_size = 8*4; - const int bpp = 2; - int width_blocks = width / 4; - width_blocks = width_blocks < 2 ? 2 : width_blocks; - pitch = width_blocks * ((block_size * bpp) / 8); - } + pitch = (_sg_max(width, 16) * 2 + 7) / 8; break; default: pitch = width * _sg_pixelformat_bytesize(fmt); @@ -4034,11 +5136,19 @@ _SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) { case SG_PIXELFORMAT_BC6H_RGBF: case SG_PIXELFORMAT_BC6H_RGBUF: case SG_PIXELFORMAT_BC7_RGBA: + num_rows = ((height + 3) / 4); + break; case SG_PIXELFORMAT_PVRTC_RGB_4BPP: case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: case SG_PIXELFORMAT_PVRTC_RGB_2BPP: case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: - num_rows = ((height + 3) / 4); + /* NOTE: this is most likely not correct because it ignores any + PVCRTC block size, but multiplied with _sg_row_pitch() + it gives the correct surface pitch. + + See: https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt + */ + num_rows = ((_sg_max(height, 8) + 7) / 8) * 8; break; default: num_rows = height; @@ -4143,7 +5253,13 @@ _SOKOL_PRIVATE void _sg_resolve_default_pass_action(const sg_pass_action* from, } } -/*== DUMMY BACKEND IMPL ======================================================*/ +// ██████ ██ ██ ███ ███ ███ ███ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ██ ██ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>dummy backend #if defined(SOKOL_DUMMY_BACKEND) _SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) { @@ -4175,7 +5291,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_context(_sg_context_t* ctx) { return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_dummy_discard_context(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); _SOKOL_UNUSED(ctx); } @@ -4191,7 +5307,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, cons return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_dummy_discard_buffer(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); _SOKOL_UNUSED(buf); } @@ -4202,7 +5318,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_dummy_discard_image(_sg_image_t* img) { SOKOL_ASSERT(img); _SOKOL_UNUSED(img); } @@ -4213,7 +5329,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, cons return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_dummy_discard_shader(_sg_shader_t* shd) { SOKOL_ASSERT(shd); _SOKOL_UNUSED(shd); } @@ -4233,7 +5349,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_dummy_discard_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); _SOKOL_UNUSED(pip); } @@ -4265,7 +5381,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pass(_sg_pass_t* pass, _sg_ima return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_dummy_destroy_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_dummy_discard_pass(_sg_pass_t* pass) { SOKOL_ASSERT(pass); _SOKOL_UNUSED(pass); } @@ -4377,9 +5493,163 @@ _SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data } } -/*== GL BACKEND ==============================================================*/ +// ██████ ██████ ███████ ███ ██ ██████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██████ █████ ██ ██ ██ ██ ███ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ███████ ██ ████ ██████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>opengl backend #elif defined(_SOKOL_ANY_GL) +// optional GL loader for win32 +#if defined(_SOKOL_USE_WIN32_GL_LOADER) + +// X Macro list of GL function names and signatures +#define _SG_GL_FUNCS \ + _SG_XMACRO(glBindVertexArray, void, (GLuint array)) \ + _SG_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ + _SG_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ + _SG_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ + _SG_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ + _SG_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ + _SG_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ + _SG_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ + _SG_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ + _SG_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ + _SG_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ + _SG_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform1iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform2iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform3iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform4iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ + _SG_XMACRO(glUseProgram, void, (GLuint program)) \ + _SG_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ + _SG_XMACRO(glLinkProgram, void, (GLuint program)) \ + _SG_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ + _SG_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glDeleteShader, void, (GLuint shader)) \ + _SG_XMACRO(glDeleteProgram, void, (GLuint program)) \ + _SG_XMACRO(glCompileShader, void, (GLuint shader)) \ + _SG_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ + _SG_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ + _SG_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ + _SG_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ + _SG_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ + _SG_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ + _SG_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ + _SG_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glActiveTexture, void, (GLenum texture)) \ + _SG_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ + _SG_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ + _SG_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ + _SG_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ + _SG_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ + _SG_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ + _SG_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ + _SG_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ + _SG_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glCreateShader, GLuint, (GLenum type)) \ + _SG_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glClearDepth, void, (GLdouble depth)) \ + _SG_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ + _SG_XMACRO(glCreateProgram, GLuint, (void)) \ + _SG_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ + _SG_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ + _SG_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ + _SG_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ + _SG_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ + _SG_XMACRO(glDisable, void, (GLenum cap)) \ + _SG_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ + _SG_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ + _SG_XMACRO(glDepthMask, void, (GLboolean flag)) \ + _SG_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ + _SG_XMACRO(glClearStencil, void, (GLint s)) \ + _SG_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ + _SG_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ + _SG_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ + _SG_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ + _SG_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ + _SG_XMACRO(glEnable, void, (GLenum cap)) \ + _SG_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ + _SG_XMACRO(glStencilMask, void, (GLuint mask)) \ + _SG_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ + _SG_XMACRO(glGetError, GLenum, (void)) \ + _SG_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SG_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SG_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ + _SG_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, const GLfloat* params)) \ + _SG_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glDepthFunc, void, (GLenum func)) \ + _SG_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ + _SG_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ + _SG_XMACRO(glReadBuffer, void, (GLenum src)) \ + _SG_XMACRO(glReadPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data)) \ + _SG_XMACRO(glClear, void, (GLbitfield mask)) \ + _SG_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ + _SG_XMACRO(glFrontFace, void, (GLenum mode)) \ + _SG_XMACRO(glCullFace, void, (GLenum mode)) \ + _SG_XMACRO(glPixelStorei, void, (GLenum pname, GLint param)) + +// generate GL function pointer typedefs +#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// generate GL function pointers +#define _SG_XMACRO(name, ret, args) static PFN_ ## name name; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// helper function to lookup GL functions in GL DLL +typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR); +_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) { + void* proc_addr = (void*) wgl_getprocaddress(name); + if (0 == proc_addr) { + proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name); + } + SOKOL_ASSERT(proc_addr); + return proc_addr; +} + +// populate GL function pointers +_SOKOL_PRIVATE void _sg_gl_load_opengl(void) { + SOKOL_ASSERT(0 == _sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll"); + SOKOL_ASSERT(_sg.gl.opengl32_dll); + _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress"); + SOKOL_ASSERT(wgl_getprocaddress); + #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress); + _SG_GL_FUNCS + #undef _SG_XMACRO +} + +_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) { + SOKOL_ASSERT(_sg.gl.opengl32_dll); + FreeLibrary(_sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = 0; +} +#endif // _SOKOL_USE_WIN32_GL_LOADER + /*-- type translation --------------------------------------------------------*/ _SOKOL_PRIVATE GLenum _sg_gl_buffer_target(sg_buffer_type t) { switch (t) { @@ -4435,6 +5705,8 @@ _SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) { case SG_VERTEXFORMAT_SHORT4N: return 4; case SG_VERTEXFORMAT_USHORT4N: return 4; case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 2; + case SG_VERTEXFORMAT_HALF4: return 4; default: SOKOL_UNREACHABLE; return 0; } } @@ -4462,6 +5734,9 @@ _SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) { return GL_UNSIGNED_SHORT; case SG_VERTEXFORMAT_UINT10_N2: return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_VERTEXFORMAT_HALF2: + case SG_VERTEXFORMAT_HALF4: + return GL_HALF_FLOAT; default: SOKOL_UNREACHABLE; return 0; } @@ -4593,6 +5868,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { case SG_PIXELFORMAT_RG8: case SG_PIXELFORMAT_RG8UI: case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: case SG_PIXELFORMAT_RGBA8UI: case SG_PIXELFORMAT_BGRA8: return GL_UNSIGNED_BYTE; @@ -4638,6 +5914,8 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { return GL_UNSIGNED_INT_2_10_10_10_REV; case SG_PIXELFORMAT_RG11B10F: return GL_UNSIGNED_INT_10F_11F_11F_REV; + case SG_PIXELFORMAT_RGB9E5: + return GL_UNSIGNED_INT_5_9_9_9_REV; #endif case SG_PIXELFORMAT_DEPTH: return GL_UNSIGNED_SHORT; @@ -4690,6 +5968,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { return GL_RG_INTEGER; #endif case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: case SG_PIXELFORMAT_RGBA8SN: case SG_PIXELFORMAT_RGBA16: case SG_PIXELFORMAT_RGBA16SN: @@ -4707,6 +5986,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { return GL_RGBA_INTEGER; #endif case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: return GL_RGB; case SG_PIXELFORMAT_DEPTH: return GL_DEPTH_COMPONENT; @@ -4790,11 +6070,13 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) { case SG_PIXELFORMAT_RG16SI: return GL_RG16I; case SG_PIXELFORMAT_RG16F: return GL_RG16F; case SG_PIXELFORMAT_RGBA8: return GL_RGBA8; + case SG_PIXELFORMAT_SRGB8A8: return GL_SRGB8_ALPHA8; case SG_PIXELFORMAT_RGBA8SN: return GL_RGBA8_SNORM; case SG_PIXELFORMAT_RGBA8UI: return GL_RGBA8UI; case SG_PIXELFORMAT_RGBA8SI: return GL_RGBA8I; case SG_PIXELFORMAT_RGB10A2: return GL_RGB10_A2; case SG_PIXELFORMAT_RG11B10F: return GL_R11F_G11F_B10F; + case SG_PIXELFORMAT_RGB9E5: return GL_RGB9_E5; case SG_PIXELFORMAT_RG32UI: return GL_RG32UI; case SG_PIXELFORMAT_RG32SI: return GL_RG32I; case SG_PIXELFORMAT_RG32F: return GL_RG32F; @@ -4895,6 +6177,7 @@ _SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); #if !defined(SOKOL_GLES2) if (!_sg.gl.gles2) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); @@ -4907,6 +6190,7 @@ _SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { if (!_sg.gl.gles2) { _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); #if !defined(SOKOL_GLES3) @@ -5095,6 +6379,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); @@ -5115,7 +6402,7 @@ _SOKOL_PRIVATE void _sg_gl_init_limits(void) { } glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int); _SG_GL_CHECK_ERROR(); - _sg.gl.max_combined_texture_image_units = gl_int; + _sg.limits.gl_max_combined_texture_image_units = gl_int; } #if defined(SOKOL_GLCORE33) @@ -5335,7 +6622,7 @@ _SOKOL_PRIVATE void _sg_gl_init_caps_gles2(void) { } _sg.features.origin_top_left = false; - #if defined(SOKOL_INSTANCING_ENABLED) + #if defined(_SOKOL_GL_INSTANCING_ENABLED) _sg.features.instancing = has_instancing; #endif _sg.features.multiple_render_targets = false; @@ -5437,7 +6724,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) { } } -/* called when from _sg_gl_destroy_buffer() */ +/* called when _sg_gl_deinit_buffer() */ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) { if (buf == _sg.gl.cache.vertex_buffer) { _sg.gl.cache.vertex_buffer = 0; @@ -5468,7 +6755,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) { } _SOKOL_PRIVATE void _sg_gl_cache_clear_texture_bindings(bool force) { - for (int i = 0; (i < SG_MAX_SHADERSTAGE_IMAGES) && (i < _sg.gl.max_combined_texture_image_units); i++) { + for (int i = 0; (i < _SG_GL_IMAGE_CACHE_SIZE) && (i < _sg.limits.gl_max_combined_texture_image_units); i++) { if (force || (_sg.gl.cache.textures[i].texture != 0)) { GLenum gl_texture_slot = (GLenum) (GL_TEXTURE0 + i); glActiveTexture(gl_texture_slot); @@ -5492,8 +6779,8 @@ _SOKOL_PRIVATE void _sg_gl_cache_bind_texture(int slot_index, GLenum target, GLu target=0 will unbind the previous binding, texture=0 will clear the new binding */ - SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); - if (slot_index >= _sg.gl.max_combined_texture_image_units) { + SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); + if (slot_index >= _sg.limits.gl_max_combined_texture_image_units) { return; } _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[slot_index]; @@ -5513,12 +6800,12 @@ _SOKOL_PRIVATE void _sg_gl_cache_bind_texture(int slot_index, GLenum target, GLu } _SOKOL_PRIVATE void _sg_gl_cache_store_texture_binding(int slot_index) { - SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); + SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); _sg.gl.cache.stored_texture = _sg.gl.cache.textures[slot_index]; } _SOKOL_PRIVATE void _sg_gl_cache_restore_texture_binding(int slot_index) { - SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); + SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.stored_texture; if (slot->texture != 0) { /* we only care restoring valid ids */ @@ -5531,7 +6818,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_restore_texture_binding(int slot_index) { /* called from _sg_gl_destroy_texture() */ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture(GLuint tex) { - for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + for (int i = 0; i < _SG_GL_IMAGE_CACHE_SIZE; i++) { _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[i]; if (tex == slot->texture) { _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i)); @@ -5546,7 +6833,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture(GLuint tex) { } } -/* called from _sg_gl_destroy_shader() */ +/* called from _sg_gl_discard_shader() */ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { if (prog == _sg.gl.cache.prog) { _sg.gl.cache.prog = 0; @@ -5554,6 +6841,14 @@ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { } } +/* called from _sg_gl_discard_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(); @@ -5563,7 +6858,7 @@ _SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { _SG_GL_CHECK_ERROR(); } #endif - memset(&_sg.gl.cache, 0, sizeof(_sg.gl.cache)); + _sg_clear(&_sg.gl.cache, sizeof(_sg.gl.cache)); _sg_gl_cache_clear_buffer_bindings(true); _SG_GL_CHECK_ERROR(); _sg_gl_cache_clear_texture_bindings(true); @@ -5645,6 +6940,10 @@ _SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { _sg.gl.gles2 = false; #endif + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_load_opengl(); + #endif + /* clear initial GL error state */ #if defined(SOKOL_DEBUG) while (glGetError() != GL_NO_ERROR); @@ -5666,6 +6965,9 @@ _SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { _SOKOL_PRIVATE void _sg_gl_discard_backend(void) { SOKOL_ASSERT(_sg.gl.valid); _sg.gl.valid = false; + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_unload_opengl(); + #endif } _SOKOL_PRIVATE void _sg_gl_activate_context(_sg_context_t* ctx) { @@ -5690,10 +6992,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_context(_sg_context_t* ctx) { _SG_GL_CHECK_ERROR(); } #endif + // incoming texture data is generally expected to be packed tightly + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_gl_discard_context(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); #if !defined(SOKOL_GLES2) if (!_sg.gl.gles2) { @@ -5722,6 +7026,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); @@ -5737,7 +7042,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const s return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_gl_discard_buffer(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); _SG_GL_CHECK_ERROR(); for (int slot = 0; slot < buf->cmn.num_slots; slot++) { @@ -5765,16 +7070,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ /* check if texture format is support */ if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) { - SOKOL_LOG("texture format not supported by GL context\n"); + _SG_ERROR(GL_TEXTURE_FORMAT_NOT_SUPPORTED); return SG_RESOURCESTATE_FAILED; } /* check for optional texture types */ if ((img->cmn.type == SG_IMAGETYPE_3D) && !_sg.features.imagetype_3d) { - SOKOL_LOG("3D textures not supported by GL context\n"); + _SG_ERROR(GL_3D_TEXTURES_NOT_SUPPORTED); return SG_RESOURCESTATE_FAILED; } if ((img->cmn.type == SG_IMAGETYPE_ARRAY) && !_sg.features.imagetype_array) { - SOKOL_LOG("array textures not supported by GL context\n"); + _SG_ERROR(GL_ARRAY_TEXTURES_NOT_SUPPORTED); return SG_RESOURCESTATE_FAILED; } @@ -5892,7 +7197,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ gl_img_target = _sg_gl_cubeface_target(face_index); } const GLvoid* data_ptr = desc->data.subimage[face_index][mip_index].ptr; - const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; int mip_width = img->cmn.width >> mip_index; if (mip_width == 0) { mip_width = 1; @@ -5903,6 +7207,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ } if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { if (is_compressed) { + const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; glCompressedTexImage2D(gl_img_target, mip_index, gl_internal_format, mip_width, mip_height, 0, data_size, data_ptr); } @@ -5922,6 +7227,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ mip_depth = 1; } if (is_compressed) { + const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; glCompressedTexImage3D(gl_img_target, mip_index, gl_internal_format, mip_width, mip_height, mip_depth, 0, data_size, data_ptr); } @@ -5942,7 +7248,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_gl_discard_image(_sg_image_t* img) { SOKOL_ASSERT(img); _SG_GL_CHECK_ERROR(); for (int slot = 0; slot < img->cmn.num_slots; slot++) { @@ -5975,10 +7281,11 @@ _SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* s GLint log_len = 0; glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len); if (log_len > 0) { - GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf); - SOKOL_LOG(log_buf); - SOKOL_FREE(log_buf); + _SG_ERROR(GL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(GL_SHADER_COMPILATION_FAILED, log_buf); + _sg_free(log_buf); } glDeleteShader(gl_shd); gl_shd = 0; @@ -6018,10 +7325,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s GLint log_len = 0; glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len); if (log_len > 0) { - GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf); - SOKOL_LOG(log_buf); - SOKOL_FREE(log_buf); + _SG_ERROR(GL_SHADER_LINKING_FAILED); + _SG_LOGMSG(GL_SHADER_LINKING_FAILED, log_buf); + _sg_free(log_buf); } glDeleteProgram(gl_prog); return SG_RESOURCESTATE_FAILED; @@ -6038,17 +7346,20 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s SOKOL_ASSERT(ub_desc->size > 0); _sg_gl_uniform_block_t* ub = &gl_stage->uniform_blocks[ub_index]; SOKOL_ASSERT(ub->num_uniforms == 0); - int cur_uniform_offset = 0; + uint32_t cur_uniform_offset = 0; for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; if (u_desc->type == SG_UNIFORMTYPE_INVALID) { break; } + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, u_desc->array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, u_desc->array_count, ub_desc->layout); + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, u_align); _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); + cur_uniform_offset += u_size; if (u_desc->name) { u->gl_loc = glGetUniformLocation(gl_prog, u_desc->name); } @@ -6057,7 +7368,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s } ub->num_uniforms++; } + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, 16); + } SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset); + _SOKOL_UNUSED(cur_uniform_offset); } } @@ -6093,7 +7408,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_gl_discard_shader(_sg_shader_t* shd) { SOKOL_ASSERT(shd); _SG_GL_CHECK_ERROR(); if (shd->gl.prog) { @@ -6150,6 +7465,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg } else { gl_attr->divisor = (int8_t) step_rate; + pip->cmn.use_instanced_draw = true; } SOKOL_ASSERT(l_desc->stride > 0); gl_attr->stride = (uint8_t) l_desc->stride; @@ -6160,17 +7476,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; } else { - SOKOL_LOG("Vertex attribute not found in shader: "); - SOKOL_LOG(_sg_strptr(&shd->gl.attrs[attr_index].name)); + _SG_ERROR(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, _sg_strptr(&shd->gl.attrs[attr_index].name)); } } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_gl_discard_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); - _SOKOL_UNUSED(pip); - /* empty */ + _sg_gl_cache_invalidate_pipeline(pip); } /* @@ -6267,7 +7582,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ /* check if framebuffer is complete */ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - SOKOL_LOG("Framebuffer completeness check failed!\n"); + _SG_ERROR(GL_FRAMEBUFFER_INCOMPLETE); return SG_RESOURCESTATE_FAILED; } @@ -6314,7 +7629,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ } /* check if framebuffer is complete */ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - SOKOL_LOG("Framebuffer completeness check failed (msaa resolve buffer)!\n"); + _SG_ERROR(GL_MSAA_FRAMEBUFFER_INCOMPLETE); return SG_RESOURCESTATE_FAILED; } /* setup color attachments for the framebuffer */ @@ -6334,8 +7649,9 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_gl_destroy_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_gl_discard_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); @@ -6383,15 +7699,29 @@ _SOKOL_PRIVATE void _sg_gl_begin_pass(_sg_pass_t* pass, const sg_pass_action* ac /* number of color attachments */ const int num_color_atts = pass ? pass->cmn.num_color_atts : 1; - /* bind the render pass framebuffer */ + // bind the render pass framebuffer + // + // FIXME: Disabling SRGB conversion for the default framebuffer is + // a crude hack to make behaviour for sRGB render target textures + // identical with the Metal and D3D11 swapchains created by sokol-app. + // + // This will need a cleaner solution (e.g. allowing to configure + // sokol_app.h with an sRGB or RGB framebuffer. if (pass) { - /* offscreen pass */ + // offscreen pass SOKOL_ASSERT(pass->gl.fb); + #if defined(SOKOL_GLCORE33) + glEnable(GL_FRAMEBUFFER_SRGB); + #endif glBindFramebuffer(GL_FRAMEBUFFER, pass->gl.fb); + } else { - /* default pass */ + // default pass SOKOL_ASSERT(_sg.gl.cur_context); + #if defined(SOKOL_GLCORE33) + glDisable(GL_FRAMEBUFFER_SRGB); + #endif glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.cur_context->default_framebuffer); } glViewport(0, 0, w, h); @@ -6696,7 +8026,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { } /* standalone state */ - for (GLuint i = 0; i < (GLuint)pip->cmn.color_attachment_count; i++) { + for (GLuint i = 0; i < (GLuint)pip->cmn.color_count; i++) { if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) { const sg_color_mask cm = pip->gl.color_write_mask[i]; _sg.gl.cache.color_write_mask[i] = cm; @@ -6836,7 +8166,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_bindings( glVertexAttribPointer(attr_index, attr->size, attr->type, attr->normalized, attr->stride, (const GLvoid*)(GLintptr)vb_offset); - #ifdef SOKOL_INSTANCING_ENABLED + #if defined(_SOKOL_GL_INSTANCING_ENABLED) if (_sg.features.instancing) { glVertexAttribDivisor(attr_index, (GLuint)attr->divisor); } @@ -6878,24 +8208,37 @@ _SOKOL_PRIVATE void _sg_gl_apply_uniforms(sg_shader_stage stage_index, int ub_in if (u->gl_loc == -1) { continue; } - GLfloat* ptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); + GLfloat* fptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); + GLint* iptr = (GLint*) (((uint8_t*)data->ptr) + u->offset); switch (u->type) { case SG_UNIFORMTYPE_INVALID: break; case SG_UNIFORMTYPE_FLOAT: - glUniform1fv(u->gl_loc, u->count, ptr); + glUniform1fv(u->gl_loc, u->count, fptr); break; case SG_UNIFORMTYPE_FLOAT2: - glUniform2fv(u->gl_loc, u->count, ptr); + glUniform2fv(u->gl_loc, u->count, fptr); break; case SG_UNIFORMTYPE_FLOAT3: - glUniform3fv(u->gl_loc, u->count, ptr); + glUniform3fv(u->gl_loc, u->count, fptr); break; case SG_UNIFORMTYPE_FLOAT4: - glUniform4fv(u->gl_loc, u->count, ptr); + glUniform4fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_INT: + glUniform1iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT2: + glUniform2iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT3: + glUniform3iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT4: + glUniform4iv(u->gl_loc, u->count, iptr); break; case SG_UNIFORMTYPE_MAT4: - glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, ptr); + glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, fptr); break; default: SOKOL_UNREACHABLE; @@ -6905,6 +8248,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_uniforms(sg_shader_stage stage_index, int ub_in } _SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); const GLenum i_type = _sg.gl.cache.cur_index_type; const GLenum p_type = _sg.gl.cache.cur_primitive_type; if (0 != i_type) { @@ -6912,25 +8256,25 @@ _SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_inst const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4; const int ib_offset = _sg.gl.cache.cur_ib_offset; const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset); - if (num_instances == 1) { - glDrawElements(p_type, num_elements, i_type, indices); - } - else { + if (_sg.gl.cache.cur_pipeline->cmn.use_instanced_draw) { if (_sg.features.instancing) { glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); } } + else { + glDrawElements(p_type, num_elements, i_type, indices); + } } else { /* non-indexed rendering */ - if (num_instances == 1) { - glDrawArrays(p_type, base_element, num_elements); - } - else { + if (_sg.gl.cache.cur_pipeline->cmn.use_instanced_draw) { if (_sg.features.instancing) { glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); } } + else { + glDrawArrays(p_type, base_element, num_elements); + } } } @@ -7035,7 +8379,13 @@ _SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* d _sg_gl_cache_restore_texture_binding(0); } -/*== D3D11 BACKEND IMPLEMENTATION ============================================*/ +// ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ █████ ██ ██ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>d3d11 backend #elif defined(SOKOL_D3D11) #if defined(__cplusplus) @@ -7411,6 +8761,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) { @@ -7462,12 +8820,14 @@ _SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_pixel_format(sg_pixel_format fmt) { case SG_PIXELFORMAT_RG16SI: return DXGI_FORMAT_R16G16_SINT; case SG_PIXELFORMAT_RG16F: return DXGI_FORMAT_R16G16_FLOAT; case SG_PIXELFORMAT_RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_PIXELFORMAT_SRGB8A8: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; case SG_PIXELFORMAT_RGBA8SN: return DXGI_FORMAT_R8G8B8A8_SNORM; case SG_PIXELFORMAT_RGBA8UI: return DXGI_FORMAT_R8G8B8A8_UINT; case SG_PIXELFORMAT_RGBA8SI: return DXGI_FORMAT_R8G8B8A8_SINT; case SG_PIXELFORMAT_BGRA8: return DXGI_FORMAT_B8G8R8A8_UNORM; case SG_PIXELFORMAT_RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; case SG_PIXELFORMAT_RG11B10F: return DXGI_FORMAT_R11G11B10_FLOAT; + case SG_PIXELFORMAT_RGB9E5: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; case SG_PIXELFORMAT_RG32UI: return DXGI_FORMAT_R32G32_UINT; case SG_PIXELFORMAT_RG32SI: return DXGI_FORMAT_R32G32_SINT; case SG_PIXELFORMAT_RG32F: return DXGI_FORMAT_R32G32_FLOAT; @@ -7583,6 +8943,8 @@ _SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) { case SG_VERTEXFORMAT_SHORT4N: return DXGI_FORMAT_R16G16B16A16_SNORM; case SG_VERTEXFORMAT_USHORT4N: return DXGI_FORMAT_R16G16B16A16_UNORM; case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_VERTEXFORMAT_HALF2: return DXGI_FORMAT_R16G16_FLOAT; + case SG_VERTEXFORMAT_HALF4: return DXGI_FORMAT_R16G16B16A16_FLOAT; default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; } } @@ -7749,21 +9111,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) { @@ -7782,7 +9130,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_context(_sg_context_t* ctx) { return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_d3d11_discard_context(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); _SOKOL_UNUSED(ctx); /* empty */ @@ -7799,27 +9147,29 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, cons } else { D3D11_BUFFER_DESC d3d11_desc; - memset(&d3d11_desc, 0, sizeof(d3d11_desc)); + _sg_clear(&d3d11_desc, sizeof(d3d11_desc)); d3d11_desc.ByteWidth = (UINT)buf->cmn.size; d3d11_desc.Usage = _sg_d3d11_usage(buf->cmn.usage); d3d11_desc.BindFlags = buf->cmn.type == SG_BUFFERTYPE_VERTEXBUFFER ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER; d3d11_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(buf->cmn.usage); D3D11_SUBRESOURCE_DATA* init_data_ptr = 0; D3D11_SUBRESOURCE_DATA init_data; - memset(&init_data, 0, sizeof(init_data)); + _sg_clear(&init_data, sizeof(init_data)); if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { SOKOL_ASSERT(desc->data.ptr); init_data.pSysMem = desc->data.ptr; init_data_ptr = &init_data; } HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_desc, init_data_ptr, &buf->d3d11.buf); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr) && buf->d3d11.buf); + if (!(SUCCEEDED(hr) && buf->d3d11.buf)) { + _SG_ERROR(D3D11_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_d3d11_discard_buffer(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); if (buf->d3d11.buf) { _sg_d3d11_Release(buf->d3d11.buf); @@ -7860,7 +9210,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const SOKOL_ASSERT(!img->d3d11.tex2d && !img->d3d11.tex3d && !img->d3d11.texds && !img->d3d11.texmsaa); SOKOL_ASSERT(!img->d3d11.srv && !img->d3d11.smp); HRESULT hr; - _SOKOL_UNUSED(hr); _sg_image_common_init(&img->cmn, desc); const bool injected = (0 != desc->d3d11_texture) || (0 != desc->d3d11_shader_resource_view); @@ -7872,11 +9221,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const /* create only a depth-texture */ SOKOL_ASSERT(!injected); if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { - SOKOL_LOG("trying to create a D3D11 depth-texture with unsupported pixel format\n"); + _SG_ERROR(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); return SG_RESOURCESTATE_FAILED; } D3D11_TEXTURE2D_DESC d3d11_desc; - memset(&d3d11_desc, 0, sizeof(d3d11_desc)); + _sg_clear(&d3d11_desc, sizeof(d3d11_desc)); d3d11_desc.Width = (UINT)img->cmn.width; d3d11_desc.Height = (UINT)img->cmn.height; d3d11_desc.MipLevels = 1; @@ -7887,7 +9236,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; d3d11_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_desc, NULL, &img->d3d11.texds); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texds); + if (!(SUCCEEDED(hr) && img->d3d11.texds)) { + _SG_ERROR(D3D11_CREATE_DEPTH_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } } else { /* create (or inject) color texture and shader-resource-view */ @@ -7925,7 +9277,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const /* if not injected, create texture */ if (0 == img->d3d11.tex2d) { D3D11_TEXTURE2D_DESC d3d11_tex_desc; - memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); d3d11_tex_desc.Width = (UINT)img->cmn.width; d3d11_tex_desc.Height = (UINT)img->cmn.height; d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; @@ -7948,8 +9300,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); } if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { - /* trying to create a texture format that's not supported by D3D */ - SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n"); + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); return SG_RESOURCESTATE_FAILED; } d3d11_tex_desc.SampleDesc.Count = 1; @@ -7957,13 +9308,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0; hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex2d); + if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) { + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } } /* ...and similar, if not injected, create shader-resource-view */ if (0 == img->d3d11.srv) { D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; - memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); d3d11_srv_desc.Format = img->d3d11.format; switch (img->cmn.type) { case SG_IMAGETYPE_2D: @@ -7983,7 +9337,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const SOKOL_UNREACHABLE; break; } hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } } } else { @@ -8006,7 +9363,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const if (0 == img->d3d11.tex3d) { D3D11_TEXTURE3D_DESC d3d11_tex_desc; - memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); d3d11_tex_desc.Width = (UINT)img->cmn.width; d3d11_tex_desc.Height = (UINT)img->cmn.height; d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices; @@ -8025,29 +9382,34 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); } if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { - /* trying to create a texture format that's not supported by D3D */ - SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n"); + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); return SG_RESOURCESTATE_FAILED; } hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex3d); + if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) { + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } } if (0 == img->d3d11.srv) { D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; - memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); d3d11_srv_desc.Format = img->d3d11.format; d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; d3d11_srv_desc.Texture3D.MipLevels = (UINT)img->cmn.num_mipmaps; hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex3d, &d3d11_srv_desc, &img->d3d11.srv); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_3D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } } } /* also need to create a separate MSAA render target texture? */ if (msaa) { D3D11_TEXTURE2D_DESC d3d11_tex_desc; - memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); d3d11_tex_desc.Width = (UINT)img->cmn.width; d3d11_tex_desc.Height = (UINT)img->cmn.height; d3d11_tex_desc.MipLevels = 1; @@ -8059,12 +9421,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; d3d11_tex_desc.SampleDesc.Quality = (UINT)D3D11_STANDARD_MULTISAMPLE_PATTERN; hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, NULL, &img->d3d11.texmsaa); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texmsaa); + if (!(SUCCEEDED(hr) && img->d3d11.texmsaa)) { + _SG_ERROR(D3D11_CREATE_MSAA_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } } /* sampler state object, note D3D11 implements an internal shared-pool for sampler objects */ D3D11_SAMPLER_DESC d3d11_smp_desc; - memset(&d3d11_smp_desc, 0, sizeof(d3d11_smp_desc)); + _sg_clear(&d3d11_smp_desc, sizeof(d3d11_smp_desc)); d3d11_smp_desc.Filter = _sg_d3d11_filter(img->cmn.min_filter, img->cmn.mag_filter, img->cmn.max_anisotropy); d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(img->cmn.wrap_u); d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(img->cmn.wrap_v); @@ -8088,12 +9453,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const d3d11_smp_desc.MinLOD = desc->min_lod; d3d11_smp_desc.MaxLOD = desc->max_lod; hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &img->d3d11.smp); - SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.smp); + if (!(SUCCEEDED(hr) && img->d3d11.smp)) { + _SG_ERROR(D3D11_CREATE_SAMPLER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_d3d11_discard_image(_sg_image_t* img) { SOKOL_ASSERT(img); if (img->d3d11.tex2d) { _sg_d3d11_Release(img->d3d11.tex2d); @@ -8125,7 +9493,7 @@ _SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) { _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll"); if (0 == _sg.d3d11.d3dcompiler_dll) { /* don't attempt to load missing DLL in the future */ - SOKOL_LOG("failed to load d3dcompiler_47.dll!\n"); + _SG_ERROR(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED); _sg.d3d11.d3dcompiler_dll_load_failed = true; return false; } @@ -8162,8 +9530,12 @@ _SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_stage_desc* st 0, /* Flags2 */ &output, /* ppCode */ &errors_or_warnings); /* ppErrorMsgs */ + if (FAILED(hr)) { + _SG_ERROR(D3D11_SHADER_COMPILATION_FAILED); + } if (errors_or_warnings) { - SOKOL_LOG((LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); + _SG_WARN(D3D11_SHADER_COMPILATION_OUTPUT); + _SG_LOGMSG(D3D11_SHADER_COMPILATION_OUTPUT, (LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL; } if (FAILED(hr)) { @@ -8180,7 +9552,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons SOKOL_ASSERT(shd && desc); SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.vs_blob); HRESULT hr; - _SOKOL_UNUSED(hr); _sg_shader_common_init(&shd->cmn, desc); @@ -8195,17 +9566,20 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { - const _sg_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; + const _sg_shader_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; /* create a D3D constant buffer for each uniform block */ SOKOL_ASSERT(0 == d3d11_stage->cbufs[ub_index]); D3D11_BUFFER_DESC cb_desc; - memset(&cb_desc, 0, sizeof(cb_desc)); + _sg_clear(&cb_desc, sizeof(cb_desc)); cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16); cb_desc.Usage = D3D11_USAGE_DEFAULT; cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &d3d11_stage->cbufs[ub_index]); - SOKOL_ASSERT(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index]); + if (!(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index])) { + _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } } } @@ -8241,7 +9615,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons /* need to store the vertex shader byte code, this is needed later in sg_create_pipeline */ if (vs_succeeded && fs_succeeded) { shd->d3d11.vs_blob_length = vs_length; - shd->d3d11.vs_blob = SOKOL_MALLOC((size_t)vs_length); + shd->d3d11.vs_blob = _sg_malloc((size_t)vs_length); SOKOL_ASSERT(shd->d3d11.vs_blob); memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length); result = SG_RESOURCESTATE_VALID; @@ -8256,7 +9630,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons return result; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_d3d11_discard_shader(_sg_shader_t* shd) { SOKOL_ASSERT(shd); if (shd->d3d11.vs) { _sg_d3d11_Release(shd->d3d11.vs); @@ -8265,7 +9639,7 @@ _SOKOL_PRIVATE void _sg_d3d11_destroy_shader(_sg_shader_t* shd) { _sg_d3d11_Release(shd->d3d11.fs); } if (shd->d3d11.vs_blob) { - SOKOL_FREE(shd->d3d11.vs_blob); + _sg_free(shd->d3d11.vs_blob); } for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; @@ -8293,9 +9667,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, /* create input layout object */ HRESULT hr; - _SOKOL_UNUSED(hr); D3D11_INPUT_ELEMENT_DESC d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]; - memset(d3d11_comps, 0, sizeof(d3d11_comps)); + _sg_clear(d3d11_comps, sizeof(d3d11_comps)); int attr_index = 0; for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; @@ -8315,6 +9688,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func); if (SG_VERTEXSTEP_PER_INSTANCE == step_func) { d3d11_comp->InstanceDataStepRate = (UINT)step_rate; + pip->cmn.use_instanced_draw = true; } pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; } @@ -8334,27 +9708,33 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, shd->d3d11.vs_blob, /* pShaderByteCodeWithInputSignature */ shd->d3d11.vs_blob_length, /* BytecodeLength */ &pip->d3d11.il); - SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.il); + if (!(SUCCEEDED(hr) && pip->d3d11.il)) { + _SG_ERROR(D3D11_CREATE_INPUT_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } /* create rasterizer state */ D3D11_RASTERIZER_DESC rs_desc; - memset(&rs_desc, 0, sizeof(rs_desc)); + _sg_clear(&rs_desc, sizeof(rs_desc)); rs_desc.FillMode = D3D11_FILL_SOLID; rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode); rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW; - rs_desc.DepthBias = (INT) pip->cmn.depth_bias; - rs_desc.DepthBiasClamp = pip->cmn.depth_bias_clamp; - rs_desc.SlopeScaledDepthBias = pip->cmn.depth_bias_slope_scale; + rs_desc.DepthBias = (INT) pip->cmn.depth.bias; + rs_desc.DepthBiasClamp = pip->cmn.depth.bias_clamp; + rs_desc.SlopeScaledDepthBias = pip->cmn.depth.bias_slope_scale; rs_desc.DepthClipEnable = TRUE; rs_desc.ScissorEnable = TRUE; rs_desc.MultisampleEnable = desc->sample_count > 1; rs_desc.AntialiasedLineEnable = FALSE; hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs); - SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.rs); + if (!(SUCCEEDED(hr) && pip->d3d11.rs)) { + _SG_ERROR(D3D11_CREATE_RASTERIZER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } /* create depth-stencil state */ D3D11_DEPTH_STENCIL_DESC dss_desc; - memset(&dss_desc, 0, sizeof(dss_desc)); + _sg_clear(&dss_desc, sizeof(dss_desc)); dss_desc.DepthEnable = TRUE; dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare); @@ -8372,11 +9752,14 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op); dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare); hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss); - SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.dss); + if (!(SUCCEEDED(hr) && pip->d3d11.dss)) { + _SG_ERROR(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } /* create blend state */ D3D11_BLEND_DESC bs_desc; - memset(&bs_desc, 0, sizeof(bs_desc)); + _sg_clear(&bs_desc, sizeof(bs_desc)); bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled; bs_desc.IndependentBlendEnable = TRUE; { @@ -8403,13 +9786,19 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, } } hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs); - SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.bs); - + if (!(SUCCEEDED(hr) && pip->d3d11.bs)) { + _SG_ERROR(D3D11_CREATE_BLEND_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_d3d11_discard_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); } @@ -8447,7 +9836,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima ID3D11Resource* d3d11_res = 0; const bool is_msaa = att_img->cmn.sample_count > 1; D3D11_RENDER_TARGET_VIEW_DESC d3d11_rtv_desc; - memset(&d3d11_rtv_desc, 0, sizeof(d3d11_rtv_desc)); + _sg_clear(&d3d11_rtv_desc, sizeof(d3d11_rtv_desc)); d3d11_rtv_desc.Format = att_img->d3d11.format; if ((att_img->cmn.type == SG_IMAGETYPE_2D) || is_msaa) { if (is_msaa) { @@ -8477,8 +9866,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima } SOKOL_ASSERT(d3d11_res); HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, d3d11_res, &d3d11_rtv_desc, &pass->d3d11.color_atts[i].rtv); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv); + if (!(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv)) { + _SG_ERROR(D3D11_CREATE_RTV_FAILED); + return SG_RESOURCESTATE_FAILED; + } } /* optional depth-stencil image */ @@ -8496,7 +9887,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima /* create D3D11 depth-stencil-view */ D3D11_DEPTH_STENCIL_VIEW_DESC d3d11_dsv_desc; - memset(&d3d11_dsv_desc, 0, sizeof(d3d11_dsv_desc)); + _sg_clear(&d3d11_dsv_desc, sizeof(d3d11_dsv_desc)); d3d11_dsv_desc.Format = att_img->d3d11.format; const bool is_msaa = att_img->cmn.sample_count > 1; if (is_msaa) { @@ -8508,14 +9899,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima ID3D11Resource* d3d11_res = (ID3D11Resource*) att_img->d3d11.texds; SOKOL_ASSERT(d3d11_res); HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, d3d11_res, &d3d11_dsv_desc, &pass->d3d11.ds_att.dsv); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv); + if (!(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv)) { + _SG_ERROR(D3D11_CREATE_DSV_FAILED); + return SG_RESOURCESTATE_FAILED; + } } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_d3d11_destroy_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_d3d11_discard_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); @@ -8585,7 +9979,7 @@ _SOKOL_PRIVATE void _sg_d3d11_begin_pass(_sg_pass_t* pass, const sg_pass_action* /* set viewport and scissor rect to cover whole screen */ D3D11_VIEWPORT vp; - memset(&vp, 0, sizeof(vp)); + _sg_clear(&vp, sizeof(vp)); vp.Width = (FLOAT) w; vp.Height = (FLOAT) h; vp.MaxDepth = 1.0f; @@ -8690,6 +10084,7 @@ _SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) { _sg.d3d11.cur_pipeline = pip; _sg.d3d11.cur_pipeline_id.id = pip->slot.id; _sg.d3d11.use_indexed_draw = (pip->d3d11.index_format != DXGI_FORMAT_UNKNOWN); + _sg.d3d11.use_instanced_draw = pip->cmn.use_instanced_draw; _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs); _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref); @@ -8774,19 +10169,19 @@ _SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(sg_shader_stage stage_index, int ub _SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances) { SOKOL_ASSERT(_sg.d3d11.in_pass); if (_sg.d3d11.use_indexed_draw) { - if (1 == num_instances) { - _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); + if (_sg.d3d11.use_instanced_draw) { + _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); } else { - _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); + _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); } } else { - if (1 == num_instances) { - _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); + if (_sg.d3d11.use_instanced_draw) { + _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); } else { - _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); + _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); } } } @@ -8801,10 +10196,13 @@ _SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* d SOKOL_ASSERT(buf->d3d11.buf); D3D11_MAPPED_SUBRESOURCE d3d11_msr; HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr)); - memcpy(d3d11_msr.pData, data->ptr, data->size); - _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + if (SUCCEEDED(hr)) { + memcpy(d3d11_msr.pData, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + } + else { + _SG_ERROR(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED); + } } _SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { @@ -8814,12 +10212,15 @@ _SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* da D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; D3D11_MAPPED_SUBRESOURCE d3d11_msr; HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr)); - uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; - memcpy(dst_ptr, data->ptr, data->size); - _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); - /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backend */ + if (SUCCEEDED(hr)) { + uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; + memcpy(dst_ptr, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + } + else { + _SG_ERROR(D3D11_MAP_FOR_APPEND_BUFFER_FAILED); + } + /* NOTE: this alignment is a requirement from WebGPU, but we want identical behaviour across all backend */ return _sg_roundup((int)data->size, 4); } @@ -8839,7 +10240,6 @@ _SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; UINT subres_index = 0; HRESULT hr; - _SOKOL_UNUSED(hr); D3D11_MAPPED_SUBRESOURCE d3d11_msr; for (int face_index = 0; face_index < num_faces; face_index++) { for (int slice_index = 0; slice_index < num_slices; slice_index++) { @@ -8853,38 +10253,46 @@ _SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data const size_t slice_offset = slice_size * (size_t)slice_index; const uint8_t* slice_ptr = ((const uint8_t*)subimg_data->ptr) + slice_offset; hr = _sg_d3d11_Map(_sg.d3d11.ctx, d3d11_res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); - SOKOL_ASSERT(SUCCEEDED(hr)); - /* FIXME: need to handle difference in depth-pitch for 3D textures as well! */ - if (src_pitch == (int)d3d11_msr.RowPitch) { - memcpy(d3d11_msr.pData, slice_ptr, slice_size); + if (SUCCEEDED(hr)) { + /* FIXME: need to handle difference in depth-pitch for 3D textures as well! */ + if (src_pitch == (int)d3d11_msr.RowPitch) { + memcpy(d3d11_msr.pData, slice_ptr, slice_size); + } + else { + SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); + const uint8_t* src_ptr = slice_ptr; + uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; + for (int row_index = 0; row_index < mip_height; row_index++) { + memcpy(dst_ptr, src_ptr, (size_t)src_pitch); + src_ptr += src_pitch; + dst_ptr += d3d11_msr.RowPitch; + } + } + _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index); } else { - SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); - const uint8_t* src_ptr = slice_ptr; - uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; - for (int row_index = 0; row_index < mip_height; row_index++) { - memcpy(dst_ptr, src_ptr, (size_t)src_pitch); - src_ptr += src_pitch; - dst_ptr += d3d11_msr.RowPitch; - } + _SG_ERROR(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED); } - _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index); } } } } -/*== METAL BACKEND IMPLEMENTATION ============================================*/ +// ███ ███ ███████ ████████ █████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ████ ██ █████ ██ ███████ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>metal backend #elif defined(SOKOL_METAL) #if __has_feature(objc_arc) #define _SG_OBJC_RETAIN(obj) { } #define _SG_OBJC_RELEASE(obj) { obj = nil; } -#define _SG_OBJC_RELEASE_WITH_NULL(obj) { obj = [NSNull null]; } #else #define _SG_OBJC_RETAIN(obj) { [obj retain]; } #define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; } -#define _SG_OBJC_RELEASE_WITH_NULL(obj) { [obj release]; obj = [NSNull null]; } #endif /*-- enum translation functions ----------------------------------------------*/ @@ -8900,13 +10308,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; @@ -8939,6 +10351,8 @@ _SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) { case SG_VERTEXFORMAT_SHORT4N: return MTLVertexFormatShort4Normalized; case SG_VERTEXFORMAT_USHORT4N: return MTLVertexFormatUShort4Normalized; case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized; + case SG_VERTEXFORMAT_HALF2: return MTLVertexFormatHalf2; + case SG_VERTEXFORMAT_HALF4: return MTLVertexFormatHalf4; default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0; } } @@ -8978,12 +10392,14 @@ _SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) { case SG_PIXELFORMAT_RG16SI: return MTLPixelFormatRG16Sint; case SG_PIXELFORMAT_RG16F: return MTLPixelFormatRG16Float; case SG_PIXELFORMAT_RGBA8: return MTLPixelFormatRGBA8Unorm; + case SG_PIXELFORMAT_SRGB8A8: return MTLPixelFormatRGBA8Unorm_sRGB; case SG_PIXELFORMAT_RGBA8SN: return MTLPixelFormatRGBA8Snorm; case SG_PIXELFORMAT_RGBA8UI: return MTLPixelFormatRGBA8Uint; case SG_PIXELFORMAT_RGBA8SI: return MTLPixelFormatRGBA8Sint; case SG_PIXELFORMAT_BGRA8: return MTLPixelFormatBGRA8Unorm; case SG_PIXELFORMAT_RGB10A2: return MTLPixelFormatRGB10A2Unorm; case SG_PIXELFORMAT_RG11B10F: return MTLPixelFormatRG11B10Float; + case SG_PIXELFORMAT_RGB9E5: return MTLPixelFormatRGB9E5Float; case SG_PIXELFORMAT_RG32UI: return MTLPixelFormatRG32Uint; case SG_PIXELFORMAT_RG32SI: return MTLPixelFormatRG32Sint; case SG_PIXELFORMAT_RG32F: return MTLPixelFormatRG32Float; @@ -9231,7 +10647,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots); /* a queue of currently free slot indices */ _sg.mtl.idpool.free_queue_top = 0; - _sg.mtl.idpool.free_queue = (int*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); + _sg.mtl.idpool.free_queue = (int*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); /* pool slot 0 is reserved! */ for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) { _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i; @@ -9242,7 +10658,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { */ _sg.mtl.idpool.release_queue_front = 0; _sg.mtl.idpool.release_queue_back = 0; - _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); + _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { _sg.mtl.idpool.release_queue[i].frame_index = 0; _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX; @@ -9250,8 +10666,8 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { } _SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) { - SOKOL_FREE(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; - SOKOL_FREE(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; + _sg_free(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; + _sg_free(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; _SG_OBJC_RELEASE(_sg.mtl.idpool.pool); } @@ -9276,6 +10692,7 @@ _SOKOL_PRIVATE int _sg_mtl_add_resource(id res) { return _SG_MTL_INVALID_SLOT_INDEX; } const int slot_index = _sg_mtl_alloc_pool_slot(); + // NOTE: the NSMutableArray will take ownership of its items SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]); _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res; return slot_index; @@ -9315,8 +10732,11 @@ _SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) { /* safe to release this resource */ const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index; SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + /* note: the NSMutableArray takes ownership of its items, assigning an NSNull object will + release the object, no matter if using ARC or not + */ SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]); - _SG_OBJC_RELEASE_WITH_NULL(_sg.mtl.idpool.pool[(NSUInteger)slot_index]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = [NSNull null]; /* put the now free pool index back on the free queue */ _sg_mtl_free_pool_slot(slot_index); /* reset the release queue slot and advance the back index */ @@ -9381,13 +10801,14 @@ _SOKOL_PRIVATE int _sg_mtl_create_sampler(id mtl_device, const sg_ima id mtl_sampler = [mtl_device newSamplerStateWithDescriptor:mtl_desc]; _SG_OBJC_RELEASE(mtl_desc); int sampler_handle = _sg_mtl_add_resource(mtl_sampler); + _SG_OBJC_RELEASE(mtl_sampler); _sg_smpcache_add_item(&_sg.mtl.sampler_cache, img_desc, (uintptr_t)sampler_handle); return sampler_handle; } } _SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) { - memset(&_sg.mtl.state_cache, 0, sizeof(_sg.mtl.state_cache)); + _sg_clear(&_sg.mtl.state_cache, sizeof(_sg.mtl.state_cache)); } /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */ @@ -9467,6 +10888,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) { _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); @@ -9474,9 +10896,11 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) { _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); #else + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); #endif @@ -9552,14 +10976,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(); @@ -9588,6 +11008,7 @@ _SOKOL_PRIVATE void _sg_mtl_discard_backend(void) { } /* NOTE: MTLCommandBuffer and MTLRenderCommandEncoder are auto-released */ _sg.mtl.cmd_buffer = nil; + _sg.mtl.present_cmd_buffer = nil; _sg.mtl.cmd_encoder = nil; } @@ -9622,7 +11043,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_context(_sg_context_t* ctx) { return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_mtl_destroy_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_mtl_discard_context(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); _SOKOL_UNUSED(ctx); /* empty */ @@ -9654,11 +11075,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const } } buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf); + _SG_OBJC_RELEASE(mtl_buf); } return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_mtl_destroy_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_mtl_discard_buffer(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); for (int slot = 0; slot < buf->cmn.num_slots; slot++) { /* it's valid to call release resource with '0' */ @@ -9676,22 +11098,31 @@ _SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unr const uint8_t* data_ptr = (const uint8_t*)data->subimage[face_index][mip_index].ptr; const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); - /* special case PVRTC formats: bytePerRow must be 0 */ + /* special case PVRTC formats: bytePerRow and bytesPerImage must be 0 */ int bytes_per_row = 0; - int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + int bytes_per_slice = 0; if (!_sg_mtl_is_pvrtc(img->cmn.pixel_format)) { bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); } + /* bytesPerImage special case: https://developer.apple.com/documentation/metal/mtltexture/1515679-replaceregion + + "Supply a nonzero value only when you copy data to a MTLTextureType3D type texture" + */ MTLRegion region; + int bytes_per_image; if (img->cmn.type == SG_IMAGETYPE_3D) { const int mip_depth = _sg_max(img->cmn.num_slices >> mip_index, 1); region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth); + bytes_per_image = bytes_per_slice; /* FIXME: apparently the minimal bytes_per_image size for 3D texture is 4 KByte... somehow need to handle this */ } else { region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height); + bytes_per_image = 0; } + for (int slice_index = 0; slice_index < num_slices; slice_index++) { const int mtl_slice_index = (img->cmn.type == SG_IMAGETYPE_CUBE) ? face_index : slice_index; const int slice_offset = slice_index * bytes_per_slice; @@ -9701,7 +11132,7 @@ _SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unr slice:(NSUInteger)mtl_slice_index withBytes:data_ptr + slice_offset bytesPerRow:(NSUInteger)bytes_per_row - bytesPerImage:(NSUInteger)bytes_per_slice]; + bytesPerImage:(NSUInteger)bytes_per_image]; } } } @@ -9727,7 +11158,7 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type); mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format); if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) { - SOKOL_LOG("Unsupported texture pixel format!\n"); + _SG_ERROR(METAL_TEXTURE_FORMAT_NOT_SUPPORTED); return false; } mtl_desc.width = (NSUInteger)img->cmn.width; @@ -9746,18 +11177,21 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, mtl_desc.arrayLength = 1; } mtl_desc.usage = MTLTextureUsageShaderRead; + if (img->cmn.render_target) { + mtl_desc.usage |= MTLTextureUsageRenderTarget; + } + 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; } @@ -9765,11 +11199,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; } @@ -9777,11 +11208,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; @@ -9828,6 +11256,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg id tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; SOKOL_ASSERT(nil != tex); img->mtl.depth_tex = _sg_mtl_add_resource(tex); + _SG_OBJC_RELEASE(tex); } else { /* create the color texture @@ -9854,6 +11283,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg } } img->mtl.tex[slot] = _sg_mtl_add_resource(tex); + _SG_OBJC_RELEASE(tex); } /* if MSAA color render target, create an additional MSAA render-surface texture */ @@ -9861,6 +11291,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg _sg_mtl_init_texdesc_rt_msaa(mtl_desc, img); id tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; img->mtl.msaa_tex = _sg_mtl_add_resource(tex); + _SG_OBJC_RELEASE(tex); } /* create (possibly shared) sampler state */ @@ -9870,7 +11301,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_mtl_destroy_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_mtl_discard_image(_sg_image_t* img) { SOKOL_ASSERT(img); /* it's valid to call release resource with a 'null resource' */ for (int slot = 0; slot < img->cmn.num_slots; slot++) { @@ -9889,7 +11320,8 @@ _SOKOL_PRIVATE id _sg_mtl_compile_library(const char* src) { error:&err ]; if (err) { - SOKOL_LOG([err.localizedDescription UTF8String]); + _SG_ERROR(METAL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); } return lib; } @@ -9899,7 +11331,8 @@ _SOKOL_PRIVATE id _sg_mtl_library_from_bytecode(const void* ptr, siz dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT); id lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err]; if (err) { - SOKOL_LOG([err.localizedDescription UTF8String]); + _SG_ERROR(METAL_SHADER_CREATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); } _SG_OBJC_RELEASE(lib_data); return lib; @@ -9910,19 +11343,19 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const _sg_shader_common_init(&shd->cmn, desc); - /* create metal libray objects and lookup entry functions */ - id vs_lib; - id fs_lib; - id vs_func; - id fs_func; + /* create metal library objects and lookup entry functions */ + id vs_lib = nil; + id fs_lib = nil; + id vs_func = nil; + id fs_func = nil; const char* vs_entry = desc->vs.entry; const char* fs_entry = desc->fs.entry; if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { /* separate byte code provided */ vs_lib = _sg_mtl_library_from_bytecode(desc->vs.bytecode.ptr, desc->vs.bytecode.size); fs_lib = _sg_mtl_library_from_bytecode(desc->fs.bytecode.ptr, desc->fs.bytecode.size); - if (nil == vs_lib || nil == fs_lib) { - return SG_RESOURCESTATE_FAILED; + if ((nil == vs_lib) || (nil == fs_lib)) { + goto failed; } vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; @@ -9931,32 +11364,50 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const /* separate sources provided */ vs_lib = _sg_mtl_compile_library(desc->vs.source); fs_lib = _sg_mtl_compile_library(desc->fs.source); - if (nil == vs_lib || nil == fs_lib) { - return SG_RESOURCESTATE_FAILED; + if ((nil == vs_lib) || (nil == fs_lib)) { + goto failed; } vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; } else { - return SG_RESOURCESTATE_FAILED; + goto failed; } if (nil == vs_func) { - SOKOL_LOG("vertex shader entry function not found\n"); - return SG_RESOURCESTATE_FAILED; + _SG_ERROR(METAL_VERTEX_SHADER_ENTRY_NOT_FOUND); + goto failed; } if (nil == fs_func) { - SOKOL_LOG("fragment shader entry function not found\n"); - return SG_RESOURCESTATE_FAILED; + _SG_ERROR(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND); + goto failed; } /* it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index */ shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib = _sg_mtl_add_resource(vs_lib); + _SG_OBJC_RELEASE(vs_lib); shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib = _sg_mtl_add_resource(fs_lib); + _SG_OBJC_RELEASE(fs_lib); shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func = _sg_mtl_add_resource(vs_func); + _SG_OBJC_RELEASE(vs_func); shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func = _sg_mtl_add_resource(fs_func); + _SG_OBJC_RELEASE(fs_func); return SG_RESOURCESTATE_VALID; +failed: + if (vs_lib != nil) { + _SG_OBJC_RELEASE(vs_lib); + } + if (fs_lib != nil) { + _SG_OBJC_RELEASE(fs_lib); + } + if (vs_func != nil) { + _SG_OBJC_RELEASE(vs_func); + } + if (fs_func != nil) { + _SG_OBJC_RELEASE(fs_func); + } + return SG_RESOURCESTATE_FAILED; } -_SOKOL_PRIVATE void _sg_mtl_destroy_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) { SOKOL_ASSERT(shd); /* it is valid to call _sg_mtl_release_resource with a 'null resource' */ _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); @@ -10003,6 +11454,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_desc->stride; vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_desc->step_func); vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_desc->step_rate; + if (SG_VERTEXSTEP_PER_INSTANCE == l_desc->step_func) { + // NOTE: not actually used in _sg_mtl_draw() + pip->cmn.use_instanced_draw = true; + } } } @@ -10013,7 +11468,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func); - rp_desc.sampleCount = (NSUInteger)desc->sample_count; + rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count; rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; rp_desc.alphaToOneEnabled = NO; rp_desc.rasterizationEnabled = YES; @@ -10047,7 +11502,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s _SG_OBJC_RELEASE(rp_desc); if (nil == mtl_rps) { SOKOL_ASSERT(err); - SOKOL_LOG([err.localizedDescription UTF8String]); + _SG_ERROR(METAL_CREATE_RPS_FAILED); + _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]); return SG_RESOURCESTATE_FAILED; } @@ -10073,14 +11529,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask; ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask; } + // FIXME: can this actually fail? id mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc]; _SG_OBJC_RELEASE(ds_desc); pip->mtl.rps = _sg_mtl_add_resource(mtl_rps); + _SG_OBJC_RELEASE(mtl_rps); pip->mtl.dss = _sg_mtl_add_resource(mtl_dss); + _SG_OBJC_RELEASE(mtl_dss); return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_mtl_destroy_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_mtl_discard_pipeline(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); /* it's valid to call release resource with a 'null resource' */ _sg_mtl_release_resource(_sg.mtl.frame_index, pip->mtl.rps); @@ -10116,7 +11575,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pass(_sg_pass_t* pass, _sg_image return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_mtl_destroy_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_mtl_discard_pass(_sg_pass_t* pass) { SOKOL_ASSERT(pass); _SOKOL_UNUSED(pass); } @@ -10144,11 +11603,30 @@ _SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* a _sg.mtl.cur_height = h; _sg_mtl_clear_state_cache(); - /* if this is the first pass in the frame, create a command buffer */ + /* + if this is the first pass in the frame, create command buffers + + NOTE: we're creating two command buffers here, one with unretained references + for storing the regular commands, and one with retained references for + storing the presentDrawable call (this needs to hold on the drawable until + presentation has happened - and the easiest way to do this is to let the + command buffer manage the lifetime of the drawable). + + Also see: https://github.com/floooh/sokol/issues/762 + */ if (nil == _sg.mtl.cmd_buffer) { + SOKOL_ASSERT(nil == _sg.mtl.present_cmd_buffer); /* 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.present_cmd_buffer = [_sg.mtl.cmd_queue commandBuffer]; + [_sg.mtl.cmd_buffer enqueue]; + [_sg.mtl.present_cmd_buffer enqueue]; + [_sg.mtl.present_cmd_buffer addCompletedHandler:^(id cmd_buf) { + // NOTE: this code is called on a different thread! + _SOKOL_UNUSED(cmd_buf); + dispatch_semaphore_signal(_sg.mtl.sem); + }]; } /* if this is first pass in frame, get uniform buffer base pointer */ @@ -10235,10 +11713,12 @@ _SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* a SOKOL_ASSERT(ds_att_img->mtl.depth_tex != _SG_MTL_INVALID_SLOT_INDEX); pass_desc.depthAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.action); + pass_desc.depthAttachment.storeAction = MTLStoreActionStore; pass_desc.depthAttachment.clearDepth = action->depth.value; if (_sg_is_depth_stencil_format(ds_att_img->cmn.pixel_format)) { pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.action); + pass_desc.stencilAttachment.storeAction = MTLStoreActionStore; pass_desc.stencilAttachment.clearStencil = action->stencil.value; } } @@ -10282,10 +11762,7 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { SOKOL_ASSERT(_sg.mtl.drawable_cb || _sg.mtl.drawable_userdata_cb); 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 + SOKOL_ASSERT(nil != _sg.mtl.present_cmd_buffer); /* present, commit and signal semaphore when done */ id cur_drawable = nil; @@ -10295,12 +11772,11 @@ _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.present_cmd_buffer presentDrawable:cur_drawable]; + } [_sg.mtl.cmd_buffer commit]; + [_sg.mtl.present_cmd_buffer commit]; /* garbage-collect resources pending for release */ _sg_mtl_garbage_collect(_sg.mtl.frame_index); @@ -10314,6 +11790,7 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { _sg.mtl.cur_ub_base_ptr = 0; /* NOTE: MTLCommandBuffer is autoreleased */ _sg.mtl.cmd_buffer = nil; + _sg.mtl.present_cmd_buffer = nil; } _SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { @@ -10375,7 +11852,7 @@ _SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) { [_sg.mtl.cmd_encoder setCullMode:pip->mtl.cull_mode]; [_sg.mtl.cmd_encoder setFrontFacingWinding:pip->mtl.winding]; [_sg.mtl.cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; - [_sg.mtl.cmd_encoder setDepthBias:pip->cmn.depth_bias slopeScale:pip->cmn.depth_bias_slope_scale clamp:pip->cmn.depth_bias_clamp]; + [_sg.mtl.cmd_encoder setDepthBias:pip->cmn.depth.bias slopeScale:pip->cmn.depth.bias_slope_scale clamp:pip->cmn.depth.bias_clamp]; SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX); [_sg.mtl.cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)]; SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX); @@ -10467,7 +11944,7 @@ _SOKOL_PRIVATE void _sg_mtl_apply_uniforms(sg_shader_stage stage_index, int ub_i SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id); SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->shader->slot.id == _sg.mtl.state_cache.cur_pipeline->cmn.shader_id.id); SOKOL_ASSERT(ub_index < _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); - SOKOL_ASSERT(data->size <= _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); + SOKOL_ASSERT(data->size == _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); /* copy to global uniform buffer, record offset into cmd encoder, and advance offset */ uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset]; @@ -10550,7 +12027,13 @@ _SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* _sg_mtl_copy_image_data(img, mtl_tex, data); } -/*== WEBGPU BACKEND IMPLEMENTATION ===========================================*/ +// ██ ██ ███████ ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ █ ██ █████ ██████ ██ ███ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ███████ ██████ ██████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>webgpu backend #elif defined(SOKOL_WGPU) _SOKOL_PRIVATE WGPUBufferUsageFlags _sg_wgpu_buffer_usage(sg_buffer_type t, sg_usage u) { @@ -10680,6 +12163,8 @@ _SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) { case SG_VERTEXFORMAT_SHORT4: return WGPUVertexFormat_Short4; case SG_VERTEXFORMAT_SHORT4N: return WGPUVertexFormat_Short4Norm; case SG_VERTEXFORMAT_USHORT4N: return WGPUVertexFormat_UShort4Norm; + case SG_VERTEXFORMAT_HALF2: return WGPUVertexFormat_Half2; + case SG_VERTEXFORMAT_HALF3: return WGPUVertexFormat_Half4; /* FIXME! UINT10_N2 */ case SG_VERTEXFORMAT_UINT10_N2: default: @@ -10768,6 +12253,8 @@ _SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) { case SG_PIXELFORMAT_RG16SN: case SG_PIXELFORMAT_RGBA16: case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGB9E5: case SG_PIXELFORMAT_PVRTC_RGB_2BPP: case SG_PIXELFORMAT_PVRTC_RGB_4BPP: case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: @@ -10915,6 +12402,7 @@ _SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + /* FIXME: missing SG_PIXELFORMAT_RG11B10F */ _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); @@ -10966,14 +12454,14 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { _sg.wgpu.ub.num_bytes = desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; WGPUBufferDescriptor ub_desc; - memset(&ub_desc, 0, sizeof(ub_desc)); + _sg_clear(&ub_desc, sizeof(ub_desc)); ub_desc.size = _sg.wgpu.ub.num_bytes; ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst; _sg.wgpu.ub.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc); SOKOL_ASSERT(_sg.wgpu.ub.buf); WGPUBindGroupLayoutBinding ub_bglb_desc[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; - memset(ub_bglb_desc, 0, sizeof(ub_bglb_desc)); + _sg_clear(ub_bglb_desc, sizeof(ub_bglb_desc)); for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { WGPUShaderStage vis = (stage_index == SG_SHADERSTAGE_VS) ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { @@ -10986,14 +12474,14 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { } WGPUBindGroupLayoutDescriptor ub_bgl_desc; - memset(&ub_bgl_desc, 0, sizeof(ub_bgl_desc)); + _sg_clear(&ub_bgl_desc, sizeof(ub_bgl_desc)); ub_bgl_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; ub_bgl_desc.bindings = &ub_bglb_desc[0][0]; _sg.wgpu.ub.bindgroup_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &ub_bgl_desc); SOKOL_ASSERT(_sg.wgpu.ub.bindgroup_layout); WGPUBindGroupBinding ub_bgb[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; - memset(ub_bgb, 0, sizeof(ub_bgb)); + _sg_clear(ub_bgb, sizeof(ub_bgb)); for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { int bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; @@ -11004,7 +12492,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { } } WGPUBindGroupDescriptor bg_desc; - memset(&bg_desc, 0, sizeof(bg_desc)); + _sg_clear(&bg_desc, sizeof(bg_desc)); bg_desc.layout = _sg.wgpu.ub.bindgroup_layout; bg_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; bg_desc.bindings = &ub_bgb[0][0]; @@ -11040,7 +12528,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_mapped_callback(WGPUBufferMapAsyncStatus sta } /* FIXME: better handling for this */ if (WGPUBufferMapAsyncStatus_Success != status) { - SOKOL_LOG("Mapping uniform buffer failed!\n"); + _SG_ERROR(WGPU_MAP_UNIFORM_BUFFER_FAILED); SOKOL_ASSERT(false); } SOKOL_ASSERT(data && (data_len == _sg.wgpu.ub.num_bytes)); @@ -11060,7 +12548,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_next_frame(bool first_frame) { /* rewind per-frame offsets */ _sg.wgpu.ub.offset = 0; - memset(&_sg.wgpu.ub.bind_offsets, 0, sizeof(_sg.wgpu.ub.bind_offsets)); + _sg_clear(&_sg.wgpu.ub.bind_offsets, sizeof(_sg.wgpu.ub.bind_offsets)); /* check if a mapped staging buffer is available, otherwise create one */ for (int i = 0; i < _sg.wgpu.ub.stage.num; i++) { @@ -11076,7 +12564,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_next_frame(bool first_frame) { const int cur = _sg.wgpu.ub.stage.cur; WGPUBufferDescriptor desc; - memset(&desc, 0, sizeof(desc)); + _sg_clear(&desc, sizeof(desc)); desc.size = _sg.wgpu.ub.num_bytes; desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); @@ -11128,13 +12616,13 @@ _SOKOL_PRIVATE uint32_t _sg_wgpu_copy_image_data(WGPUBuffer stg_buf, uint8_t* st const uint32_t num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; const sg_pixel_format fmt = img->cmn.pixel_format; WGPUBufferCopyView src_view; - memset(&src_view, 0, sizeof(src_view)); + _sg_clear(&src_view, sizeof(src_view)); src_view.buffer = stg_buf; WGPUTextureCopyView dst_view; - memset(&dst_view, 0, sizeof(dst_view)); + _sg_clear(&dst_view, sizeof(dst_view)); dst_view.texture = img->wgpu.tex; WGPUExtent3D extent; - memset(&extent, 0, sizeof(extent)); + _sg_clear(&extent, sizeof(extent)); for (uint32_t face_index = 0; face_index < num_faces; face_index++) { for (uint32_t mip_index = 0; mip_index < (uint32_t)img->cmn.num_mipmaps; mip_index++) { @@ -11274,7 +12762,7 @@ _SOKOL_PRIVATE void _sg_wgpu_staging_next_frame(bool first_frame) { const int cur = _sg.wgpu.staging.cur; WGPUBufferDescriptor desc; - memset(&desc, 0, sizeof(desc)); + _sg_clear(&desc, sizeof(desc)); desc.size = _sg.wgpu.staging.num_bytes; desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); @@ -11299,7 +12787,7 @@ _SOKOL_PRIVATE uint32_t _sg_wgpu_staging_copy_to_buffer(WGPUBuffer dst_buf, uint SOKOL_ASSERT(data_num_bytes > 0); uint32_t copy_num_bytes = _sg_roundup(data_num_bytes, 4); if ((_sg.wgpu.staging.offset + copy_num_bytes) >= _sg.wgpu.staging.num_bytes) { - SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_buffer())!\n"); + _SG_ERROR(WGPU_STAGING_BUFFER_FULL_COPY_TO_BUFFER); return false; } const int cur = _sg.wgpu.staging.cur; @@ -11318,7 +12806,7 @@ _SOKOL_PRIVATE bool _sg_wgpu_staging_copy_to_texture(_sg_image_t* img, const sg_ SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); uint32_t num_bytes = _sg_wgpu_image_data_buffer_size(img); if ((_sg.wgpu.staging.offset + num_bytes) >= _sg.wgpu.staging.num_bytes) { - SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_texture)!\n"); + _SG_ERROR(WGPU_STAGING_BUFFER_FULL_COPY_TO_TEXTURE); return false; } const int cur = _sg.wgpu.staging.cur; @@ -11367,7 +12855,7 @@ _SOKOL_PRIVATE WGPUSampler _sg_wgpu_create_sampler(const sg_image_desc* img_desc /* create a new WGPU sampler and add to sampler cache */ /* FIXME: anisotropic filtering not supported? */ WGPUSamplerDescriptor smp_desc; - memset(&smp_desc, 0, sizeof(smp_desc)); + _sg_clear(&smp_desc, sizeof(smp_desc)); smp_desc.addressModeU = _sg_wgpu_sampler_addrmode(img_desc->wrap_u); smp_desc.addressModeV = _sg_wgpu_sampler_addrmode(img_desc->wrap_v); smp_desc.addressModeW = _sg_wgpu_sampler_addrmode(img_desc->wrap_w); @@ -11417,11 +12905,11 @@ _SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { /* create an empty bind group for shader stages without bound images */ WGPUBindGroupLayoutDescriptor bgl_desc; - memset(&bgl_desc, 0, sizeof(bgl_desc)); + _sg_clear(&bgl_desc, sizeof(bgl_desc)); WGPUBindGroupLayout empty_bgl = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); SOKOL_ASSERT(empty_bgl); WGPUBindGroupDescriptor bg_desc; - memset(&bg_desc, 0, sizeof(bg_desc)); + _sg_clear(&bg_desc, sizeof(bg_desc)); bg_desc.layout = empty_bgl; _sg.wgpu.empty_bind_group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); SOKOL_ASSERT(_sg.wgpu.empty_bind_group); @@ -11429,7 +12917,7 @@ _SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { /* create initial per-frame command encoders */ WGPUCommandEncoderDescriptor cmd_enc_desc; - memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); + _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); @@ -11456,7 +12944,7 @@ _SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { } _SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) { - SOKOL_LOG("_sg_wgpu_reset_state_cache: FIXME\n"); + _SG_WARN(WGPU_RESET_STATE_CACHE_FIXME); } _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) { @@ -11465,14 +12953,14 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) { return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_wgpu_discard_context(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); _SOKOL_UNUSED(ctx); } _SOKOL_PRIVATE void _sg_wgpu_activate_context(_sg_context_t* ctx) { (void)ctx; - SOKOL_LOG("_sg_wgpu_activate_context: FIXME\n"); + _SG_WARN(WGPU_ACTIVATE_CONTEXT_FIXME); } _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { @@ -11485,7 +12973,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const } else { WGPUBufferDescriptor wgpu_buf_desc; - memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); + _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(buf->cmn.type, buf->cmn.usage); wgpu_buf_desc.size = buf->cmn.size; if (SG_USAGE_IMMUTABLE == buf->cmn.usage) { @@ -11503,7 +12991,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_wgpu_discard_buffer(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); WGPUBuffer wgpu_buf = buf->wgpu.buf; if (0 != wgpu_buf) { @@ -11543,7 +13031,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s const bool injected = (0 != desc->wgpu_texture); const bool is_msaa = desc->sample_count > 1; WGPUTextureDescriptor wgpu_tex_desc; - memset(&wgpu_tex_desc, 0, sizeof(wgpu_tex_desc)); + _sg_clear(&wgpu_tex_desc, sizeof(wgpu_tex_desc)); _sg_wgpu_init_texdesc_common(&wgpu_tex_desc, desc); if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { SOKOL_ASSERT(img->cmn.render_target); @@ -11576,7 +13064,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s /* copy content into texture via a throw-away staging buffer */ if (desc->usage == SG_USAGE_IMMUTABLE && !desc->render_target) { WGPUBufferDescriptor wgpu_buf_desc; - memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); + _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); wgpu_buf_desc.size = _sg_wgpu_image_data_buffer_size(img); wgpu_buf_desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_CopyDst; WGPUCreateBufferMappedResult map = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &wgpu_buf_desc); @@ -11591,7 +13079,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s /* create texture view object */ WGPUTextureViewDescriptor wgpu_view_desc; - memset(&wgpu_view_desc, 0, sizeof(wgpu_view_desc)); + _sg_clear(&wgpu_view_desc, sizeof(wgpu_view_desc)); wgpu_view_desc.dimension = _sg_wgpu_tex_viewdim(desc->type); img->wgpu.tex_view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_view_desc); @@ -11617,7 +13105,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) { SOKOL_ASSERT(img); if (img->wgpu.tex) { wgpuTextureRelease(img->wgpu.tex); @@ -11677,7 +13165,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const _sg_strcpy(&wgpu_stage->entry, stage_desc->entry); WGPUShaderModuleDescriptor wgpu_shdmod_desc; - memset(&wgpu_shdmod_desc, 0, sizeof(wgpu_shdmod_desc)); + _sg_clear(&wgpu_shdmod_desc, sizeof(wgpu_shdmod_desc)); wgpu_shdmod_desc.codeSize = stage_desc->bytecode.size >> 2; wgpu_shdmod_desc.code = (const uint32_t*) stage_desc->bytecode.ptr; wgpu_stage->module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); @@ -11692,7 +13180,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const num_imgs = _SG_WGPU_MAX_SHADERSTAGE_IMAGES; } WGPUBindGroupLayoutBinding bglb_desc[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; - memset(bglb_desc, 0, sizeof(bglb_desc)); + _sg_clear(bglb_desc, sizeof(bglb_desc)); for (int img_index = 0; img_index < num_imgs; img_index++) { /* texture- and sampler-bindings */ WGPUBindGroupLayoutBinding* tex_desc = &bglb_desc[img_index*2 + 0]; @@ -11709,7 +13197,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const smp_desc->type = WGPUBindingType_Sampler; } WGPUBindGroupLayoutDescriptor img_bgl_desc; - memset(&img_bgl_desc, 0, sizeof(img_bgl_desc)); + _sg_clear(&img_bgl_desc, sizeof(img_bgl_desc)); img_bgl_desc.bindingCount = num_imgs * 2; img_bgl_desc.bindings = &bglb_desc[0]; wgpu_stage->bind_group_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &img_bgl_desc); @@ -11718,7 +13206,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const return success ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) { SOKOL_ASSERT(shd); for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; @@ -11748,15 +13236,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ shd->wgpu.stage[SG_SHADERSTAGE_FS].bind_group_layout }; WGPUPipelineLayoutDescriptor pl_desc; - memset(&pl_desc, 0, sizeof(pl_desc)); + _sg_clear(&pl_desc, sizeof(pl_desc)); pl_desc.bindGroupLayoutCount = 3; pl_desc.bindGroupLayouts = &pip_bgl[0]; WGPUPipelineLayout pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &pl_desc); WGPUVertexBufferLayoutDescriptor vb_desc[SG_MAX_SHADERSTAGE_BUFFERS]; - memset(&vb_desc, 0, sizeof(vb_desc)); + _sg_clear(&vb_desc, sizeof(vb_desc)); WGPUVertexAttributeDescriptor va_desc[SG_MAX_SHADERSTAGE_BUFFERS][SG_MAX_VERTEX_ATTRIBUTES]; - memset(&va_desc, 0, sizeof(va_desc)); + _sg_clear(&va_desc, sizeof(va_desc)); int vb_idx = 0; for (; vb_idx < SG_MAX_SHADERSTAGE_BUFFERS; vb_idx++) { const sg_buffer_layout_desc* src_vb_desc = &desc->layout.buffers[vb_idx]; @@ -11786,13 +13274,13 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ vb_desc[vb_idx].attributes = &va_desc[vb_idx][0]; } WGPUVertexStateDescriptor vx_state_desc; - memset(&vx_state_desc, 0, sizeof(vx_state_desc)); + _sg_clear(&vx_state_desc, sizeof(vx_state_desc)); vx_state_desc.indexFormat = _sg_wgpu_indexformat(desc->index_type); vx_state_desc.vertexBufferCount = vb_idx; vx_state_desc.vertexBuffers = vb_desc; WGPURasterizationStateDescriptor rs_desc; - memset(&rs_desc, 0, sizeof(rs_desc)); + _sg_clear(&rs_desc, sizeof(rs_desc)); rs_desc.frontFace = _sg_wgpu_frontface(desc->face_winding); rs_desc.cullMode = _sg_wgpu_cullmode(desc->cull_mode); rs_desc.depthBias = (int32_t) desc->depth.bias; @@ -11800,7 +13288,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ rs_desc.depthBiasSlopeScale = desc->depth.bias_slope_scale; WGPUDepthStencilStateDescriptor ds_desc; - memset(&ds_desc, 0, sizeof(ds_desc)); + _sg_clear(&ds_desc, sizeof(ds_desc)); ds_desc.format = _sg_wgpu_textureformat(desc->depth.pixel_format); ds_desc.depthWriteEnabled = desc->depth.write_enabled; ds_desc.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare); @@ -11816,12 +13304,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ ds_desc.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op); WGPUProgrammableStageDescriptor fs_desc; - memset(&fs_desc, 0, sizeof(fs_desc)); + _sg_clear(&fs_desc, sizeof(fs_desc)); fs_desc.module = shd->wgpu.stage[SG_SHADERSTAGE_FS].module; fs_desc.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; WGPUColorStateDescriptor cs_desc[SG_MAX_COLOR_ATTACHMENTS]; - memset(cs_desc, 0, sizeof(cs_desc)); + _sg_clear(cs_desc, sizeof(cs_desc)); for (uint32_t i = 0; i < desc->color_count; i++) { SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); cs_desc[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format); @@ -11835,7 +13323,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ } WGPURenderPipelineDescriptor pip_desc; - memset(&pip_desc, 0, sizeof(pip_desc)); + _sg_clear(&pip_desc, sizeof(pip_desc)); pip_desc.layout = pip_layout; pip_desc.vertexStage.module = shd->wgpu.stage[SG_SHADERSTAGE_VS].module; pip_desc.vertexStage.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; @@ -11857,8 +13345,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_wgpu_discard_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; @@ -11884,7 +13376,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag /* create a render-texture-view to render into the right sub-surface */ const bool is_msaa = img->cmn.sample_count > 1; WGPUTextureViewDescriptor view_desc; - memset(&view_desc, 0, sizeof(view_desc)); + _sg_clear(&view_desc, sizeof(view_desc)); view_desc.baseMipLevel = is_msaa ? 0 : att_desc->mip_level; view_desc.mipLevelCount = 1; view_desc.baseArrayLayer = is_msaa ? 0 : att_desc->slice; @@ -11915,7 +13407,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag SOKOL_ASSERT(0 == att_desc->mip_level); SOKOL_ASSERT(0 == att_desc->slice); WGPUTextureViewDescriptor view_desc; - memset(&view_desc, 0, sizeof(view_desc)); + _sg_clear(&view_desc, sizeof(view_desc)); WGPUTexture wgpu_tex = ds_img->wgpu.tex; SOKOL_ASSERT(wgpu_tex); pass->wgpu.ds_att.render_tex_view = wgpuTextureCreateView(wgpu_tex, &view_desc); @@ -11924,7 +13416,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag return SG_RESOURCESTATE_VALID; } -_SOKOL_PRIVATE void _sg_wgpu_destroy_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_wgpu_discard_pass(_sg_pass_t* pass) { SOKOL_ASSERT(pass); for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { if (pass->wgpu.color_atts[i].render_tex_view) { @@ -11971,9 +13463,9 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); if (pass) { WGPURenderPassDescriptor wgpu_pass_desc; - memset(&wgpu_pass_desc, 0, sizeof(wgpu_pass_desc)); + _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); WGPURenderPassColorAttachmentDescriptor wgpu_color_att_desc[SG_MAX_COLOR_ATTACHMENTS]; - memset(&wgpu_color_att_desc, 0, sizeof(wgpu_color_att_desc)); + _sg_clear(&wgpu_color_att_desc, sizeof(wgpu_color_att_desc)); SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_VALID); for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { const _sg_wgpu_attachment_t* wgpu_att = &pass->wgpu.color_atts[i]; @@ -11992,7 +13484,7 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* wgpu_pass_desc.colorAttachments = &wgpu_color_att_desc[0]; if (pass->wgpu.ds_att.image) { WGPURenderPassDepthStencilAttachmentDescriptor wgpu_ds_att_desc; - memset(&wgpu_ds_att_desc, 0, sizeof(wgpu_ds_att_desc)); + _sg_clear(&wgpu_ds_att_desc, sizeof(wgpu_ds_att_desc)); wgpu_ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); wgpu_ds_att_desc.clearDepth = action->depth.value; wgpu_ds_att_desc.stencilLoadOp = _sg_wgpu_load_op(action->stencil.action); @@ -12009,9 +13501,9 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* WGPUTextureView wgpu_depth_stencil_view = _sg.wgpu.depth_stencil_view_cb ? _sg.wgpu.depth_stencil_view_cb() : _sg.wgpu.depth_stencil_view_userdata_cb(_sg.wgpu.user_data); WGPURenderPassDescriptor pass_desc; - memset(&pass_desc, 0, sizeof(pass_desc)); + _sg_clear(&pass_desc, sizeof(pass_desc)); WGPURenderPassColorAttachmentDescriptor color_att_desc; - memset(&color_att_desc, 0, sizeof(color_att_desc)); + _sg_clear(&color_att_desc, sizeof(color_att_desc)); color_att_desc.loadOp = _sg_wgpu_load_op(action->colors[0].action); color_att_desc.clearColor.r = action->colors[0].value.r; color_att_desc.clearColor.g = action->colors[0].value.g; @@ -12022,7 +13514,7 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* pass_desc.colorAttachmentCount = 1; pass_desc.colorAttachments = &color_att_desc; WGPURenderPassDepthStencilAttachmentDescriptor ds_att_desc; - memset(&ds_att_desc, 0, sizeof(ds_att_desc)); + _sg_clear(&ds_att_desc, sizeof(ds_att_desc)); ds_att_desc.attachment = wgpu_depth_stencil_view; SOKOL_ASSERT(0 != ds_att_desc.attachment); ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); @@ -12064,7 +13556,7 @@ _SOKOL_PRIVATE void _sg_wgpu_commit(void) { WGPUCommandBuffer cmd_bufs[2]; WGPUCommandBufferDescriptor cmd_buf_desc; - memset(&cmd_buf_desc, 0, sizeof(cmd_buf_desc)); + _sg_clear(&cmd_buf_desc, sizeof(cmd_buf_desc)); cmd_bufs[0] = wgpuCommandEncoderFinish(_sg.wgpu.staging_cmd_enc, &cmd_buf_desc); SOKOL_ASSERT(cmd_bufs[0]); wgpuCommandEncoderRelease(_sg.wgpu.staging_cmd_enc); @@ -12082,7 +13574,7 @@ _SOKOL_PRIVATE void _sg_wgpu_commit(void) { /* create a new render- and staging-command-encoders for next frame */ WGPUCommandEncoderDescriptor cmd_enc_desc; - memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); + _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); @@ -12143,7 +13635,7 @@ _SOKOL_PRIVATE WGPUBindGroup _sg_wgpu_create_images_bindgroup(WGPUBindGroupLayou SOKOL_ASSERT(_sg.wgpu.dev); SOKOL_ASSERT(num_imgs <= _SG_WGPU_MAX_SHADERSTAGE_IMAGES); WGPUBindGroupBinding img_bgb[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; - memset(&img_bgb, 0, sizeof(img_bgb)); + _sg_clear(&img_bgb, sizeof(img_bgb)); for (int img_index = 0; img_index < num_imgs; img_index++) { WGPUBindGroupBinding* tex_bdg = &img_bgb[img_index*2 + 0]; WGPUBindGroupBinding* smp_bdg = &img_bgb[img_index*2 + 1]; @@ -12153,7 +13645,7 @@ _SOKOL_PRIVATE WGPUBindGroup _sg_wgpu_create_images_bindgroup(WGPUBindGroupLayou smp_bdg->sampler = imgs[img_index]->wgpu.sampler; } WGPUBindGroupDescriptor bg_desc; - memset(&bg_desc, 0, sizeof(bg_desc)); + _sg_clear(&bg_desc, sizeof(bg_desc)); bg_desc.layout = bgl; bg_desc.bindingCount = 2 * num_imgs; bg_desc.bindings = &img_bgb[0]; @@ -12269,7 +13761,13 @@ _SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* } #endif -/*== BACKEND API WRAPPERS ====================================================*/ +// ██████ ███████ ███ ██ ███████ ██████ ██ ██████ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ███ █████ ██ ██ ██ █████ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ████ ███████ ██ ██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>generic backend static inline void _sg_setup_backend(const sg_desc* desc) { #if defined(_SOKOL_ANY_GL) _sg_gl_setup_backend(desc); @@ -12350,17 +13848,17 @@ static inline sg_resource_state _sg_create_context(_sg_context_t* ctx) { #endif } -static inline void _sg_destroy_context(_sg_context_t* ctx) { +static inline void _sg_discard_context(_sg_context_t* ctx) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_context(ctx); + _sg_gl_discard_context(ctx); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_context(ctx); + _sg_mtl_discard_context(ctx); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_context(ctx); + _sg_d3d11_discard_context(ctx); #elif defined(SOKOL_WGPU) - _sg_wgpu_destroy_context(ctx); + _sg_wgpu_discard_context(ctx); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_context(ctx); + _sg_dummy_discard_context(ctx); #else #error("INVALID BACKEND"); #endif @@ -12382,17 +13880,17 @@ static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_bu #endif } -static inline void _sg_destroy_buffer(_sg_buffer_t* buf) { +static inline void _sg_discard_buffer(_sg_buffer_t* buf) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_buffer(buf); + _sg_gl_discard_buffer(buf); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_buffer(buf); + _sg_mtl_discard_buffer(buf); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_buffer(buf); + _sg_d3d11_discard_buffer(buf); #elif defined(SOKOL_WGPU) - _sg_wgpu_destroy_buffer(buf); + _sg_wgpu_discard_buffer(buf); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_buffer(buf); + _sg_dummy_discard_buffer(buf); #else #error("INVALID BACKEND"); #endif @@ -12414,17 +13912,17 @@ static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_imag #endif } -static inline void _sg_destroy_image(_sg_image_t* img) { +static inline void _sg_discard_image(_sg_image_t* img) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_image(img); + _sg_gl_discard_image(img); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_image(img); + _sg_mtl_discard_image(img); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_image(img); + _sg_d3d11_discard_image(img); #elif defined(SOKOL_WGPU) - _sg_wgpu_destroy_image(img); + _sg_wgpu_discard_image(img); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_image(img); + _sg_dummy_discard_image(img); #else #error("INVALID BACKEND"); #endif @@ -12446,17 +13944,17 @@ static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_sh #endif } -static inline void _sg_destroy_shader(_sg_shader_t* shd) { +static inline void _sg_discard_shader(_sg_shader_t* shd) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_shader(shd); + _sg_gl_discard_shader(shd); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_shader(shd); + _sg_mtl_discard_shader(shd); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_shader(shd); + _sg_d3d11_discard_shader(shd); #elif defined(SOKOL_WGPU) - _sg_wgpu_destroy_shader(shd); + _sg_wgpu_discard_shader(shd); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_shader(shd); + _sg_dummy_discard_shader(shd); #else #error("INVALID BACKEND"); #endif @@ -12478,17 +13976,17 @@ static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, _sg_sha #endif } -static inline void _sg_destroy_pipeline(_sg_pipeline_t* pip) { +static inline void _sg_discard_pipeline(_sg_pipeline_t* pip) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_pipeline(pip); + _sg_gl_discard_pipeline(pip); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_pipeline(pip); + _sg_mtl_discard_pipeline(pip); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_pipeline(pip); + _sg_d3d11_discard_pipeline(pip); #elif defined(SOKOL_WGPU) - _sg_wgpu_destroy_pipeline(pip); + _sg_wgpu_discard_pipeline(pip); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_pipeline(pip); + _sg_dummy_discard_pipeline(pip); #else #error("INVALID BACKEND"); #endif @@ -12510,17 +14008,17 @@ static inline sg_resource_state _sg_create_pass(_sg_pass_t* pass, _sg_image_t** #endif } -static inline void _sg_destroy_pass(_sg_pass_t* pass) { +static inline void _sg_discard_pass(_sg_pass_t* pass) { #if defined(_SOKOL_ANY_GL) - _sg_gl_destroy_pass(pass); + _sg_gl_discard_pass(pass); #elif defined(SOKOL_METAL) - _sg_mtl_destroy_pass(pass); + _sg_mtl_discard_pass(pass); #elif defined(SOKOL_D3D11) - _sg_d3d11_destroy_pass(pass); + _sg_d3d11_discard_pass(pass); #elif defined(SOKOL_WGPU) - return _sg_wgpu_destroy_pass(pass); + return _sg_wgpu_discard_pass(pass); #elif defined(SOKOL_DUMMY_BACKEND) - _sg_dummy_destroy_pass(pass); + _sg_dummy_discard_pass(pass); #else #error("INVALID BACKEND"); #endif @@ -12756,8 +14254,13 @@ static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) #endif } -/*== RESOURCE POOLS ==========================================================*/ - +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { SOKOL_ASSERT(pool && (num >= 1)); /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */ @@ -12765,12 +14268,9 @@ _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { pool->queue_top = 0; /* generation counters indexable by pool slot index, slot 0 is reserved */ size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; - pool->gen_ctrs = (uint32_t*) SOKOL_MALLOC(gen_ctrs_size); - SOKOL_ASSERT(pool->gen_ctrs); - memset(pool->gen_ctrs, 0, gen_ctrs_size); + pool->gen_ctrs = (uint32_t*)_sg_malloc_clear(gen_ctrs_size); /* it's not a bug to only reserve 'num' here */ - pool->free_queue = (int*) SOKOL_MALLOC(sizeof(int) * (size_t)num); - SOKOL_ASSERT(pool->free_queue); + pool->free_queue = (int*) _sg_malloc_clear(sizeof(int) * (size_t)num); /* never allocate the zero-th pool item since the invalid id is 0 */ for (int i = pool->size-1; i >= 1; i--) { pool->free_queue[pool->queue_top++] = i; @@ -12780,10 +14280,10 @@ _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { _SOKOL_PRIVATE void _sg_discard_pool(_sg_pool_t* pool) { SOKOL_ASSERT(pool); SOKOL_ASSERT(pool->free_queue); - SOKOL_FREE(pool->free_queue); + _sg_free(pool->free_queue); pool->free_queue = 0; SOKOL_ASSERT(pool->gen_ctrs); - SOKOL_FREE(pool->gen_ctrs); + _sg_free(pool->gen_ctrs); pool->gen_ctrs = 0; pool->size = 0; pool->queue_top = 0; @@ -12820,53 +14320,53 @@ _SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) { _SOKOL_PRIVATE void _sg_reset_slot(_sg_slot_t* slot) { SOKOL_ASSERT(slot); - memset(slot, 0, sizeof(_sg_slot_t)); + _sg_clear(slot, sizeof(_sg_slot_t)); } -_SOKOL_PRIVATE void _sg_reset_buffer(_sg_buffer_t* buf) { +_SOKOL_PRIVATE void _sg_reset_buffer_to_alloc_state(_sg_buffer_t* buf) { SOKOL_ASSERT(buf); _sg_slot_t slot = buf->slot; - memset(buf, 0, sizeof(_sg_buffer_t)); + _sg_clear(buf, sizeof(_sg_buffer_t)); buf->slot = slot; buf->slot.state = SG_RESOURCESTATE_ALLOC; } -_SOKOL_PRIVATE void _sg_reset_image(_sg_image_t* img) { +_SOKOL_PRIVATE void _sg_reset_image_to_alloc_state(_sg_image_t* img) { SOKOL_ASSERT(img); _sg_slot_t slot = img->slot; - memset(img, 0, sizeof(_sg_image_t)); + _sg_clear(img, sizeof(_sg_image_t)); img->slot = slot; img->slot.state = SG_RESOURCESTATE_ALLOC; } -_SOKOL_PRIVATE void _sg_reset_shader(_sg_shader_t* shd) { +_SOKOL_PRIVATE void _sg_reset_shader_to_alloc_state(_sg_shader_t* shd) { SOKOL_ASSERT(shd); _sg_slot_t slot = shd->slot; - memset(shd, 0, sizeof(_sg_shader_t)); + _sg_clear(shd, sizeof(_sg_shader_t)); shd->slot = slot; shd->slot.state = SG_RESOURCESTATE_ALLOC; } -_SOKOL_PRIVATE void _sg_reset_pipeline(_sg_pipeline_t* pip) { +_SOKOL_PRIVATE void _sg_reset_pipeline_to_alloc_state(_sg_pipeline_t* pip) { SOKOL_ASSERT(pip); _sg_slot_t slot = pip->slot; - memset(pip, 0, sizeof(_sg_pipeline_t)); + _sg_clear(pip, sizeof(_sg_pipeline_t)); pip->slot = slot; pip->slot.state = SG_RESOURCESTATE_ALLOC; } -_SOKOL_PRIVATE void _sg_reset_pass(_sg_pass_t* pass) { +_SOKOL_PRIVATE void _sg_reset_pass_to_alloc_state(_sg_pass_t* pass) { SOKOL_ASSERT(pass); _sg_slot_t slot = pass->slot; - memset(pass, 0, sizeof(_sg_pass_t)); + _sg_clear(pass, sizeof(_sg_pass_t)); pass->slot = slot; pass->slot.state = SG_RESOURCESTATE_ALLOC; } -_SOKOL_PRIVATE void _sg_reset_context(_sg_context_t* ctx) { +_SOKOL_PRIVATE void _sg_reset_context_to_alloc_state(_sg_context_t* ctx) { SOKOL_ASSERT(ctx); _sg_slot_t slot = ctx->slot; - memset(ctx, 0, sizeof(_sg_context_t)); + _sg_clear(ctx, sizeof(_sg_context_t)); ctx->slot = slot; ctx->slot.state = SG_RESOURCESTATE_ALLOC; } @@ -12878,54 +14378,42 @@ _SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) { SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->buffer_pool, desc->buffer_pool_size); size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size; - p->buffers = (_sg_buffer_t*) SOKOL_MALLOC(buffer_pool_byte_size); - SOKOL_ASSERT(p->buffers); - memset(p->buffers, 0, buffer_pool_byte_size); + p->buffers = (_sg_buffer_t*) _sg_malloc_clear(buffer_pool_byte_size); SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->image_pool, desc->image_pool_size); size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size; - p->images = (_sg_image_t*) SOKOL_MALLOC(image_pool_byte_size); - SOKOL_ASSERT(p->images); - memset(p->images, 0, image_pool_byte_size); + p->images = (_sg_image_t*) _sg_malloc_clear(image_pool_byte_size); SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->shader_pool, desc->shader_pool_size); size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size; - p->shaders = (_sg_shader_t*) SOKOL_MALLOC(shader_pool_byte_size); - SOKOL_ASSERT(p->shaders); - memset(p->shaders, 0, shader_pool_byte_size); + p->shaders = (_sg_shader_t*) _sg_malloc_clear(shader_pool_byte_size); SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->pipeline_pool, desc->pipeline_pool_size); size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size; - p->pipelines = (_sg_pipeline_t*) SOKOL_MALLOC(pipeline_pool_byte_size); - SOKOL_ASSERT(p->pipelines); - memset(p->pipelines, 0, pipeline_pool_byte_size); + p->pipelines = (_sg_pipeline_t*) _sg_malloc_clear(pipeline_pool_byte_size); SOKOL_ASSERT((desc->pass_pool_size > 0) && (desc->pass_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->pass_pool, desc->pass_pool_size); size_t pass_pool_byte_size = sizeof(_sg_pass_t) * (size_t)p->pass_pool.size; - p->passes = (_sg_pass_t*) SOKOL_MALLOC(pass_pool_byte_size); - SOKOL_ASSERT(p->passes); - memset(p->passes, 0, pass_pool_byte_size); + p->passes = (_sg_pass_t*) _sg_malloc_clear(pass_pool_byte_size); SOKOL_ASSERT((desc->context_pool_size > 0) && (desc->context_pool_size < _SG_MAX_POOL_SIZE)); _sg_init_pool(&p->context_pool, desc->context_pool_size); size_t context_pool_byte_size = sizeof(_sg_context_t) * (size_t)p->context_pool.size; - p->contexts = (_sg_context_t*) SOKOL_MALLOC(context_pool_byte_size); - SOKOL_ASSERT(p->contexts); - memset(p->contexts, 0, context_pool_byte_size); + p->contexts = (_sg_context_t*) _sg_malloc_clear(context_pool_byte_size); } _SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { SOKOL_ASSERT(p); - SOKOL_FREE(p->contexts); p->contexts = 0; - SOKOL_FREE(p->passes); p->passes = 0; - SOKOL_FREE(p->pipelines); p->pipelines = 0; - SOKOL_FREE(p->shaders); p->shaders = 0; - SOKOL_FREE(p->images); p->images = 0; - SOKOL_FREE(p->buffers); p->buffers = 0; + _sg_free(p->contexts); p->contexts = 0; + _sg_free(p->passes); p->passes = 0; + _sg_free(p->pipelines); p->pipelines = 0; + _sg_free(p->shaders); p->shaders = 0; + _sg_free(p->images); p->images = 0; + _sg_free(p->buffers); p->buffers = 0; _sg_discard_pool(&p->context_pool); _sg_discard_pool(&p->pass_pool); _sg_discard_pool(&p->pipeline_pool); @@ -13070,7 +14558,7 @@ _SOKOL_PRIVATE _sg_context_t* _sg_lookup_context(const _sg_pools_t* p, uint32_t return 0; } -_SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { +_SOKOL_PRIVATE void _sg_discard_all_resources(_sg_pools_t* p, uint32_t ctx_id) { /* this is a bit dumb since it loops over all pool slots to find the occupied slots, on the other hand it is only ever executed at shutdown @@ -13082,7 +14570,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { if (p->buffers[i].slot.ctx_id == ctx_id) { sg_resource_state state = p->buffers[i].slot.state; if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { - _sg_destroy_buffer(&p->buffers[i]); + _sg_discard_buffer(&p->buffers[i]); } } } @@ -13090,7 +14578,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { if (p->images[i].slot.ctx_id == ctx_id) { sg_resource_state state = p->images[i].slot.state; if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { - _sg_destroy_image(&p->images[i]); + _sg_discard_image(&p->images[i]); } } } @@ -13098,7 +14586,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { if (p->shaders[i].slot.ctx_id == ctx_id) { sg_resource_state state = p->shaders[i].slot.state; if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { - _sg_destroy_shader(&p->shaders[i]); + _sg_discard_shader(&p->shaders[i]); } } } @@ -13106,7 +14594,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { if (p->pipelines[i].slot.ctx_id == ctx_id) { sg_resource_state state = p->pipelines[i].slot.state; if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { - _sg_destroy_pipeline(&p->pipelines[i]); + _sg_discard_pipeline(&p->pipelines[i]); } } } @@ -13114,160 +14602,32 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { if (p->passes[i].slot.ctx_id == ctx_id) { sg_resource_state state = p->passes[i].slot.state; if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { - _sg_destroy_pass(&p->passes[i]); + _sg_discard_pass(&p->passes[i]); } } } } -/*== VALIDATION LAYER ========================================================*/ -#if defined(SOKOL_DEBUG) -/* return a human readable string for an _sg_validate_error */ -_SOKOL_PRIVATE const char* _sg_validate_string(_sg_validate_error_t err) { - switch (err) { - /* buffer creation validation errors */ - case _SG_VALIDATE_BUFFERDESC_CANARY: return "sg_buffer_desc not initialized"; - case _SG_VALIDATE_BUFFERDESC_SIZE: return "sg_buffer_desc.size cannot be 0"; - case _SG_VALIDATE_BUFFERDESC_DATA: return "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)"; - case _SG_VALIDATE_BUFFERDESC_DATA_SIZE: return "immutable buffer data size differs from buffer size"; - case _SG_VALIDATE_BUFFERDESC_NO_DATA: return "dynamic/stream usage buffers cannot be initialized with data"; - - /* image creation validation errros */ - case _SG_VALIDATE_IMAGEDESC_CANARY: return "sg_image_desc not initialized"; - case _SG_VALIDATE_IMAGEDESC_WIDTH: return "sg_image_desc.width must be > 0"; - case _SG_VALIDATE_IMAGEDESC_HEIGHT: return "sg_image_desc.height must be > 0"; - case _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT: return "invalid pixel format for render-target image"; - case _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT: return "invalid pixel format for non-render-target image"; - case _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT: return "non-render-target images cannot be multisampled"; - case _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT: return "MSAA not supported for this pixel format"; - case _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE: return "render target images must be SG_USAGE_IMMUTABLE"; - case _SG_VALIDATE_IMAGEDESC_RT_NO_DATA: return "render target images cannot be initialized with data"; - case _SG_VALIDATE_IMAGEDESC_DATA: return "missing or invalid data for immutable image"; - case _SG_VALIDATE_IMAGEDESC_NO_DATA: return "dynamic/stream usage images cannot be initialized with data"; - - /* shader creation */ - case _SG_VALIDATE_SHADERDESC_CANARY: return "sg_shader_desc not initialized"; - case _SG_VALIDATE_SHADERDESC_SOURCE: return "shader source code required"; - case _SG_VALIDATE_SHADERDESC_BYTECODE: return "shader byte code required"; - case _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE: return "shader source or byte code required"; - case _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE: return "shader byte code length (in bytes) required"; - case _SG_VALIDATE_SHADERDESC_NO_CONT_UBS: return "shader uniform blocks must occupy continuous slots"; - case _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS: return "uniform block members must occupy continuous slots"; - case _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS: return "GL backend requires uniform block member declarations"; - case _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME: return "uniform block member name missing"; - case _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH: return "size of uniform block members doesn't match uniform block size"; - case _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS: return "shader images must occupy continuous slots"; - case _SG_VALIDATE_SHADERDESC_IMG_NAME: return "GL backend requires uniform block member names"; - case _SG_VALIDATE_SHADERDESC_ATTR_NAMES: return "GLES2 backend requires vertex attribute names"; - case _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS: return "D3D11 backend requires vertex attribute semantics"; - case _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG: return "vertex attribute name/semantic string too long (max len 16)"; - - /* pipeline creation */ - case _SG_VALIDATE_PIPELINEDESC_CANARY: return "sg_pipeline_desc not initialized"; - case _SG_VALIDATE_PIPELINEDESC_SHADER: return "sg_pipeline_desc.shader missing or invalid"; - case _SG_VALIDATE_PIPELINEDESC_NO_ATTRS: return "sg_pipeline_desc.layout.attrs is empty or not continuous"; - case _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4: return "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4"; - case _SG_VALIDATE_PIPELINEDESC_ATTR_NAME: return "GLES2/WebGL missing vertex attribute name in shader"; - case _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS: return "D3D11 missing vertex attribute semantics in shader"; - - /* pass creation */ - case _SG_VALIDATE_PASSDESC_CANARY: return "sg_pass_desc not initialized"; - case _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS: return "sg_pass_desc.color_attachments[0] must be valid"; - case _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS: return "color attachments must occupy continuous slots"; - case _SG_VALIDATE_PASSDESC_IMAGE: return "pass attachment image is not valid"; - case _SG_VALIDATE_PASSDESC_MIPLEVEL: return "pass attachment mip level is bigger than image has mipmaps"; - case _SG_VALIDATE_PASSDESC_FACE: return "pass attachment image is cubemap, but face index is too big"; - case _SG_VALIDATE_PASSDESC_LAYER: return "pass attachment image is array texture, but layer index is too big"; - case _SG_VALIDATE_PASSDESC_SLICE: return "pass attachment image is 3d texture, but slice value is too big"; - case _SG_VALIDATE_PASSDESC_IMAGE_NO_RT: return "pass attachment image must be render targets"; - case _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT: return "pass color-attachment images must have a renderable pixel format"; - case _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT: return "pass depth-attachment image must have depth pixel format"; - case _SG_VALIDATE_PASSDESC_IMAGE_SIZES: return "all pass attachments must have the same size"; - case _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS: return "all pass attachments must have the same sample count"; - - /* sg_begin_pass */ - case _SG_VALIDATE_BEGINPASS_PASS: return "sg_begin_pass: pass must be valid"; - case _SG_VALIDATE_BEGINPASS_IMAGE: return "sg_begin_pass: one or more attachment images are not valid"; - - /* sg_apply_pipeline */ - case _SG_VALIDATE_APIP_PIPELINE_VALID_ID: return "sg_apply_pipeline: invalid pipeline id provided"; - case _SG_VALIDATE_APIP_PIPELINE_EXISTS: return "sg_apply_pipeline: pipeline object no longer alive"; - case _SG_VALIDATE_APIP_PIPELINE_VALID: return "sg_apply_pipeline: pipeline object not in valid state"; - case _SG_VALIDATE_APIP_SHADER_EXISTS: return "sg_apply_pipeline: shader object no longer alive"; - case _SG_VALIDATE_APIP_SHADER_VALID: return "sg_apply_pipeline: shader object not in valid state"; - case _SG_VALIDATE_APIP_ATT_COUNT: return "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments"; - case _SG_VALIDATE_APIP_COLOR_FORMAT: return "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format"; - case _SG_VALIDATE_APIP_DEPTH_FORMAT: return "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format"; - case _SG_VALIDATE_APIP_SAMPLE_COUNT: return "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count"; - - /* sg_apply_bindings */ - case _SG_VALIDATE_ABND_PIPELINE: return "sg_apply_bindings: must be called after sg_apply_pipeline"; - case _SG_VALIDATE_ABND_PIPELINE_EXISTS: return "sg_apply_bindings: currently applied pipeline object no longer alive"; - case _SG_VALIDATE_ABND_PIPELINE_VALID: return "sg_apply_bindings: currently applied pipeline object not in valid state"; - case _SG_VALIDATE_ABND_VBS: return "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts"; - case _SG_VALIDATE_ABND_VB_EXISTS: return "sg_apply_bindings: vertex buffer no longer alive"; - case _SG_VALIDATE_ABND_VB_TYPE: return "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER"; - case _SG_VALIDATE_ABND_VB_OVERFLOW: return "sg_apply_bindings: buffer in vertex buffer slot is overflown"; - case _SG_VALIDATE_ABND_NO_IB: return "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided"; - case _SG_VALIDATE_ABND_IB: return "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided"; - case _SG_VALIDATE_ABND_IB_EXISTS: return "sg_apply_bindings: index buffer no longer alive"; - case _SG_VALIDATE_ABND_IB_TYPE: return "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER"; - case _SG_VALIDATE_ABND_IB_OVERFLOW: return "sg_apply_bindings: buffer in index buffer slot is overflown"; - case _SG_VALIDATE_ABND_VS_IMGS: return "sg_apply_bindings: vertex shader image count doesn't match sg_shader_desc"; - case _SG_VALIDATE_ABND_VS_IMG_EXISTS: return "sg_apply_bindings: vertex shader image no longer alive"; - case _SG_VALIDATE_ABND_VS_IMG_TYPES: return "sg_apply_bindings: one or more vertex shader image types don't match sg_shader_desc"; - case _SG_VALIDATE_ABND_FS_IMGS: return "sg_apply_bindings: fragment shader image count doesn't match sg_shader_desc"; - case _SG_VALIDATE_ABND_FS_IMG_EXISTS: return "sg_apply_bindings: fragment shader image no longer alive"; - case _SG_VALIDATE_ABND_FS_IMG_TYPES: return "sg_apply_bindings: one or more fragment shader image types don't match sg_shader_desc"; - - /* sg_apply_uniforms */ - case _SG_VALIDATE_AUB_NO_PIPELINE: return "sg_apply_uniforms: must be called after sg_apply_pipeline()"; - case _SG_VALIDATE_AUB_NO_UB_AT_SLOT: return "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot"; - case _SG_VALIDATE_AUB_SIZE: return "sg_apply_uniforms: data size exceeds declared uniform block size"; - - /* sg_update_buffer */ - case _SG_VALIDATE_UPDATEBUF_USAGE: return "sg_update_buffer: cannot update immutable buffer"; - case _SG_VALIDATE_UPDATEBUF_SIZE: return "sg_update_buffer: update size is bigger than buffer size"; - case _SG_VALIDATE_UPDATEBUF_ONCE: return "sg_update_buffer: only one update allowed per buffer and frame"; - case _SG_VALIDATE_UPDATEBUF_APPEND: return "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame"; - - /* sg_append_buffer */ - case _SG_VALIDATE_APPENDBUF_USAGE: return "sg_append_buffer: cannot append to immutable buffer"; - case _SG_VALIDATE_APPENDBUF_SIZE: return "sg_append_buffer: overall appended size is bigger than buffer size"; - case _SG_VALIDATE_APPENDBUF_UPDATE: return "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame"; - - /* sg_update_image */ - case _SG_VALIDATE_UPDIMG_USAGE: return "sg_update_image: cannot update immutable image"; - case _SG_VALIDATE_UPDIMG_NOTENOUGHDATA: return "sg_update_image: not enough subimage data provided"; - case _SG_VALIDATE_UPDIMG_SIZE: return "sg_update_image: provided subimage data size too big"; - case _SG_VALIDATE_UPDIMG_COMPRESSED: return "sg_update_image: cannot update images with compressed format"; - case _SG_VALIDATE_UPDIMG_ONCE: return "sg_update_image: only one update allowed per image and frame"; - - default: return "unknown validation error"; - } -} -#endif /* defined(SOKOL_DEBUG) */ - -/*-- validation checks -------------------------------------------------------*/ +// ██ ██ █████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ███████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ████ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ██ ████ +// +// >>validation #if defined(SOKOL_DEBUG) _SOKOL_PRIVATE void _sg_validate_begin(void) { - _sg.validate_error = _SG_VALIDATE_SUCCESS; -} - -_SOKOL_PRIVATE void _sg_validate(bool cond, _sg_validate_error_t err) { - if (!cond) { - _sg.validate_error = err; - SOKOL_LOG(_sg_validate_string(err)); - } + _sg.validate_error = SG_LOGITEM_OK; } _SOKOL_PRIVATE bool _sg_validate_end(void) { - if (_sg.validate_error != _SG_VALIDATE_SUCCESS) { + if (_sg.validate_error != SG_LOGITEM_OK) { #if !defined(SOKOL_VALIDATE_NON_FATAL) - SOKOL_LOG("^^^^ VALIDATION FAILED, TERMINATING ^^^^"); - SOKOL_ASSERT(false); + _SG_PANIC(VALIDATION_FAILED); + return false; + #else + return false; #endif - return false; } else { return true; @@ -13280,23 +14640,51 @@ _SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) { _SOKOL_UNUSED(desc); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(desc); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); - SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); - SOKOL_VALIDATE(desc->size > 0, _SG_VALIDATE_BUFFERDESC_SIZE); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->size > 0, VALIDATE_BUFFERDESC_SIZE); bool injected = (0 != desc->gl_buffers[0]) || (0 != desc->mtl_buffers[0]) || (0 != desc->d3d11_buffer) || (0 != desc->wgpu_buffer); if (!injected && (desc->usage == SG_USAGE_IMMUTABLE)) { - SOKOL_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), _SG_VALIDATE_BUFFERDESC_DATA); - SOKOL_VALIDATE(desc->size == desc->data.size, _SG_VALIDATE_BUFFERDESC_DATA_SIZE); + _SG_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), VALIDATE_BUFFERDESC_DATA); + _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_DATA_SIZE); } else { - SOKOL_VALIDATE(0 == desc->data.ptr, _SG_VALIDATE_BUFFERDESC_NO_DATA); + _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_NO_DATA); + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE void _sg_validate_image_data(const sg_image_data* data, sg_pixel_format fmt, int width, int height, int num_faces, int num_mips, int num_slices) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(data); + _SOKOL_UNUSED(fmt); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(num_faces); + _SOKOL_UNUSED(num_mips); + _SOKOL_UNUSED(num_slices); + #else + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + const bool has_data = data->subimage[face_index][mip_index].ptr != 0; + const bool has_size = data->subimage[face_index][mip_index].size > 0; + _SG_VALIDATE(has_data && has_size, VALIDATE_IMAGEDATA_NODATA); + const int mip_width = _sg_max(width >> mip_index, 1); + const int mip_height = _sg_max(height >> mip_index, 1); + const int bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1); + const int expected_size = bytes_per_slice * num_slices; + _SG_VALIDATE(expected_size == (int)data->subimage[face_index][mip_index].size, VALIDATE_IMAGEDATA_DATA_SIZE); + } } - return SOKOL_VALIDATE_END(); #endif } @@ -13305,12 +14693,15 @@ _SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { _SOKOL_UNUSED(desc); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(desc); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); - SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); - SOKOL_VALIDATE(desc->width > 0, _SG_VALIDATE_IMAGEDESC_WIDTH); - SOKOL_VALIDATE(desc->height > 0, _SG_VALIDATE_IMAGEDESC_HEIGHT); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(desc->width > 0, VALIDATE_IMAGEDESC_WIDTH); + _SG_VALIDATE(desc->height > 0, VALIDATE_IMAGEDESC_HEIGHT); const sg_pixel_format fmt = desc->pixel_format; const sg_usage usage = desc->usage; const bool injected = (0 != desc->gl_textures[0]) || @@ -13319,47 +14710,56 @@ _SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { (0 != desc->wgpu_texture); if (desc->render_target) { SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); - SOKOL_VALIDATE(_sg.formats[fmt].render, _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT); + _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_RT_PIXELFORMAT); /* on GLES2, sample count for render targets is completely ignored */ #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) if (!_sg.gl.gles2) { #endif if (desc->sample_count > 1) { - SOKOL_VALIDATE(_sg.features.msaa_render_targets && _sg.formats[fmt].msaa, _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); + _SG_VALIDATE(_sg.features.msaa_render_targets && _sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); } #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) } #endif - SOKOL_VALIDATE(usage == SG_USAGE_IMMUTABLE, _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE); - SOKOL_VALIDATE(desc->data.subimage[0][0].ptr==0, _SG_VALIDATE_IMAGEDESC_RT_NO_DATA); + _SG_VALIDATE(usage == SG_USAGE_IMMUTABLE, VALIDATE_IMAGEDESC_RT_IMMUTABLE); + _SG_VALIDATE(desc->data.subimage[0][0].ptr==0, VALIDATE_IMAGEDESC_RT_NO_DATA); } else { - SOKOL_VALIDATE(desc->sample_count <= 1, _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); + _SG_VALIDATE(desc->sample_count <= 1, VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); const bool valid_nonrt_fmt = !_sg_is_valid_rendertarget_depth_format(fmt); - SOKOL_VALIDATE(valid_nonrt_fmt, _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); - /* FIXME: should use the same "expected size" computation as in _sg_validate_update_image() here */ - if (!injected && (usage == SG_USAGE_IMMUTABLE)) { - const int num_faces = desc->type == SG_IMAGETYPE_CUBE ? 6:1; - const int num_mips = desc->num_mipmaps; - for (int face_index = 0; face_index < num_faces; face_index++) { - for (int mip_index = 0; mip_index < num_mips; mip_index++) { - const bool has_data = desc->data.subimage[face_index][mip_index].ptr != 0; - const bool has_size = desc->data.subimage[face_index][mip_index].size > 0; - SOKOL_VALIDATE(has_data && has_size, _SG_VALIDATE_IMAGEDESC_DATA); - } - } + _SG_VALIDATE(valid_nonrt_fmt, VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); + const bool is_compressed = _sg_is_compressed_pixel_format(desc->pixel_format); + const bool is_immutable = (usage == SG_USAGE_IMMUTABLE); + if (is_compressed) { + _SG_VALIDATE(is_immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); + } + if (!injected && is_immutable) { + // image desc must have valid data + _sg_validate_image_data(&desc->data, + desc->pixel_format, + desc->width, + desc->height, + (desc->type == SG_IMAGETYPE_CUBE) ? 6 : 1, + desc->num_mipmaps, + desc->num_slices); } else { + // image desc must not have data for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { const bool no_data = 0 == desc->data.subimage[face_index][mip_index].ptr; const bool no_size = 0 == desc->data.subimage[face_index][mip_index].size; - SOKOL_VALIDATE(no_data && no_size, _SG_VALIDATE_IMAGEDESC_NO_DATA); + if (injected) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA); + } + if (!is_immutable) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA); + } } } } } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13368,44 +14768,47 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { _SOKOL_UNUSED(desc); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(desc); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); - SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY); #if defined(SOKOL_GLES2) - SOKOL_VALIDATE(0 != desc->attrs[0].name, _SG_VALIDATE_SHADERDESC_ATTR_NAMES); + _SG_VALIDATE(0 != desc->attrs[0].name, VALIDATE_SHADERDESC_ATTR_NAMES); #elif defined(SOKOL_D3D11) - SOKOL_VALIDATE(0 != desc->attrs[0].sem_name, _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS); + _SG_VALIDATE(0 != desc->attrs[0].sem_name, VALIDATE_SHADERDESC_ATTR_SEMANTICS); #endif #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) /* on GL, must provide shader source code */ - SOKOL_VALIDATE(0 != desc->vs.source, _SG_VALIDATE_SHADERDESC_SOURCE); - SOKOL_VALIDATE(0 != desc->fs.source, _SG_VALIDATE_SHADERDESC_SOURCE); + _SG_VALIDATE(0 != desc->vs.source, VALIDATE_SHADERDESC_SOURCE); + _SG_VALIDATE(0 != desc->fs.source, VALIDATE_SHADERDESC_SOURCE); #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) /* on Metal or D3D11, must provide shader source code or byte code */ - SOKOL_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); - SOKOL_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); + _SG_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); + _SG_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); #elif defined(SOKOL_WGPU) /* on WGPU byte code must be provided */ - SOKOL_VALIDATE((0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); - SOKOL_VALIDATE((0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); + _SG_VALIDATE((0 != desc->vs.bytecode.ptr), VALIDATE_SHADERDESC_BYTECODE); + _SG_VALIDATE((0 != desc->fs.bytecode.ptr), VALIDATE_SHADERDESC_BYTECODE); #else /* Dummy Backend, don't require source or bytecode */ #endif for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { if (desc->attrs[i].name) { - SOKOL_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + _SG_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); } if (desc->attrs[i].sem_name) { - SOKOL_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + _SG_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); } } /* if shader byte code, the size must also be provided */ if (0 != desc->vs.bytecode.ptr) { - SOKOL_VALIDATE(desc->vs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + _SG_VALIDATE(desc->vs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); } if (0 != desc->fs.bytecode.ptr) { - SOKOL_VALIDATE(desc->fs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + _SG_VALIDATE(desc->fs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); } for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { const sg_shader_stage_desc* stage_desc = (stage_index == 0)? &desc->vs : &desc->fs; @@ -13413,28 +14816,41 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; if (ub_desc->size > 0) { - SOKOL_VALIDATE(uniform_blocks_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UBS); + _SG_VALIDATE(uniform_blocks_continuous, VALIDATE_SHADERDESC_NO_CONT_UBS); + #if defined(_SOKOL_ANY_GL) bool uniforms_continuous = true; - int uniform_offset = 0; + uint32_t uniform_offset = 0; int num_uniforms = 0; for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; if (u_desc->type != SG_UNIFORMTYPE_INVALID) { - SOKOL_VALIDATE(uniforms_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); + _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - SOKOL_VALIDATE(0 != u_desc->name, _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME); + _SG_VALIDATE(0 != u_desc->name, VALIDATE_SHADERDESC_UB_MEMBER_NAME); #endif const int array_count = u_desc->array_count; - uniform_offset += _sg_uniform_size(u_desc->type, array_count); + _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UB_ARRAY_COUNT); + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, array_count, ub_desc->layout); + uniform_offset = _sg_align_u32(uniform_offset, u_align); + uniform_offset += u_size; num_uniforms++; + // with std140, arrays are only allowed for FLOAT4, INT4, MAT4 + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + if (array_count > 1) { + _SG_VALIDATE((u_desc->type == SG_UNIFORMTYPE_FLOAT4) || (u_desc->type == SG_UNIFORMTYPE_INT4) || (u_desc->type == SG_UNIFORMTYPE_MAT4), VALIDATE_SHADERDESC_UB_STD140_ARRAY_TYPE); + } + } } else { uniforms_continuous = false; } } - #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - SOKOL_VALIDATE((size_t)uniform_offset == ub_desc->size, _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH); - SOKOL_VALIDATE(num_uniforms > 0, _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS); + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + uniform_offset = _sg_align_u32(uniform_offset, 16); + } + _SG_VALIDATE((size_t)uniform_offset == ub_desc->size, VALIDATE_SHADERDESC_UB_SIZE_MISMATCH); + _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_NO_UB_MEMBERS); #endif } else { @@ -13445,9 +14861,9 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; if (img_desc->image_type != _SG_IMAGETYPE_DEFAULT) { - SOKOL_VALIDATE(images_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS); + _SG_VALIDATE(images_continuous, VALIDATE_SHADERDESC_NO_CONT_IMGS); #if defined(SOKOL_GLES2) - SOKOL_VALIDATE(0 != img_desc->name, _SG_VALIDATE_SHADERDESC_IMG_NAME); + _SG_VALIDATE(0 != img_desc->name, VALIDATE_SHADERDESC_IMG_NAME); #endif } else { @@ -13455,7 +14871,7 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { } } } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13464,23 +14880,26 @@ _SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { _SOKOL_UNUSED(desc); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(desc); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); - SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); - SOKOL_VALIDATE(desc->shader.id != SG_INVALID_ID, _SG_VALIDATE_PIPELINEDESC_SHADER); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->shader.id != SG_INVALID_ID, VALIDATE_PIPELINEDESC_SHADER); for (int buf_index = 0; buf_index < SG_MAX_SHADERSTAGE_BUFFERS; buf_index++) { const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[buf_index]; if (l_desc->stride == 0) { continue; } - SOKOL_VALIDATE((l_desc->stride & 3) == 0, _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); + _SG_VALIDATE((l_desc->stride & 3) == 0, VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); } - SOKOL_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); + _SG_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, VALIDATE_PIPELINEDESC_NO_ATTRS); const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); - SOKOL_VALIDATE(0 != shd, _SG_VALIDATE_PIPELINEDESC_SHADER); + _SG_VALIDATE(0 != shd, VALIDATE_PIPELINEDESC_SHADER); if (shd) { - SOKOL_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PIPELINEDESC_SHADER); + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PIPELINEDESC_SHADER); bool attrs_cont = true; for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; @@ -13488,18 +14907,18 @@ _SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { attrs_cont = false; continue; } - SOKOL_VALIDATE(attrs_cont, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); + _SG_VALIDATE(attrs_cont, VALIDATE_PIPELINEDESC_NO_ATTRS); SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); #if defined(SOKOL_GLES2) /* on GLES2, vertex attribute names must be provided */ - SOKOL_VALIDATE(!_sg_strempty(&shd->gl.attrs[attr_index].name), _SG_VALIDATE_PIPELINEDESC_ATTR_NAME); + _SG_VALIDATE(!_sg_strempty(&shd->gl.attrs[attr_index].name), VALIDATE_PIPELINEDESC_ATTR_NAME); #elif defined(SOKOL_D3D11) /* on D3D11, semantic names (and semantic indices) must be provided */ - SOKOL_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); + _SG_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); #endif } } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13508,68 +14927,71 @@ _SOKOL_PRIVATE bool _sg_validate_pass_desc(const sg_pass_desc* desc) { _SOKOL_UNUSED(desc); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(desc); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); - SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PASSDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PASSDESC_CANARY); bool atts_cont = true; int width = -1, height = -1, sample_count = -1; for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { const sg_pass_attachment_desc* att = &desc->color_attachments[att_index]; if (att->image.id == SG_INVALID_ID) { - SOKOL_VALIDATE(att_index > 0, _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS); + _SG_VALIDATE(att_index > 0, VALIDATE_PASSDESC_NO_COLOR_ATTS); atts_cont = false; continue; } - SOKOL_VALIDATE(atts_cont, _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS); + _SG_VALIDATE(atts_cont, VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS); const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); SOKOL_ASSERT(img); - SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); - SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PASSDESC_IMAGE); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_PASSDESC_MIPLEVEL); if (img->cmn.type == SG_IMAGETYPE_CUBE) { - SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); + _SG_VALIDATE(att->slice < 6, VALIDATE_PASSDESC_FACE); } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { - SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_LAYER); } else if (img->cmn.type == SG_IMAGETYPE_3D) { - SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_SLICE); } - SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); + _SG_VALIDATE(img->cmn.render_target, VALIDATE_PASSDESC_IMAGE_NO_RT); if (att_index == 0) { width = img->cmn.width >> att->mip_level; height = img->cmn.height >> att->mip_level; sample_count = img->cmn.sample_count; } else { - SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); - SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); - SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); + _SG_VALIDATE(width == img->cmn.width >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); + _SG_VALIDATE(height == img->cmn.height >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); + _SG_VALIDATE(sample_count == img->cmn.sample_count, VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); } - SOKOL_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT); + _SG_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT); } if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { const sg_pass_attachment_desc* att = &desc->depth_stencil_attachment; const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); SOKOL_ASSERT(img); - SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); - SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PASSDESC_IMAGE); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_PASSDESC_MIPLEVEL); if (img->cmn.type == SG_IMAGETYPE_CUBE) { - SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); + _SG_VALIDATE(att->slice < 6, VALIDATE_PASSDESC_FACE); } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { - SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_LAYER); } else if (img->cmn.type == SG_IMAGETYPE_3D) { - SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_SLICE); } - SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); - SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); - SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); - SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); - SOKOL_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT); + _SG_VALIDATE(img->cmn.render_target, VALIDATE_PASSDESC_IMAGE_NO_RT); + _SG_VALIDATE(width == img->cmn.width >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); + _SG_VALIDATE(height == img->cmn.height >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); + _SG_VALIDATE(sample_count == img->cmn.sample_count, VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); + _SG_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT); } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13578,24 +15000,27 @@ _SOKOL_PRIVATE bool _sg_validate_begin_pass(_sg_pass_t* pass) { _SOKOL_UNUSED(pass); return true; #else - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(pass->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_PASS); + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(pass->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_PASS); for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { const _sg_pass_attachment_t* att = &pass->cmn.color_atts[i]; const _sg_image_t* img = _sg_pass_color_image(pass, i); if (img) { - SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); - SOKOL_VALIDATE(img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_IMAGE); + _SG_VALIDATE(img->slot.id == att->image_id.id, VALIDATE_BEGINPASS_IMAGE); } } const _sg_image_t* ds_img = _sg_pass_ds_image(pass); if (ds_img) { const _sg_pass_attachment_t* att = &pass->cmn.ds_att; - SOKOL_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); - SOKOL_VALIDATE(ds_img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); + _SG_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_IMAGE); + _SG_VALIDATE(ds_img->slot.id == att->image_id.id, VALIDATE_BEGINPASS_IMAGE); } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13604,45 +15029,48 @@ _SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) { _SOKOL_UNUSED(pip_id); return true; #else - SOKOL_VALIDATE_BEGIN(); + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); /* the pipeline object must be alive and valid */ - SOKOL_VALIDATE(pip_id.id != SG_INVALID_ID, _SG_VALIDATE_APIP_PIPELINE_VALID_ID); + _SG_VALIDATE(pip_id.id != SG_INVALID_ID, VALIDATE_APIP_PIPELINE_VALID_ID); const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); - SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_APIP_PIPELINE_EXISTS); + _SG_VALIDATE(pip != 0, VALIDATE_APIP_PIPELINE_EXISTS); if (!pip) { - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); } - SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_PIPELINE_VALID); + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_VALID); /* the pipeline's shader must be alive and valid */ SOKOL_ASSERT(pip->shader); - SOKOL_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, _SG_VALIDATE_APIP_SHADER_EXISTS); - SOKOL_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_SHADER_VALID); + _SG_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, VALIDATE_APIP_SHADER_EXISTS); + _SG_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_SHADER_VALID); /* check that pipeline attributes match current pass attributes */ const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, _sg.cur_pass.id); if (pass) { /* an offscreen pass */ - SOKOL_VALIDATE(pip->cmn.color_attachment_count == pass->cmn.num_color_atts, _SG_VALIDATE_APIP_ATT_COUNT); - for (int i = 0; i < pip->cmn.color_attachment_count; i++) { + _SG_VALIDATE(pip->cmn.color_count == pass->cmn.num_color_atts, VALIDATE_APIP_ATT_COUNT); + for (int i = 0; i < pip->cmn.color_count; i++) { const _sg_image_t* att_img = _sg_pass_color_image(pass, i); - SOKOL_VALIDATE(pip->cmn.color_formats[i] == att_img->cmn.pixel_format, _SG_VALIDATE_APIP_COLOR_FORMAT); - SOKOL_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); + _SG_VALIDATE(pip->cmn.colors[i].pixel_format == att_img->cmn.pixel_format, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, VALIDATE_APIP_SAMPLE_COUNT); } const _sg_image_t* att_dsimg = _sg_pass_ds_image(pass); if (att_dsimg) { - SOKOL_VALIDATE(pip->cmn.depth_format == att_dsimg->cmn.pixel_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == att_dsimg->cmn.pixel_format, VALIDATE_APIP_DEPTH_FORMAT); } else { - SOKOL_VALIDATE(pip->cmn.depth_format == SG_PIXELFORMAT_NONE, _SG_VALIDATE_APIP_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTH_FORMAT); } } else { /* default pass */ - SOKOL_VALIDATE(pip->cmn.color_attachment_count == 1, _SG_VALIDATE_APIP_ATT_COUNT); - SOKOL_VALIDATE(pip->cmn.color_formats[0] == _sg.desc.context.color_format, _SG_VALIDATE_APIP_COLOR_FORMAT); - SOKOL_VALIDATE(pip->cmn.depth_format == _sg.desc.context.depth_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); - SOKOL_VALIDATE(pip->cmn.sample_count == _sg.desc.context.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); + _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_ATT_COUNT); + _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.desc.context.color_format, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.desc.context.depth_format, VALIDATE_APIP_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == _sg.desc.context.sample_count, VALIDATE_APIP_SAMPLE_COUNT); } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13651,52 +15079,55 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { _SOKOL_UNUSED(bindings); return true; #else - SOKOL_VALIDATE_BEGIN(); + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); /* a pipeline object must have been applied */ - SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_ABND_PIPELINE); + _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_ABND_PIPELINE); const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); - SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_ABND_PIPELINE_EXISTS); + _SG_VALIDATE(pip != 0, VALIDATE_ABND_PIPELINE_EXISTS); if (!pip) { - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); } - SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_ABND_PIPELINE_VALID); + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_VALID); SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); /* has expected vertex buffers, and vertex buffers still exist */ for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { - SOKOL_VALIDATE(pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); + _SG_VALIDATE(pip->cmn.vertex_layout_valid[i], VALIDATE_ABND_VBS); /* buffers in vertex-buffer-slots must be of type SG_BUFFERTYPE_VERTEXBUFFER */ const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); - SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_VB_EXISTS); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_VB_EXISTS); if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { - SOKOL_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_VB_TYPE); - SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_VB_OVERFLOW); + _SG_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, VALIDATE_ABND_VB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VB_OVERFLOW); } } else { /* vertex buffer provided in a slot which has no vertex layout in pipeline */ - SOKOL_VALIDATE(!pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); + _SG_VALIDATE(!pip->cmn.vertex_layout_valid[i], VALIDATE_ABND_VBS); } } /* index buffer expected or not, and index buffer still exists */ if (pip->cmn.index_type == SG_INDEXTYPE_NONE) { /* pipeline defines non-indexed rendering, but index buffer provided */ - SOKOL_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, _SG_VALIDATE_ABND_IB); + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_IB); } else { /* pipeline defines indexed rendering, but no index buffer provided */ - SOKOL_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, _SG_VALIDATE_ABND_NO_IB); + _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_NO_IB); } if (bindings->index_buffer.id != SG_INVALID_ID) { /* buffer in index-buffer-slot must be of type SG_BUFFERTYPE_INDEXBUFFER */ const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); - SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_IB_EXISTS); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_IB_EXISTS); if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { - SOKOL_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_IB_TYPE); - SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_IB_OVERFLOW); + _SG_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, VALIDATE_ABND_IB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IB_OVERFLOW); } } @@ -13704,15 +15135,15 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS]; if (bindings->vs_images[i].id != SG_INVALID_ID) { - SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); + _SG_VALIDATE(i < stage->num_images, VALIDATE_ABND_VS_IMGS); const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->vs_images[i].id); - SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_VS_IMG_EXISTS); + _SG_VALIDATE(img != 0, VALIDATE_ABND_VS_IMG_EXISTS); if (img && img->slot.state == SG_RESOURCESTATE_VALID) { - SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_VS_IMG_TYPES); + _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_VS_IMG_TYPES); } } else { - SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); + _SG_VALIDATE(i >= stage->num_images, VALIDATE_ABND_VS_IMGS); } } @@ -13720,18 +15151,18 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS]; if (bindings->fs_images[i].id != SG_INVALID_ID) { - SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); + _SG_VALIDATE(i < stage->num_images, VALIDATE_ABND_FS_IMGS); const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->fs_images[i].id); - SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_FS_IMG_EXISTS); + _SG_VALIDATE(img != 0, VALIDATE_ABND_FS_IMG_EXISTS); if (img && img->slot.state == SG_RESOURCESTATE_VALID) { - SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_FS_IMG_TYPES); + _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_FS_IMG_TYPES); } } else { - SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); + _SG_VALIDATE(i >= stage->num_images, VALIDATE_ABND_FS_IMGS); } } - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13742,22 +15173,25 @@ _SOKOL_PRIVATE bool _sg_validate_apply_uniforms(sg_shader_stage stage_index, int _SOKOL_UNUSED(data); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT((stage_index == SG_SHADERSTAGE_VS) || (stage_index == SG_SHADERSTAGE_FS)); SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_AUB_NO_PIPELINE); + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_AUB_NO_PIPELINE); const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); SOKOL_ASSERT(pip && (pip->slot.id == _sg.cur_pipeline.id)); SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); /* check that there is a uniform block at 'stage' and 'ub_index' */ const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index]; - SOKOL_VALIDATE(ub_index < stage->num_uniform_blocks, _SG_VALIDATE_AUB_NO_UB_AT_SLOT); + _SG_VALIDATE(ub_index < stage->num_uniform_blocks, VALIDATE_AUB_NO_UB_AT_SLOT); /* check that the provided data size doesn't exceed the uniform block size */ - SOKOL_VALIDATE(data->size <= stage->uniform_blocks[ub_index].size, _SG_VALIDATE_AUB_SIZE); + _SG_VALIDATE(data->size == stage->uniform_blocks[ub_index].size, VALIDATE_AUB_SIZE); - return SOKOL_VALIDATE_END(); + return _sg_validate_end(); #endif } @@ -13767,13 +15201,16 @@ _SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg _SOKOL_UNUSED(data); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(buf && data && data->ptr); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDATEBUF_USAGE); - SOKOL_VALIDATE(buf->cmn.size >= (int)data->size, _SG_VALIDATE_UPDATEBUF_SIZE); - SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_ONCE); - SOKOL_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_APPEND); - return SOKOL_VALIDATE_END(); + _sg_validate_begin(); + _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDATEBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (int)data->size, VALIDATE_UPDATEBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_ONCE); + _SG_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_APPEND); + return _sg_validate_end(); #endif } @@ -13783,12 +15220,15 @@ _SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg _SOKOL_UNUSED(data); return true; #else + if (_sg.desc.disable_validation) { + return true; + } SOKOL_ASSERT(buf && data && data->ptr); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_APPENDBUF_USAGE); - SOKOL_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), _SG_VALIDATE_APPENDBUF_SIZE); - SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_APPENDBUF_UPDATE); - return SOKOL_VALIDATE_END(); + _sg_validate_begin(); + _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_APPENDBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), VALIDATE_APPENDBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_APPENDBUF_UPDATE); + return _sg_validate_end(); #endif } @@ -13798,28 +15238,31 @@ _SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_i _SOKOL_UNUSED(data); return true; #else - SOKOL_ASSERT(img && data); - SOKOL_VALIDATE_BEGIN(); - SOKOL_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDIMG_USAGE); - SOKOL_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, _SG_VALIDATE_UPDIMG_ONCE); - SOKOL_VALIDATE(!_sg_is_compressed_pixel_format(img->cmn.pixel_format), _SG_VALIDATE_UPDIMG_COMPRESSED); - const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1; - const int num_mips = img->cmn.num_mipmaps; - for (int face_index = 0; face_index < num_faces; face_index++) { - for (int mip_index = 0; mip_index < num_mips; mip_index++) { - SOKOL_VALIDATE(0 != data->subimage[face_index][mip_index].ptr, _SG_VALIDATE_UPDIMG_NOTENOUGHDATA); - const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); - const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); - const int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); - const int expected_size = bytes_per_slice * img->cmn.num_slices; - SOKOL_VALIDATE(data->subimage[face_index][mip_index].size <= (size_t)expected_size, _SG_VALIDATE_UPDIMG_SIZE); - } + if (_sg.desc.disable_validation) { + return true; } - return SOKOL_VALIDATE_END(); + SOKOL_ASSERT(img && data); + _sg_validate_begin(); + _SG_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDIMG_USAGE); + _SG_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, VALIDATE_UPDIMG_ONCE); + _sg_validate_image_data(data, + img->cmn.pixel_format, + img->cmn.width, + img->cmn.height, + (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1, + img->cmn.num_mipmaps, + img->cmn.num_slices); + return _sg_validate_end(); #endif } -/*== fill in desc default values =============================================*/ +// ██████ ███████ ███████ ██████ ██ ██ ██████ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ███████ ██ ██ ██ ██ ██████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██████ ██████ ██ ██ ██████ ███████ ███████ +// +// >>resources _SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) { sg_buffer_desc def = *desc; def.type = _sg_def(def.type, SG_BUFFERTYPE_VERTEXBUFFER); @@ -13882,6 +15325,7 @@ _SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* des if (0 == ub_desc->size) { break; } + ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE); for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; if (u_desc->type == SG_UNIFORMTYPE_INVALID) { @@ -13951,7 +15395,7 @@ _SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_des /* resolve vertex layout strides and offsets */ int auto_offset[SG_MAX_SHADERSTAGE_BUFFERS]; - memset(auto_offset, 0, sizeof(auto_offset)); + _sg_clear(auto_offset, sizeof(auto_offset)); bool use_auto_offset = true; for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { /* to use computed offsets, *all* attr offsets must be 0 */ @@ -13987,7 +15431,6 @@ _SOKOL_PRIVATE sg_pass_desc _sg_pass_desc_defaults(const sg_pass_desc* desc) { return def; } -/*== allocate/initialize resource private functions ==========================*/ _SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { sg_buffer res; int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool); @@ -13995,8 +15438,9 @@ _SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index); } else { - /* pool is exhausted */ res.id = SG_INVALID_ID; + _SG_ERROR(BUFFER_POOL_EXHAUSTED); + _SG_TRACE_NOARGS(err_buffer_pool_exhausted); } return res; } @@ -14008,8 +15452,9 @@ _SOKOL_PRIVATE sg_image _sg_alloc_image(void) { res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index); } else { - /* pool is exhausted */ res.id = SG_INVALID_ID; + _SG_ERROR(IMAGE_POOL_EXHAUSTED); + _SG_TRACE_NOARGS(err_image_pool_exhausted); } return res; } @@ -14021,8 +15466,9 @@ _SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) { res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index); } else { - /* pool is exhausted */ res.id = SG_INVALID_ID; + _SG_ERROR(SHADER_POOL_EXHAUSTED); + _SG_TRACE_NOARGS(err_shader_pool_exhausted); } return res; } @@ -14034,8 +15480,9 @@ _SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) { res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index); } else { - /* pool is exhausted */ res.id = SG_INVALID_ID; + _SG_ERROR(PIPELINE_POOL_EXHAUSTED); + _SG_TRACE_NOARGS(err_pipeline_pool_exhausted); } return res; } @@ -14047,56 +15494,46 @@ _SOKOL_PRIVATE sg_pass _sg_alloc_pass(void) { res.id = _sg_slot_alloc(&_sg.pools.pass_pool, &_sg.pools.passes[slot_index].slot, slot_index); } else { - /* pool is exhausted */ res.id = SG_INVALID_ID; + _SG_ERROR(PASS_POOL_EXHAUSTED); + _SG_TRACE_NOARGS(err_pass_pool_exhausted); } return res; } -_SOKOL_PRIVATE void _sg_dealloc_buffer(sg_buffer buf_id) { - SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); - _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); - SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_dealloc_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC) && (buf->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf->slot.id)); _sg_reset_slot(&buf->slot); - _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf_id.id)); } -_SOKOL_PRIVATE void _sg_dealloc_image(sg_image img_id) { - SOKOL_ASSERT(img_id.id != SG_INVALID_ID); - _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); - SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_dealloc_image(_sg_image_t* img) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC) && (img->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img->slot.id)); _sg_reset_slot(&img->slot); - _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img_id.id)); } -_SOKOL_PRIVATE void _sg_dealloc_shader(sg_shader shd_id) { - SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); - _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); - SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_dealloc_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC) && (shd->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd->slot.id)); _sg_reset_slot(&shd->slot); - _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd_id.id)); } -_SOKOL_PRIVATE void _sg_dealloc_pipeline(sg_pipeline pip_id) { - SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); - _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); - SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_dealloc_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC) && (pip->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip->slot.id)); _sg_reset_slot(&pip->slot); - _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip_id.id)); } -_SOKOL_PRIVATE void _sg_dealloc_pass(sg_pass pass_id) { - SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); - _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); - SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_dealloc_pass(_sg_pass_t* pass) { + SOKOL_ASSERT(pass && (pass->slot.state == SG_RESOURCESTATE_ALLOC) && (pass->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.pass_pool, _sg_slot_index(pass->slot.id)); _sg_reset_slot(&pass->slot); - _sg_pool_free_index(&_sg.pools.pass_pool, _sg_slot_index(pass_id.id)); } -_SOKOL_PRIVATE void _sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { - SOKOL_ASSERT(buf_id.id != SG_INVALID_ID && desc); - _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); - SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_init_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); buf->slot.ctx_id = _sg.active_context.id; if (_sg_validate_buffer_desc(desc)) { buf->slot.state = _sg_create_buffer(buf, desc); @@ -14107,10 +15544,9 @@ _SOKOL_PRIVATE void _sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED)); } -_SOKOL_PRIVATE void _sg_init_image(sg_image img_id, const sg_image_desc* desc) { - SOKOL_ASSERT(img_id.id != SG_INVALID_ID && desc); - _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); - SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_init_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); img->slot.ctx_id = _sg.active_context.id; if (_sg_validate_image_desc(desc)) { img->slot.state = _sg_create_image(img, desc); @@ -14121,10 +15557,9 @@ _SOKOL_PRIVATE void _sg_init_image(sg_image img_id, const sg_image_desc* desc) { SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED)); } -_SOKOL_PRIVATE void _sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { - SOKOL_ASSERT(shd_id.id != SG_INVALID_ID && desc); - _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); - SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_init_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); shd->slot.ctx_id = _sg.active_context.id; if (_sg_validate_shader_desc(desc)) { shd->slot.state = _sg_create_shader(shd, desc); @@ -14135,10 +15570,9 @@ _SOKOL_PRIVATE void _sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); } -_SOKOL_PRIVATE void _sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { - SOKOL_ASSERT(pip_id.id != SG_INVALID_ID && desc); - _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); - SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +_SOKOL_PRIVATE void _sg_init_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); pip->slot.ctx_id = _sg.active_context.id; if (_sg_validate_pipeline_desc(desc)) { _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); @@ -14155,10 +15589,9 @@ _SOKOL_PRIVATE void _sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED)); } -_SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { - SOKOL_ASSERT(pass_id.id != SG_INVALID_ID && desc); - _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +_SOKOL_PRIVATE void _sg_init_pass(_sg_pass_t* pass, const sg_pass_desc* desc) { SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); + SOKOL_ASSERT(desc); pass->slot.ctx_id = _sg.active_context.id; if (_sg_validate_pass_desc(desc)) { /* lookup pass attachment image pointers */ @@ -14166,8 +15599,10 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { if (desc->color_attachments[i].image.id) { att_imgs[i] = _sg_lookup_image(&_sg.pools, desc->color_attachments[i].image.id); - /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ - SOKOL_ASSERT(att_imgs[i] && att_imgs[i]->slot.state == SG_RESOURCESTATE_VALID); + if (!(att_imgs[i] && att_imgs[i]->slot.state == SG_RESOURCESTATE_VALID)) { + pass->slot.state = SG_RESOURCESTATE_FAILED; + return; + } } else { att_imgs[i] = 0; @@ -14176,8 +15611,10 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { const int ds_att_index = SG_MAX_COLOR_ATTACHMENTS; if (desc->depth_stencil_attachment.image.id) { att_imgs[ds_att_index] = _sg_lookup_image(&_sg.pools, desc->depth_stencil_attachment.image.id); - /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ - SOKOL_ASSERT(att_imgs[ds_att_index] && att_imgs[ds_att_index]->slot.state == SG_RESOURCESTATE_VALID); + if (!(att_imgs[ds_att_index] && att_imgs[ds_att_index]->slot.state == SG_RESOURCESTATE_VALID)) { + pass->slot.state = SG_RESOURCESTATE_FAILED; + return; + } } else { att_imgs[ds_att_index] = 0; @@ -14190,129 +15627,185 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID)||(pass->slot.state == SG_RESOURCESTATE_FAILED)); } -_SOKOL_PRIVATE bool _sg_uninit_buffer(sg_buffer buf_id) { - _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); - if (buf) { - if (buf->slot.ctx_id == _sg.active_context.id) { - _sg_destroy_buffer(buf); - _sg_reset_buffer(buf); - return true; - } - else { - SOKOL_LOG("_sg_uninit_buffer: active context mismatch (must be same as for creation)"); - _SG_TRACE_NOARGS(err_context_mismatch); - } +_SOKOL_PRIVATE void _sg_uninit_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED))); + if (buf->slot.ctx_id == _sg.active_context.id) { + _sg_discard_buffer(buf); + _sg_reset_buffer_to_alloc_state(buf); + } + else { + _SG_WARN(UNINIT_BUFFER_ACTIVE_CONTEXT_MISMATCH); + _SG_TRACE_NOARGS(err_context_mismatch); } - return false; } -_SOKOL_PRIVATE bool _sg_uninit_image(sg_image img_id) { - _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); - if (img) { - if (img->slot.ctx_id == _sg.active_context.id) { - _sg_destroy_image(img); - _sg_reset_image(img); - return true; - } - else { - SOKOL_LOG("_sg_uninit_image: active context mismatch (must be same as for creation)"); - _SG_TRACE_NOARGS(err_context_mismatch); - } +_SOKOL_PRIVATE void _sg_uninit_image(_sg_image_t* img) { + SOKOL_ASSERT(img && ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED))); + if (img->slot.ctx_id == _sg.active_context.id) { + _sg_discard_image(img); + _sg_reset_image_to_alloc_state(img); + } + else { + _SG_WARN(UNINIT_IMAGE_ACTIVE_CONTEXT_MISMATCH); + _SG_TRACE_NOARGS(err_context_mismatch); } - return false; } -_SOKOL_PRIVATE bool _sg_uninit_shader(sg_shader shd_id) { - _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); - if (shd) { - if (shd->slot.ctx_id == _sg.active_context.id) { - _sg_destroy_shader(shd); - _sg_reset_shader(shd); - return true; - } - else { - SOKOL_LOG("_sg_uninit_shader: active context mismatch (must be same as for creation)"); - _SG_TRACE_NOARGS(err_context_mismatch); +_SOKOL_PRIVATE void _sg_uninit_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED))); + if (shd->slot.ctx_id == _sg.active_context.id) { + _sg_discard_shader(shd); + _sg_reset_shader_to_alloc_state(shd); + } + else { + _SG_WARN(UNINIT_SHADER_ACTIVE_CONTEXT_MISMATCH); + _SG_TRACE_NOARGS(err_context_mismatch); + } +} + +_SOKOL_PRIVATE void _sg_uninit_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED))); + if (pip->slot.ctx_id == _sg.active_context.id) { + _sg_discard_pipeline(pip); + _sg_reset_pipeline_to_alloc_state(pip); + } + else { + _SG_WARN(UNINIT_PIPELINE_ACTIVE_CONTEXT_MISMATCH); + _SG_TRACE_NOARGS(err_context_mismatch); + } +} + +_SOKOL_PRIVATE void _sg_uninit_pass(_sg_pass_t* pass) { + SOKOL_ASSERT(pass && ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED))); + if (pass->slot.ctx_id == _sg.active_context.id) { + _sg_discard_pass(pass); + _sg_reset_pass_to_alloc_state(pass); + } + else { + _SG_WARN(UNINIT_PASS_ACTIVE_CONTEXT_MISMATCH); + _SG_TRACE_NOARGS(err_context_mismatch); + } +} + +_SOKOL_PRIVATE void _sg_setup_commit_listeners(const sg_desc* desc) { + SOKOL_ASSERT(desc->max_commit_listeners > 0); + SOKOL_ASSERT(0 == _sg.commit_listeners.items); + SOKOL_ASSERT(0 == _sg.commit_listeners.num); + SOKOL_ASSERT(0 == _sg.commit_listeners.upper); + _sg.commit_listeners.num = desc->max_commit_listeners; + const size_t size = (size_t)_sg.commit_listeners.num * sizeof(sg_commit_listener); + _sg.commit_listeners.items = (sg_commit_listener*)_sg_malloc_clear(size); +} + +_SOKOL_PRIVATE void _sg_discard_commit_listeners(void) { + SOKOL_ASSERT(0 != _sg.commit_listeners.items); + _sg_free(_sg.commit_listeners.items); + _sg.commit_listeners.items = 0; +} + +_SOKOL_PRIVATE void _sg_notify_commit_listeners(void) { + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* listener = &_sg.commit_listeners.items[i]; + if (listener->func) { + listener->func(listener->user_data); } } - return false; } -_SOKOL_PRIVATE bool _sg_uninit_pipeline(sg_pipeline pip_id) { - _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); - if (pip) { - if (pip->slot.ctx_id == _sg.active_context.id) { - _sg_destroy_pipeline(pip); - _sg_reset_pipeline(pip); - return true; +_SOKOL_PRIVATE bool _sg_add_commit_listener(const sg_commit_listener* new_listener) { + SOKOL_ASSERT(new_listener && new_listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + // first check if the listener hadn't been added already + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + if ((slot->func == new_listener->func) && (slot->user_data == new_listener->user_data)) { + _SG_ERROR(IDENTICAL_COMMIT_LISTENER); + return false; } - else { - SOKOL_LOG("_sg_uninit_pipeline: active context mismatch (must be same as for creation)"); - _SG_TRACE_NOARGS(err_context_mismatch); + } + // first try to plug a hole + sg_commit_listener* slot = 0; + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + if (_sg.commit_listeners.items[i].func == 0) { + slot = &_sg.commit_listeners.items[i]; + break; } } - return false; + if (!slot) { + // append to end + if (_sg.commit_listeners.upper < _sg.commit_listeners.num) { + slot = &_sg.commit_listeners.items[_sg.commit_listeners.upper++]; + } + } + if (!slot) { + _SG_ERROR(COMMIT_LISTENER_ARRAY_FULL); + return false; + } + *slot = *new_listener; + return true; } -_SOKOL_PRIVATE bool _sg_uninit_pass(sg_pass pass_id) { - _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); - if (pass) { - if (pass->slot.ctx_id == _sg.active_context.id) { - _sg_destroy_pass(pass); - _sg_reset_pass(pass); +_SOKOL_PRIVATE bool _sg_remove_commit_listener(const sg_commit_listener* listener) { + SOKOL_ASSERT(listener && listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + // both the function pointer and user data must match! + if ((slot->func == listener->func) && (slot->user_data == listener->user_data)) { + slot->func = 0; + slot->user_data = 0; + // NOTE: since _sg_add_commit_listener() already catches duplicates, + // we don't need to worry about them here return true; } - else { - SOKOL_LOG("_sg_uninit_pass: active context mismatch (must be same as for creation)"); - _SG_TRACE_NOARGS(err_context_mismatch); - } } return false; } -/*== PUBLIC API FUNCTIONS ====================================================*/ - -#if defined(SOKOL_METAL) - // this is ARC compatible - #if defined(__cplusplus) - #define _SG_CLEAR(type, item) { item = (type) { }; } - #else - #define _SG_CLEAR(type, item) { item = (type) { 0 }; } - #endif -#else - #define _SG_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } -#endif - -SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { - SOKOL_ASSERT(desc); - SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); - _SG_CLEAR(_sg_state_t, _sg); - _sg.desc = *desc; - - /* replace zero-init items with their default values +_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) { + /* NOTE: on WebGPU, the default color pixel format MUST be provided, cannot be a default compile-time constant. */ + sg_desc res = *desc; #if defined(SOKOL_WGPU) - SOKOL_ASSERT(SG_PIXELFORMAT_NONE != _sg.desc.context.color_format); + SOKOL_ASSERT(SG_PIXELFORMAT_NONE != res.context.color_format); #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) - _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_BGRA8); + res.context.color_format = _sg_def(res.context.color_format, SG_PIXELFORMAT_BGRA8); #else - _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_RGBA8); - #endif - _sg.desc.context.depth_format = _sg_def(_sg.desc.context.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); - _sg.desc.context.sample_count = _sg_def(_sg.desc.context.sample_count, 1); - _sg.desc.buffer_pool_size = _sg_def(_sg.desc.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); - _sg.desc.image_pool_size = _sg_def(_sg.desc.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); - _sg.desc.shader_pool_size = _sg_def(_sg.desc.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); - _sg.desc.pipeline_pool_size = _sg_def(_sg.desc.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); - _sg.desc.pass_pool_size = _sg_def(_sg.desc.pass_pool_size, _SG_DEFAULT_PASS_POOL_SIZE); - _sg.desc.context_pool_size = _sg_def(_sg.desc.context_pool_size, _SG_DEFAULT_CONTEXT_POOL_SIZE); - _sg.desc.uniform_buffer_size = _sg_def(_sg.desc.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); - _sg.desc.staging_buffer_size = _sg_def(_sg.desc.staging_buffer_size, _SG_DEFAULT_STAGING_SIZE); - _sg.desc.sampler_cache_size = _sg_def(_sg.desc.sampler_cache_size, _SG_DEFAULT_SAMPLER_CACHE_CAPACITY); + res.context.color_format = _sg_def(res.context.color_format, SG_PIXELFORMAT_RGBA8); + #endif + res.context.depth_format = _sg_def(res.context.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); + res.context.sample_count = _sg_def(res.context.sample_count, 1); + res.buffer_pool_size = _sg_def(res.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); + res.image_pool_size = _sg_def(res.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); + res.shader_pool_size = _sg_def(res.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); + res.pipeline_pool_size = _sg_def(res.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); + res.pass_pool_size = _sg_def(res.pass_pool_size, _SG_DEFAULT_PASS_POOL_SIZE); + res.context_pool_size = _sg_def(res.context_pool_size, _SG_DEFAULT_CONTEXT_POOL_SIZE); + res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); + res.staging_buffer_size = _sg_def(res.staging_buffer_size, _SG_DEFAULT_STAGING_SIZE); + res.sampler_cache_size = _sg_def(res.sampler_cache_size, _SG_DEFAULT_SAMPLER_CACHE_CAPACITY); + res.max_commit_listeners = _sg_def(res.max_commit_listeners, _SG_DEFAULT_MAX_COMMIT_LISTENERS); + return res; +} +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); + SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); + _sg.desc = _sg_desc_defaults(desc); _sg_setup_pools(&_sg.pools, &_sg.desc); + _sg_setup_commit_listeners(&_sg.desc); _sg.frame_index = 1; _sg_setup_backend(&_sg.desc); _sg.valid = true; @@ -14327,13 +15820,14 @@ SOKOL_API_IMPL void sg_shutdown(void) { if (_sg.active_context.id != SG_INVALID_ID) { _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, _sg.active_context.id); if (ctx) { - _sg_destroy_all_resources(&_sg.pools, _sg.active_context.id); - _sg_destroy_context(ctx); + _sg_discard_all_resources(&_sg.pools, _sg.active_context.id); + _sg_discard_context(ctx); } } _sg_discard_backend(); + _sg_discard_commit_listeners(); _sg_discard_pools(&_sg.pools); - _sg.valid = false; + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); } SOKOL_API_IMPL bool sg_isvalid(void) { @@ -14388,11 +15882,11 @@ SOKOL_API_IMPL sg_context sg_setup_context(void) { SOKOL_API_IMPL void sg_discard_context(sg_context ctx_id) { SOKOL_ASSERT(_sg.valid); - _sg_destroy_all_resources(&_sg.pools, ctx_id.id); + _sg_discard_all_resources(&_sg.pools, ctx_id.id); _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, ctx_id.id); if (ctx) { - _sg_destroy_context(ctx); - _sg_reset_context(ctx); + _sg_discard_context(ctx); + _sg_reset_context_to_alloc_state(ctx); _sg_reset_slot(&ctx->slot); _sg_pool_free_index(&_sg.pools.context_pool, _sg_slot_index(ctx_id.id)); } @@ -14417,7 +15911,7 @@ SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace _sg.hooks = *trace_hooks; #else static sg_trace_hooks old_hooks; - SOKOL_LOG("sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined!"); + _SG_WARN(TRACE_HOOKS_NOT_ENABLED); #endif return old_hooks; } @@ -14459,152 +15953,302 @@ SOKOL_API_IMPL sg_pass sg_alloc_pass(void) { SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) { SOKOL_ASSERT(_sg.valid); - _sg_dealloc_buffer(buf_id); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + } + else { + _SG_ERROR(DEALLOC_BUFFER_INVALID_STATE); + } + } _SG_TRACE_ARGS(dealloc_buffer, buf_id); } SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) { SOKOL_ASSERT(_sg.valid); - _sg_dealloc_image(img_id); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + } + else { + _SG_ERROR(DEALLOC_IMAGE_INVALID_STATE); + } + } _SG_TRACE_ARGS(dealloc_image, img_id); } SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) { SOKOL_ASSERT(_sg.valid); - _sg_dealloc_shader(shd_id); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + } + else { + _SG_ERROR(DEALLOC_SHADER_INVALID_STATE); + } + } _SG_TRACE_ARGS(dealloc_shader, shd_id); } SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) { SOKOL_ASSERT(_sg.valid); - _sg_dealloc_pipeline(pip_id); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + } + else { + _SG_ERROR(DEALLOC_PIPELINE_INVALID_STATE); + } + } _SG_TRACE_ARGS(dealloc_pipeline, pip_id); } SOKOL_API_IMPL void sg_dealloc_pass(sg_pass pass_id) { SOKOL_ASSERT(_sg.valid); - _sg_dealloc_pass(pass_id); + _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pass(pass); + } + else { + _SG_ERROR(DEALLOC_PASS_INVALID_STATE); + } + } _SG_TRACE_ARGS(dealloc_pass, pass_id); } SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { SOKOL_ASSERT(_sg.valid); sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); - _sg_init_buffer(buf_id, &desc_def); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } + else { + _SG_ERROR(INIT_BUFFER_INVALID_STATE); + } + } _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def); } SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) { SOKOL_ASSERT(_sg.valid); sg_image_desc desc_def = _sg_image_desc_defaults(desc); - _sg_init_image(img_id, &desc_def); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } + else { + _SG_ERROR(INIT_IMAGE_INVALID_STATE); + } + } _SG_TRACE_ARGS(init_image, img_id, &desc_def); } SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { SOKOL_ASSERT(_sg.valid); sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); - _sg_init_shader(shd_id, &desc_def); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } + else { + _SG_ERROR(INIT_SHADER_INVALID_STATE); + } + } _SG_TRACE_ARGS(init_shader, shd_id, &desc_def); } SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { SOKOL_ASSERT(_sg.valid); sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); - _sg_init_pipeline(pip_id, &desc_def); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } + else { + _SG_ERROR(INIT_PIPELINE_INVALID_STATE); + } + } _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def); } SOKOL_API_IMPL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { SOKOL_ASSERT(_sg.valid); sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); - _sg_init_pass(pass_id, &desc_def); + _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_pass(pass, &desc_def); + SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)); + } + else { + _SG_ERROR(INIT_PASS_INVALID_STATE); + } + } _SG_TRACE_ARGS(init_pass, pass_id, &desc_def); } -SOKOL_API_IMPL bool sg_uninit_buffer(sg_buffer buf_id) { +SOKOL_API_IMPL void sg_uninit_buffer(sg_buffer buf_id) { SOKOL_ASSERT(_sg.valid); - bool res = _sg_uninit_buffer(buf_id); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } + else { + _SG_ERROR(UNINIT_BUFFER_INVALID_STATE); + } + } _SG_TRACE_ARGS(uninit_buffer, buf_id); - return res; } -SOKOL_API_IMPL bool sg_uninit_image(sg_image img_id) { +SOKOL_API_IMPL void sg_uninit_image(sg_image img_id) { SOKOL_ASSERT(_sg.valid); - bool res = _sg_uninit_image(img_id); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } + else { + _SG_ERROR(UNINIT_IMAGE_INVALID_STATE); + } + } _SG_TRACE_ARGS(uninit_image, img_id); - return res; } -SOKOL_API_IMPL bool sg_uninit_shader(sg_shader shd_id) { +SOKOL_API_IMPL void sg_uninit_shader(sg_shader shd_id) { SOKOL_ASSERT(_sg.valid); - bool res = _sg_uninit_shader(shd_id); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } + else { + _SG_ERROR(UNINIT_SHADER_INVALID_STATE); + } + } _SG_TRACE_ARGS(uninit_shader, shd_id); - return res; } -SOKOL_API_IMPL bool sg_uninit_pipeline(sg_pipeline pip_id) { +SOKOL_API_IMPL void sg_uninit_pipeline(sg_pipeline pip_id) { SOKOL_ASSERT(_sg.valid); - bool res = _sg_uninit_pipeline(pip_id); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } + else { + _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE); + } + } _SG_TRACE_ARGS(uninit_pipeline, pip_id); - return res; } -SOKOL_API_IMPL bool sg_uninit_pass(sg_pass pass_id) { +SOKOL_API_IMPL void sg_uninit_pass(sg_pass pass_id) { SOKOL_ASSERT(_sg.valid); - bool res = _sg_uninit_pass(pass_id); + _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + if ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pass(pass); + SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_ALLOC); + } + else { + _SG_ERROR(UNINIT_PASS_INVALID_STATE); + } + } _SG_TRACE_ARGS(uninit_pass, pass_id); - return res; } /*-- set allocated resource to failed state ----------------------------------*/ SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) { SOKOL_ASSERT(_sg.valid); - SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); - SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); - buf->slot.ctx_id = _sg.active_context.id; - buf->slot.state = SG_RESOURCESTATE_FAILED; + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + buf->slot.ctx_id = _sg.active_context.id; + buf->slot.state = SG_RESOURCESTATE_FAILED; + } + else { + _SG_ERROR(FAIL_BUFFER_INVALID_STATE); + } + } _SG_TRACE_ARGS(fail_buffer, buf_id); } SOKOL_API_IMPL void sg_fail_image(sg_image img_id) { SOKOL_ASSERT(_sg.valid); - SOKOL_ASSERT(img_id.id != SG_INVALID_ID); _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); - SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); - img->slot.ctx_id = _sg.active_context.id; - img->slot.state = SG_RESOURCESTATE_FAILED; + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + img->slot.ctx_id = _sg.active_context.id; + img->slot.state = SG_RESOURCESTATE_FAILED; + } + else { + _SG_ERROR(FAIL_IMAGE_INVALID_STATE); + } + } _SG_TRACE_ARGS(fail_image, img_id); } SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) { SOKOL_ASSERT(_sg.valid); - SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); - SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); - shd->slot.ctx_id = _sg.active_context.id; - shd->slot.state = SG_RESOURCESTATE_FAILED; + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + shd->slot.ctx_id = _sg.active_context.id; + shd->slot.state = SG_RESOURCESTATE_FAILED; + } + else { + _SG_ERROR(FAIL_SHADER_INVALID_STATE); + } + } _SG_TRACE_ARGS(fail_shader, shd_id); } SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) { SOKOL_ASSERT(_sg.valid); - SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); - SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); - pip->slot.ctx_id = _sg.active_context.id; - pip->slot.state = SG_RESOURCESTATE_FAILED; + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + pip->slot.ctx_id = _sg.active_context.id; + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + else { + _SG_ERROR(FAIL_PIPELINE_INVALID_STATE); + } + } _SG_TRACE_ARGS(fail_pipeline, pip_id); } SOKOL_API_IMPL void sg_fail_pass(sg_pass pass_id) { SOKOL_ASSERT(_sg.valid); - SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); - SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); - pass->slot.ctx_id = _sg.active_context.id; - pass->slot.state = SG_RESOURCESTATE_FAILED; + if (pass) { + if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { + pass->slot.ctx_id = _sg.active_context.id; + pass->slot.state = SG_RESOURCESTATE_FAILED; + } + else { + _SG_ERROR(FAIL_PASS_INVALID_STATE); + } + } _SG_TRACE_ARGS(fail_pass, pass_id); } @@ -14651,11 +16295,10 @@ SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) { sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); sg_buffer buf_id = _sg_alloc_buffer(); if (buf_id.id != SG_INVALID_ID) { - _sg_init_buffer(buf_id, &desc_def); - } - else { - SOKOL_LOG("buffer pool exhausted!"); - _SG_TRACE_NOARGS(err_buffer_pool_exhausted); + _sg_buffer_t* buf = _sg_buffer_at(&_sg.pools, buf_id.id); + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); } _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id); return buf_id; @@ -14667,11 +16310,10 @@ SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) { sg_image_desc desc_def = _sg_image_desc_defaults(desc); sg_image img_id = _sg_alloc_image(); if (img_id.id != SG_INVALID_ID) { - _sg_init_image(img_id, &desc_def); - } - else { - SOKOL_LOG("image pool exhausted!"); - _SG_TRACE_NOARGS(err_image_pool_exhausted); + _sg_image_t* img = _sg_image_at(&_sg.pools, img_id.id); + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); } _SG_TRACE_ARGS(make_image, &desc_def, img_id); return img_id; @@ -14683,11 +16325,10 @@ SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) { sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); sg_shader shd_id = _sg_alloc_shader(); if (shd_id.id != SG_INVALID_ID) { - _sg_init_shader(shd_id, &desc_def); - } - else { - SOKOL_LOG("shader pool exhausted!"); - _SG_TRACE_NOARGS(err_shader_pool_exhausted); + _sg_shader_t* shd = _sg_shader_at(&_sg.pools, shd_id.id); + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); } _SG_TRACE_ARGS(make_shader, &desc_def, shd_id); return shd_id; @@ -14699,11 +16340,10 @@ SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) { sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); sg_pipeline pip_id = _sg_alloc_pipeline(); if (pip_id.id != SG_INVALID_ID) { - _sg_init_pipeline(pip_id, &desc_def); - } - else { - SOKOL_LOG("pipeline pool exhausted!"); - _SG_TRACE_NOARGS(err_pipeline_pool_exhausted); + _sg_pipeline_t* pip = _sg_pipeline_at(&_sg.pools, pip_id.id); + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); } _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id); return pip_id; @@ -14715,11 +16355,10 @@ SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) { sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); sg_pass pass_id = _sg_alloc_pass(); if (pass_id.id != SG_INVALID_ID) { - _sg_init_pass(pass_id, &desc_def); - } - else { - SOKOL_LOG("pass pool exhausted!"); - _SG_TRACE_NOARGS(err_pass_pool_exhausted); + _sg_pass_t* pass = _sg_pass_at(&_sg.pools, pass_id.id); + SOKOL_ASSERT(pass && (pass->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_pass(pass, &desc_def); + SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)); } _SG_TRACE_ARGS(make_pass, &desc_def, pass_id); return pass_id; @@ -14729,40 +16368,80 @@ SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) { SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) { SOKOL_ASSERT(_sg.valid); _SG_TRACE_ARGS(destroy_buffer, buf_id); - if (_sg_uninit_buffer(buf_id)) { - _sg_dealloc_buffer(buf_id); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_INITIAL); + } } } SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) { SOKOL_ASSERT(_sg.valid); _SG_TRACE_ARGS(destroy_image, img_id); - if (_sg_uninit_image(img_id)) { - _sg_dealloc_image(img_id); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_INITIAL); + } } } SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) { SOKOL_ASSERT(_sg.valid); _SG_TRACE_ARGS(destroy_shader, shd_id); - if (_sg_uninit_shader(shd_id)) { - _sg_dealloc_shader(shd_id); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_INITIAL); + } } } SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) { SOKOL_ASSERT(_sg.valid); _SG_TRACE_ARGS(destroy_pipeline, pip_id); - if (_sg_uninit_pipeline(pip_id)) { - _sg_dealloc_pipeline(pip_id); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_INITIAL); + } } } SOKOL_API_IMPL void sg_destroy_pass(sg_pass pass_id) { SOKOL_ASSERT(_sg.valid); _SG_TRACE_ARGS(destroy_pass, pass_id); - if (_sg_uninit_pass(pass_id)) { - _sg_dealloc_pass(pass_id); + _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + if ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pass(pass); + SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pass(pass); + SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_INITIAL); + } } } @@ -14942,6 +16621,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); @@ -14954,7 +16634,7 @@ SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instance SOKOL_ASSERT(num_instances >= 0); #if defined(SOKOL_DEBUG) if (!_sg.bindings_valid) { - SOKOL_LOG("attempting to draw without resource bindings"); + _SG_WARN(DRAW_WITHOUT_BINDINGS); } #endif if (!_sg.pass_valid) { @@ -14996,6 +16676,7 @@ SOKOL_API_IMPL void sg_end_pass(void) { SOKOL_API_IMPL void sg_commit(void) { SOKOL_ASSERT(_sg.valid); _sg_commit(); + _sg_notify_commit_listeners(); _SG_TRACE_NOARGS(commit); _sg.frame_index++; } @@ -15067,6 +16748,23 @@ SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) { return result; } +SOKOL_API_IMPL bool sg_query_buffer_will_overflow(sg_buffer buf_id, size_t size) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + bool result = false; + if (buf) { + int append_pos = buf->cmn.append_pos; + /* rewind append cursor in a new frame */ + if (buf->cmn.append_frame_index != _sg.frame_index) { + append_pos = 0; + } + if ((append_pos + _sg_roundup((int)size, 4)) > buf->cmn.size) { + result = true; + } + } + return result; +} + SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) { SOKOL_ASSERT(_sg.valid); _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); @@ -15092,10 +16790,20 @@ SOKOL_API_IMPL void sg_pop_debug_group(void) { _SG_TRACE_NOARGS(pop_debug_group); } +SOKOL_API_IMPL bool sg_add_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_add_commit_listener(&listener); +} + +SOKOL_API_IMPL bool sg_remove_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_remove_commit_listener(&listener); +} + SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { SOKOL_ASSERT(_sg.valid); sg_buffer_info info; - memset(&info, 0, sizeof(info)); + _sg_clear(&info, sizeof(info)); const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); if (buf) { info.slot.state = buf->slot.state; @@ -15119,12 +16827,13 @@ SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { SOKOL_ASSERT(_sg.valid); sg_image_info info; - memset(&info, 0, sizeof(info)); + _sg_clear(&info, sizeof(info)); const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); if (img) { 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; @@ -15132,8 +16841,6 @@ SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { info.num_slots = img->cmn.num_slots; info.active_slot = img->cmn.active_slot; #endif - info.width = img->cmn.width; - info.height = img->cmn.height; } return info; } @@ -15141,7 +16848,7 @@ SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { SOKOL_ASSERT(_sg.valid); sg_shader_info info; - memset(&info, 0, sizeof(info)); + _sg_clear(&info, sizeof(info)); const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); if (shd) { info.slot.state = shd->slot.state; @@ -15154,7 +16861,7 @@ SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { SOKOL_ASSERT(_sg.valid); sg_pipeline_info info; - memset(&info, 0, sizeof(info)); + _sg_clear(&info, sizeof(info)); const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); if (pip) { info.slot.state = pip->slot.state; @@ -15167,7 +16874,7 @@ SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { SOKOL_API_IMPL sg_pass_info sg_query_pass_info(sg_pass pass_id) { SOKOL_ASSERT(_sg.valid); sg_pass_info info; - memset(&info, 0, sizeof(info)); + _sg_clear(&info, sizeof(info)); const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); if (pass) { info.slot.state = pass->slot.state; @@ -15177,6 +16884,115 @@ SOKOL_API_IMPL sg_pass_info sg_query_pass_info(sg_pass pass_id) { return info; } +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + desc.size = (size_t)buf->cmn.size; + desc.type = buf->cmn.type; + desc.usage = buf->cmn.usage; + } + return desc; +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_desc(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + desc.type = img->cmn.type; + desc.render_target = img->cmn.render_target; + desc.width = img->cmn.width; + desc.height = img->cmn.height; + desc.num_slices = img->cmn.num_slices; + desc.num_mipmaps = img->cmn.num_mipmaps; + desc.usage = img->cmn.usage; + desc.pixel_format = img->cmn.pixel_format; + desc.sample_count = img->cmn.sample_count; + desc.min_filter = img->cmn.min_filter; + desc.mag_filter = img->cmn.mag_filter; + desc.wrap_u = img->cmn.wrap_u; + desc.wrap_v = img->cmn.wrap_v; + desc.wrap_w = img->cmn.wrap_w; + desc.border_color = img->cmn.border_color; + desc.max_anisotropy = img->cmn.max_anisotropy; + desc.min_lod = img->cmn.min_lod; + desc.max_lod = img->cmn.max_lod; + } + return desc; +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_desc(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + for (int stage_idx = 0; stage_idx < SG_NUM_SHADER_STAGES; stage_idx++) { + sg_shader_stage_desc* stage_desc = (stage_idx == 0) ? &desc.vs : &desc.fs; + const _sg_shader_stage_t* stage = &shd->cmn.stage[stage_idx]; + for (int ub_idx = 0; ub_idx < stage->num_uniform_blocks; ub_idx++) { + sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_idx]; + const _sg_shader_uniform_block_t* ub = &stage->uniform_blocks[ub_idx]; + ub_desc->size = ub->size; + } + for (int img_idx = 0; img_idx < stage->num_images; img_idx++) { + sg_shader_image_desc* img_desc = &stage_desc->images[img_idx]; + const _sg_shader_image_t* img = &stage->images[img_idx]; + img_desc->image_type = img->image_type; + img_desc->sampler_type = img->sampler_type; + } + } + } + return desc; +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + desc.shader = pip->cmn.shader_id; + desc.layout = pip->cmn.layout; + desc.depth = pip->cmn.depth; + desc.stencil = pip->cmn.stencil; + desc.color_count = pip->cmn.color_count; + for (int i = 0; i < pip->cmn.color_count; i++) { + desc.colors[i] = pip->cmn.colors[i]; + } + desc.primitive_type = pip->cmn.primitive_type; + desc.index_type = pip->cmn.index_type; + desc.cull_mode = pip->cmn.cull_mode; + desc.face_winding = pip->cmn.face_winding; + desc.sample_count = pip->cmn.sample_count; + desc.blend_color = pip->cmn.blend_color; + desc.alpha_to_coverage_enabled = pip->cmn.alpha_to_coverage_enabled; + } + return desc; +} + +SOKOL_API_IMPL sg_pass_desc sg_query_pass_desc(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); + sg_pass_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + for (int i = 0; i < pass->cmn.num_color_atts; i++) { + desc.color_attachments[i].image = pass->cmn.color_atts[i].image_id; + desc.color_attachments[i].mip_level = pass->cmn.color_atts[i].mip_level; + desc.color_attachments[i].slice = pass->cmn.color_atts[i].slice; + } + desc.depth_stencil_attachment.image = pass->cmn.ds_att.image_id; + desc.depth_stencil_attachment.mip_level = pass->cmn.ds_att.mip_level; + desc.depth_stencil_attachment.slice = pass->cmn.ds_att.slice; + } + return desc; +} + SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) { SOKOL_ASSERT(_sg.valid && desc); return _sg_buffer_desc_defaults(desc); From 0910336c1d9f04fb72b35f8d101b5a28a8d80f6c Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 17:53:13 +0100 Subject: [PATCH 02/20] Include newer FindOpenGL --- CMakeLists.txt | 2 +- cmake/FindOpenGL.cmake | 780 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 cmake/FindOpenGL.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2db44446a..580235339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) - +import("cmake/FindOpenGL.cmake") if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) endif() diff --git a/cmake/FindOpenGL.cmake b/cmake/FindOpenGL.cmake new file mode 100644 index 000000000..69faf7e15 --- /dev/null +++ b/cmake/FindOpenGL.cmake @@ -0,0 +1,780 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindOpenGL +---------- + +FindModule for OpenGL and OpenGL Utility Library (GLU). + +.. versionchanged:: 3.2 + X11 is no longer added as a dependency on Unix/Linux systems. + +.. versionadded:: 3.10 + GLVND support on Linux. See the :ref:`Linux Specific` section below. + +Optional COMPONENTS +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.10 + +This module respects several optional COMPONENTS: + +``EGL`` + The EGL interface between OpenGL, OpenGL ES and the underlying windowing system. + +``GLX`` + An extension to X that interfaces OpenGL, OpenGL ES with X window system. + +``OpenGL`` + The cross platform API for 3D graphics. + +``GLES2`` + .. versionadded:: 3.27 + + A subset of OpenGL API for embedded systems with limited capabilities. + +``GLES3`` + .. versionadded:: 3.27 + + A subset of OpenGL API for embedded systems with more capabilities. + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.8 + +This module defines the :prop_tgt:`IMPORTED` targets: + +``OpenGL::GL`` + Defined to the platform-specific OpenGL libraries if the system has OpenGL. +``OpenGL::GLU`` + Defined if the system has OpenGL Utility Library (GLU). + +.. versionadded:: 3.10 + Additionally, the following GLVND-specific library targets are defined: + +``OpenGL::OpenGL`` + Defined to libOpenGL if the system is GLVND-based. +``OpenGL::GLX`` + Defined if the system has OpenGL Extension to the X Window System (GLX). +``OpenGL::EGL`` + Defined if the system has EGL. +``OpenGL::GLES2`` + .. versionadded:: 3.27 + + Defined if the system has GLES2. +``OpenGL::GLES3`` + .. versionadded:: 3.27 + + Defined if the system has GLES3. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following variables: + +``OPENGL_FOUND`` + True, if the system has OpenGL and all components are found. +``OPENGL_XMESA_FOUND`` + True, if the system has XMESA. +``OPENGL_GLU_FOUND`` + True, if the system has GLU. +``OpenGL_OpenGL_FOUND`` + True, if the system has an OpenGL library. +``OpenGL_GLX_FOUND`` + True, if the system has GLX. +``OpenGL_EGL_FOUND`` + True, if the system has EGL. +``OpenGL::GLES2`` + Defined if the system has GLES2. +``OpenGL::GLES3`` + Defined if the system has GLES3. +``OPENGL_INCLUDE_DIR`` + Path to the OpenGL include directory. + The ``OPENGL_INCLUDE_DIRS`` variable is preferred. +``OPENGL_EGL_INCLUDE_DIRS`` + Path to the EGL include directory. +``OPENGL_LIBRARIES`` + Paths to the OpenGL library, windowing system libraries, and GLU libraries. + On Linux, this assumes GLX and is never correct for EGL-based targets. + Clients are encouraged to use the ``OpenGL::*`` import targets instead. +``OPENGL_INCLUDE_DIRS`` + .. versionadded:: 3.29 + + Paths to the OpenGL include directories. + +.. versionadded:: 3.10 + Variables for GLVND-specific libraries ``OpenGL``, ``EGL`` and ``GLX``. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``OPENGL_egl_LIBRARY`` + Path to the EGL library. +``OPENGL_glu_LIBRARY`` + Path to the GLU library. +``OPENGL_glx_LIBRARY`` + Path to the GLVND 'GLX' library. +``OPENGL_opengl_LIBRARY`` + Path to the GLVND 'OpenGL' library +``OPENGL_gl_LIBRARY`` + Path to the OpenGL library. New code should prefer the ``OpenGL::*`` import + targets. +``OPENGL_gles2_LIBRARY`` + .. versionadded:: 3.27 + + Path to the OpenGL GLES2 library. +``OPENGL_gles3_LIBRARY`` + .. versionadded:: 3.27 + + Path to the OpenGL GLES3 library. + +``OPENGL_GLU_INCLUDE_DIR`` + .. versionadded:: 3.29 + + Path to the OpenGL GLU include directory. + +.. versionadded:: 3.10 + Variables for GLVND-specific libraries ``OpenGL``, ``EGL`` and ``GLX``. + +.. _`Linux Specific`: + +Linux-specific +^^^^^^^^^^^^^^ + +Some Linux systems utilize GLVND as a new ABI for OpenGL. GLVND separates +context libraries from OpenGL itself; OpenGL lives in "libOpenGL", and +contexts are defined in "libGLX" or "libEGL". GLVND is currently the only way +to get OpenGL 3+ functionality via EGL in a manner portable across vendors. +Projects may use GLVND explicitly with target ``OpenGL::OpenGL`` and either +``OpenGL::GLX`` or ``OpenGL::EGL``. + +Projects may use the ``OpenGL::GL`` target (or ``OPENGL_LIBRARIES`` variable) +to use legacy GL interfaces. These will use the legacy GL library located +by ``OPENGL_gl_LIBRARY``, if available. If ``OPENGL_gl_LIBRARY`` is empty or +not found and GLVND is available, the ``OpenGL::GL`` target will use GLVND +``OpenGL::OpenGL`` and ``OpenGL::GLX`` (and the ``OPENGL_LIBRARIES`` +variable will use the corresponding libraries). Thus, for non-EGL-based +Linux targets, the ``OpenGL::GL`` target is most portable. + +A ``OpenGL_GL_PREFERENCE`` variable may be set to specify the preferred way +to provide legacy GL interfaces in case multiple choices are available. +The value may be one of: + +``GLVND`` + If the GLVND OpenGL and GLX libraries are available, prefer them. + This forces ``OPENGL_gl_LIBRARY`` to be empty. + + .. versionchanged:: 3.11 + This is the default, unless policy :policy:`CMP0072` is set to ``OLD`` + and no components are requested (since components + correspond to GLVND libraries). + +``LEGACY`` + Prefer to use the legacy libGL library, if available. + +For EGL targets the client must rely on GLVND support on the user's system. +Linking should use the ``OpenGL::OpenGL OpenGL::EGL`` targets. Using GLES* +libraries is theoretically possible in place of ``OpenGL::OpenGL``, but this +module does not currently support that; contributions welcome. + +``OPENGL_egl_LIBRARY`` and ``OPENGL_EGL_INCLUDE_DIRS`` are defined in the case of +GLVND. For non-GLVND Linux and other systems these are left undefined. + +macOS-Specific +^^^^^^^^^^^^^^ + +On macOS this module defaults to using the macOS-native framework +version of OpenGL. To use the X11 version of OpenGL on macOS, one +can disable searching of frameworks. For example: + +.. code-block:: cmake + + find_package(X11) + if(APPLE AND X11_FOUND) + set(CMAKE_FIND_FRAMEWORK NEVER) + find_package(OpenGL) + unset(CMAKE_FIND_FRAMEWORK) + else() + find_package(OpenGL) + endif() + +An end user building this project may need to point CMake at their +X11 installation, e.g., with ``-DOpenGL_ROOT=/opt/X11``. + +#]=======================================================================] + +set(_OpenGL_REQUIRED_VARS OPENGL_gl_LIBRARY) + +# Provide OPENGL_USE_ variables for each component. +foreach(component ${OpenGL_FIND_COMPONENTS}) + string(TOUPPER ${component} _COMPONENT) + set(OPENGL_USE_${_COMPONENT} 1) +endforeach() + +set(_OpenGL_CACHE_VARS) + +if (WIN32) + + if(BORLAND) + set (OPENGL_gl_LIBRARY import32 CACHE STRING "OpenGL library for win32") + set (OPENGL_glu_LIBRARY import32 CACHE STRING "GLU library for win32") + else() + set (OPENGL_gl_LIBRARY opengl32 CACHE STRING "OpenGL library for win32") + set (OPENGL_glu_LIBRARY glu32 CACHE STRING "GLU library for win32") + endif() + + list(APPEND _OpenGL_CACHE_VARS + OPENGL_gl_LIBRARY + OPENGL_glu_LIBRARY + ) +elseif (APPLE) + # The OpenGL.framework provides both gl and glu in OpenGL + # XQuartz provides libgl and libglu + find_library(OPENGL_gl_LIBRARY NAMES OpenGL GL DOC + "OpenGL GL library") + find_library(OPENGL_glu_LIBRARY NAMES OpenGL GLU DOC + "OpenGL GLU library") + find_path(OPENGL_INCLUDE_DIR NAMES OpenGL/gl.h GL/gl.h DOC + "Include for OpenGL") + find_path(OPENGL_GLU_INCLUDE_DIR NAMES OpenGL/glu.h GL/glu.h DOC + "Include for the OpenGL GLU library") + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR) + + list(APPEND _OpenGL_CACHE_VARS + OPENGL_INCLUDE_DIR + OPENGL_GLU_INCLUDE_DIR + OPENGL_gl_LIBRARY + OPENGL_glu_LIBRARY + ) +else() + if (CMAKE_ANDROID_NDK) + set(_OPENGL_INCLUDE_PATH ${CMAKE_ANDROID_NDK}/sysroot/usr/include) + set(_OPENGL_LIB_PATH ${CMAKE_ANDROID_NDK}/platforms/android-${CMAKE_SYSTEM_VERSION}/arch-${CMAKE_ANDROID_ARCH}/usr/lib) + elseif (CMAKE_SYSTEM_NAME MATCHES "HP-UX") + # Handle HP-UX cases where we only want to find OpenGL in either hpux64 + # or hpux32 depending on if we're doing a 64 bit build. + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(_OPENGL_LIB_PATH + /opt/graphics/OpenGL/lib/hpux32/) + else() + set(_OPENGL_LIB_PATH + /opt/graphics/OpenGL/lib/hpux64/ + /opt/graphics/OpenGL/lib/pa20_64) + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL Haiku) + set(_OPENGL_LIB_PATH + /boot/develop/lib/x86) + set(_OPENGL_INCLUDE_PATH + /boot/develop/headers/os/opengl) + elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + # CMake doesn't support arbitrary globs in search paths. + file(GLOB _OPENGL_LIB_PATH + # The NVidia driver installation tool on Linux installs libraries to a + # `nvidia-` subdirectory. + "/usr/lib/nvidia-*" + "/usr/lib32/nvidia-*") + endif() + + # The first line below is to make sure that the proper headers + # are used on a Linux machine with the NVidia drivers installed. + # They replace Mesa with NVidia's own library but normally do not + # install headers and that causes the linking to + # fail since the compiler finds the Mesa headers but NVidia's library. + # Make sure the NVIDIA directory comes BEFORE the others. + # - Atanas Georgiev + find_path(OPENGL_INCLUDE_DIR GL/gl.h + /usr/share/doc/NVIDIA_GLX-1.0/include + /usr/openwin/share/include + /opt/graphics/OpenGL/include + ${_OPENGL_INCLUDE_PATH} + ) + find_path(OPENGL_GLX_INCLUDE_DIR GL/glx.h ${_OPENGL_INCLUDE_PATH}) + find_path(OPENGL_EGL_INCLUDE_DIR EGL/egl.h ${_OPENGL_INCLUDE_PATH}) + find_path(OPENGL_GLES2_INCLUDE_DIR GLES2/gl2.h ${_OPENGL_INCLUDE_PATH}) + find_path(OPENGL_GLES3_INCLUDE_DIR GLES3/gl3.h ${_OPENGL_INCLUDE_PATH}) + find_path(OPENGL_xmesa_INCLUDE_DIR GL/xmesa.h + /usr/share/doc/NVIDIA_GLX-1.0/include + /usr/openwin/share/include + /opt/graphics/OpenGL/include + ) + + find_path(OPENGL_GLU_INCLUDE_DIR GL/glu.h ${_OPENGL_INCLUDE_PATH}) + + list(APPEND _OpenGL_CACHE_VARS + OPENGL_INCLUDE_DIR + OPENGL_GLX_INCLUDE_DIR + OPENGL_EGL_INCLUDE_DIR + OPENGL_GLES2_INCLUDE_DIR + OPENGL_GLES3_INCLUDE_DIR + OPENGL_xmesa_INCLUDE_DIR + OPENGL_GLU_INCLUDE_DIR + ) + + # Search for the GLVND libraries. We do this regardless of COMPONENTS; we'll + # take into account the COMPONENTS logic later. + find_library(OPENGL_opengl_LIBRARY + NAMES OpenGL + PATHS ${_OPENGL_LIB_PATH} + ) + + find_library(OPENGL_glx_LIBRARY + NAMES GLX + PATHS ${_OPENGL_LIB_PATH} + PATH_SUFFIXES libglvnd + ) + + find_library(OPENGL_egl_LIBRARY + NAMES EGL + PATHS ${_OPENGL_LIB_PATH} + PATH_SUFFIXES libglvnd + ) + + find_library(OPENGL_gles2_LIBRARY + NAMES GLESv2 + PATHS ${_OPENGL_LIB_PATH} + ) + + find_library(OPENGL_gles3_LIBRARY + NAMES GLESv3 + GLESv2 # mesa provides only libGLESv2 + PATHS ${_OPENGL_LIB_PATH} + ) + + find_library(OPENGL_glu_LIBRARY + NAMES GLU MesaGLU + PATHS ${OPENGL_gl_LIBRARY} + /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib + ) + + list(APPEND _OpenGL_CACHE_VARS + OPENGL_opengl_LIBRARY + OPENGL_glx_LIBRARY + OPENGL_egl_LIBRARY + OPENGL_gles2_LIBRARY + OPENGL_gles3_LIBRARY + OPENGL_glu_LIBRARY + ) + + set(_OpenGL_GL_POLICY_WARN 0) + if(NOT DEFINED OpenGL_GL_PREFERENCE) + set(OpenGL_GL_PREFERENCE "") + endif() + if(NOT OpenGL_GL_PREFERENCE STREQUAL "") + # A preference has been explicitly specified. + if(NOT OpenGL_GL_PREFERENCE MATCHES "^(GLVND|LEGACY)$") + message(FATAL_ERROR + "OpenGL_GL_PREFERENCE value '${OpenGL_GL_PREFERENCE}' not recognized. " + "Allowed values are 'GLVND' and 'LEGACY'." + ) + endif() + elseif(OpenGL_FIND_COMPONENTS) + # No preference was explicitly specified, but the caller did request + # at least one GLVND component. Prefer GLVND for legacy GL. + set(OpenGL_GL_PREFERENCE "GLVND") + else() + # No preference was explicitly specified and no GLVND components were + # requested. Use a policy to choose the default. + cmake_policy(GET CMP0072 _OpenGL_GL_POLICY) + if("x${_OpenGL_GL_POLICY}x" STREQUAL "xNEWx") + set(OpenGL_GL_PREFERENCE "GLVND") + else() + set(OpenGL_GL_PREFERENCE "LEGACY") + if("x${_OpenGL_GL_POLICY}x" STREQUAL "xx") + set(_OpenGL_GL_POLICY_WARN 1) + endif() + endif() + unset(_OpenGL_GL_POLICY) + endif() + + if("x${OpenGL_GL_PREFERENCE}x" STREQUAL "xGLVNDx" AND OPENGL_opengl_LIBRARY AND OPENGL_glx_LIBRARY) + # We can provide legacy GL using GLVND libraries. + # Do not use any legacy GL library. + set(OPENGL_gl_LIBRARY "") + else() + # We cannot provide legacy GL using GLVND libraries. + # Search for the legacy GL library. + find_library(OPENGL_gl_LIBRARY + NAMES GL MesaGL + PATHS /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib + ${_OPENGL_LIB_PATH} + PATH_SUFFIXES libglvnd + ) + list(APPEND _OpenGL_CACHE_VARS OPENGL_gl_LIBRARY) + endif() + + if(_OpenGL_GL_POLICY_WARN AND OPENGL_gl_LIBRARY AND OPENGL_opengl_LIBRARY AND OPENGL_glx_LIBRARY) + cmake_policy(GET_WARNING CMP0072 _cmp0072_warning) + message(AUTHOR_WARNING + "${_cmp0072_warning}\n" + "FindOpenGL found both a legacy GL library:\n" + " OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}\n" + "and GLVND libraries for OpenGL and GLX:\n" + " OPENGL_opengl_LIBRARY: ${OPENGL_opengl_LIBRARY}\n" + " OPENGL_glx_LIBRARY: ${OPENGL_glx_LIBRARY}\n" + "OpenGL_GL_PREFERENCE has not been set to \"GLVND\" or \"LEGACY\", so for " + "compatibility with CMake 3.10 and below the legacy GL library will be used." + ) + endif() + unset(_OpenGL_GL_POLICY_WARN) + + # FPHSA cannot handle "this OR that is required", so we conditionally set what + # it must look for. First clear any previous config we might have done: + set(_OpenGL_REQUIRED_VARS) + + # now we append the libraries as appropriate. The complicated logic + # basically comes down to "use libOpenGL when we can, and add in specific + # context mechanisms when requested, or we need them to preserve the previous + # default where glx is always available." + if((NOT OPENGL_USE_EGL AND + NOT OPENGL_opengl_LIBRARY AND + OPENGL_glx_LIBRARY AND + NOT OPENGL_gl_LIBRARY) OR + (NOT OPENGL_USE_EGL AND + NOT OPENGL_USE_GLES3 AND + NOT OPENGL_USE_GLES2 AND + NOT OPENGL_glx_LIBRARY AND + NOT OPENGL_gl_LIBRARY) OR + (NOT OPENGL_USE_EGL AND + OPENGL_opengl_LIBRARY AND + OPENGL_glx_LIBRARY) OR + (NOT OPENGL_USE_GLES3 AND + NOT OPENGL_USE_GLES2 AND + OPENGL_USE_EGL)) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_opengl_LIBRARY) + endif() + + # GLVND GLX library. Preferred when available. + if((NOT OPENGL_USE_OPENGL AND + NOT OPENGL_USE_GLX AND + NOT OPENGL_USE_EGL AND + NOT OPENGL_USE_GLES3 AND + NOT OPENGL_USE_GLES2 AND + NOT OPENGL_glx_LIBRARY AND + NOT OPENGL_gl_LIBRARY) OR + ( OPENGL_USE_GLX AND + NOT OPENGL_USE_EGL AND + NOT OPENGL_USE_GLES3 AND + NOT OPENGL_USE_GLES2 AND + NOT OPENGL_glx_LIBRARY AND + NOT OPENGL_gl_LIBRARY) OR + (NOT OPENGL_USE_EGL AND + NOT OPENGL_USE_GLES3 AND + NOT OPENGL_USE_GLES2 AND + OPENGL_opengl_LIBRARY AND + OPENGL_glx_LIBRARY) OR + (OPENGL_USE_GLX AND OPENGL_USE_EGL)) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_glx_LIBRARY) + endif() + + # GLVND EGL library. + if(OPENGL_USE_EGL) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_egl_LIBRARY) + endif() + + # GLVND GLES2 library. + if(OPENGL_USE_GLES2) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles2_LIBRARY) + endif() + + # GLVND GLES3 library. + if(OPENGL_USE_GLES3) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles3_LIBRARY) + endif() + + # Old-style "libGL" library: used as a fallback when GLVND isn't available. + if((NOT OPENGL_USE_EGL AND + NOT OPENGL_opengl_LIBRARY AND + OPENGL_glx_LIBRARY AND + OPENGL_gl_LIBRARY) OR + (NOT OPENGL_USE_EGL AND + NOT OPENGL_glx_LIBRARY AND + OPENGL_gl_LIBRARY)) + list(PREPEND _OpenGL_REQUIRED_VARS OPENGL_gl_LIBRARY) + endif() + + # We always need the 'gl.h' include dir. + if(OPENGL_USE_EGL) + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_EGL_INCLUDE_DIR) + else() + list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR) + endif() + + unset(_OPENGL_INCLUDE_PATH) + unset(_OPENGL_LIB_PATH) + + find_library(OPENGL_glu_LIBRARY + NAMES GLU MesaGLU + PATHS ${OPENGL_gl_LIBRARY} + /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib + ) +endif () + +if(OPENGL_xmesa_INCLUDE_DIR) + set( OPENGL_XMESA_FOUND "YES" ) +else() + set( OPENGL_XMESA_FOUND "NO" ) +endif() + +if(OPENGL_glu_LIBRARY AND (WIN32 OR OPENGL_GLU_INCLUDE_DIR)) + set( OPENGL_GLU_FOUND "YES" ) +else() + set( OPENGL_GLU_FOUND "NO" ) +endif() + +# OpenGL_OpenGL_FOUND is a bit unique in that it is okay if /either/ libOpenGL +# or libGL is found. +# Using libGL with libEGL is never okay, though; we handle that case later. +if(NOT OPENGL_opengl_LIBRARY AND NOT OPENGL_gl_LIBRARY) + set(OpenGL_OpenGL_FOUND FALSE) +else() + set(OpenGL_OpenGL_FOUND TRUE) +endif() + +if(OPENGL_glx_LIBRARY AND OPENGL_GLX_INCLUDE_DIR) + set(OpenGL_GLX_FOUND TRUE) +else() + set(OpenGL_GLX_FOUND FALSE) +endif() + +if(OPENGL_egl_LIBRARY AND OPENGL_EGL_INCLUDE_DIR) + set(OpenGL_EGL_FOUND TRUE) +else() + set(OpenGL_EGL_FOUND FALSE) +endif() + +if(OPENGL_gles2_LIBRARY AND OPENGL_GLES2_INCLUDE_DIR) + set(OpenGL_GLES2_FOUND TRUE) +else() + set(OpenGL_GLES2_FOUND FALSE) +endif() + +if(OPENGL_gles3_LIBRARY AND OPENGL_GLES3_INCLUDE_DIR) + set(OpenGL_GLES3_FOUND TRUE) +else() + set(OpenGL_GLES3_FOUND FALSE) +endif() + +# User-visible names should be plural. +if(OPENGL_EGL_INCLUDE_DIR) + set(OPENGL_EGL_INCLUDE_DIRS ${OPENGL_EGL_INCLUDE_DIR}) +endif() + +include(FindPackageHandleStandardArgs) +if (CMAKE_FIND_PACKAGE_NAME STREQUAL "GLU") + # FindGLU include()'s this module. It's an old pattern, but rather than + # trying to suppress this from outside the module (which is then sensitive to + # the contents, detect the case in this module and suppress it explicitly. + set(FPHSA_NAME_MISMATCHED 1) +endif () +find_package_handle_standard_args(OpenGL REQUIRED_VARS ${_OpenGL_REQUIRED_VARS} + HANDLE_COMPONENTS) +unset(FPHSA_NAME_MISMATCHED) +unset(_OpenGL_REQUIRED_VARS) + +# OpenGL:: targets +if(OPENGL_FOUND) + set(OPENGL_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR}) + + # ::OpenGL is a GLVND library, and thus Linux-only: we don't bother checking + # for a framework version of this library. + if(OPENGL_opengl_LIBRARY AND NOT TARGET OpenGL::OpenGL) + if(IS_ABSOLUTE "${OPENGL_opengl_LIBRARY}") + add_library(OpenGL::OpenGL UNKNOWN IMPORTED) + set_target_properties(OpenGL::OpenGL PROPERTIES IMPORTED_LOCATION + "${OPENGL_opengl_LIBRARY}") + else() + add_library(OpenGL::OpenGL INTERFACE IMPORTED) + set_target_properties(OpenGL::OpenGL PROPERTIES IMPORTED_LIBNAME + "${OPENGL_opengl_LIBRARY}") + endif() + set_target_properties(OpenGL::OpenGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_INCLUDE_DIR}") + set(_OpenGL_EGL_IMPL OpenGL::OpenGL) + endif() + + # ::GLX is a GLVND library, and thus Linux-only: we don't bother checking + # for a framework version of this library. + if(OpenGL_GLX_FOUND AND NOT TARGET OpenGL::GLX AND TARGET OpenGL::OpenGL) + if(IS_ABSOLUTE "${OPENGL_glx_LIBRARY}") + add_library(OpenGL::GLX UNKNOWN IMPORTED) + set_target_properties(OpenGL::GLX PROPERTIES IMPORTED_LOCATION + "${OPENGL_glx_LIBRARY}") + else() + add_library(OpenGL::GLX INTERFACE IMPORTED) + set_target_properties(OpenGL::GLX PROPERTIES IMPORTED_LIBNAME + "${OPENGL_glx_LIBRARY}") + endif() + set_target_properties(OpenGL::GLX PROPERTIES INTERFACE_LINK_LIBRARIES + OpenGL::OpenGL) + set_target_properties(OpenGL::GLX PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_GLX_INCLUDE_DIR}") + list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLX_INCLUDE_DIR}) + endif() + + # ::GLES2 is a GLVND library, and thus Linux-only: we don't bother checking + # for a framework version of this library. + if(OpenGL_GLES2_FOUND AND NOT TARGET OpenGL::GLES2) + + # Initialize target + if(NOT OPENGL_gles2_LIBRARY) + add_library(OpenGL::GLES2 INTERFACE IMPORTED) + else() + if(IS_ABSOLUTE "${OPENGL_gles2_LIBRARY}") + add_library(OpenGL::GLES2 UNKNOWN IMPORTED) + set_target_properties(OpenGL::GLES2 PROPERTIES + IMPORTED_LOCATION "${OPENGL_gles2_LIBRARY}" + ) + else() + add_library(OpenGL::GLES2 INTERFACE IMPORTED) + set_target_properties(OpenGL::GLES2 PROPERTIES + IMPORTED_LIBNAME "${OPENGL_gles2_LIBRARY}" + ) + endif() + endif() + + # Attach target properties + set_target_properties(OpenGL::GLES2 + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_GLES2_INCLUDE_DIR}" + ) + list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLES2_INCLUDE_DIR}) + + if (OPENGL_USE_GLES2) + set(_OpenGL_EGL_IMPL OpenGL::GLES2) + endif () + + endif() + + # ::GLES3 is a GLVND library, and thus Linux-only: we don't bother checking + # for a framework version of this library. + if(OpenGL_GLES3_FOUND AND NOT TARGET OpenGL::GLES3) + + # Initialize target + if(NOT OPENGL_gles3_LIBRARY) + add_library(OpenGL::GLES3 INTERFACE IMPORTED) + else() + if(IS_ABSOLUTE "${OPENGL_gles3_LIBRARY}") + add_library(OpenGL::GLES3 UNKNOWN IMPORTED) + set_target_properties(OpenGL::GLES3 PROPERTIES + IMPORTED_LOCATION "${OPENGL_gles3_LIBRARY}" + ) + else() + add_library(OpenGL::GLES3 INTERFACE IMPORTED) + set_target_properties(OpenGL::GLES3 PROPERTIES + IMPORTED_LIBNAME "${OPENGL_gles3_LIBRARY}" + ) + endif() + endif() + + # Attach target properties + set_target_properties(OpenGL::GLES3 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_GLES3_INCLUDE_DIR}" + ) + list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLES3_INCLUDE_DIR}) + + if (OPENGL_USE_GLES3) + set(_OpenGL_EGL_IMPL OpenGL::GLES3) + endif () + + endif() + + if(OPENGL_gl_LIBRARY AND NOT TARGET OpenGL::GL) + # A legacy GL library is available, so use it for the legacy GL target. + if(IS_ABSOLUTE "${OPENGL_gl_LIBRARY}") + add_library(OpenGL::GL UNKNOWN IMPORTED) + set_target_properties(OpenGL::GL PROPERTIES + IMPORTED_LOCATION "${OPENGL_gl_LIBRARY}") + else() + add_library(OpenGL::GL INTERFACE IMPORTED) + set_target_properties(OpenGL::GL PROPERTIES + IMPORTED_LIBNAME "${OPENGL_gl_LIBRARY}") + endif() + set_target_properties(OpenGL::GL PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}") + elseif(NOT TARGET OpenGL::GL AND TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) + # A legacy GL library is not available, but we can provide the legacy GL + # target using GLVND OpenGL+GLX. + add_library(OpenGL::GL INTERFACE IMPORTED) + set_target_properties(OpenGL::GL PROPERTIES INTERFACE_LINK_LIBRARIES + OpenGL::OpenGL) + set_property(TARGET OpenGL::GL APPEND PROPERTY INTERFACE_LINK_LIBRARIES + OpenGL::GLX) + set_target_properties(OpenGL::GL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_INCLUDE_DIR}") + endif() + + # ::EGL is a GLVND library, and thus Linux-only: we don't bother checking + # for a framework version of this library. + # Note we test whether _OpenGL_EGL_IMPL is set. Based on the OpenGL implementation, + # _OpenGL_EGL_IMPL will be one of OpenGL::OpenGL, OpenGL::GLES2, OpenGL::GLES3 + if(_OpenGL_EGL_IMPL AND OpenGL_EGL_FOUND AND NOT TARGET OpenGL::EGL) + if(IS_ABSOLUTE "${OPENGL_egl_LIBRARY}") + add_library(OpenGL::EGL UNKNOWN IMPORTED) + set_target_properties(OpenGL::EGL PROPERTIES IMPORTED_LOCATION + "${OPENGL_egl_LIBRARY}") + else() + add_library(OpenGL::EGL INTERFACE IMPORTED) + set_target_properties(OpenGL::EGL PROPERTIES IMPORTED_LIBNAME + "${OPENGL_egl_LIBRARY}") + endif() + set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_LINK_LIBRARIES + "${_OpenGL_EGL_IMPL}") + # Note that EGL's include directory is different from OpenGL/GLX's! + set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_EGL_INCLUDE_DIR}") + list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_EGL_INCLUDE_DIR}) + endif() + + if(OPENGL_GLU_FOUND AND NOT TARGET OpenGL::GLU) + if(IS_ABSOLUTE "${OPENGL_glu_LIBRARY}") + add_library(OpenGL::GLU UNKNOWN IMPORTED) + set_target_properties(OpenGL::GLU PROPERTIES + IMPORTED_LOCATION "${OPENGL_glu_LIBRARY}") + else() + add_library(OpenGL::GLU INTERFACE IMPORTED) + set_target_properties(OpenGL::GLU PROPERTIES + IMPORTED_LIBNAME "${OPENGL_glu_LIBRARY}") + endif() + set_target_properties(OpenGL::GLU PROPERTIES + INTERFACE_LINK_LIBRARIES OpenGL::GL) + # Note that GLU's include directory may be different from OpenGL's! + set_target_properties(OpenGL::GLU PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OPENGL_GLU_INCLUDE_DIR}") + list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLU_INCLUDE_DIR}) + endif() + + # OPENGL_LIBRARIES mirrors OpenGL::GL's logic ... + if(OPENGL_gl_LIBRARY) + set(OPENGL_LIBRARIES ${OPENGL_gl_LIBRARY}) + elseif(TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) + set(OPENGL_LIBRARIES ${OPENGL_opengl_LIBRARY} ${OPENGL_glx_LIBRARY}) + else() + set(OPENGL_LIBRARIES "") + endif() + # ... and also includes GLU, if available. + if(TARGET OpenGL::GLU) + list(APPEND OPENGL_LIBRARIES ${OPENGL_glu_LIBRARY}) + endif() +endif() + +list(REMOVE_DUPLICATES OPENGL_INCLUDE_DIRS) + +# This deprecated setting is for backward compatibility with CMake1.4 +set(OPENGL_LIBRARY ${OPENGL_LIBRARIES}) +# This deprecated setting is for backward compatibility with CMake1.4 +set(OPENGL_INCLUDE_PATH ${OPENGL_INCLUDE_DIR}) + +mark_as_advanced(${_OpenGL_CACHE_VARS}) +unset(_OpenGL_CACHE_VARS) From 5b962c27d8edd8164cf65007efcb0ad3a39e99b6 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 17:54:42 +0100 Subject: [PATCH 03/20] Revert cmake version requirement --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 580235339..e7c51d7ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) import("cmake/FindOpenGL.cmake") + if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) endif() From ac49345ea956511dbeba09c85e04484070053531 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 17:59:50 +0100 Subject: [PATCH 04/20] Use include not import --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7c51d7ff..c91ec1657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) -import("cmake/FindOpenGL.cmake") +include("cmake/FindOpenGL.cmake") if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) From b8146438a17674342ee9afe4fe57a5a06d3cabfb Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 19:18:04 +0100 Subject: [PATCH 05/20] Cleanup and fix gles3 detection --- CMakeLists.txt | 34 +- cmake/FindOpenGL.cmake | 780 --------------------------------------- cmake/RPi.cmake | 29 ++ cmake/get_soc_version.sh | 2 + 4 files changed, 35 insertions(+), 810 deletions(-) delete mode 100644 cmake/FindOpenGL.cmake create mode 100644 cmake/RPi.cmake create mode 100755 cmake/get_soc_version.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index c91ec1657..6f0bc8cdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type +cmake_minimum_required(VERSION 3.27) # Needed to set MSVC Runtime type set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) -include("cmake/FindOpenGL.cmake") +include("cmake/RPi.cmake") if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) @@ -21,36 +21,8 @@ else() set(IS_ARM FALSE) endif() -# Function to check if a command exists -function(command_exists CMD RESULT_VAR) - execute_process(COMMAND which ${CMD} OUTPUT_VARIABLE CMD_PATH RESULT_VARIABLE CMD_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) - if(CMD_RESULT EQUAL 0) - set(${RESULT_VAR} TRUE PARENT_SCOPE) - else() - set(${RESULT_VAR} FALSE PARENT_SCOPE) - endif() -endfunction() - command_exists("vcgencmd" IS_VIDEOCORE) -function(check_gles3_support SUPPORTS_GLES3) - # Initialize variable - set(${SUPPORTS_GLES3} OFF PARENT_SCOPE) - - # Run the Bash one-liner to get the SoC version as a decimal number - execute_process( - COMMAND bash -c "awk '/Revision/ { rev = \"0x\" substr(\$3, length(\$3)-3, 4); printf \"%d\n\", (rev / 4096) % 16 }' /proc/cpuinfo" - OUTPUT_VARIABLE SOC_VERSION_DEC - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Determine GLES3 support based on the SoC version - if(SOC_VERSION_DEC GREATER_EQUAL 3) - # BCM2711 or newer - set(${SUPPORTS_GLES3} ON PARENT_SCOPE) - endif() -endfunction() - set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) set(LINK_LIBS "") @@ -134,9 +106,11 @@ if ((CMAKE_SYSTEM_NAME STREQUAL Linux) AND NOT EMSCRIPTEN AND NOT ANDROID) if (IS_ARM AND IS_VIDEOCORE) check_gles3_support(SUPPORTS_GLES3) if(SUPPORTS_GLES3) + message("GLES3 supported") set(SE_FORCE_GLES3 TRUE) add_definitions(-DSE_FORCE_GLES3=1) else() + message("GLES3 supported") set(SE_FORCE_GLES2 TRUE) add_definitions(-DSE_FORCE_GLES2=1) endif() diff --git a/cmake/FindOpenGL.cmake b/cmake/FindOpenGL.cmake deleted file mode 100644 index 69faf7e15..000000000 --- a/cmake/FindOpenGL.cmake +++ /dev/null @@ -1,780 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindOpenGL ----------- - -FindModule for OpenGL and OpenGL Utility Library (GLU). - -.. versionchanged:: 3.2 - X11 is no longer added as a dependency on Unix/Linux systems. - -.. versionadded:: 3.10 - GLVND support on Linux. See the :ref:`Linux Specific` section below. - -Optional COMPONENTS -^^^^^^^^^^^^^^^^^^^ - -.. versionadded:: 3.10 - -This module respects several optional COMPONENTS: - -``EGL`` - The EGL interface between OpenGL, OpenGL ES and the underlying windowing system. - -``GLX`` - An extension to X that interfaces OpenGL, OpenGL ES with X window system. - -``OpenGL`` - The cross platform API for 3D graphics. - -``GLES2`` - .. versionadded:: 3.27 - - A subset of OpenGL API for embedded systems with limited capabilities. - -``GLES3`` - .. versionadded:: 3.27 - - A subset of OpenGL API for embedded systems with more capabilities. - -Imported Targets -^^^^^^^^^^^^^^^^ - -.. versionadded:: 3.8 - -This module defines the :prop_tgt:`IMPORTED` targets: - -``OpenGL::GL`` - Defined to the platform-specific OpenGL libraries if the system has OpenGL. -``OpenGL::GLU`` - Defined if the system has OpenGL Utility Library (GLU). - -.. versionadded:: 3.10 - Additionally, the following GLVND-specific library targets are defined: - -``OpenGL::OpenGL`` - Defined to libOpenGL if the system is GLVND-based. -``OpenGL::GLX`` - Defined if the system has OpenGL Extension to the X Window System (GLX). -``OpenGL::EGL`` - Defined if the system has EGL. -``OpenGL::GLES2`` - .. versionadded:: 3.27 - - Defined if the system has GLES2. -``OpenGL::GLES3`` - .. versionadded:: 3.27 - - Defined if the system has GLES3. - -Result Variables -^^^^^^^^^^^^^^^^ - -This module sets the following variables: - -``OPENGL_FOUND`` - True, if the system has OpenGL and all components are found. -``OPENGL_XMESA_FOUND`` - True, if the system has XMESA. -``OPENGL_GLU_FOUND`` - True, if the system has GLU. -``OpenGL_OpenGL_FOUND`` - True, if the system has an OpenGL library. -``OpenGL_GLX_FOUND`` - True, if the system has GLX. -``OpenGL_EGL_FOUND`` - True, if the system has EGL. -``OpenGL::GLES2`` - Defined if the system has GLES2. -``OpenGL::GLES3`` - Defined if the system has GLES3. -``OPENGL_INCLUDE_DIR`` - Path to the OpenGL include directory. - The ``OPENGL_INCLUDE_DIRS`` variable is preferred. -``OPENGL_EGL_INCLUDE_DIRS`` - Path to the EGL include directory. -``OPENGL_LIBRARIES`` - Paths to the OpenGL library, windowing system libraries, and GLU libraries. - On Linux, this assumes GLX and is never correct for EGL-based targets. - Clients are encouraged to use the ``OpenGL::*`` import targets instead. -``OPENGL_INCLUDE_DIRS`` - .. versionadded:: 3.29 - - Paths to the OpenGL include directories. - -.. versionadded:: 3.10 - Variables for GLVND-specific libraries ``OpenGL``, ``EGL`` and ``GLX``. - -Cache variables -^^^^^^^^^^^^^^^ - -The following cache variables may also be set: - -``OPENGL_egl_LIBRARY`` - Path to the EGL library. -``OPENGL_glu_LIBRARY`` - Path to the GLU library. -``OPENGL_glx_LIBRARY`` - Path to the GLVND 'GLX' library. -``OPENGL_opengl_LIBRARY`` - Path to the GLVND 'OpenGL' library -``OPENGL_gl_LIBRARY`` - Path to the OpenGL library. New code should prefer the ``OpenGL::*`` import - targets. -``OPENGL_gles2_LIBRARY`` - .. versionadded:: 3.27 - - Path to the OpenGL GLES2 library. -``OPENGL_gles3_LIBRARY`` - .. versionadded:: 3.27 - - Path to the OpenGL GLES3 library. - -``OPENGL_GLU_INCLUDE_DIR`` - .. versionadded:: 3.29 - - Path to the OpenGL GLU include directory. - -.. versionadded:: 3.10 - Variables for GLVND-specific libraries ``OpenGL``, ``EGL`` and ``GLX``. - -.. _`Linux Specific`: - -Linux-specific -^^^^^^^^^^^^^^ - -Some Linux systems utilize GLVND as a new ABI for OpenGL. GLVND separates -context libraries from OpenGL itself; OpenGL lives in "libOpenGL", and -contexts are defined in "libGLX" or "libEGL". GLVND is currently the only way -to get OpenGL 3+ functionality via EGL in a manner portable across vendors. -Projects may use GLVND explicitly with target ``OpenGL::OpenGL`` and either -``OpenGL::GLX`` or ``OpenGL::EGL``. - -Projects may use the ``OpenGL::GL`` target (or ``OPENGL_LIBRARIES`` variable) -to use legacy GL interfaces. These will use the legacy GL library located -by ``OPENGL_gl_LIBRARY``, if available. If ``OPENGL_gl_LIBRARY`` is empty or -not found and GLVND is available, the ``OpenGL::GL`` target will use GLVND -``OpenGL::OpenGL`` and ``OpenGL::GLX`` (and the ``OPENGL_LIBRARIES`` -variable will use the corresponding libraries). Thus, for non-EGL-based -Linux targets, the ``OpenGL::GL`` target is most portable. - -A ``OpenGL_GL_PREFERENCE`` variable may be set to specify the preferred way -to provide legacy GL interfaces in case multiple choices are available. -The value may be one of: - -``GLVND`` - If the GLVND OpenGL and GLX libraries are available, prefer them. - This forces ``OPENGL_gl_LIBRARY`` to be empty. - - .. versionchanged:: 3.11 - This is the default, unless policy :policy:`CMP0072` is set to ``OLD`` - and no components are requested (since components - correspond to GLVND libraries). - -``LEGACY`` - Prefer to use the legacy libGL library, if available. - -For EGL targets the client must rely on GLVND support on the user's system. -Linking should use the ``OpenGL::OpenGL OpenGL::EGL`` targets. Using GLES* -libraries is theoretically possible in place of ``OpenGL::OpenGL``, but this -module does not currently support that; contributions welcome. - -``OPENGL_egl_LIBRARY`` and ``OPENGL_EGL_INCLUDE_DIRS`` are defined in the case of -GLVND. For non-GLVND Linux and other systems these are left undefined. - -macOS-Specific -^^^^^^^^^^^^^^ - -On macOS this module defaults to using the macOS-native framework -version of OpenGL. To use the X11 version of OpenGL on macOS, one -can disable searching of frameworks. For example: - -.. code-block:: cmake - - find_package(X11) - if(APPLE AND X11_FOUND) - set(CMAKE_FIND_FRAMEWORK NEVER) - find_package(OpenGL) - unset(CMAKE_FIND_FRAMEWORK) - else() - find_package(OpenGL) - endif() - -An end user building this project may need to point CMake at their -X11 installation, e.g., with ``-DOpenGL_ROOT=/opt/X11``. - -#]=======================================================================] - -set(_OpenGL_REQUIRED_VARS OPENGL_gl_LIBRARY) - -# Provide OPENGL_USE_ variables for each component. -foreach(component ${OpenGL_FIND_COMPONENTS}) - string(TOUPPER ${component} _COMPONENT) - set(OPENGL_USE_${_COMPONENT} 1) -endforeach() - -set(_OpenGL_CACHE_VARS) - -if (WIN32) - - if(BORLAND) - set (OPENGL_gl_LIBRARY import32 CACHE STRING "OpenGL library for win32") - set (OPENGL_glu_LIBRARY import32 CACHE STRING "GLU library for win32") - else() - set (OPENGL_gl_LIBRARY opengl32 CACHE STRING "OpenGL library for win32") - set (OPENGL_glu_LIBRARY glu32 CACHE STRING "GLU library for win32") - endif() - - list(APPEND _OpenGL_CACHE_VARS - OPENGL_gl_LIBRARY - OPENGL_glu_LIBRARY - ) -elseif (APPLE) - # The OpenGL.framework provides both gl and glu in OpenGL - # XQuartz provides libgl and libglu - find_library(OPENGL_gl_LIBRARY NAMES OpenGL GL DOC - "OpenGL GL library") - find_library(OPENGL_glu_LIBRARY NAMES OpenGL GLU DOC - "OpenGL GLU library") - find_path(OPENGL_INCLUDE_DIR NAMES OpenGL/gl.h GL/gl.h DOC - "Include for OpenGL") - find_path(OPENGL_GLU_INCLUDE_DIR NAMES OpenGL/glu.h GL/glu.h DOC - "Include for the OpenGL GLU library") - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR) - - list(APPEND _OpenGL_CACHE_VARS - OPENGL_INCLUDE_DIR - OPENGL_GLU_INCLUDE_DIR - OPENGL_gl_LIBRARY - OPENGL_glu_LIBRARY - ) -else() - if (CMAKE_ANDROID_NDK) - set(_OPENGL_INCLUDE_PATH ${CMAKE_ANDROID_NDK}/sysroot/usr/include) - set(_OPENGL_LIB_PATH ${CMAKE_ANDROID_NDK}/platforms/android-${CMAKE_SYSTEM_VERSION}/arch-${CMAKE_ANDROID_ARCH}/usr/lib) - elseif (CMAKE_SYSTEM_NAME MATCHES "HP-UX") - # Handle HP-UX cases where we only want to find OpenGL in either hpux64 - # or hpux32 depending on if we're doing a 64 bit build. - if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(_OPENGL_LIB_PATH - /opt/graphics/OpenGL/lib/hpux32/) - else() - set(_OPENGL_LIB_PATH - /opt/graphics/OpenGL/lib/hpux64/ - /opt/graphics/OpenGL/lib/pa20_64) - endif() - elseif(CMAKE_SYSTEM_NAME STREQUAL Haiku) - set(_OPENGL_LIB_PATH - /boot/develop/lib/x86) - set(_OPENGL_INCLUDE_PATH - /boot/develop/headers/os/opengl) - elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") - # CMake doesn't support arbitrary globs in search paths. - file(GLOB _OPENGL_LIB_PATH - # The NVidia driver installation tool on Linux installs libraries to a - # `nvidia-` subdirectory. - "/usr/lib/nvidia-*" - "/usr/lib32/nvidia-*") - endif() - - # The first line below is to make sure that the proper headers - # are used on a Linux machine with the NVidia drivers installed. - # They replace Mesa with NVidia's own library but normally do not - # install headers and that causes the linking to - # fail since the compiler finds the Mesa headers but NVidia's library. - # Make sure the NVIDIA directory comes BEFORE the others. - # - Atanas Georgiev - find_path(OPENGL_INCLUDE_DIR GL/gl.h - /usr/share/doc/NVIDIA_GLX-1.0/include - /usr/openwin/share/include - /opt/graphics/OpenGL/include - ${_OPENGL_INCLUDE_PATH} - ) - find_path(OPENGL_GLX_INCLUDE_DIR GL/glx.h ${_OPENGL_INCLUDE_PATH}) - find_path(OPENGL_EGL_INCLUDE_DIR EGL/egl.h ${_OPENGL_INCLUDE_PATH}) - find_path(OPENGL_GLES2_INCLUDE_DIR GLES2/gl2.h ${_OPENGL_INCLUDE_PATH}) - find_path(OPENGL_GLES3_INCLUDE_DIR GLES3/gl3.h ${_OPENGL_INCLUDE_PATH}) - find_path(OPENGL_xmesa_INCLUDE_DIR GL/xmesa.h - /usr/share/doc/NVIDIA_GLX-1.0/include - /usr/openwin/share/include - /opt/graphics/OpenGL/include - ) - - find_path(OPENGL_GLU_INCLUDE_DIR GL/glu.h ${_OPENGL_INCLUDE_PATH}) - - list(APPEND _OpenGL_CACHE_VARS - OPENGL_INCLUDE_DIR - OPENGL_GLX_INCLUDE_DIR - OPENGL_EGL_INCLUDE_DIR - OPENGL_GLES2_INCLUDE_DIR - OPENGL_GLES3_INCLUDE_DIR - OPENGL_xmesa_INCLUDE_DIR - OPENGL_GLU_INCLUDE_DIR - ) - - # Search for the GLVND libraries. We do this regardless of COMPONENTS; we'll - # take into account the COMPONENTS logic later. - find_library(OPENGL_opengl_LIBRARY - NAMES OpenGL - PATHS ${_OPENGL_LIB_PATH} - ) - - find_library(OPENGL_glx_LIBRARY - NAMES GLX - PATHS ${_OPENGL_LIB_PATH} - PATH_SUFFIXES libglvnd - ) - - find_library(OPENGL_egl_LIBRARY - NAMES EGL - PATHS ${_OPENGL_LIB_PATH} - PATH_SUFFIXES libglvnd - ) - - find_library(OPENGL_gles2_LIBRARY - NAMES GLESv2 - PATHS ${_OPENGL_LIB_PATH} - ) - - find_library(OPENGL_gles3_LIBRARY - NAMES GLESv3 - GLESv2 # mesa provides only libGLESv2 - PATHS ${_OPENGL_LIB_PATH} - ) - - find_library(OPENGL_glu_LIBRARY - NAMES GLU MesaGLU - PATHS ${OPENGL_gl_LIBRARY} - /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/shlib - ) - - list(APPEND _OpenGL_CACHE_VARS - OPENGL_opengl_LIBRARY - OPENGL_glx_LIBRARY - OPENGL_egl_LIBRARY - OPENGL_gles2_LIBRARY - OPENGL_gles3_LIBRARY - OPENGL_glu_LIBRARY - ) - - set(_OpenGL_GL_POLICY_WARN 0) - if(NOT DEFINED OpenGL_GL_PREFERENCE) - set(OpenGL_GL_PREFERENCE "") - endif() - if(NOT OpenGL_GL_PREFERENCE STREQUAL "") - # A preference has been explicitly specified. - if(NOT OpenGL_GL_PREFERENCE MATCHES "^(GLVND|LEGACY)$") - message(FATAL_ERROR - "OpenGL_GL_PREFERENCE value '${OpenGL_GL_PREFERENCE}' not recognized. " - "Allowed values are 'GLVND' and 'LEGACY'." - ) - endif() - elseif(OpenGL_FIND_COMPONENTS) - # No preference was explicitly specified, but the caller did request - # at least one GLVND component. Prefer GLVND for legacy GL. - set(OpenGL_GL_PREFERENCE "GLVND") - else() - # No preference was explicitly specified and no GLVND components were - # requested. Use a policy to choose the default. - cmake_policy(GET CMP0072 _OpenGL_GL_POLICY) - if("x${_OpenGL_GL_POLICY}x" STREQUAL "xNEWx") - set(OpenGL_GL_PREFERENCE "GLVND") - else() - set(OpenGL_GL_PREFERENCE "LEGACY") - if("x${_OpenGL_GL_POLICY}x" STREQUAL "xx") - set(_OpenGL_GL_POLICY_WARN 1) - endif() - endif() - unset(_OpenGL_GL_POLICY) - endif() - - if("x${OpenGL_GL_PREFERENCE}x" STREQUAL "xGLVNDx" AND OPENGL_opengl_LIBRARY AND OPENGL_glx_LIBRARY) - # We can provide legacy GL using GLVND libraries. - # Do not use any legacy GL library. - set(OPENGL_gl_LIBRARY "") - else() - # We cannot provide legacy GL using GLVND libraries. - # Search for the legacy GL library. - find_library(OPENGL_gl_LIBRARY - NAMES GL MesaGL - PATHS /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/shlib - ${_OPENGL_LIB_PATH} - PATH_SUFFIXES libglvnd - ) - list(APPEND _OpenGL_CACHE_VARS OPENGL_gl_LIBRARY) - endif() - - if(_OpenGL_GL_POLICY_WARN AND OPENGL_gl_LIBRARY AND OPENGL_opengl_LIBRARY AND OPENGL_glx_LIBRARY) - cmake_policy(GET_WARNING CMP0072 _cmp0072_warning) - message(AUTHOR_WARNING - "${_cmp0072_warning}\n" - "FindOpenGL found both a legacy GL library:\n" - " OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}\n" - "and GLVND libraries for OpenGL and GLX:\n" - " OPENGL_opengl_LIBRARY: ${OPENGL_opengl_LIBRARY}\n" - " OPENGL_glx_LIBRARY: ${OPENGL_glx_LIBRARY}\n" - "OpenGL_GL_PREFERENCE has not been set to \"GLVND\" or \"LEGACY\", so for " - "compatibility with CMake 3.10 and below the legacy GL library will be used." - ) - endif() - unset(_OpenGL_GL_POLICY_WARN) - - # FPHSA cannot handle "this OR that is required", so we conditionally set what - # it must look for. First clear any previous config we might have done: - set(_OpenGL_REQUIRED_VARS) - - # now we append the libraries as appropriate. The complicated logic - # basically comes down to "use libOpenGL when we can, and add in specific - # context mechanisms when requested, or we need them to preserve the previous - # default where glx is always available." - if((NOT OPENGL_USE_EGL AND - NOT OPENGL_opengl_LIBRARY AND - OPENGL_glx_LIBRARY AND - NOT OPENGL_gl_LIBRARY) OR - (NOT OPENGL_USE_EGL AND - NOT OPENGL_USE_GLES3 AND - NOT OPENGL_USE_GLES2 AND - NOT OPENGL_glx_LIBRARY AND - NOT OPENGL_gl_LIBRARY) OR - (NOT OPENGL_USE_EGL AND - OPENGL_opengl_LIBRARY AND - OPENGL_glx_LIBRARY) OR - (NOT OPENGL_USE_GLES3 AND - NOT OPENGL_USE_GLES2 AND - OPENGL_USE_EGL)) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_opengl_LIBRARY) - endif() - - # GLVND GLX library. Preferred when available. - if((NOT OPENGL_USE_OPENGL AND - NOT OPENGL_USE_GLX AND - NOT OPENGL_USE_EGL AND - NOT OPENGL_USE_GLES3 AND - NOT OPENGL_USE_GLES2 AND - NOT OPENGL_glx_LIBRARY AND - NOT OPENGL_gl_LIBRARY) OR - ( OPENGL_USE_GLX AND - NOT OPENGL_USE_EGL AND - NOT OPENGL_USE_GLES3 AND - NOT OPENGL_USE_GLES2 AND - NOT OPENGL_glx_LIBRARY AND - NOT OPENGL_gl_LIBRARY) OR - (NOT OPENGL_USE_EGL AND - NOT OPENGL_USE_GLES3 AND - NOT OPENGL_USE_GLES2 AND - OPENGL_opengl_LIBRARY AND - OPENGL_glx_LIBRARY) OR - (OPENGL_USE_GLX AND OPENGL_USE_EGL)) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_glx_LIBRARY) - endif() - - # GLVND EGL library. - if(OPENGL_USE_EGL) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_egl_LIBRARY) - endif() - - # GLVND GLES2 library. - if(OPENGL_USE_GLES2) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles2_LIBRARY) - endif() - - # GLVND GLES3 library. - if(OPENGL_USE_GLES3) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles3_LIBRARY) - endif() - - # Old-style "libGL" library: used as a fallback when GLVND isn't available. - if((NOT OPENGL_USE_EGL AND - NOT OPENGL_opengl_LIBRARY AND - OPENGL_glx_LIBRARY AND - OPENGL_gl_LIBRARY) OR - (NOT OPENGL_USE_EGL AND - NOT OPENGL_glx_LIBRARY AND - OPENGL_gl_LIBRARY)) - list(PREPEND _OpenGL_REQUIRED_VARS OPENGL_gl_LIBRARY) - endif() - - # We always need the 'gl.h' include dir. - if(OPENGL_USE_EGL) - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_EGL_INCLUDE_DIR) - else() - list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR) - endif() - - unset(_OPENGL_INCLUDE_PATH) - unset(_OPENGL_LIB_PATH) - - find_library(OPENGL_glu_LIBRARY - NAMES GLU MesaGLU - PATHS ${OPENGL_gl_LIBRARY} - /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/shlib - ) -endif () - -if(OPENGL_xmesa_INCLUDE_DIR) - set( OPENGL_XMESA_FOUND "YES" ) -else() - set( OPENGL_XMESA_FOUND "NO" ) -endif() - -if(OPENGL_glu_LIBRARY AND (WIN32 OR OPENGL_GLU_INCLUDE_DIR)) - set( OPENGL_GLU_FOUND "YES" ) -else() - set( OPENGL_GLU_FOUND "NO" ) -endif() - -# OpenGL_OpenGL_FOUND is a bit unique in that it is okay if /either/ libOpenGL -# or libGL is found. -# Using libGL with libEGL is never okay, though; we handle that case later. -if(NOT OPENGL_opengl_LIBRARY AND NOT OPENGL_gl_LIBRARY) - set(OpenGL_OpenGL_FOUND FALSE) -else() - set(OpenGL_OpenGL_FOUND TRUE) -endif() - -if(OPENGL_glx_LIBRARY AND OPENGL_GLX_INCLUDE_DIR) - set(OpenGL_GLX_FOUND TRUE) -else() - set(OpenGL_GLX_FOUND FALSE) -endif() - -if(OPENGL_egl_LIBRARY AND OPENGL_EGL_INCLUDE_DIR) - set(OpenGL_EGL_FOUND TRUE) -else() - set(OpenGL_EGL_FOUND FALSE) -endif() - -if(OPENGL_gles2_LIBRARY AND OPENGL_GLES2_INCLUDE_DIR) - set(OpenGL_GLES2_FOUND TRUE) -else() - set(OpenGL_GLES2_FOUND FALSE) -endif() - -if(OPENGL_gles3_LIBRARY AND OPENGL_GLES3_INCLUDE_DIR) - set(OpenGL_GLES3_FOUND TRUE) -else() - set(OpenGL_GLES3_FOUND FALSE) -endif() - -# User-visible names should be plural. -if(OPENGL_EGL_INCLUDE_DIR) - set(OPENGL_EGL_INCLUDE_DIRS ${OPENGL_EGL_INCLUDE_DIR}) -endif() - -include(FindPackageHandleStandardArgs) -if (CMAKE_FIND_PACKAGE_NAME STREQUAL "GLU") - # FindGLU include()'s this module. It's an old pattern, but rather than - # trying to suppress this from outside the module (which is then sensitive to - # the contents, detect the case in this module and suppress it explicitly. - set(FPHSA_NAME_MISMATCHED 1) -endif () -find_package_handle_standard_args(OpenGL REQUIRED_VARS ${_OpenGL_REQUIRED_VARS} - HANDLE_COMPONENTS) -unset(FPHSA_NAME_MISMATCHED) -unset(_OpenGL_REQUIRED_VARS) - -# OpenGL:: targets -if(OPENGL_FOUND) - set(OPENGL_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR}) - - # ::OpenGL is a GLVND library, and thus Linux-only: we don't bother checking - # for a framework version of this library. - if(OPENGL_opengl_LIBRARY AND NOT TARGET OpenGL::OpenGL) - if(IS_ABSOLUTE "${OPENGL_opengl_LIBRARY}") - add_library(OpenGL::OpenGL UNKNOWN IMPORTED) - set_target_properties(OpenGL::OpenGL PROPERTIES IMPORTED_LOCATION - "${OPENGL_opengl_LIBRARY}") - else() - add_library(OpenGL::OpenGL INTERFACE IMPORTED) - set_target_properties(OpenGL::OpenGL PROPERTIES IMPORTED_LIBNAME - "${OPENGL_opengl_LIBRARY}") - endif() - set_target_properties(OpenGL::OpenGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_INCLUDE_DIR}") - set(_OpenGL_EGL_IMPL OpenGL::OpenGL) - endif() - - # ::GLX is a GLVND library, and thus Linux-only: we don't bother checking - # for a framework version of this library. - if(OpenGL_GLX_FOUND AND NOT TARGET OpenGL::GLX AND TARGET OpenGL::OpenGL) - if(IS_ABSOLUTE "${OPENGL_glx_LIBRARY}") - add_library(OpenGL::GLX UNKNOWN IMPORTED) - set_target_properties(OpenGL::GLX PROPERTIES IMPORTED_LOCATION - "${OPENGL_glx_LIBRARY}") - else() - add_library(OpenGL::GLX INTERFACE IMPORTED) - set_target_properties(OpenGL::GLX PROPERTIES IMPORTED_LIBNAME - "${OPENGL_glx_LIBRARY}") - endif() - set_target_properties(OpenGL::GLX PROPERTIES INTERFACE_LINK_LIBRARIES - OpenGL::OpenGL) - set_target_properties(OpenGL::GLX PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_GLX_INCLUDE_DIR}") - list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLX_INCLUDE_DIR}) - endif() - - # ::GLES2 is a GLVND library, and thus Linux-only: we don't bother checking - # for a framework version of this library. - if(OpenGL_GLES2_FOUND AND NOT TARGET OpenGL::GLES2) - - # Initialize target - if(NOT OPENGL_gles2_LIBRARY) - add_library(OpenGL::GLES2 INTERFACE IMPORTED) - else() - if(IS_ABSOLUTE "${OPENGL_gles2_LIBRARY}") - add_library(OpenGL::GLES2 UNKNOWN IMPORTED) - set_target_properties(OpenGL::GLES2 PROPERTIES - IMPORTED_LOCATION "${OPENGL_gles2_LIBRARY}" - ) - else() - add_library(OpenGL::GLES2 INTERFACE IMPORTED) - set_target_properties(OpenGL::GLES2 PROPERTIES - IMPORTED_LIBNAME "${OPENGL_gles2_LIBRARY}" - ) - endif() - endif() - - # Attach target properties - set_target_properties(OpenGL::GLES2 - PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_GLES2_INCLUDE_DIR}" - ) - list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLES2_INCLUDE_DIR}) - - if (OPENGL_USE_GLES2) - set(_OpenGL_EGL_IMPL OpenGL::GLES2) - endif () - - endif() - - # ::GLES3 is a GLVND library, and thus Linux-only: we don't bother checking - # for a framework version of this library. - if(OpenGL_GLES3_FOUND AND NOT TARGET OpenGL::GLES3) - - # Initialize target - if(NOT OPENGL_gles3_LIBRARY) - add_library(OpenGL::GLES3 INTERFACE IMPORTED) - else() - if(IS_ABSOLUTE "${OPENGL_gles3_LIBRARY}") - add_library(OpenGL::GLES3 UNKNOWN IMPORTED) - set_target_properties(OpenGL::GLES3 PROPERTIES - IMPORTED_LOCATION "${OPENGL_gles3_LIBRARY}" - ) - else() - add_library(OpenGL::GLES3 INTERFACE IMPORTED) - set_target_properties(OpenGL::GLES3 PROPERTIES - IMPORTED_LIBNAME "${OPENGL_gles3_LIBRARY}" - ) - endif() - endif() - - # Attach target properties - set_target_properties(OpenGL::GLES3 PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_GLES3_INCLUDE_DIR}" - ) - list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLES3_INCLUDE_DIR}) - - if (OPENGL_USE_GLES3) - set(_OpenGL_EGL_IMPL OpenGL::GLES3) - endif () - - endif() - - if(OPENGL_gl_LIBRARY AND NOT TARGET OpenGL::GL) - # A legacy GL library is available, so use it for the legacy GL target. - if(IS_ABSOLUTE "${OPENGL_gl_LIBRARY}") - add_library(OpenGL::GL UNKNOWN IMPORTED) - set_target_properties(OpenGL::GL PROPERTIES - IMPORTED_LOCATION "${OPENGL_gl_LIBRARY}") - else() - add_library(OpenGL::GL INTERFACE IMPORTED) - set_target_properties(OpenGL::GL PROPERTIES - IMPORTED_LIBNAME "${OPENGL_gl_LIBRARY}") - endif() - set_target_properties(OpenGL::GL PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}") - elseif(NOT TARGET OpenGL::GL AND TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) - # A legacy GL library is not available, but we can provide the legacy GL - # target using GLVND OpenGL+GLX. - add_library(OpenGL::GL INTERFACE IMPORTED) - set_target_properties(OpenGL::GL PROPERTIES INTERFACE_LINK_LIBRARIES - OpenGL::OpenGL) - set_property(TARGET OpenGL::GL APPEND PROPERTY INTERFACE_LINK_LIBRARIES - OpenGL::GLX) - set_target_properties(OpenGL::GL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_INCLUDE_DIR}") - endif() - - # ::EGL is a GLVND library, and thus Linux-only: we don't bother checking - # for a framework version of this library. - # Note we test whether _OpenGL_EGL_IMPL is set. Based on the OpenGL implementation, - # _OpenGL_EGL_IMPL will be one of OpenGL::OpenGL, OpenGL::GLES2, OpenGL::GLES3 - if(_OpenGL_EGL_IMPL AND OpenGL_EGL_FOUND AND NOT TARGET OpenGL::EGL) - if(IS_ABSOLUTE "${OPENGL_egl_LIBRARY}") - add_library(OpenGL::EGL UNKNOWN IMPORTED) - set_target_properties(OpenGL::EGL PROPERTIES IMPORTED_LOCATION - "${OPENGL_egl_LIBRARY}") - else() - add_library(OpenGL::EGL INTERFACE IMPORTED) - set_target_properties(OpenGL::EGL PROPERTIES IMPORTED_LIBNAME - "${OPENGL_egl_LIBRARY}") - endif() - set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_LINK_LIBRARIES - "${_OpenGL_EGL_IMPL}") - # Note that EGL's include directory is different from OpenGL/GLX's! - set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_EGL_INCLUDE_DIR}") - list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_EGL_INCLUDE_DIR}) - endif() - - if(OPENGL_GLU_FOUND AND NOT TARGET OpenGL::GLU) - if(IS_ABSOLUTE "${OPENGL_glu_LIBRARY}") - add_library(OpenGL::GLU UNKNOWN IMPORTED) - set_target_properties(OpenGL::GLU PROPERTIES - IMPORTED_LOCATION "${OPENGL_glu_LIBRARY}") - else() - add_library(OpenGL::GLU INTERFACE IMPORTED) - set_target_properties(OpenGL::GLU PROPERTIES - IMPORTED_LIBNAME "${OPENGL_glu_LIBRARY}") - endif() - set_target_properties(OpenGL::GLU PROPERTIES - INTERFACE_LINK_LIBRARIES OpenGL::GL) - # Note that GLU's include directory may be different from OpenGL's! - set_target_properties(OpenGL::GLU PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${OPENGL_GLU_INCLUDE_DIR}") - list(APPEND OPENGL_INCLUDE_DIRS ${OPENGL_GLU_INCLUDE_DIR}) - endif() - - # OPENGL_LIBRARIES mirrors OpenGL::GL's logic ... - if(OPENGL_gl_LIBRARY) - set(OPENGL_LIBRARIES ${OPENGL_gl_LIBRARY}) - elseif(TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) - set(OPENGL_LIBRARIES ${OPENGL_opengl_LIBRARY} ${OPENGL_glx_LIBRARY}) - else() - set(OPENGL_LIBRARIES "") - endif() - # ... and also includes GLU, if available. - if(TARGET OpenGL::GLU) - list(APPEND OPENGL_LIBRARIES ${OPENGL_glu_LIBRARY}) - endif() -endif() - -list(REMOVE_DUPLICATES OPENGL_INCLUDE_DIRS) - -# This deprecated setting is for backward compatibility with CMake1.4 -set(OPENGL_LIBRARY ${OPENGL_LIBRARIES}) -# This deprecated setting is for backward compatibility with CMake1.4 -set(OPENGL_INCLUDE_PATH ${OPENGL_INCLUDE_DIR}) - -mark_as_advanced(${_OpenGL_CACHE_VARS}) -unset(_OpenGL_CACHE_VARS) diff --git a/cmake/RPi.cmake b/cmake/RPi.cmake new file mode 100644 index 000000000..ab2ac677c --- /dev/null +++ b/cmake/RPi.cmake @@ -0,0 +1,29 @@ +# Function to check if a command exists +function(command_exists CMD RESULT_VAR) + execute_process(COMMAND which ${CMD} OUTPUT_VARIABLE CMD_PATH RESULT_VARIABLE CMD_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) + if(CMD_RESULT EQUAL 0) + set(${RESULT_VAR} TRUE PARENT_SCOPE) + else() + set(${RESULT_VAR} FALSE PARENT_SCOPE) + endif() +endfunction() + +function(check_gles3_support SUPPORTS_GLES3) + # Initialize variable + set(SUPPORTS_GLES3 OFF PARENT_SCOPE) + + # Run the Bash one-liner to get the SoC version as a decimal number + execute_process( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/cmake/get_soc_version.sh + OUTPUT_VARIABLE SOC_VERSION_DEC + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS, "soc verision: ${SOC_VERSION_DEC}") + + # Determine GLES3 support based on the SoC version + if(SOC_VERSION_DEC GREATER_EQUAL 3) + # BCM2711 or newer + message(STATUS, "GLES3 supported") + set(SUPPORTS_GLES3 ON PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/get_soc_version.sh b/cmake/get_soc_version.sh new file mode 100755 index 000000000..688ad6935 --- /dev/null +++ b/cmake/get_soc_version.sh @@ -0,0 +1,2 @@ + #!/bin/bash + awk '/Revision/ { rev = "0x" substr($3, length($3)-3, 4); printf "%d\n", (rev / 4096) % 16 }' /proc/cpuinfo From eb9ccb7948e816f0564416d87c30b3afd7c6dbea Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 19:20:44 +0100 Subject: [PATCH 06/20] Remove debug messages --- cmake/RPi.cmake | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmake/RPi.cmake b/cmake/RPi.cmake index ab2ac677c..424932d19 100644 --- a/cmake/RPi.cmake +++ b/cmake/RPi.cmake @@ -18,12 +18,9 @@ function(check_gles3_support SUPPORTS_GLES3) OUTPUT_VARIABLE SOC_VERSION_DEC OUTPUT_STRIP_TRAILING_WHITESPACE ) - message(STATUS, "soc verision: ${SOC_VERSION_DEC}") # Determine GLES3 support based on the SoC version if(SOC_VERSION_DEC GREATER_EQUAL 3) - # BCM2711 or newer - message(STATUS, "GLES3 supported") set(SUPPORTS_GLES3 ON PARENT_SCOPE) endif() endfunction() From db92fa94ba776c9cb2dd3aa4d27f32a426479ce2 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Sun, 2 Feb 2025 21:35:50 +0100 Subject: [PATCH 07/20] Fix message --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f0bc8cdc..5d351a26f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ if ((CMAKE_SYSTEM_NAME STREQUAL Linux) AND NOT EMSCRIPTEN AND NOT ANDROID) set(SE_FORCE_GLES3 TRUE) add_definitions(-DSE_FORCE_GLES3=1) else() - message("GLES3 supported") + message("GLES2 supported") set(SE_FORCE_GLES2 TRUE) add_definitions(-DSE_FORCE_GLES2=1) endif() From 413693fdc0e0d444d89b7ce885689c3df4353b44 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Fri, 14 Feb 2025 21:55:54 +0100 Subject: [PATCH 08/20] Revert CMake version and find gles with module --- CMakeLists.txt | 28 +++-- cmake/FindOpenGLES.cmake | 262 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 cmake/FindOpenGLES.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d351a26f..109df07dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ -cmake_minimum_required(VERSION 3.27) # Needed to set MSVC Runtime type +cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) -include("cmake/RPi.cmake") +include(RPi) if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) @@ -91,7 +92,13 @@ if(ANDROID) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() if(NOT IOS) - find_package(OpenGL) + if(IS_ARM AND IS_VIDEOCORE) + # For Raspberry Pi, use OpenGLES + find_package(OpenGLES REQUIRED) + else() + # For other platforms, use regular OpenGL + find_package(OpenGL) + endif() endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) @@ -275,11 +282,17 @@ else() if (SE_PLATFORM_LINUX OR SE_PLATFORM_FREEBSD) target_include_directories(sokol PRIVATE "${X11_INCLUDE_DIR}") if(SE_FORCE_GLES2) - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::GLES2 EGL dl) + find_package(OpenGLES COMPONENTS V2 REQUIRED) + if(OpenGLES_V2_FOUND) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv2 EGL dl) + endif() elseif(SE_FORCE_GLES3) - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::GLES3 EGL dl) + find_package(OpenGLES COMPONENTS V3 REQUIRED) + if(OpenGLES_V3_FOUND) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv3 EGL dl) + endif() else() - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::OpenGL dl) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::OpenGL dl) endif() endif() if (CMAKE_SYSTEM_NAME STREQUAL Linux) @@ -499,4 +512,5 @@ endif() install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" - BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}/Applications") + BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}/Applications" +) diff --git a/cmake/FindOpenGLES.cmake b/cmake/FindOpenGLES.cmake new file mode 100644 index 000000000..e909e3529 --- /dev/null +++ b/cmake/FindOpenGLES.cmake @@ -0,0 +1,262 @@ +# Copyright 2020-2021, Collabora, Ltd. +# +# SPDX-License-Identifier: BSL-1.0 +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Original Author: +# 2020-2021, Rylie Pavlik + +#[[.rst: +FindOpenGLES +--------------- + +Find the OpenGL ES graphics API. + +Components +^^^^^^^^^^ + +The following components are supported: + +* ``V1`` - OpenGL ES 1 (including emulation on OpenGL ES 2) +* ``V2`` - OpenGL ES 2 +* ``V3`` - OpenGL ES 3 +* ``V31` - OpenGL ES 3.1 - same as 3 but checking also for gl31.h +* ``V32` - OpenGL ES 3.2 - same as 3 but checking also for gl32.h + +If none are specified, the default is ``V2``. + +Targets +^^^^^^^ + +If successful, some subset of the following imported targets are created. + +* ``OpenGLES::OpenGLESv1`` +* ``OpenGLES::OpenGLESv2`` +* ``OpenGLES::OpenGLESv3`` +* ``OpenGLES::OpenGLESv31`` +* ``OpenGLES::OpenGLESv32`` + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variable may also be set to assist/control the operation of this module: + +``OpenGLES_ROOT_DIR`` + The root to search for OpenGLES. +#]] + +set(OpenGLES_ROOT_DIR + "${OpenGLES_ROOT_DIR}" + CACHE PATH "Root to search for OpenGLES") + +if(NOT OpenGLES_FIND_COMPONENTS) + set(OpenGLES_FIND_COMPONENTS V2) +endif() + +if(NOT ANDROID) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + set(_old_prefix_path "${CMAKE_PREFIX_PATH}") + # So pkg-config uses OpenGLES_ROOT_DIR too. + if(OpenGLES_ROOT_DIR) + list(APPEND CMAKE_PREFIX_PATH ${OpenGLES_ROOT_DIR}) + endif() + pkg_check_modules(PC_glesv1_cm QUIET glesv1_cm) + pkg_check_modules(PC_glesv2 QUIET glesv2) + # Restore + set(CMAKE_PREFIX_PATH "${_old_prefix_path}") + endif() +endif() + +find_path( + OpenGLES_V1_INCLUDE_DIR + NAMES GLES/gl.h + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${PC_glesv2_INCLUDE_DIRS} ${PC_glesv1_cm_INCLUDE_DIRS} + PATH_SUFFIXES include) +find_path( + OpenGLES_V2_INCLUDE_DIR + NAMES GLES2/gl2.h + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${PC_glesv2_INCLUDE_DIRS} ${PC_glesv1_cm_INCLUDE_DIRS} + PATH_SUFFIXES include) +find_path( + OpenGLES_V3_INCLUDE_DIR + NAMES GLES3/gl3.h + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${OpenGLES_V1_INCLUDE_DIR} ${OpenGLES_V2_INCLUDE_DIR} + ${PC_glesv2_INCLUDE_DIRS} ${PC_glesv1_cm_INCLUDE_DIRS} + PATH_SUFFIXES include) +find_path( + OpenGLES_V31_INCLUDE_DIR + NAMES GLES3/gl31.h + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${OpenGLES_V1_INCLUDE_DIR} ${OpenGLES_V2_INCLUDE_DIR} + ${OpenGLES_V3_INCLUDE_DIR} ${PC_glesv2_INCLUDE_DIRS} + ${PC_glesv1_cm_INCLUDE_DIRS} + PATH_SUFFIXES include) +find_path( + OpenGLES_V32_INCLUDE_DIR + NAMES GLES3/gl32.h + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${OpenGLES_V1_INCLUDE_DIR} ${OpenGLES_V2_INCLUDE_DIR} + ${OpenGLES_V3_INCLUDE_DIR} ${OpenGLES_V31_INCLUDE_DIR} + ${PC_glesv2_INCLUDE_DIRS} ${PC_glesv1_cm_INCLUDE_DIRS} + PATH_SUFFIXES include) + +find_library( + OpenGLES_V1_LIBRARY + NAMES GLES GLESv1_CM + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${PC_glesv1_cm_LIBRARY_DIRS} + PATH_SUFFIXES lib) +find_library( + OpenGLES_V2_LIBRARY + NAMES GLESv2 OpenGLES # for Apple framework + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${PC_glesv2_LIBRARY_DIRS} + PATH_SUFFIXES lib) +find_library( + OpenGLES_V3_LIBRARY + NAMES GLESv3 + PATHS ${OpenGLES_ROOT_DIR} + HINTS ${PC_glesv2_LIBRARY_DIRS} + PATH_SUFFIXES lib) + +if(OpenGLES_V2_LIBRARY AND NOT OpenGLES_V3_LIBRARY) + set(OpenGLES_V3_LIBRARY ${OpenGLES_V2_LIBRARY}) +endif() + +set(_gles_required_vars) +foreach(_comp IN LISTS OpenGLES_FIND_COMPONENTS) + if(_comp STREQUAL "V1") + list(APPEND _gles_required_vars OpenGLES_V1_LIBRARY + OpenGLES_V1_INCLUDE_DIR) + if(OpenGLES_V1_INCLUDE_DIR AND OpenGLES_V1_LIBRARY) + set(OpenGLES_${_comp}_FOUND TRUE) + else() + set(OpenGLES_${_comp}_FOUND FALSE) + endif() + elseif(_comp STREQUAL "V2") + list(APPEND _gles_required_vars OpenGLES_V2_LIBRARY + OpenGLES_V2_INCLUDE_DIR) + if(OpenGLES_V2_INCLUDE_DIR AND OpenGLES_V2_LIBRARY) + set(OpenGLES_${_comp}_FOUND TRUE) + else() + set(OpenGLES_${_comp}_FOUND FALSE) + endif() + elseif(_comp STREQUAL "V3") + list(APPEND _gles_required_vars OpenGLES_V3_LIBRARY + OpenGLES_V3_INCLUDE_DIR) + if(OpenGLES_V3_INCLUDE_DIR AND OpenGLES_V3_LIBRARY) + set(OpenGLES_${_comp}_FOUND TRUE) + else() + set(OpenGLES_${_comp}_FOUND FALSE) + endif() + elseif(_comp STREQUAL "V31") + list(APPEND _gles_required_vars OpenGLES_V3_LIBRARY + OpenGLES_V31_INCLUDE_DIR) + + if(OpenGLES_V31_INCLUDE_DIR AND OpenGLES_V3_LIBRARY) + set(OpenGLES_${_comp}_FOUND TRUE) + else() + set(OpenGLES_${_comp}_FOUND FALSE) + endif() + elseif(_comp STREQUAL "V32") + list(APPEND _gles_required_vars OpenGLES_V3_LIBRARY + OpenGLES_V32_INCLUDE_DIR) + if(OpenGLES_V32_INCLUDE_DIR AND OpenGLES_V3_LIBRARY) + set(OpenGLES_${_comp}_FOUND TRUE) + else() + set(OpenGLES_${_comp}_FOUND FALSE) + endif() + else() + message( + WARNING "${_comp} is not a recognized OpenGL-ES component/version") + set(OpenGLES_${_comp}_FOUND FALSE) + endif() +endforeach() +if(_gles_required_vars) + list(REMOVE_DUPLICATES _gles_required_vars) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + OpenGLES + REQUIRED_VARS ${_gles_required_vars} + HANDLE_COMPONENTS) +if(OpenGLES_FOUND) + if(OpenGLES_V1_FOUND AND NOT TARGET OpenGLES::OpenGLESv1) + add_library(OpenGLES::OpenGLESv1 SHARED IMPORTED) + + set_target_properties( + OpenGLES::OpenGLESv1 + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OpenGLES_V1_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${OpenGLES_V1_LIBRARY}) + endif() + if(OpenGLES_V2_FOUND AND NOT TARGET OpenGLES::OpenGLESv2) + add_library(OpenGLES::OpenGLESv2 SHARED IMPORTED) + + set_target_properties( + OpenGLES::OpenGLESv2 + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OpenGLES_V2_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${OpenGLES_V2_LIBRARY}) + endif() + if(OpenGLES_V3_FOUND) + if(NOT TARGET OpenGLES::OpenGLESv3) + add_library(OpenGLES::OpenGLESv3 SHARED IMPORTED) + + set_target_properties( + OpenGLES::OpenGLESv3 + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OpenGLES_V3_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${OpenGLES_V3_LIBRARY}) + endif() + if(OpenGLES_V31_FOUND AND NOT TARGET OpenGLES::OpenGLESv31) + add_library(OpenGLES::OpenGLESv31 SHARED IMPORTED) + + set_target_properties( + OpenGLES::OpenGLESv31 + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OpenGLES_V31_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${OpenGLES_V3_LIBRARY}) + endif() + if(OpenGLES_V32_FOUND AND NOT TARGET OpenGLES::OpenGLESv32) + add_library(OpenGLES::OpenGLESv32 SHARED IMPORTED) + + set_target_properties( + OpenGLES::OpenGLESv32 + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${OpenGLES_V32_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${OpenGLES_V3_LIBRARY}) + endif() + endif() + mark_as_advanced( + OpenGLES_V1_LIBRARY + OpenGLES_V1_INCLUDE_DIR + OpenGLES_V2_LIBRARY + OpenGLES_V2_INCLUDE_DIR + OpenGLES_V3_LIBRARY + OpenGLES_V3_INCLUDE_DIR + OpenGLES_V31_INCLUDE_DIR + OpenGLES_V32_INCLUDE_DIR) +endif() +mark_as_advanced(OpenGLES_ROOT_DIR) + +include(FeatureSummary) +set_package_properties( + OpenGLES PROPERTIES + URL "https://www.khronos.org/opengles/" + DESCRIPTION + "A cross-platform graphics API, specialized for mobile and embedded, defined as a subset of desktop OpenGL." +) From 80d06549070c67bb7e6b75b7f03449fd3793d590 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Mon, 17 Feb 2025 21:17:40 +0100 Subject: [PATCH 09/20] Prep for github action --- CMakeLists.txt | 49 +++++++++++++++++----------------------- README.md | 36 +++++++++++++++++------------ cmake/LICENSE_1_0.txt | 23 +++++++++++++++++++ cmake/RPi.cmake | 26 --------------------- cmake/get_soc_version.sh | 2 -- 5 files changed, 66 insertions(+), 70 deletions(-) create mode 100644 cmake/LICENSE_1_0.txt delete mode 100644 cmake/RPi.cmake delete mode 100755 cmake/get_soc_version.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 109df07dc..b0fdf62ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.15) # Needed to set MSVC Runtime type list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) -include(RPi) if(APPLE AND (NOT IOS) AND (NOT ANDROID)) set(MACOS 1) @@ -16,14 +15,10 @@ else() project(SkyEmu C CXX) endif() -if(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7l|aarch64|arm") - set(IS_ARM TRUE) -else() - set(IS_ARM FALSE) +if(UNIX AND NOT APPLE AND (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")) + set(SE_PLATFORM_ARM64_LINUX 1) endif() -command_exists("vcgencmd" IS_VIDEOCORE) - set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) set(LINK_LIBS "") @@ -37,6 +32,9 @@ option(USE_SYSTEM_CURL "Use the system's libcurl package" OFF) option(USE_SYSTEM_OPENSSL "Use the system's OpenSSL package" OFF) option(USE_SYSTEM_SDL2 "Use the system's SDL2 package" OFF) +option(USE_GLES3 "Use OpenGLES3" OFF) +option(USE_GLES2 "Use OpenGLES2" OFF) + if (EMSCRIPTEN) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ELIMINATE\_DUPLICATE\_FUNCTIONS=1 -s ENVIRONMENT=web -s ASSERTIONS=0 -s WASM=1 -DSE_PLATFORM_WEB --shell-file ${PROJECT_SOURCE_DIR}/src/shell.html -s USE_CLOSURE_COMPILER=0 ") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ELIMINATE\_DUPLICATE\_FUNCTIONS=1 -s ENVIRONMENT=web -s ASSERTIONS=0 -s WASM=1 -DSE_PLATFORM_WEB --shell-file ${PROJECT_SOURCE_DIR}/src/shell.html -s USE_CLOSURE_COMPILER=0 ") @@ -92,7 +90,7 @@ if(ANDROID) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() if(NOT IOS) - if(IS_ARM AND IS_VIDEOCORE) + if(SE_PLATFORM_ARM64_LINUX AND USE_GLES3) # For Raspberry Pi, use OpenGLES find_package(OpenGLES REQUIRED) else() @@ -109,18 +107,13 @@ if ((CMAKE_SYSTEM_NAME STREQUAL Linux) AND NOT EMSCRIPTEN AND NOT ANDROID) find_package(ALSA REQUIRED) set(SE_PLATFORM_LINUX TRUE) - # Check for GLES3 support on the Raspberry Pi - if (IS_ARM AND IS_VIDEOCORE) - check_gles3_support(SUPPORTS_GLES3) - if(SUPPORTS_GLES3) - message("GLES3 supported") - set(SE_FORCE_GLES3 TRUE) - add_definitions(-DSE_FORCE_GLES3=1) - else() - message("GLES2 supported") - set(SE_FORCE_GLES2 TRUE) - add_definitions(-DSE_FORCE_GLES2=1) - endif() + # GLES3 support on the Raspberry Pi + if (SE_PLATFORM_ARM64_LINUX AND USE_GLES3) + set(SE_FORCE_GLES3 TRUE) + add_definitions(-DSE_FORCE_GLES3=1) + elseif (SE_PLATFORM_ARM64_LINUX AND USE_GLES2) + set(SE_FORCE_GLES2 TRUE) + add_definitions(-DSE_FORCE_GLES2=1) endif() add_definitions(-DSE_PLATFORM_LINUX=1) endif() @@ -282,15 +275,15 @@ else() if (SE_PLATFORM_LINUX OR SE_PLATFORM_FREEBSD) target_include_directories(sokol PRIVATE "${X11_INCLUDE_DIR}") if(SE_FORCE_GLES2) - find_package(OpenGLES COMPONENTS V2 REQUIRED) - if(OpenGLES_V2_FOUND) - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv2 EGL dl) - endif() + find_package(OpenGLES COMPONENTS V2 REQUIRED) + if(OpenGLES_V2_FOUND) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv2 EGL dl) + endif() elseif(SE_FORCE_GLES3) - find_package(OpenGLES COMPONENTS V3 REQUIRED) - if(OpenGLES_V3_FOUND) - target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv3 EGL dl) - endif() + find_package(OpenGLES COMPONENTS V3 REQUIRED) + if(OpenGLES_V3_FOUND) + target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGLES::OpenGLESv3 EGL dl) + endif() else() target_link_libraries(sokol INTERFACE ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} OpenGL::OpenGL dl) endif() diff --git a/README.md b/README.md index a79806ff1..d5a723856 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![iOS Build](https://github.com/skylersaleh/SkyEmu/actions/workflows/deploy_ios.yml/badge.svg) ![FreeBSD Build](https://github.com/skylersaleh/SkyEmu/actions/workflows/deploy_freebsd.yml/badge.svg) ![Web Build](https://github.com/skylersaleh/SkyEmu/actions/workflows/deploy_web.yml/badge.svg) -![Discord Shield](https://discordapp.com/api/guilds/1131322341645893783/widget.png?style=shield) +![Discord Shield](https://discordapp.com/api/guilds/1131322341645893783/widget.png?style=shield) ![SkyEmu](https://github.com/skylersaleh/SkyEmu/assets/7118296/03d74d15-070c-4353-8f37-263847bc0750) @@ -21,7 +21,7 @@ SkyEmu is a low level GameBoy, GameBoy Color, Game Boy Advance, and DS emulator. - Game Controller and Rumble Support with configureable keybinds - 4x Persistent Save State Slots with screenshot preview - Game fastforward and rewind support (supporting [very long rewind times](https://www.youtube.com/watch?v=Sfc_1NKbiKg)) -- Action Replay Cheat Code Engine +- Action Replay Cheat Code Engine - Localization in Armenian, Chinese, Danish, Dutch, English, German, Greek, Italian, Polish, and Russian - Support for emulating the Real Time Clock and Solar Sensor - CPU, MMIO, and Memory Debuggers @@ -37,11 +37,11 @@ The latest version of the emulator can also be played without installing at the [https://web.skyemu.app/](https://web.skyemu.app/) -The web app emulates everything locally on your machine using web assembly and javascript. Because of this all files stay local to your machine like the regular native build, however there is a performance cost to performing the emulation inside a web browser. +The web app emulates everything locally on your machine using web assembly and javascript. Because of this all files stay local to your machine like the regular native build, however there is a performance cost to performing the emulation inside a web browser. On Mobile platforms it is recommended to add this to the home screen and launch from there. This will prevent the web browser from auto deleting save files and will make the app full screen. -Note: Platform BIOS/Firmware files are not required as SkyEmu bundles open source replacement BIOS/stubs. However, it is strongly recommended to dump official BIOS/firmware as the open source replacements lack many of the features of the native firmware/BIOS (such as colorizing GB games and the startup splashes) and are not as accurate. +Note: Platform BIOS/Firmware files are not required as SkyEmu bundles open source replacement BIOS/stubs. However, it is strongly recommended to dump official BIOS/firmware as the open source replacements lack many of the features of the native firmware/BIOS (such as colorizing GB games and the startup splashes) and are not as accurate. ## Discord Server @@ -57,11 +57,11 @@ Note: Platform BIOS/Firmware files are not required as SkyEmu bundles open sourc - U: L shoulder - I: R shoulder -On mobile platforms an onscreen touch screen controller is provided. +On mobile platforms an onscreen touch screen controller is provided. ## Loading save files and BIOSs -On web builds save files and the BIOS can be loaded by dragging them onto the page or loading them using the ROM file picker. The GBA BIOS must be named `gba_bios.bin` for the emulator to pick it up. Save files must be named the name of the rom file with the extension `.sav`. So for example if the ROM was `MyRomFile.gba` the save file must be called `MyRomFile.sav`. +On web builds save files and the BIOS can be loaded by dragging them onto the page or loading them using the ROM file picker. The GBA BIOS must be named `gba_bios.bin` for the emulator to pick it up. Save files must be named the name of the rom file with the extension `.sav`. So for example if the ROM was `MyRomFile.gba` the save file must be called `MyRomFile.sav`. On native builds the above naming convention still applies, but the save/BIOS files must be instead located in the same folder as the ROM file, instead of being dragged or loaded in the emulator itself. @@ -69,27 +69,35 @@ On native builds the above naming convention still applies, but the save/BIOS fi Native builds are experimental currently but can be built using the following commands: +```bash +mkdir build +cd build +cmake .. +cmake --build . ``` + +For Raspberry Pi, you'll want to enable OpenGLES support: +```bash mkdir build cd build -cmake .. -cmake --build . +cmake .. -DUSE_GLES3=ON # or -DUSE_GLES2=ON if GLES3 isn't supported +cmake --build . ``` The output binaries should be in the build/bin folder -Native builds support loading roms through the command line by specifying the path to the ROM as the first argument: +Native builds support loading roms through the command line by specifying the path to the ROM as the first argument: -``` +```bash ./SkyEmu path/to/rom.gba ``` ## Accuracy/Compatibility -SkyEmu has been tested on 100s of ROMs and most common games should be playable with no to minor bugs currently. However, the GBA emulation is significantly more accurate than the GB/GBC emulation. +SkyEmu has been tested on 100s of ROMs and most common games should be playable with no to minor bugs currently. However, the GBA emulation is significantly more accurate than the GB/GBC emulation. **GBA**: -- Per Pixel PPU Implementation capable of both scan line and mid scan line effects (SkyEmu and NanoBoyAdvance are the only GBA emulators released to support this) +- Per Pixel PPU Implementation capable of both scan line and mid scan line effects (SkyEmu and NanoBoyAdvance are the only GBA emulators released to support this) - Passes the AGS Aging Test ROM (SkyEmu is the second SW based GBA emulator to ever pass this) - Can run difficult to emulate GBA games such as the NES Classics Series, Golden Sun and Hello Kitty Miracle Fashion Maker - 100% Passes all ArmWrestler Tests @@ -98,7 +106,7 @@ SkyEmu has been tested on 100s of ROMs and most common games should be playable - Passes 2020/2020 GBA Suite timing tests when utilizing the official GBA BIOS (SkyEmu is one of the few emulators capable of passing this test). - Full instruction pipeline and prefetch emulation -**GB**: +**GB**: - Passes all of Blargg's CPU instruction tests - Passes DMG and GBC acid2 PPU conformance tests - Passes MBCtest @@ -106,7 +114,7 @@ SkyEmu has been tested on 100s of ROMs and most common games should be playable - Anti-aliased audio synthesis with support for APU changes per sample (supports Pikachu's voice in Pokemon Yellow/Pokemon Pinball) ## Birds of a Feather -- [**Pokemon Bot**](https://github.com/OFFTKP/pokemon-bot): A discord bot that can connect to SkyEmu to allow your discord users to play GB/GBC/GBA/NDS games. +- [**Pokemon Bot**](https://github.com/OFFTKP/pokemon-bot): A discord bot that can connect to SkyEmu to allow your discord users to play GB/GBC/GBA/NDS games. - [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): Panda themed HLE 3DS emulator - [**NanoBoyAdvance**](https://github.com/nba-emu/NanoBoyAdvance): A Game Boy Advance emulator focusing on hardware research and cycle-accurate emulation - [**Dust**](https://github.com/kelpsyberry/dust): DS emulator for desktop devices and the web diff --git a/cmake/LICENSE_1_0.txt b/cmake/LICENSE_1_0.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/cmake/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/cmake/RPi.cmake b/cmake/RPi.cmake deleted file mode 100644 index 424932d19..000000000 --- a/cmake/RPi.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# Function to check if a command exists -function(command_exists CMD RESULT_VAR) - execute_process(COMMAND which ${CMD} OUTPUT_VARIABLE CMD_PATH RESULT_VARIABLE CMD_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) - if(CMD_RESULT EQUAL 0) - set(${RESULT_VAR} TRUE PARENT_SCOPE) - else() - set(${RESULT_VAR} FALSE PARENT_SCOPE) - endif() -endfunction() - -function(check_gles3_support SUPPORTS_GLES3) - # Initialize variable - set(SUPPORTS_GLES3 OFF PARENT_SCOPE) - - # Run the Bash one-liner to get the SoC version as a decimal number - execute_process( - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/cmake/get_soc_version.sh - OUTPUT_VARIABLE SOC_VERSION_DEC - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Determine GLES3 support based on the SoC version - if(SOC_VERSION_DEC GREATER_EQUAL 3) - set(SUPPORTS_GLES3 ON PARENT_SCOPE) - endif() -endfunction() diff --git a/cmake/get_soc_version.sh b/cmake/get_soc_version.sh deleted file mode 100755 index 688ad6935..000000000 --- a/cmake/get_soc_version.sh +++ /dev/null @@ -1,2 +0,0 @@ - #!/bin/bash - awk '/Revision/ { rev = "0x" substr($3, length($3)-3, 4); printf "%d\n", (rev / 4096) % 16 }' /proc/cpuinfo From 64e0cd6cf68110fbec005c443a3564c6b24e1d19 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Mon, 17 Feb 2025 21:27:48 +0100 Subject: [PATCH 10/20] Try adding action for linux arm64 --- .github/workflows/deploy_arm64_linux.yml | 37 ++++++++++++++++++++++++ .github/workflows/deploy_linux.yml | 13 ++++----- 2 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/deploy_arm64_linux.yml diff --git a/.github/workflows/deploy_arm64_linux.yml b/.github/workflows/deploy_arm64_linux.yml new file mode 100644 index 000000000..1273e5efd --- /dev/null +++ b/.github/workflows/deploy_arm64_linux.yml @@ -0,0 +1,37 @@ +name: Build Linux Arm64 +on: [push, pull_request] +jobs: + build-and-deploy: + runs-on: ubuntu-22.04-arm + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v2.3.1 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libx11-dev libxi-dev libxrandr-dev libxinerama-dev libxcursor-dev + sudo apt-get install -y libgl1-mesa-dev libegl1-mesa-dev libasound2-dev + - name: Configure & Build 🔧 + run: | + mkdir build + cd build + cmake ..-DUSE_GLES3=ON && cmake --build . + + - name: GH Release 🚀 + # You may pin to the exact commit or the version. + uses: actions/upload-artifact@v4 + with: + name: LinuxArm64Release + path: build/bin/ + #uses: softprops/action-gh-release@v0.1.5 + #with: + # # Note-worthy description of changes in release + # # body: # optional + # # Path to load note-worthy description of changes in release from + # # body_path: # optional + # # Gives the release a custom name. Defaults to tag name + # name: LinuxRelease + # # Identify the release as a prerelease. Defaults to false + # prerelease: True + # # Newline-delimited list of path globs for asset files to upload + # files: build/bin/* diff --git a/.github/workflows/deploy_linux.yml b/.github/workflows/deploy_linux.yml index d9f2353d6..29be9a527 100644 --- a/.github/workflows/deploy_linux.yml +++ b/.github/workflows/deploy_linux.yml @@ -1,28 +1,28 @@ name: Build Linux -on: [push,pull_request] +on: [push, pull_request] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 + uses: actions/checkout@v2.3.1 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y libx11-dev libxi-dev libxrandr-dev libxinerama-dev libxcursor-dev sudo apt-get install -y libgl1-mesa-dev libegl1-mesa-dev libasound2-dev - - name: Configure & Build 🔧 + - name: Configure & Build 🔧 run: | mkdir build cd build cmake .. && cmake --build . - + - name: GH Release 🚀 # You may pin to the exact commit or the version. uses: actions/upload-artifact@v4 with: - name: LinuxRelease - path: build/bin/ + name: LinuxRelease + path: build/bin/ #uses: softprops/action-gh-release@v0.1.5 #with: # # Note-worthy description of changes in release @@ -35,4 +35,3 @@ jobs: # prerelease: True # # Newline-delimited list of path globs for asset files to upload # files: build/bin/* - From 90c0b81837ba35df3c6958bfeabf6a8dfc4140a3 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Mon, 17 Feb 2025 22:27:16 +0100 Subject: [PATCH 11/20] Fix typo in cmake command in workflow --- .github/workflows/deploy_arm64_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_arm64_linux.yml b/.github/workflows/deploy_arm64_linux.yml index 1273e5efd..e09d153f3 100644 --- a/.github/workflows/deploy_arm64_linux.yml +++ b/.github/workflows/deploy_arm64_linux.yml @@ -15,7 +15,7 @@ jobs: run: | mkdir build cd build - cmake ..-DUSE_GLES3=ON && cmake --build . + cmake .. -DUSE_GLES3=ON && cmake --build . - name: GH Release 🚀 # You may pin to the exact commit or the version. From 72d2ea14aedca1018d306fc4bad9a5dfc4ac0564 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Mon, 17 Feb 2025 23:13:23 +0100 Subject: [PATCH 12/20] Update iOS deployment target to version 12.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0fdf62ce..7dcbaa5ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") endif () if(IOS) - set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum iOS deployment version" FORCE) + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum iOS deployment version" FORCE) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" FORCE) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" FORCE) endif() From 3ac31ee5d20b8b65babc286acc3c29e6bfa162ff Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 07:38:54 +0100 Subject: [PATCH 13/20] Switch from GLES3 to GLES2 in build --- .github/workflows/deploy_arm64_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_arm64_linux.yml b/.github/workflows/deploy_arm64_linux.yml index e09d153f3..27802273d 100644 --- a/.github/workflows/deploy_arm64_linux.yml +++ b/.github/workflows/deploy_arm64_linux.yml @@ -15,7 +15,7 @@ jobs: run: | mkdir build cd build - cmake .. -DUSE_GLES3=ON && cmake --build . + cmake .. -DUSE_GLES2=ON && cmake --build . - name: GH Release 🚀 # You may pin to the exact commit or the version. From 5563ee2df523354d41efc51ec00dea536bcdd173 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 20:13:45 +0100 Subject: [PATCH 14/20] Revert "Update iOS deployment target to version 12.0" This reverts commit 3a3b4d1d09864007d7f90f5619e00d1298c0fab3. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dcbaa5ff..b0fdf62ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") endif () if(IOS) - set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum iOS deployment version" FORCE) + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum iOS deployment version" FORCE) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" FORCE) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" FORCE) endif() From 78ebbf0864053e873076086f961a2c7a882954fb Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 21:10:02 +0100 Subject: [PATCH 15/20] Repatch sokol_app.h with extra files to document the changes --- src/sokol/sokol_app.h | 256 +- src/sokol/sokol_app.h.base | 10967 +++++++++++++++++++++++++++++++++ src/sokol/sokol_app.h.orig | 11529 +++++++++++++++++++++++++++++++++++ src/sokol/sokol_app.h.rej | 19 + src/sokol/sokol_app.patch | 328 + 5 files changed, 23089 insertions(+), 10 deletions(-) create mode 100644 src/sokol/sokol_app.h.base create mode 100644 src/sokol/sokol_app.h.orig create mode 100644 src/sokol/sokol_app.h.rej create mode 100644 src/sokol/sokol_app.patch diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index 4b3d62cc3..b585063f7 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -1320,6 +1320,7 @@ typedef enum sapp_keycode { SAPP_KEYCODE_RIGHT_ALT = 346, SAPP_KEYCODE_RIGHT_SUPER = 347, SAPP_KEYCODE_MENU = 348, + SAPP_KEYCODE_BACK = 349, } sapp_keycode; /* @@ -1800,6 +1801,7 @@ SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); /* iOS: get bridged pointer to iOS UIWindow */ SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); +SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); /* D3D11: get pointer to ID3D11Device object */ SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); @@ -1825,6 +1827,7 @@ SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); /* Android: get native activity handle */ SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); +SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); #ifdef __cplusplus } /* extern "C" */ @@ -7916,6 +7919,10 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { _sapp.android.context = context; return true; } +SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ + //eglSwapInterval(_sapp.android.display,0); +} + _SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { if (_sapp.android.display != EGL_NO_DISPLAY) { @@ -8053,6 +8060,9 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { if (!_sapp_events_enabled()) { return false; } + if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; + if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; + int32_t action_idx = AMotionEvent_getAction(e); int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; sapp_event_type type = SAPP_EVENTTYPE_INVALID; @@ -8086,8 +8096,8 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { for (int32_t i = 0; i < _sapp.event.num_touches; i++) { sapp_touchpoint* dst = &_sapp.event.touches[i]; dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); - dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; - dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || action == AMOTION_EVENT_ACTION_POINTER_UP) { @@ -8100,19 +8110,225 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { return true; } +_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { + switch (scancode) { + case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; + case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; + case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; + case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; + case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; + case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; + case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; + case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; + case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; + case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; + case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; + case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; + case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; + case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; + case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; + case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; + case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; + case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; + case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; + case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; + case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; + case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; + case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; + case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; + case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; + case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; + case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; + case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; + case AKEYCODE_F1: return SAPP_KEYCODE_F1; + case AKEYCODE_F2: return SAPP_KEYCODE_F2; + case AKEYCODE_F3: return SAPP_KEYCODE_F3; + case AKEYCODE_F4: return SAPP_KEYCODE_F4; + case AKEYCODE_F5: return SAPP_KEYCODE_F5; + case AKEYCODE_F6: return SAPP_KEYCODE_F6; + case AKEYCODE_F7: return SAPP_KEYCODE_F7; + case AKEYCODE_F8: return SAPP_KEYCODE_F8; + case AKEYCODE_F9: return SAPP_KEYCODE_F9; + case AKEYCODE_F10: return SAPP_KEYCODE_F10; + case AKEYCODE_F11: return SAPP_KEYCODE_F11; + case AKEYCODE_F12: return SAPP_KEYCODE_F12; + case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; + case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; + case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; + case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; + case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; + case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; + case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; + case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; + case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; + case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; + case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; + case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; + case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; + case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; + case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; + case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; + case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; + case AKEYCODE_A: return SAPP_KEYCODE_A; + case AKEYCODE_B: return SAPP_KEYCODE_B; + case AKEYCODE_C: return SAPP_KEYCODE_C; + case AKEYCODE_D: return SAPP_KEYCODE_D; + case AKEYCODE_E: return SAPP_KEYCODE_E; + case AKEYCODE_F: return SAPP_KEYCODE_F; + case AKEYCODE_G: return SAPP_KEYCODE_G; + case AKEYCODE_H: return SAPP_KEYCODE_H; + case AKEYCODE_I: return SAPP_KEYCODE_I; + case AKEYCODE_J: return SAPP_KEYCODE_J; + case AKEYCODE_K: return SAPP_KEYCODE_K; + case AKEYCODE_L: return SAPP_KEYCODE_L; + case AKEYCODE_M: return SAPP_KEYCODE_M; + case AKEYCODE_N: return SAPP_KEYCODE_N; + case AKEYCODE_O: return SAPP_KEYCODE_O; + case AKEYCODE_P: return SAPP_KEYCODE_P; + case AKEYCODE_Q: return SAPP_KEYCODE_Q; + case AKEYCODE_R: return SAPP_KEYCODE_R; + case AKEYCODE_S: return SAPP_KEYCODE_S; + case AKEYCODE_T: return SAPP_KEYCODE_T; + case AKEYCODE_U: return SAPP_KEYCODE_U; + case AKEYCODE_V: return SAPP_KEYCODE_V; + case AKEYCODE_W: return SAPP_KEYCODE_W; + case AKEYCODE_X: return SAPP_KEYCODE_X; + case AKEYCODE_Y: return SAPP_KEYCODE_Y; + case AKEYCODE_Z: return SAPP_KEYCODE_Z; + case AKEYCODE_1: return SAPP_KEYCODE_1; + case AKEYCODE_2: return SAPP_KEYCODE_2; + case AKEYCODE_3: return SAPP_KEYCODE_3; + case AKEYCODE_4: return SAPP_KEYCODE_4; + case AKEYCODE_5: return SAPP_KEYCODE_5; + case AKEYCODE_6: return SAPP_KEYCODE_6; + case AKEYCODE_7: return SAPP_KEYCODE_7; + case AKEYCODE_8: return SAPP_KEYCODE_8; + case AKEYCODE_9: return SAPP_KEYCODE_9; + case AKEYCODE_0: return SAPP_KEYCODE_0; + case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; + case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; + case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; + case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; + case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; + case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; + case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; + case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; + case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; + case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; + case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; + case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; + /* Android Buttons */ + case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; + default: return SAPP_KEYCODE_INVALID; + } + return SAPP_KEYCODE_INVALID; +} +_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { + uint32_t meta_state = AKeyEvent_getMetaState(e); + uint32_t mods = 0; + if (meta_state& AMETA_SHIFT_ON) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (meta_state& AMETA_CTRL_ON) { + mods |= SAPP_MODIFIER_CTRL; + } + if (meta_state& AMETA_ALT_ON) { + mods |= SAPP_MODIFIER_ALT; + } + if (meta_state& AMETA_META_ON) { + mods |= SAPP_MODIFIER_SUPER; + } + return mods; +} +int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) +{ + ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); + // Attaches the current thread to the JVM. + JavaVM *javaVM = activity->vm; + JNIEnv *jniEnv = activity->env; + + jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); + if(result == JNI_ERR){ + return 0; + } + + jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); + int unicodeKey; + + if(metaState == 0){ + jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); + jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); + jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); + unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); + }else{ + jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); + jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); + jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); + unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); + } + + (*javaVM)->DetachCurrentThread(javaVM); + + return unicodeKey; +} +_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { + if (_sapp_events_enabled() ) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_android_mods(e); + _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} _SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { return false; } - if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { - /* FIXME: this should be hooked into a "really quit?" mechanism - so the app can ask the user for confirmation, this is currently - generally missing in sokol_app.h - */ - _sapp_android_shutdown(); - return true; + if (!_sapp_events_enabled()) { + return false; } - return false; + int32_t action = AKeyEvent_getAction(e); + // Don't process soft keyboard commands as they are not reliable through this interface. + if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; + // Don't relay key press events from joysticks or game pads as Sokol key down events + if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || + (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { + return false; + } + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + + case AKEY_EVENT_ACTION_DOWN : + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case AKEY_EVENT_ACTION_UP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + bool repeat = AKeyEvent_getRepeatCount(e)>0; + _sapp_init_event(type); + _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); + _sapp.event.modifiers = _sapp_android_mods(e); + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + if(type==SAPP_EVENTTYPE_KEY_DOWN){ + _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); + } + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + return _sapp.event.key_code != SAPP_KEYCODE_INVALID; } _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { @@ -8275,6 +8491,16 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; } + /* handle quit-requested, either from window or from sapp_request_quit() */ + if (_sapp.quit_requested && !_sapp.quit_ordered) { + /* give user code a chance to intervene */ + _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if(_sapp.quit_ordered) _sapp_android_shutdown(); } /* cleanup thread */ @@ -11406,6 +11632,16 @@ SOKOL_API_IMPL const void* sapp_ios_get_window(void) { return 0; #endif } +SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { SOKOL_ASSERT(_sapp.valid); diff --git a/src/sokol/sokol_app.h.base b/src/sokol/sokol_app.h.base new file mode 100644 index 000000000..5af91c241 --- /dev/null +++ b/src/sokol/sokol_app.h.base @@ -0,0 +1,10967 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) +#define SOKOL_APP_IMPL +#endif +#ifndef SOKOL_APP_INCLUDED +/* + sokol_app.h -- cross-platform application wrapper + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_APP_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the 3D-API + which should be initialized by sokol_app.h (this must also match + the backend selected for sokol_gfx.h if both are used in the same + project): + + #define SOKOL_GLCORE33 + #define SOKOL_GLES2 + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_LOG(msg) - your own logging function (default: puts(msg)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_ABORT() - called after an unrecoverable error (default: abort()) + SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain + SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function + SOKOL_APP_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_APP_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_CALLOC - your own calloc function (default: calloc(n, s)) + SOKOL_FREE - your own free function (default: free(p)) + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + If sokol_app.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + If you use sokol_app.h together with sokol_gfx.h, include both headers + in the implementation source file, and include sokol_app.h before + sokol_gfx.h since sokol_app.h will also include the required 3D-API + headers. + + On Windows, a minimal 'GL header' and function loader is integrated which + contains just enough of GL for sokol_gfx.h. If you want to use your own + GL header-generator/loader instead, define SOKOL_WIN32_NO_GL_LOADER + before including the implementation part of sokol_app.h. + + To make use of the integrated GL loader, simply include the sokol_app.h + implementation before the sokol_gfx.h implementation. + + 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/) + + iOS onscreen keyboard support 'inspired' by libgdx. + + Link with the following system libraries: + + - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit + - on macOS with GL: Cocoa, QuartzCore, OpenGL + - on iOS with Metal: Foundation, UIKit, Metal, MetalKit + - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit + - on Linux: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Android: GLESv3, EGL, log, android + - on Windows: no action needed, libs are defined in-source via pragma-comment-lib + + 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 + + Building for UWP requires a recent Visual Studio toolchain and Windows SDK + (at least VS2019 and Windows SDK 10.0.19041.0). When the UWP backend is + selected, the sokol_app.h implementation must be compiled as C++17. + + On macOS and iOS, the implementation must be compiled as Objective-C. + + FEATURE OVERVIEW + ================ + sokol_app.h provides a minimalistic cross-platform API which + implements the 'application-wrapper' parts of a 3D application: + + - a common application entry function + - creates a window and 3D-API context/device with a 'default framebuffer' + - makes the rendered frame visible + - provides keyboard-, mouse- and low-level touch-events + - platforms: MacOS, iOS, HTML5, Win32, Linux, Android (TODO: RaspberryPi) + - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | UWP | Raspi | HTML5 + --------------------+---------+-------+-------+-------+---------+------+-------+------- + gl 3.x | YES | YES | YES | --- | --- | --- | --- | --- + gles2/webgl | --- | --- | --- | YES | YES | --- | TODO | YES + gles3/webgl2 | --- | --- | --- | YES | YES | --- | --- | YES + metal | --- | YES | --- | YES | --- | --- | --- | --- + d3d11 | YES | --- | --- | --- | --- | YES | --- | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES | TODO | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES | TODO | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES | TODO | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES | TODO | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES | TODO | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES | TODO | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | TODO | --- | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | TODO | --- | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | TODO | --- | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | TODO | --- | YES + RESIZED | YES | YES | YES | YES | YES | YES | --- | YES + ICONIFIED | YES | YES | YES | --- | --- | YES | --- | --- + RESTORED | YES | YES | YES | --- | --- | YES | --- | --- + SUSPENDED | --- | --- | --- | YES | YES | YES | --- | TODO + RESUMED | --- | --- | --- | YES | YES | YES | --- | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | TODO | YES + UPDATE_CURSOR | YES | YES | TODO | --- | --- | TODO | --- | TODO + IME | TODO | TODO? | TODO | ??? | TODO | --- | ??? | ??? + key repeat flag | YES | YES | YES | --- | --- | YES | TODO | YES + windowed | YES | YES | YES | --- | --- | YES | TODO | YES + fullscreen | YES | YES | YES | YES | YES | YES | TODO | --- + mouse hide | YES | YES | YES | --- | --- | YES | TODO | TODO + mouse lock | YES | YES | YES | --- | --- | TODO | TODO | YES + screen keyboard | --- | --- | --- | YES | TODO | TODO | --- | YES + swap interval | YES | YES | YES | YES | TODO | --- | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES | TODO | YES + clipboard | YES | YES | TODO | --- | --- | TODO | --- | YES + MSAA | YES | YES | YES | YES | YES | TODO | TODO | YES + drag'n'drop | YES | YES | YES | --- | --- | TODO | TODO | YES + + TODO + ==== + - Linux: + - clipboard support + - UWP: + - clipboard, mouselock + - sapp_consume_event() on non-web platforms? + + STEP BY STEP + ============ + --- Add a sokol_main() function to your code which returns a sapp_desc structure + with initialization parameters and callback function pointers. This + function is called very early, usually at the start of the + platform's entry function (e.g. main or WinMain). You should do as + little as possible here, since the rest of your code might be called + from another thread (this depends on the platform): + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + .width = 640, + .height = 480, + .init_cb = my_init_func, + .frame_cb = my_frame_func, + .cleanup_cb = my_cleanup_func, + .event_cb = my_event_func, + ... + }; + } + + There are many more setup parameters, but these are the most important. + For a complete list search for the sapp_desc structure declaration + below. + + DO NOT call any sokol-app function from inside sokol_main(), since + sokol-app will not be initialized at this point. + + The .width and .height parameters are the preferred size of the 3D + rendering canvas. The actual size may differ from this depending on + platform and other circumstances. Also the canvas size may change at + any time (for instance when the user resizes the application window, + or rotates the mobile device). + + All provided function callbacks will be called from the same thread, + but this may be different from the thread where sokol_main() was called. + + .init_cb (void (*)(void)) + This function is called once after the application window, + 3D rendering context and swap chain have been created. The + function takes no arguments and has no return value. + .frame_cb (void (*)(void)) + This is the per-frame callback, which is usually called 60 + times per second. This is where your application would update + most of its state and perform all rendering. + .cleanup_cb (void (*)(void)) + The cleanup callback is called once right before the application + quits. + .event_cb (void (*)(const sapp_event* event)) + The event callback is mainly for input handling, but is also + used to communicate other types of events to the application. Keep the + event_cb struct member zero-initialized if your application doesn't require + event handling. + .fail_cb (void (*)(const char* msg)) + The fail callback is called when a fatal error is encountered + during start which doesn't allow the program to continue. + Providing a callback here gives you a chance to show an error message + to the user. The default behaviour is SOKOL_LOG(msg) + + As you can see, those 'standard callbacks' don't have a user_data + argument, so any data that needs to be preserved between callbacks + must live in global variables. If keeping state in global variables + is not an option, there's an alternative set of callbacks with + an additional user_data pointer argument: + + .user_data (void*) + The user-data argument for the callbacks below + .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)) + 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. + + The function sapp_userdata() can be used to query the user_data + pointer provided in the sapp_desc struct. + + You can also call sapp_query_desc() to get a copy of the + original sapp_desc structure. + + NOTE that there's also an alternative compile mode where sokol_app.h + doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + + --- Implement the initialization callback function (init_cb), this is called + once after the rendering surface, 3D API and swap chain have been + initialized by sokol_app. All sokol-app functions can be called + from inside the initialization callback, the most useful functions + at this point are: + + int sapp_width(void) + int sapp_height(void) + Returns the current width and height of the default framebuffer in pixels, + this may change from one frame to the next, and it may be different + from the initial size provided in the sapp_desc struct. + + float sapp_widthf(void) + float sapp_heightf(void) + These are alternatives to sapp_width() and sapp_height() which return + the default framebuffer size as float values instead of integer. This + may help to prevent casting back and forth between int and float + in more strongly typed languages than C and C++. + + int sapp_color_format(void) + int sapp_depth_format(void) + The color and depth-stencil pixelformats of the default framebuffer, + as integer values which are compatible with sokol-gfx's + sg_pixel_format enum (so that they can be plugged directly in places + where sg_pixel_format is expected). Possible values are: + + 23 == SG_PIXELFORMAT_RGBA8 + 27 == SG_PIXELFORMAT_BGRA8 + 41 == SG_PIXELFORMAT_DEPTH + 42 == SG_PIXELFORMAT_DEPTH_STENCIL + + int sapp_sample_count(void) + Return the MSAA sample count of the default framebuffer. + + bool sapp_gles2(void) + Returns true if a GLES2 or WebGL context has been created. This + is useful when a GLES3/WebGL2 context was requested but is not + available so that sokol_app.h had to fallback to GLES2/WebGL. + + const void* sapp_metal_get_device(void) + const void* sapp_metal_get_renderpass_descriptor(void) + const void* sapp_metal_get_drawable(void) + If the Metal backend has been selected, these functions return pointers + to various Metal API objects required for rendering, otherwise + they return a null pointer. These void pointers are actually + Objective-C ids converted with a (ARC) __bridge cast so that + the ids can be tunnel through C code. Also note that the returned + pointers to the renderpass-descriptor and drawable may change from one + frame to the next, only the Metal device object is guaranteed to + stay the same. + + const void* sapp_macos_get_window(void) + On macOS, get the NSWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_ios_get_window(void) + On iOS, get the UIWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_win32_get_hwnd(void) + On Windows, get the window's HWND, otherwise a null pointer. The + HWND has been cast to a void pointer in order to be tunneled + through code which doesn't include Windows.h. + + const void* sapp_d3d11_get_device(void) + const void* sapp_d3d11_get_device_context(void) + const void* sapp_d3d11_get_render_target_view(void) + const void* sapp_d3d11_get_depth_stencil_view(void) + Similar to the sapp_metal_* functions, the sapp_d3d11_* functions + return pointers to D3D11 API objects required for rendering, + only if the D3D11 backend has been selected. Otherwise they + return a null pointer. Note that the returned pointers to the + render-target-view and depth-stencil-view may change from one + frame to the next! + + const void* sapp_wgpu_get_device(void) + const void* sapp_wgpu_get_render_view(void) + const void* sapp_wgpu_get_resolve_view(void) + const void* sapp_wgpu_get_depth_stencil_view(void) + These are the WebGPU-specific functions to get the WebGPU + objects and values required for rendering. If sokol_app.h + is not compiled with SOKOL_WGPU, these functions return null. + + const void* sapp_android_get_native_activity(void); + On Android, get the native activity ANativeActivity pointer, otherwise + a null pointer. + + --- Implement the frame-callback function, this function will be called + on the same thread as the init callback, but might be on a different + thread than the sokol_main() function. Note that the size of + the rendering framebuffer might have changed since the frame callback + was called last. Call the functions sapp_width() and sapp_height() + each frame to get the current size. + + --- Optionally implement the event-callback to handle input events. + sokol-app provides the following type of input events: + - a 'virtual key' was pressed down or released + - a single text character was entered (provided as UTF-32 code point) + - a mouse button was pressed down or released (left, right, middle) + - mouse-wheel or 2D scrolling events + - the mouse was moved + - the mouse has entered or left the application window boundaries + - low-level, portable multi-touch events (began, moved, ended, cancelled) + - the application window was resized, iconified or restored + - the application was suspended or restored (on mobile platforms) + - the user or application code has asked to quit the application + - a string was pasted to the system clipboard + - one or more files have been dropped onto the application window + + To explicitly 'consume' an event and prevent that the event is + forwarded for further handling to the operating system, call + sapp_consume_event() from inside the event handler (NOTE that + this behaviour is currently only implemented for some HTML5 + events, support for other platforms and event types will + be added as needed, please open a github ticket and/or provide + a PR if needed). + + NOTE: Do *not* call any 3D API rendering functions in the event + callback function, since the 3D API context may not be active when the + event callback is called (it may work on some platforms and 3D APIs, + but not others, and the exact behaviour may change between + sokol-app versions). + + --- Implement the cleanup-callback function, this is called once + after the user quits the application (see the section + "APPLICATION QUIT" for detailed information on quitting + behaviour, and how to intercept a pending quit - for instance to show a + "Really Quit?" dialog box). Note that the cleanup-callback isn't + guaranteed to be called on the web and mobile platforms. + + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) + ================================================ + In normal mouse mode, no mouse movement events are reported when the + mouse leaves the windows client area or hits the screen border (whether + it's one or the other depends on the platform), and the mouse move events + (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in + framebuffer pixels in the sapp_event items mouse_x and mouse_y, and + relative movement in framebuffer pixels in the sapp_event items mouse_dx + and mouse_dy. + + To get continuous mouse movement (also when the mouse leaves the window + client area or hits the screen border), activate mouse-lock mode + by calling: + + sapp_lock_mouse(true) + + When mouse lock is activated, the mouse pointer is hidden, the + reported absolute mouse position (sapp_event.mouse_x/y) appears + frozen, and the relative mouse movement in sapp_event.mouse_dx/dy + no longer has a direct relation to framebuffer pixels but instead + uses "raw mouse input" (what "raw mouse input" exactly means also + differs by platform). + + To deactivate mouse lock and return to normal mouse mode, call + + sapp_lock_mouse(false) + + And finally, to check if mouse lock is currently active, call + + if (sapp_mouse_locked()) { ... } + + On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() + functions work as expected (mouse lock is activated or deactivated + immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() + also immediately returns the new state after sapp_lock_mouse() + is called. + + On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave + differently, as dictated by the limitations of the HTML5 Pointer Lock API: + + - sapp_lock_mouse(true) can be called at any time, but it will + only take effect in a 'short-lived input event handler of a specific + type', meaning when one of the following events happens: + - SAPP_EVENTTYPE_MOUSE_DOWN + - SAPP_EVENTTYPE_MOUSE_UP + - SAPP_EVENTTYPE_MOUSE_SCROLL + - SAPP_EVENTYTPE_KEY_UP + - SAPP_EVENTTYPE_KEY_DOWN + - The mouse lock/unlock action on the web platform is asynchronous, + this means that sapp_mouse_locked() won't immediately return + the new status after calling sapp_lock_mouse(), instead the + reported status will only change when the pointer lock has actually + been activated or deactivated in the browser. + - On the web, mouse lock can be deactivated by the user at any time + by pressing the Esc key. When this happens, sokol_app.h behaves + the same as if sapp_lock_mouse(false) is called. + + For things like camera manipulation it's most straightforward to lock + and unlock the mouse right from the sokol_app.h event handler, for + instance the following code enters and leaves mouse lock when the + left mouse button is pressed and released, and then uses the relative + movement information to manipulate a camera (taken from the + cgltf-sapp.c sample in the sokol-samples repository + at https://github.com/floooh/sokol-samples): + + static void input(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(true); + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(false); + } + break; + + case SAPP_EVENTTYPE_MOUSE_MOVE: + if (sapp_mouse_locked()) { + cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); + } + break; + + default: + break; + } + } + + CLIPBOARD SUPPORT + ================= + Applications can send and receive UTF-8 encoded text data from and to the + system clipboard. By default, clipboard support is disabled and + must be enabled at startup via the following sapp_desc struct + members: + + sapp_desc.enable_clipboard - set to true to enable clipboard support + sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes + + Enabling the clipboard will dynamically allocate a clipboard buffer + for UTF-8 encoded text data of the requested size in bytes, the default + size is 8 KBytes. Strings that don't fit into the clipboard buffer + (including the terminating zero) will be silently clipped, so it's + important that you provide a big enough clipboard size for your + use case. + + To send data to the clipboard, call sapp_set_clipboard_string() with + a pointer to an UTF-8 encoded, null-terminated C-string. + + NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be + called from inside a 'short-lived event handler', and there are a few + other HTML5-specific caveats to workaround. You'll basically have to + tinker until it works in all browsers :/ (maybe the situation will + improve when all browsers agree on and implement the new + HTML5 navigator.clipboard API). + + To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED + event in your event handler function, and then call sapp_get_clipboard_string() + to obtain the pasted UTF-8 encoded text. + + NOTE that behaviour of sapp_get_clipboard_string() is slightly different + depending on platform: + + - on the HTML5 platform, the internal clipboard buffer will only be updated + right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, + and sapp_get_clipboard_string() will simply return the current content + of the clipboard buffer + - on 'native' platforms, the call to sapp_get_clipboard_string() will + update the internal clipboard buffer with the most recent data + from the system clipboard + + Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, + and then call sapp_get_clipboard_string() right in the event handler. + + The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app + as follows: + + - on macOS: when the Cmd+V key is pressed down + - on HTML5: when the browser sends a 'paste' event to the global 'window' object + - on all other platforms: when the Ctrl+V key is pressed down + + DRAG AND DROP SUPPORT + ===================== + PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 + and on the native desktop platforms (Win32, Linux and macOS) because + of security-related restrictions in the HTML5 drag'n'drop API. The + WASM/HTML5 specifics are described at the end of this documentation + section: + + Like clipboard support, drag'n'drop support must be explicitly enabled + at startup in the sapp_desc struct. + + sapp_desc sokol_main() { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + ... + }; + } + + You can also adjust the maximum number of files that are accepted + in a drop operation, and the maximum path length in bytes if needed: + + sapp_desc sokol_main() { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + .max_dropped_files = 8, // default is 1 + .max_dropped_file_path_length = 8192, // in bytes, default is 2048 + ... + }; + } + + When drag'n'drop is enabled, the event callback will be invoked with an + event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on + the application window. + + After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the + number of dropped files, and their absolute paths by calling separate + functions: + + void on_event(const sapp_event* ev) { + if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + + // the mouse position where the drop happened + float x = ev->mouse_x; + float y = ev->mouse_y; + + // get the number of files and their paths like this: + const int num_dropped_files = sapp_get_num_dropped_files(); + for (int i = 0; i < num_dropped_files; i++) { + const char* path = sapp_get_dropped_file_path(i); + ... + } + } + } + + The returned file paths are UTF-8 encoded strings. + + You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() + anywhere, also outside the event handler callback, but be aware that the + file path strings will be overwritten with the next drop operation. + + In any case, sapp_get_dropped_file_path() will never return a null pointer, + instead an empty string "" will be returned if the drag'n'drop feature + hasn't been enabled, the last drop-operation failed, or the file path index + is out of range. + + Drag'n'drop caveats: + + - if more files are dropped in a single drop-action + than sapp_desc.max_dropped_files, the additional + files will be silently ignored + - if any of the file paths is longer than + sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 + encoding) the entire drop operation will be silently ignored (this + needs some sort of error feedback in the future) + - no mouse positions are reported while the drag is in + process, this may change in the future + + Drag'n'drop on HTML5/WASM: + + The HTML5 drag'n'drop API doesn't return file paths, but instead + black-box 'file objects' which must be used to load the content + of dropped files. This is the reason why sokol_app.h adds two + HTML5-specific functions to the drag'n'drop API: + + uint32_t sapp_html5_get_dropped_file_size(int index) + Returns the size in bytes of a dropped file. + + void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) + Asynchronously loads the content of a dropped file into a + provided memory buffer (which must be big enough to hold + the file content) + + To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED + event is received: + + sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ + .dropped_file_index = 0, + .callback = fetch_cb + .buffer_ptr = buf, + .buffer_size = buf_size, + .user_data = ... + }); + + Make sure that the memory pointed to by 'buf' stays valid until the + callback function is called! + + As result of the asynchronous loading operation (no matter if succeeded or + failed) the 'fetch_cb' function will be called: + + void fetch_cb(const sapp_html5_fetch_response* response) { + // IMPORTANT: check if the loading operation actually succeeded: + if (response->succeeded) { + // the size of the loaded file: + const uint32_t num_bytes = response->fetched_size; + // and the pointer to the data (same as 'buf' in the fetch-call): + const void* ptr = response->buffer_ptr; + } + else { + // on error check the error code: + switch (response->error_code) { + case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: + ... + break; + case SAPP_HTML5_FETCH_ERROR_OTHER: + ... + break; + } + } + } + + Check the droptest-sapp example for a real-world example which works + both on native platforms and the web: + + https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + + HIGH-DPI RENDERING + ================== + You can set the sapp_desc.high_dpi flag during initialization to request + a full-resolution framebuffer on HighDPI displays. The default behaviour + is sapp_desc.high_dpi=false, this means that the application will + render to a lower-resolution framebuffer on HighDPI displays and the + rendered content will be upscaled by the window system composer. + + In a HighDPI scenario, you still request the same window size during + sokol_main(), but the framebuffer sizes returned by sapp_width() + and sapp_height() will be scaled up according to the DPI scaling + ratio. You can also get a DPI scaling factor with the function + sapp_dpi_scale(). + + Here's an example on a Mac with Retina display: + + sapp_desc sokol_main() { + return (sapp_desc) { + .width = 640, + .height = 480, + .high_dpi = true, + ... + }; + } + + The functions sapp_width(), sapp_height() and sapp_dpi_scale() will + return the following values: + + sapp_width -> 1280 + sapp_height -> 960 + sapp_dpi_scale -> 2.0 + + If the high_dpi flag is false, or you're not running on a Retina display, + the values would be: + + sapp_width -> 640 + sapp_height -> 480 + sapp_dpi_scale -> 1.0 + + APPLICATION QUIT + ================ + Without special quit handling, a sokol_app.h application will quit + 'gracefully' when the user clicks the window close-button unless a + platform's application model prevents this (e.g. on web or mobile). + 'Graceful exit' means that the application-provided cleanup callback will + be called before the application quits. + + On native desktop platforms sokol_app.h provides more control over the + application-quit-process. It's possible to initiate a 'programmatic quit' + from the application code, and a quit initiated by the application user can + be intercepted (for instance to show a custom dialog box). + + This 'programmatic quit protocol' is implemented through 3 functions + and 1 event: + + - sapp_quit(): This function simply quits the application without + giving the user a chance to intervene. Usually this might + be called when the user clicks the 'Ok' button in a 'Really Quit?' + dialog box + - sapp_request_quit(): Calling sapp_request_quit() will send the + event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler + callback, giving the user code a chance to intervene and cancel the + pending quit process (for instance to show a 'Really Quit?' dialog + box). If the event handler callback does nothing, the application + will be quit as usual. To prevent this, call the function + sapp_cancel_quit() from inside the event handler. + - sapp_cancel_quit(): Cancels a pending quit request, either initiated + by the user clicking the window close button, or programmatically + by calling sapp_request_quit(). The only place where calling this + function makes sense is from inside the event handler callback when + the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. + - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user + clicks the window's close button or application code calls the + sapp_request_quit() function. The event handler callback code can handle + this event by calling sapp_cancel_quit() to cancel the quit. + If the event is ignored, the application will quit as usual. + + On the web platform, the quit behaviour differs from native platforms, + because of web-specific restrictions: + + A `programmatic quit` initiated by calling sapp_quit() or + sapp_request_quit() will work as described above: the cleanup callback is + called, platform-specific cleanup is performed (on the web + this means that JS event handlers are unregisters), and then + the request-animation-loop will be exited. However that's all. The + web page itself will continue to exist (e.g. it's not possible to + programmatically close the browser tab). + + On the web it's also not possible to run custom code when the user + closes a brower tab, so it's not possible to prevent this with a + fancy custom dialog box. + + Instead the standard "Leave Site?" dialog box can be activated (or + deactivated) with the following function: + + sapp_html5_ask_leave_site(bool ask); + + The initial state of the associated internal flag can be provided + at startup via sapp_desc.html5_ask_leave_site. + + This feature should only be used sparingly in critical situations - for + instance when the user would loose data - since popping up modal dialog + boxes is considered quite rude in the web world. Note that there's no way + to customize the content of this dialog box or run any code as a result + of the user's decision. Also note that the user must have interacted with + the site before the dialog box will appear. These are all security measures + to prevent fishing. + + The Dear ImGui HighDPI sample contains example code of how to + implement a 'Really Quit?' dialog box with Dear ImGui (native desktop + platforms only), and for showing the hardwired "Leave Site?" dialog box + when running on the web platform: + + https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + + FULLSCREEN + ========== + If the sapp_desc.fullscreen flag is true, sokol-app will try to create + a fullscreen window on platforms with a 'proper' window system + (mobile devices will always use fullscreen). The implementation details + depend on the target platform, in general sokol-app will use a + 'soft approach' which doesn't interfere too much with the platform's + window system (for instance borderless fullscreen window instead of + a 'real' fullscreen mode). Such details might change over time + as sokol-app is adapted for different needs. + + The most important effect of fullscreen mode to keep in mind is that + the requested canvas width and height will be ignored for the initial + window size, calling sapp_width() and sapp_height() will instead return + the resolution of the fullscreen canvas (however the provided size + might still be used for the non-fullscreen window, in case the user can + switch back from fullscreen- to windowed-mode). + + To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + + To check if the application window is currently in fullscreen mode, + call sapp_is_fullscreen(). + + ONSCREEN KEYBOARD + ================= + On some platforms which don't provide a physical keyboard, sokol-app + can display the platform's integrated onscreen keyboard for text + input. To request that the onscreen keyboard is shown, call + + sapp_show_keyboard(true); + + Likewise, to hide the keyboard call: + + sapp_show_keyboard(false); + + Note that on the web platform, the keyboard can only be shown from + inside an input handler. On such platforms, sapp_show_keyboard() + will only work as expected when it is called from inside the + sokol-app event callback function. When called from other places, + an internal flag will be set, and the onscreen keyboard will be + called at the next 'legal' opportunity (when the next input event + is handled). + + OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) + ====================================================== + In its default configuration, sokol_app.h "hijacks" the platform's + standard main() function. This was done because different platforms + have different main functions which are not compatible with + C's main() (for instance WinMain on Windows has completely different + arguments). However, this "main hijacking" posed a problem for + usage scenarios like integrating sokol_app.h with other languages than + C or C++, so an alternative SOKOL_NO_ENTRY mode has been added + in which the user code provides the platform's main function: + + - define SOKOL_NO_ENTRY before including the sokol_app.h implementation + - do *not* provide a sokol_main() function + - instead provide the standard main() function of the platform + - from the main function, call the function ```sapp_run()``` which + takes a pointer to an ```sapp_desc``` structure. + - ```sapp_run()``` takes over control and calls the provided init-, frame-, + shutdown- and event-callbacks just like in the default model, it + will only return when the application quits (or not at all on some + platforms, like emscripten) + + NOTE: SOKOL_NO_ENTRY is currently not supported on Android. + + WINDOWS CONSOLE OUTPUT + ====================== + On Windows, regular windowed applications don't show any stdout/stderr text + output, which can be a bit of a hassle for printf() debugging or generally + logging text to the console. Also, console output by default uses a local + codepage setting and thus international UTF-8 encoded text is printed + as garbage. + + To help with these issues, sokol_app.h can be configured at startup + via the following Windows-specific sapp_desc flags: + + sapp_desc.win32_console_utf8 (default: false) + When set to true, the output console codepage will be switched + to UTF-8 (and restored to the original codepage on exit) + + sapp_desc.win32_console_attach (default: false) + When set to true, stdout and stderr will be attached to the + console of the parent process (if the parent process actually + has a console). This means that if the application was started + in a command line window, stdout and stderr output will be printed + to the terminal, just like a regular command line program. But if + the application is started via double-click, it will behave like + a regular UI application, and stdout/stderr will not be visible. + + sapp_desc.win32_console_create (default: false) + When set to true, a new console window will be created and + stdout/stderr will be redirected to that console window. It + doesn't matter if the application is started from the command + line or via double-click. + + TEMP NOTE DUMP + ============== + - onscreen keyboard support on Android requires Java :(, should we even bother? + - sapp_desc needs a bool whether to initialize depth-stencil surface + - GL context initialization needs more control (at least what GL version to initialize) + - application icon + - the UPDATE_CURSOR event currently behaves differently between Win32 and OSX + (Win32 sends the event each frame when the mouse moves and is inside the window + client area, OSX sends it only once when the mouse enters the client area) + - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy + at the latest but should do it earlier, in onStop, as an app is "killable" after onStop + on Android Honeycomb and later (it can't be done at the moment as the app may be started + again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_APP_INCLUDED (1) +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) +#define SOKOL_APP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_APP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) +#define SOKOL_APP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_APP_API_DECL __declspec(dllimport) +#else +#define SOKOL_APP_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + SAPP_MAX_TOUCHPOINTS = 8, + SAPP_MAX_MOUSEBUTTONS = 3, + SAPP_MAX_KEYCODES = 512, +}; + +typedef enum sapp_event_type { + SAPP_EVENTTYPE_INVALID, + SAPP_EVENTTYPE_KEY_DOWN, + SAPP_EVENTTYPE_KEY_UP, + SAPP_EVENTTYPE_CHAR, + SAPP_EVENTTYPE_MOUSE_DOWN, + SAPP_EVENTTYPE_MOUSE_UP, + SAPP_EVENTTYPE_MOUSE_SCROLL, + SAPP_EVENTTYPE_MOUSE_MOVE, + SAPP_EVENTTYPE_MOUSE_ENTER, + SAPP_EVENTTYPE_MOUSE_LEAVE, + SAPP_EVENTTYPE_TOUCHES_BEGAN, + SAPP_EVENTTYPE_TOUCHES_MOVED, + SAPP_EVENTTYPE_TOUCHES_ENDED, + SAPP_EVENTTYPE_TOUCHES_CANCELLED, + SAPP_EVENTTYPE_RESIZED, + SAPP_EVENTTYPE_ICONIFIED, + SAPP_EVENTTYPE_RESTORED, + SAPP_EVENTTYPE_SUSPENDED, + SAPP_EVENTTYPE_RESUMED, + SAPP_EVENTTYPE_UPDATE_CURSOR, + SAPP_EVENTTYPE_QUIT_REQUESTED, + SAPP_EVENTTYPE_CLIPBOARD_PASTED, + SAPP_EVENTTYPE_FILES_DROPPED, + _SAPP_EVENTTYPE_NUM, + _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF +} sapp_event_type; + +/* key codes are the same names and values as GLFW */ +typedef enum sapp_keycode { + SAPP_KEYCODE_INVALID = 0, + SAPP_KEYCODE_SPACE = 32, + SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ + SAPP_KEYCODE_COMMA = 44, /* , */ + SAPP_KEYCODE_MINUS = 45, /* - */ + SAPP_KEYCODE_PERIOD = 46, /* . */ + SAPP_KEYCODE_SLASH = 47, /* / */ + SAPP_KEYCODE_0 = 48, + SAPP_KEYCODE_1 = 49, + SAPP_KEYCODE_2 = 50, + SAPP_KEYCODE_3 = 51, + SAPP_KEYCODE_4 = 52, + SAPP_KEYCODE_5 = 53, + SAPP_KEYCODE_6 = 54, + SAPP_KEYCODE_7 = 55, + SAPP_KEYCODE_8 = 56, + SAPP_KEYCODE_9 = 57, + SAPP_KEYCODE_SEMICOLON = 59, /* ; */ + SAPP_KEYCODE_EQUAL = 61, /* = */ + SAPP_KEYCODE_A = 65, + SAPP_KEYCODE_B = 66, + SAPP_KEYCODE_C = 67, + SAPP_KEYCODE_D = 68, + SAPP_KEYCODE_E = 69, + SAPP_KEYCODE_F = 70, + SAPP_KEYCODE_G = 71, + SAPP_KEYCODE_H = 72, + SAPP_KEYCODE_I = 73, + SAPP_KEYCODE_J = 74, + SAPP_KEYCODE_K = 75, + SAPP_KEYCODE_L = 76, + SAPP_KEYCODE_M = 77, + SAPP_KEYCODE_N = 78, + SAPP_KEYCODE_O = 79, + SAPP_KEYCODE_P = 80, + SAPP_KEYCODE_Q = 81, + SAPP_KEYCODE_R = 82, + SAPP_KEYCODE_S = 83, + SAPP_KEYCODE_T = 84, + SAPP_KEYCODE_U = 85, + SAPP_KEYCODE_V = 86, + SAPP_KEYCODE_W = 87, + SAPP_KEYCODE_X = 88, + SAPP_KEYCODE_Y = 89, + SAPP_KEYCODE_Z = 90, + SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ + SAPP_KEYCODE_BACKSLASH = 92, /* \ */ + SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ + SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ + SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ + SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ + SAPP_KEYCODE_ESCAPE = 256, + SAPP_KEYCODE_ENTER = 257, + SAPP_KEYCODE_TAB = 258, + SAPP_KEYCODE_BACKSPACE = 259, + SAPP_KEYCODE_INSERT = 260, + SAPP_KEYCODE_DELETE = 261, + SAPP_KEYCODE_RIGHT = 262, + SAPP_KEYCODE_LEFT = 263, + SAPP_KEYCODE_DOWN = 264, + SAPP_KEYCODE_UP = 265, + SAPP_KEYCODE_PAGE_UP = 266, + SAPP_KEYCODE_PAGE_DOWN = 267, + SAPP_KEYCODE_HOME = 268, + SAPP_KEYCODE_END = 269, + SAPP_KEYCODE_CAPS_LOCK = 280, + SAPP_KEYCODE_SCROLL_LOCK = 281, + SAPP_KEYCODE_NUM_LOCK = 282, + SAPP_KEYCODE_PRINT_SCREEN = 283, + SAPP_KEYCODE_PAUSE = 284, + SAPP_KEYCODE_F1 = 290, + SAPP_KEYCODE_F2 = 291, + SAPP_KEYCODE_F3 = 292, + SAPP_KEYCODE_F4 = 293, + SAPP_KEYCODE_F5 = 294, + SAPP_KEYCODE_F6 = 295, + SAPP_KEYCODE_F7 = 296, + SAPP_KEYCODE_F8 = 297, + SAPP_KEYCODE_F9 = 298, + SAPP_KEYCODE_F10 = 299, + SAPP_KEYCODE_F11 = 300, + SAPP_KEYCODE_F12 = 301, + SAPP_KEYCODE_F13 = 302, + SAPP_KEYCODE_F14 = 303, + SAPP_KEYCODE_F15 = 304, + SAPP_KEYCODE_F16 = 305, + SAPP_KEYCODE_F17 = 306, + SAPP_KEYCODE_F18 = 307, + SAPP_KEYCODE_F19 = 308, + SAPP_KEYCODE_F20 = 309, + SAPP_KEYCODE_F21 = 310, + SAPP_KEYCODE_F22 = 311, + SAPP_KEYCODE_F23 = 312, + SAPP_KEYCODE_F24 = 313, + SAPP_KEYCODE_F25 = 314, + SAPP_KEYCODE_KP_0 = 320, + SAPP_KEYCODE_KP_1 = 321, + SAPP_KEYCODE_KP_2 = 322, + SAPP_KEYCODE_KP_3 = 323, + SAPP_KEYCODE_KP_4 = 324, + SAPP_KEYCODE_KP_5 = 325, + SAPP_KEYCODE_KP_6 = 326, + SAPP_KEYCODE_KP_7 = 327, + SAPP_KEYCODE_KP_8 = 328, + SAPP_KEYCODE_KP_9 = 329, + SAPP_KEYCODE_KP_DECIMAL = 330, + SAPP_KEYCODE_KP_DIVIDE = 331, + SAPP_KEYCODE_KP_MULTIPLY = 332, + SAPP_KEYCODE_KP_SUBTRACT = 333, + SAPP_KEYCODE_KP_ADD = 334, + SAPP_KEYCODE_KP_ENTER = 335, + SAPP_KEYCODE_KP_EQUAL = 336, + SAPP_KEYCODE_LEFT_SHIFT = 340, + SAPP_KEYCODE_LEFT_CONTROL = 341, + SAPP_KEYCODE_LEFT_ALT = 342, + SAPP_KEYCODE_LEFT_SUPER = 343, + SAPP_KEYCODE_RIGHT_SHIFT = 344, + SAPP_KEYCODE_RIGHT_CONTROL = 345, + SAPP_KEYCODE_RIGHT_ALT = 346, + SAPP_KEYCODE_RIGHT_SUPER = 347, + SAPP_KEYCODE_MENU = 348, + SAPP_KEYCODE_BACK = 349, +} sapp_keycode; + +typedef struct sapp_touchpoint { + uintptr_t identifier; + float pos_x; + float pos_y; + bool changed; +} sapp_touchpoint; + +typedef enum sapp_mousebutton { + SAPP_MOUSEBUTTON_LEFT = 0x0, + SAPP_MOUSEBUTTON_RIGHT = 0x1, + SAPP_MOUSEBUTTON_MIDDLE = 0x2, + SAPP_MOUSEBUTTON_INVALID = 0x100, +} sapp_mousebutton; + +enum { + SAPP_MODIFIER_SHIFT = 0x1, + SAPP_MODIFIER_CTRL = 0x2, + SAPP_MODIFIER_ALT = 0x4, + SAPP_MODIFIER_SUPER = 0x8 +}; + +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; + int window_height; + int framebuffer_width; + int framebuffer_height; +} sapp_event; + +typedef struct sapp_desc { + 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 (*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) */ + + /* 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 */ +} sapp_desc; + +/* HTML5 specific: request and response structs for + asynchronously loading dropped-file content. +*/ +typedef enum sapp_html5_fetch_error { + SAPP_HTML5_FETCH_ERROR_NO_ERROR, + SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, + SAPP_HTML5_FETCH_ERROR_OTHER, +} sapp_html5_fetch_error; + +typedef struct sapp_html5_fetch_response { + bool succeeded; /* true if the loading operation has succeeded */ + sapp_html5_fetch_error error_code; + int file_index; /* index of the dropped file (0..sapp_get_num_dropped_filed()-1) */ + uint32_t fetched_size; /* size in bytes of loaded data */ + void* buffer_ptr; /* pointer to user-provided buffer which contains the loaded data */ + uint32_t buffer_size; /* size of user-provided buffer (buffer_size >= fetched_size) */ + void* user_data; /* user-provided user data pointer */ +} sapp_html5_fetch_response; + +typedef struct sapp_html5_fetch_request { + int dropped_file_index; /* 0..sapp_get_num_dropped_files()-1 */ + void (*callback)(const sapp_html5_fetch_response*); /* response callback function pointer (required) */ + void* buffer_ptr; /* pointer to buffer to load data into */ + uint32_t buffer_size; /* size in bytes of buffer */ + void* user_data; /* optional userdata pointer */ +} sapp_html5_fetch_request; + +/* user-provided functions */ +extern sapp_desc sokol_main(int argc, char* argv[]); + +/* returns true after sokol-app has been initialized */ +SOKOL_APP_API_DECL bool sapp_isvalid(void); +/* returns the current framebuffer width in pixels */ +SOKOL_APP_API_DECL int sapp_width(void); +/* same as sapp_width(), but returns float */ +SOKOL_APP_API_DECL float sapp_widthf(void); +/* returns the current framebuffer height in pixels */ +SOKOL_APP_API_DECL int sapp_height(void); +/* same as sapp_height(), but returns float */ +SOKOL_APP_API_DECL float sapp_heightf(void); +/* get default framebuffer color pixel format */ +SOKOL_APP_API_DECL int sapp_color_format(void); +/* get default framebuffer depth pixel format */ +SOKOL_APP_API_DECL int sapp_depth_format(void); +/* get default framebuffer sample count */ +SOKOL_APP_API_DECL int sapp_sample_count(void); +/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ +SOKOL_APP_API_DECL bool sapp_high_dpi(void); +/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ +SOKOL_APP_API_DECL float sapp_dpi_scale(void); +/* show or hide the mobile device onscreen keyboard */ +SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); +/* return true if the mobile device onscreen keyboard is currently shown */ +SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); +/* query fullscreen mode */ +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); +/* toggle fullscreen mode */ +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL void sapp_show_mouse(bool show); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL bool sapp_mouse_shown(); +/* enable/disable mouse-pointer-lock mode */ +SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); +/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ +SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* return the userdata pointer optionally provided in sapp_desc */ +SOKOL_APP_API_DECL void* sapp_userdata(void); +/* return a copy of the sapp_desc structure */ +SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); +/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_request_quit(void); +/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ +SOKOL_APP_API_DECL void sapp_cancel_quit(void); +/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ +SOKOL_APP_API_DECL void sapp_quit(void); +/* call from inside event callback to consume the current event (don't forward to platform) */ +SOKOL_APP_API_DECL void sapp_consume_event(void); +/* get the current frame counter (for comparison with sapp_event.frame_count) */ +SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* write string into clipboard */ +SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); +/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ +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); +/* 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 */ +SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); + +/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ +SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); + +/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ +SOKOL_APP_API_DECL bool sapp_gles2(void); + +/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ +SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); +/* HTML5: get byte size of a dropped file */ +SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); +/* HTML5: asynchronously load the content of a dropped file */ +SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); + +/* Metal: get bridged pointer to Metal device object */ +SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); +/* Metal: get bridged pointer to this frame's renderpass descriptor */ +SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); +/* Metal: get bridged pointer to current drawable */ +SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); +/* macOS: get bridged pointer to macOS NSWindow */ +SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); +/* iOS: get bridged pointer to iOS UIWindow */ +SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); +SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); + +/* D3D11: get pointer to ID3D11Device object */ +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 ID3D11RenderTargetView object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void); +/* D3D11: get pointer to ID3D11DepthStencilView */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); +/* Win32: get the HWND window handle */ +SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); + +/* WebGPU: get WGPUDevice handle */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); +/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); +/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); +/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + +/* Android: get native activity handle */ +SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); +SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } + +#endif + +// this WinRT specific hack is required when wWinMain is in a static library +#if defined(_MSC_VER) && defined(UNICODE) +#include +#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(linker, "/include:wWinMain") +#endif +#endif + +#endif // SOKOL_APP_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_APP_IMPL +#define SOKOL_APP_IMPL_INCLUDED (1) + +#include // memset +#include // size_t + +/* check if the config defines are alright */ +#if defined(__APPLE__) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #define _SAPP_APPLE (1) + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + /* MacOS */ + #define _SAPP_MACOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") + #endif + #else + /* iOS or iOS Simulator */ + #define _SAPP_IOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") + #endif + #endif +#elif defined(__EMSCRIPTEN__) + /* emscripten (asm.js or wasm) */ + #define _SAPP_EMSCRIPTEN (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU") + #endif +#elif defined(_WIN32) + /* Windows (D3D11 or GL) */ + #include + #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) + #define _SAPP_UWP (1) + #if !defined(SOKOL_D3D11) + #error("sokol_app.h: unknown 3D API selected for UWP, must be SOKOL_D3D11") + #endif + #if !defined(__cplusplus) + #error("sokol_app.h: UWP bindings require C++/17") + #endif + #else + #define _SAPP_WIN32 (1) + #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") + #endif + #endif +#elif defined(__ANDROID__) + /* Android */ + #define _SAPP_ANDROID (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) + #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2") + #endif + #if defined(SOKOL_NO_ENTRY) + #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") + #endif +#elif defined(__linux__) || defined(__unix__) + /* Linux */ + #define _SAPP_LINUX (1) + #if !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33") + #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG (1) + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#if !defined(SOKOL_CALLOC) || !defined(SOKOL_FREE) + #include +#endif +#if !defined(SOKOL_CALLOC) + #define SOKOL_CALLOC(n,s) calloc(n,s) +#endif +#if !defined(SOKOL_FREE) + #define SOKOL_FREE(p) free(p) +#endif +#ifndef SOKOL_LOG + #ifdef SOKOL_DEBUG + #if defined(__ANDROID__) + #include + #define SOKOL_LOG(s) { SOKOL_ASSERT(s); __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s); } + #else + #include + #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } + #endif + #else + #define SOKOL_LOG(s) + #endif +#endif +#ifndef SOKOL_ABORT + #include + #define SOKOL_ABORT() abort() +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +/*== PLATFORM SPECIFIC INCLUDES AND DEFINES ==================================*/ +#if defined(_SAPP_APPLE) + #if defined(SOKOL_METAL) + #import + #import + #endif + #if defined(_SAPP_MACOS) + #if !defined(SOKOL_METAL) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #include + #endif + #elif defined(_SAPP_IOS) + #import + #if !defined(SOKOL_METAL) + #import + #include + #include + #endif + #endif +#elif defined(_SAPP_EMSCRIPTEN) + #if defined(SOKOL_GLES3) + #include + #elif defined(SOKOL_GLES2) + #ifndef GL_EXT_PROTOTYPES + #define GL_GLEXT_PROTOTYPES + #endif + #include + #include + #elif defined(SOKOL_WGPU) + #include + #endif + #include + #include +#elif defined(_SAPP_WIN32) + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ + #pragma warning(disable:4054) /* 'type cast': from function pointer */ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #pragma warning(disable:4996) /* 'freopen': This function or variable may be unsafe. */ + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem + #if defined(SOKOL_WIN32_FORCE_MAIN) + #pragma comment (linker, "/subsystem:console") + #else + #pragma comment (linker, "/subsystem:windows") + #endif + #endif + #include /* freopen() */ + + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ + #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) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #include + #include + // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it + #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) + #endif + #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ + #define WM_MOUSEHWHEEL (0x020E) + #endif +#elif defined(_SAPP_UWP) + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4054) /* 'type cast': from function pointer */ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #endif + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + + #pragma comment (lib, "WindowsApp") + #pragma comment (lib, "dxguid") +#elif defined(_SAPP_ANDROID) + #include + #include + #include + #include + #include + #if defined(SOKOL_GLES3) + #include + #else + #ifndef GL_EXT_PROTOTYPES + #define GL_GLEXT_PROTOTYPES + #endif + #include + #include + #endif +#elif defined(_SAPP_LINUX) + #define GL_GLEXT_PROTOTYPES + #include + #include + #include + #include + #include + #include + #include + #include + #include /* CARD32 */ + #include + #include /* dlopen, dlsym, dlclose */ + #include /* LONG_MAX */ + #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ +#endif + +/*== MACOS DECLARATIONS ======================================================*/ +#if defined(_SAPP_MACOS) +@interface _sapp_macos_app_delegate : NSObject +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject +@end +#if defined(SOKOL_METAL) + @interface _sapp_macos_view : MTKView + @end +#elif defined(SOKOL_GLCORE33) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#endif // SOKOL_GLCORE33 + +typedef struct { + uint32_t flags_changed_store; + uint8_t mouse_buttons; + NSWindow* window; + NSTrackingArea* tracking_area; + _sapp_macos_app_delegate* app_dlg; + _sapp_macos_window_delegate* win_dlg; + _sapp_macos_view* view; + #if defined(SOKOL_METAL) + id mtl_device; + #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +/*== IOS DECLARATIONS ========================================================*/ +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject +@end +@interface _sapp_textfield_dlg : NSObject +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) + @interface _sapp_ios_view : MTKView; + @end +#else + @interface _sapp_ios_view : GLKView + @end +#endif + +typedef struct { + UIWindow* window; + _sapp_ios_view* view; + UITextField* textfield; + _sapp_textfield_dlg* textfield_dlg; + #if defined(SOKOL_METAL) + UIViewController* view_ctrl; + id mtl_device; + #else + GLKViewController* view_ctrl; + EAGLContext* eagl_ctx; + #endif + bool suspended; +} _sapp_ios_t; + +#endif // _SAPP_IOS + +/*== EMSCRIPTEN DECLARATIONS =================================================*/ +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(SOKOL_WGPU) +typedef struct { + int state; + WGPUDevice device; + WGPUSwapChain swapchain; + WGPUTextureFormat render_format; + WGPUTexture msaa_tex; + WGPUTexture depth_stencil_tex; + WGPUTextureView swapchain_view; + WGPUTextureView msaa_view; + WGPUTextureView depth_stencil_view; +} _sapp_wgpu_t; +#endif + +typedef struct { + bool textfield_created; + bool wants_show_keyboard; + bool wants_hide_keyboard; + bool mouse_lock_requested; + #if defined(SOKOL_WGPU) + _sapp_wgpu_t wgpu; + #endif +} _sapp_emsc_t; +#endif // _SAPP_EMSCRIPTEN + +/*== WIN32 DECLARATIONS ======================================================*/ +#if defined(SOKOL_D3D11) && (defined(_SAPP_WIN32) || defined(_SAPP_UWP)) +typedef struct { + ID3D11Device* device; + ID3D11DeviceContext* device_context; + ID3D11Texture2D* rt; + ID3D11RenderTargetView* rtv; + ID3D11Texture2D* msaa_rt; + ID3D11RenderTargetView* msaa_rtv; + ID3D11Texture2D* ds; + ID3D11DepthStencilView* dsv; + DXGI_SWAP_CHAIN_DESC swap_chain_desc; + IDXGISwapChain* swap_chain; +} _sapp_d3d11_t; +#endif + +/*== WIN32 DECLARATIONS ======================================================*/ +#if defined(_SAPP_WIN32) + +#ifndef DPI_ENUMS_DECLARED +typedef enum PROCESS_DPI_AWARENESS +{ + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif /*DPI_ENUMS_DECLARED*/ + +typedef struct { + bool aware; + float content_scale; + float window_scale; + float mouse_scale; +} _sapp_win32_dpi_t; + +typedef struct { + HWND hwnd; + HDC dc; + UINT orig_codepage; + LONG mouse_locked_x, mouse_locked_y; + bool is_win10_or_greater; + bool in_create_window; + bool iconified; + bool mouse_tracked; + uint8_t mouse_capture_mask; + _sapp_win32_dpi_t dpi; + bool raw_input_mousepos_valid; + LONG raw_input_mousepos_x; + LONG raw_input_mousepos_y; + uint8_t raw_input_data[256]; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE33) +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); +typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); +typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); +typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); +typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); +typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); + +typedef struct { + HINSTANCE opengl32; + HGLRC gl_ctx; + PFN_wglCreateContext CreateContext; + PFN_wglDeleteContext DeleteContext; + PFN_wglGetProcAddress GetProcAddress; + PFN_wglGetCurrentDC GetCurrentDC; + PFN_wglMakeCurrent MakeCurrent; + PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; + PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; + PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; + PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + bool ext_swap_control; + bool arb_multisample; + bool arb_pixel_format; + bool arb_create_context; + bool arb_create_context_profile; + HWND msg_hwnd; + HDC msg_dc; +} _sapp_wgl_t; +#endif // SOKOL_GLCORE33 + +#endif // _SAPP_WIN32 + +/*== UWP DECLARATIONS ======================================================*/ +#if defined(_SAPP_UWP) + +typedef struct { + float content_scale; + float window_scale; + float mouse_scale; +} _sapp_uwp_dpi_t; + +typedef struct { + bool mouse_tracked; + uint8_t mouse_buttons; + _sapp_uwp_dpi_t dpi; +} _sapp_uwp_t; + +#endif // _SAPP_UWP + +/*== ANDROID DECLARATIONS ====================================================*/ + +#if defined(_SAPP_ANDROID) +typedef enum { + _SOKOL_ANDROID_MSG_CREATE, + _SOKOL_ANDROID_MSG_RESUME, + _SOKOL_ANDROID_MSG_PAUSE, + _SOKOL_ANDROID_MSG_FOCUS, + _SOKOL_ANDROID_MSG_NO_FOCUS, + _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, + _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, + _SOKOL_ANDROID_MSG_DESTROY, +} _sapp_android_msg_t; + +typedef struct { + pthread_t thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + int read_from_main_fd; + int write_from_main_fd; +} _sapp_android_pt_t; + +typedef struct { + ANativeWindow* window; + AInputQueue* input; +} _sapp_android_resources_t; + +typedef struct { + ANativeActivity* activity; + _sapp_android_pt_t pt; + _sapp_android_resources_t pending; + _sapp_android_resources_t current; + ALooper* looper; + bool is_thread_started; + bool is_thread_stopping; + bool is_thread_stopped; + bool has_created; + bool has_resumed; + bool has_focus; + EGLConfig config; + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_android_t; + +#endif // _SAPP_ANDROID + +/*== LINUX DECLARATIONS ======================================================*/ +#if defined(_SAPP_LINUX) + +#define _SAPP_X11_XDND_VERSION (5) + +#define GLX_VENDOR 1 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_DOUBLEBUFFER 5 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_SAMPLES 0x186a1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef XID GLXWindow; +typedef XID GLXDrawable; +typedef struct __GLXFBConfig* GLXFBConfig; +typedef struct __GLXcontext* GLXContext; +typedef void (*__GLXextproc)(void); + +typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); +typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); +typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); +typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); +typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); +typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); +typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); +typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); +typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const GLubyte *procName); +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); +typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); +typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); + +typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); + +typedef struct { + bool available; + int major_opcode; + int event_base; + int error_base; + int major; + int minor; +} _sapp_xi_t; + +typedef struct { + int version; + Window source; + Atom format; + Atom XdndAware; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XdndTypeList; + Atom text_uri_list; +} _sapp_xdnd_t; + +typedef struct { + uint8_t mouse_buttons; + Display* display; + int screen; + Window root; + Colormap colormap; + Window window; + Cursor hidden_cursor; + int window_state; + float dpi; + unsigned char error_code; + Atom UTF8_STRING; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom WM_STATE; + Atom NET_WM_NAME; + Atom NET_WM_ICON_NAME; + Atom NET_WM_STATE; + Atom NET_WM_STATE_FULLSCREEN; + _sapp_xi_t xi; + _sapp_xdnd_t xdnd; +} _sapp_x11_t; + +typedef struct { + void* libgl; + int major; + int minor; + int event_base; + int error_base; + GLXContext ctx; + GLXWindow window; + + // GLX 1.3 functions + PFNGLXGETFBCONFIGSPROC GetFBConfigs; + PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; + PFNGLXGETCLIENTSTRINGPROC GetClientString; + PFNGLXQUERYEXTENSIONPROC QueryExtension; + PFNGLXQUERYVERSIONPROC QueryVersion; + PFNGLXDESTROYCONTEXTPROC DestroyContext; + PFNGLXMAKECURRENTPROC MakeCurrent; + PFNGLXSWAPBUFFERSPROC SwapBuffers; + PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; + PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; + PFNGLXCREATEWINDOWPROC CreateWindow; + PFNGLXDESTROYWINDOWPROC DestroyWindow; + + // GLX 1.4 and extension functions + PFNGLXGETPROCADDRESSPROC GetProcAddress; + PFNGLXGETPROCADDRESSPROC GetProcAddressARB; + PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; + PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + + // extension availability + bool EXT_swap_control; + bool MESA_swap_control; + bool ARB_multisample; + bool ARB_create_context; + bool ARB_create_context_profile; +} _sapp_glx_t; + +#endif // _SAPP_LINUX + +/*== COMMON DECLARATIONS =====================================================*/ + +/* helper macros */ +#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) + +#define _SAPP_MAX_TITLE_LENGTH (128) +/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ +#define _SAPP_PIXELFORMAT_RGBA8 (23) +#define _SAPP_PIXELFORMAT_BGRA8 (27) +#define _SAPP_PIXELFORMAT_DEPTH (41) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (42) + +#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) + // this is ARC compatible + #if defined(__cplusplus) + #define _SAPP_CLEAR(type, item) { item = (type) { }; } + #else + #define _SAPP_CLEAR(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SAPP_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } +#endif + +typedef struct { + bool enabled; + int buf_size; + char* buffer; +} _sapp_clipboard_t; + +typedef struct { + bool enabled; + int max_files; + int max_path_length; + int num_files; + int buf_size; + char* buffer; +} _sapp_drop_t; + +typedef struct { + float x, y; + float dx, dy; + bool shown; + bool locked; + bool pos_valid; +} _sapp_mouse_t; + +typedef struct { + sapp_desc desc; + bool valid; + bool fullscreen; + bool gles2_fallback; + bool first_frame; + bool init_called; + bool cleanup_called; + bool quit_requested; + bool quit_ordered; + bool event_consumed; + bool html5_ask_leave_site; + bool onscreen_keyboard_shown; + int window_width; + int window_height; + int framebuffer_width; + int framebuffer_height; + int sample_count; + int swap_interval; + float dpi_scale; + uint64_t frame_count; + sapp_event event; + _sapp_mouse_t mouse; + _sapp_clipboard_t clipboard; + _sapp_drop_t drop; + #if defined(_SAPP_MACOS) + _sapp_macos_t macos; + #elif defined(_SAPP_IOS) + _sapp_ios_t ios; + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_t emsc; + #elif defined(_SAPP_WIN32) + _sapp_win32_t win32; + #if defined(SOKOL_D3D11) + _sapp_d3d11_t d3d11; + #elif defined(SOKOL_GLCORE33) + _sapp_wgl_t wgl; + #endif + #elif defined(_SAPP_UWP) + _sapp_uwp_t uwp; + #if defined(SOKOL_D3D11) + _sapp_d3d11_t d3d11; + #endif + #elif defined(_SAPP_ANDROID) + _sapp_android_t android; + #elif defined(_SAPP_LINUX) + _sapp_x11_t x11; + _sapp_glx_t glx; + #endif + char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; + char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ + wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ + sapp_keycode keycodes[SAPP_MAX_KEYCODES]; +} _sapp_t; +static _sapp_t _sapp; + +/*=== OPTIONAL MINI GL LOADER FOR WIN32/WGL ==================================*/ +#if defined(_SAPP_WIN32) && defined(SOKOL_GLCORE33) && !defined(SOKOL_WIN32_NO_GL_LOADER) +#define __gl_h_ 1 +#define __gl32_h_ 1 +#define __gl31_h_ 1 +#define __GL_H__ 1 +#define __glext_h_ 1 +#define __GLEXT_H_ 1 +#define __gltypes_h_ 1 +#define __glcorearb_h_ 1 +#define __gl_glcorearb_h_ 1 +#define GL_APIENTRY APIENTRY + +typedef unsigned int GLenum; +typedef unsigned int GLuint; +typedef int GLsizei; +typedef char GLchar; +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +typedef double GLclampd; +typedef unsigned short GLushort; +typedef unsigned char GLubyte; +typedef unsigned char GLboolean; +typedef uint64_t GLuint64; +typedef double GLdouble; +typedef unsigned short GLhalf; +typedef float GLclampf; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef void GLvoid; +typedef int64_t GLint64; +typedef float GLfloat; +typedef struct __GLsync * GLsync; +typedef int GLint; +#define GL_INT_2_10_10_10_REV 0x8D9F +#define GL_R32F 0x822E +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_R16F 0x822D +#define GL_COLOR_ATTACHMENT22 0x8CF6 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_NUM_EXTENSIONS 0x821D +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_INCR 0x1E02 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_STATIC_DRAW 0x88E4 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONSTANT_COLOR 0x8001 +#define GL_DECR_WRAP 0x8508 +#define GL_R8 0x8229 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_SHORT 0x1402 +#define GL_DEPTH_TEST 0x0B71 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_LINK_STATUS 0x8B82 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_RGBA16F 0x881A +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_STREAM_DRAW 0x88E0 +#define GL_ONE 1 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA8 0x8058 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_RGBA4 0x8056 +#define GL_RGB8 0x8051 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_STENCIL 0x1802 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_DEPTH 0x1801 +#define GL_FRONT 0x0404 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_REPEAT 0x2901 +#define GL_RGBA 0x1908 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_DECR 0x1E03 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_FLOAT 0x1406 +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_COLOR 0x1800 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TRIANGLES 0x0004 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_NONE 0 +#define GL_SRC_COLOR 0x0300 +#define GL_BYTE 0x1400 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_LINE_STRIP 0x0003 +#define GL_TEXTURE_3D 0x806F +#define GL_CW 0x0900 +#define GL_LINEAR 0x2601 +#define GL_RENDERBUFFER 0x8D41 +#define GL_GEQUAL 0x0206 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_RGBA32F 0x8814 +#define GL_BLEND 0x0BE2 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_EXTENSIONS 0x1F03 +#define GL_NO_ERROR 0 +#define GL_REPLACE 0x1E01 +#define GL_KEEP 0x1E00 +#define GL_CCW 0x0901 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_RGB 0x1907 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_FALSE 0 +#define GL_ZERO 0 +#define GL_CULL_FACE 0x0B44 +#define GL_INVERT 0x150A +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_NEAREST 0x2600 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_LEQUAL 0x0203 +#define GL_STENCIL_TEST 0x0B90 +#define GL_DITHER 0x0BD0 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_EQUAL 0x0202 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RGB5 0x8050 +#define GL_LINES 0x0001 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_SRC_ALPHA 0x0302 +#define GL_INCR_WRAP 0x8507 +#define GL_LESS 0x0201 +#define GL_MULTISAMPLE 0x809D +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_BACK 0x0405 +#define GL_ALWAYS 0x0207 +#define GL_FUNC_ADD 0x8006 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_NOTEQUAL 0x0205 +#define GL_DST_COLOR 0x0306 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_RED 0x1903 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_DST_ALPHA 0x0304 +#define GL_RGB5_A1 0x8057 +#define GL_GREATER 0x0204 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_TRUE 1 +#define GL_NEVER 0x0200 +#define GL_POINTS 0x0000 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#define GL_RGBA_INTEGER 0x8D99 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_RGBA16 0x805B +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_CURRENT_PROGRAM 0x8B8D + +// X Macro list of GL function names and signatures +#define _SAPP_GL_FUNCS \ + _SAPP_XMACRO(glBindVertexArray, void, (GLuint array)) \ + _SAPP_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ + _SAPP_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ + _SAPP_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ + _SAPP_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ + _SAPP_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ + _SAPP_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ + _SAPP_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ + _SAPP_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ + _SAPP_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ + _SAPP_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ + _SAPP_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SAPP_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SAPP_XMACRO(glUseProgram, void, (GLuint program)) \ + _SAPP_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ + _SAPP_XMACRO(glLinkProgram, void, (GLuint program)) \ + _SAPP_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ + _SAPP_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ + _SAPP_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SAPP_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ + _SAPP_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ + _SAPP_XMACRO(glDeleteShader, void, (GLuint shader)) \ + _SAPP_XMACRO(glDeleteProgram, void, (GLuint program)) \ + _SAPP_XMACRO(glCompileShader, void, (GLuint shader)) \ + _SAPP_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ + _SAPP_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ + _SAPP_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SAPP_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ + _SAPP_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ + _SAPP_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ + _SAPP_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ + _SAPP_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ + _SAPP_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ + _SAPP_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ + _SAPP_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ + _SAPP_XMACRO(glActiveTexture, void, (GLenum texture)) \ + _SAPP_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ + _SAPP_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ + _SAPP_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SAPP_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ + _SAPP_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ + _SAPP_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ + _SAPP_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ + _SAPP_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ + _SAPP_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ + _SAPP_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ + _SAPP_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ + _SAPP_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SAPP_XMACRO(glCreateShader, GLuint, (GLenum type)) \ + _SAPP_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ + _SAPP_XMACRO(glClearDepth, void, (GLdouble depth)) \ + _SAPP_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ + _SAPP_XMACRO(glCreateProgram, GLuint, (void)) \ + _SAPP_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SAPP_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ + _SAPP_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ + _SAPP_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ + _SAPP_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ + _SAPP_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ + _SAPP_XMACRO(glDisable, void, (GLenum cap)) \ + _SAPP_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SAPP_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SAPP_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ + _SAPP_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ + _SAPP_XMACRO(glDepthMask, void, (GLboolean flag)) \ + _SAPP_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ + _SAPP_XMACRO(glClearStencil, void, (GLint s)) \ + _SAPP_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SAPP_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SAPP_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ + _SAPP_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ + _SAPP_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ + _SAPP_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ + _SAPP_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ + _SAPP_XMACRO(glEnable, void, (GLenum cap)) \ + _SAPP_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ + _SAPP_XMACRO(glStencilMask, void, (GLuint mask)) \ + _SAPP_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ + _SAPP_XMACRO(glGetError, GLenum, (void)) \ + _SAPP_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SAPP_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SAPP_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ + _SAPP_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, GLfloat* params)) \ + _SAPP_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SAPP_XMACRO(glDepthFunc, void, (GLenum func)) \ + _SAPP_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ + _SAPP_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ + _SAPP_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ + _SAPP_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ + _SAPP_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SAPP_XMACRO(glReadBuffer, void, (GLenum src)) \ + _SAPP_XMACRO(glClear, void, (GLbitfield mask)) \ + _SAPP_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SAPP_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ + _SAPP_XMACRO(glFrontFace, void, (GLenum mode)) \ + _SAPP_XMACRO(glCullFace, void, (GLenum mode)) + +// generate GL function pointer typedefs +#define _SAPP_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; +_SAPP_GL_FUNCS +#undef _SAPP_XMACRO + +// generate GL function pointers +#define _SAPP_XMACRO(name, ret, args) static PFN_ ## name name; +_SAPP_GL_FUNCS +#undef _SAPP_XMACRO + +// helper function to lookup GL functions in GL DLL +_SOKOL_PRIVATE void* _sapp_win32_glgetprocaddr(const char* name) { + void* proc_addr = (void*) _sapp.wgl.GetProcAddress(name); + if (0 == proc_addr) { + proc_addr = (void*) GetProcAddress(_sapp.wgl.opengl32, name); + } + SOKOL_ASSERT(proc_addr); + return proc_addr; +} + +// populate GL function pointers +_SOKOL_PRIVATE void _sapp_win32_gl_loadfuncs(void) { + SOKOL_ASSERT(_sapp.wgl.GetProcAddress); + SOKOL_ASSERT(_sapp.wgl.opengl32); + #define _SAPP_XMACRO(name, ret, args) name = (PFN_ ## name) _sapp_win32_glgetprocaddr(#name); + _SAPP_GL_FUNCS + #undef _SAPP_XMACRO +} + +#endif // _SAPP_WIN32 && SOKOL_GLCORE33 && !SOKOL_WIN32_NO_GL_LOADER + +/*=== PRIVATE HELPER FUNCTIONS ===============================================*/ +_SOKOL_PRIVATE void _sapp_fail(const char* msg) { + if (_sapp.desc.fail_cb) { + _sapp.desc.fail_cb(msg); + } + else if (_sapp.desc.fail_userdata_cb) { + _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data); + } + else { + SOKOL_LOG(msg); + } + SOKOL_ABORT(); +} + +_SOKOL_PRIVATE void _sapp_call_init(void) { + if (_sapp.desc.init_cb) { + _sapp.desc.init_cb(); + } + else if (_sapp.desc.init_userdata_cb) { + _sapp.desc.init_userdata_cb(_sapp.desc.user_data); + } + _sapp.init_called = true; +} + +_SOKOL_PRIVATE void _sapp_call_frame(void) { + if (_sapp.init_called && !_sapp.cleanup_called) { + if (_sapp.desc.frame_cb) { + _sapp.desc.frame_cb(); + } + else if (_sapp.desc.frame_userdata_cb) { + _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); + } + } +} + +_SOKOL_PRIVATE void _sapp_call_cleanup(void) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.cleanup_cb) { + _sapp.desc.cleanup_cb(); + } + else if (_sapp.desc.cleanup_userdata_cb) { + _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); + } + _sapp.cleanup_called = true; + } +} + +_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.event_cb) { + _sapp.desc.event_cb(e); + } + else if (_sapp.desc.event_userdata_cb) { + _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); + } + } + if (_sapp.event_consumed) { + _sapp.event_consumed = false; + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { + SOKOL_ASSERT(_sapp.drop.buffer); + SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); + int offset = index * _sapp.drop.max_path_length; + SOKOL_ASSERT(offset < _sapp.drop.buf_size); + return &_sapp.drop.buffer[offset]; +} + +/* Copy a string into a fixed size buffer with guaranteed zero- + termination. + + Return false if the string didn't fit into the buffer and had to be clamped. + + FIXME: Currently UTF-8 strings might become invalid if the string + is clamped, because the last zero-byte might be written into + the middle of a multi-byte sequence. +*/ +_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { + SOKOL_ASSERT(src && dst && (max_len > 0)); + char* const end = &(dst[max_len-1]); + char c = 0; + for (int i = 0; i < max_len; i++) { + c = *src; + if (c != 0) { + src++; + } + *dst++ = c; + } + /* truncated? */ + if (c != 0) { + *end = 0; + return false; + } + else { + return true; + } +} + +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* in_desc) { + sapp_desc desc = *in_desc; + desc.width = _sapp_def(desc.width, 640); + desc.height = _sapp_def(desc.height, 480); + desc.sample_count = _sapp_def(desc.sample_count, 1); + desc.swap_interval = _sapp_def(desc.swap_interval, 1); + desc.html5_canvas_name = _sapp_def(desc.html5_canvas_name, "canvas"); + desc.clipboard_size = _sapp_def(desc.clipboard_size, 8192); + desc.max_dropped_files = _sapp_def(desc.max_dropped_files, 1); + desc.max_dropped_file_path_length = _sapp_def(desc.max_dropped_file_path_length, 2048); + desc.window_title = _sapp_def(desc.window_title, "sokol_app"); + return desc; +} + +_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { + _SAPP_CLEAR(_sapp_t, _sapp); + _sapp.desc = _sapp_desc_defaults(desc); + _sapp.first_frame = true; + _sapp.window_width = _sapp.desc.width; + _sapp.window_height = _sapp.desc.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp.sample_count = _sapp.desc.sample_count; + _sapp.swap_interval = _sapp.desc.swap_interval; + _sapp.html5_canvas_selector[0] = '#'; + _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); + _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; + _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; + _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; + if (_sapp.clipboard.enabled) { + _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; + _sapp.clipboard.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.clipboard.buf_size); + } + _sapp.drop.enabled = _sapp.desc.enable_dragndrop; + if (_sapp.drop.enabled) { + _sapp.drop.max_files = _sapp.desc.max_dropped_files; + _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; + _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; + _sapp.drop.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.drop.buf_size); + } + _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); + _sapp.desc.window_title = _sapp.window_title; + _sapp.dpi_scale = 1.0f; + _sapp.fullscreen = _sapp.desc.fullscreen; + _sapp.mouse.shown = true; +} + +_SOKOL_PRIVATE void _sapp_discard_state(void) { + if (_sapp.clipboard.enabled) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + SOKOL_FREE((void*)_sapp.clipboard.buffer); + } + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + SOKOL_FREE((void*)_sapp.drop.buffer); + } + _SAPP_CLEAR(_sapp_t, _sapp); +} + +_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { + memset(&_sapp.event, 0, sizeof(_sapp.event)); + _sapp.event.type = type; + _sapp.event.frame_count = _sapp.frame_count; + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + _sapp.event.window_width = _sapp.window_width; + _sapp.event.window_height = _sapp.window_height; + _sapp.event.framebuffer_width = _sapp.framebuffer_width; + _sapp.event.framebuffer_height = _sapp.framebuffer_height; + _sapp.event.mouse_x = _sapp.mouse.x; + _sapp.event.mouse_y = _sapp.mouse.y; + _sapp.event.mouse_dx = _sapp.mouse.dx; + _sapp.event.mouse_dy = _sapp.mouse.dy; +} + +_SOKOL_PRIVATE bool _sapp_events_enabled(void) { + /* only send events when an event callback is set, and the init function was called */ + return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { + if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { + return _sapp.keycodes[scan_code]; + } + else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + memset(_sapp.drop.buffer, 0, (size_t)_sapp.drop.buf_size); + } +} + +_SOKOL_PRIVATE void _sapp_frame(void) { + if (_sapp.first_frame) { + _sapp.first_frame = false; + _sapp_call_init(); + } + _sapp_call_frame(); + _sapp.frame_count++; +} + +/*== MacOS/iOS ===============================================================*/ +#if defined(_SAPP_APPLE) + +#if __has_feature(objc_arc) +#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +/*== MacOS ===================================================================*/ +#if defined(_SAPP_MACOS) + +_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { + _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; + _sapp.keycodes[0x12] = SAPP_KEYCODE_1; + _sapp.keycodes[0x13] = SAPP_KEYCODE_2; + _sapp.keycodes[0x14] = SAPP_KEYCODE_3; + _sapp.keycodes[0x15] = SAPP_KEYCODE_4; + _sapp.keycodes[0x17] = SAPP_KEYCODE_5; + _sapp.keycodes[0x16] = SAPP_KEYCODE_6; + _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; + _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; + _sapp.keycodes[0x19] = SAPP_KEYCODE_9; + _sapp.keycodes[0x00] = SAPP_KEYCODE_A; + _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; + _sapp.keycodes[0x08] = SAPP_KEYCODE_C; + _sapp.keycodes[0x02] = SAPP_KEYCODE_D; + _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; + _sapp.keycodes[0x03] = SAPP_KEYCODE_F; + _sapp.keycodes[0x05] = SAPP_KEYCODE_G; + _sapp.keycodes[0x04] = SAPP_KEYCODE_H; + _sapp.keycodes[0x22] = SAPP_KEYCODE_I; + _sapp.keycodes[0x26] = SAPP_KEYCODE_J; + _sapp.keycodes[0x28] = SAPP_KEYCODE_K; + _sapp.keycodes[0x25] = SAPP_KEYCODE_L; + _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; + _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; + _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; + _sapp.keycodes[0x23] = SAPP_KEYCODE_P; + _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01] = SAPP_KEYCODE_S; + _sapp.keycodes[0x11] = SAPP_KEYCODE_T; + _sapp.keycodes[0x20] = SAPP_KEYCODE_U; + _sapp.keycodes[0x09] = SAPP_KEYCODE_V; + _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; + _sapp.keycodes[0x07] = SAPP_KEYCODE_X; + _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; + _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x77] = SAPP_KEYCODE_END; + _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; + _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; +} + +_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.view); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); + #endif + _SAPP_OBJC_RELEASE(_sapp.macos.window); +} + +_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_macos_init_keytable(); + [NSApplication sharedApplication]; + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + _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 +} + +/* MacOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_macos_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE uint32_t _sapp_macos_mod(NSEventModifierFlags f) { + uint32_t m = 0; + if (f & NSEventModifierFlagShift) { + m |= SAPP_MODIFIER_SHIFT; + } + if (f & NSEventModifierFlagControl) { + m |= SAPP_MODIFIER_CTRL; + } + if (f & NSEventModifierFlagOption) { + m |= SAPP_MODIFIER_ALT; + } + if (f & NSEventModifierFlagCommand) { + m |= SAPP_MODIFIER_SUPER; + } + return m; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { + #if defined(SOKOL_METAL) + const NSRect fb_rect = [_sapp.macos.view bounds]; + _sapp.framebuffer_width = fb_rect.size.width * _sapp.dpi_scale; + _sapp.framebuffer_height = fb_rect.size.height * _sapp.dpi_scale; + #elif defined(SOKOL_GLCORE33) + const NSRect fb_rect = [_sapp.macos.view convertRectToBacking:[_sapp.macos.view frame]]; + _sapp.framebuffer_width = fb_rect.size.width; + _sapp.framebuffer_height = fb_rect.size.height; + #endif + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = bounds.size.width; + _sapp.window_height = bounds.size.height; + if (_sapp.framebuffer_width == 0) { + _sapp.framebuffer_width = 1; + } + if (_sapp.framebuffer_height == 0) { + _sapp.framebuffer_height = 1; + } + if (_sapp.window_width == 0) { + _sapp.window_width = 1; + } + if (_sapp.window_height == 0) { + _sapp.window_height = 1; + } + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + + /* also make sure the MTKView drawable size is uptodate */ + #if defined(SOKOL_METAL) + CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + #endif +} + +_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { + /* NOTE: the _sapp.fullscreen flag is also notified by the + windowDidEnterFullscreen / windowDidExitFullscreen + event handlers + */ + _sapp.fullscreen = !_sapp.fullscreen; + [_sapp.macos.window toggleFullScreen:nil]; +} + +_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(str) forType:NSPasteboardTypeString]; + } +} + +_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + @autoreleasepool { + _sapp.clipboard.buffer[0] = 0; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { + return _sapp.clipboard.buffer; + } + NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; + if (!str) { + return _sapp.clipboard.buffer; + } + _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + } + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { + [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; +} + +_SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) { + if (!_sapp.mouse.locked) { + const NSPoint mouse_pos = event.locationInWindow; + float new_x = mouse_pos.x * _sapp.dpi_scale; + float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; + /* don't update dx/dy in the very first update */ + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { + /* NOTE: this function is only called when the mouse visibility actually changes */ + if (visible) { + CGDisplayShowCursor(kCGDirectMainDisplay); + } + else { + CGDisplayHideCursor(kCGDirectMainDisplay); + } +} + +_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + /* + NOTE that this code doesn't warp the mouse cursor to the window + center as everybody else does it. This lead to a spike in the + *second* mouse-moved event after the warp happened. The + mouse centering doesn't seem to be required (mouse-moved events + are reported correctly even when the cursor is at an edge of the screen). + + NOTE also that the hide/show of the mouse cursor should properly + stack with calls to sapp_show_mouse() + */ + if (_sapp.mouse.locked) { + CGAssociateMouseAndMouseCursorPosition(NO); + CGDisplayHideCursor(kCGDirectMainDisplay); + } + else { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(YES); + } +} + +_SOKOL_PRIVATE void _sapp_macos_frame(void) { + _sapp_frame(); + if (_sapp.quit_requested || _sapp.quit_ordered) { + [_sapp.macos.window performClose:nil]; + } +} + +@implementation _sapp_macos_app_delegate +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + _SOKOL_UNUSED(aNotification); + if (_sapp.fullscreen) { + NSRect screen_rect = NSScreen.mainScreen.frame; + _sapp.window_width = screen_rect.size.width; + _sapp.window_height = screen_rect.size.height; + } + if (_sapp.desc.high_dpi) { + _sapp.framebuffer_width = 2 * _sapp.window_width; + _sapp.framebuffer_height = 2 * _sapp.window_height; + } + else { + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + } + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; + const NSUInteger style = + NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable; + NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); + _sapp.macos.window = [[_sapp_macos_window alloc] + initWithContentRect:window_rect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate + _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; + _sapp.macos.window.acceptsMouseMovedEvents = YES; + _sapp.macos.window.restorable = YES; + + _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; + _sapp.macos.window.delegate = _sapp.macos.win_dlg; + #if defined(SOKOL_METAL) + _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.macos.view = [[_sapp_macos_view alloc] init]; + [_sapp.macos.view updateTrackingAreas]; + _sapp.macos.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.macos.view.device = _sapp.macos.mtl_device; + _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; + _sapp.macos.view.autoResizeDrawable = false; + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; + #elif defined(SOKOL_GLCORE33) + NSOpenGLPixelFormatAttribute attrs[32]; + int i = 0; + attrs[i++] = NSOpenGLPFAAccelerated; + attrs[i++] = NSOpenGLPFADoubleBuffer; + attrs[i++] = NSOpenGLPFAOpenGLProfile; attrs[i++] = NSOpenGLProfileVersion3_2Core; + attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; + attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; + if (_sapp.sample_count > 1) { + attrs[i++] = NSOpenGLPFAMultisample; + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; + } + else { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; + } + attrs[i++] = 0; + NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + SOKOL_ASSERT(glpixelformat_obj != nil); + + _sapp.macos.view = [[_sapp_macos_view alloc] + initWithFrame:window_rect + pixelFormat:glpixelformat_obj]; + _SAPP_OBJC_RELEASE(glpixelformat_obj); + [_sapp.macos.view updateTrackingAreas]; + if (_sapp.desc.high_dpi) { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; + } + else { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; + } + + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + + NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 + target:_sapp.macos.view + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; + timer_obj = nil; + #endif + _sapp.valid = true; + if (_sapp.fullscreen) { + /* on GL, this already toggles a rendered frame, so set the valid flag before */ + [_sapp.macos.window toggleFullScreen:self]; + } + else { + [_sapp.macos.window center]; + } + [_sapp.macos.window makeKeyAndOrderFront:nil]; + _sapp_macos_update_dimensions(); + [NSEvent setMouseCoalescingEnabled:NO]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { + _SOKOL_UNUSED(sender); + return YES; +} + +- (void)applicationWillTerminate:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_call_cleanup(); + _sapp_macos_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_macos_window_delegate +- (BOOL)windowShouldClose:(id)sender { + _SOKOL_UNUSED(sender); + /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a chance to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + return YES; + } + else { + return NO; + } +} + +- (void)windowDidResize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_update_dimensions(); + if (!_sapp.first_frame) { + _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +- (void)windowDidMiniaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = true; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = false; +} +@end + +@implementation _sapp_macos_window +- (instancetype)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag { + if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; + #endif + } + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + NSPasteboard *pboard = [sender draggingPasteboard]; + if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : pboard.pasteboardItems.count; + bool drop_failed = false; + for (int i = 0; i < _sapp.drop.num_files; i++) { + NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; + if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); + drop_failed = true; + break; + } + } + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } + return YES; + } + #endif + return NO; +} +@end + +@implementation _sapp_macos_view +#if defined(SOKOL_GLCORE33) +/* NOTE: this is a hack/fix when the initial window size has been clipped by + macOS because it didn't fit on the screen, in that case the + frame size of the window is reported wrong if low-dpi rendering + was requested (instead the high-dpi dimensions are returned) + until the window is resized for the first time. + + Hooking into reshape and getting the frame dimensions seems to report + the correct dimensions. +*/ +- (void)reshape { + _sapp_macos_update_dimensions(); + [super reshape]; +} +- (void)timerFired:(id)sender { + _SOKOL_UNUSED(sender); + [self setNeedsDisplay:YES]; +} +- (void)prepareOpenGL { + [super prepareOpenGL]; + GLint swapInt = 1; + NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; + [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; + [ctx makeCurrentContext]; +} +#endif + +_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { + const NSEventMask mask = NSEventMaskLeftMouseDown | + NSEventMaskLeftMouseUp| + NSEventMaskRightMouseDown | + NSEventMaskRightMouseUp | + NSEventMaskMouseMoved | + NSEventMaskLeftMouseDragged | + NSEventMaskRightMouseDragged | + NSEventMaskMouseEntered | + NSEventMaskMouseExited | + NSEventMaskKeyDown | + NSEventMaskKeyUp | + NSEventMaskCursorUpdate | + NSEventMaskScrollWheel | + NSEventMaskTabletPoint | + NSEventMaskTabletProximity | + NSEventMaskOtherMouseDown | + NSEventMaskOtherMouseUp | + NSEventMaskOtherMouseDragged | + NSEventMaskPressure | + NSEventMaskDirectTouch; + @autoreleasepool { + for (;;) { + // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, + // see: https://github.com/floooh/sokol/issues/486 + NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; + if (event == nil) { + break; + } + [NSApp sendEvent:event]; + } + } +} + +- (void)drawRect:(NSRect)rect { + _SOKOL_UNUSED(rect); + /* Catch any last-moment input events */ + _sapp_macos_poll_input_events(); + _sapp_macos_frame(); + #if !defined(SOKOL_METAL) + [[_sapp.macos.view openGLContext] flushBuffer]; + #endif +} + +- (BOOL)isOpaque { + return YES; +} +- (BOOL)canBecomeKeyView { + return YES; +} +- (BOOL)acceptsFirstResponder { + return YES; +} +- (void)updateTrackingAreas { + if (_sapp.macos.tracking_area != nil) { + [self removeTrackingArea:_sapp.macos.tracking_area]; + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + } + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + [self addTrackingArea:_sapp.macos.tracking_area]; + [super updateTrackingAreas]; +} +- (void)mouseEntered:(NSEvent*)event { + _sapp_macos_update_mouse(event); + /* don't send mouse enter/leave while dragging (so that it behaves the same as + 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)); + } +} +- (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)); + } +} +- (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_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.scroll_x = dx; + _sapp.event.scroll_y = dy; + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyDown:(NSEvent*)event { + if (_sapp_events_enabled()) { + const uint32_t mods = _sapp_macos_mod(event.modifierFlags); + /* 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 + */ + const sapp_keycode key_code = _sapp_translate_key(event.keyCode); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); + if (0 != (mods & SAPP_MODIFIER_SUPER)) { + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods); + } + const NSString* chars = event.characters; + const NSUInteger len = chars.length; + if (len > 0) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = mods; + for (NSUInteger i = 0; i < len; i++) { + const unichar codepoint = [chars characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + _sapp.event.char_code = codepoint; + _sapp.event.key_repeat = event.isARepeat; + _sapp_call_event(&_sapp.event); + } + } + /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ + if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyUp:(NSEvent*)event { + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, + _sapp_translate_key(event.keyCode), + event.isARepeat, + _sapp_macos_mod(event.modifierFlags)); +} +- (void)flagsChanged:(NSEvent*)event { + const uint32_t old_f = _sapp.macos.flags_changed_store; + const uint32_t new_f = event.modifierFlags; + _sapp.macos.flags_changed_store = new_f; + sapp_keycode key_code = SAPP_KEYCODE_INVALID; + bool down = false; + if ((new_f ^ old_f) & NSEventModifierFlagShift) { + key_code = SAPP_KEYCODE_LEFT_SHIFT; + down = 0 != (new_f & NSEventModifierFlagShift); + } + if ((new_f ^ old_f) & NSEventModifierFlagControl) { + key_code = SAPP_KEYCODE_LEFT_CONTROL; + down = 0 != (new_f & NSEventModifierFlagControl); + } + if ((new_f ^ old_f) & NSEventModifierFlagOption) { + key_code = SAPP_KEYCODE_LEFT_ALT; + down = 0 != (new_f & NSEventModifierFlagOption); + } + if ((new_f ^ old_f) & NSEventModifierFlagCommand) { + key_code = SAPP_KEYCODE_LEFT_SUPER; + down = 0 != (new_f & NSEventModifierFlagCommand); + } + if (key_code != SAPP_KEYCODE_INVALID) { + _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, + key_code, + false, + _sapp_macos_mod(event.modifierFlags)); + } +} +- (void)cursorUpdate:(NSEvent*)event { + _SOKOL_UNUSED(event); + if (_sapp.desc.user_cursor) { + _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); + } +} +@end + +#endif /* MacOS */ + +/*== iOS =====================================================================*/ +#if defined(_SAPP_IOS) + +_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); + _SAPP_OBJC_RELEASE(_sapp.ios.textfield); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); + #else + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); + #endif + _SAPP_OBJC_RELEASE(_sapp.ios.view); + _SAPP_OBJC_RELEASE(_sapp.ios.window); +} + +_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { + _sapp_init_state(desc); + static int argc = 1; + static char* argv[] = { (char*)"sokol_app" }; + UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); +} + +/* iOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_ios_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + NSEnumerator* enumerator = event.allTouches.objectEnumerator; + UITouch* ios_touch; + while ((ios_touch = [enumerator nextObject])) { + if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { + CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; + sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; + cur_point->identifier = (uintptr_t) ios_touch; + cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; + cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; + cur_point->changed = [touches containsObject:ios_touch]; + } + } + if (_sapp.event.num_touches > 0) { + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.window_width = (int) screen_rect.size.width; + _sapp.window_height = (int) screen_rect.size.height; + int cur_fb_width, cur_fb_height; + #if defined(SOKOL_METAL) + const CGSize fb_size = _sapp.ios.view.drawableSize; + cur_fb_width = (int) fb_size.width; + cur_fb_height = (int) fb_size.height; + #else + cur_fb_width = (int) _sapp.ios.view.drawableWidth; + cur_fb_height = (int) _sapp.ios.view.drawableHeight; + #endif + const bool dim_changed = + (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + _sapp.framebuffer_width = cur_fb_width; + _sapp.framebuffer_height = cur_fb_height; + SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; + if (dim_changed && !_sapp.first_frame) { + _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +_SOKOL_PRIVATE void _sapp_ios_frame(void) { + _sapp_ios_update_dimensions(); + _sapp_frame(); +} + +_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { + /* if not happened yet, create an invisible text field */ + if (nil == _sapp.ios.textfield) { + _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; + _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; + _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; + _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; + _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; + _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; + _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; + _sapp.ios.textfield.hidden = YES; + _sapp.ios.textfield.text = @"x"; + _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; + [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; + + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWasShown:) + name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWillBeHidden:) + name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardDidChangeFrame:) + name:UIKeyboardDidChangeFrameNotification object:nil]; + } + if (shown) { + /* setting the text field as first responder brings up the onscreen keyboard */ + [_sapp.ios.textfield becomeFirstResponder]; + } + else { + [_sapp.ios.textfield resignFirstResponder]; + } +} + +@implementation _sapp_app_delegate +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; + _sapp.window_width = screen_rect.size.width; + _sapp.window_height = screen_rect.size.height; + if (_sapp.desc.high_dpi) { + _sapp.framebuffer_width = 2 * _sapp.window_width; + _sapp.framebuffer_height = 2 * _sapp.window_height; + } + else { + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + } + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; + #if defined(SOKOL_METAL) + _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.ios.view = [[_sapp_ios_view alloc] init]; + _sapp.ios.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.ios.view.device = _sapp.ios.mtl_device; + _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; + if (_sapp.desc.high_dpi) { + _sapp.ios.view.contentScaleFactor = 2.0; + } + else { + _sapp.ios.view.contentScaleFactor = 1.0; + } + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + _sapp.ios.view_ctrl = [[UIViewController alloc] init]; + _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #else + if (_sapp.desc.gl_force_gles2) { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + _sapp.gles2_fallback = true; + } + else { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + if (_sapp.ios.eagl_ctx == nil) { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + _sapp.gles2_fallback = true; + } + } + _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; + _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; + _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; + _sapp.ios.view.drawableMultisample = GLKViewDrawableMultisampleNone; /* FIXME */ + _sapp.ios.view.context = _sapp.ios.eagl_ctx; + _sapp.ios.view.enableSetNeedsDisplay = NO; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + if (_sapp.desc.high_dpi) { + _sapp.ios.view.contentScaleFactor = 2.0; + } + else { + _sapp.ios.view.contentScaleFactor = 1.0; + } + _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.view_ctrl.preferredFramesPerSecond = 60 / _sapp.swap_interval; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #endif + [_sapp.ios.window makeKeyAndVisible]; + + _sapp.valid = true; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + if (!_sapp.ios.suspended) { + _sapp.ios.suspended = true; + _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); + } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + if (_sapp.ios.suspended) { + _sapp.ios.suspended = false; + _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); + } +} + +/* NOTE: this method will rarely ever be called, iOS application + which are terminated by the user are usually killed via signal 9 + by the operating system. +*/ +- (void)applicationWillTerminate:(UIApplication *)application { + _SOKOL_UNUSED(application); + _sapp_call_cleanup(); + _sapp_ios_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_textfield_dlg +- (void)keyboardWasShown:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = true; + /* query the keyboard's size, and modify the content view's size */ + if (_sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (void)keyboardWillBeHidden:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = false; + if (_sapp.desc.ios_keyboard_resizes_canvas) { + _sapp.ios.view.frame = UIScreen.mainScreen.bounds; + } +} +- (void)keyboardDidChangeFrame:(NSNotification*)notif { + /* this is for the case when the screen rotation changes while the keyboard is open */ + if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { + if (_sapp_events_enabled()) { + const NSUInteger len = string.length; + if (len > 0) { + for (NSUInteger i = 0; i < len; i++) { + unichar c = [string characterAtIndex:i]; + if (c >= 32) { + /* ignore surrogates for now */ + if ((c < 0xD800) || (c > 0xDFFF)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = c; + _sapp_call_event(&_sapp.event); + } + } + if (c <= 32) { + sapp_keycode k = SAPP_KEYCODE_INVALID; + switch (c) { + case 10: k = SAPP_KEYCODE_ENTER; break; + case 32: k = SAPP_KEYCODE_SPACE; break; + default: break; + } + if (k != SAPP_KEYCODE_INVALID) { + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + } + } + } + } + else { + /* this was a backspace */ + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + } + } + return NO; +} +@end + +@implementation _sapp_ios_view +- (void)drawRect:(CGRect)rect { + _SOKOL_UNUSED(rect); + _sapp_ios_frame(); +} +- (BOOL)isOpaque { + return YES; +} +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); +} +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); +} +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); +} +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); +} +@end +#endif /* TARGET_OS_IPHONE */ + +#endif /* _SAPP_APPLE */ + +/*== EMSCRIPTEN ==============================================================*/ +#if defined(_SAPP_EMSCRIPTEN) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); + +/* this function is called from a JS event handler when the user hides + the onscreen keyboard pressing the 'dismiss keyboard key' +*/ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { + _sapp.onscreen_keyboard_shown = false; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { + if (_sapp.clipboard.enabled) { + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ +EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { + return _sapp.html5_ask_leave_site ? 1 : 0; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { + if (!_sapp.drop.enabled) { + return; + } + if (num < 0) { + num = 0; + } + if (num > _sapp.drop.max_files) { + num = _sapp.drop.max_files; + } + _sapp.drop.num_files = num; + _sapp_clear_drop_buffer(); +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { + /* NOTE: name is only the filename part, not a path */ + if (!_sapp.drop.enabled) { + return; + } + if (0 == name) { + return; + } + SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); + if ((i < 0) || (i >= _sapp.drop.num_files)) { + return; + } + if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + SOKOL_LOG("sokol_app.h: dropped file path too long!\n"); + _sapp.drop.num_files = 0; + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { + if (!_sapp.drop.enabled) { + return; + } + if (0 == _sapp.drop.num_files) { + /* there was an error copying the filenames */ + _sapp_clear_drop_buffer(); + return; + + } + if (_sapp_events_enabled()) { + _sapp.mouse.x = (float)x * _sapp.dpi_scale; + _sapp.mouse.y = (float)y * _sapp.dpi_scale; + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { + sapp_html5_fetch_response response; + memset(&response, 0, sizeof(response)); + response.succeeded = (0 != success); + response.error_code = (sapp_html5_fetch_error) error_code; + response.file_index = index; + response.fetched_size = fetched_size; + response.buffer_ptr = buf_ptr; + response.buffer_size = buf_size; + response.user_data = user_data; + callback(&response); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* Javascript helper functions for mobile virtual keyboard input */ +EM_JS(void, sapp_js_create_textfield, (void), { + var _sapp_inp = document.createElement("input"); + _sapp_inp.type = "text"; + _sapp_inp.id = "_sokol_app_input_element"; + _sapp_inp.autocapitalize = "none"; + _sapp_inp.addEventListener("focusout", function(_sapp_event) { + __sapp_emsc_notify_keyboard_hidden() + + }); + document.body.append(_sapp_inp); +}); + +EM_JS(void, sapp_js_focus_textfield, (void), { + document.getElementById("_sokol_app_input_element").focus(); +}); + +EM_JS(void, sapp_js_unfocus_textfield, (void), { + document.getElementById("_sokol_app_input_element").blur(); +}); + +EM_JS(void, sapp_js_add_beforeunload_listener, (void), { + Module.sokol_beforeunload = function(event) { + if (__sapp_html5_get_ask_leave_site() != 0) { + event.preventDefault(); + event.returnValue = ' '; + } + }; + window.addEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { + window.removeEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_add_clipboard_listener, (void), { + Module.sokol_paste = function(event) { + var pasted_str = event.clipboardData.getData('text'); + ccall('_sapp_emsc_onpaste', 'void', ['string'], [pasted_str]); + }; + window.addEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_remove_clipboard_listener, (void), { + window.removeEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { + var str = UTF8ToString(c_str); + var ta = document.createElement('textarea'); + ta.setAttribute('autocomplete', 'off'); + ta.setAttribute('autocorrect', 'off'); + ta.setAttribute('autocapitalize', 'off'); + ta.setAttribute('spellcheck', 'false'); + ta.style.left = -100 + 'px'; + ta.style.top = -100 + 'px'; + ta.style.height = 1; + ta.style.width = 1; + ta.value = str; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); +}); + +_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { + sapp_js_write_clipboard(str); +} + +EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { + Module.sokol_drop_files = []; + var canvas_name = UTF8ToString(canvas_name_cstr); + var canvas = document.getElementById(canvas_name); + Module.sokol_dragenter = function(event) { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragleave = function(event) { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragover = function(event) { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_drop = function(event) { + event.stopPropagation(); + event.preventDefault(); + var files = event.dataTransfer.files; + Module.sokol_dropped_files = files; + __sapp_emsc_begin_drop(files.length); + var i; + for (i = 0; i < files.length; i++) { + ccall('_sapp_emsc_drop', 'void', ['number', 'string'], [i, files[i].name]); + } + // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect + __sapp_emsc_end_drop(event.clientX, event.clientY); + }; + canvas.addEventListener('dragenter', Module.sokol_dragenter, false); + canvas.addEventListener('dragleave', Module.sokol_dragleave, false); + canvas.addEventListener('dragover', Module.sokol_dragover, false); + canvas.addEventListener('drop', Module.sokol_drop, false); +}); + +EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { + if ((index < 0) || (index >= Module.sokol_dropped_files.length)) { + return 0; + } + else { + return Module.sokol_dropped_files[index].size; + } +}); + +EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { + var reader = new FileReader(); + reader.onload = function(loadEvent) { + var content = loadEvent.target.result; + if (content.byteLength > buf_size) { + // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL + __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); + } + else { + HEAPU8.set(new Uint8Array(content), buf_ptr); + __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); + } + }; + reader.onerror = function() { + // SAPP_HTML5_FETCH_ERROR_OTHER + __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); + }; + reader.readAsArrayBuffer(Module.sokol_dropped_files[index]); +}); + +EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { + var canvas_name = UTF8ToString(canvas_name_cstr); + var canvas = document.getElementById(canvas_name); + canvas.removeEventListener('dragenter', Module.sokol_dragenter); + canvas.removeEventListener('dragleave', Module.sokol_dragleave); + canvas.removeEventListener('dragover', Module.sokol_dragover); + canvas.removeEventListener('drop', Module.sokol_drop); +}); + +/* called from the emscripten event handler to update the keyboard visibility + state, this must happen from an JS input event handler, otherwise + the request will be ignored by the browser +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { + if (_sapp.emsc.wants_show_keyboard) { + /* create input text field on demand */ + if (!_sapp.emsc.textfield_created) { + _sapp.emsc.textfield_created = true; + sapp_js_create_textfield(); + } + /* focus the text input field, this will bring up the keyboard */ + _sapp.onscreen_keyboard_shown = true; + _sapp.emsc.wants_show_keyboard = false; + sapp_js_focus_textfield(); + } + if (_sapp.emsc.wants_hide_keyboard) { + /* unfocus the text input field */ + if (_sapp.emsc.textfield_created) { + _sapp.onscreen_keyboard_shown = false; + _sapp.emsc.wants_hide_keyboard = false; + sapp_js_unfocus_textfield(); + } + } +} + +/* actually showing the onscreen keyboard must be initiated from a JS + input event handler, so we'll just keep track of the desired + state, and the actual state change will happen with the next input event +*/ +_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { + if (show) { + _sapp.emsc.wants_show_keyboard = true; + } + else { + _sapp.emsc.wants_hide_keyboard = true; + } +} + +EM_JS(void, sapp_js_pointer_init, (const char* c_str_target), { + // lookup and store canvas object by name + var target_str = UTF8ToString(c_str_target); + Module.sapp_emsc_target = document.getElementById(target_str); + if (!Module.sapp_emsc_target) { + console.log("sokol_app.h: invalid target:" + target_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); + } +}); + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = emsc_event->isActive; + return EM_TRUE; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = false; + _sapp.emsc.mouse_lock_requested = false; + return true; +} + +EM_JS(void, sapp_js_request_pointerlock, (void), { + if (Module.sapp_emsc_target) { + if (Module.sapp_emsc_target.requestPointerLock) { + Module.sapp_emsc_target.requestPointerLock(); + } + } +}); + +EM_JS(void, sapp_js_exit_pointerlock, (void), { + if (document.exitPointerLock) { + document.exitPointerLock(); + } +}); + +_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { + if (lock) { + /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ + _sapp.emsc.mouse_lock_requested = true; + } + else { + /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ + _sapp.emsc.mouse_lock_requested = false; + sapp_js_exit_pointerlock(); + } +} + +/* called from inside event handlers to check if mouse lock had been requested, + and if yes, actually enter mouse lock. +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { + if (_sapp.emsc.mouse_lock_requested) { + _sapp.emsc.mouse_lock_requested = false; + sapp_js_request_pointerlock(); + } +} + +#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 EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { + _SOKOL_UNUSED(event_type); + _SOKOL_UNUSED(user_data); + double w, h; + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + /* The above method might report zero when toggling HTML5 fullscreen, + in that case use the window's inner width reported by the + emscripten event. This works ok when toggling *into* fullscreen + but doesn't properly restore the previous canvas size when switching + back from fullscreen. + + In general, due to the HTML5's fullscreen API's flaky nature it is + recommended to use 'soft fullscreen' (stretching the WebGL canvas + over the browser windows client rect) with a CSS definition like this: + + position: absolute; + top: 0px; + left: 0px; + margin: 0px; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + display: block; + */ + if (w < 1.0) { + w = ui_event->windowInnerWidth; + } + else { + _sapp.window_width = (int) w; + } + if (h < 1.0) { + h = ui_event->windowInnerHeight; + } + else { + _sapp.window_height = (int) h; + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); + SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_WGPU) + /* on WebGPU: recreate size-dependent rendering surfaces */ + _sapp_emsc_wgpu_surfaces_discard(); + _sapp_emsc_wgpu_surfaces_create(); + #endif + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_RESIZED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + if (_sapp.mouse.locked) { + _sapp.mouse.dx = (float) emsc_event->movementX; + _sapp.mouse.dy = (float) emsc_event->movementY; + } + else { + float new_x = emsc_event->targetX * _sapp.dpi_scale; + float new_y = emsc_event->targetY * _sapp.dpi_scale; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } + if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { + sapp_event_type type; + bool is_button_event = false; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_MOUSEDOWN: + type = SAPP_EVENTTYPE_MOUSE_DOWN; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + type = SAPP_EVENTTYPE_MOUSE_UP; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + type = SAPP_EVENTTYPE_MOUSE_MOVE; + break; + case EMSCRIPTEN_EVENT_MOUSEENTER: + type = SAPP_EVENTTYPE_MOUSE_ENTER; + break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + type = SAPP_EVENTTYPE_MOUSE_LEAVE; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + 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; + } + if (is_button_event) { + switch (emsc_event->button) { + case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; + case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; + case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; + default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; + } + } + else { + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + } + _sapp_call_event(&_sapp.event); + } + /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */ + if (is_button_event) { + _sapp_emsc_update_mouse_lock_state(); + } + } + _sapp_emsc_update_keyboard_state(); + return true; +} + +_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); + 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; + } + /* see https://github.com/floooh/sokol/issues/339 */ + float scale; + switch (emsc_event->deltaMode) { + case DOM_DELTA_PIXEL: scale = -0.04f; break; + case DOM_DELTA_LINE: scale = -1.33f; break; + case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess + default: scale = -0.1f; break; // shouldn't happen + } + _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; + _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; + _sapp_call_event(&_sapp.event); + } + _sapp_emsc_update_keyboard_state(); + _sapp_emsc_update_mouse_lock_state(); + return true; +} + +_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; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_KEYDOWN: + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case EMSCRIPTEN_EVENT_KEYUP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + case EMSCRIPTEN_EVENT_KEYPRESS: + type = SAPP_EVENTTYPE_CHAR; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + 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; + } + if (type == SAPP_EVENTTYPE_CHAR) { + _sapp.event.char_code = emsc_event->charCode; + /* workaround to make Cmd+V work on Safari */ + if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { + retval = false; + } + } + else { + _sapp.event.key_code = _sapp_translate_key((int)emsc_event->keyCode); + /* 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 + when the SUPER key is pressed + */ + if ((type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && + (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && + (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) + { + send_keyup_followup = true; + } + /* only forward a certain key ranges to the browser */ + switch (_sapp.event.key_code) { + case SAPP_KEYCODE_WORLD_1: + case SAPP_KEYCODE_WORLD_2: + case SAPP_KEYCODE_ESCAPE: + case SAPP_KEYCODE_ENTER: + case SAPP_KEYCODE_TAB: + case SAPP_KEYCODE_BACKSPACE: + case SAPP_KEYCODE_INSERT: + case SAPP_KEYCODE_DELETE: + case SAPP_KEYCODE_RIGHT: + case SAPP_KEYCODE_LEFT: + case SAPP_KEYCODE_DOWN: + case SAPP_KEYCODE_UP: + case SAPP_KEYCODE_PAGE_UP: + case SAPP_KEYCODE_PAGE_DOWN: + case SAPP_KEYCODE_HOME: + case SAPP_KEYCODE_END: + case SAPP_KEYCODE_CAPS_LOCK: + case SAPP_KEYCODE_SCROLL_LOCK: + case SAPP_KEYCODE_NUM_LOCK: + case SAPP_KEYCODE_PRINT_SCREEN: + case SAPP_KEYCODE_PAUSE: + case SAPP_KEYCODE_F1: + case SAPP_KEYCODE_F2: + case SAPP_KEYCODE_F3: + case SAPP_KEYCODE_F4: + case SAPP_KEYCODE_F5: + case SAPP_KEYCODE_F6: + case SAPP_KEYCODE_F7: + case SAPP_KEYCODE_F8: + case SAPP_KEYCODE_F9: + case SAPP_KEYCODE_F10: + case SAPP_KEYCODE_F11: + case SAPP_KEYCODE_F12: + case SAPP_KEYCODE_F13: + case SAPP_KEYCODE_F14: + case SAPP_KEYCODE_F15: + case SAPP_KEYCODE_F16: + case SAPP_KEYCODE_F17: + case SAPP_KEYCODE_F18: + case SAPP_KEYCODE_F19: + case SAPP_KEYCODE_F20: + case SAPP_KEYCODE_F21: + case SAPP_KEYCODE_F22: + case SAPP_KEYCODE_F23: + case SAPP_KEYCODE_F24: + case SAPP_KEYCODE_F25: + case SAPP_KEYCODE_LEFT_SHIFT: + case SAPP_KEYCODE_LEFT_CONTROL: + case SAPP_KEYCODE_LEFT_ALT: + case SAPP_KEYCODE_LEFT_SUPER: + case SAPP_KEYCODE_RIGHT_SHIFT: + case SAPP_KEYCODE_RIGHT_CONTROL: + case SAPP_KEYCODE_RIGHT_ALT: + case SAPP_KEYCODE_RIGHT_SUPER: + case SAPP_KEYCODE_MENU: + /* consume the event */ + break; + default: + /* forward key to browser */ + retval = false; + break; + } + } + if (_sapp_call_event(&_sapp.event)) { + /* consume event via sapp_consume_event() */ + retval = true; + } + if (send_keyup_followup) { + _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; + if (_sapp_call_event(&_sapp.event)) { + retval = true; + } + } + } + } + _sapp_emsc_update_keyboard_state(); + _sapp_emsc_update_mouse_lock_state(); + return retval; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool retval = true; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + retval = false; + break; + } + 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.num_touches = emsc_event->numTouches; + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int i = 0; i < _sapp.event.num_touches; i++) { + const EmscriptenTouchPoint* src = &emsc_event->touches[i]; + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)src->identifier; + dst->pos_x = src->targetX * _sapp.dpi_scale; + dst->pos_y = src->targetY * _sapp.dpi_scale; + dst->changed = src->isChanged; + } + _sapp_call_event(&_sapp.event); + } + } + _sapp_emsc_update_keyboard_state(); + 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; +} + +#if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; + case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; + default: type = SAPP_EVENTTYPE_INVALID; break; + } + if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + attrs.alpha = _sapp.desc.alpha; + attrs.depth = true; + attrs.stencil = true; + attrs.antialias = _sapp.sample_count > 1; + attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; + attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; + attrs.enableExtensionsByDefault = true; + #if defined(SOKOL_GLES3) + if (_sapp.desc.gl_force_gles2) { + attrs.majorVersion = 1; + _sapp.gles2_fallback = true; + } + else { + attrs.majorVersion = 2; + } + #endif + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + if (!ctx) { + attrs.majorVersion = 1; + ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + _sapp.gles2_fallback = true; + } + emscripten_webgl_make_context_current(ctx); + + /* some WebGL extension are not enabled automatically by emscripten */ + emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); +} +#endif + +#if defined(SOKOL_WGPU) +#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) +#define _SAPP_EMSC_WGPU_STATE_READY (1) +#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) + +#if defined(__cplusplus) +extern "C" { +#endif +/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); + _sapp.emsc.wgpu.device = (WGPUDevice) device_id; + _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; + _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; + _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; +} +#if defined(__cplusplus) +} // extern "C" +#endif + +/* embedded JS function to handle all the asynchronous WebGPU setup */ +EM_JS(void, sapp_js_wgpu_init, (), { + WebGPU.initManagers(); + // FIXME: the extension activation must be more clever here + navigator.gpu.requestAdapter().then(function(adapter) { + console.log("wgpu adapter extensions: " + adapter.extensions); + adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then(function(device) { + var gpuContext = document.getElementById("canvas").getContext("gpupresent"); + console.log("wgpu device extensions: " + adapter.extensions); + gpuContext.getSwapChainPreferredFormat(device).then(function(fmt) { + var swapChainDescriptor = { device: device, format: fmt }; + var swapChain = gpuContext.configureSwapChain(swapChainDescriptor); + var deviceId = WebGPU.mgrDevice.create(device); + var swapChainId = WebGPU.mgrSwapChain.create(swapChain); + var fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); + console.log("wgpu device: " + device); + console.log("wgpu swap chain: " + swapChain); + console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); + __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); + }); + }); + }); +}); + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { + SOKOL_ASSERT(_sapp.emsc.wgpu.device); + SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); + + WGPUTextureDescriptor ds_desc; + memset(&ds_desc, 0, sizeof(ds_desc)); + ds_desc.usage = WGPUTextureUsage_OutputAttachment; + ds_desc.dimension = WGPUTextureDimension_2D; + ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; + ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; + ds_desc.size.depth = 1; + ds_desc.arrayLayerCount = 1; + ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; + ds_desc.mipLevelCount = 1; + ds_desc.sampleCount = _sapp.sample_count; + _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); + _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); + + if (_sapp.sample_count > 1) { + WGPUTextureDescriptor msaa_desc; + memset(&msaa_desc, 0, sizeof(msaa_desc)); + msaa_desc.usage = WGPUTextureUsage_OutputAttachment; + msaa_desc.dimension = WGPUTextureDimension_2D; + msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; + msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; + msaa_desc.size.depth = 1; + msaa_desc.arrayLayerCount = 1; + msaa_desc.format = _sapp.emsc.wgpu.render_format; + msaa_desc.mipLevelCount = 1; + msaa_desc.sampleCount = _sapp.sample_count; + _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); + _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { + if (_sapp.emsc.wgpu.msaa_tex) { + wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); + _sapp.emsc.wgpu.msaa_tex = 0; + } + if (_sapp.emsc.wgpu.msaa_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); + _sapp.emsc.wgpu.msaa_view = 0; + } + if (_sapp.emsc.wgpu.depth_stencil_tex) { + wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); + _sapp.emsc.wgpu.depth_stencil_tex = 0; + } + if (_sapp.emsc.wgpu.depth_stencil_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); + _sapp.emsc.wgpu.depth_stencil_view = 0; + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { + if (_sapp.emsc.wgpu.swapchain_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); + } + _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); +} +#endif + +_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + 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); + sapp_js_add_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_add_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); + } + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + #endif +} + +_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); + 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); + sapp_js_remove_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_remove_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); + } + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); + #endif +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { + _SOKOL_UNUSED(time); + _SOKOL_UNUSED(userData); + + #if defined(SOKOL_WGPU) + /* + on WebGPU, the emscripten frame callback will already be called while + the asynchronous WebGPU device and swapchain initialization is still + in progress + */ + switch (_sapp.emsc.wgpu.state) { + case _SAPP_EMSC_WGPU_STATE_INITIAL: + /* async JS init hasn't finished yet */ + break; + case _SAPP_EMSC_WGPU_STATE_READY: + /* perform post-async init stuff */ + _sapp_emsc_wgpu_surfaces_create(); + _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; + break; + case _SAPP_EMSC_WGPU_STATE_RUNNING: + /* a regular frame */ + _sapp_emsc_wgpu_next_frame(); + _sapp_frame(); + break; + } + #else + /* WebGL code path */ + _sapp_frame(); + #endif + + /* quit-handling */ + if (_sapp.quit_requested) { + _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + _sapp_call_event(&_sapp.event); + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + _sapp_emsc_unregister_eventhandlers(); + _sapp_call_cleanup(); + _sapp_discard_state(); + return EM_FALSE; + } + return EM_TRUE; +} + +_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; + h = (double) _sapp.desc.height; + } + else { + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.window_width = (int) w; + _sapp.window_height = (int) h; + _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + _sapp_emsc_webgl_init(); + #elif defined(SOKOL_WGPU) + sapp_js_wgpu_init(); + #endif + _sapp.valid = true; + _sapp_emsc_register_eventhandlers(); + + /* start the frame loop */ + emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0); + + /* NOT A BUG: do not call _sapp_discard_state() here, instead this is + called in _sapp_emsc_frame() when the application is ordered to quit + */ +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_emsc_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_EMSCRIPTEN */ + +/*== MISC GL SUPPORT FUNCTIONS ================================================*/ +#if defined(SOKOL_GLCORE33) +typedef struct { + int red_bits; + int green_bits; + int blue_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + int samples; + bool doublebuffer; + uintptr_t handle; +} _sapp_gl_fbconfig; + +_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { + memset(fbconfig, 0, sizeof(_sapp_gl_fbconfig)); + /* -1 means "don't care" */ + fbconfig->red_bits = -1; + fbconfig->green_bits = -1; + fbconfig->blue_bits = -1; + fbconfig->alpha_bits = -1; + fbconfig->depth_bits = -1; + fbconfig->stencil_bits = -1; + fbconfig->samples = -1; +} + +_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { + int missing, least_missing = 1000000; + int color_diff, least_color_diff = 10000000; + int extra_diff, least_extra_diff = 10000000; + const _sapp_gl_fbconfig* current; + const _sapp_gl_fbconfig* closest = 0; + for (int i = 0; i < count; i++) { + current = alternatives + i; + if (desired->doublebuffer != current->doublebuffer) { + continue; + } + missing = 0; + if (desired->alpha_bits > 0 && current->alpha_bits == 0) { + missing++; + } + if (desired->depth_bits > 0 && current->depth_bits == 0) { + missing++; + } + if (desired->stencil_bits > 0 && current->stencil_bits == 0) { + missing++; + } + if (desired->samples > 0 && current->samples == 0) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + if (missing < least_missing) { + closest = current; + } + else if (missing == least_missing) { + if ((color_diff < least_color_diff) || + (color_diff == least_color_diff && extra_diff < least_extra_diff)) + { + closest = current; + } + } + if (current == closest) { + least_missing = missing; + least_color_diff = color_diff; + least_extra_diff = extra_diff; + } + } + return closest; +} +#endif + +/*== WINDOWS DESKTOP and UWP====================================================*/ +#if defined(_SAPP_WIN32) || defined(_SAPP_UWP) +_SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + memset(dst, 0, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } + else { + /* input string doesn't fit into destination buffer */ + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_uwp_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { + /* same as GLFW */ + _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; + _sapp.keycodes[0x002] = SAPP_KEYCODE_1; + _sapp.keycodes[0x003] = SAPP_KEYCODE_2; + _sapp.keycodes[0x004] = SAPP_KEYCODE_3; + _sapp.keycodes[0x005] = SAPP_KEYCODE_4; + _sapp.keycodes[0x006] = SAPP_KEYCODE_5; + _sapp.keycodes[0x007] = SAPP_KEYCODE_6; + _sapp.keycodes[0x008] = SAPP_KEYCODE_7; + _sapp.keycodes[0x009] = SAPP_KEYCODE_8; + _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; + _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; + _sapp.keycodes[0x030] = SAPP_KEYCODE_B; + _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; + _sapp.keycodes[0x020] = SAPP_KEYCODE_D; + _sapp.keycodes[0x012] = SAPP_KEYCODE_E; + _sapp.keycodes[0x021] = SAPP_KEYCODE_F; + _sapp.keycodes[0x022] = SAPP_KEYCODE_G; + _sapp.keycodes[0x023] = SAPP_KEYCODE_H; + _sapp.keycodes[0x017] = SAPP_KEYCODE_I; + _sapp.keycodes[0x024] = SAPP_KEYCODE_J; + _sapp.keycodes[0x025] = SAPP_KEYCODE_K; + _sapp.keycodes[0x026] = SAPP_KEYCODE_L; + _sapp.keycodes[0x032] = SAPP_KEYCODE_M; + _sapp.keycodes[0x031] = SAPP_KEYCODE_N; + _sapp.keycodes[0x018] = SAPP_KEYCODE_O; + _sapp.keycodes[0x019] = SAPP_KEYCODE_P; + _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x013] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; + _sapp.keycodes[0x014] = SAPP_KEYCODE_T; + _sapp.keycodes[0x016] = SAPP_KEYCODE_U; + _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; + _sapp.keycodes[0x011] = SAPP_KEYCODE_W; + _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; + _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; + _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; + _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; + _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; + _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; + _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; + _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; + _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; + _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; +} +#endif // _SAPP_WIN32 || _SAPP_UWP + +/*== WINDOWS DESKTOP===========================================================*/ +#if defined(_SAPP_WIN32) + +#if defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sapp_d3d11_Release(self) (self)->Release() +#else +#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#endif + +#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } + +static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { + #if defined(__cplusplus) + return self->GetBuffer(Buffer, riid, ppSurface); + #else + return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { + #if defined(__cplusplus) + self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #else + self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #endif +} + +static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { + #if defined(__cplusplus) + return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); + #else + return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); + #endif +} + +static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { + #if defined(__cplusplus) + return self->Present(SyncInterval, Flags); + #else + return self->lpVtbl->Present(self, SyncInterval, Flags); + #endif +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { + DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; + sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; + sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; + sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + sc_desc->BufferDesc.RefreshRate.Numerator = 60; + sc_desc->BufferDesc.RefreshRate.Denominator = 1; + sc_desc->OutputWindow = _sapp.win32.hwnd; + sc_desc->Windowed = true; + if (_sapp.win32.is_win10_or_greater) { + sc_desc->BufferCount = 2; + sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; + } + else { + sc_desc->BufferCount = 1; + sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + } + sc_desc->SampleDesc.Count = 1; + sc_desc->SampleDesc.Quality = 0; + sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; + #if defined(SOKOL_DEBUG) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + #endif + D3D_FEATURE_LEVEL feature_level; + HRESULT hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + _SOKOL_UNUSED(hr); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device); +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { + SOKOL_ASSERT(0 == _sapp.d3d11.rt); + SOKOL_ASSERT(0 == _sapp.d3d11.rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.ds); + SOKOL_ASSERT(0 == _sapp.d3d11.dsv); + + HRESULT hr; + + /* view for the swapchain-created framebuffer */ + #ifdef __cplusplus + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); + #else + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &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); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); + + /* common desc for MSAA and depth-stencil texture */ + D3D11_TEXTURE2D_DESC tex_desc; + memset(&tex_desc, 0, sizeof(tex_desc)); + tex_desc.Width = (UINT)_sapp.framebuffer_width; + tex_desc.Height = (UINT)_sapp.framebuffer_height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Usage = D3D11_USAGE_DEFAULT; + tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; + tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + + /* create MSAA texture and view if antialiasing requested */ + if (_sapp.sample_count > 1) { + tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); + } + + /* texture and view for the depth-stencil-surface */ + tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); + hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { + if (_sapp.d3d11.swap_chain) { + _sapp_d3d11_destroy_default_render_target(); + _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); + _sapp_d3d11_create_default_render_target(); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_present(void) { + /* do MSAA resolve if needed */ + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.rt); + SOKOL_ASSERT(_sapp.d3d11.msaa_rt); + _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); + } + _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, 0); +} + +#endif /* SOKOL_D3D11 */ + +#if defined(SOKOL_GLCORE33) +_SOKOL_PRIVATE void _sapp_wgl_init(void) { + _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); + if (!_sapp.wgl.opengl32) { + _sapp_fail("Failed to load opengl32.dll\n"); + } + SOKOL_ASSERT(_sapp.wgl.opengl32); + _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); + SOKOL_ASSERT(_sapp.wgl.CreateContext); + _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); + SOKOL_ASSERT(_sapp.wgl.DeleteContext); + _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); + SOKOL_ASSERT(_sapp.wgl.GetProcAddress); + _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); + SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); + _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); + SOKOL_ASSERT(_sapp.wgl.MakeCurrent); + + _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, + L"SOKOLAPP", + L"sokol-app message window", + WS_CLIPSIBLINGS|WS_CLIPCHILDREN, + 0, 0, 1, 1, + NULL, NULL, + GetModuleHandleW(NULL), + NULL); + if (!_sapp.wgl.msg_hwnd) { + _sapp_fail("Win32: failed to create helper window!\n"); + } + SOKOL_ASSERT(_sapp.wgl.msg_hwnd); + ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); + MSG msg; + while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); + if (!_sapp.wgl.msg_dc) { + _sapp_fail("Win32: failed to obtain helper window DC!\n"); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { + SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); + DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; + FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; +} + +_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext && extensions); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { + SOKOL_ASSERT(ext); + if (_sapp.wgl.GetExtensionsStringEXT) { + const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + if (_sapp.wgl.GetExtensionsStringARB) { + const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + return false; +} + +_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { + SOKOL_ASSERT(_sapp.wgl.msg_dc); + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { + _sapp_fail("WGL: failed to set pixel format for dummy context\n"); + } + HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); + if (!rc) { + _sapp_fail("WGL: Failed to create dummy context\n"); + } + if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { + _sapp_fail("WGL: Failed to make context current\n"); + } + _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); + _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); + _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); + _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); + _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); + _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); + _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); + _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); + _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); + _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); + _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); + _sapp.wgl.DeleteContext(rc); +} + +_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + int value = 0; + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { + _sapp_fail("WGL: Failed to retrieve pixel format attribute\n"); + } + return value; +} + +_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { + SOKOL_ASSERT(_sapp.win32.dc); + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + const _sapp_gl_fbconfig* closest; + + int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); + _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); + SOKOL_ASSERT(usable_configs); + int usable_count = 0; + for (int i = 0; i < native_count; i++) { + const int n = i + 1; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { + continue; + } + if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { + continue; + } + if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { + continue; + } + u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); + u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); + u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); + u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); + u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); + u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); + if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { + u->doublebuffer = true; + } + if (_sapp.wgl.arb_multisample) { + u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); + } + u->handle = (uintptr_t)n; + usable_count++; + } + SOKOL_ASSERT(usable_count > 0); + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + int pixel_format = 0; + if (closest) { + pixel_format = (int) closest->handle; + } + SOKOL_FREE(usable_configs); + return pixel_format; +} + +_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { + int pixel_format = _sapp_wgl_find_pixel_format(); + if (0 == pixel_format) { + _sapp_fail("WGL: Didn't find matching pixel format.\n"); + } + PIXELFORMATDESCRIPTOR pfd; + if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { + _sapp_fail("WGL: Failed to retrieve PFD for selected pixel format!\n"); + } + if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { + _sapp_fail("WGL: Failed to set selected pixel format!\n"); + } + if (!_sapp.wgl.arb_create_context) { + _sapp_fail("WGL: ARB_create_context required!\n"); + } + if (!_sapp.wgl.arb_create_context_profile) { + _sapp_fail("WGL: ARB_create_context_profile required!\n"); + } + const int attrs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, 0 + }; + _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); + if (!_sapp.wgl.gl_ctx) { + const DWORD err = GetLastError(); + if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { + _sapp_fail("WGL: Driver does not support OpenGL version 3.3\n"); + } + else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { + _sapp_fail("WGL: Driver does not support the requested OpenGL profile"); + } + else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { + _sapp_fail("WGL: The share context is not compatible with the requested context"); + } + else { + _sapp_fail("WGL: Failed to create OpenGL context"); + } + } + _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); + if (_sapp.wgl.ext_swap_control) { + /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ + _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { + SOKOL_ASSERT(_sapp.wgl.gl_ctx); + _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); + _sapp.wgl.gl_ctx = 0; +} + +_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { + SOKOL_ASSERT(_sapp.win32.dc); + /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ + SwapBuffers(_sapp.win32.dc); +} +#endif /* SOKOL_GLCORE33 */ + +_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + memset(dst, 0, (size_t)dst_num_bytes); + const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); + if (bytes_needed <= dst_num_bytes) { + WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { + HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo; + memset(&minfo, 0, sizeof(minfo)); + minfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &minfo); + const RECT mr = minfo.rcMonitor; + const int monitor_w = mr.right - mr.left; + const int monitor_h = mr.bottom - mr.top; + + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD win_style; + RECT rect = { 0, 0, 0, 0 }; + + _sapp.fullscreen = !_sapp.fullscreen; + if (!_sapp.fullscreen) { + win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.desc.width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.desc.height * _sapp.win32.dpi.window_scale); + } + else { + win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; + rect.right = monitor_w; + rect.bottom = monitor_h; + } + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + int win_width = rect.right - rect.left; + int win_height = rect.bottom - rect.top; + if (!_sapp.fullscreen) { + rect.left = (monitor_w - win_width) / 2; + rect.top = (monitor_h - win_height) / 2; + } + + SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool visible) { + /* NOTE: this function is only called when the mouse visibility actually changes */ + ShowCursor((BOOL)visible); +} + +_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { + if (0 == _sapp.win32.mouse_capture_mask) { + SetCapture(_sapp.win32.hwnd); + } + _sapp.win32.mouse_capture_mask |= btn_mask; +} + +_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { + if (0 != _sapp.win32.mouse_capture_mask) { + _sapp.win32.mouse_capture_mask &= ~btn_mask; + if (0 == _sapp.win32.mouse_capture_mask) { + ReleaseCapture(); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + _sapp_win32_release_mouse(0xFF); + if (_sapp.mouse.locked) { + /* store the current mouse position, so it can be restored when unlocked */ + POINT pos; + BOOL res = GetCursorPos(&pos); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + _sapp.win32.mouse_locked_x = pos.x; + _sapp.win32.mouse_locked_y = pos.y; + + /* while the mouse is locked, make the mouse cursor invisible and + confine the mouse movement to a small rectangle inside our window + (so that we dont miss any mouse up events) + */ + RECT client_rect = { + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y, + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y + }; + ClipCursor(&client_rect); + + /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ + ShowCursor(FALSE); + + /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ + const RAWINPUTDEVICE rid = { + 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC + 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE + 0, // dwFlags + _sapp.win32.hwnd // hwndTarget + }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + SOKOL_LOG("RegisterRawInputDevices() failed (on mouse lock).\n"); + } + /* in case the raw mouse device only supports absolute position reporting, + we need to skip the dx/dy compution for the first WM_INPUT event + */ + _sapp.win32.raw_input_mousepos_valid = false; + } + else { + /* disable raw input for mouse */ + const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + SOKOL_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n"); + } + + /* let the mouse roam freely again */ + ClipCursor(NULL); + ShowCursor(TRUE); + + /* restore the 'pre-locked' mouse position */ + BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + } +} + +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { + RECT rect; + 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); + 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; + } + } + else { + _sapp.window_width = _sapp.window_height = 1; + _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + } + return false; +} + +_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { + uint32_t mods = 0; + if (GetKeyState(VK_SHIFT) & (1<<15)) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (GetKeyState(VK_CONTROL) & (1<<15)) { + mods |= SAPP_MODIFIER_CTRL; + } + if (GetKeyState(VK_MENU) & (1<<15)) { + mods |= SAPP_MODIFIER_ALT; + } + if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { + mods |= SAPP_MODIFIER_SUPER; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.mouse_button = btn; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.scroll_x = -x / 30.0f; + _sapp.event.scroll_y = y / 30.0f; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { + if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.key_code = _sapp.keycodes[vk]; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { + if (_sapp_events_enabled() && (c >= 32)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.char_code = c; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { + if (!_sapp.drop.enabled) { + return; + } + _sapp_clear_drop_buffer(); + bool drop_failed = false; + const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); + _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; + for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { + const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; + WCHAR* buffer = (WCHAR*) SOKOL_CALLOC(num_chars, sizeof(WCHAR)); + DragQueryFileW(hdrop, i, buffer, num_chars); + if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { + SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); + drop_failed = true; + } + SOKOL_FREE(buffer); + } + DragFinish(hdrop); + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } +} + +_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (!_sapp.win32.in_create_window) { + switch (uMsg) { + case WM_CLOSE: + /* only give user a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a change to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + PostQuitMessage(0); + } + return 0; + case WM_SYSCOMMAND: + switch (wParam & 0xFFF0) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + if (_sapp.fullscreen) { + /* disable screen saver and blanking in fullscreen mode */ + return 0; + } + break; + case SC_KEYMENU: + /* user trying to access menu via ALT */ + return 0; + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + { + const bool iconified = wParam == SIZE_MINIMIZED; + if (iconified != _sapp.win32.iconified) { + _sapp.win32.iconified = iconified; + if (iconified) { + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else { + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + break; + case WM_SETCURSOR: + if (_sapp.desc.user_cursor) { + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); + return 1; + } + } + break; + case WM_LBUTTONDOWN: + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); + _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + /* mouse only reports absolute position + NOTE: THIS IS UNTESTED, it's unclear from reading the + Win32 RawInput docs under which circumstances absolute + positions are sent. + */ + if (_sapp.win32.raw_input_mousepos_valid) { + LONG new_x = raw_mouse_data->data.mouse.lLastX; + LONG new_y = raw_mouse_data->data.mouse.lLastY; + _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); + _sapp.win32.raw_input_mousepos_x = new_x; + _sapp.win32.raw_input_mousepos_y = new_y; + _sapp.win32.raw_input_mousepos_valid = true; + } + } + else { + /* mouse reports movement delta (this seems to be the common case) */ + _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; + _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; + } + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + + case WM_MOUSELEAVE: + if (!_sapp.mouse.locked) { + _sapp.win32.mouse_tracked = false; + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + case WM_MOUSEWHEEL: + _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); + break; + case WM_MOUSEHWHEEL: + _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); + break; + case WM_CHAR: + _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); + break; + case WM_KEYUP: + case WM_SYSKEYUP: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); + break; + case WM_ENTERSIZEMOVE: + SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); + break; + case WM_EXITSIZEMOVE: + KillTimer(_sapp.win32.hwnd, 1); + break; + case WM_TIMER: + _sapp_frame(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_present(); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_swap_buffers(); + #endif + /* NOTE: resizing the swap-chain during resize leads to a substantial + memory spike (hundreds of megabytes for a few seconds). + + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); + } + */ + break; + case WM_DROPFILES: + _sapp_win32_files_dropped((HDROP)wParam); + break; + default: + break; + } + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +_SOKOL_PRIVATE void _sapp_win32_create_window(void) { + WNDCLASSW wndclassw; + memset(&wndclassw, 0, sizeof(wndclassw)); + wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; + wndclassw.hInstance = GetModuleHandleW(NULL); + wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndclassw.lpszClassName = L"SOKOLAPP"; + RegisterClassW(&wndclassw); + + DWORD win_style; + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + RECT rect = { 0, 0, 0, 0 }; + if (_sapp.fullscreen) { + win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; + rect.right = GetSystemMetrics(SM_CXSCREEN); + rect.bottom = GetSystemMetrics(SM_CYSCREEN); + } + else { + win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); + } + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + const int win_width = rect.right - rect.left; + const int win_height = rect.bottom - rect.top; + _sapp.win32.in_create_window = true; + _sapp.win32.hwnd = CreateWindowExW( + win_ex_style, /* dwExStyle */ + L"SOKOLAPP", /* lpClassName */ + _sapp.window_title_wide, /* lpWindowName */ + win_style, /* dwStyle */ + CW_USEDEFAULT, /* X */ + CW_USEDEFAULT, /* Y */ + win_width, /* nWidth */ + win_height, /* nHeight */ + NULL, /* hWndParent */ + NULL, /* hMenu */ + GetModuleHandle(NULL), /* hInstance */ + NULL); /* lParam */ + ShowWindow(_sapp.win32.hwnd, SW_SHOW); + _sapp.win32.in_create_window = false; + _sapp.win32.dc = GetDC(_sapp.win32.hwnd); + SOKOL_ASSERT(_sapp.win32.dc); + _sapp_win32_update_dimensions(); + + DragAcceptFiles(_sapp.win32.hwnd, 1); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { + DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; + UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); +} + +_SOKOL_PRIVATE void _sapp_win32_init_console(void) { + if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { + BOOL con_valid = FALSE; + if (_sapp.desc.win32_console_create) { + con_valid = AllocConsole(); + } + else if (_sapp.desc.win32_console_attach) { + con_valid = AttachConsole(ATTACH_PARENT_PROCESS); + } + if (con_valid) { + freopen("CON", "w", stdout); + freopen("CON", "w", stderr); + } + } + if (_sapp.desc.win32_console_utf8) { + _sapp.win32.orig_codepage = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + } +} + +_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { + if (_sapp.desc.win32_console_utf8) { + SetConsoleOutputCP(_sapp.win32.orig_codepage); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { + + typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); + typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); + typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; + SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; + GETDPIFORMONITOR_T fn_getdpiformonitor = 0; + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (user32) { + fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); + } + HINSTANCE shcore = LoadLibraryA("shcore.dll"); + if (shcore) { + fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); + fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); + } + if (fn_setprocessdpiawareness) { + /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ + PROCESS_DPI_AWARENESS process_dpi_awareness = PROCESS_SYSTEM_DPI_AWARE; + _sapp.win32.dpi.aware = true; + if (!_sapp.desc.high_dpi) { + process_dpi_awareness = PROCESS_DPI_UNAWARE; + _sapp.win32.dpi.aware = false; + } + fn_setprocessdpiawareness(process_dpi_awareness); + } + else if (fn_setprocessdpiaware) { + fn_setprocessdpiaware(); + _sapp.win32.dpi.aware = true; + } + /* get dpi scale factor for main monitor */ + if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { + POINT pt = { 1, 1 }; + HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + UINT dpix, dpiy; + HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); + _SOKOL_UNUSED(hr); + SOKOL_ASSERT(SUCCEEDED(hr)); + /* clamp window scale to an integer factor */ + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + } + else { + _sapp.win32.dpi.window_scale = 1.0f; + } + if (_sapp.desc.high_dpi) { + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.win32.dpi.mouse_scale = 1.0f; + } + else { + _sapp.win32.dpi.content_scale = 1.0f; + _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; + } + _sapp.dpi_scale = _sapp.win32.dpi.content_scale; + if (user32) { + FreeLibrary(user32); + } + if (shcore) { + FreeLibrary(shcore); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { + SOKOL_ASSERT(str); + SOKOL_ASSERT(_sapp.win32.hwnd); + SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + + wchar_t* wchar_buf = 0; + const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); + HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); + if (!object) { + goto error; + } + wchar_buf = (wchar_t*) GlobalLock(object); + if (!wchar_buf) { + goto error; + } + if (!_sapp_win32_uwp_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { + goto error; + } + GlobalUnlock(wchar_buf); + wchar_buf = 0; + if (!OpenClipboard(_sapp.win32.hwnd)) { + goto error; + } + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, object); + CloseClipboard(); + return true; + +error: + if (wchar_buf) { + GlobalUnlock(object); + } + if (object) { + GlobalFree(object); + } + return false; +} + +_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + SOKOL_ASSERT(_sapp.win32.hwnd); + if (!OpenClipboard(_sapp.win32.hwnd)) { + /* silently ignore any errors and just return the current + content of the local clipboard buffer + */ + return _sapp.clipboard.buffer; + } + HANDLE object = GetClipboardData(CF_UNICODETEXT); + if (!object) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); + if (!wchar_buf) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { + SOKOL_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n"); + } + GlobalUnlock(object); + CloseClipboard(); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { + _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); +} + +/* don't laugh, but this seems to be the easiest and most robust + way to check if we're running on Win10 + + From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 +*/ +_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { + HMODULE h = GetModuleHandleW(L"kernel32.dll"); + if (NULL != h) { + return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_win32_init_console(); + _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); + _sapp_win32_uwp_init_keytable(); + _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(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_create_device_and_swapchain(); + _sapp_d3d11_create_default_render_target(); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_init(); + _sapp_wgl_load_extensions(); + _sapp_wgl_create_context(); + #if !defined(SOKOL_WIN32_NO_GL_LOADER) + _sapp_win32_gl_loadfuncs(); + #endif + #endif + _sapp.valid = true; + + bool done = false; + while (!(done || _sapp.quit_ordered)) { + MSG msg; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (WM_QUIT == msg.message) { + done = true; + continue; + } + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + _sapp_frame(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_present(); + if (IsIconic(_sapp.win32.hwnd)) { + Sleep((DWORD)(16 * _sapp.swap_interval)); + } + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_swap_buffers(); + #endif + /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); + } + if (_sapp.quit_requested) { + PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); + } + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #else + _sapp_wgl_destroy_context(); + _sapp_wgl_shutdown(); + #endif + _sapp_win32_destroy_window(); + _sapp_win32_restore_console(); + _sapp_discard_state(); +} + +_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { + int argc = 0; + char** argv = 0; + char* args; + + LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); + if (w_argv == NULL) { + _sapp_fail("Win32: failed to parse command line"); + } else { + size_t size = wcslen(w_command_line) * 4; + argv = (char**) SOKOL_CALLOC(1, ((size_t)argc + 1) * sizeof(char*) + size); + SOKOL_ASSERT(argv); + args = (char*) &argv[argc + 1]; + int n; + for (int i = 0; i < argc; ++i) { + n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); + if (n == 0) { + _sapp_fail("Win32: failed to convert all arguments to utf8"); + break; + } + argv[i] = args; + size -= (size_t)n; + args += n; + } + LocalFree(w_argv); + } + *o_argc = argc; + return argv; +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(SOKOL_WIN32_FORCE_MAIN) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_win32_run(&desc); + return 0; +} +#else +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + _SOKOL_UNUSED(hInstance); + _SOKOL_UNUSED(hPrevInstance); + _SOKOL_UNUSED(lpCmdLine); + _SOKOL_UNUSED(nCmdShow); + int argc_utf8 = 0; + char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); + sapp_desc desc = sokol_main(argc_utf8, argv_utf8); + _sapp_win32_run(&desc); + SOKOL_FREE(argv_utf8); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_MAIN */ +#endif /* SOKOL_NO_ENTRY */ + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif /* _SAPP_WIN32 */ + +/*== UWP ================================================================*/ +#if defined(_SAPP_UWP) + +// Helper functions +_SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) { + _sapp.uwp.dpi.window_scale = monitor_dpi / 96.0f; + if (_sapp.desc.high_dpi) { + _sapp.uwp.dpi.content_scale = _sapp.uwp.dpi.window_scale; + _sapp.uwp.dpi.mouse_scale = 1.0f * _sapp.uwp.dpi.window_scale; + } + else { + _sapp.uwp.dpi.content_scale = 1.0f; + _sapp.uwp.dpi.mouse_scale = 1.0f; + } + _sapp.dpi_scale = _sapp.uwp.dpi.content_scale; +} + +_SOKOL_PRIVATE void _sapp_uwp_show_mouse(bool visible) { + using namespace winrt::Windows::UI::Core; + + /* NOTE: this function is only called when the mouse visibility actually changes */ + CoreWindow::GetForCurrentThread().PointerCursor(visible ? + CoreCursor(CoreCursorType::Arrow, 0) : + CoreCursor(nullptr)); +} + +_SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) { + using namespace winrt::Windows::System; + using namespace winrt::Windows::UI::Core; + + uint32_t mods = 0; + if ((sender_window.GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { + mods |= SAPP_MODIFIER_SHIFT; + } + if ((sender_window.GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { + mods |= SAPP_MODIFIER_CTRL; + } + if ((sender_window.GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { + mods |= SAPP_MODIFIER_ALT; + } + if (((sender_window.GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) || + ((sender_window.GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)) + { + mods |= SAPP_MODIFIER_SUPER; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_uwp_mouse_event(sapp_event_type type, sapp_mousebutton btn, winrt::Windows::UI::Core::CoreWindow const& sender_window) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_uwp_mods(sender_window); + _sapp.event.mouse_button = btn; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_uwp_scroll_event(float delta, bool horizontal, winrt::Windows::UI::Core::CoreWindow const& sender_window) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_uwp_mods(sender_window); + _sapp.event.scroll_x = horizontal ? (-delta / 30.0f) : 0.0f; + _sapp.event.scroll_y = horizontal ? 0.0f : (delta / 30.0f); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_uwp_extract_mouse_button_events(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + + // we need to figure out ourselves what mouse buttons have been pressed and released, + // because UWP doesn't properly send down/up mouse button events when multiple buttons + // are pressed down, so we also need to check the mouse button state in other mouse events + // to track what buttons have been pressed down and released + // + auto properties = args.CurrentPoint().Properties(); + const uint8_t lmb_bit = (1 << SAPP_MOUSEBUTTON_LEFT); + const uint8_t rmb_bit = (1 << SAPP_MOUSEBUTTON_RIGHT); + const uint8_t mmb_bit = (1 << SAPP_MOUSEBUTTON_MIDDLE); + uint8_t new_btns = 0; + if (properties.IsLeftButtonPressed()) { + new_btns |= lmb_bit; + } + if (properties.IsRightButtonPressed()) { + new_btns |= rmb_bit; + } + if (properties.IsMiddleButtonPressed()) { + new_btns |= mmb_bit; + } + const uint8_t old_btns = _sapp.uwp.mouse_buttons; + const uint8_t chg_btns = new_btns ^ old_btns; + + _sapp.uwp.mouse_buttons = new_btns; + + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + sapp_mousebutton btn = SAPP_MOUSEBUTTON_INVALID; + if (chg_btns & lmb_bit) { + btn = SAPP_MOUSEBUTTON_LEFT; + type = (new_btns & lmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; + } + if (chg_btns & rmb_bit) { + btn = SAPP_MOUSEBUTTON_RIGHT; + type = (new_btns & rmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; + } + if (chg_btns & mmb_bit) { + btn = SAPP_MOUSEBUTTON_MIDDLE; + type = (new_btns & mmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_uwp_mouse_event(type, btn, sender); + } +} + +_SOKOL_PRIVATE void _sapp_uwp_key_event(sapp_event_type type, winrt::Windows::UI::Core::CoreWindow const& sender_window, winrt::Windows::UI::Core::KeyEventArgs const& args) { + auto key_status = args.KeyStatus(); + uint32_t ext_scan_code = key_status.ScanCode | (key_status.IsExtendedKey ? 0x100 : 0); + if (_sapp_events_enabled() && (ext_scan_code < SAPP_MAX_KEYCODES)) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_uwp_mods(sender_window); + _sapp.event.key_code = _sapp.keycodes[ext_scan_code]; + _sapp.event.key_repeat = type == SAPP_EVENTTYPE_KEY_UP ? false : key_status.WasKeyDown; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_uwp_char_event(uint32_t c, bool repeat, winrt::Windows::UI::Core::CoreWindow const& sender_window) { + if (_sapp_events_enabled() && (c >= 32)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_uwp_mods(sender_window); + _sapp.event.char_code = c; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_uwp_toggle_fullscreen(void) { + auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + _sapp.fullscreen = appView.IsFullScreenMode(); + if (!_sapp.fullscreen) { + appView.TryEnterFullScreenMode(); + } + else { + appView.ExitFullScreenMode(); + } + _sapp.fullscreen = appView.IsFullScreenMode(); +} + +namespace {/* Empty namespace to ensure internal linkage (same as _SOKOL_PRIVATE) */ + +// Controls all the DirectX device resources. +class DeviceResources { +public: + // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. + interface IDeviceNotify { + virtual void OnDeviceLost() = 0; + virtual void OnDeviceRestored() = 0; + }; + + DeviceResources(); + ~DeviceResources(); + void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); + void SetLogicalSize(winrt::Windows::Foundation::Size logicalSize); + void SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation); + void SetDpi(float dpi); + void ValidateDevice(); + void HandleDeviceLost(); + void RegisterDeviceNotify(IDeviceNotify* deviceNotify); + void Trim(); + void Present(); + +private: + + // Swapchain Rotation Matrices (Z-rotation) + static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation0 = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation90 = { + 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation180 = { + -1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation270 = { + 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + void CreateDeviceResources(); + void CreateWindowSizeDependentResources(); + void UpdateRenderTargetSize(); + DXGI_MODE_ROTATION ComputeDisplayRotation(); + bool SdkLayersAvailable(); + + // Direct3D objects. + winrt::com_ptr m_d3dDevice; + winrt::com_ptr m_d3dContext; + winrt::com_ptr m_swapChain; + + // Direct3D rendering objects. Required for 3D. + winrt::com_ptr m_d3dRenderTarget; + winrt::com_ptr m_d3dRenderTargetView; + winrt::com_ptr m_d3dMSAARenderTarget; + winrt::com_ptr m_d3dMSAARenderTargetView; + winrt::com_ptr m_d3dDepthStencil; + winrt::com_ptr m_d3dDepthStencilView; + D3D11_VIEWPORT m_screenViewport = { }; + + // Cached reference to the Window. + winrt::agile_ref< winrt::Windows::UI::Core::CoreWindow> m_window; + + // Cached device properties. + D3D_FEATURE_LEVEL m_d3dFeatureLevel = D3D_FEATURE_LEVEL_9_1; + winrt::Windows::Foundation::Size m_d3dRenderTargetSize = { }; + winrt::Windows::Foundation::Size m_outputSize = { }; + winrt::Windows::Foundation::Size m_logicalSize = { }; + winrt::Windows::Graphics::Display::DisplayOrientations m_nativeOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; + winrt::Windows::Graphics::Display::DisplayOrientations m_currentOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; + float m_dpi = -1.0f; + + // Transforms used for display orientation. + DirectX::XMFLOAT4X4 m_orientationTransform3D; + + // The IDeviceNotify can be held directly as it owns the DeviceResources. + IDeviceNotify* m_deviceNotify = nullptr; +}; + +// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events. +struct App : winrt::implements { +public: + // IFrameworkViewSource Methods + winrt::Windows::ApplicationModel::Core::IFrameworkView CreateView() { return *this; } + + // IFrameworkView Methods. + virtual void Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView); + virtual void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); + virtual void Load(winrt::hstring const& entryPoint); + virtual void Run(); + virtual void Uninitialize(); + +protected: + // Application lifecycle event handlers + void OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args); + void OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args); + void OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args); + + // Window event handlers + void OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args); + void OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args); + + // Navigation event handlers + void OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args); + + // Input event handlers + void OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); + void OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); + void OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args); + + // Pointer event handlers + void OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + void OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + void OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + void OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + void OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + void OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + + // DisplayInformation event handlers. + void OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); + void OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); + void OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); + +private: + std::unique_ptr m_deviceResources; + bool m_windowVisible = true; +}; + +DeviceResources::DeviceResources() { + CreateDeviceResources(); +} + +DeviceResources::~DeviceResources() { + // Cleanup Sokol Context + _sapp.d3d11.device = nullptr; + _sapp.d3d11.device_context = nullptr; +} + +void DeviceResources::CreateDeviceResources() { + // This flag adds support for surfaces with a different color channel ordering + // than the API default. It is required for compatibility with Direct2D. + UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + #if defined(_DEBUG) + if (SdkLayersAvailable()) { + // If the project is in a debug build, enable debugging via SDK Layers with this flag. + creationFlags |= D3D11_CREATE_DEVICE_DEBUG; + } + #endif + + // This array defines the set of DirectX hardware feature levels this app will support. + // Note the ordering should be preserved. + // Don't forget to declare your application's minimum required feature level in its + // description. All applications are assumed to support 9.1 unless otherwise stated. + D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + // Create the Direct3D 11 API device object and a corresponding context. + winrt::com_ptr device; + winrt::com_ptr context; + + HRESULT hr = D3D11CreateDevice( + nullptr, // Specify nullptr to use the default adapter. + D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver. + 0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. + creationFlags, // Set debug and Direct2D compatibility flags. + featureLevels, // List of feature levels this app can support. + ARRAYSIZE(featureLevels), // Size of the list above. + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. + device.put(), // Returns the Direct3D device created. + &m_d3dFeatureLevel, // Returns feature level of device created. + context.put() // Returns the device immediate context. + ); + + if (FAILED(hr)) { + // If the initialization fails, fall back to the WARP device. + // For more information on WARP, see: + // https://go.microsoft.com/fwlink/?LinkId=286690 + winrt::check_hresult( + D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. + 0, + creationFlags, + featureLevels, + ARRAYSIZE(featureLevels), + D3D11_SDK_VERSION, + device.put(), + &m_d3dFeatureLevel, + context.put() + ) + ); + } + + // Store pointers to the Direct3D 11.3 API device and immediate context. + m_d3dDevice = device.as(); + m_d3dContext = context.as(); + + // Setup Sokol Context + _sapp.d3d11.device = m_d3dDevice.get(); + _sapp.d3d11.device_context = m_d3dContext.get(); +} + +void DeviceResources::CreateWindowSizeDependentResources() { + // Cleanup Sokol Context (these are non-owning raw pointers) + _sapp.d3d11.rt = nullptr; + _sapp.d3d11.rtv = nullptr; + _sapp.d3d11.msaa_rt = nullptr; + _sapp.d3d11.msaa_rtv = nullptr; + _sapp.d3d11.ds = nullptr; + _sapp.d3d11.dsv = nullptr; + + // Clear the previous window size specific context. + ID3D11RenderTargetView* nullViews[] = { nullptr }; + m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr); + // these are smart pointers, setting to nullptr will delete the objects + m_d3dRenderTarget = nullptr; + m_d3dRenderTargetView = nullptr; + m_d3dMSAARenderTarget = nullptr; + m_d3dMSAARenderTargetView = nullptr; + m_d3dDepthStencilView = nullptr; + m_d3dDepthStencil = nullptr; + m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr); + + UpdateRenderTargetSize(); + + // The width and height of the swap chain must be based on the window's + // natively-oriented width and height. If the window is not in the native + // orientation, the dimensions must be reversed. + DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation(); + + bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270; + m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width; + m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height; + + if (m_swapChain != nullptr) { + // If the swap chain already exists, resize it. + HRESULT hr = m_swapChain->ResizeBuffers( + 2, // Double-buffered swap chain. + lround(m_d3dRenderTargetSize.Width), + lround(m_d3dRenderTargetSize.Height), + DXGI_FORMAT_B8G8R8A8_UNORM, + 0 + ); + + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + // If the device was removed for any reason, a new device and swap chain will need to be created. + HandleDeviceLost(); + + // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method + // and correctly set up the new device. + return; + } + else { + winrt::check_hresult(hr); + } + } + else { + // Otherwise, create a new one using the same adapter as the existing Direct3D device. + DXGI_SCALING scaling = (_sapp.uwp.dpi.content_scale == _sapp.uwp.dpi.window_scale) ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH; + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 }; + + swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window. + swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height); + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format. + swapChainDesc.Stereo = false; + swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling. + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency. + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Microsoft Store apps must use this SwapEffect. + swapChainDesc.Flags = 0; + swapChainDesc.Scaling = scaling; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + + // This sequence obtains the DXGI factory that was used to create the Direct3D device above. + winrt::com_ptr dxgiDevice = m_d3dDevice.as(); + winrt::com_ptr dxgiAdapter; + winrt::check_hresult(dxgiDevice->GetAdapter(dxgiAdapter.put())); + winrt::com_ptr dxgiFactory; + winrt::check_hresult(dxgiAdapter->GetParent(__uuidof(IDXGIFactory4), dxgiFactory.put_void())); + winrt::com_ptr swapChain; + winrt::check_hresult(dxgiFactory->CreateSwapChainForCoreWindow(m_d3dDevice.get(), m_window.get().as<::IUnknown>().get(), &swapChainDesc, nullptr, swapChain.put())); + m_swapChain = swapChain.as(); + + // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and + // ensures that the application will only render after each VSync, minimizing power consumption. + winrt::check_hresult(dxgiDevice->SetMaximumFrameLatency(1)); + + // Setup Sokol Context + winrt::check_hresult(swapChain->GetDesc(&_sapp.d3d11.swap_chain_desc)); + _sapp.d3d11.swap_chain = m_swapChain.as().detach(); + } + + // Set the proper orientation for the swap chain, and generate 2D and + // 3D matrix transformations for rendering to the rotated swap chain. + // Note the rotation angle for the 2D and 3D transforms are different. + // This is due to the difference in coordinate spaces. Additionally, + // the 3D matrix is specified explicitly to avoid rounding errors. + switch (displayRotation) { + case DXGI_MODE_ROTATION_IDENTITY: + m_orientationTransform3D = m_rotation0; + break; + + case DXGI_MODE_ROTATION_ROTATE90: + m_orientationTransform3D = m_rotation270; + break; + + case DXGI_MODE_ROTATION_ROTATE180: + m_orientationTransform3D = m_rotation180; + break; + + case DXGI_MODE_ROTATION_ROTATE270: + m_orientationTransform3D = m_rotation90; + break; + } + winrt::check_hresult(m_swapChain->SetRotation(displayRotation)); + + // Create a render target view of the swap chain back buffer. + winrt::check_hresult(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_d3dRenderTarget))); + winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dRenderTarget.get(), nullptr, m_d3dRenderTargetView.put())); + + // Create MSAA texture and view if needed + if (_sapp.sample_count > 1) { + CD3D11_TEXTURE2D_DESC1 msaaTexDesc( + DXGI_FORMAT_B8G8R8A8_UNORM, + lround(m_d3dRenderTargetSize.Width), + lround(m_d3dRenderTargetSize.Height), + 1, // arraySize + 1, // mipLevels + D3D11_BIND_RENDER_TARGET, + D3D11_USAGE_DEFAULT, + 0, // cpuAccessFlags + _sapp.sample_count, + _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 + ); + winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&msaaTexDesc, nullptr, m_d3dMSAARenderTarget.put())); + winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dMSAARenderTarget.get(), nullptr, m_d3dMSAARenderTargetView.put())); + } + + // Create a depth stencil view for use with 3D rendering if needed. + CD3D11_TEXTURE2D_DESC1 depthStencilDesc( + DXGI_FORMAT_D24_UNORM_S8_UINT, + lround(m_d3dRenderTargetSize.Width), + lround(m_d3dRenderTargetSize.Height), + 1, // This depth stencil view has only one texture. + 1, // Use a single mipmap level. + D3D11_BIND_DEPTH_STENCIL, + D3D11_USAGE_DEFAULT, + 0, // cpuAccessFlag + _sapp.sample_count, + _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 + ); + winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&depthStencilDesc, nullptr, m_d3dDepthStencil.put())); + + CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); + winrt::check_hresult(m_d3dDevice->CreateDepthStencilView(m_d3dDepthStencil.get(), nullptr, m_d3dDepthStencilView.put())); + + // Set sokol window and framebuffer sizes + _sapp.window_width = (int) m_logicalSize.Width; + _sapp.window_height = (int) m_logicalSize.Height; + _sapp.framebuffer_width = lround(m_d3dRenderTargetSize.Width); + _sapp.framebuffer_height = lround(m_d3dRenderTargetSize.Height); + + // Setup Sokol Context + _sapp.d3d11.rt = m_d3dRenderTarget.as().get(); + _sapp.d3d11.rtv = m_d3dRenderTargetView.as().get(); + _sapp.d3d11.ds = m_d3dDepthStencil.as().get(); + _sapp.d3d11.dsv = m_d3dDepthStencilView.get(); + if (_sapp.sample_count > 1) { + _sapp.d3d11.msaa_rt = m_d3dMSAARenderTarget.as().get(); + _sapp.d3d11.msaa_rtv = m_d3dMSAARenderTargetView.as().get(); + } + + // Sokol app is now valid + _sapp.valid = true; +} + +// Determine the dimensions of the render target and whether it will be scaled down. +void DeviceResources::UpdateRenderTargetSize() { + // Calculate the necessary render target size in pixels. + m_outputSize.Width = m_logicalSize.Width * _sapp.uwp.dpi.content_scale; + m_outputSize.Height = m_logicalSize.Height * _sapp.uwp.dpi.content_scale; + + // Prevent zero size DirectX content from being created. + m_outputSize.Width = std::max(m_outputSize.Width, 1.0f); + m_outputSize.Height = std::max(m_outputSize.Height, 1.0f); +} + +// This method is called when the CoreWindow is created (or re-created). +void DeviceResources::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { + auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); + m_window = window; + m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); + m_nativeOrientation = currentDisplayInformation.NativeOrientation(); + m_currentOrientation = currentDisplayInformation.CurrentOrientation(); + m_dpi = currentDisplayInformation.LogicalDpi(); + _sapp_uwp_configure_dpi(m_dpi); + CreateWindowSizeDependentResources(); +} + +// This method is called in the event handler for the SizeChanged event. +void DeviceResources::SetLogicalSize(winrt::Windows::Foundation::Size logicalSize) { + if (m_logicalSize != logicalSize) { + m_logicalSize = logicalSize; + CreateWindowSizeDependentResources(); + } +} + +// This method is called in the event handler for the DpiChanged event. +void DeviceResources::SetDpi(float dpi) { + if (dpi != m_dpi) { + m_dpi = dpi; + _sapp_uwp_configure_dpi(m_dpi); + // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated. + auto window = m_window.get(); + m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); + CreateWindowSizeDependentResources(); + } +} + +// This method is called in the event handler for the OrientationChanged event. +void DeviceResources::SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation) { + if (m_currentOrientation != currentOrientation) { + m_currentOrientation = currentOrientation; + CreateWindowSizeDependentResources(); + } +} + +// This method is called in the event handler for the DisplayContentsInvalidated event. +void DeviceResources::ValidateDevice() { + // The D3D Device is no longer valid if the default adapter changed since the device + // was created or if the device has been removed. + + // First, get the information for the default adapter from when the device was created. + winrt::com_ptr dxgiDevice = m_d3dDevice.as< IDXGIDevice3>(); + winrt::com_ptr deviceAdapter; + winrt::check_hresult(dxgiDevice->GetAdapter(deviceAdapter.put())); + winrt::com_ptr deviceFactory; + winrt::check_hresult(deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory))); + winrt::com_ptr previousDefaultAdapter; + winrt::check_hresult(deviceFactory->EnumAdapters1(0, previousDefaultAdapter.put())); + DXGI_ADAPTER_DESC1 previousDesc; + winrt::check_hresult(previousDefaultAdapter->GetDesc1(&previousDesc)); + + // Next, get the information for the current default adapter. + winrt::com_ptr currentFactory; + winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory))); + winrt::com_ptr currentDefaultAdapter; + winrt::check_hresult(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put())); + DXGI_ADAPTER_DESC1 currentDesc; + winrt::check_hresult(currentDefaultAdapter->GetDesc1(¤tDesc)); + + // If the adapter LUIDs don't match, or if the device reports that it has been removed, + // a new D3D device must be created. + if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart || + previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart || + FAILED(m_d3dDevice->GetDeviceRemovedReason())) + { + // Release references to resources related to the old device. + dxgiDevice = nullptr; + deviceAdapter = nullptr; + deviceFactory = nullptr; + previousDefaultAdapter = nullptr; + + // Create a new device and swap chain. + HandleDeviceLost(); + } +} + +// Recreate all device resources and set them back to the current state. +void DeviceResources::HandleDeviceLost() { + m_swapChain = nullptr; + if (m_deviceNotify != nullptr) { + m_deviceNotify->OnDeviceLost(); + } + CreateDeviceResources(); + CreateWindowSizeDependentResources(); + if (m_deviceNotify != nullptr) { + m_deviceNotify->OnDeviceRestored(); + } +} + +// Register our DeviceNotify to be informed on device lost and creation. +void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) { + m_deviceNotify = deviceNotify; +} + +// Call this method when the app suspends. It provides a hint to the driver that the app +// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. +void DeviceResources::Trim() { + m_d3dDevice.as()->Trim(); +} + +// Present the contents of the swap chain to the screen. +void DeviceResources::Present() { + + // MSAA resolve if needed + if (_sapp.sample_count > 1) { + m_d3dContext->ResolveSubresource(m_d3dRenderTarget.get(), 0, m_d3dMSAARenderTarget.get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM); + m_d3dContext->DiscardView1(m_d3dMSAARenderTargetView.get(), nullptr, 0); + } + + // The first argument instructs DXGI to block until VSync, putting the application + // to sleep until the next VSync. This ensures we don't waste any cycles rendering + // frames that will never be displayed to the screen. + DXGI_PRESENT_PARAMETERS parameters = { 0 }; + HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); + + // Discard the contents of the render target. + // This is a valid operation only when the existing contents will be entirely + // overwritten. If dirty or scroll rects are used, this call should be removed. + m_d3dContext->DiscardView1(m_d3dRenderTargetView.get(), nullptr, 0); + + // Discard the contents of the depth stencil. + m_d3dContext->DiscardView1(m_d3dDepthStencilView.get(), nullptr, 0); + + // If the device was removed either by a disconnection or a driver upgrade, we + // must recreate all device resources. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + HandleDeviceLost(); + } + else { + winrt::check_hresult(hr); + } +} + +// This method determines the rotation between the display device's native orientation and the +// current display orientation. +DXGI_MODE_ROTATION DeviceResources::ComputeDisplayRotation() { + DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED; + + // Note: NativeOrientation can only be Landscape or Portrait even though + // the DisplayOrientations enum has other values. + switch (m_nativeOrientation) { + case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: + switch (m_currentOrientation) { + case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: + rotation = DXGI_MODE_ROTATION_IDENTITY; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: + rotation = DXGI_MODE_ROTATION_ROTATE270; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: + rotation = DXGI_MODE_ROTATION_ROTATE180; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: + rotation = DXGI_MODE_ROTATION_ROTATE90; + break; + } + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: + switch (m_currentOrientation) { + case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: + rotation = DXGI_MODE_ROTATION_ROTATE90; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: + rotation = DXGI_MODE_ROTATION_IDENTITY; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: + rotation = DXGI_MODE_ROTATION_ROTATE270; + break; + + case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: + rotation = DXGI_MODE_ROTATION_ROTATE180; + break; + } + break; + } + return rotation; +} + +// Check for SDK Layer support. +bool DeviceResources::SdkLayersAvailable() { + #if defined(_DEBUG) + HRESULT hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. + 0, + D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. + nullptr, // Any feature level will do. + 0, + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. + nullptr, // No need to keep the D3D device reference. + nullptr, // No need to know the feature level. + nullptr // No need to keep the D3D device context reference. + ); + return SUCCEEDED(hr); + #else + return false; + #endif +} + +// The first method called when the IFrameworkView is being created. +void App::Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) { + // Register event handlers for app lifecycle. This example includes Activated, so that we + // can make the CoreWindow active and start rendering on the window. + applicationView.Activated({ this, &App::OnActivated }); + + winrt::Windows::ApplicationModel::Core::CoreApplication::Suspending({ this, &App::OnSuspending }); + winrt::Windows::ApplicationModel::Core::CoreApplication::Resuming({ this, &App::OnResuming }); + + // At this point we have access to the device. + // We can create the device-dependent resources. + m_deviceResources = std::make_unique(); +} + +// Called when the CoreWindow object is created (or re-created). +void App::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { + window.SizeChanged({ this, &App::OnWindowSizeChanged }); + window.VisibilityChanged({ this, &App::OnVisibilityChanged }); + + window.KeyDown({ this, &App::OnKeyDown }); + window.KeyUp({ this, &App::OnKeyUp }); + window.CharacterReceived({ this, &App::OnCharacterReceived }); + + window.PointerEntered({ this, &App::OnPointerEntered }); + window.PointerExited({ this, &App::OnPointerExited }); + window.PointerPressed({ this, &App::OnPointerPressed }); + window.PointerReleased({ this, &App::OnPointerReleased }); + window.PointerMoved({ this, &App::OnPointerMoved }); + window.PointerWheelChanged({ this, &App::OnPointerWheelChanged }); + + auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); + + currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged }); + currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged }); + winrt::Windows::Graphics::Display::DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated }); + + winrt::Windows::UI::Core::SystemNavigationManager::GetForCurrentView().BackRequested({ this, &App::OnBackRequested }); + + m_deviceResources->SetWindow(window); +} + +// Initializes scene resources, or loads a previously saved app state. +void App::Load(winrt::hstring const& entryPoint) { + _SOKOL_UNUSED(entryPoint); +} + +// This method is called after the window becomes active. +void App::Run() { + // NOTE: UWP will simply terminate an application, it's not possible to detect when an application is being closed + while (true) { + if (m_windowVisible) { + winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent); + _sapp_frame(); + m_deviceResources->Present(); + } + else { + winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessOneAndAllPending); + } + } +} + +// Required for IFrameworkView. +// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView +// class is torn down while the app is in the foreground. +void App::Uninitialize() { + // empty +} + +// Application lifecycle event handlers. +void App::OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) { + _SOKOL_UNUSED(args); + _SOKOL_UNUSED(applicationView); + auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + auto targetSize = winrt::Windows::Foundation::Size((float)_sapp.desc.width, (float)_sapp.desc.height); + appView.SetPreferredMinSize(targetSize); + appView.TryResizeView(targetSize); + + // Disabling this since it can only append the title to the app name (Title - Appname). + // There's no way of just setting a string to be the window title. + //appView.Title(_sapp.window_title_wide); + + // Run() won't start until the CoreWindow is activated. + winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Activate(); + if (_sapp.desc.fullscreen) { + appView.TryEnterFullScreenMode(); + } + _sapp.fullscreen = appView.IsFullScreenMode(); +} + +void App::OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args) { + _SOKOL_UNUSED(sender); + _SOKOL_UNUSED(args); + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_SUSPENDED); +} + +void App::OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args) { + _SOKOL_UNUSED(args); + _SOKOL_UNUSED(sender); + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESUMED); +} + +void App::OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args) { + _SOKOL_UNUSED(args); + m_deviceResources->SetLogicalSize(winrt::Windows::Foundation::Size(sender.Bounds().Width, sender.Bounds().Height)); + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) { + _SOKOL_UNUSED(sender); + m_windowVisible = args.Visible(); + _sapp_win32_uwp_app_event(m_windowVisible ? SAPP_EVENTTYPE_RESTORED : SAPP_EVENTTYPE_ICONIFIED); +} + +void App::OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args) { + _SOKOL_UNUSED(sender); + args.Handled(true); +} + +void App::OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { + auto status = args.KeyStatus(); + _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_DOWN, sender, args); +} + +void App::OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { + auto status = args.KeyStatus(); + _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_UP, sender, args); +} + +void App::OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) { + _sapp_uwp_char_event(args.KeyCode(), args.KeyStatus().WasKeyDown, sender); +} + +void App::OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + _SOKOL_UNUSED(args); + _sapp.uwp.mouse_tracked = true; + _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); +} + +void App::OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + _SOKOL_UNUSED(args); + _sapp.uwp.mouse_tracked = false; + _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, sender); +} + +void App::OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + _sapp_uwp_extract_mouse_button_events(sender, args); +} + +// NOTE: for some reason this event handler is never called?? +void App::OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + _sapp_uwp_extract_mouse_button_events(sender, args); +} + +void App::OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + auto position = args.CurrentPoint().Position(); + const float new_x = (float)(int)(position.X * _sapp.uwp.dpi.mouse_scale + 0.5f); + const float new_y = (float)(int)(position.Y * _sapp.uwp.dpi.mouse_scale + 0.5f); + // don't update dx/dy in the very first event + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + if (!_sapp.uwp.mouse_tracked) { + _sapp.uwp.mouse_tracked = true; + _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); + } + _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, sender); + + // HACK for detecting multiple mouse button presses + _sapp_uwp_extract_mouse_button_events(sender, args); +} + +void App::OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + auto properties = args.CurrentPoint().Properties(); + _sapp_uwp_scroll_event((float)properties.MouseWheelDelta(), properties.IsHorizontalMouseWheel(), sender); +} + +void App::OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { + // NOTE: UNTESTED + _SOKOL_UNUSED(args); + m_deviceResources->SetDpi(sender.LogicalDpi()); + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { + // NOTE: UNTESTED + _SOKOL_UNUSED(args); + m_deviceResources->SetCurrentOrientation(sender.CurrentOrientation()); + _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { + // NOTE: UNTESTED + _SOKOL_UNUSED(args); + _SOKOL_UNUSED(sender); + m_deviceResources->ValidateDevice(); +} + +} /* End empty namespace */ + +_SOKOL_PRIVATE void _sapp_uwp_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_win32_uwp_init_keytable(); + _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + winrt::Windows::ApplicationModel::Core::CoreApplication::Run(winrt::make()); +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(UNICODE) +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { +#else +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { +#endif + _SOKOL_UNUSED(hInstance); + _SOKOL_UNUSED(hPrevInstance); + _SOKOL_UNUSED(lpCmdLine); + _SOKOL_UNUSED(nCmdShow); + sapp_desc desc = sokol_main(0, nullptr); + _sapp_uwp_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_UWP */ + +/*== Android ================================================================*/ +#if defined(_SAPP_ANDROID) + +/* android loop thread */ +_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { + SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); + + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return false; + } + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + return false; + } + + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint cfg_attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 0, + EGL_NONE, + }; + EGLConfig available_cfgs[32]; + EGLint cfg_count; + eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); + SOKOL_ASSERT(cfg_count > 0); + SOKOL_ASSERT(cfg_count <= 32); + + /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ + EGLConfig config; + bool exact_cfg_found = false; + for (int i = 0; i < cfg_count; ++i) { + EGLConfig c = available_cfgs[i]; + EGLint r, g, b, a, d; + if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && + r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { + exact_cfg_found = true; + config = c; + break; + } + } + if (!exact_cfg_found) { + config = available_cfgs[0]; + } + + EGLint ctx_attributes[] = { + #if defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, + #else + EGL_CONTEXT_CLIENT_VERSION, 2, + #endif + EGL_NONE, + }; + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); + if (context == EGL_NO_CONTEXT) { + return false; + } + + _sapp.android.config = config; + _sapp.android.display = display; + _sapp.android.context = context; + return true; +} +SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ + //eglSwapInterval(_sapp.android.display,0); +} + + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { + if (_sapp.android.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + SOKOL_LOG("Destroying egl surface"); + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } + if (_sapp.android.context != EGL_NO_CONTEXT) { + SOKOL_LOG("Destroying egl context"); + eglDestroyContext(_sapp.android.display, _sapp.android.context); + _sapp.android.context = EGL_NO_CONTEXT; + } + SOKOL_LOG("Terminating egl display"); + eglTerminate(_sapp.android.display); + _sapp.android.display = EGL_NO_DISPLAY; + } +} + +_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + /* TODO: set window flags */ + /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ + + /* create egl surface and make it current */ + EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); + if (surface == EGL_NO_SURFACE) { + return false; + } + if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { + return false; + } + _sapp.android.surface = surface; + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { + if (_sapp.android.display == EGL_NO_DISPLAY) { + return; + } + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } +} + +_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + SOKOL_LOG("event_cb()"); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + const int32_t win_w = ANativeWindow_getWidth(window); + const int32_t win_h = ANativeWindow_getHeight(window); + SOKOL_ASSERT(win_w >= 0 && win_h >= 0); + const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); + _sapp.window_width = win_w; + _sapp.window_height = win_h; + if (win_changed || force_update) { + if (!_sapp.desc.high_dpi) { + const int32_t buf_w = win_w / 2; + const int32_t buf_h = win_h / 2; + EGLint format; + EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); + SOKOL_ASSERT(egl_result == EGL_TRUE); + /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions + as the ANativeWindow size results in weird display artefacts, that's + why it's only called when the buffer geometry is different from + the window size + */ + int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); + SOKOL_ASSERT(result == 0); + } + } + + /* query surface size */ + EGLint fb_w, fb_h; + EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); + EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); + SOKOL_ASSERT(egl_result_w == EGL_TRUE); + SOKOL_ASSERT(egl_result_h == EGL_TRUE); + const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); + _sapp.framebuffer_width = fb_w; + _sapp.framebuffer_height = fb_h; + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + if (win_changed || fb_changed || force_update) { + if (!_sapp.first_frame) { + SOKOL_LOG("SAPP_EVENTTYPE_RESIZED"); + _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_android_cleanup(void) { + SOKOL_LOG("Cleaning up"); + if (_sapp.android.surface != EGL_NO_SURFACE) { + /* egl context is bound, cleanup gracefully */ + if (_sapp.init_called && !_sapp.cleanup_called) { + SOKOL_LOG("cleanup_cb()"); + _sapp_call_cleanup(); + } + } + /* always try to cleanup by destroying egl context */ + _sapp_android_cleanup_egl(); +} + +_SOKOL_PRIVATE void _sapp_android_shutdown(void) { + /* try to cleanup while we still have a surface and can call cleanup_cb() */ + _sapp_android_cleanup(); + /* request exit */ + ANativeActivity_finish(_sapp.android.activity); +} + +_SOKOL_PRIVATE void _sapp_android_frame(void) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + _sapp_android_update_dimensions(_sapp.android.current.window, false); + _sapp_frame(); + eglSwapBuffers(_sapp.android.display, _sapp.android.surface); +} + +_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { + return false; + } + if (!_sapp_events_enabled()) { + return false; + } + if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; + if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; + + int32_t action_idx = AMotionEvent_getAction(e); + int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + SOKOL_LOG("Touch: down"); + case AMOTION_EVENT_ACTION_POINTER_DOWN: + SOKOL_LOG("Touch: ptr down"); + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case AMOTION_EVENT_ACTION_MOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + SOKOL_LOG("Touch: up"); + case AMOTION_EVENT_ACTION_POINTER_UP: + SOKOL_LOG("Touch: ptr up"); + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case AMOTION_EVENT_ACTION_CANCEL: + SOKOL_LOG("Touch: cancel"); + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + _sapp_init_event(type); + _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); + dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { + dst->changed = (i == idx); + } else { + dst->changed = true; + } + } + _sapp_call_event(&_sapp.event); + return true; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { + switch (scancode) { + case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; + case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; + case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; + case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; + case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; + case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; + case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; + case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; + case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; + case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; + case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; + case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; + case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; + case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; + case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; + case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; + case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; + case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; + case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; + case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; + case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; + case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; + case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; + case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; + case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; + case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; + case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; + case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; + case AKEYCODE_F1: return SAPP_KEYCODE_F1; + case AKEYCODE_F2: return SAPP_KEYCODE_F2; + case AKEYCODE_F3: return SAPP_KEYCODE_F3; + case AKEYCODE_F4: return SAPP_KEYCODE_F4; + case AKEYCODE_F5: return SAPP_KEYCODE_F5; + case AKEYCODE_F6: return SAPP_KEYCODE_F6; + case AKEYCODE_F7: return SAPP_KEYCODE_F7; + case AKEYCODE_F8: return SAPP_KEYCODE_F8; + case AKEYCODE_F9: return SAPP_KEYCODE_F9; + case AKEYCODE_F10: return SAPP_KEYCODE_F10; + case AKEYCODE_F11: return SAPP_KEYCODE_F11; + case AKEYCODE_F12: return SAPP_KEYCODE_F12; + case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; + case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; + case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; + case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; + case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; + case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; + case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; + case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; + case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; + case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; + case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; + case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; + case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; + case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; + case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; + case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; + case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; + case AKEYCODE_A: return SAPP_KEYCODE_A; + case AKEYCODE_B: return SAPP_KEYCODE_B; + case AKEYCODE_C: return SAPP_KEYCODE_C; + case AKEYCODE_D: return SAPP_KEYCODE_D; + case AKEYCODE_E: return SAPP_KEYCODE_E; + case AKEYCODE_F: return SAPP_KEYCODE_F; + case AKEYCODE_G: return SAPP_KEYCODE_G; + case AKEYCODE_H: return SAPP_KEYCODE_H; + case AKEYCODE_I: return SAPP_KEYCODE_I; + case AKEYCODE_J: return SAPP_KEYCODE_J; + case AKEYCODE_K: return SAPP_KEYCODE_K; + case AKEYCODE_L: return SAPP_KEYCODE_L; + case AKEYCODE_M: return SAPP_KEYCODE_M; + case AKEYCODE_N: return SAPP_KEYCODE_N; + case AKEYCODE_O: return SAPP_KEYCODE_O; + case AKEYCODE_P: return SAPP_KEYCODE_P; + case AKEYCODE_Q: return SAPP_KEYCODE_Q; + case AKEYCODE_R: return SAPP_KEYCODE_R; + case AKEYCODE_S: return SAPP_KEYCODE_S; + case AKEYCODE_T: return SAPP_KEYCODE_T; + case AKEYCODE_U: return SAPP_KEYCODE_U; + case AKEYCODE_V: return SAPP_KEYCODE_V; + case AKEYCODE_W: return SAPP_KEYCODE_W; + case AKEYCODE_X: return SAPP_KEYCODE_X; + case AKEYCODE_Y: return SAPP_KEYCODE_Y; + case AKEYCODE_Z: return SAPP_KEYCODE_Z; + case AKEYCODE_1: return SAPP_KEYCODE_1; + case AKEYCODE_2: return SAPP_KEYCODE_2; + case AKEYCODE_3: return SAPP_KEYCODE_3; + case AKEYCODE_4: return SAPP_KEYCODE_4; + case AKEYCODE_5: return SAPP_KEYCODE_5; + case AKEYCODE_6: return SAPP_KEYCODE_6; + case AKEYCODE_7: return SAPP_KEYCODE_7; + case AKEYCODE_8: return SAPP_KEYCODE_8; + case AKEYCODE_9: return SAPP_KEYCODE_9; + case AKEYCODE_0: return SAPP_KEYCODE_0; + case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; + case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; + case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; + case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; + case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; + case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; + case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; + case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; + case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; + case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; + case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; + case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; + /* Android Buttons */ + case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; + default: return SAPP_KEYCODE_INVALID; + } + return SAPP_KEYCODE_INVALID; +} +_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { + uint32_t meta_state = AKeyEvent_getMetaState(e); + uint32_t mods = 0; + if (meta_state& AMETA_SHIFT_ON) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (meta_state& AMETA_CTRL_ON) { + mods |= SAPP_MODIFIER_CTRL; + } + if (meta_state& AMETA_ALT_ON) { + mods |= SAPP_MODIFIER_ALT; + } + if (meta_state& AMETA_META_ON) { + mods |= SAPP_MODIFIER_SUPER; + } + return mods; +} +int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) +{ + ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); + // Attaches the current thread to the JVM. + JavaVM *javaVM = activity->vm; + JNIEnv *jniEnv = activity->env; + + jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); + if(result == JNI_ERR){ + return 0; + } + + jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); + int unicodeKey; + + if(metaState == 0){ + jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); + jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); + jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); + unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); + }else{ + jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); + jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); + jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); + unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); + } + + (*javaVM)->DetachCurrentThread(javaVM); + + return unicodeKey; +} +_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { + if (_sapp_events_enabled() ) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_android_mods(e); + _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} +_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { + return false; + } + if (!_sapp_events_enabled()) { + return false; + } + int32_t action = AKeyEvent_getAction(e); + // Don't process soft keyboard commands as they are not reliable through this interface. + if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; + // Don't relay key press events from joysticks or game pads as Sokol key down events + if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || + (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { + return false; + } + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + + case AKEY_EVENT_ACTION_DOWN : + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case AKEY_EVENT_ACTION_UP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + bool repeat = AKeyEvent_getRepeatCount(e)>0; + _sapp_init_event(type); + _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); + _sapp.event.modifiers = _sapp_android_mods(e); + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + if(type==SAPP_EVENTTYPE_KEY_DOWN){ + _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); + } + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + return _sapp.event.key_code != SAPP_KEYCODE_INVALID; +} + +_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { + if ((events & ALOOPER_EVENT_INPUT) == 0) { + SOKOL_LOG("_sapp_android_input_cb() encountered unsupported event"); + return 1; + } + SOKOL_ASSERT(_sapp.android.current.input); + AInputEvent* event = NULL; + while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { + if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { + continue; + } + int32_t handled = 0; + if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { + handled = 1; + } + AInputQueue_finishEvent(_sapp.android.current.input, event, handled); + } + return 1; +} + +_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { + if ((events & ALOOPER_EVENT_INPUT) == 0) { + SOKOL_LOG("_sapp_android_main_cb() encountered unsupported event"); + return 1; + } + + _sapp_android_msg_t msg; + if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { + SOKOL_LOG("Could not write to read_from_main_fd"); + return 1; + } + + pthread_mutex_lock(&_sapp.android.pt.mutex); + switch (msg) { + case _SOKOL_ANDROID_MSG_CREATE: + { + SOKOL_LOG("MSG_CREATE"); + SOKOL_ASSERT(!_sapp.valid); + bool result = _sapp_android_init_egl(); + SOKOL_ASSERT(result); + _sapp.valid = true; + _sapp.android.has_created = true; + } + break; + case _SOKOL_ANDROID_MSG_RESUME: + SOKOL_LOG("MSG_RESUME"); + _sapp.android.has_resumed = true; + _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); + break; + case _SOKOL_ANDROID_MSG_PAUSE: + SOKOL_LOG("MSG_PAUSE"); + _sapp.android.has_resumed = false; + _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); + break; + case _SOKOL_ANDROID_MSG_FOCUS: + SOKOL_LOG("MSG_FOCUS"); + _sapp.android.has_focus = true; + break; + case _SOKOL_ANDROID_MSG_NO_FOCUS: + SOKOL_LOG("MSG_NO_FOCUS"); + _sapp.android.has_focus = false; + break; + case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: + SOKOL_LOG("MSG_SET_NATIVE_WINDOW"); + if (_sapp.android.current.window != _sapp.android.pending.window) { + if (_sapp.android.current.window != NULL) { + _sapp_android_cleanup_egl_surface(); + } + if (_sapp.android.pending.window != NULL) { + SOKOL_LOG("Creating egl surface ..."); + if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { + SOKOL_LOG("... ok!"); + _sapp_android_update_dimensions(_sapp.android.pending.window, true); + } else { + SOKOL_LOG("... failed!"); + _sapp_android_shutdown(); + } + } + } + _sapp.android.current.window = _sapp.android.pending.window; + break; + case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: + SOKOL_LOG("MSG_SET_INPUT_QUEUE"); + if (_sapp.android.current.input != _sapp.android.pending.input) { + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + if (_sapp.android.pending.input != NULL) { + AInputQueue_attachLooper( + _sapp.android.pending.input, + _sapp.android.looper, + ALOOPER_POLL_CALLBACK, + _sapp_android_input_cb, + NULL); /* data */ + } + } + _sapp.android.current.input = _sapp.android.pending.input; + break; + case _SOKOL_ANDROID_MSG_DESTROY: + SOKOL_LOG("MSG_DESTROY"); + _sapp_android_cleanup(); + _sapp.valid = false; + _sapp.android.is_thread_stopping = true; + break; + default: + SOKOL_LOG("Unknown msg type received"); + break; + } + pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ + pthread_mutex_unlock(&_sapp.android.pt.mutex); + return 1; +} + +_SOKOL_PRIVATE bool _sapp_android_should_update(void) { + bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; + bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; + return is_in_front && has_surface; +} + +_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { + SOKOL_ASSERT(_sapp.valid); + /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ + if (shown) { + SOKOL_LOG("Showing keyboard"); + ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); + } else { + SOKOL_LOG("Hiding keyboard"); + ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); + } +} + +_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { + _SOKOL_UNUSED(arg); + SOKOL_LOG("Loop thread started"); + + _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); + ALooper_addFd(_sapp.android.looper, + _sapp.android.pt.read_from_main_fd, + ALOOPER_POLL_CALLBACK, + ALOOPER_EVENT_INPUT, + _sapp_android_main_cb, + NULL); /* data */ + + /* signal start to main thread */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_started = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* main loop */ + while (!_sapp.android.is_thread_stopping) { + /* sokol frame */ + if (_sapp_android_should_update()) { + _sapp_android_frame(); + } + + /* process all events (or stop early if app is requested to quit) */ + bool process_events = true; + while (process_events && !_sapp.android.is_thread_stopping) { + bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); + process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; + } + /* handle quit-requested, either from window or from sapp_request_quit() */ + if (_sapp.quit_requested && !_sapp.quit_ordered) { + /* give user code a chance to intervene */ + _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if(_sapp.quit_ordered) _sapp_android_shutdown(); + } + + /* cleanup thread */ + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + + /* the following causes heap corruption on exit, why?? + ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); + ALooper_release(_sapp.android.looper);*/ + + /* signal "destroyed" */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_stopped = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + SOKOL_LOG("Loop thread done"); + return NULL; +} + +/* android main/ui thread */ +_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { + if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { + SOKOL_LOG("Could not write to write_from_main_fd"); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onStart()"); +} + +_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onResume()"); + _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); +} + +_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { + SOKOL_LOG("NativeActivity onSaveInstanceState()"); + *out_size = 0; + return NULL; +} + +_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { + SOKOL_LOG("NativeActivity onWindowFocusChanged()"); + if (has_focus) { + _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); + } else { + _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onPause()"); + _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); +} + +_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onStop()"); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.window = window; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); + while (_sapp.android.current.window != window) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { + SOKOL_LOG("NativeActivity onNativeWindowCreated()"); + _sapp_android_msg_set_native_window(window); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { + SOKOL_LOG("NativeActivity onNativeWindowDestroyed()"); + _sapp_android_msg_set_native_window(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.input = input; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); + while (_sapp.android.current.input != input) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { + SOKOL_LOG("NativeActivity onInputQueueCreated()"); + _sapp_android_msg_set_input_queue(queue); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { + SOKOL_LOG("NativeActivity onInputQueueDestroyed()"); + _sapp_android_msg_set_input_queue(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onConfigurationChanged()"); + /* see android:configChanges in manifest */ +} + +_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { + SOKOL_LOG("NativeActivity onLowMemory()"); +} + +_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { + /* + * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) + * on my device (Moto X 2nd gen) when the app is removed from the task view + * (TaskStackView: onTaskViewDismissed). + * + * However, if ANativeActivity_finish() is explicitly called from for example + * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? + */ + SOKOL_LOG("NativeActivity onDestroy()"); + + /* send destroy msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); + while (!_sapp.android.is_thread_stopped) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* clean up main thread */ + pthread_cond_destroy(&_sapp.android.pt.cond); + pthread_mutex_destroy(&_sapp.android.pt.mutex); + + close(_sapp.android.pt.read_from_main_fd); + close(_sapp.android.pt.write_from_main_fd); + + SOKOL_LOG("NativeActivity done"); + + /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ + exit(0); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { + SOKOL_LOG("NativeActivity onCreate()"); + + sapp_desc desc = sokol_main(0, NULL); + _sapp_init_state(&desc); + + /* start loop thread */ + _sapp.android.activity = activity; + + int pipe_fd[2]; + if (pipe(pipe_fd) != 0) { + SOKOL_LOG("Could not create thread pipe"); + return; + } + _sapp.android.pt.read_from_main_fd = pipe_fd[0]; + _sapp.android.pt.write_from_main_fd = pipe_fd[1]; + + pthread_mutex_init(&_sapp.android.pt.mutex, NULL); + pthread_cond_init(&_sapp.android.pt.cond, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); + pthread_attr_destroy(&attr); + + /* wait until main loop has started */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + while (!_sapp.android.is_thread_started) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* send create msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); + while (!_sapp.android.has_created) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* register for callbacks */ + activity->callbacks->onStart = _sapp_android_on_start; + activity->callbacks->onResume = _sapp_android_on_resume; + activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; + activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; + activity->callbacks->onPause = _sapp_android_on_pause; + activity->callbacks->onStop = _sapp_android_on_stop; + activity->callbacks->onDestroy = _sapp_android_on_destroy; + activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; + /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ + /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ + activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; + activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; + activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; + /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ + activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; + activity->callbacks->onLowMemory = _sapp_android_on_low_memory; + + SOKOL_LOG("NativeActivity successfully created"); + + /* NOT A BUG: do NOT call sapp_discard_state() */ +} + +#endif /* _SAPP_ANDROID */ + +/*== LINUX ==================================================================*/ +#if defined(_SAPP_LINUX) + +/* see GLFW's xkb_unicode.c */ +static const struct _sapp_x11_codepair { + uint16_t keysym; + uint16_t ucs; +} _sapp_x11_keysymtab[] = { + { 0x01a1, 0x0104 }, + { 0x01a2, 0x02d8 }, + { 0x01a3, 0x0141 }, + { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, + { 0x01a9, 0x0160 }, + { 0x01aa, 0x015e }, + { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, + { 0x01ae, 0x017d }, + { 0x01af, 0x017b }, + { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, + { 0x01b3, 0x0142 }, + { 0x01b5, 0x013e }, + { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, + { 0x01b9, 0x0161 }, + { 0x01ba, 0x015f }, + { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, + { 0x01bd, 0x02dd }, + { 0x01be, 0x017e }, + { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, + { 0x01c3, 0x0102 }, + { 0x01c5, 0x0139 }, + { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, + { 0x01ca, 0x0118 }, + { 0x01cc, 0x011a }, + { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, + { 0x01d1, 0x0143 }, + { 0x01d2, 0x0147 }, + { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, + { 0x01d9, 0x016e }, + { 0x01db, 0x0170 }, + { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, + { 0x01e3, 0x0103 }, + { 0x01e5, 0x013a }, + { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, + { 0x01ea, 0x0119 }, + { 0x01ec, 0x011b }, + { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, + { 0x01f1, 0x0144 }, + { 0x01f2, 0x0148 }, + { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, + { 0x01f9, 0x016f }, + { 0x01fb, 0x0171 }, + { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, + { 0x02a1, 0x0126 }, + { 0x02a6, 0x0124 }, + { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, + { 0x02ac, 0x0134 }, + { 0x02b1, 0x0127 }, + { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, + { 0x02bb, 0x011f }, + { 0x02bc, 0x0135 }, + { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, + { 0x02d5, 0x0120 }, + { 0x02d8, 0x011c }, + { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, + { 0x02e5, 0x010b }, + { 0x02e6, 0x0109 }, + { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, + { 0x02fd, 0x016d }, + { 0x02fe, 0x015d }, + { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, + { 0x03a5, 0x0128 }, + { 0x03a6, 0x013b }, + { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, + { 0x03ac, 0x0166 }, + { 0x03b3, 0x0157 }, + { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, + { 0x03ba, 0x0113 }, + { 0x03bb, 0x0123 }, + { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, + { 0x03bf, 0x014b }, + { 0x03c0, 0x0100 }, + { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, + { 0x03cf, 0x012a }, + { 0x03d1, 0x0145 }, + { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, + { 0x03d9, 0x0172 }, + { 0x03dd, 0x0168 }, + { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, + { 0x03e7, 0x012f }, + { 0x03ec, 0x0117 }, + { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, + { 0x03f2, 0x014d }, + { 0x03f3, 0x0137 }, + { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, + { 0x03fe, 0x016b }, + { 0x047e, 0x203e }, + { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, + { 0x04a3, 0x300d }, + { 0x04a4, 0x3001 }, + { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, + { 0x04a7, 0x30a1 }, + { 0x04a8, 0x30a3 }, + { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, + { 0x04ab, 0x30a9 }, + { 0x04ac, 0x30e3 }, + { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, + { 0x04af, 0x30c3 }, + { 0x04b0, 0x30fc }, + { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, + { 0x04b3, 0x30a6 }, + { 0x04b4, 0x30a8 }, + { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, + { 0x04b7, 0x30ad }, + { 0x04b8, 0x30af }, + { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, + { 0x04bb, 0x30b5 }, + { 0x04bc, 0x30b7 }, + { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, + { 0x04bf, 0x30bd }, + { 0x04c0, 0x30bf }, + { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, + { 0x04c3, 0x30c6 }, + { 0x04c4, 0x30c8 }, + { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, + { 0x04c7, 0x30cc }, + { 0x04c8, 0x30cd }, + { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, + { 0x04cb, 0x30d2 }, + { 0x04cc, 0x30d5 }, + { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, + { 0x04cf, 0x30de }, + { 0x04d0, 0x30df }, + { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, + { 0x04d3, 0x30e2 }, + { 0x04d4, 0x30e4 }, + { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, + { 0x04d7, 0x30e9 }, + { 0x04d8, 0x30ea }, + { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, + { 0x04db, 0x30ed }, + { 0x04dc, 0x30ef }, + { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, + { 0x04df, 0x309c }, + { 0x05ac, 0x060c }, + { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, + { 0x05c1, 0x0621 }, + { 0x05c2, 0x0622 }, + { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, + { 0x05c5, 0x0625 }, + { 0x05c6, 0x0626 }, + { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, + { 0x05c9, 0x0629 }, + { 0x05ca, 0x062a }, + { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, + { 0x05cd, 0x062d }, + { 0x05ce, 0x062e }, + { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, + { 0x05d1, 0x0631 }, + { 0x05d2, 0x0632 }, + { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, + { 0x05d5, 0x0635 }, + { 0x05d6, 0x0636 }, + { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, + { 0x05d9, 0x0639 }, + { 0x05da, 0x063a }, + { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, + { 0x05e2, 0x0642 }, + { 0x05e3, 0x0643 }, + { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, + { 0x05e6, 0x0646 }, + { 0x05e7, 0x0647 }, + { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, + { 0x05ea, 0x064a }, + { 0x05eb, 0x064b }, + { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, + { 0x05ee, 0x064e }, + { 0x05ef, 0x064f }, + { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, + { 0x05f2, 0x0652 }, + { 0x06a1, 0x0452 }, + { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, + { 0x06a4, 0x0454 }, + { 0x06a5, 0x0455 }, + { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, + { 0x06a8, 0x0458 }, + { 0x06a9, 0x0459 }, + { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, + { 0x06ac, 0x045c }, + { 0x06ae, 0x045e }, + { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, + { 0x06b1, 0x0402 }, + { 0x06b2, 0x0403 }, + { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, + { 0x06b5, 0x0405 }, + { 0x06b6, 0x0406 }, + { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, + { 0x06b9, 0x0409 }, + { 0x06ba, 0x040a }, + { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, + { 0x06be, 0x040e }, + { 0x06bf, 0x040f }, + { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, + { 0x06c2, 0x0431 }, + { 0x06c3, 0x0446 }, + { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, + { 0x06c6, 0x0444 }, + { 0x06c7, 0x0433 }, + { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, + { 0x06ca, 0x0439 }, + { 0x06cb, 0x043a }, + { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, + { 0x06ce, 0x043d }, + { 0x06cf, 0x043e }, + { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, + { 0x06d2, 0x0440 }, + { 0x06d3, 0x0441 }, + { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, + { 0x06d6, 0x0436 }, + { 0x06d7, 0x0432 }, + { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, + { 0x06da, 0x0437 }, + { 0x06db, 0x0448 }, + { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, + { 0x06de, 0x0447 }, + { 0x06df, 0x044a }, + { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, + { 0x06e2, 0x0411 }, + { 0x06e3, 0x0426 }, + { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, + { 0x06e6, 0x0424 }, + { 0x06e7, 0x0413 }, + { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, + { 0x06ea, 0x0419 }, + { 0x06eb, 0x041a }, + { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, + { 0x06ee, 0x041d }, + { 0x06ef, 0x041e }, + { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, + { 0x06f2, 0x0420 }, + { 0x06f3, 0x0421 }, + { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, + { 0x06f6, 0x0416 }, + { 0x06f7, 0x0412 }, + { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, + { 0x06fa, 0x0417 }, + { 0x06fb, 0x0428 }, + { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, + { 0x06fe, 0x0427 }, + { 0x06ff, 0x042a }, + { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, + { 0x07a3, 0x0389 }, + { 0x07a4, 0x038a }, + { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, + { 0x07a8, 0x038e }, + { 0x07a9, 0x03ab }, + { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, + { 0x07af, 0x2015 }, + { 0x07b1, 0x03ac }, + { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, + { 0x07b4, 0x03af }, + { 0x07b5, 0x03ca }, + { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, + { 0x07b8, 0x03cd }, + { 0x07b9, 0x03cb }, + { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, + { 0x07c1, 0x0391 }, + { 0x07c2, 0x0392 }, + { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, + { 0x07c5, 0x0395 }, + { 0x07c6, 0x0396 }, + { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, + { 0x07c9, 0x0399 }, + { 0x07ca, 0x039a }, + { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, + { 0x07cd, 0x039d }, + { 0x07ce, 0x039e }, + { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, + { 0x07d1, 0x03a1 }, + { 0x07d2, 0x03a3 }, + { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, + { 0x07d6, 0x03a6 }, + { 0x07d7, 0x03a7 }, + { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, + { 0x07e1, 0x03b1 }, + { 0x07e2, 0x03b2 }, + { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, + { 0x07e5, 0x03b5 }, + { 0x07e6, 0x03b6 }, + { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, + { 0x07e9, 0x03b9 }, + { 0x07ea, 0x03ba }, + { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, + { 0x07ed, 0x03bd }, + { 0x07ee, 0x03be }, + { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, + { 0x07f1, 0x03c1 }, + { 0x07f2, 0x03c3 }, + { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, + { 0x07f5, 0x03c5 }, + { 0x07f6, 0x03c6 }, + { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, + { 0x07f9, 0x03c9 }, + { 0x08a1, 0x23b7 }, + { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, + { 0x08a4, 0x2320 }, + { 0x08a5, 0x2321 }, + { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, + { 0x08a8, 0x23a3 }, + { 0x08a9, 0x23a4 }, + { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, + { 0x08ac, 0x239d }, + { 0x08ad, 0x239e }, + { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, + { 0x08b0, 0x23ac }, + { 0x08bc, 0x2264 }, + { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, + { 0x08bf, 0x222b }, + { 0x08c0, 0x2234 }, + { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, + { 0x08c5, 0x2207 }, + { 0x08c8, 0x223c }, + { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, + { 0x08ce, 0x21d2 }, + { 0x08cf, 0x2261 }, + { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, + { 0x08db, 0x2283 }, + { 0x08dc, 0x2229 }, + { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, + { 0x08df, 0x2228 }, + { 0x08ef, 0x2202 }, + { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, + { 0x08fc, 0x2191 }, + { 0x08fd, 0x2192 }, + { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, + { 0x09e1, 0x2592 }, + { 0x09e2, 0x2409 }, + { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, + { 0x09e5, 0x240a }, + { 0x09e8, 0x2424 }, + { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, + { 0x09eb, 0x2510 }, + { 0x09ec, 0x250c }, + { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, + { 0x09ef, 0x23ba }, + { 0x09f0, 0x23bb }, + { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, + { 0x09f3, 0x23bd }, + { 0x09f4, 0x251c }, + { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, + { 0x09f7, 0x252c }, + { 0x09f8, 0x2502 }, + { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, + { 0x0aa3, 0x2004 }, + { 0x0aa4, 0x2005 }, + { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, + { 0x0aa7, 0x2009 }, + { 0x0aa8, 0x200a }, + { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, + { 0x0aae, 0x2026 }, + { 0x0aaf, 0x2025 }, + { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, + { 0x0ab2, 0x2155 }, + { 0x0ab3, 0x2156 }, + { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, + { 0x0ab6, 0x2159 }, + { 0x0ab7, 0x215a }, + { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, + { 0x0abc, 0x2329 }, + { 0x0abe, 0x232a }, + { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, + { 0x0ac5, 0x215d }, + { 0x0ac6, 0x215e }, + { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, + { 0x0acc, 0x25c1 }, + { 0x0acd, 0x25b7 }, + { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, + { 0x0ad0, 0x2018 }, + { 0x0ad1, 0x2019 }, + { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, + { 0x0ad4, 0x211e }, + { 0x0ad6, 0x2032 }, + { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, + { 0x0adb, 0x25ac }, + { 0x0adc, 0x25c0 }, + { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, + { 0x0adf, 0x25ae }, + { 0x0ae0, 0x25e6 }, + { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, + { 0x0ae3, 0x25b3 }, + { 0x0ae4, 0x25bd }, + { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, + { 0x0ae7, 0x25aa }, + { 0x0ae8, 0x25b2 }, + { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, + { 0x0aeb, 0x261e }, + { 0x0aec, 0x2663 }, + { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, + { 0x0af0, 0x2720 }, + { 0x0af1, 0x2020 }, + { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, + { 0x0af4, 0x2717 }, + { 0x0af5, 0x266f }, + { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, + { 0x0af8, 0x2640 }, + { 0x0af9, 0x260e }, + { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, + { 0x0afc, 0x2038 }, + { 0x0afd, 0x201a }, + { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, + { 0x0ba6, 0x003e }, + { 0x0ba8, 0x2228 }, + { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, + { 0x0bc2, 0x22a5 }, + { 0x0bc3, 0x2229 }, + { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, + { 0x0bca, 0x2218 }, + { 0x0bcc, 0x2395 }, + { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, + { 0x0bd3, 0x2308 }, + { 0x0bd6, 0x222a }, + { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, + { 0x0bdc, 0x22a2 }, + { 0x0bfc, 0x22a3 }, + { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, + { 0x0ce1, 0x05d1 }, + { 0x0ce2, 0x05d2 }, + { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, + { 0x0ce5, 0x05d5 }, + { 0x0ce6, 0x05d6 }, + { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, + { 0x0ce9, 0x05d9 }, + { 0x0cea, 0x05da }, + { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, + { 0x0ced, 0x05dd }, + { 0x0cee, 0x05de }, + { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, + { 0x0cf1, 0x05e1 }, + { 0x0cf2, 0x05e2 }, + { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, + { 0x0cf5, 0x05e5 }, + { 0x0cf6, 0x05e6 }, + { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, + { 0x0cf9, 0x05e9 }, + { 0x0cfa, 0x05ea }, + { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, + { 0x0da3, 0x0e03 }, + { 0x0da4, 0x0e04 }, + { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, + { 0x0da7, 0x0e07 }, + { 0x0da8, 0x0e08 }, + { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, + { 0x0dab, 0x0e0b }, + { 0x0dac, 0x0e0c }, + { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, + { 0x0daf, 0x0e0f }, + { 0x0db0, 0x0e10 }, + { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, + { 0x0db3, 0x0e13 }, + { 0x0db4, 0x0e14 }, + { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, + { 0x0db7, 0x0e17 }, + { 0x0db8, 0x0e18 }, + { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, + { 0x0dbb, 0x0e1b }, + { 0x0dbc, 0x0e1c }, + { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, + { 0x0dbf, 0x0e1f }, + { 0x0dc0, 0x0e20 }, + { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, + { 0x0dc3, 0x0e23 }, + { 0x0dc4, 0x0e24 }, + { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, + { 0x0dc7, 0x0e27 }, + { 0x0dc8, 0x0e28 }, + { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, + { 0x0dcb, 0x0e2b }, + { 0x0dcc, 0x0e2c }, + { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, + { 0x0dcf, 0x0e2f }, + { 0x0dd0, 0x0e30 }, + { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, + { 0x0dd3, 0x0e33 }, + { 0x0dd4, 0x0e34 }, + { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, + { 0x0dd7, 0x0e37 }, + { 0x0dd8, 0x0e38 }, + { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, + { 0x0ddf, 0x0e3f }, + { 0x0de0, 0x0e40 }, + { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, + { 0x0de3, 0x0e43 }, + { 0x0de4, 0x0e44 }, + { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, + { 0x0de7, 0x0e47 }, + { 0x0de8, 0x0e48 }, + { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, + { 0x0deb, 0x0e4b }, + { 0x0dec, 0x0e4c }, + { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, + { 0x0df1, 0x0e51 }, + { 0x0df2, 0x0e52 }, + { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, + { 0x0df5, 0x0e55 }, + { 0x0df6, 0x0e56 }, + { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, + { 0x0df9, 0x0e59 }, + { 0x0ea1, 0x3131 }, + { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, + { 0x0ea4, 0x3134 }, + { 0x0ea5, 0x3135 }, + { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, + { 0x0ea8, 0x3138 }, + { 0x0ea9, 0x3139 }, + { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, + { 0x0eac, 0x313c }, + { 0x0ead, 0x313d }, + { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, + { 0x0eb0, 0x3140 }, + { 0x0eb1, 0x3141 }, + { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, + { 0x0eb4, 0x3144 }, + { 0x0eb5, 0x3145 }, + { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, + { 0x0eb8, 0x3148 }, + { 0x0eb9, 0x3149 }, + { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, + { 0x0ebc, 0x314c }, + { 0x0ebd, 0x314d }, + { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, + { 0x0ec0, 0x3150 }, + { 0x0ec1, 0x3151 }, + { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, + { 0x0ec4, 0x3154 }, + { 0x0ec5, 0x3155 }, + { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, + { 0x0ec8, 0x3158 }, + { 0x0ec9, 0x3159 }, + { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, + { 0x0ecc, 0x315c }, + { 0x0ecd, 0x315d }, + { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, + { 0x0ed0, 0x3160 }, + { 0x0ed1, 0x3161 }, + { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, + { 0x0ed4, 0x11a8 }, + { 0x0ed5, 0x11a9 }, + { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, + { 0x0ed8, 0x11ac }, + { 0x0ed9, 0x11ad }, + { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, + { 0x0edc, 0x11b0 }, + { 0x0edd, 0x11b1 }, + { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, + { 0x0ee0, 0x11b4 }, + { 0x0ee1, 0x11b5 }, + { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, + { 0x0ee4, 0x11b8 }, + { 0x0ee5, 0x11b9 }, + { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, + { 0x0ee8, 0x11bc }, + { 0x0ee9, 0x11bd }, + { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, + { 0x0eec, 0x11c0 }, + { 0x0eed, 0x11c1 }, + { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, + { 0x0ef0, 0x3171 }, + { 0x0ef1, 0x3178 }, + { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, + { 0x0ef4, 0x3184 }, + { 0x0ef5, 0x3186 }, + { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, + { 0x0ef8, 0x11eb }, + { 0x0ef9, 0x11f0 }, + { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, + { 0x13a4, 0x20ac }, + { 0x13bc, 0x0152 }, + { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, + { 0x20ac, 0x20ac }, + { 0xfe50, '`' }, + { 0xfe51, 0x00b4 }, + { 0xfe52, '^' }, + { 0xfe53, '~' }, + { 0xfe54, 0x00af }, + { 0xfe55, 0x02d8 }, + { 0xfe56, 0x02d9 }, + { 0xfe57, 0x00a8 }, + { 0xfe58, 0x02da }, + { 0xfe59, 0x02dd }, + { 0xfe5a, 0x02c7 }, + { 0xfe5b, 0x00b8 }, + { 0xfe5c, 0x02db }, + { 0xfe5d, 0x037a }, + { 0xfe5e, 0x309b }, + { 0xfe5f, 0x309c }, + { 0xfe63, '/' }, + { 0xfe64, 0x02bc }, + { 0xfe65, 0x02bd }, + { 0xfe66, 0x02f5 }, + { 0xfe67, 0x02f3 }, + { 0xfe68, 0x02cd }, + { 0xfe69, 0xa788 }, + { 0xfe6a, 0x02f7 }, + { 0xfe6e, ',' }, + { 0xfe6f, 0x00a4 }, + { 0xfe80, 'a' }, /* XK_dead_a */ + { 0xfe81, 'A' }, /* XK_dead_A */ + { 0xfe82, 'e' }, /* XK_dead_e */ + { 0xfe83, 'E' }, /* XK_dead_E */ + { 0xfe84, 'i' }, /* XK_dead_i */ + { 0xfe85, 'I' }, /* XK_dead_I */ + { 0xfe86, 'o' }, /* XK_dead_o */ + { 0xfe87, 'O' }, /* XK_dead_O */ + { 0xfe88, 'u' }, /* XK_dead_u */ + { 0xfe89, 'U' }, /* XK_dead_U */ + { 0xfe8a, 0x0259 }, + { 0xfe8b, 0x018f }, + { 0xfe8c, 0x00b5 }, + { 0xfe90, '_' }, + { 0xfe91, 0x02c8 }, + { 0xfe92, 0x02cc }, + { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, + { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, + { 0xffab /*XKB_KEY_KP_Add*/, '+' }, + { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, + { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, + { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, + { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, + { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } +}; + +_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { + _SOKOL_UNUSED(display); + _sapp.x11.error_code = event->error_code; + return 0; +} + +_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { + _sapp.x11.error_code = Success; + XSetErrorHandler(_sapp_x11_error_handler); +} + +_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { + XSync(_sapp.x11.display, False); + XSetErrorHandler(NULL); +} + +_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { + _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); + _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); + _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); + _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); + _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); + _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); + _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); + if (_sapp.drop.enabled) { + _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); + _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); + _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); + _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); + _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); + _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); + _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); + _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); + _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); + _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); + } + + /* check Xi extension for raw mouse input */ + if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { + _sapp.x11.xi.major = 2; + _sapp.x11.xi.minor = 0; + if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { + _sapp.x11.xi.available = true; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { + /* from GLFW: + + NOTE: Default to the display-wide DPI as we don't currently have a policy + for which monitor a window is considered to be on + + _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * + 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); + + NOTE: Basing the scale on Xft.dpi where available should provide the most + consistent user experience (matches Qt, Gtk, etc), although not + always the most accurate one + */ + char* rms = XResourceManagerString(_sapp.x11.display); + if (rms) { + XrmDatabase db = XrmGetStringDatabase(rms); + if (db) { + XrmValue value; + char* type = NULL; + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { + if (type && strcmp(type, "String") == 0) { + _sapp.x11.dpi = atof(value.addr); + } + } + XrmDestroyDatabase(db); + } + } +} + +_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { + if (extensions) { + return _sapp_glx_has_ext(ext, extensions); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) +{ + if (_sapp.glx.GetProcAddress) { + return (void*) _sapp.glx.GetProcAddress((const GLubyte*) procname); + } + else if (_sapp.glx.GetProcAddressARB) { + return (void*) _sapp.glx.GetProcAddressARB((const GLubyte*) procname); + } + else { + return dlsym(_sapp.glx.libgl, procname); + } +} + +_SOKOL_PRIVATE void _sapp_glx_init() { + const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; + for (int i = 0; sonames[i]; i++) { + _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); + if (_sapp.glx.libgl) { + break; + } + } + if (!_sapp.glx.libgl) { + _sapp_fail("GLX: failed to load libGL"); + } + _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); + _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); + _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); + _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); + _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); + _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); + _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); + _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); + _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); + _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); + _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); + _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); + _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); + _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); + if (!_sapp.glx.GetFBConfigs || + !_sapp.glx.GetFBConfigAttrib || + !_sapp.glx.GetClientString || + !_sapp.glx.QueryExtension || + !_sapp.glx.QueryVersion || + !_sapp.glx.DestroyContext || + !_sapp.glx.MakeCurrent || + !_sapp.glx.SwapBuffers || + !_sapp.glx.QueryExtensionsString || + !_sapp.glx.CreateWindow || + !_sapp.glx.DestroyWindow || + !_sapp.glx.GetProcAddress || + !_sapp.glx.GetProcAddressARB || + !_sapp.glx.GetVisualFromFBConfig) + { + _sapp_fail("GLX: failed to load required entry points"); + } + + if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { + _sapp_fail("GLX: GLX extension not found"); + } + if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { + _sapp_fail("GLX: Failed to query GLX version"); + } + if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { + _sapp_fail("GLX: GLX version 1.3 is required"); + } + const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); + if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { + _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); + _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; + } + if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { + _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); + _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; + } + _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); + if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { + _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); + _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; + } + _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); +} + +_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { + int value; + _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); + return value; +} + +_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { + GLXFBConfig* native_configs; + _sapp_gl_fbconfig* usable_configs; + const _sapp_gl_fbconfig* closest; + int i, native_count, usable_count; + const char* vendor; + bool trust_window_bit = true; + + /* HACK: This is a (hopefully temporary) workaround for Chromium + (VirtualBox GL) not setting the window bit on any GLXFBConfigs + */ + vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); + if (vendor && strcmp(vendor, "Chromium") == 0) { + trust_window_bit = false; + } + + native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); + if (!native_configs || !native_count) { + _sapp_fail("GLX: No GLXFBConfigs returned"); + } + + usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); + usable_count = 0; + for (i = 0; i < native_count; i++) { + const GLXFBConfig n = native_configs[i]; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + + /* Only consider RGBA GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { + continue; + } + /* Only consider window GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { + if (trust_window_bit) { + continue; + } + } + u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); + u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); + u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); + u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); + u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); + u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); + if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { + u->doublebuffer = true; + } + if (_sapp.glx.ARB_multisample) { + u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); + } + u->handle = (uintptr_t) n; + usable_count++; + } + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + GLXFBConfig result = 0; + if (closest) { + result = (GLXFBConfig) closest->handle; + } + XFree(native_configs); + SOKOL_FREE(usable_configs); + return result; +} + +_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native) { + _sapp_fail("GLX: Failed to find a suitable GLXFBConfig"); + } + XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); + if (!result) { + _sapp_fail("GLX: Failed to retrieve Visual for GLXFBConfig"); + } + *visual = result->visual; + *depth = result->depth; + XFree(result); +} + +_SOKOL_PRIVATE void _sapp_glx_create_context(void) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native){ + _sapp_fail("GLX: Failed to find a suitable GLXFBConfig (2)"); + } + if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { + _sapp_fail("GLX: ARB_create_context and ARB_create_context_profile required"); + } + _sapp_x11_grab_error_handler(); + const int attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + 0, 0 + }; + _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); + if (!_sapp.glx.ctx) { + _sapp_fail("GLX: failed to create GL context"); + } + _sapp_x11_release_error_handler(); + _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); + if (!_sapp.glx.window) { + _sapp_fail("GLX: failed to create window"); + } +} + +_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { + if (_sapp.glx.window) { + _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); + _sapp.glx.window = 0; + } + if (_sapp.glx.ctx) { + _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); + _sapp.glx.ctx = 0; + } +} + +_SOKOL_PRIVATE void _sapp_glx_make_current(void) { + _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); +} + +_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { + _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); +} + +_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { + _sapp_glx_make_current(); + if (_sapp.glx.EXT_swap_control) { + _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); + } + else if (_sapp.glx.MESA_swap_control) { + _sapp.glx.SwapIntervalMESA(interval); + } +} + +_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { + XEvent event; + memset(&event, 0, sizeof(event)); + + event.type = ClientMessage; + event.xclient.window = _sapp.x11.window; + event.xclient.format = 32; + event.xclient.message_type = type; + event.xclient.data.l[0] = a; + event.xclient.data.l[1] = b; + event.xclient.data.l[2] = c; + event.xclient.data.l[3] = d; + event.xclient.data.l[4] = e; + + XSendEvent(_sapp.x11.display, _sapp.x11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); +} + +_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { + XWindowAttributes attribs; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); + _sapp.window_width = attribs.width; + _sapp.window_height = attribs.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; +} + +_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { + /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ + if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { + if (enable) { + const int _NET_WM_STATE_ADD = 1; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_ADD, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + else { + const int _NET_WM_STATE_REMOVE = 0; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_REMOVE, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { + SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); + const int w = 16; + const int h = 16; + XcursorImage* img = XcursorImageCreate(w, h); + SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); + img->xhot = 0; + img->yhot = 0; + const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); + memset(img->pixels, 0, num_bytes); + _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { + _sapp.fullscreen = !_sapp.fullscreen; + _sapp_x11_set_fullscreen(_sapp.fullscreen); + _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_show_mouse(bool show) { + if (show) { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } + else { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); + } +} + +_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + if (_sapp.mouse.locked) { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISetMask(mask, XI_RawMotion); + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XGrabPointer(_sapp.x11.display, // display + _sapp.x11.window, // grab_window + True, // owner_events + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask + GrabModeAsync, // pointer_mode + GrabModeAsync, // keyboard_mode + _sapp.x11.window, // confine_to + _sapp.x11.hidden_cursor, // cursor + CurrentTime); // time + } + else { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[] = { 0 }; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); + XUngrabPointer(_sapp.x11.display, CurrentTime); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { + Xutf8SetWMProperties(_sapp.x11.display, + _sapp.x11.window, + _sapp.window_title, _sapp.window_title, + NULL, 0, NULL, NULL, NULL); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + 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; + memset(&wa, 0, sizeof(wa)); + const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; + wa.colormap = _sapp.x11.colormap; + wa.border_pixel = 0; + wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | + ExposureMask | FocusChangeMask | VisibilityChangeMask | + EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + _sapp_x11_grab_error_handler(); + _sapp.x11.window = XCreateWindow(_sapp.x11.display, + _sapp.x11.root, + 0, 0, + (uint32_t)_sapp.window_width, + (uint32_t)_sapp.window_height, + 0, /* border width */ + depth, /* color depth */ + InputOutput, + visual, + wamask, + &wa); + _sapp_x11_release_error_handler(); + if (!_sapp.x11.window) { + _sapp_fail("X11: Failed to create window"); + } + Atom protocols[] = { + _sapp.x11.WM_DELETE_WINDOW + }; + XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); + + XSizeHints* hints = XAllocSizeHints(); + hints->flags |= PWinGravity; + hints->win_gravity = StaticGravity; + XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); + XFree(hints); + + /* announce support for drag'n'drop */ + if (_sapp.drop.enabled) { + const Atom version = _SAPP_X11_XDND_VERSION; + XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); + } + + _sapp_x11_update_window_title(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { + if (_sapp.x11.window) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XDestroyWindow(_sapp.x11.display, _sapp.x11.window); + _sapp.x11.window = 0; + } + if (_sapp.x11.colormap) { + XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); + _sapp.x11.colormap = 0; + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { + XWindowAttributes wa; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); + return wa.map_state == IsViewable; +} + +_SOKOL_PRIVATE void _sapp_x11_show_window(void) { + if (!_sapp_x11_window_visible()) { + XMapWindow(_sapp.x11.display, _sapp.x11.window); + XRaiseWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + XGetWindowProperty(_sapp.x11.display, + window, + property, + 0, + LONG_MAX, + False, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + return itemCount; +} + +_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { + int result = WithdrawnState; + struct { + CARD32 state; + Window icon; + } *state = NULL; + + if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { + result = (int)state->state; + } + if (state) { + XFree(state); + } + return result; +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_mod(uint32_t x11_mods) { + uint32_t mods = 0; + if (x11_mods & ShiftMask) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (x11_mods & ControlMask) { + mods |= SAPP_MODIFIER_CTRL; + } + if (x11_mods & Mod1Mask) { + mods |= SAPP_MODIFIER_ALT; + } + if (x11_mods & Mod4Mask) { + mods |= SAPP_MODIFIER_SUPER; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { + switch (event->xbutton.button) { + case Button1: return SAPP_MOUSEBUTTON_LEFT; + case Button2: return SAPP_MOUSEBUTTON_MIDDLE; + case Button3: return SAPP_MOUSEBUTTON_RIGHT; + default: return SAPP_MOUSEBUTTON_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = mods; + _sapp.event.scroll_x = x; + _sapp.event.scroll_y = y; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = chr; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { + int dummy; + KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); + SOKOL_ASSERT(keysyms); + KeySym keysym = keysyms[0]; + XFree(keysyms); + switch (keysym) { + case XK_Escape: return SAPP_KEYCODE_ESCAPE; + case XK_Tab: return SAPP_KEYCODE_TAB; + case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; + case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; + case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; + case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; + case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ + case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ + case XK_Meta_R: + case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; + case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; + case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; + case XK_Menu: return SAPP_KEYCODE_MENU; + case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; + case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; + case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; + case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; + case XK_Pause: return SAPP_KEYCODE_PAUSE; + case XK_Delete: return SAPP_KEYCODE_DELETE; + case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; + case XK_Return: return SAPP_KEYCODE_ENTER; + case XK_Home: return SAPP_KEYCODE_HOME; + case XK_End: return SAPP_KEYCODE_END; + case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; + case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; + case XK_Insert: return SAPP_KEYCODE_INSERT; + case XK_Left: return SAPP_KEYCODE_LEFT; + case XK_Right: return SAPP_KEYCODE_RIGHT; + case XK_Down: return SAPP_KEYCODE_DOWN; + case XK_Up: return SAPP_KEYCODE_UP; + case XK_F1: return SAPP_KEYCODE_F1; + case XK_F2: return SAPP_KEYCODE_F2; + case XK_F3: return SAPP_KEYCODE_F3; + case XK_F4: return SAPP_KEYCODE_F4; + case XK_F5: return SAPP_KEYCODE_F5; + case XK_F6: return SAPP_KEYCODE_F6; + case XK_F7: return SAPP_KEYCODE_F7; + case XK_F8: return SAPP_KEYCODE_F8; + case XK_F9: return SAPP_KEYCODE_F9; + case XK_F10: return SAPP_KEYCODE_F10; + case XK_F11: return SAPP_KEYCODE_F11; + case XK_F12: return SAPP_KEYCODE_F12; + case XK_F13: return SAPP_KEYCODE_F13; + case XK_F14: return SAPP_KEYCODE_F14; + case XK_F15: return SAPP_KEYCODE_F15; + case XK_F16: return SAPP_KEYCODE_F16; + case XK_F17: return SAPP_KEYCODE_F17; + case XK_F18: return SAPP_KEYCODE_F18; + case XK_F19: return SAPP_KEYCODE_F19; + case XK_F20: return SAPP_KEYCODE_F20; + case XK_F21: return SAPP_KEYCODE_F21; + case XK_F22: return SAPP_KEYCODE_F22; + case XK_F23: return SAPP_KEYCODE_F23; + case XK_F24: return SAPP_KEYCODE_F24; + case XK_F25: return SAPP_KEYCODE_F25; + + case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; + case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; + case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; + case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; + + case XK_KP_Insert: return SAPP_KEYCODE_KP_0; + case XK_KP_End: return SAPP_KEYCODE_KP_1; + case XK_KP_Down: return SAPP_KEYCODE_KP_2; + case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; + case XK_KP_Left: return SAPP_KEYCODE_KP_4; + case XK_KP_Begin: return SAPP_KEYCODE_KP_5; + case XK_KP_Right: return SAPP_KEYCODE_KP_6; + case XK_KP_Home: return SAPP_KEYCODE_KP_7; + case XK_KP_Up: return SAPP_KEYCODE_KP_8; + case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; + case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + + case XK_a: return SAPP_KEYCODE_A; + case XK_b: return SAPP_KEYCODE_B; + case XK_c: return SAPP_KEYCODE_C; + case XK_d: return SAPP_KEYCODE_D; + case XK_e: return SAPP_KEYCODE_E; + case XK_f: return SAPP_KEYCODE_F; + case XK_g: return SAPP_KEYCODE_G; + case XK_h: return SAPP_KEYCODE_H; + case XK_i: return SAPP_KEYCODE_I; + case XK_j: return SAPP_KEYCODE_J; + case XK_k: return SAPP_KEYCODE_K; + case XK_l: return SAPP_KEYCODE_L; + case XK_m: return SAPP_KEYCODE_M; + case XK_n: return SAPP_KEYCODE_N; + case XK_o: return SAPP_KEYCODE_O; + case XK_p: return SAPP_KEYCODE_P; + case XK_q: return SAPP_KEYCODE_Q; + case XK_r: return SAPP_KEYCODE_R; + case XK_s: return SAPP_KEYCODE_S; + case XK_t: return SAPP_KEYCODE_T; + case XK_u: return SAPP_KEYCODE_U; + case XK_v: return SAPP_KEYCODE_V; + case XK_w: return SAPP_KEYCODE_W; + case XK_x: return SAPP_KEYCODE_X; + case XK_y: return SAPP_KEYCODE_Y; + case XK_z: return SAPP_KEYCODE_Z; + case XK_1: return SAPP_KEYCODE_1; + case XK_2: return SAPP_KEYCODE_2; + case XK_3: return SAPP_KEYCODE_3; + case XK_4: return SAPP_KEYCODE_4; + case XK_5: return SAPP_KEYCODE_5; + case XK_6: return SAPP_KEYCODE_6; + case XK_7: return SAPP_KEYCODE_7; + case XK_8: return SAPP_KEYCODE_8; + case XK_9: return SAPP_KEYCODE_9; + case XK_0: return SAPP_KEYCODE_0; + case XK_space: return SAPP_KEYCODE_SPACE; + case XK_minus: return SAPP_KEYCODE_MINUS; + case XK_equal: return SAPP_KEYCODE_EQUAL; + case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; + case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; + case XK_backslash: return SAPP_KEYCODE_BACKSLASH; + case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; + case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; + case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; + case XK_comma: return SAPP_KEYCODE_COMMA; + case XK_period: return SAPP_KEYCODE_PERIOD; + case XK_slash: return SAPP_KEYCODE_SLASH; + case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ + default: return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { + int min = 0; + int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; + int mid; + + /* First check for Latin-1 characters (1:1 mapping) */ + if ((keysym >= 0x0020 && keysym <= 0x007e) || + (keysym >= 0x00a0 && keysym <= 0x00ff)) + { + return keysym; + } + + /* Also check for directly encoded 24-bit UCS characters */ + if ((keysym & 0xff000000) == 0x01000000) { + return keysym & 0x00ffffff; + } + + /* Binary search in table */ + while (max >= min) { + mid = (min + max) / 2; + if (_sapp_x11_keysymtab[mid].keysym < keysym) { + min = mid + 1; + } + else if (_sapp_x11_keysymtab[mid].keysym > keysym) { + max = mid - 1; + } + else { + return _sapp_x11_keysymtab[mid].ucs; + } + } + + /* No matching Unicode value found */ + return -1; +} + +_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { + SOKOL_ASSERT(src); + SOKOL_ASSERT(_sapp.drop.buffer); + + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + + /* + src is (potentially percent-encoded) string made of one or multiple paths + separated by \r\n, each path starting with 'file://' + */ + bool err = false; + int src_count = 0; + char src_chr = 0; + char* dst_ptr = _sapp.drop.buffer; + const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 + while (0 != (src_chr = *src++)) { + src_count++; + char dst_chr = 0; + /* check leading 'file://' */ + if (src_count <= 7) { + if (((src_count == 1) && (src_chr != 'f')) || + ((src_count == 2) && (src_chr != 'i')) || + ((src_count == 3) && (src_chr != 'l')) || + ((src_count == 4) && (src_chr != 'e')) || + ((src_count == 5) && (src_chr != ':')) || + ((src_count == 6) && (src_chr != '/')) || + ((src_count == 7) && (src_chr != '/'))) + { + SOKOL_LOG("sokol_app.h: dropped file URI doesn't start with file://"); + err = true; + break; + } + } + else if (src_chr == '\r') { + // skip + } + else if (src_chr == '\n') { + src_chr = 0; + src_count = 0; + _sapp.drop.num_files++; + // too many files is not an error + if (_sapp.drop.num_files >= _sapp.drop.max_files) { + break; + } + dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; + dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); + } + else if ((src_chr == '%') && src[0] && src[1]) { + // a percent-encoded byte (most like UTF-8 multibyte sequence) + const char digits[3] = { src[0], src[1], 0 }; + src += 2; + dst_chr = (char) strtol(digits, 0, 16); + } + else { + dst_chr = src_chr; + } + if (dst_chr) { + // dst_end_ptr already has adjustment for terminating zero + if (dst_ptr < dst_end_ptr) { + *dst_ptr++ = dst_chr; + } + else { + SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)"); + err = true; + break; + } + } + } + if (err) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + return false; + } + else { + return true; + } +} + +// XLib manual says keycodes are in the range [8, 255] inclusive. +// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html +static bool _sapp_x11_keycodes[256]; + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { + Bool filtered = XFilterEvent(event, None); + switch (event->type) { + case GenericEvent: + if (_sapp.mouse.locked && _sapp.x11.xi.available) { + if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { + if (XGetEventData(_sapp.x11.display, &event->xcookie)) { + if (event->xcookie.evtype == XI_RawMotion) { + XIRawEvent* re = (XIRawEvent*) event->xcookie.data; + if (re->valuators.mask_len) { + const double* values = re->raw_values; + if (XIMaskIsSet(re->valuators.mask, 0)) { + _sapp.mouse.dx = (float) *values; + values++; + } + 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)); + } + } + XFreeEventData(_sapp.x11.display, &event->xcookie); + } + } + } + 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); + } + break; + case KeyPress: + { + int keycode = (int)event->xkey.keycode; + 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); + if (key != SAPP_KEYCODE_INVALID) { + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); + } + KeySym keysym; + XLookupString(&event->xkey, NULL, 0, &keysym, NULL); + int32_t chr = _sapp_x11_keysym_to_unicode(keysym); + if (chr > 0) { + _sapp_x11_char_event((uint32_t)chr, repeat, mods); + } + } + break; + case KeyRelease: + { + int keycode = (int)event->xkey.keycode; + 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); + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); + } + } + break; + case ButtonPress: + { + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + const uint32_t mods = _sapp_x11_mod(event->xbutton.state); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); + _sapp.x11.mouse_buttons |= (1 << btn); + } + else { + /* might be a scroll event */ + switch (event->xbutton.button) { + case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; + case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; + case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; + case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; + } + } + } + break; + case ButtonRelease: + { + 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)); + _sapp.x11.mouse_buttons &= ~(1 << btn); + } + } + break; + 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)); + } + 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)); + } + break; + case MotionNotify: + if (!_sapp.mouse.locked) { + const float new_x = (float) event->xmotion.x; + const float new_y = (float) event->xmotion.y; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _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)); + } + break; + case ConfigureNotify: + if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { + _sapp.window_width = event->xconfigure.width; + _sapp.window_height = event->xconfigure.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); + } + break; + case PropertyNotify: + if (event->xproperty.state == PropertyNewValue) { + if (event->xproperty.atom == _sapp.x11.WM_STATE) { + const int state = _sapp_x11_get_window_state(); + if (state != _sapp.x11.window_state) { + _sapp.x11.window_state = state; + if (state == IconicState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else if (state == NormalState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + } + break; + case ClientMessage: + if (filtered) { + return; + } + if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { + const Atom protocol = (Atom)event->xclient.data.l[0]; + if (protocol == _sapp.x11.WM_DELETE_WINDOW) { + _sapp.quit_requested = true; + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { + const bool is_list = 0 != (event->xclient.data.l[1] & 1); + _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; + _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; + _sapp.x11.xdnd.format = None; + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + uint32_t count = 0; + Atom* formats = 0; + if (is_list) { + count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); + } + else { + count = 3; + formats = (Atom*) event->xclient.data.l + 2; + } + for (uint32_t i = 0; i < count; i++) { + if (formats[i] == _sapp.x11.xdnd.text_uri_list) { + _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; + break; + } + } + if (is_list && formats) { + XFree(formats); + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + Time time = CurrentTime; + if (_sapp.x11.xdnd.format) { + if (_sapp.x11.xdnd.version >= 1) { + time = (Time)event->xclient.data.l[2]; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.xdnd.format, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.window, + time); + } + else if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + memset(&reply, 0, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.window; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = 0; // drag was rejected + reply.xclient.data.l[2] = None; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { + /* drag operation has moved over the window + FIXME: we could track the mouse position here, but + this isn't implemented on other platforms either so far + */ + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + XEvent reply; + memset(&reply, 0, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + if (_sapp.x11.xdnd.format) { + /* reply that we are ready to copy the dragged data */ + reply.xclient.data.l[1] = 1; // accept with no rectangle + if (_sapp.x11.xdnd.version >= 2) { + reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; + } + } + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + break; + case SelectionNotify: + if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { + char* data = 0; + uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + if (_sapp.drop.enabled && result) { + if (_sapp_x11_parse_dropped_files_list(data)) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + } + if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + memset(&reply, 0, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.window; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = result; + reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } + break; + case DestroyNotify: + break; + } +} + +_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { + /* The following lines are here to trigger a linker error instead of an + obscure runtime error if the user has forgotten to add -pthread to + the compiler or linker options. They have no other purpose. + */ + pthread_attr_t pthread_attr; + pthread_attr_init(&pthread_attr); + pthread_attr_destroy(&pthread_attr); + + _sapp_init_state(desc); + _sapp.x11.window_state = NormalState; + + XInitThreads(); + XrmInitialize(); + _sapp.x11.display = XOpenDisplay(NULL); + if (!_sapp.x11.display) { + _sapp_fail("XOpenDisplay() failed!\n"); + } + _sapp.x11.screen = DefaultScreen(_sapp.x11.display); + _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); + XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); + _sapp_x11_query_system_dpi(); + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_hidden_cursor(); + _sapp_glx_init(); + Visual* visual = 0; + int depth = 0; + _sapp_glx_choose_visual(&visual, &depth); + _sapp_x11_create_window(visual, depth); + _sapp_glx_create_context(); + _sapp.valid = true; + _sapp_x11_show_window(); + if (_sapp.fullscreen) { + _sapp_x11_set_fullscreen(true); + } + _sapp_x11_query_window_size(); + _sapp_glx_swapinterval(_sapp.swap_interval); + XFlush(_sapp.x11.display); + while (!_sapp.quit_ordered) { + _sapp_glx_make_current(); + int count = XPending(_sapp.x11.display); + while (count--) { + XEvent event; + XNextEvent(_sapp.x11.display, &event); + _sapp_x11_process_event(&event); + } + _sapp_frame(); + _sapp_glx_swap_buffers(); + XFlush(_sapp.x11.display); + /* handle quit-requested, either from window or from sapp_request_quit() */ + if (_sapp.quit_requested && !_sapp.quit_ordered) { + /* give user code a chance to intervene */ + _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + } + _sapp_call_cleanup(); + _sapp_glx_destroy_context(); + _sapp_x11_destroy_window(); + XCloseDisplay(_sapp.x11.display); + _sapp_discard_state(); +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_linux_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_LINUX */ + +/*== PUBLIC API FUNCTIONS ====================================================*/ +#if defined(SOKOL_NO_ENTRY) +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + #if defined(_SAPP_MACOS) + _sapp_macos_run(desc); + #elif defined(_SAPP_IOS) + _sapp_ios_run(desc); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_run(desc); + #elif defined(_SAPP_WIN32) + _sapp_win32_run(desc); + #elif defined(_SAPP_UWP) + _sapp_uwp_run(desc); + #elif defined(_SAPP_LINUX) + _sapp_linux_run(desc); + #else + // calling sapp_run() directly is not supported on Android) + _sapp_fail("sapp_run() not supported on this platform!"); + #endif +} + +/* this is just a stub so the linker doesn't complain */ +sapp_desc sokol_main(int argc, char* argv[]) { + _SOKOL_UNUSED(argc); + _SOKOL_UNUSED(argv); + sapp_desc desc; + memset(&desc, 0, sizeof(desc)); + return desc; +} +#else +/* likewise, in normal mode, sapp_run() is just an empty stub */ +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + _SOKOL_UNUSED(desc); +} +#endif + +SOKOL_API_IMPL bool sapp_isvalid(void) { + return _sapp.valid; +} + +SOKOL_API_IMPL void* sapp_userdata(void) { + return _sapp.desc.user_data; +} + +SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { + return _sapp.desc; +} + +SOKOL_API_IMPL uint64_t sapp_frame_count(void) { + return _sapp.frame_count; +} + +SOKOL_API_IMPL int sapp_width(void) { + return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; +} + +SOKOL_API_IMPL float sapp_widthf(void) { + return (float)sapp_width(); +} + +SOKOL_API_IMPL int sapp_height(void) { + return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; +} + +SOKOL_API_IMPL float sapp_heightf(void) { + return (float)sapp_height(); +} + +SOKOL_API_IMPL int sapp_color_format(void) { + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + switch (_sapp.emsc.wgpu.render_format) { + case WGPUTextureFormat_RGBA8Unorm: + return _SAPP_PIXELFORMAT_RGBA8; + case WGPUTextureFormat_BGRA8Unorm: + return _SAPP_PIXELFORMAT_BGRA8; + default: + SOKOL_UNREACHABLE; + return 0; + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + return _SAPP_PIXELFORMAT_BGRA8; + #else + return _SAPP_PIXELFORMAT_RGBA8; + #endif +} + +SOKOL_API_IMPL int sapp_depth_format(void) { + return _SAPP_PIXELFORMAT_DEPTH_STENCIL; +} + +SOKOL_API_IMPL int sapp_sample_count(void) { + return _sapp.sample_count; +} + +SOKOL_API_IMPL bool sapp_high_dpi(void) { + return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); +} + +SOKOL_API_IMPL float sapp_dpi_scale(void) { + return _sapp.dpi_scale; +} + +SOKOL_API_IMPL bool sapp_gles2(void) { + return _sapp.gles2_fallback; +} + +SOKOL_API_IMPL void sapp_show_keyboard(bool show) { + #if defined(_SAPP_IOS) + _sapp_ios_show_keyboard(show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_show_keyboard(show); + #elif defined(_SAPP_ANDROID) + _sapp_android_show_keyboard(show); + #else + _SOKOL_UNUSED(show); + #endif +} + +SOKOL_API_IMPL bool sapp_keyboard_shown(void) { + return _sapp.onscreen_keyboard_shown; +} + +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void) { + return _sapp.fullscreen; +} + +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void) { + #if defined(_SAPP_MACOS) + _sapp_macos_toggle_fullscreen(); + #elif defined(_SAPP_WIN32) + _sapp_win32_toggle_fullscreen(); + #elif defined(_SAPP_UWP) + _sapp_uwp_toggle_fullscreen(); + #elif defined(_SAPP_LINUX) + _sapp_x11_toggle_fullscreen(); + #endif +} + +/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ +SOKOL_API_IMPL void sapp_show_mouse(bool show) { + if (_sapp.mouse.shown != show) { + #if defined(_SAPP_MACOS) + _sapp_macos_show_mouse(show); + #elif defined(_SAPP_WIN32) + _sapp_win32_show_mouse(show); + #elif defined(_SAPP_LINUX) + _sapp_x11_show_mouse(show); + #elif defined(_SAPP_UWP) + _sapp_uwp_show_mouse(show); + #endif + _sapp.mouse.shown = show; + } +} + +SOKOL_API_IMPL bool sapp_mouse_shown(void) { + return _sapp.mouse.shown; +} + +SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { + #if defined(_SAPP_MACOS) + _sapp_macos_lock_mouse(lock); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_lock_mouse(lock); + #elif defined(_SAPP_WIN32) + _sapp_win32_lock_mouse(lock); + #elif defined(_SAPP_LINUX) + _sapp_x11_lock_mouse(lock); + #else + _sapp.mouse.locked = lock; + #endif +} + +SOKOL_API_IMPL bool sapp_mouse_locked(void) { + return _sapp.mouse.locked; +} + +SOKOL_API_IMPL void sapp_request_quit(void) { + _sapp.quit_requested = true; +} + +SOKOL_API_IMPL void sapp_cancel_quit(void) { + _sapp.quit_requested = false; +} + +SOKOL_API_IMPL void sapp_quit(void) { + _sapp.quit_ordered = true; +} + +SOKOL_API_IMPL void sapp_consume_event(void) { + _sapp.event_consumed = true; +} + +/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ +SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { + SOKOL_ASSERT(_sapp.clipboard.enabled); + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(str); + #if defined(_SAPP_MACOS) + _sapp_macos_set_clipboard_string(str); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_clipboard_string(str); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_clipboard_string(str); + #else + /* not implemented */ + #endif + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +} + +SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled); + if (!_sapp.clipboard.enabled) { + return ""; + } + #if defined(_SAPP_MACOS) + return _sapp_macos_get_clipboard_string(); + #elif defined(_SAPP_EMSCRIPTEN) + return _sapp.clipboard.buffer; + #elif defined(_SAPP_WIN32) + return _sapp_win32_get_clipboard_string(); + #else + /* not implemented */ + return _sapp.clipboard.buffer; + #endif +} + +SOKOL_API_IMPL void sapp_set_window_title(const char* title) { + SOKOL_ASSERT(title); + _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); + #if defined(_SAPP_MACOS) + _sapp_macos_update_window_title(); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_window_title(); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_window_title(); + #endif +} + +SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { + SOKOL_ASSERT(_sapp.drop.enabled); + return _sapp.drop.num_files; +} + +SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + SOKOL_ASSERT(_sapp.drop.buffer); + if (!_sapp.drop.enabled) { + return ""; + } + if ((index < 0) || (index >= _sapp.drop.max_files)) { + return ""; + } + return (const char*) _sapp_dropped_file_path_ptr(index); +} + +SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + #if defined(_SAPP_EMSCRIPTEN) + if (!_sapp.drop.enabled) { + return 0; + } + return sapp_js_dropped_file_size(index); + #else + (void)index; + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT(request); + SOKOL_ASSERT(request->callback); + SOKOL_ASSERT(request->buffer_ptr); + SOKOL_ASSERT(request->buffer_size > 0); + #if defined(_SAPP_EMSCRIPTEN) + const int index = request->dropped_file_index; + sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; + if ((index < 0) || (index >= _sapp.drop.num_files)) { + error_code = SAPP_HTML5_FETCH_ERROR_OTHER; + } + if (sapp_html5_get_dropped_file_size(index) > request->buffer_size) { + error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; + } + if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { + _sapp_emsc_invoke_fetch_cb(index, + false, // success + (int)error_code, + request->callback, + 0, // fetched_size + request->buffer_ptr, + request->buffer_size, + request->user_data); + } + else { + sapp_js_fetch_dropped_file(index, + request->callback, + request->buffer_ptr, + request->buffer_size, + request->user_data); + } + #else + (void)request; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.mtl_device; + #else + const void* obj = (__bridge const void*) _sapp.ios.mtl_device; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_macos_get_window(void) { + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_ios_get_window(void) { + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} +SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + + +SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device_context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + if (_sapp.d3d11.msaa_rtv) { + return _sapp.d3d11.msaa_rtv; + } + else { + return _sapp.d3d11.rtv; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.dsv; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_WIN32) + return _sapp.win32.hwnd; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.emsc.wgpu.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + return (const void*) _sapp.emsc.wgpu.msaa_view; + } + else { + return (const void*) _sapp.emsc.wgpu.swapchain_view; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + return (const void*) _sapp.emsc.wgpu.swapchain_view; + } + else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.emsc.wgpu.depth_stencil_view; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return (void*)_sapp.android.activity; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { + _sapp.html5_ask_leave_site = ask; +} + +#endif /* SOKOL_APP_IMPL */ diff --git a/src/sokol/sokol_app.h.orig b/src/sokol/sokol_app.h.orig new file mode 100644 index 000000000..4b3d62cc3 --- /dev/null +++ b/src/sokol/sokol_app.h.orig @@ -0,0 +1,11529 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) +#define SOKOL_APP_IMPL +#endif +#ifndef SOKOL_APP_INCLUDED +/* + sokol_app.h -- cross-platform application wrapper + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_APP_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the 3D-API + which should be initialized by sokol_app.h (this must also match + the backend selected for sokol_gfx.h if both are used in the same + project): + + #define SOKOL_GLCORE33 + #define SOKOL_GLES2 + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain + SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function + SOKOL_APP_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_APP_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + If sokol_app.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + On Linux, SOKOL_GLCORE33 can use either GLX or EGL. + GLX is default, set SOKOL_FORCE_EGL to override. + + For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp + + 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. + + Link with the following system libraries: + + - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit + - on macOS with GL: Cocoa, QuartzCore, OpenGL + - on iOS with Metal: Foundation, UIKit, Metal, MetalKit + - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit + - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) + - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Android: GLESv3, EGL, log, android + - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib + - 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 + + 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 + + On macOS and iOS, the implementation must be compiled as Objective-C. + + FEATURE OVERVIEW + ================ + sokol_app.h provides a minimalistic cross-platform API which + implements the 'application-wrapper' parts of a 3D application: + + - a common application entry function + - creates a window and 3D-API context/device with a 'default framebuffer' + - makes the rendered frame visible + - provides keyboard-, mouse- and low-level touch-events + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android + - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 3.x | YES | YES | YES | --- | --- | --- + gles2/webgl | --- | --- | YES(2)| YES | YES | YES + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES + RESIZED | YES | YES | YES | YES | YES | YES + ICONIFIED | YES | YES | YES | --- | --- | --- + RESTORED | YES | YES | YES | --- | --- | --- + FOCUSED | YES | YES | YES | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | YES + SUSPENDED | --- | --- | --- | YES | YES | TODO + RESUMED | --- | --- | --- | YES | YES | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | YES + IME | TODO | TODO? | TODO | ??? | TODO | ??? + key repeat flag | YES | YES | YES | --- | --- | YES + windowed | YES | YES | YES | --- | --- | YES + fullscreen | YES | YES | YES | YES | YES | --- + mouse hide | YES | YES | YES | --- | --- | YES + mouse lock | YES | YES | YES | --- | --- | YES + set cursor type | YES | YES | YES | --- | --- | YES + screen keyboard | --- | --- | --- | YES | TODO | YES + swap interval | YES | YES | YES | YES | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES + clipboard | YES | YES | TODO | --- | --- | YES + MSAA | YES | YES | YES | YES | YES | YES + drag'n'drop | YES | YES | YES | --- | --- | YES + window icon | YES | YES(1)| YES | --- | --- | YES + + (1) macOS has no regular window icons, instead the dock icon is changed + (2) supported with EGL only (not GLX) + + STEP BY STEP + ============ + --- Add a sokol_main() function to your code which returns a sapp_desc structure + with initialization parameters and callback function pointers. This + function is called very early, usually at the start of the + platform's entry function (e.g. main or WinMain). You should do as + little as possible here, since the rest of your code might be called + from another thread (this depends on the platform): + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + .width = 640, + .height = 480, + .init_cb = my_init_func, + .frame_cb = my_frame_func, + .cleanup_cb = my_cleanup_func, + .event_cb = my_event_func, + ... + }; + } + + To get any logging output in case of errors you need to provide a log + callback. The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + There are many more setup parameters, but these are the most important. + For a complete list search for the sapp_desc structure declaration + below. + + DO NOT call any sokol-app function from inside sokol_main(), since + sokol-app will not be initialized at this point. + + The .width and .height parameters are the preferred size of the 3D + rendering canvas. The actual size may differ from this depending on + platform and other circumstances. Also the canvas size may change at + any time (for instance when the user resizes the application window, + or rotates the mobile device). You can just keep .width and .height + zero-initialized to open a default-sized window (what "default-size" + exactly means is platform-specific, but usually it's a size that covers + most of, but not all, of the display). + + All provided function callbacks will be called from the same thread, + but this may be different from the thread where sokol_main() was called. + + .init_cb (void (*)(void)) + This function is called once after the application window, + 3D rendering context and swap chain have been created. The + function takes no arguments and has no return value. + .frame_cb (void (*)(void)) + This is the per-frame callback, which is usually called 60 + times per second. This is where your application would update + most of its state and perform all rendering. + .cleanup_cb (void (*)(void)) + The cleanup callback is called once right before the application + quits. + .event_cb (void (*)(const sapp_event* event)) + The event callback is mainly for input handling, but is also + used to communicate other types of events to the application. Keep the + event_cb struct member zero-initialized if your application doesn't require + event handling. + + As you can see, those 'standard callbacks' don't have a user_data + argument, so any data that needs to be preserved between callbacks + must live in global variables. If keeping state in global variables + is not an option, there's an alternative set of callbacks with + an additional user_data pointer argument: + + .user_data (void*) + The user-data argument for the callbacks below + .init_userdata_cb (void (*)(void* user_data)) + .frame_userdata_cb (void (*)(void* user_data)) + .cleanup_userdata_cb (void (*)(void* user_data)) + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) + + The function sapp_userdata() can be used to query the user_data + pointer provided in the sapp_desc struct. + + You can also call sapp_query_desc() to get a copy of the + original sapp_desc structure. + + NOTE that there's also an alternative compile mode where sokol_app.h + doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + + --- Implement the initialization callback function (init_cb), this is called + once after the rendering surface, 3D API and swap chain have been + initialized by sokol_app. All sokol-app functions can be called + from inside the initialization callback, the most useful functions + at this point are: + + int sapp_width(void) + int sapp_height(void) + Returns the current width and height of the default framebuffer in pixels, + this may change from one frame to the next, and it may be different + from the initial size provided in the sapp_desc struct. + + float sapp_widthf(void) + float sapp_heightf(void) + These are alternatives to sapp_width() and sapp_height() which return + the default framebuffer size as float values instead of integer. This + may help to prevent casting back and forth between int and float + in more strongly typed languages than C and C++. + + double sapp_frame_duration(void) + Returns the frame duration in seconds averaged over a number of + frames to smooth out any jittering spikes. + + int sapp_color_format(void) + int sapp_depth_format(void) + The color and depth-stencil pixelformats of the default framebuffer, + as integer values which are compatible with sokol-gfx's + sg_pixel_format enum (so that they can be plugged directly in places + where sg_pixel_format is expected). Possible values are: + + 23 == SG_PIXELFORMAT_RGBA8 + 28 == SG_PIXELFORMAT_BGRA8 + 42 == SG_PIXELFORMAT_DEPTH + 43 == SG_PIXELFORMAT_DEPTH_STENCIL + + int sapp_sample_count(void) + Return the MSAA sample count of the default framebuffer. + + bool sapp_gles2(void) + Returns true if a GLES2 or WebGL context has been created. This + is useful when a GLES3/WebGL2 context was requested but is not + available so that sokol_app.h had to fallback to GLES2/WebGL. + + const void* sapp_metal_get_device(void) + const void* sapp_metal_get_renderpass_descriptor(void) + const void* sapp_metal_get_drawable(void) + If the Metal backend has been selected, these functions return pointers + to various Metal API objects required for rendering, otherwise + they return a null pointer. These void pointers are actually + Objective-C ids converted with a (ARC) __bridge cast so that + the ids can be tunnel through C code. Also note that the returned + pointers to the renderpass-descriptor and drawable may change from one + frame to the next, only the Metal device object is guaranteed to + stay the same. + + const void* sapp_macos_get_window(void) + On macOS, get the NSWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_ios_get_window(void) + On iOS, get the UIWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_win32_get_hwnd(void) + On Windows, get the window's HWND, otherwise a null pointer. The + HWND has been cast to a void pointer in order to be tunneled + through code which doesn't include Windows.h. + + const void* sapp_d3d11_get_device(void) + const void* sapp_d3d11_get_device_context(void) + const void* sapp_d3d11_get_render_target_view(void) + const void* sapp_d3d11_get_depth_stencil_view(void) + Similar to the sapp_metal_* functions, the sapp_d3d11_* functions + return pointers to D3D11 API objects required for rendering, + only if the D3D11 backend has been selected. Otherwise they + return a null pointer. Note that the returned pointers to the + render-target-view and depth-stencil-view may change from one + frame to the next! + + const void* sapp_wgpu_get_device(void) + const void* sapp_wgpu_get_render_view(void) + const void* sapp_wgpu_get_resolve_view(void) + const void* sapp_wgpu_get_depth_stencil_view(void) + These are the WebGPU-specific functions to get the WebGPU + objects and values required for rendering. If sokol_app.h + is not compiled with SOKOL_WGPU, these functions return null. + + const void* sapp_android_get_native_activity(void); + On Android, get the native activity ANativeActivity pointer, otherwise + a null pointer. + + --- Implement the frame-callback function, this function will be called + on the same thread as the init callback, but might be on a different + thread than the sokol_main() function. Note that the size of + the rendering framebuffer might have changed since the frame callback + was called last. Call the functions sapp_width() and sapp_height() + each frame to get the current size. + + --- Optionally implement the event-callback to handle input events. + sokol-app provides the following type of input events: + - a 'virtual key' was pressed down or released + - a single text character was entered (provided as UTF-32 code point) + - a mouse button was pressed down or released (left, right, middle) + - mouse-wheel or 2D scrolling events + - the mouse was moved + - the mouse has entered or left the application window boundaries + - low-level, portable multi-touch events (began, moved, ended, cancelled) + - the application window was resized, iconified or restored + - the application was suspended or restored (on mobile platforms) + - the user or application code has asked to quit the application + - a string was pasted to the system clipboard + - one or more files have been dropped onto the application window + + To explicitly 'consume' an event and prevent that the event is + forwarded for further handling to the operating system, call + sapp_consume_event() from inside the event handler (NOTE that + this behaviour is currently only implemented for some HTML5 + events, support for other platforms and event types will + be added as needed, please open a github ticket and/or provide + a PR if needed). + + NOTE: Do *not* call any 3D API rendering functions in the event + callback function, since the 3D API context may not be active when the + event callback is called (it may work on some platforms and 3D APIs, + but not others, and the exact behaviour may change between + sokol-app versions). + + --- Implement the cleanup-callback function, this is called once + after the user quits the application (see the section + "APPLICATION QUIT" for detailed information on quitting + behaviour, and how to intercept a pending quit - for instance to show a + "Really Quit?" dialog box). Note that the cleanup-callback isn't + guaranteed to be called on the web and mobile platforms. + + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + + void sapp_show_mouse(bool show) + + And to get the current shown status: + + bool sapp_mouse_shown(void) + + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + + To change the mouse cursor to one of several predefined types, call + the function: + + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + + To get the currently active mouse cursor type, call: + + sapp_mouse_cursor sapp_get_mouse_cursor(void) + + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) + ================================================ + In normal mouse mode, no mouse movement events are reported when the + mouse leaves the windows client area or hits the screen border (whether + it's one or the other depends on the platform), and the mouse move events + (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in + framebuffer pixels in the sapp_event items mouse_x and mouse_y, and + relative movement in framebuffer pixels in the sapp_event items mouse_dx + and mouse_dy. + + To get continuous mouse movement (also when the mouse leaves the window + client area or hits the screen border), activate mouse-lock mode + by calling: + + sapp_lock_mouse(true) + + When mouse lock is activated, the mouse pointer is hidden, the + reported absolute mouse position (sapp_event.mouse_x/y) appears + frozen, and the relative mouse movement in sapp_event.mouse_dx/dy + no longer has a direct relation to framebuffer pixels but instead + uses "raw mouse input" (what "raw mouse input" exactly means also + differs by platform). + + To deactivate mouse lock and return to normal mouse mode, call + + sapp_lock_mouse(false) + + And finally, to check if mouse lock is currently active, call + + if (sapp_mouse_locked()) { ... } + + On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() + functions work as expected (mouse lock is activated or deactivated + immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() + also immediately returns the new state after sapp_lock_mouse() + is called. + + On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave + differently, as dictated by the limitations of the HTML5 Pointer Lock API: + + - sapp_lock_mouse(true) can be called at any time, but it will + only take effect in a 'short-lived input event handler of a specific + type', meaning when one of the following events happens: + - SAPP_EVENTTYPE_MOUSE_DOWN + - SAPP_EVENTTYPE_MOUSE_UP + - SAPP_EVENTTYPE_MOUSE_SCROLL + - SAPP_EVENTTYPE_KEY_UP + - SAPP_EVENTTYPE_KEY_DOWN + - The mouse lock/unlock action on the web platform is asynchronous, + this means that sapp_mouse_locked() won't immediately return + the new status after calling sapp_lock_mouse(), instead the + reported status will only change when the pointer lock has actually + been activated or deactivated in the browser. + - On the web, mouse lock can be deactivated by the user at any time + by pressing the Esc key. When this happens, sokol_app.h behaves + the same as if sapp_lock_mouse(false) is called. + + For things like camera manipulation it's most straightforward to lock + and unlock the mouse right from the sokol_app.h event handler, for + instance the following code enters and leaves mouse lock when the + left mouse button is pressed and released, and then uses the relative + movement information to manipulate a camera (taken from the + cgltf-sapp.c sample in the sokol-samples repository + at https://github.com/floooh/sokol-samples): + + static void input(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(true); + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(false); + } + break; + + case SAPP_EVENTTYPE_MOUSE_MOVE: + if (sapp_mouse_locked()) { + cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); + } + break; + + default: + break; + } + } + + CLIPBOARD SUPPORT + ================= + Applications can send and receive UTF-8 encoded text data from and to the + system clipboard. By default, clipboard support is disabled and + must be enabled at startup via the following sapp_desc struct + members: + + sapp_desc.enable_clipboard - set to true to enable clipboard support + sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes + + Enabling the clipboard will dynamically allocate a clipboard buffer + for UTF-8 encoded text data of the requested size in bytes, the default + size is 8 KBytes. Strings that don't fit into the clipboard buffer + (including the terminating zero) will be silently clipped, so it's + important that you provide a big enough clipboard size for your + use case. + + To send data to the clipboard, call sapp_set_clipboard_string() with + a pointer to an UTF-8 encoded, null-terminated C-string. + + NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be + called from inside a 'short-lived event handler', and there are a few + other HTML5-specific caveats to workaround. You'll basically have to + tinker until it works in all browsers :/ (maybe the situation will + improve when all browsers agree on and implement the new + HTML5 navigator.clipboard API). + + To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED + event in your event handler function, and then call sapp_get_clipboard_string() + to obtain the pasted UTF-8 encoded text. + + NOTE that behaviour of sapp_get_clipboard_string() is slightly different + depending on platform: + + - on the HTML5 platform, the internal clipboard buffer will only be updated + right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, + and sapp_get_clipboard_string() will simply return the current content + of the clipboard buffer + - on 'native' platforms, the call to sapp_get_clipboard_string() will + update the internal clipboard buffer with the most recent data + from the system clipboard + + Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, + and then call sapp_get_clipboard_string() right in the event handler. + + The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app + as follows: + + - on macOS: when the Cmd+V key is pressed down + - on HTML5: when the browser sends a 'paste' event to the global 'window' object + - on all other platforms: when the Ctrl+V key is pressed down + + DRAG AND DROP SUPPORT + ===================== + PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 + and on the native desktop platforms (Win32, Linux and macOS) because + of security-related restrictions in the HTML5 drag'n'drop API. The + WASM/HTML5 specifics are described at the end of this documentation + section: + + Like clipboard support, drag'n'drop support must be explicitly enabled + at startup in the sapp_desc struct. + + sapp_desc sokol_main() { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + ... + }; + } + + You can also adjust the maximum number of files that are accepted + in a drop operation, and the maximum path length in bytes if needed: + + sapp_desc sokol_main() { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + .max_dropped_files = 8, // default is 1 + .max_dropped_file_path_length = 8192, // in bytes, default is 2048 + ... + }; + } + + When drag'n'drop is enabled, the event callback will be invoked with an + event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on + the application window. + + After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the + number of dropped files, and their absolute paths by calling separate + functions: + + void on_event(const sapp_event* ev) { + if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + + // the mouse position where the drop happened + float x = ev->mouse_x; + float y = ev->mouse_y; + + // get the number of files and their paths like this: + const int num_dropped_files = sapp_get_num_dropped_files(); + for (int i = 0; i < num_dropped_files; i++) { + const char* path = sapp_get_dropped_file_path(i); + ... + } + } + } + + The returned file paths are UTF-8 encoded strings. + + You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() + anywhere, also outside the event handler callback, but be aware that the + file path strings will be overwritten with the next drop operation. + + In any case, sapp_get_dropped_file_path() will never return a null pointer, + instead an empty string "" will be returned if the drag'n'drop feature + hasn't been enabled, the last drop-operation failed, or the file path index + is out of range. + + Drag'n'drop caveats: + + - if more files are dropped in a single drop-action + than sapp_desc.max_dropped_files, the additional + files will be silently ignored + - if any of the file paths is longer than + sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 + encoding) the entire drop operation will be silently ignored (this + needs some sort of error feedback in the future) + - no mouse positions are reported while the drag is in + process, this may change in the future + + Drag'n'drop on HTML5/WASM: + + The HTML5 drag'n'drop API doesn't return file paths, but instead + black-box 'file objects' which must be used to load the content + of dropped files. This is the reason why sokol_app.h adds two + HTML5-specific functions to the drag'n'drop API: + + uint32_t sapp_html5_get_dropped_file_size(int index) + Returns the size in bytes of a dropped file. + + void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) + Asynchronously loads the content of a dropped file into a + provided memory buffer (which must be big enough to hold + the file content) + + To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED + event is received: + + sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ + .dropped_file_index = 0, + .callback = fetch_cb + .buffer = { + .ptr = buf, + .size = sizeof(buf) + }, + .user_data = ... + }); + + Make sure that the memory pointed to by 'buf' stays valid until the + callback function is called! + + As result of the asynchronous loading operation (no matter if succeeded or + failed) the 'fetch_cb' function will be called: + + void fetch_cb(const sapp_html5_fetch_response* response) { + // IMPORTANT: check if the loading operation actually succeeded: + if (response->succeeded) { + // the size of the loaded file: + const size_t num_bytes = response->data.size; + // and the pointer to the data (same as 'buf' in the fetch-call): + const void* ptr = response->data.ptr; + } + else { + // on error check the error code: + switch (response->error_code) { + case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: + ... + break; + case SAPP_HTML5_FETCH_ERROR_OTHER: + ... + break; + } + } + } + + Check the droptest-sapp example for a real-world example which works + both on native platforms and the web: + + https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + + HIGH-DPI RENDERING + ================== + You can set the sapp_desc.high_dpi flag during initialization to request + a full-resolution framebuffer on HighDPI displays. The default behaviour + is sapp_desc.high_dpi=false, this means that the application will + render to a lower-resolution framebuffer on HighDPI displays and the + rendered content will be upscaled by the window system composer. + + In a HighDPI scenario, you still request the same window size during + sokol_main(), but the framebuffer sizes returned by sapp_width() + and sapp_height() will be scaled up according to the DPI scaling + ratio. + + Note that on some platforms the DPI scaling factor may change at any + time (for instance when a window is moved from a high-dpi display + to a low-dpi display). + + To query the current DPI scaling factor, call the function: + + float sapp_dpi_scale(void); + + For instance on a Retina Mac, returning the following sapp_desc + struct from sokol_main(): + + sapp_desc sokol_main() { + return (sapp_desc) { + .width = 640, + .height = 480, + .high_dpi = true, + ... + }; + } + + ...the functions the functions sapp_width(), sapp_height() + and sapp_dpi_scale() will return the following values: + + sapp_width: 1280 + sapp_height: 960 + sapp_dpi_scale: 2.0 + + If the high_dpi flag is false, or you're not running on a Retina display, + the values would be: + + sapp_width: 640 + sapp_height: 480 + sapp_dpi_scale: 1.0 + + If the window is moved from the Retina display to a low-dpi external display, + the values would change as follows: + + sapp_width: 1280 => 640 + sapp_height: 960 => 480 + sapp_dpi_scale: 2.0 => 1.0 + + Currently there is no event associated with a DPI change, but an + SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the + framebuffer size changing. + + Per-monitor DPI is currently supported on macOS and Windows. + + APPLICATION QUIT + ================ + Without special quit handling, a sokol_app.h application will quit + 'gracefully' when the user clicks the window close-button unless a + platform's application model prevents this (e.g. on web or mobile). + 'Graceful exit' means that the application-provided cleanup callback will + be called before the application quits. + + On native desktop platforms sokol_app.h provides more control over the + application-quit-process. It's possible to initiate a 'programmatic quit' + from the application code, and a quit initiated by the application user can + be intercepted (for instance to show a custom dialog box). + + This 'programmatic quit protocol' is implemented through 3 functions + and 1 event: + + - sapp_quit(): This function simply quits the application without + giving the user a chance to intervene. Usually this might + be called when the user clicks the 'Ok' button in a 'Really Quit?' + dialog box + - sapp_request_quit(): Calling sapp_request_quit() will send the + event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler + callback, giving the user code a chance to intervene and cancel the + pending quit process (for instance to show a 'Really Quit?' dialog + box). If the event handler callback does nothing, the application + will be quit as usual. To prevent this, call the function + sapp_cancel_quit() from inside the event handler. + - sapp_cancel_quit(): Cancels a pending quit request, either initiated + by the user clicking the window close button, or programmatically + by calling sapp_request_quit(). The only place where calling this + function makes sense is from inside the event handler callback when + the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. + - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user + clicks the window's close button or application code calls the + sapp_request_quit() function. The event handler callback code can handle + this event by calling sapp_cancel_quit() to cancel the quit. + If the event is ignored, the application will quit as usual. + + On the web platform, the quit behaviour differs from native platforms, + because of web-specific restrictions: + + A `programmatic quit` initiated by calling sapp_quit() or + sapp_request_quit() will work as described above: the cleanup callback is + called, platform-specific cleanup is performed (on the web + this means that JS event handlers are unregisters), and then + the request-animation-loop will be exited. However that's all. The + web page itself will continue to exist (e.g. it's not possible to + programmatically close the browser tab). + + On the web it's also not possible to run custom code when the user + closes a browser tab, so it's not possible to prevent this with a + fancy custom dialog box. + + Instead the standard "Leave Site?" dialog box can be activated (or + deactivated) with the following function: + + sapp_html5_ask_leave_site(bool ask); + + The initial state of the associated internal flag can be provided + at startup via sapp_desc.html5_ask_leave_site. + + This feature should only be used sparingly in critical situations - for + instance when the user would loose data - since popping up modal dialog + boxes is considered quite rude in the web world. Note that there's no way + to customize the content of this dialog box or run any code as a result + of the user's decision. Also note that the user must have interacted with + the site before the dialog box will appear. These are all security measures + to prevent fishing. + + The Dear ImGui HighDPI sample contains example code of how to + implement a 'Really Quit?' dialog box with Dear ImGui (native desktop + platforms only), and for showing the hardwired "Leave Site?" dialog box + when running on the web platform: + + https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + + FULLSCREEN + ========== + If the sapp_desc.fullscreen flag is true, sokol-app will try to create + a fullscreen window on platforms with a 'proper' window system + (mobile devices will always use fullscreen). The implementation details + depend on the target platform, in general sokol-app will use a + 'soft approach' which doesn't interfere too much with the platform's + window system (for instance borderless fullscreen window instead of + a 'real' fullscreen mode). Such details might change over time + as sokol-app is adapted for different needs. + + The most important effect of fullscreen mode to keep in mind is that + the requested canvas width and height will be ignored for the initial + window size, calling sapp_width() and sapp_height() will instead return + the resolution of the fullscreen canvas (however the provided size + might still be used for the non-fullscreen window, in case the user can + switch back from fullscreen- to windowed-mode). + + To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + + 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 + can display the platform's integrated onscreen keyboard for text + input. To request that the onscreen keyboard is shown, call + + sapp_show_keyboard(true); + + Likewise, to hide the keyboard call: + + sapp_show_keyboard(false); + + Note that on the web platform, the keyboard can only be shown from + inside an input handler. On such platforms, sapp_show_keyboard() + will only work as expected when it is called from inside the + sokol-app event callback function. When called from other places, + an internal flag will be set, and the onscreen keyboard will be + called at the next 'legal' opportunity (when the next input event + is handled). + + OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) + ====================================================== + In its default configuration, sokol_app.h "hijacks" the platform's + standard main() function. This was done because different platforms + have different main functions which are not compatible with + C's main() (for instance WinMain on Windows has completely different + arguments). However, this "main hijacking" posed a problem for + usage scenarios like integrating sokol_app.h with other languages than + C or C++, so an alternative SOKOL_NO_ENTRY mode has been added + in which the user code provides the platform's main function: + + - define SOKOL_NO_ENTRY before including the sokol_app.h implementation + - do *not* provide a sokol_main() function + - instead provide the standard main() function of the platform + - from the main function, call the function ```sapp_run()``` which + takes a pointer to an ```sapp_desc``` structure. + - ```sapp_run()``` takes over control and calls the provided init-, frame-, + shutdown- and event-callbacks just like in the default model, it + will only return when the application quits (or not at all on some + platforms, like emscripten) + + NOTE: SOKOL_NO_ENTRY is currently not supported on Android. + + WINDOWS CONSOLE OUTPUT + ====================== + On Windows, regular windowed applications don't show any stdout/stderr text + output, which can be a bit of a hassle for printf() debugging or generally + logging text to the console. Also, console output by default uses a local + codepage setting and thus international UTF-8 encoded text is printed + as garbage. + + To help with these issues, sokol_app.h can be configured at startup + via the following Windows-specific sapp_desc flags: + + sapp_desc.win32_console_utf8 (default: false) + When set to true, the output console codepage will be switched + to UTF-8 (and restored to the original codepage on exit) + + sapp_desc.win32_console_attach (default: false) + When set to true, stdout and stderr will be attached to the + console of the parent process (if the parent process actually + has a console). This means that if the application was started + in a command line window, stdout and stderr output will be printed + to the terminal, just like a regular command line program. But if + the application is started via double-click, it will behave like + a regular UI application, and stdout/stderr will not be visible. + + sapp_desc.win32_console_create (default: false) + When set to true, a new console window will be created and + stdout/stderr will be redirected to that console window. It + doesn't matter if the application is started from the command + line or via double-click. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + // ... + .allocator = { + .alloc = my_alloc, + .free = my_free, + .user_data = ..., + } + }; + } + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_app.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sapp' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-app like this: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }; + } + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + TEMP NOTE DUMP + ============== + - onscreen keyboard support on Android requires Java :(, should we even bother? + - sapp_desc needs a bool whether to initialize depth-stencil surface + - GL context initialization needs more control (at least what GL version to initialize) + - application icon + - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy + at the latest but should do it earlier, in onStop, as an app is "killable" after onStop + on Android Honeycomb and later (it can't be done at the moment as the app may be started + again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_APP_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) +#define SOKOL_APP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_APP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) +#define SOKOL_APP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_APP_API_DECL __declspec(dllimport) +#else +#define SOKOL_APP_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +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, + SAPP_EVENTTYPE_KEY_UP, + SAPP_EVENTTYPE_CHAR, + SAPP_EVENTTYPE_MOUSE_DOWN, + SAPP_EVENTTYPE_MOUSE_UP, + SAPP_EVENTTYPE_MOUSE_SCROLL, + SAPP_EVENTTYPE_MOUSE_MOVE, + SAPP_EVENTTYPE_MOUSE_ENTER, + SAPP_EVENTTYPE_MOUSE_LEAVE, + SAPP_EVENTTYPE_TOUCHES_BEGAN, + SAPP_EVENTTYPE_TOUCHES_MOVED, + SAPP_EVENTTYPE_TOUCHES_ENDED, + SAPP_EVENTTYPE_TOUCHES_CANCELLED, + SAPP_EVENTTYPE_RESIZED, + SAPP_EVENTTYPE_ICONIFIED, + SAPP_EVENTTYPE_RESTORED, + SAPP_EVENTTYPE_FOCUSED, + SAPP_EVENTTYPE_UNFOCUSED, + SAPP_EVENTTYPE_SUSPENDED, + SAPP_EVENTTYPE_RESUMED, + SAPP_EVENTTYPE_QUIT_REQUESTED, + SAPP_EVENTTYPE_CLIPBOARD_PASTED, + SAPP_EVENTTYPE_FILES_DROPPED, + _SAPP_EVENTTYPE_NUM, + _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF +} sapp_event_type; + +/* + 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, + SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ + SAPP_KEYCODE_COMMA = 44, /* , */ + SAPP_KEYCODE_MINUS = 45, /* - */ + SAPP_KEYCODE_PERIOD = 46, /* . */ + SAPP_KEYCODE_SLASH = 47, /* / */ + SAPP_KEYCODE_0 = 48, + SAPP_KEYCODE_1 = 49, + SAPP_KEYCODE_2 = 50, + SAPP_KEYCODE_3 = 51, + SAPP_KEYCODE_4 = 52, + SAPP_KEYCODE_5 = 53, + SAPP_KEYCODE_6 = 54, + SAPP_KEYCODE_7 = 55, + SAPP_KEYCODE_8 = 56, + SAPP_KEYCODE_9 = 57, + SAPP_KEYCODE_SEMICOLON = 59, /* ; */ + SAPP_KEYCODE_EQUAL = 61, /* = */ + SAPP_KEYCODE_A = 65, + SAPP_KEYCODE_B = 66, + SAPP_KEYCODE_C = 67, + SAPP_KEYCODE_D = 68, + SAPP_KEYCODE_E = 69, + SAPP_KEYCODE_F = 70, + SAPP_KEYCODE_G = 71, + SAPP_KEYCODE_H = 72, + SAPP_KEYCODE_I = 73, + SAPP_KEYCODE_J = 74, + SAPP_KEYCODE_K = 75, + SAPP_KEYCODE_L = 76, + SAPP_KEYCODE_M = 77, + SAPP_KEYCODE_N = 78, + SAPP_KEYCODE_O = 79, + SAPP_KEYCODE_P = 80, + SAPP_KEYCODE_Q = 81, + SAPP_KEYCODE_R = 82, + SAPP_KEYCODE_S = 83, + SAPP_KEYCODE_T = 84, + SAPP_KEYCODE_U = 85, + SAPP_KEYCODE_V = 86, + SAPP_KEYCODE_W = 87, + SAPP_KEYCODE_X = 88, + SAPP_KEYCODE_Y = 89, + SAPP_KEYCODE_Z = 90, + SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ + SAPP_KEYCODE_BACKSLASH = 92, /* \ */ + SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ + SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ + SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ + SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ + SAPP_KEYCODE_ESCAPE = 256, + SAPP_KEYCODE_ENTER = 257, + SAPP_KEYCODE_TAB = 258, + SAPP_KEYCODE_BACKSPACE = 259, + SAPP_KEYCODE_INSERT = 260, + SAPP_KEYCODE_DELETE = 261, + SAPP_KEYCODE_RIGHT = 262, + SAPP_KEYCODE_LEFT = 263, + SAPP_KEYCODE_DOWN = 264, + SAPP_KEYCODE_UP = 265, + SAPP_KEYCODE_PAGE_UP = 266, + SAPP_KEYCODE_PAGE_DOWN = 267, + SAPP_KEYCODE_HOME = 268, + SAPP_KEYCODE_END = 269, + SAPP_KEYCODE_CAPS_LOCK = 280, + SAPP_KEYCODE_SCROLL_LOCK = 281, + SAPP_KEYCODE_NUM_LOCK = 282, + SAPP_KEYCODE_PRINT_SCREEN = 283, + SAPP_KEYCODE_PAUSE = 284, + SAPP_KEYCODE_F1 = 290, + SAPP_KEYCODE_F2 = 291, + SAPP_KEYCODE_F3 = 292, + SAPP_KEYCODE_F4 = 293, + SAPP_KEYCODE_F5 = 294, + SAPP_KEYCODE_F6 = 295, + SAPP_KEYCODE_F7 = 296, + SAPP_KEYCODE_F8 = 297, + SAPP_KEYCODE_F9 = 298, + SAPP_KEYCODE_F10 = 299, + SAPP_KEYCODE_F11 = 300, + SAPP_KEYCODE_F12 = 301, + SAPP_KEYCODE_F13 = 302, + SAPP_KEYCODE_F14 = 303, + SAPP_KEYCODE_F15 = 304, + SAPP_KEYCODE_F16 = 305, + SAPP_KEYCODE_F17 = 306, + SAPP_KEYCODE_F18 = 307, + SAPP_KEYCODE_F19 = 308, + SAPP_KEYCODE_F20 = 309, + SAPP_KEYCODE_F21 = 310, + SAPP_KEYCODE_F22 = 311, + SAPP_KEYCODE_F23 = 312, + SAPP_KEYCODE_F24 = 313, + SAPP_KEYCODE_F25 = 314, + SAPP_KEYCODE_KP_0 = 320, + SAPP_KEYCODE_KP_1 = 321, + SAPP_KEYCODE_KP_2 = 322, + SAPP_KEYCODE_KP_3 = 323, + SAPP_KEYCODE_KP_4 = 324, + SAPP_KEYCODE_KP_5 = 325, + SAPP_KEYCODE_KP_6 = 326, + SAPP_KEYCODE_KP_7 = 327, + SAPP_KEYCODE_KP_8 = 328, + SAPP_KEYCODE_KP_9 = 329, + SAPP_KEYCODE_KP_DECIMAL = 330, + SAPP_KEYCODE_KP_DIVIDE = 331, + SAPP_KEYCODE_KP_MULTIPLY = 332, + SAPP_KEYCODE_KP_SUBTRACT = 333, + SAPP_KEYCODE_KP_ADD = 334, + SAPP_KEYCODE_KP_ENTER = 335, + SAPP_KEYCODE_KP_EQUAL = 336, + SAPP_KEYCODE_LEFT_SHIFT = 340, + SAPP_KEYCODE_LEFT_CONTROL = 341, + SAPP_KEYCODE_LEFT_ALT = 342, + SAPP_KEYCODE_LEFT_SUPER = 343, + SAPP_KEYCODE_RIGHT_SHIFT = 344, + SAPP_KEYCODE_RIGHT_CONTROL = 345, + SAPP_KEYCODE_RIGHT_ALT = 346, + SAPP_KEYCODE_RIGHT_SUPER = 347, + SAPP_KEYCODE_MENU = 348, +} sapp_keycode; + +/* + Android specific 'tool type' enum for touch events. This lets the + application check what type of input device was used for + touch events. + + NOTE: the values must remain in sync with the corresponding + Android SDK type, so don't change those. + + See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN +*/ +typedef enum sapp_android_tooltype { + SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN + SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER + SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS + SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE +} sapp_android_tooltype; + +/* + 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; + float pos_y; + sapp_android_tooltype android_tooltype; // only valid on Android + 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, + SAPP_MOUSEBUTTON_MIDDLE = 0x2, + 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, // 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; // 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; // = 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; + +/* + sapp_allocator + + Used in sapp_desc to provide custom memory-alloc and -free functions + to sokol_app.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sapp_allocator { + void* (*alloc)(size_t size, void* user_data); + void (*free)(void* ptr, void* user_data); + void* user_data; +} sapp_allocator; + +/* + sapp_log_item + + Log items are defined via X-Macros and expanded to an enum + 'sapp_log_item', and in debug mode to corresponding + human readable error messages. +*/ +#define _SAPP_LOG_ITEMS \ + _SAPP_LOGITEM_XMACRO(OK, "Ok") \ + _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0, 3.2 and 4.1)") \ + _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ + _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED, "OpenGL 3.2 not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ + _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity sucessfully created") \ + _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ + _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ + _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ + +#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, +typedef enum sapp_log_item { + _SAPP_LOG_ITEMS +} sapp_log_item; +#undef _SAPP_LOGITEM_XMACRO + +/* + sapp_logger + + Used in sapp_desc to provide a logging function. Please be aware that + without logging function, sokol-app will be completely silent, e.g. it will + not report errors or warnings. For maximum error verbosity, compile in + debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance + the standard logging function from sokol_log.h). +*/ +typedef struct sapp_logger { + void (*func)( + const char* tag, // always "sapp" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sapp_logger; + +typedef struct sapp_desc { + 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* 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*); + + 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 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 + sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) + sapp_logger logger; // logging callback override (default: NO LOGGING!) + + /* backend-specific options */ + bool gl_force_gles2; // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available + int gl_major_version; // override GL major and minor version (the default GL version is 3.2) + int gl_minor_version; + 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 +} sapp_desc; + +/* HTML5 specific: request and response structs for + asynchronously loading dropped-file content. +*/ +typedef enum sapp_html5_fetch_error { + SAPP_HTML5_FETCH_ERROR_NO_ERROR, + SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, + SAPP_HTML5_FETCH_ERROR_OTHER, +} sapp_html5_fetch_error; + +typedef struct sapp_html5_fetch_response { + bool succeeded; // true if the loading operation has succeeded + sapp_html5_fetch_error error_code; + int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) + sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) + sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) + void* user_data; // user-provided user data pointer +} sapp_html5_fetch_response; + +typedef struct sapp_html5_fetch_request { + int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 + void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) + sapp_range buffer; // ptr/size of a memory buffer to load the data into + void* user_data; // optional userdata pointer +} sapp_html5_fetch_request; + +/* + sapp_mouse_cursor + + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +typedef enum sapp_mouse_cursor { + SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor + SAPP_MOUSECURSOR_ARROW, + SAPP_MOUSECURSOR_IBEAM, + SAPP_MOUSECURSOR_CROSSHAIR, + SAPP_MOUSECURSOR_POINTING_HAND, + SAPP_MOUSECURSOR_RESIZE_EW, + SAPP_MOUSECURSOR_RESIZE_NS, + SAPP_MOUSECURSOR_RESIZE_NWSE, + SAPP_MOUSECURSOR_RESIZE_NESW, + SAPP_MOUSECURSOR_RESIZE_ALL, + SAPP_MOUSECURSOR_NOT_ALLOWED, + _SAPP_MOUSECURSOR_NUM, +} sapp_mouse_cursor; + +/* user-provided functions */ +extern sapp_desc sokol_main(int argc, char* argv[]); + +/* returns true after sokol-app has been initialized */ +SOKOL_APP_API_DECL bool sapp_isvalid(void); +/* returns the current framebuffer width in pixels */ +SOKOL_APP_API_DECL int sapp_width(void); +/* same as sapp_width(), but returns float */ +SOKOL_APP_API_DECL float sapp_widthf(void); +/* returns the current framebuffer height in pixels */ +SOKOL_APP_API_DECL int sapp_height(void); +/* same as sapp_height(), but returns float */ +SOKOL_APP_API_DECL float sapp_heightf(void); +/* get default framebuffer color pixel format */ +SOKOL_APP_API_DECL int sapp_color_format(void); +/* get default framebuffer depth pixel format */ +SOKOL_APP_API_DECL int sapp_depth_format(void); +/* get default framebuffer sample count */ +SOKOL_APP_API_DECL int sapp_sample_count(void); +/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ +SOKOL_APP_API_DECL bool sapp_high_dpi(void); +/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ +SOKOL_APP_API_DECL float sapp_dpi_scale(void); +/* show or hide the mobile device onscreen keyboard */ +SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); +/* return true if the mobile device onscreen keyboard is currently shown */ +SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); +/* query fullscreen mode */ +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); +/* toggle fullscreen mode */ +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL void sapp_show_mouse(bool show); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL bool sapp_mouse_shown(void); +/* enable/disable mouse-pointer-lock mode */ +SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); +/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ +SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* set mouse cursor type */ +SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); +/* get current mouse cursor type */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); +/* return the userdata pointer optionally provided in sapp_desc */ +SOKOL_APP_API_DECL void* sapp_userdata(void); +/* return a copy of the sapp_desc structure */ +SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); +/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_request_quit(void); +/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ +SOKOL_APP_API_DECL void sapp_cancel_quit(void); +/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ +SOKOL_APP_API_DECL void sapp_quit(void); +/* call from inside event callback to consume the current event (don't forward to platform) */ +SOKOL_APP_API_DECL void sapp_consume_event(void); +/* get the current frame counter (for comparison with sapp_event.frame_count) */ +SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* get an averaged/smoothed frame duration in seconds */ +SOKOL_APP_API_DECL double sapp_frame_duration(void); +/* write string into clipboard */ +SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); +/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ +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 */ +SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); + +/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ +SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); + +/* EGL: get EGLDisplay object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); +/* EGL: get EGLContext object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); + +/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ +SOKOL_APP_API_DECL bool sapp_gles2(void); + +/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ +SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); +/* HTML5: get byte size of a dropped file */ +SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); +/* HTML5: asynchronously load the content of a dropped file */ +SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); + +/* Metal: get bridged pointer to Metal device object */ +SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); +/* Metal: get bridged pointer to this frame's renderpass descriptor */ +SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); +/* Metal: get bridged pointer to current drawable */ +SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); +/* macOS: get bridged pointer to macOS NSWindow */ +SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); +/* iOS: get bridged pointer to iOS UIWindow */ +SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); + +/* D3D11: get pointer to ID3D11Device object */ +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 */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); +/* Win32: get the HWND window handle */ +SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); + +/* WebGPU: get WGPUDevice handle */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); +/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); +/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); +/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + +/* Android: get native activity handle */ +SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } + +#endif + +// this WinRT specific hack is required when wWinMain is in a static library +#if defined(_MSC_VER) && defined(UNICODE) +#include +#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(linker, "/include:wWinMain") +#endif +#endif + +#endif // SOKOL_APP_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_APP_IMPL +#define SOKOL_APP_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free +#include // memset +#include // size_t +#include // roundf + +/* check if the config defines are alright */ +#if defined(__APPLE__) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #define _SAPP_APPLE (1) + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + /* MacOS */ + #define _SAPP_MACOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") + #endif + #else + /* iOS or iOS Simulator */ + #define _SAPP_IOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") + #endif + #endif +#elif defined(__EMSCRIPTEN__) + /* emscripten (asm.js or wasm) */ + #define _SAPP_EMSCRIPTEN (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU") + #endif +#elif defined(_WIN32) + /* Windows (D3D11 or GL) */ + #define _SAPP_WIN32 (1) + #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") + #endif +#elif defined(__ANDROID__) + /* Android */ + #define _SAPP_ANDROID (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) + #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2") + #endif + #if defined(SOKOL_NO_ENTRY) + #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") + #endif +#elif defined(__linux__) || defined(__unix__) + /* Linux */ + #define _SAPP_LINUX (1) + #if defined(SOKOL_GLCORE33) + #if !defined(SOKOL_FORCE_EGL) + #define _SAPP_GLX (1) + #endif + #elif !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3 or SOKOL_GLES2") + #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(_SAPP_APPLE) + #if defined(SOKOL_METAL) + #import + #import + #endif + #if defined(_SAPP_MACOS) + #if !defined(SOKOL_METAL) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #endif + #elif defined(_SAPP_IOS) + #import + #if !defined(SOKOL_METAL) + #import + #endif + #endif + #include + #include +#elif defined(_SAPP_EMSCRIPTEN) + #if defined(SOKOL_WGPU) + #include + #endif + #include + #include +#elif defined(_SAPP_WIN32) + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ + #pragma warning(disable:4054) /* 'type cast': from function pointer */ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem + #if defined(SOKOL_WIN32_FORCE_MAIN) + #pragma comment (linker, "/subsystem:console") + #else + #pragma comment (linker, "/subsystem:windows") + #endif + #endif + #include /* freopen_s() */ + #include /* wcslen() */ + + #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") + #endif + + #if defined(SOKOL_D3D11) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #include + #include + // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it + #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) + #endif + #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ + #define WM_MOUSEHWHEEL (0x020E) + #endif + #ifndef WM_DPICHANGED + #define WM_DPICHANGED (0x02E0) + #endif +#elif defined(_SAPP_ANDROID) + #include + #include + #include + #include + #include + #include +#elif defined(_SAPP_LINUX) + #define GL_GLEXT_PROTOTYPES + #include + #include + #include + #include + #include + #include + #include + #include + #include /* XC_* font cursors */ + #include /* CARD32 */ + #if !defined(_SAPP_GLX) + #include + #endif + #include /* dlopen, dlsym, dlclose */ + #include /* LONG_MAX */ + #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ + #include +#endif + +// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ +// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ +// +// >>frame timing +#define _SAPP_RING_NUM_SLOTS (256) +typedef struct { + int head; + int tail; + double buf[_SAPP_RING_NUM_SLOTS]; +} _sapp_ring_t; + +_SOKOL_PRIVATE int _sapp_ring_idx(int i) { + return i % _SAPP_RING_NUM_SLOTS; +} + +_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { + ring->head = 0; + ring->tail = 0; +} + +_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { + return _sapp_ring_idx(ring->head + 1) == ring->tail; +} + +_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { + return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { + int count; + if (ring->head >= ring->tail) { + count = ring->head - ring->tail; + } + else { + count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; + } + SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); + return count; +} + +_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { + SOKOL_ASSERT(!_sapp_ring_full(ring)); + ring->buf[ring->head] = val; + ring->head = _sapp_ring_idx(ring->head + 1); +} + +_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { + SOKOL_ASSERT(!_sapp_ring_empty(ring)); + double val = ring->buf[ring->tail]; + ring->tail = _sapp_ring_idx(ring->tail + 1); + return val; +} + +/* + NOTE: + + Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? + A: The value appears to be highly unstable during the first few + seconds, sometimes several frames are dropped in sequence, or + switch between 120 and 60 Hz for a few frames. Simply measuring + and averaging the frame time yielded a more stable frame duration. + Maybe switching to CVDisplayLink would yield better results. + Until then just measure the time. +*/ +typedef struct { + #if defined(_SAPP_APPLE) + struct { + mach_timebase_info_data_t timebase; + uint64_t start; + } mach; + #elif defined(_SAPP_EMSCRIPTEN) + // empty + #elif defined(_SAPP_WIN32) + struct { + LARGE_INTEGER freq; + LARGE_INTEGER start; + } win; + #else // Linux, Android, ... + #ifdef CLOCK_MONOTONIC + #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC + #else + // on some embedded platforms, CLOCK_MONOTONIC isn't defined + #define _SAPP_CLOCK_MONOTONIC (1) + #endif + struct { + uint64_t start; + } posix; + #endif +} _sapp_timestamp_t; + +_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} + +_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + mach_timebase_info(&ts->mach.timebase); + ts->mach.start = mach_absolute_time(); + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + #elif defined(_SAPP_WIN32) + QueryPerformanceFrequency(&ts->win.freq); + QueryPerformanceCounter(&ts->win.start); + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; + #endif +} + +_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + const uint64_t traw = mach_absolute_time() - ts->mach.start; + const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); + return (double)now / 1000000000.0; + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + SOKOL_ASSERT(false); + return 0.0; + #elif defined(_SAPP_WIN32) + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); + return (double)now / 1000000000.0; + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; + return (double)now / 1000000000.0; + #endif +} + +typedef struct { + double last; + double accum; + double avg; + int spike_count; + int num; + _sapp_timestamp_t timestamp; + _sapp_ring_t ring; +} _sapp_timing_t; + +_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { + t->last = 0.0; + t->accum = 0.0; + t->spike_count = 0; + t->num = 0; + _sapp_ring_init(&t->ring); +} + +_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { + t->avg = 1.0 / 60.0; // dummy value until first actual value is available + _sapp_timing_reset(t); + _sapp_timestamp_init(&t->timestamp); +} + +_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { + // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) + double min_dur = 0.0; + double max_dur = 0.1; + // if we have enough samples for a useful average, use a much tighter 'valid window' + if (_sapp_ring_full(&t->ring)) { + min_dur = t->avg * 0.8; + max_dur = t->avg * 1.2; + } + if ((dur < min_dur) || (dur > max_dur)) { + t->spike_count++; + // if there have been many spikes in a row, the display refresh rate + // might have changed, so a timing reset is needed + if (t->spike_count > 20) { + _sapp_timing_reset(t); + } + return; + } + if (_sapp_ring_full(&t->ring)) { + double old_val = _sapp_ring_dequeue(&t->ring); + t->accum -= old_val; + t->num -= 1; + } + _sapp_ring_enqueue(&t->ring, dur); + t->accum += dur; + t->num += 1; + SOKOL_ASSERT(t->num > 0); + t->avg = t->accum / t->num; + t->spike_count = 0; +} + +_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { + t->last = 0.0; +} + +_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { + const double now = _sapp_timestamp_now(&t->timestamp); + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { + return t->avg; +} + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >> structs +#if defined(_SAPP_MACOS) +@interface _sapp_macos_app_delegate : NSObject +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject +@end +#if defined(SOKOL_METAL) + @interface _sapp_macos_view : MTKView + @end +#elif defined(SOKOL_GLCORE33) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#endif // SOKOL_GLCORE33 + +typedef struct { + uint32_t flags_changed_store; + uint8_t mouse_buttons; + NSWindow* window; + NSTrackingArea* tracking_area; + id keyup_monitor; + _sapp_macos_app_delegate* app_dlg; + _sapp_macos_window_delegate* win_dlg; + _sapp_macos_view* view; + NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; + #if defined(SOKOL_METAL) + id mtl_device; + #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject +@end +@interface _sapp_textfield_dlg : NSObject +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) + @interface _sapp_ios_view : MTKView; + @end +#else + @interface _sapp_ios_view : GLKView + @end +#endif + +typedef struct { + UIWindow* window; + _sapp_ios_view* view; + UITextField* textfield; + _sapp_textfield_dlg* textfield_dlg; + #if defined(SOKOL_METAL) + UIViewController* view_ctrl; + id mtl_device; + #else + GLKViewController* view_ctrl; + EAGLContext* eagl_ctx; + #endif + bool suspended; +} _sapp_ios_t; + +#endif // _SAPP_IOS + +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(SOKOL_WGPU) +typedef struct { + int state; + WGPUDevice device; + WGPUSwapChain swapchain; + WGPUTextureFormat render_format; + WGPUTexture msaa_tex; + WGPUTexture depth_stencil_tex; + WGPUTextureView swapchain_view; + WGPUTextureView msaa_view; + WGPUTextureView depth_stencil_view; +} _sapp_wgpu_t; +#endif + +typedef struct { + bool textfield_created; + 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 +} _sapp_emsc_t; +#endif // _SAPP_EMSCRIPTEN + +#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) +typedef struct { + ID3D11Device* device; + ID3D11DeviceContext* device_context; + ID3D11Texture2D* rt; + ID3D11RenderTargetView* rtv; + ID3D11Texture2D* msaa_rt; + ID3D11RenderTargetView* msaa_rtv; + ID3D11Texture2D* ds; + ID3D11DepthStencilView* dsv; + DXGI_SWAP_CHAIN_DESC swap_chain_desc; + IDXGISwapChain* swap_chain; + IDXGIDevice1* dxgi_device; + bool use_dxgi_frame_stats; + UINT sync_refresh_count; +} _sapp_d3d11_t; +#endif + +#if defined(_SAPP_WIN32) + +#ifndef DPI_ENUMS_DECLARED +typedef enum PROCESS_DPI_AWARENESS +{ + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif /*DPI_ENUMS_DECLARED*/ + +typedef struct { + bool aware; + float content_scale; + float window_scale; + float mouse_scale; +} _sapp_win32_dpi_t; + +typedef struct { + HWND hwnd; + HMONITOR hmonitor; + HDC dc; + HICON big_icon; + HICON small_icon; + HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; + UINT orig_codepage; + LONG mouse_locked_x, mouse_locked_y; + RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed + bool is_win10_or_greater; + bool in_create_window; + bool iconified; + bool mouse_tracked; + uint8_t mouse_capture_mask; + _sapp_win32_dpi_t dpi; + bool raw_input_mousepos_valid; + LONG raw_input_mousepos_x; + LONG raw_input_mousepos_y; + uint8_t raw_input_data[256]; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE33) +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); +typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); +typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); +typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); +typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); +typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); + +typedef struct { + HINSTANCE opengl32; + HGLRC gl_ctx; + PFN_wglCreateContext CreateContext; + PFN_wglDeleteContext DeleteContext; + PFN_wglGetProcAddress GetProcAddress; + PFN_wglGetCurrentDC GetCurrentDC; + PFN_wglMakeCurrent MakeCurrent; + PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; + PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; + PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; + PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + bool ext_swap_control; + bool arb_multisample; + bool arb_pixel_format; + bool arb_create_context; + bool arb_create_context_profile; + HWND msg_hwnd; + HDC msg_dc; +} _sapp_wgl_t; +#endif // SOKOL_GLCORE33 + +#endif // _SAPP_WIN32 + +#if defined(_SAPP_ANDROID) +typedef enum { + _SOKOL_ANDROID_MSG_CREATE, + _SOKOL_ANDROID_MSG_RESUME, + _SOKOL_ANDROID_MSG_PAUSE, + _SOKOL_ANDROID_MSG_FOCUS, + _SOKOL_ANDROID_MSG_NO_FOCUS, + _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, + _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, + _SOKOL_ANDROID_MSG_DESTROY, +} _sapp_android_msg_t; + +typedef struct { + pthread_t thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + int read_from_main_fd; + int write_from_main_fd; +} _sapp_android_pt_t; + +typedef struct { + ANativeWindow* window; + AInputQueue* input; +} _sapp_android_resources_t; + +typedef struct { + ANativeActivity* activity; + _sapp_android_pt_t pt; + _sapp_android_resources_t pending; + _sapp_android_resources_t current; + ALooper* looper; + bool is_thread_started; + bool is_thread_stopping; + bool is_thread_stopped; + bool has_created; + bool has_resumed; + bool has_focus; + EGLConfig config; + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_android_t; + +#endif // _SAPP_ANDROID + +#if defined(_SAPP_LINUX) + +#define _SAPP_X11_XDND_VERSION (5) + +#define GLX_VENDOR 1 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_DOUBLEBUFFER 5 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_SAMPLES 0x186a1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef XID GLXWindow; +typedef XID GLXDrawable; +typedef struct __GLXFBConfig* GLXFBConfig; +typedef struct __GLXcontext* GLXContext; +typedef void (*__GLXextproc)(void); + +typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); +typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); +typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); +typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); +typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); +typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); +typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); +typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); +typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); +typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); +typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); + +typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); + +typedef struct { + bool available; + int major_opcode; + int event_base; + int error_base; + int major; + int minor; +} _sapp_xi_t; + +typedef struct { + int version; + Window source; + Atom format; + Atom XdndAware; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XdndTypeList; + Atom text_uri_list; +} _sapp_xdnd_t; + +typedef struct { + uint8_t mouse_buttons; + Display* display; + int screen; + Window root; + Colormap colormap; + Window window; + Cursor hidden_cursor; + Cursor cursors[_SAPP_MOUSECURSOR_NUM]; + int window_state; + float dpi; + unsigned char error_code; + Atom UTF8_STRING; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + 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; + _sapp_xdnd_t xdnd; +} _sapp_x11_t; + +#if defined(_SAPP_GLX) + +typedef struct { + void* libgl; + int major; + int minor; + int event_base; + int error_base; + GLXContext ctx; + GLXWindow window; + + // GLX 1.3 functions + PFNGLXGETFBCONFIGSPROC GetFBConfigs; + PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; + PFNGLXGETCLIENTSTRINGPROC GetClientString; + PFNGLXQUERYEXTENSIONPROC QueryExtension; + PFNGLXQUERYVERSIONPROC QueryVersion; + PFNGLXDESTROYCONTEXTPROC DestroyContext; + PFNGLXMAKECURRENTPROC MakeCurrent; + PFNGLXSWAPBUFFERSPROC SwapBuffers; + PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; + PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; + PFNGLXCREATEWINDOWPROC CreateWindow; + PFNGLXDESTROYWINDOWPROC DestroyWindow; + + // GLX 1.4 and extension functions + PFNGLXGETPROCADDRESSPROC GetProcAddress; + PFNGLXGETPROCADDRESSPROC GetProcAddressARB; + PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; + PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + + // extension availability + bool EXT_swap_control; + bool MESA_swap_control; + bool ARB_multisample; + bool ARB_create_context; + bool ARB_create_context_profile; +} _sapp_glx_t; + +#else + +typedef struct { + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_egl_t; + +#endif // _SAPP_GLX + +#endif // _SAPP_LINUX + +/* helper macros */ +#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) + +#define _SAPP_MAX_TITLE_LENGTH (128) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) +/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ +#define _SAPP_PIXELFORMAT_RGBA8 (23) +#define _SAPP_PIXELFORMAT_BGRA8 (28) +#define _SAPP_PIXELFORMAT_DEPTH (42) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (43) + +#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) + // this is ARC compatible + #if defined(__cplusplus) + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } +#endif + +typedef struct { + bool enabled; + int buf_size; + char* buffer; +} _sapp_clipboard_t; + +typedef struct { + bool enabled; + int max_files; + int max_path_length; + int num_files; + int buf_size; + char* buffer; +} _sapp_drop_t; + +typedef struct { + float x, y; + float dx, dy; + bool shown; + bool locked; + bool pos_valid; + sapp_mouse_cursor current_cursor; +} _sapp_mouse_t; + +typedef struct { + sapp_desc desc; + bool valid; + bool fullscreen; + bool gles2_fallback; + bool first_frame; + bool init_called; + bool cleanup_called; + bool quit_requested; + bool quit_ordered; + bool event_consumed; + bool html5_ask_leave_site; + bool onscreen_keyboard_shown; + int window_width; + int window_height; + int framebuffer_width; + int framebuffer_height; + int sample_count; + int swap_interval; + float dpi_scale; + uint64_t frame_count; + _sapp_timing_t timing; + sapp_event event; + _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) + _sapp_ios_t ios; + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_t emsc; + #elif defined(_SAPP_WIN32) + _sapp_win32_t win32; + #if defined(SOKOL_D3D11) + _sapp_d3d11_t d3d11; + #elif defined(SOKOL_GLCORE33) + _sapp_wgl_t wgl; + #endif + #elif defined(_SAPP_ANDROID) + _sapp_android_t android; + #elif defined(_SAPP_LINUX) + _sapp_x11_t x11; + #if defined(_SAPP_GLX) + _sapp_glx_t glx; + #else + _sapp_egl_t egl; + #endif + #endif + char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; + char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ + wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ + sapp_keycode keycodes[SAPP_MAX_KEYCODES]; +} _sapp_t; +static _sapp_t _sapp; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sapp_log_messages[] = { + _SAPP_LOG_ITEMS +}; +#undef _SAPP_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) + +static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sapp.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sapp_log_messages[log_item]; + } + #endif + _sapp.desc.logger.func("sapp", log_level, log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sapp.desc.allocator.alloc) { + ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data); + } + else { + ptr = malloc(size); + } + if (0 == ptr) { + _SAPP_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { + void* ptr = _sapp_malloc(size); + _sapp_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sapp_free(void* ptr) { + if (_sapp.desc.allocator.free) { + _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data); + } + else { + free(ptr); + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers +_SOKOL_PRIVATE void _sapp_call_init(void) { + if (_sapp.desc.init_cb) { + _sapp.desc.init_cb(); + } + else if (_sapp.desc.init_userdata_cb) { + _sapp.desc.init_userdata_cb(_sapp.desc.user_data); + } + _sapp.init_called = true; +} + +_SOKOL_PRIVATE void _sapp_call_frame(void) { + if (_sapp.init_called && !_sapp.cleanup_called) { + if (_sapp.desc.frame_cb) { + _sapp.desc.frame_cb(); + } + else if (_sapp.desc.frame_userdata_cb) { + _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); + } + } +} + +_SOKOL_PRIVATE void _sapp_call_cleanup(void) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.cleanup_cb) { + _sapp.desc.cleanup_cb(); + } + else if (_sapp.desc.cleanup_userdata_cb) { + _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); + } + _sapp.cleanup_called = true; + } +} + +_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.event_cb) { + _sapp.desc.event_cb(e); + } + else if (_sapp.desc.event_userdata_cb) { + _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); + } + } + if (_sapp.event_consumed) { + _sapp.event_consumed = false; + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { + SOKOL_ASSERT(_sapp.drop.buffer); + SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); + int offset = index * _sapp.drop.max_path_length; + SOKOL_ASSERT(offset < _sapp.drop.buf_size); + return &_sapp.drop.buffer[offset]; +} + +/* Copy a string into a fixed size buffer with guaranteed zero- + termination. + + Return false if the string didn't fit into the buffer and had to be clamped. + + FIXME: Currently UTF-8 strings might become invalid if the string + is clamped, because the last zero-byte might be written into + the middle of a multi-byte sequence. +*/ +_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { + SOKOL_ASSERT(src && dst && (max_len > 0)); + char* const end = &(dst[max_len-1]); + char c = 0; + for (int i = 0; i < max_len; i++) { + c = *src; + if (c != 0) { + src++; + } + *dst++ = c; + } + /* truncated? */ + if (c != 0) { + *end = 0; + return false; + } + else { + return true; + } +} + +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { + SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); + sapp_desc res = *desc; + res.sample_count = _sapp_def(res.sample_count, 1); + res.swap_interval = _sapp_def(res.swap_interval, 1); + // NOTE: can't patch the default for gl_major_version and gl_minor_version + // independently, because a desired version 4.0 would be patched to 4.2 + // (or expressed differently: zero is a valid value for gl_minor_version + // and can't be used to indicate 'default') + if (0 == res.gl_major_version) { + res.gl_major_version = 3; + res.gl_minor_version = 2; + } + res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas"); + res.clipboard_size = _sapp_def(res.clipboard_size, 8192); + res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); + res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); + res.window_title = _sapp_def(res.window_title, "sokol_app"); + return res; +} + +_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->width >= 0); + SOKOL_ASSERT(desc->height >= 0); + SOKOL_ASSERT(desc->sample_count >= 0); + SOKOL_ASSERT(desc->swap_interval >= 0); + SOKOL_ASSERT(desc->clipboard_size >= 0); + SOKOL_ASSERT(desc->max_dropped_files >= 0); + SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); + _sapp.desc = _sapp_desc_defaults(desc); + _sapp.first_frame = true; + // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this + _sapp.window_width = _sapp.desc.width; + _sapp.window_height = _sapp.desc.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp.sample_count = _sapp.desc.sample_count; + _sapp.swap_interval = _sapp.desc.swap_interval; + _sapp.html5_canvas_selector[0] = '#'; + _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); + _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; + _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; + _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; + if (_sapp.clipboard.enabled) { + _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; + _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); + } + _sapp.drop.enabled = _sapp.desc.enable_dragndrop; + if (_sapp.drop.enabled) { + _sapp.drop.max_files = _sapp.desc.max_dropped_files; + _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; + _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; + _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); + } + _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); + _sapp.desc.window_title = _sapp.window_title; + _sapp.dpi_scale = 1.0f; + _sapp.fullscreen = _sapp.desc.fullscreen; + _sapp.mouse.shown = true; + _sapp_timing_init(&_sapp.timing); +} + +_SOKOL_PRIVATE void _sapp_discard_state(void) { + if (_sapp.clipboard.enabled) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + _sapp_free((void*)_sapp.clipboard.buffer); + } + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_free((void*)_sapp.drop.buffer); + } + if (_sapp.default_icon_pixels) { + _sapp_free((void*)_sapp.default_icon_pixels); + } + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); +} + +_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { + _sapp_clear(&_sapp.event, sizeof(_sapp.event)); + _sapp.event.type = type; + _sapp.event.frame_count = _sapp.frame_count; + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + _sapp.event.window_width = _sapp.window_width; + _sapp.event.window_height = _sapp.window_height; + _sapp.event.framebuffer_width = _sapp.framebuffer_width; + _sapp.event.framebuffer_height = _sapp.framebuffer_height; + _sapp.event.mouse_x = _sapp.mouse.x; + _sapp.event.mouse_y = _sapp.mouse.y; + _sapp.event.mouse_dx = _sapp.mouse.dx; + _sapp.event.mouse_dy = _sapp.mouse.dy; +} + +_SOKOL_PRIVATE bool _sapp_events_enabled(void) { + /* only send events when an event callback is set, and the init function was called */ + return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { + if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { + return _sapp.keycodes[scan_code]; + } + else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); + } +} + +_SOKOL_PRIVATE void _sapp_frame(void) { + if (_sapp.first_frame) { + _sapp.first_frame = false; + _sapp_call_init(); + } + _sapp_call_frame(); + _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) { + _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); + 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*) _sapp_malloc_clear((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); +} + +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>apple +#if defined(_SAPP_APPLE) + +#if __has_feature(objc_arc) +#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +// ███ ███ █████ ██████ ██████ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ ███████ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██████ ██████ ███████ +// +// >>macos +#if defined(_SAPP_MACOS) + +_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { + _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; + _sapp.keycodes[0x12] = SAPP_KEYCODE_1; + _sapp.keycodes[0x13] = SAPP_KEYCODE_2; + _sapp.keycodes[0x14] = SAPP_KEYCODE_3; + _sapp.keycodes[0x15] = SAPP_KEYCODE_4; + _sapp.keycodes[0x17] = SAPP_KEYCODE_5; + _sapp.keycodes[0x16] = SAPP_KEYCODE_6; + _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; + _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; + _sapp.keycodes[0x19] = SAPP_KEYCODE_9; + _sapp.keycodes[0x00] = SAPP_KEYCODE_A; + _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; + _sapp.keycodes[0x08] = SAPP_KEYCODE_C; + _sapp.keycodes[0x02] = SAPP_KEYCODE_D; + _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; + _sapp.keycodes[0x03] = SAPP_KEYCODE_F; + _sapp.keycodes[0x05] = SAPP_KEYCODE_G; + _sapp.keycodes[0x04] = SAPP_KEYCODE_H; + _sapp.keycodes[0x22] = SAPP_KEYCODE_I; + _sapp.keycodes[0x26] = SAPP_KEYCODE_J; + _sapp.keycodes[0x28] = SAPP_KEYCODE_K; + _sapp.keycodes[0x25] = SAPP_KEYCODE_L; + _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; + _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; + _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; + _sapp.keycodes[0x23] = SAPP_KEYCODE_P; + _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01] = SAPP_KEYCODE_S; + _sapp.keycodes[0x11] = SAPP_KEYCODE_T; + _sapp.keycodes[0x20] = SAPP_KEYCODE_U; + _sapp.keycodes[0x09] = SAPP_KEYCODE_V; + _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; + _sapp.keycodes[0x07] = SAPP_KEYCODE_X; + _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; + _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x77] = SAPP_KEYCODE_END; + _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; + _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; +} + +_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + if (_sapp.macos.keyup_monitor != nil) { + [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; + // NOTE: removeMonitor also releases the object + _sapp.macos.keyup_monitor = nil; + } + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.view); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); + #endif + _SAPP_OBJC_RELEASE(_sapp.macos.window); +} + +// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end + +_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { + _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug + _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; +} + +_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_macos_init_keytable(); + [NSApplication sharedApplication]; + + // 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; + + // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: + NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + [[NSApp keyWindow] sendEvent:event]; + } + return event; + }; + _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; + + [NSApp run]; + // NOTE: [NSApp run] never returns, instead cleanup code + // must be put into applicationWillTerminate +} + +/* MacOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_macos_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_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; + } + if (f & NSEventModifierFlagControl) { + m |= SAPP_MODIFIER_CTRL; + } + if (f & NSEventModifierFlagOption) { + m |= SAPP_MODIFIER_ALT; + } + 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; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +/* NOTE: unlike the iOS version of this function, the macOS version + can dynamically update the DPI scaling factor when a window is moved + between HighDPI / LowDPI screens. +*/ +_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; + } + else { + _sapp.dpi_scale = 1.0f; + } + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = (int)roundf(bounds.size.width); + _sapp.window_height = (int)roundf(bounds.size.height); + #if defined(SOKOL_METAL) + _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const CGSize fb_size = _sapp.macos.view.drawableSize; + const int cur_fb_width = (int)roundf(fb_size.width); + const int cur_fb_height = (int)roundf(fb_size.height); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + #elif defined(SOKOL_GLCORE33) + const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + _sapp.framebuffer_width = cur_fb_width; + _sapp.framebuffer_height = cur_fb_height; + #endif + if (_sapp.framebuffer_width == 0) { + _sapp.framebuffer_width = 1; + } + if (_sapp.framebuffer_height == 0) { + _sapp.framebuffer_height = 1; + } + if (_sapp.window_width == 0) { + _sapp.window_width = 1; + } + if (_sapp.window_height == 0) { + _sapp.window_height = 1; + } + if (dim_changed) { + #if defined(SOKOL_METAL) + CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + #else + // nothing to do for GL? + #endif + if (!_sapp.first_frame) { + _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { + /* NOTE: the _sapp.fullscreen flag is also notified by the + windowDidEnterFullscreen / windowDidExitFullscreen + event handlers + */ + _sapp.fullscreen = !_sapp.fullscreen; + [_sapp.macos.window toggleFullScreen:nil]; +} + +_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(str) forType:NSPasteboardTypeString]; + } +} + +_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + @autoreleasepool { + _sapp.clipboard.buffer[0] = 0; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { + return _sapp.clipboard.buffer; + } + NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; + if (!str) { + return _sapp.clipboard.buffer; + } + _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + } + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { + [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_update(NSEvent* event) { + if (!_sapp.mouse.locked) { + const NSPoint mouse_pos = event.locationInWindow; + float new_x = mouse_pos.x * _sapp.dpi_scale; + float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; + if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first update + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { + /* NOTE: this function is only called when the mouse visibility actually changes */ + if (visible) { + CGDisplayShowCursor(kCGDirectMainDisplay); + } + else { + CGDisplayHideCursor(kCGDirectMainDisplay); + } +} + +_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + /* + NOTE that this code doesn't warp the mouse cursor to the window + center as everybody else does it. This lead to a spike in the + *second* mouse-moved event after the warp happened. The + mouse centering doesn't seem to be required (mouse-moved events + are reported correctly even when the cursor is at an edge of the screen). + + NOTE also that the hide/show of the mouse cursor should properly + stack with calls to sapp_show_mouse() + */ + if (_sapp.mouse.locked) { + CGAssociateMouseAndMouseCursorPosition(NO); + [NSCursor hide]; + } + else { + [NSCursor unhide]; + CGAssociateMouseAndMouseCursorPosition(YES); + } +} + +_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { + // show/hide cursor only if visibility status has changed (required because show/hide stacks) + if (shown != _sapp.mouse.shown) { + if (shown) { + [NSCursor unhide]; + } + else { + [NSCursor hide]; + } + } + // update cursor type + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.macos.cursors[cursor]) { + [_sapp.macos.cursors[cursor] set]; + } + else { + [[NSCursor arrowCursor] set]; + } +} + +_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) { + [_sapp.macos.window performClose:nil]; + } +} + +@implementation _sapp_macos_app_delegate +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + _SOKOL_UNUSED(aNotification); + _sapp_macos_init_cursors(); + if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { + // use 4/5 of screen size as default size + NSRect screen_rect = NSScreen.mainScreen.frame; + if (_sapp.window_width == 0) { + _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); + } + if (_sapp.window_height == 0) { + _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f); + } + } + const NSUInteger style = + NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable; + NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); + _sapp.macos.window = [[_sapp_macos_window alloc] + initWithContentRect:window_rect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate + _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; + _sapp.macos.window.acceptsMouseMovedEvents = YES; + _sapp.macos.window.restorable = YES; + + _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; + _sapp.macos.window.delegate = _sapp.macos.win_dlg; + #if defined(SOKOL_METAL) + NSInteger max_fps = 60; + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + if (@available(macOS 12.0, *)) { + max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; + } + #endif + _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.macos.view = [[_sapp_macos_view alloc] init]; + [_sapp.macos.view updateTrackingAreas]; + _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.macos.view.device = _sapp.macos.mtl_device; + _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; + _sapp.macos.view.autoResizeDrawable = false; + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; + #elif defined(SOKOL_GLCORE33) + NSOpenGLPixelFormatAttribute attrs[32]; + int i = 0; + attrs[i++] = NSOpenGLPFAAccelerated; + attrs[i++] = NSOpenGLPFADoubleBuffer; + attrs[i++] = NSOpenGLPFAOpenGLProfile; + const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; + switch(glVersion) { + case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; + case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; + case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; + default: + _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); + } + attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; + attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; + if (_sapp.sample_count > 1) { + attrs[i++] = NSOpenGLPFAMultisample; + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; + } + else { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; + } + attrs[i++] = 0; + NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + SOKOL_ASSERT(glpixelformat_obj != nil); + + _sapp.macos.view = [[_sapp_macos_view alloc] + initWithFrame:window_rect + pixelFormat:glpixelformat_obj]; + _SAPP_OBJC_RELEASE(glpixelformat_obj); + [_sapp.macos.view updateTrackingAreas]; + if (_sapp.desc.high_dpi) { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; + } + else { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; + } + + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + + NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 + target:_sapp.macos.view + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; + timer_obj = nil; + #endif + [_sapp.macos.window center]; + _sapp.valid = true; + if (_sapp.fullscreen) { + /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ + [_sapp.macos.window toggleFullScreen:self]; + } + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + [NSApp activateIgnoringOtherApps:YES]; + [_sapp.macos.window makeKeyAndOrderFront:nil]; + _sapp_macos_update_dimensions(); + [NSEvent setMouseCoalescingEnabled:NO]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { + _SOKOL_UNUSED(sender); + return YES; +} + +- (void)applicationWillTerminate:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_call_cleanup(); + _sapp_macos_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_macos_window_delegate +- (BOOL)windowShouldClose:(id)sender { + _SOKOL_UNUSED(sender); + /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a chance to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + return YES; + } + else { + return NO; + } +} + +- (void)windowDidResize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidChangeScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_timing_reset(&_sapp.timing); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidMiniaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _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; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = false; +} +@end + +@implementation _sapp_macos_window +- (instancetype)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag { + if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; + #endif + } + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + NSPasteboard *pboard = [sender draggingPasteboard]; + if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; + bool drop_failed = false; + for (int i = 0; i < _sapp.drop.num_files; i++) { + NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; + if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + break; + } + } + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } + return YES; + } + #endif + return NO; +} +@end + +@implementation _sapp_macos_view +#if defined(SOKOL_GLCORE33) +/* NOTE: this is a hack/fix when the initial window size has been clipped by + macOS because it didn't fit on the screen, in that case the + frame size of the window is reported wrong if low-dpi rendering + was requested (instead the high-dpi dimensions are returned) + until the window is resized for the first time. + + Hooking into reshape and getting the frame dimensions seems to report + the correct dimensions. +*/ +- (void)reshape { + _sapp_macos_update_dimensions(); + [super reshape]; +} +- (void)timerFired:(id)sender { + _SOKOL_UNUSED(sender); + [self setNeedsDisplay:YES]; +} +- (void)prepareOpenGL { + [super prepareOpenGL]; + GLint swapInt = 1; + NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; + [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; + [ctx makeCurrentContext]; +} +#endif + +_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { + /* + + NOTE: late event polling temporarily out-commented to check if this + causes infrequent and almost impossible to reproduce problems with the + window close events, see: + https://github.com/floooh/sokol/pull/483#issuecomment-805148815 + + + const NSEventMask mask = NSEventMaskLeftMouseDown | + NSEventMaskLeftMouseUp| + NSEventMaskRightMouseDown | + NSEventMaskRightMouseUp | + NSEventMaskMouseMoved | + NSEventMaskLeftMouseDragged | + NSEventMaskRightMouseDragged | + NSEventMaskMouseEntered | + NSEventMaskMouseExited | + NSEventMaskKeyDown | + NSEventMaskKeyUp | + NSEventMaskCursorUpdate | + NSEventMaskScrollWheel | + NSEventMaskTabletPoint | + NSEventMaskTabletProximity | + NSEventMaskOtherMouseDown | + NSEventMaskOtherMouseUp | + NSEventMaskOtherMouseDragged | + NSEventMaskPressure | + NSEventMaskDirectTouch; + @autoreleasepool { + for (;;) { + // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, + // see: https://github.com/floooh/sokol/issues/486 + NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; + if (event == nil) { + break; + } + [NSApp sendEvent:event]; + } + } + */ +} + +- (void)drawRect:(NSRect)rect { + _SOKOL_UNUSED(rect); + _sapp_timing_measure(&_sapp.timing); + /* Catch any last-moment input events */ + _sapp_macos_poll_input_events(); + @autoreleasepool { + _sapp_macos_frame(); + } + #if !defined(SOKOL_METAL) + [[_sapp.macos.view openGLContext] flushBuffer]; + #endif +} + +- (BOOL)isOpaque { + return YES; +} +- (BOOL)canBecomeKeyView { + return YES; +} +- (BOOL)acceptsFirstResponder { + return YES; +} +- (void)updateTrackingAreas { + if (_sapp.macos.tracking_area != nil) { + [self removeTrackingArea:_sapp.macos.tracking_area]; + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + } + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + [self addTrackingArea:_sapp.macos.tracking_area]; + [super updateTrackingAreas]; +} +- (void)mouseEntered:(NSEvent*)event { + _sapp_macos_mouse_update(event); + /* don't send mouse enter/leave while dragging (so that it behaves the same as + 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_mods(event)); + } +} +- (void)mouseExited:(NSEvent*)event { + _sapp_macos_mouse_update(event); + if (0 == _sapp.macos.mouse_buttons) { + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); + } +} +- (void)mouseDown:(NSEvent*)event { + _sapp_macos_mouse_update(event); + _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_mods(event); + _sapp.event.scroll_x = dx; + _sapp.event.scroll_y = dy; + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyDown:(NSEvent*)event { + if (_sapp_events_enabled()) { + const uint32_t mods = _sapp_macos_mods(event); + const sapp_keycode key_code = _sapp_translate_key(event.keyCode); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); + const NSString* chars = event.characters; + const NSUInteger len = chars.length; + if (len > 0) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = mods; + for (NSUInteger i = 0; i < len; i++) { + const unichar codepoint = [chars characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + _sapp.event.char_code = codepoint; + _sapp.event.key_repeat = event.isARepeat; + _sapp_call_event(&_sapp.event); + } + } + /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ + if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyUp:(NSEvent*)event { + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, + _sapp_translate_key(event.keyCode), + event.isARepeat, + _sapp_macos_mods(event)); +} +- (void)flagsChanged:(NSEvent*)event { + const uint32_t old_f = _sapp.macos.flags_changed_store; + const uint32_t new_f = (uint32_t)event.modifierFlags; + _sapp.macos.flags_changed_store = new_f; + sapp_keycode key_code = SAPP_KEYCODE_INVALID; + bool down = false; + if ((new_f ^ old_f) & NSEventModifierFlagShift) { + key_code = SAPP_KEYCODE_LEFT_SHIFT; + down = 0 != (new_f & NSEventModifierFlagShift); + } + if ((new_f ^ old_f) & NSEventModifierFlagControl) { + key_code = SAPP_KEYCODE_LEFT_CONTROL; + down = 0 != (new_f & NSEventModifierFlagControl); + } + if ((new_f ^ old_f) & NSEventModifierFlagOption) { + key_code = SAPP_KEYCODE_LEFT_ALT; + down = 0 != (new_f & NSEventModifierFlagOption); + } + if ((new_f ^ old_f) & NSEventModifierFlagCommand) { + key_code = SAPP_KEYCODE_LEFT_SUPER; + down = 0 != (new_f & NSEventModifierFlagCommand); + } + if (key_code != SAPP_KEYCODE_INVALID) { + _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, + key_code, + false, + _sapp_macos_mods(event)); + } +} +@end + +#endif // macOS + +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios +#if defined(_SAPP_IOS) + +_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); + _SAPP_OBJC_RELEASE(_sapp.ios.textfield); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); + #else + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); + #endif + _SAPP_OBJC_RELEASE(_sapp.ios.view); + _SAPP_OBJC_RELEASE(_sapp.ios.window); +} + +_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { + _sapp_init_state(desc); + static int argc = 1; + static char* argv[] = { (char*)"sokol_app" }; + UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); +} + +/* iOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_ios_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + NSEnumerator* enumerator = event.allTouches.objectEnumerator; + UITouch* ios_touch; + while ((ios_touch = [enumerator nextObject])) { + if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { + CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; + sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; + cur_point->identifier = (uintptr_t) ios_touch; + cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; + cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; + cur_point->changed = [touches containsObject:ios_touch]; + } + } + if (_sapp.event.num_touches > 0) { + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); + int cur_fb_width, cur_fb_height; + #if defined(SOKOL_METAL) + const CGSize fb_size = _sapp.ios.view.drawableSize; + cur_fb_width = (int)roundf(fb_size.width); + cur_fb_height = (int)roundf(fb_size.height); + #else + cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); + cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); + #endif + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + #if defined(SOKOL_METAL) + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.ios.view.drawableSize = drawable_size; + #else + // nothing to do here, GLKView correctly respects the view's contentScaleFactor + #endif + if (!_sapp.first_frame) { + _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_frame(void) { + _sapp_ios_update_dimensions(); + _sapp_frame(); +} + +_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { + /* if not happened yet, create an invisible text field */ + if (nil == _sapp.ios.textfield) { + _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; + _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; + _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; + _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; + _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; + _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; + _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; + _sapp.ios.textfield.hidden = YES; + _sapp.ios.textfield.text = @"x"; + _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; + [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; + + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWasShown:) + name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWillBeHidden:) + name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardDidChangeFrame:) + name:UIKeyboardDidChangeFrameNotification object:nil]; + } + if (shown) { + /* setting the text field as first responder brings up the onscreen keyboard */ + [_sapp.ios.textfield becomeFirstResponder]; + } + else { + [_sapp.ios.textfield resignFirstResponder]; + } +} + +@implementation _sapp_app_delegate +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; + } + else { + _sapp.dpi_scale = 1.0f; + } + _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); + NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; + #if defined(SOKOL_METAL) + _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.ios.view = [[_sapp_ios_view alloc] init]; + _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.view.device = _sapp.ios.mtl_device; + _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; + /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor + and automatically renders at Retina resolution. We'll disable + autoResize and instead do the resizing in _sapp_ios_update_dimensions() + */ + _sapp.ios.view.autoResizeDrawable = false; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + _sapp.ios.view_ctrl = [[UIViewController alloc] init]; + _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #else + if (_sapp.desc.gl_force_gles2) { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + _sapp.gles2_fallback = true; + } + else { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + if (_sapp.ios.eagl_ctx == nil) { + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + _sapp.gles2_fallback = true; + } + } + _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; + _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; + _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; + GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; + _sapp.ios.view.drawableMultisample = msaa; + _sapp.ios.view.context = _sapp.ios.eagl_ctx; + _sapp.ios.view.enableSetNeedsDisplay = NO; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + // on GLKView, contentScaleFactor appears to work just fine! + if (_sapp.desc.high_dpi) { + _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; + } + else { + _sapp.ios.view.contentScaleFactor = 1.0; + } + _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #endif + [_sapp.ios.window makeKeyAndVisible]; + + _sapp.valid = true; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + if (!_sapp.ios.suspended) { + _sapp.ios.suspended = true; + _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); + } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + if (_sapp.ios.suspended) { + _sapp.ios.suspended = false; + _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); + } +} + +/* NOTE: this method will rarely ever be called, iOS application + which are terminated by the user are usually killed via signal 9 + by the operating system. +*/ +- (void)applicationWillTerminate:(UIApplication *)application { + _SOKOL_UNUSED(application); + _sapp_call_cleanup(); + _sapp_ios_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_textfield_dlg +- (void)keyboardWasShown:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = true; + /* query the keyboard's size, and modify the content view's size */ + if (_sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (void)keyboardWillBeHidden:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = false; + if (_sapp.desc.ios_keyboard_resizes_canvas) { + _sapp.ios.view.frame = UIScreen.mainScreen.bounds; + } +} +- (void)keyboardDidChangeFrame:(NSNotification*)notif { + /* this is for the case when the screen rotation changes while the keyboard is open */ + if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { + if (_sapp_events_enabled()) { + const NSUInteger len = string.length; + if (len > 0) { + for (NSUInteger i = 0; i < len; i++) { + unichar c = [string characterAtIndex:i]; + if (c >= 32) { + /* ignore surrogates for now */ + if ((c < 0xD800) || (c > 0xDFFF)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = c; + _sapp_call_event(&_sapp.event); + } + } + if (c <= 32) { + sapp_keycode k = SAPP_KEYCODE_INVALID; + switch (c) { + case 10: k = SAPP_KEYCODE_ENTER; break; + case 32: k = SAPP_KEYCODE_SPACE; break; + default: break; + } + if (k != SAPP_KEYCODE_INVALID) { + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + } + } + } + } + else { + /* this was a backspace */ + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + } + } + return NO; +} +@end + +@implementation _sapp_ios_view +- (void)drawRect:(CGRect)rect { + _SOKOL_UNUSED(rect); + _sapp_timing_measure(&_sapp.timing); + @autoreleasepool { + _sapp_ios_frame(); + } +} +- (BOOL)isOpaque { + return YES; +} +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); +} +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); +} +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); +} +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); +} +@end +#endif /* TARGET_OS_IPHONE */ + +#endif /* _SAPP_APPLE */ + +// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ +// +// >>emscripten +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(EM_JS_DEPS) +EM_JS_DEPS(sokol_app, "$withStackSave,$allocateUTF8OnStack"); +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); + +/* this function is called from a JS event handler when the user hides + the onscreen keyboard pressing the 'dismiss keyboard key' +*/ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { + _sapp.onscreen_keyboard_shown = false; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { + if (_sapp.clipboard.enabled) { + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ +EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { + return _sapp.html5_ask_leave_site ? 1 : 0; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { + if (!_sapp.drop.enabled) { + return; + } + if (num < 0) { + num = 0; + } + if (num > _sapp.drop.max_files) { + num = _sapp.drop.max_files; + } + _sapp.drop.num_files = num; + _sapp_clear_drop_buffer(); +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { + /* NOTE: name is only the filename part, not a path */ + if (!_sapp.drop.enabled) { + return; + } + if (0 == name) { + return; + } + SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); + if ((i < 0) || (i >= _sapp.drop.num_files)) { + return; + } + if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + _sapp.drop.num_files = 0; + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { + if (!_sapp.drop.enabled) { + return; + } + if (0 == _sapp.drop.num_files) { + /* there was an error copying the filenames */ + _sapp_clear_drop_buffer(); + return; + + } + if (_sapp_events_enabled()) { + _sapp.mouse.x = (float)x * _sapp.dpi_scale; + _sapp.mouse.y = (float)y * _sapp.dpi_scale; + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { + sapp_html5_fetch_response response; + _sapp_clear(&response, sizeof(response)); + response.succeeded = (0 != success); + response.error_code = (sapp_html5_fetch_error) error_code; + response.file_index = index; + response.data.ptr = buf_ptr; + response.data.size = fetched_size; + response.buffer.ptr = buf_ptr; + response.buffer.size = buf_size; + response.user_data = user_data; + callback(&response); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* Javascript helper functions for mobile virtual keyboard input */ +EM_JS(void, sapp_js_create_textfield, (void), { + const _sapp_inp = document.createElement("input"); + _sapp_inp.type = "text"; + _sapp_inp.id = "_sokol_app_input_element"; + _sapp_inp.autocapitalize = "none"; + _sapp_inp.addEventListener("focusout", function(_sapp_event) { + __sapp_emsc_notify_keyboard_hidden() + + }); + document.body.append(_sapp_inp); +}); + +EM_JS(void, sapp_js_focus_textfield, (void), { + document.getElementById("_sokol_app_input_element").focus(); +}); + +EM_JS(void, sapp_js_unfocus_textfield, (void), { + document.getElementById("_sokol_app_input_element").blur(); +}); + +EM_JS(void, sapp_js_add_beforeunload_listener, (void), { + Module.sokol_beforeunload = (event) => { + if (__sapp_html5_get_ask_leave_site() != 0) { + event.preventDefault(); + event.returnValue = ' '; + } + }; + window.addEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { + window.removeEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_add_clipboard_listener, (void), { + Module.sokol_paste = (event) => { + const pasted_str = event.clipboardData.getData('text'); + withStackSave(() => { + const cstr = allocateUTF8OnStack(pasted_str); + __sapp_emsc_onpaste(cstr); + }); + }; + window.addEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_remove_clipboard_listener, (void), { + window.removeEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { + const str = UTF8ToString(c_str); + const ta = document.createElement('textarea'); + ta.setAttribute('autocomplete', 'off'); + ta.setAttribute('autocorrect', 'off'); + ta.setAttribute('autocapitalize', 'off'); + ta.setAttribute('spellcheck', 'false'); + ta.style.left = -100 + 'px'; + ta.style.top = -100 + 'px'; + ta.style.height = 1; + ta.style.width = 1; + ta.value = str; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); +}); + +_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { + sapp_js_write_clipboard(str); +} + +EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { + Module.sokol_drop_files = []; + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + Module.sokol_dragenter = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragleave = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragover = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_drop = (event) => { + event.stopPropagation(); + event.preventDefault(); + const files = event.dataTransfer.files; + Module.sokol_dropped_files = files; + __sapp_emsc_begin_drop(files.length); + for (let i = 0; i < files.length; i++) { + withStackSave(() => { + const cstr = allocateUTF8OnStack(files[i].name); + __sapp_emsc_drop(i, cstr); + }); + } + // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect + __sapp_emsc_end_drop(event.clientX, event.clientY); + }; + canvas.addEventListener('dragenter', Module.sokol_dragenter, false); + canvas.addEventListener('dragleave', Module.sokol_dragleave, false); + canvas.addEventListener('dragover', Module.sokol_dragover, false); + canvas.addEventListener('drop', Module.sokol_drop, false); +}); + +EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + if ((index < 0) || (index >= files.length)) { + return 0; + } + else { + return files[index].size; + } +}); + +EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { + const reader = new FileReader(); + reader.onload = (loadEvent) => { + const content = loadEvent.target.result; + if (content.byteLength > buf_size) { + // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL + __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); + } + else { + HEAPU8.set(new Uint8Array(content), buf_ptr); + __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); + } + }; + reader.onerror = () => { + // SAPP_HTML5_FETCH_ERROR_OTHER + __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); + }; + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + reader.readAsArrayBuffer(files[index]); +}); + +EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + canvas.removeEventListener('dragenter', Module.sokol_dragenter); + canvas.removeEventListener('dragleave', Module.sokol_dragleave); + canvas.removeEventListener('dragover', Module.sokol_dragover); + canvas.removeEventListener('drop', Module.sokol_drop); +}); + +/* called from the emscripten event handler to update the keyboard visibility + state, this must happen from an JS input event handler, otherwise + the request will be ignored by the browser +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { + if (_sapp.emsc.wants_show_keyboard) { + /* create input text field on demand */ + if (!_sapp.emsc.textfield_created) { + _sapp.emsc.textfield_created = true; + sapp_js_create_textfield(); + } + /* focus the text input field, this will bring up the keyboard */ + _sapp.onscreen_keyboard_shown = true; + _sapp.emsc.wants_show_keyboard = false; + sapp_js_focus_textfield(); + } + if (_sapp.emsc.wants_hide_keyboard) { + /* unfocus the text input field */ + if (_sapp.emsc.textfield_created) { + _sapp.onscreen_keyboard_shown = false; + _sapp.emsc.wants_hide_keyboard = false; + sapp_js_unfocus_textfield(); + } + } +} + +/* actually showing the onscreen keyboard must be initiated from a JS + input event handler, so we'll just keep track of the desired + state, and the actual state change will happen with the next input event +*/ +_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { + if (show) { + _sapp.emsc.wants_show_keyboard = true; + } + else { + _sapp.emsc.wants_hide_keyboard = true; + } +} + +EM_JS(void, sapp_js_init, (const char* c_str_target), { + // lookup and store canvas object by name + const target_str = UTF8ToString(c_str_target); + Module.sapp_emsc_target = document.getElementById(target_str); + if (!Module.sapp_emsc_target) { + console.log("sokol_app.h: invalid target:" + target_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); + } +}); + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = emsc_event->isActive; + return EM_TRUE; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = false; + _sapp.emsc.mouse_lock_requested = false; + return true; +} + +EM_JS(void, sapp_js_request_pointerlock, (void), { + if (Module.sapp_emsc_target) { + if (Module.sapp_emsc_target.requestPointerLock) { + Module.sapp_emsc_target.requestPointerLock(); + } + } +}); + +EM_JS(void, sapp_js_exit_pointerlock, (void), { + if (document.exitPointerLock) { + document.exitPointerLock(); + } +}); + +_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { + if (lock) { + /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ + _sapp.emsc.mouse_lock_requested = true; + } + else { + /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ + _sapp.emsc.mouse_lock_requested = false; + sapp_js_exit_pointerlock(); + } +} + +/* called from inside event handlers to check if mouse lock had been requested, + and if yes, actually enter mouse lock. +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { + if (_sapp.emsc.mouse_lock_requested) { + _sapp.emsc.mouse_lock_requested = false; + sapp_js_request_pointerlock(); + } +} + +// set mouse cursor type +EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { + if (Module.sapp_emsc_target) { + let cursor; + if (shown === 0) { + cursor = "none"; + } + else switch (cursor_type) { + case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT + case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW + case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM + case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR + case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND + case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW + case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS + case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE + case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW + case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL + case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED + default: cursor = "auto"; break; + } + Module.sapp_emsc_target.style.cursor = cursor; + } +}); + +_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + sapp_js_set_cursor((int)cursor, shown ? 1 : 0); +} + +/* JS helper functions to update browser tab favicon */ +EM_JS(void, sapp_js_clear_favicon, (void), { + const 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), { + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + const img_data = ctx.createImageData(w, h); + img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); + ctx.putImageData(img_data, 0, 0); + const 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); + double w, h; + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + /* The above method might report zero when toggling HTML5 fullscreen, + in that case use the window's inner width reported by the + emscripten event. This works ok when toggling *into* fullscreen + but doesn't properly restore the previous canvas size when switching + back from fullscreen. + + In general, due to the HTML5's fullscreen API's flaky nature it is + recommended to use 'soft fullscreen' (stretching the WebGL canvas + over the browser windows client rect) with a CSS definition like this: + + position: absolute; + top: 0px; + left: 0px; + margin: 0px; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + display: block; + */ + if (w < 1.0) { + w = ui_event->windowInnerWidth; + } + else { + _sapp.window_width = (int)roundf(w); + } + if (h < 1.0) { + h = ui_event->windowInnerHeight; + } + else { + _sapp.window_height = (int)roundf(h); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); + SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_WGPU) + /* on WebGPU: recreate size-dependent rendering surfaces */ + _sapp_emsc_wgpu_surfaces_discard(); + _sapp_emsc_wgpu_surfaces_create(); + #endif + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_RESIZED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_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; + } + else { + float new_x = emsc_event->targetX * _sapp.dpi_scale; + float new_y = emsc_event->targetY * _sapp.dpi_scale; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } + if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { + sapp_event_type type; + bool is_button_event = false; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_MOUSEDOWN: + type = SAPP_EVENTTYPE_MOUSE_DOWN; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + type = SAPP_EVENTTYPE_MOUSE_UP; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + type = SAPP_EVENTTYPE_MOUSE_MOVE; + break; + case EMSCRIPTEN_EVENT_MOUSEENTER: + type = SAPP_EVENTTYPE_MOUSE_ENTER; + break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + type = SAPP_EVENTTYPE_MOUSE_LEAVE; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _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; + case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; + case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; + default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; + } + } + else { + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + } + _sapp_call_event(&_sapp.event); + } + /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */ + if (is_button_event) { + _sapp_emsc_update_mouse_lock_state(); + } + } + _sapp_emsc_update_keyboard_state(); + return true; +} + +_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); + _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) { + case DOM_DELTA_PIXEL: scale = -0.04f; break; + case DOM_DELTA_LINE: scale = -1.33f; break; + case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess + default: scale = -0.1f; break; // shouldn't happen + } + _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; + _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; + _sapp_call_event(&_sapp.event); + } + _sapp_emsc_update_keyboard_state(); + _sapp_emsc_update_mouse_lock_state(); + 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; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_KEYDOWN: + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case EMSCRIPTEN_EVENT_KEYUP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + case EMSCRIPTEN_EVENT_KEYPRESS: + type = SAPP_EVENTTYPE_CHAR; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + bool send_keyup_followup = false; + _sapp_init_event(type); + _sapp.event.key_repeat = emsc_event->repeat; + _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); + if (type == SAPP_EVENTTYPE_CHAR) { + // FIXME: this doesn't appear to work on Android Chrome + _sapp.event.char_code = emsc_event->charCode; + /* workaround to make Cmd+V work on Safari */ + if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { + retval = false; + } + } + else { + if (0 != emsc_event->code[0]) { + // This code path is for desktop browsers which send untranslated 'physical' key code strings + // (which is what we actually want for key events) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); + } else { + // This code path is for mobile browsers which only send localized key code + // strings. Note that the translation will only work for a small subset + // of localization-agnostic keys (like Enter, arrow keys, etc...), but + // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); + } + + /* 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 + when the SUPER key is pressed + */ + if ((type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && + (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && + (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) + { + send_keyup_followup = true; + } + // only forward keys to the browser (can further be suppressed by sapp_consume_event()) + switch (_sapp.event.key_code) { + case SAPP_KEYCODE_WORLD_1: + case SAPP_KEYCODE_WORLD_2: + case SAPP_KEYCODE_ESCAPE: + case SAPP_KEYCODE_ENTER: + case SAPP_KEYCODE_TAB: + case SAPP_KEYCODE_BACKSPACE: + case SAPP_KEYCODE_INSERT: + case SAPP_KEYCODE_DELETE: + case SAPP_KEYCODE_RIGHT: + case SAPP_KEYCODE_LEFT: + case SAPP_KEYCODE_DOWN: + case SAPP_KEYCODE_UP: + case SAPP_KEYCODE_PAGE_UP: + case SAPP_KEYCODE_PAGE_DOWN: + case SAPP_KEYCODE_HOME: + case SAPP_KEYCODE_END: + case SAPP_KEYCODE_CAPS_LOCK: + case SAPP_KEYCODE_SCROLL_LOCK: + case SAPP_KEYCODE_NUM_LOCK: + case SAPP_KEYCODE_PRINT_SCREEN: + case SAPP_KEYCODE_PAUSE: + case SAPP_KEYCODE_F1: + case SAPP_KEYCODE_F2: + case SAPP_KEYCODE_F3: + case SAPP_KEYCODE_F4: + case SAPP_KEYCODE_F5: + case SAPP_KEYCODE_F6: + case SAPP_KEYCODE_F7: + case SAPP_KEYCODE_F8: + case SAPP_KEYCODE_F9: + case SAPP_KEYCODE_F10: + case SAPP_KEYCODE_F11: + case SAPP_KEYCODE_F12: + case SAPP_KEYCODE_F13: + case SAPP_KEYCODE_F14: + case SAPP_KEYCODE_F15: + case SAPP_KEYCODE_F16: + case SAPP_KEYCODE_F17: + case SAPP_KEYCODE_F18: + case SAPP_KEYCODE_F19: + case SAPP_KEYCODE_F20: + case SAPP_KEYCODE_F21: + case SAPP_KEYCODE_F22: + case SAPP_KEYCODE_F23: + case SAPP_KEYCODE_F24: + case SAPP_KEYCODE_F25: + case SAPP_KEYCODE_LEFT_SHIFT: + case SAPP_KEYCODE_LEFT_CONTROL: + case SAPP_KEYCODE_LEFT_ALT: + case SAPP_KEYCODE_LEFT_SUPER: + case SAPP_KEYCODE_RIGHT_SHIFT: + case SAPP_KEYCODE_RIGHT_CONTROL: + case SAPP_KEYCODE_RIGHT_ALT: + case SAPP_KEYCODE_RIGHT_SUPER: + case SAPP_KEYCODE_MENU: + /* consume the event */ + break; + default: + /* forward key to browser */ + retval = false; + break; + } + } + if (_sapp_call_event(&_sapp.event)) { + // event was consumed via sapp_consume_event() + retval = true; + } + if (send_keyup_followup) { + _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; + if (_sapp_call_event(&_sapp.event)) { + retval = true; + } + } + } + } + _sapp_emsc_update_keyboard_state(); + _sapp_emsc_update_mouse_lock_state(); + return retval; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool retval = true; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + retval = false; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _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; + } + for (int i = 0; i < _sapp.event.num_touches; i++) { + const EmscriptenTouchPoint* src = &emsc_event->touches[i]; + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)src->identifier; + dst->pos_x = src->targetX * _sapp.dpi_scale; + dst->pos_y = src->targetY * _sapp.dpi_scale; + dst->changed = src->isChanged; + } + _sapp_call_event(&_sapp.event); + } + } + _sapp_emsc_update_keyboard_state(); + return retval; +} + +_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) +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; + case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; + default: type = SAPP_EVENTTYPE_INVALID; break; + } + if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + attrs.alpha = _sapp.desc.alpha; + attrs.depth = true; + attrs.stencil = true; + attrs.antialias = _sapp.sample_count > 1; + attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; + attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; + attrs.enableExtensionsByDefault = true; + #if defined(SOKOL_GLES3) + if (_sapp.desc.gl_force_gles2) { + attrs.majorVersion = 1; + _sapp.gles2_fallback = true; + } + else { + attrs.majorVersion = 2; + } + #endif + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + if (!ctx) { + attrs.majorVersion = 1; + ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + _sapp.gles2_fallback = true; + } + emscripten_webgl_make_context_current(ctx); + + /* some WebGL extension are not enabled automatically by emscripten */ + emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); +} +#endif + +#if defined(SOKOL_WGPU) +#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) +#define _SAPP_EMSC_WGPU_STATE_READY (1) +#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) + +#if defined(__cplusplus) +extern "C" { +#endif +/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); + _sapp.emsc.wgpu.device = (WGPUDevice) device_id; + _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; + _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; + _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; +} +#if defined(__cplusplus) +} // extern "C" +#endif + +/* embedded JS function to handle all the asynchronous WebGPU setup */ +EM_JS(void, sapp_js_wgpu_init, (), { + WebGPU.initManagers(); + // FIXME: the extension activation must be more clever here + navigator.gpu.requestAdapter().then((adapter) => { + console.log("wgpu adapter extensions: " + adapter.extensions); + adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then((device) => { + var gpuContext = document.getElementById("canvas").getContext("gpupresent"); + console.log("wgpu device extensions: " + adapter.extensions); + gpuContext.getSwapChainPreferredFormat(device).then((fmt) => { + const swapChainDescriptor = { device: device, format: fmt }; + const swapChain = gpuContext.configureSwapChain(swapChainDescriptor); + const deviceId = WebGPU.mgrDevice.create(device); + const swapChainId = WebGPU.mgrSwapChain.create(swapChain); + const fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); + console.log("wgpu device: " + device); + console.log("wgpu swap chain: " + swapChain); + console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); + __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); + }); + }); + }); +}); + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { + SOKOL_ASSERT(_sapp.emsc.wgpu.device); + SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); + SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); + + WGPUTextureDescriptor ds_desc; + _sapp_clear(&ds_desc, sizeof(ds_desc)); + ds_desc.usage = WGPUTextureUsage_OutputAttachment; + ds_desc.dimension = WGPUTextureDimension_2D; + ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; + ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; + ds_desc.size.depth = 1; + ds_desc.arrayLayerCount = 1; + ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; + ds_desc.mipLevelCount = 1; + ds_desc.sampleCount = _sapp.sample_count; + _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); + _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); + + if (_sapp.sample_count > 1) { + WGPUTextureDescriptor msaa_desc; + _sapp_clear(&msaa_desc, sizeof(msaa_desc)); + msaa_desc.usage = WGPUTextureUsage_OutputAttachment; + msaa_desc.dimension = WGPUTextureDimension_2D; + msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; + msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; + msaa_desc.size.depth = 1; + msaa_desc.arrayLayerCount = 1; + msaa_desc.format = _sapp.emsc.wgpu.render_format; + msaa_desc.mipLevelCount = 1; + msaa_desc.sampleCount = _sapp.sample_count; + _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); + _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { + if (_sapp.emsc.wgpu.msaa_tex) { + wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); + _sapp.emsc.wgpu.msaa_tex = 0; + } + if (_sapp.emsc.wgpu.msaa_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); + _sapp.emsc.wgpu.msaa_view = 0; + } + if (_sapp.emsc.wgpu.depth_stencil_tex) { + wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); + _sapp.emsc.wgpu.depth_stencil_tex = 0; + } + if (_sapp.emsc.wgpu.depth_stencil_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); + _sapp.emsc.wgpu.depth_stencil_view = 0; + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { + if (_sapp.emsc.wgpu.swapchain_view) { + wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); + } + _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); +} +#endif + +_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + 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(); + } + if (_sapp.drop.enabled) { + sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); + } + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + #endif +} + +_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); + 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(); + } + if (_sapp.drop.enabled) { + sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); + } + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); + #endif +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { + _SOKOL_UNUSED(userData); + _sapp_timing_external(&_sapp.timing, time / 1000.0); + + #if defined(SOKOL_WGPU) + /* + on WebGPU, the emscripten frame callback will already be called while + the asynchronous WebGPU device and swapchain initialization is still + in progress + */ + switch (_sapp.emsc.wgpu.state) { + case _SAPP_EMSC_WGPU_STATE_INITIAL: + /* async JS init hasn't finished yet */ + break; + case _SAPP_EMSC_WGPU_STATE_READY: + /* perform post-async init stuff */ + _sapp_emsc_wgpu_surfaces_create(); + _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; + break; + case _SAPP_EMSC_WGPU_STATE_RUNNING: + /* a regular frame */ + _sapp_emsc_wgpu_next_frame(); + _sapp_frame(); + break; + } + #else + /* WebGL code path */ + _sapp_frame(); + #endif + + /* quit-handling */ + if (_sapp.quit_requested) { + _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + _sapp_call_event(&_sapp.event); + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + _sapp_emsc_unregister_eventhandlers(); + _sapp_call_cleanup(); + _sapp_discard_state(); + return EM_FALSE; + } + return EM_TRUE; +} + +_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { + _sapp_init_state(desc); + sapp_js_init(&_sapp.html5_canvas_selector[1]); + double w, h; + if (_sapp.desc.html5_canvas_resize) { + w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); + h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); + } + else { + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.window_width = (int)roundf(w); + _sapp.window_height = (int)roundf(h); + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + _sapp_emsc_webgl_init(); + #elif defined(SOKOL_WGPU) + sapp_js_wgpu_init(); + #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); + + /* NOT A BUG: do not call _sapp_discard_state() here, instead this is + called in _sapp_emsc_frame() when the application is ordered to quit + */ +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_emsc_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_EMSCRIPTEN */ + +// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>gl helpers +#if defined(SOKOL_GLCORE33) +typedef struct { + int red_bits; + int green_bits; + int blue_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + int samples; + bool doublebuffer; + uintptr_t handle; +} _sapp_gl_fbconfig; + +_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { + _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); + /* -1 means "don't care" */ + fbconfig->red_bits = -1; + fbconfig->green_bits = -1; + fbconfig->blue_bits = -1; + fbconfig->alpha_bits = -1; + fbconfig->depth_bits = -1; + fbconfig->stencil_bits = -1; + fbconfig->samples = -1; +} + +_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { + int missing, least_missing = 1000000; + int color_diff, least_color_diff = 10000000; + int extra_diff, least_extra_diff = 10000000; + const _sapp_gl_fbconfig* current; + const _sapp_gl_fbconfig* closest = 0; + for (int i = 0; i < count; i++) { + current = alternatives + i; + if (desired->doublebuffer != current->doublebuffer) { + continue; + } + missing = 0; + if (desired->alpha_bits > 0 && current->alpha_bits == 0) { + missing++; + } + if (desired->depth_bits > 0 && current->depth_bits == 0) { + missing++; + } + if (desired->stencil_bits > 0 && current->stencil_bits == 0) { + missing++; + } + if (desired->samples > 0 && current->samples == 0) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + if (missing < least_missing) { + closest = current; + } + else if (missing == least_missing) { + if ((color_diff < least_color_diff) || + (color_diff == least_color_diff && extra_diff < least_extra_diff)) + { + closest = current; + } + } + if (current == closest) { + least_missing = missing; + least_color_diff = color_diff; + least_extra_diff = extra_diff; + } + } + return closest; +} +#endif + +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if defined(_SAPP_WIN32) +_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } + else { + /* input string doesn't fit into destination buffer */ + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { + /* same as GLFW */ + _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; + _sapp.keycodes[0x002] = SAPP_KEYCODE_1; + _sapp.keycodes[0x003] = SAPP_KEYCODE_2; + _sapp.keycodes[0x004] = SAPP_KEYCODE_3; + _sapp.keycodes[0x005] = SAPP_KEYCODE_4; + _sapp.keycodes[0x006] = SAPP_KEYCODE_5; + _sapp.keycodes[0x007] = SAPP_KEYCODE_6; + _sapp.keycodes[0x008] = SAPP_KEYCODE_7; + _sapp.keycodes[0x009] = SAPP_KEYCODE_8; + _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; + _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; + _sapp.keycodes[0x030] = SAPP_KEYCODE_B; + _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; + _sapp.keycodes[0x020] = SAPP_KEYCODE_D; + _sapp.keycodes[0x012] = SAPP_KEYCODE_E; + _sapp.keycodes[0x021] = SAPP_KEYCODE_F; + _sapp.keycodes[0x022] = SAPP_KEYCODE_G; + _sapp.keycodes[0x023] = SAPP_KEYCODE_H; + _sapp.keycodes[0x017] = SAPP_KEYCODE_I; + _sapp.keycodes[0x024] = SAPP_KEYCODE_J; + _sapp.keycodes[0x025] = SAPP_KEYCODE_K; + _sapp.keycodes[0x026] = SAPP_KEYCODE_L; + _sapp.keycodes[0x032] = SAPP_KEYCODE_M; + _sapp.keycodes[0x031] = SAPP_KEYCODE_N; + _sapp.keycodes[0x018] = SAPP_KEYCODE_O; + _sapp.keycodes[0x019] = SAPP_KEYCODE_P; + _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x013] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; + _sapp.keycodes[0x014] = SAPP_KEYCODE_T; + _sapp.keycodes[0x016] = SAPP_KEYCODE_U; + _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; + _sapp.keycodes[0x011] = SAPP_KEYCODE_W; + _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; + _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; + _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; + _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; + _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; + _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; + _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; + _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; + _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; + _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; +} +#endif // _SAPP_WIN32 + +#if defined(_SAPP_WIN32) + +#if defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sapp_d3d11_Release(self) (self)->Release() +#define _sapp_win32_refiid(iid) iid +#else +#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#define _sapp_win32_refiid(iid) &iid +#endif + +#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 const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; +static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; + +static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { + #if defined(__cplusplus) + return self->GetBuffer(Buffer, riid, ppSurface); + #else + return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); + #endif +} + +static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { + #if defined(__cplusplus) + return self->QueryInterface(riid, ppvObject); + #else + return self->lpVtbl->QueryInterface(self, riid, ppvObject); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { + #if defined(__cplusplus) + self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #else + self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #endif +} + +static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { + #if defined(__cplusplus) + return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); + #else + return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); + #endif +} + +static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { + #if defined(__cplusplus) + return self->Present(SyncInterval, Flags); + #else + return self->lpVtbl->Present(self, SyncInterval, Flags); + #endif +} + +static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { + #if defined(__cplusplus) + return self->GetFrameStatistics(pStats); + #else + return self->lpVtbl->GetFrameStatistics(self, pStats); + #endif +} + +static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { + #if defined(__cplusplus) + return self->SetMaximumFrameLatency(MaxLatency); + #else + return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); + #endif +} + +static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { + #if defined(__cplusplus) + return self->GetAdapter(pAdapter); + #else + return self->lpVtbl->GetAdapter(self, pAdapter); + #endif +} + +static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { + #if defined(__cplusplus) + return self->GetParent(riid, ppParent); + #else + return self->lpVtbl->GetParent(self, riid, ppParent); + #endif +} + +static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { + #if defined(__cplusplus) + return self->MakeWindowAssociation(WindowHandle, Flags); + #else + return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); + #endif +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { + DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; + sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; + sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; + sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + sc_desc->BufferDesc.RefreshRate.Numerator = 60; + sc_desc->BufferDesc.RefreshRate.Denominator = 1; + sc_desc->OutputWindow = _sapp.win32.hwnd; + sc_desc->Windowed = true; + if (_sapp.win32.is_win10_or_greater) { + sc_desc->BufferCount = 2; + sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = true; + } + else { + sc_desc->BufferCount = 1; + sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = false; + } + sc_desc->SampleDesc.Count = 1; + sc_desc->SampleDesc.Quality = 0; + sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; + #if defined(SOKOL_DEBUG) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + #endif + D3D_FEATURE_LEVEL feature_level; + HRESULT hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + _SOKOL_UNUSED(hr); + #if defined(SOKOL_DEBUG) + if (!SUCCEEDED(hr)) { + // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the + // 'D3D11 debug layer' stopped working, indicated by the error message: + // === + // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. + // These flags must be removed, or the Windows 10 SDK must be installed. + // Flags include: D3D11_CREATE_DEVICE_DEBUG + // === + // + // ...just retry with the DEBUG flag switched off + _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); + create_flags &= ~D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + } + #endif + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); + + // minimize frame latency, disable Alt-Enter + hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); + if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { + _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); + IDXGIAdapter* dxgi_adapter = 0; + hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); + if (SUCCEEDED(hr) && dxgi_adapter) { + IDXGIFactory* dxgi_factory = 0; + hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); + if (SUCCEEDED(hr)) { + _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); + _SAPP_SAFE_RELEASE(dxgi_factory); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); + } + _SAPP_SAFE_RELEASE(dxgi_adapter); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); + } + } + else { + _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device); +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { + SOKOL_ASSERT(0 == _sapp.d3d11.rt); + SOKOL_ASSERT(0 == _sapp.d3d11.rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.ds); + SOKOL_ASSERT(0 == _sapp.d3d11.dsv); + + HRESULT hr; + + /* view for the swapchain-created framebuffer */ + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); + + /* common desc for MSAA and depth-stencil texture */ + D3D11_TEXTURE2D_DESC tex_desc; + _sapp_clear(&tex_desc, sizeof(tex_desc)); + tex_desc.Width = (UINT)_sapp.framebuffer_width; + tex_desc.Height = (UINT)_sapp.framebuffer_height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Usage = D3D11_USAGE_DEFAULT; + tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; + tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + + /* create MSAA texture and view if antialiasing requested */ + if (_sapp.sample_count > 1) { + tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); + } + + /* texture and view for the depth-stencil-surface */ + tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); + hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { + if (_sapp.d3d11.swap_chain) { + _sapp_d3d11_destroy_default_render_target(); + _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); + _sapp_d3d11_create_default_render_target(); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { + /* do MSAA resolve if needed */ + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.rt); + SOKOL_ASSERT(_sapp.d3d11.msaa_rt); + _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); + } + UINT flags = 0; + if (_sapp.win32.is_win10_or_greater && do_not_wait) { + /* this hack/workaround somewhat improves window-movement and -sizing + responsiveness when rendering is controlled via WM_TIMER during window + move and resize on NVIDIA cards on Win10 with recent drivers. + */ + flags = DXGI_PRESENT_DO_NOT_WAIT; + } + _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); +} + +#endif /* SOKOL_D3D11 */ + +#if defined(SOKOL_GLCORE33) +_SOKOL_PRIVATE void _sapp_wgl_init(void) { + _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); + if (!_sapp.wgl.opengl32) { + _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.opengl32); + _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); + SOKOL_ASSERT(_sapp.wgl.CreateContext); + _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); + SOKOL_ASSERT(_sapp.wgl.DeleteContext); + _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); + SOKOL_ASSERT(_sapp.wgl.GetProcAddress); + _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); + SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); + _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); + SOKOL_ASSERT(_sapp.wgl.MakeCurrent); + + _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, + L"SOKOLAPP", + L"sokol-app message window", + WS_CLIPSIBLINGS|WS_CLIPCHILDREN, + 0, 0, 1, 1, + NULL, NULL, + GetModuleHandleW(NULL), + NULL); + if (!_sapp.wgl.msg_hwnd) { + _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.msg_hwnd); + ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); + MSG msg; + while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); + if (!_sapp.wgl.msg_dc) { + _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { + SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); + DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; + FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; +} + +_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext && extensions); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { + SOKOL_ASSERT(ext); + if (_sapp.wgl.GetExtensionsStringEXT) { + const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + if (_sapp.wgl.GetExtensionsStringARB) { + const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + return false; +} + +_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { + SOKOL_ASSERT(_sapp.wgl.msg_dc); + PIXELFORMATDESCRIPTOR pfd; + _sapp_clear(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); + } + HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); + if (!rc) { + _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); + } + if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); + } + _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); + _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); + _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); + _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); + _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); + _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); + _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); + _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); + _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); + _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); + _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); + _sapp.wgl.DeleteContext(rc); +} + +_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + int value = 0; + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); + } + return value; +} + +_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { + SOKOL_ASSERT(_sapp.win32.dc); + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + const _sapp_gl_fbconfig* closest; + + int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); + _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); + SOKOL_ASSERT(usable_configs); + int usable_count = 0; + for (int i = 0; i < native_count; i++) { + const int n = i + 1; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { + continue; + } + if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { + continue; + } + if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { + continue; + } + u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); + u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); + u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); + u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); + u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); + u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); + if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { + u->doublebuffer = true; + } + if (_sapp.wgl.arb_multisample) { + u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); + } + u->handle = (uintptr_t)n; + usable_count++; + } + SOKOL_ASSERT(usable_count > 0); + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + int pixel_format = 0; + if (closest) { + pixel_format = (int) closest->handle; + } + _sapp_free(usable_configs); + return pixel_format; +} + +_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { + int pixel_format = _sapp_wgl_find_pixel_format(); + if (0 == pixel_format) { + _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); + } + PIXELFORMATDESCRIPTOR pfd; + if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { + _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); + } + if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { + _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); + } + if (!_sapp.wgl.arb_create_context) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); + } + if (!_sapp.wgl.arb_create_context_profile) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); + } + const int attrs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, 0 + }; + _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); + if (!_sapp.wgl.gl_ctx) { + const DWORD err = GetLastError(); + if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED); + } + else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); + } + else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { + _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); + } + else { + _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); + } + } + _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); + if (_sapp.wgl.ext_swap_control) { + /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ + _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { + SOKOL_ASSERT(_sapp.wgl.gl_ctx); + _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); + _sapp.wgl.gl_ctx = 0; +} + +_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { + SOKOL_ASSERT(_sapp.win32.dc); + /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ + SwapBuffers(_sapp.win32.dc); +} +#endif /* SOKOL_GLCORE33 */ + +_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); + if (bytes_needed <= dst_num_bytes) { + WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); + return true; + } + else { + return false; + } +} + +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { + RECT rect; + if (GetClientRect(_sapp.win32.hwnd, &rect)) { + float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; + float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; + _sapp.window_width = (int)roundf(window_width); + _sapp.window_height = (int)roundf(window_height); + int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); + int fb_height = (int)roundf(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; + return true; + } + } + else { + _sapp.window_width = _sapp.window_height = 1; + _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + } + return false; +} + +_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { + HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo; + _sapp_clear(&minfo, sizeof(minfo)); + minfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &minfo); + const RECT mr = minfo.rcMonitor; + const int monitor_w = mr.right - mr.left; + const int monitor_h = mr.bottom - mr.top; + + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD win_style; + RECT rect = { 0, 0, 0, 0 }; + + _sapp.fullscreen = fullscreen; + if (!_sapp.fullscreen) { + win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect = _sapp.win32.stored_window_rect; + } + else { + GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); + win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; + rect.left = mr.left; + rect.top = mr.top; + rect.right = rect.left + monitor_w; + rect.bottom = rect.top + monitor_h; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + } + const int win_w = rect.right - rect.left; + const int win_h = rect.bottom - rect.top; + const int win_x = rect.left; + const int win_y = rect.top; + SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { + _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined + // before windows.h is included, but we can't guarantee that because + // the sokol_app.h implementation may be included with other implementations + // in the same compilation unit + int id = 0; + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL + case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM + case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS + case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND + case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE + case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS + case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE + case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW + case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL + case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO + default: break; + } + if (id != 0) { + _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); + } + // fallback: default cursor + if (0 == _sapp.win32.cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp_win32_init_cursor((sapp_mouse_cursor)i); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { + POINT pos; + if (!GetCursorPos(&pos)) { + return false; + } + if (WindowFromPoint(pos) != _sapp.win32.hwnd) { + return false; + } + RECT area; + GetClientRect(_sapp.win32.hwnd, &area); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); + return PtInRect(&area, pos) == TRUE; +} + +_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + if (!shown) { + SetCursor(NULL); + } + else { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); + SetCursor(_sapp.win32.cursors[cursor]); + } +} + +_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { + if (0 == _sapp.win32.mouse_capture_mask) { + SetCapture(_sapp.win32.hwnd); + } + _sapp.win32.mouse_capture_mask |= btn_mask; +} + +_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { + if (0 != _sapp.win32.mouse_capture_mask) { + _sapp.win32.mouse_capture_mask &= ~btn_mask; + if (0 == _sapp.win32.mouse_capture_mask) { + ReleaseCapture(); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + _sapp_win32_release_mouse(0xFF); + if (_sapp.mouse.locked) { + /* store the current mouse position, so it can be restored when unlocked */ + POINT pos; + BOOL res = GetCursorPos(&pos); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + _sapp.win32.mouse_locked_x = pos.x; + _sapp.win32.mouse_locked_y = pos.y; + + /* while the mouse is locked, make the mouse cursor invisible and + confine the mouse movement to a small rectangle inside our window + (so that we don't miss any mouse up events) + */ + RECT client_rect = { + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y, + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y + }; + ClipCursor(&client_rect); + + /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ + ShowCursor(FALSE); + + /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ + const RAWINPUTDEVICE rid = { + 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC + 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE + 0, // dwFlags + _sapp.win32.hwnd // hwndTarget + }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); + } + /* in case the raw mouse device only supports absolute position reporting, + we need to skip the dx/dy compution for the first WM_INPUT event + */ + _sapp.win32.raw_input_mousepos_valid = false; + } + else { + /* disable raw input for mouse */ + const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); + } + + /* let the mouse roam freely again */ + ClipCursor(NULL); + ShowCursor(TRUE); + + /* restore the 'pre-locked' mouse position */ + BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { + const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + if (cur_monitor != _sapp.win32.hmonitor) { + _sapp.win32.hmonitor = cur_monitor; + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { + uint32_t mods = 0; + if (GetKeyState(VK_SHIFT) & (1<<15)) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (GetKeyState(VK_CONTROL) & (1<<15)) { + mods |= SAPP_MODIFIER_CTRL; + } + if (GetKeyState(VK_MENU) & (1<<15)) { + mods |= SAPP_MODIFIER_ALT; + } + 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; +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { + if (!_sapp.mouse.locked) { + const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first event + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.mouse_button = btn; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.scroll_x = -x / 30.0f; + _sapp.event.scroll_y = y / 30.0f; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { + if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.key_code = _sapp.keycodes[vk]; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { + if (_sapp_events_enabled() && (c >= 32)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.char_code = c; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { + /* called on WM_DPICHANGED, which will only be sent to the application + if sapp_desc.high_dpi is true and the Windows version is recent enough + to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + */ + SOKOL_ASSERT(_sapp.desc.high_dpi); + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (!user32) { + return; + } + typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); + GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); + if (fn_getdpiforwindow) { + UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); + // NOTE: for high-dpi apps, mouse_scale remains one + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.dpi_scale = _sapp.win32.dpi.window_scale; + SetWindowPos(hWnd, 0, + proposed_win_rect->left, + proposed_win_rect->top, + proposed_win_rect->right - proposed_win_rect->left, + proposed_win_rect->bottom - proposed_win_rect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + FreeLibrary(user32); +} + +_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { + if (!_sapp.drop.enabled) { + return; + } + _sapp_clear_drop_buffer(); + bool drop_failed = false; + const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); + _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; + for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { + const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; + WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); + DragQueryFileW(hdrop, i, buffer, num_chars); + if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + } + _sapp_free(buffer); + } + DragFinish(hdrop); + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { + #if defined(SOKOL_D3D11) + // on D3D11, use the more precise DXGI timestamp + if (_sapp.d3d11.use_dxgi_frame_stats) { + DXGI_FRAME_STATISTICS dxgi_stats; + _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); + HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); + if (SUCCEEDED(hr)) { + if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { + if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { + _sapp_timing_discontinuity(&_sapp.timing); + } + _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; + LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); + _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); + } + return; + } + } + // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_timing_measure(&_sapp.timing); + #endif +} + +_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (!_sapp.win32.in_create_window) { + switch (uMsg) { + case WM_CLOSE: + /* only give user a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a change to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + PostQuitMessage(0); + } + return 0; + case WM_SYSCOMMAND: + switch (wParam & 0xFFF0) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + if (_sapp.fullscreen) { + /* disable screen saver and blanking in fullscreen mode */ + return 0; + } + break; + case SC_KEYMENU: + /* user trying to access menu via ALT */ + return 0; + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + { + const bool iconified = wParam == SIZE_MINIMIZED; + if (iconified != _sapp.win32.iconified) { + _sapp.win32.iconified = iconified; + if (iconified) { + _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else { + _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + break; + case WM_SETFOCUS: + _sapp_win32_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_app_event(SAPP_EVENTTYPE_UNFOCUSED); + break; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); + return TRUE; + } + break; + case WM_DPICHANGED: + { + /* Update window's DPI and size if its moved to another monitor with a different DPI + Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. + */ + _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); + break; + } + case WM_LBUTTONDOWN: + _sapp_win32_mouse_update(lParam); + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); + _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + /* mouse only reports absolute position + NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. + (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). + See: https://github.com/floooh/sokol/issues/806 and + https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) + */ + LONG new_x = raw_mouse_data->data.mouse.lLastX; + LONG new_y = raw_mouse_data->data.mouse.lLastY; + if (_sapp.win32.raw_input_mousepos_valid) { + _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); + } + _sapp.win32.raw_input_mousepos_x = new_x; + _sapp.win32.raw_input_mousepos_y = new_y; + _sapp.win32.raw_input_mousepos_valid = true; + } + else { + /* mouse reports movement delta (this seems to be the common case) */ + _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; + _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; + } + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + + case WM_MOUSELEAVE: + if (!_sapp.mouse.locked) { + _sapp.win32.mouse_tracked = false; + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + case WM_MOUSEWHEEL: + _sapp_win32_mouse_update(lParam); + _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); + break; + case WM_MOUSEHWHEEL: + _sapp_win32_mouse_update(lParam); + _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); + break; + case WM_CHAR: + _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); + break; + case WM_KEYUP: + case WM_SYSKEYUP: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); + break; + case WM_ENTERSIZEMOVE: + SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); + break; + case WM_EXITSIZEMOVE: + KillTimer(_sapp.win32.hwnd, 1); + break; + case WM_TIMER: + _sapp_win32_timing_measure(); + _sapp_frame(); + #if defined(SOKOL_D3D11) + // present with DXGI_PRESENT_DO_NOT_WAIT + _sapp_d3d11_present(true); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_swap_buffers(); + #endif + /* NOTE: resizing the swap-chain during resize leads to a substantial + memory spike (hundreds of megabytes for a few seconds). + + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + */ + break; + case WM_NCLBUTTONDOWN: + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT point; + GetCursorPos(&point); + ScreenToClient(_sapp.win32.hwnd, &point); + PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + } + break; + case WM_DROPFILES: + _sapp_win32_files_dropped((HDROP)wParam); + break; + case WM_DISPLAYCHANGE: + // refresh rate might have changed + _sapp_timing_reset(&_sapp.timing); + break; + + default: + break; + } + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +_SOKOL_PRIVATE void _sapp_win32_create_window(void) { + WNDCLASSW wndclassw; + _sapp_clear(&wndclassw, sizeof(wndclassw)); + wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; + wndclassw.hInstance = GetModuleHandleW(NULL); + wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndclassw.lpszClassName = L"SOKOLAPP"; + RegisterClassW(&wndclassw); + + /* NOTE: regardless whether fullscreen is requested or not, a regular + windowed-mode window will always be created first (however in hidden + mode, so that no windowed-mode window pops up before the fullscreen window) + */ + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + RECT rect = { 0, 0, 0, 0 }; + DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); + const bool use_default_width = 0 == _sapp.window_width; + const bool use_default_height = 0 == _sapp.window_height; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + const int win_width = rect.right - rect.left; + const int win_height = rect.bottom - rect.top; + _sapp.win32.in_create_window = true; + _sapp.win32.hwnd = CreateWindowExW( + win_ex_style, // dwExStyle + L"SOKOLAPP", // lpClassName + _sapp.window_title_wide, // lpWindowName + win_style, // dwStyle + CW_USEDEFAULT, // X + SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! + use_default_width ? CW_USEDEFAULT : win_width, // nWidth + use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) + NULL, // hWndParent + NULL, // hMenu + GetModuleHandle(NULL), // hInstance + NULL); // lParam + _sapp.win32.in_create_window = false; + _sapp.win32.dc = GetDC(_sapp.win32.hwnd); + _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + SOKOL_ASSERT(_sapp.win32.dc); + + /* this will get the actual windowed-mode window size, if fullscreen + is requested, the set_fullscreen function will then capture the + current window rectangle, which then might be used later to + restore the window position when switching back to windowed + */ + _sapp_win32_update_dimensions(); + if (_sapp.fullscreen) { + _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); + _sapp_win32_update_dimensions(); + } + ShowWindow(_sapp.win32.hwnd, SW_SHOW); + DragAcceptFiles(_sapp.win32.hwnd, 1); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { + DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; + 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; + if (_sapp.desc.win32_console_create) { + con_valid = AllocConsole(); + } + else if (_sapp.desc.win32_console_attach) { + con_valid = AttachConsole(ATTACH_PARENT_PROCESS); + } + if (con_valid) { + FILE* res_fp = 0; + errno_t err; + err = freopen_s(&res_fp, "CON", "w", stdout); + (void)err; + err = freopen_s(&res_fp, "CON", "w", stderr); + (void)err; + } + } + if (_sapp.desc.win32_console_utf8) { + _sapp.win32.orig_codepage = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + } +} + +_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { + if (_sapp.desc.win32_console_utf8) { + SetConsoleOutputCP(_sapp.win32.orig_codepage); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { + + DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); + typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); + typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 + typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); + typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; + SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; + GETDPIFORMONITOR_T fn_getdpiformonitor = 0; + SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; + + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (user32) { + fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); + fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); + } + HINSTANCE shcore = LoadLibraryA("shcore.dll"); + if (shcore) { + fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); + fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); + } + /* + NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): + + These are different attempts to get DPI handling on Windows right, from oldest + to newest. SetProcessDpiAwarenessContext() is required for the new + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. + */ + if (fn_setprocessdpiawareness) { + if (_sapp.desc.high_dpi) { + /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, + if that fails, fall back to system-dpi-awareness + */ + _sapp.win32.dpi.aware = true; + DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; + if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { + // fallback to system-dpi-aware + fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); + } + } + else { + /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ + _sapp.win32.dpi.aware = false; + fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); + } + } + else if (fn_setprocessdpiaware) { + // fallback for Windows 7 + _sapp.win32.dpi.aware = true; + fn_setprocessdpiaware(); + } + /* get dpi scale factor for main monitor */ + if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { + POINT pt = { 1, 1 }; + HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + UINT dpix, dpiy; + HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); + _SOKOL_UNUSED(hr); + SOKOL_ASSERT(SUCCEEDED(hr)); + /* clamp window scale to an integer factor */ + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + } + else { + _sapp.win32.dpi.window_scale = 1.0f; + } + if (_sapp.desc.high_dpi) { + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.win32.dpi.mouse_scale = 1.0f; + } + else { + _sapp.win32.dpi.content_scale = 1.0f; + _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; + } + _sapp.dpi_scale = _sapp.win32.dpi.content_scale; + if (user32) { + FreeLibrary(user32); + } + if (shcore) { + FreeLibrary(shcore); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { + SOKOL_ASSERT(str); + SOKOL_ASSERT(_sapp.win32.hwnd); + SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + + if (!OpenClipboard(_sapp.win32.hwnd)) { + return false; + } + + HANDLE object = 0; + wchar_t* wchar_buf = 0; + + const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); + object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); + if (NULL == object) { + goto error; + } + wchar_buf = (wchar_t*) GlobalLock(object); + if (NULL == wchar_buf) { + goto error; + } + if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { + goto error; + } + GlobalUnlock(object); + wchar_buf = 0; + EmptyClipboard(); + // NOTE: when successful, SetClipboardData() takes ownership of memory object! + if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { + goto error; + } + CloseClipboard(); + return true; + +error: + if (wchar_buf) { + GlobalUnlock(object); + } + if (object) { + GlobalFree(object); + } + CloseClipboard(); + return false; +} + +_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + SOKOL_ASSERT(_sapp.win32.hwnd); + if (!OpenClipboard(_sapp.win32.hwnd)) { + /* silently ignore any errors and just return the current + content of the local clipboard buffer + */ + return _sapp.clipboard.buffer; + } + HANDLE object = GetClipboardData(CF_UNICODETEXT); + if (!object) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); + if (!wchar_buf) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + GlobalUnlock(object); + CloseClipboard(); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); +} + +_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { + BITMAPV5HEADER bi; + _sapp_clear(&bi, 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; + _sapp_clear(&icon_info, 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 + + From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 +*/ +_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { + HMODULE h = GetModuleHandleW(L"kernel32.dll"); + if (NULL != h) { + return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_win32_init_console(); + _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); + _sapp_win32_init_keytable(); + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + _sapp_win32_init_dpi(); + _sapp_win32_init_cursors(); + _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(); + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_init(); + _sapp_wgl_load_extensions(); + _sapp_wgl_create_context(); + #endif + _sapp.valid = true; + + bool done = false; + while (!(done || _sapp.quit_ordered)) { + _sapp_win32_timing_measure(); + MSG msg; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (WM_QUIT == msg.message) { + done = true; + continue; + } + else { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + _sapp_frame(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_present(false); + if (IsIconic(_sapp.win32.hwnd)) { + Sleep((DWORD)(16 * _sapp.swap_interval)); + } + #endif + #if defined(SOKOL_GLCORE33) + _sapp_wgl_swap_buffers(); + #endif + /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + /* check if the window monitor has changed, need to reset timing because + the new monitor might have a different refresh rate + */ + if (_sapp_win32_update_monitor()) { + _sapp_timing_reset(&_sapp.timing); + } + if (_sapp.quit_requested) { + PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); + } + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #else + _sapp_wgl_destroy_context(); + _sapp_wgl_shutdown(); + #endif + _sapp_win32_destroy_window(); + _sapp_win32_destroy_icons(); + _sapp_win32_restore_console(); + _sapp_discard_state(); +} + +_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { + int argc = 0; + char** argv = 0; + char* args; + + LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); + if (w_argv == NULL) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + } else { + size_t size = wcslen(w_command_line) * 4; + argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); + SOKOL_ASSERT(argv); + args = (char*) &argv[argc + 1]; + int n; + for (int i = 0; i < argc; ++i) { + n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); + if (n == 0) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + break; + } + argv[i] = args; + size -= (size_t)n; + args += n; + } + LocalFree(w_argv); + } + *o_argc = argc; + return argv; +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(SOKOL_WIN32_FORCE_MAIN) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_win32_run(&desc); + return 0; +} +#else +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + _SOKOL_UNUSED(hInstance); + _SOKOL_UNUSED(hPrevInstance); + _SOKOL_UNUSED(lpCmdLine); + _SOKOL_UNUSED(nCmdShow); + int argc_utf8 = 0; + char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); + sapp_desc desc = sokol_main(argc_utf8, argv_utf8); + _sapp_win32_run(&desc); + _sapp_free(argv_utf8); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_MAIN */ +#endif /* SOKOL_NO_ENTRY */ + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif /* _SAPP_WIN32 */ + +// █████ ███ ██ ██████ ██████ ██████ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ +// +// >>android +#if defined(_SAPP_ANDROID) + +/* android loop thread */ +_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { + SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); + + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return false; + } + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + return false; + } + _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; + + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint cfg_attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2?EGL_OPENGL_ES2_BIT:EGL_OPENGL_ES3_BIT, + #else + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + #endif + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 0, + EGL_NONE, + }; + EGLConfig available_cfgs[32]; + EGLint cfg_count; + eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); + SOKOL_ASSERT(cfg_count > 0); + SOKOL_ASSERT(cfg_count <= 32); + + /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ + EGLConfig config; + bool exact_cfg_found = false; + for (int i = 0; i < cfg_count; ++i) { + EGLConfig c = available_cfgs[i]; + EGLint r, g, b, a, d; + if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && + r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { + exact_cfg_found = true; + config = c; + break; + } + } + if (!exact_cfg_found) { + config = available_cfgs[0]; + } + + EGLint ctx_attributes[] = { + #if defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, + #else + EGL_CONTEXT_CLIENT_VERSION, 2, + #endif + EGL_NONE, + }; + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); + if (context == EGL_NO_CONTEXT) { + return false; + } + + _sapp.android.config = config; + _sapp.android.display = display; + _sapp.android.context = context; + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { + if (_sapp.android.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } + if (_sapp.android.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.android.display, _sapp.android.context); + _sapp.android.context = EGL_NO_CONTEXT; + } + eglTerminate(_sapp.android.display); + _sapp.android.display = EGL_NO_DISPLAY; + } +} + +_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + /* TODO: set window flags */ + /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ + + /* create egl surface and make it current */ + EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); + if (surface == EGL_NO_SURFACE) { + return false; + } + if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { + return false; + } + _sapp.android.surface = surface; + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { + if (_sapp.android.display == EGL_NO_DISPLAY) { + return; + } + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } +} + +_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + const int32_t win_w = ANativeWindow_getWidth(window); + const int32_t win_h = ANativeWindow_getHeight(window); + SOKOL_ASSERT(win_w >= 0 && win_h >= 0); + const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); + _sapp.window_width = win_w; + _sapp.window_height = win_h; + if (win_changed || force_update) { + if (!_sapp.desc.high_dpi) { + const int32_t buf_w = win_w / 2; + const int32_t buf_h = win_h / 2; + EGLint format; + EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); + SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); + /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions + as the ANativeWindow size results in weird display artefacts, that's + why it's only called when the buffer geometry is different from + the window size + */ + int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); + SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); + } + } + + /* query surface size */ + EGLint fb_w, fb_h; + EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); + EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); + SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); + SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); + const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); + _sapp.framebuffer_width = fb_w; + _sapp.framebuffer_height = fb_h; + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + if (win_changed || fb_changed || force_update) { + if (!_sapp.first_frame) { + _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_android_cleanup(void) { + if (_sapp.android.surface != EGL_NO_SURFACE) { + /* egl context is bound, cleanup gracefully */ + if (_sapp.init_called && !_sapp.cleanup_called) { + _sapp_call_cleanup(); + } + } + /* always try to cleanup by destroying egl context */ + _sapp_android_cleanup_egl(); +} + +_SOKOL_PRIVATE void _sapp_android_shutdown(void) { + /* try to cleanup while we still have a surface and can call cleanup_cb() */ + _sapp_android_cleanup(); + /* request exit */ + ANativeActivity_finish(_sapp.android.activity); +} + +_SOKOL_PRIVATE void _sapp_android_frame(void) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + _sapp_timing_measure(&_sapp.timing); + _sapp_android_update_dimensions(_sapp.android.current.window, false); + _sapp_frame(); + eglSwapBuffers(_sapp.android.display, _sapp.android.surface); +} + +_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { + return false; + } + if (!_sapp_events_enabled()) { + return false; + } + int32_t action_idx = AMotionEvent_getAction(e); + int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case AMOTION_EVENT_ACTION_MOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case AMOTION_EVENT_ACTION_CANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + _sapp_init_event(type); + _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); + dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { + dst->changed = (i == idx); + } else { + dst->changed = true; + } + } + _sapp_call_event(&_sapp.event); + return true; +} + +_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { + return false; + } + if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { + /* FIXME: this should be hooked into a "really quit?" mechanism + so the app can ask the user for confirmation, this is currently + generally missing in sokol_app.h + */ + _sapp_android_shutdown(); + return true; + } + return false; +} + +_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(fd); + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); + return 1; + } + SOKOL_ASSERT(_sapp.android.current.input); + AInputEvent* event = NULL; + while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { + if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { + continue; + } + int32_t handled = 0; + if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { + handled = 1; + } + AInputQueue_finishEvent(_sapp.android.current.input, event, handled); + } + return 1; +} + +_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); + return 1; + } + + _sapp_android_msg_t msg; + if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_READ_MSG_FAILED); + return 1; + } + + pthread_mutex_lock(&_sapp.android.pt.mutex); + switch (msg) { + case _SOKOL_ANDROID_MSG_CREATE: + { + _SAPP_INFO(ANDROID_MSG_CREATE); + SOKOL_ASSERT(!_sapp.valid); + bool result = _sapp_android_init_egl(); + SOKOL_ASSERT(result); _SOKOL_UNUSED(result); + _sapp.valid = true; + _sapp.android.has_created = true; + } + break; + case _SOKOL_ANDROID_MSG_RESUME: + _SAPP_INFO(ANDROID_MSG_RESUME); + _sapp.android.has_resumed = true; + _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); + break; + case _SOKOL_ANDROID_MSG_PAUSE: + _SAPP_INFO(ANDROID_MSG_PAUSE); + _sapp.android.has_resumed = false; + _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); + break; + case _SOKOL_ANDROID_MSG_FOCUS: + _SAPP_INFO(ANDROID_MSG_FOCUS); + _sapp.android.has_focus = true; + break; + case _SOKOL_ANDROID_MSG_NO_FOCUS: + _SAPP_INFO(ANDROID_MSG_NO_FOCUS); + _sapp.android.has_focus = false; + break; + case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: + _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); + if (_sapp.android.current.window != _sapp.android.pending.window) { + if (_sapp.android.current.window != NULL) { + _sapp_android_cleanup_egl_surface(); + } + if (_sapp.android.pending.window != NULL) { + if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { + _sapp_android_update_dimensions(_sapp.android.pending.window, true); + } else { + _sapp_android_shutdown(); + } + } + } + _sapp.android.current.window = _sapp.android.pending.window; + break; + case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: + _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); + if (_sapp.android.current.input != _sapp.android.pending.input) { + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + if (_sapp.android.pending.input != NULL) { + AInputQueue_attachLooper( + _sapp.android.pending.input, + _sapp.android.looper, + ALOOPER_POLL_CALLBACK, + _sapp_android_input_cb, + NULL); /* data */ + } + } + _sapp.android.current.input = _sapp.android.pending.input; + break; + case _SOKOL_ANDROID_MSG_DESTROY: + _SAPP_INFO(ANDROID_MSG_DESTROY); + _sapp_android_cleanup(); + _sapp.valid = false; + _sapp.android.is_thread_stopping = true; + break; + default: + _SAPP_WARN(ANDROID_UNKNOWN_MSG); + break; + } + pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ + pthread_mutex_unlock(&_sapp.android.pt.mutex); + return 1; +} + +_SOKOL_PRIVATE bool _sapp_android_should_update(void) { + bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; + bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; + return is_in_front && has_surface; +} + +_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { + SOKOL_ASSERT(_sapp.valid); + /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ + if (shown) { + ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); + } else { + ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); + } +} + +_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { + _SOKOL_UNUSED(arg); + _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); + + _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); + ALooper_addFd(_sapp.android.looper, + _sapp.android.pt.read_from_main_fd, + ALOOPER_POLL_CALLBACK, + ALOOPER_EVENT_INPUT, + _sapp_android_main_cb, + NULL); /* data */ + + /* signal start to main thread */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_started = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* main loop */ + while (!_sapp.android.is_thread_stopping) { + /* sokol frame */ + if (_sapp_android_should_update()) { + _sapp_android_frame(); + } + + /* process all events (or stop early if app is requested to quit) */ + bool process_events = true; + while (process_events && !_sapp.android.is_thread_stopping) { + bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); + process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; + } + } + + /* cleanup thread */ + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + + /* the following causes heap corruption on exit, why?? + ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); + ALooper_release(_sapp.android.looper);*/ + + /* signal "destroyed" */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_stopped = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); + return NULL; +} + +/* android main/ui thread */ +_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { + if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); +} + +_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); + _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); +} + +_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); + *out_size = 0; + return NULL; +} + +_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); + if (has_focus) { + _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); + } else { + _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); + _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); +} + +_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.window = window; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); + while (_sapp.android.current.window != window) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); + _sapp_android_msg_set_native_window(window); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(window); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); + _sapp_android_msg_set_native_window(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.input = input; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); + while (_sapp.android.current.input != input) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); + _sapp_android_msg_set_input_queue(queue); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(queue); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); + _sapp_android_msg_set_input_queue(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); + /* see android:configChanges in manifest */ +} + +_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); +} + +_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { + /* + * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) + * on my device (Moto X 2nd gen) when the app is removed from the task view + * (TaskStackView: onTaskViewDismissed). + * + * However, if ANativeActivity_finish() is explicitly called from for example + * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? + */ + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); + + /* send destroy msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); + while (!_sapp.android.is_thread_stopped) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* clean up main thread */ + pthread_cond_destroy(&_sapp.android.pt.cond); + pthread_mutex_destroy(&_sapp.android.pt.mutex); + + close(_sapp.android.pt.read_from_main_fd); + close(_sapp.android.pt.write_from_main_fd); + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); + + /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ + exit(0); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { + _SOKOL_UNUSED(saved_state); + _SOKOL_UNUSED(saved_state_size); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); + + // the NativeActity pointer needs to be available inside sokol_main() + // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() + // will clear the global _sapp_t struct, so we need to initialize the native + // activity pointer twice, once before sokol_main() and once after _sapp_init_state() + _sapp_clear(&_sapp, sizeof(_sapp)); + _sapp.android.activity = activity; + sapp_desc desc = sokol_main(0, NULL); + _sapp_init_state(&desc); + _sapp.android.activity = activity; + + int pipe_fd[2]; + if (pipe(pipe_fd) != 0) { + _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); + return; + } + _sapp.android.pt.read_from_main_fd = pipe_fd[0]; + _sapp.android.pt.write_from_main_fd = pipe_fd[1]; + + pthread_mutex_init(&_sapp.android.pt.mutex, NULL); + pthread_cond_init(&_sapp.android.pt.cond, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); + pthread_attr_destroy(&attr); + + /* wait until main loop has started */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + while (!_sapp.android.is_thread_started) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* send create msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); + while (!_sapp.android.has_created) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* register for callbacks */ + activity->callbacks->onStart = _sapp_android_on_start; + activity->callbacks->onResume = _sapp_android_on_resume; + activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; + activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; + activity->callbacks->onPause = _sapp_android_on_pause; + activity->callbacks->onStop = _sapp_android_on_stop; + activity->callbacks->onDestroy = _sapp_android_on_destroy; + activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; + /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ + /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ + activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; + activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; + activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; + /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ + activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; + activity->callbacks->onLowMemory = _sapp_android_on_low_memory; + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); + + /* NOT A BUG: do NOT call sapp_discard_state() */ +} + +#endif /* _SAPP_ANDROID */ + +// ██ ██ ███ ██ ██ ██ ██ ██ +// ██ ██ ████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ████ ██████ ██ ██ +// +// >>linux +#if defined(_SAPP_LINUX) + +/* see GLFW's xkb_unicode.c */ +static const struct _sapp_x11_codepair { + uint16_t keysym; + uint16_t ucs; +} _sapp_x11_keysymtab[] = { + { 0x01a1, 0x0104 }, + { 0x01a2, 0x02d8 }, + { 0x01a3, 0x0141 }, + { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, + { 0x01a9, 0x0160 }, + { 0x01aa, 0x015e }, + { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, + { 0x01ae, 0x017d }, + { 0x01af, 0x017b }, + { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, + { 0x01b3, 0x0142 }, + { 0x01b5, 0x013e }, + { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, + { 0x01b9, 0x0161 }, + { 0x01ba, 0x015f }, + { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, + { 0x01bd, 0x02dd }, + { 0x01be, 0x017e }, + { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, + { 0x01c3, 0x0102 }, + { 0x01c5, 0x0139 }, + { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, + { 0x01ca, 0x0118 }, + { 0x01cc, 0x011a }, + { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, + { 0x01d1, 0x0143 }, + { 0x01d2, 0x0147 }, + { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, + { 0x01d9, 0x016e }, + { 0x01db, 0x0170 }, + { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, + { 0x01e3, 0x0103 }, + { 0x01e5, 0x013a }, + { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, + { 0x01ea, 0x0119 }, + { 0x01ec, 0x011b }, + { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, + { 0x01f1, 0x0144 }, + { 0x01f2, 0x0148 }, + { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, + { 0x01f9, 0x016f }, + { 0x01fb, 0x0171 }, + { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, + { 0x02a1, 0x0126 }, + { 0x02a6, 0x0124 }, + { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, + { 0x02ac, 0x0134 }, + { 0x02b1, 0x0127 }, + { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, + { 0x02bb, 0x011f }, + { 0x02bc, 0x0135 }, + { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, + { 0x02d5, 0x0120 }, + { 0x02d8, 0x011c }, + { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, + { 0x02e5, 0x010b }, + { 0x02e6, 0x0109 }, + { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, + { 0x02fd, 0x016d }, + { 0x02fe, 0x015d }, + { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, + { 0x03a5, 0x0128 }, + { 0x03a6, 0x013b }, + { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, + { 0x03ac, 0x0166 }, + { 0x03b3, 0x0157 }, + { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, + { 0x03ba, 0x0113 }, + { 0x03bb, 0x0123 }, + { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, + { 0x03bf, 0x014b }, + { 0x03c0, 0x0100 }, + { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, + { 0x03cf, 0x012a }, + { 0x03d1, 0x0145 }, + { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, + { 0x03d9, 0x0172 }, + { 0x03dd, 0x0168 }, + { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, + { 0x03e7, 0x012f }, + { 0x03ec, 0x0117 }, + { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, + { 0x03f2, 0x014d }, + { 0x03f3, 0x0137 }, + { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, + { 0x03fe, 0x016b }, + { 0x047e, 0x203e }, + { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, + { 0x04a3, 0x300d }, + { 0x04a4, 0x3001 }, + { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, + { 0x04a7, 0x30a1 }, + { 0x04a8, 0x30a3 }, + { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, + { 0x04ab, 0x30a9 }, + { 0x04ac, 0x30e3 }, + { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, + { 0x04af, 0x30c3 }, + { 0x04b0, 0x30fc }, + { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, + { 0x04b3, 0x30a6 }, + { 0x04b4, 0x30a8 }, + { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, + { 0x04b7, 0x30ad }, + { 0x04b8, 0x30af }, + { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, + { 0x04bb, 0x30b5 }, + { 0x04bc, 0x30b7 }, + { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, + { 0x04bf, 0x30bd }, + { 0x04c0, 0x30bf }, + { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, + { 0x04c3, 0x30c6 }, + { 0x04c4, 0x30c8 }, + { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, + { 0x04c7, 0x30cc }, + { 0x04c8, 0x30cd }, + { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, + { 0x04cb, 0x30d2 }, + { 0x04cc, 0x30d5 }, + { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, + { 0x04cf, 0x30de }, + { 0x04d0, 0x30df }, + { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, + { 0x04d3, 0x30e2 }, + { 0x04d4, 0x30e4 }, + { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, + { 0x04d7, 0x30e9 }, + { 0x04d8, 0x30ea }, + { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, + { 0x04db, 0x30ed }, + { 0x04dc, 0x30ef }, + { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, + { 0x04df, 0x309c }, + { 0x05ac, 0x060c }, + { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, + { 0x05c1, 0x0621 }, + { 0x05c2, 0x0622 }, + { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, + { 0x05c5, 0x0625 }, + { 0x05c6, 0x0626 }, + { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, + { 0x05c9, 0x0629 }, + { 0x05ca, 0x062a }, + { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, + { 0x05cd, 0x062d }, + { 0x05ce, 0x062e }, + { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, + { 0x05d1, 0x0631 }, + { 0x05d2, 0x0632 }, + { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, + { 0x05d5, 0x0635 }, + { 0x05d6, 0x0636 }, + { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, + { 0x05d9, 0x0639 }, + { 0x05da, 0x063a }, + { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, + { 0x05e2, 0x0642 }, + { 0x05e3, 0x0643 }, + { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, + { 0x05e6, 0x0646 }, + { 0x05e7, 0x0647 }, + { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, + { 0x05ea, 0x064a }, + { 0x05eb, 0x064b }, + { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, + { 0x05ee, 0x064e }, + { 0x05ef, 0x064f }, + { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, + { 0x05f2, 0x0652 }, + { 0x06a1, 0x0452 }, + { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, + { 0x06a4, 0x0454 }, + { 0x06a5, 0x0455 }, + { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, + { 0x06a8, 0x0458 }, + { 0x06a9, 0x0459 }, + { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, + { 0x06ac, 0x045c }, + { 0x06ae, 0x045e }, + { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, + { 0x06b1, 0x0402 }, + { 0x06b2, 0x0403 }, + { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, + { 0x06b5, 0x0405 }, + { 0x06b6, 0x0406 }, + { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, + { 0x06b9, 0x0409 }, + { 0x06ba, 0x040a }, + { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, + { 0x06be, 0x040e }, + { 0x06bf, 0x040f }, + { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, + { 0x06c2, 0x0431 }, + { 0x06c3, 0x0446 }, + { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, + { 0x06c6, 0x0444 }, + { 0x06c7, 0x0433 }, + { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, + { 0x06ca, 0x0439 }, + { 0x06cb, 0x043a }, + { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, + { 0x06ce, 0x043d }, + { 0x06cf, 0x043e }, + { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, + { 0x06d2, 0x0440 }, + { 0x06d3, 0x0441 }, + { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, + { 0x06d6, 0x0436 }, + { 0x06d7, 0x0432 }, + { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, + { 0x06da, 0x0437 }, + { 0x06db, 0x0448 }, + { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, + { 0x06de, 0x0447 }, + { 0x06df, 0x044a }, + { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, + { 0x06e2, 0x0411 }, + { 0x06e3, 0x0426 }, + { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, + { 0x06e6, 0x0424 }, + { 0x06e7, 0x0413 }, + { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, + { 0x06ea, 0x0419 }, + { 0x06eb, 0x041a }, + { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, + { 0x06ee, 0x041d }, + { 0x06ef, 0x041e }, + { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, + { 0x06f2, 0x0420 }, + { 0x06f3, 0x0421 }, + { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, + { 0x06f6, 0x0416 }, + { 0x06f7, 0x0412 }, + { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, + { 0x06fa, 0x0417 }, + { 0x06fb, 0x0428 }, + { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, + { 0x06fe, 0x0427 }, + { 0x06ff, 0x042a }, + { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, + { 0x07a3, 0x0389 }, + { 0x07a4, 0x038a }, + { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, + { 0x07a8, 0x038e }, + { 0x07a9, 0x03ab }, + { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, + { 0x07af, 0x2015 }, + { 0x07b1, 0x03ac }, + { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, + { 0x07b4, 0x03af }, + { 0x07b5, 0x03ca }, + { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, + { 0x07b8, 0x03cd }, + { 0x07b9, 0x03cb }, + { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, + { 0x07c1, 0x0391 }, + { 0x07c2, 0x0392 }, + { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, + { 0x07c5, 0x0395 }, + { 0x07c6, 0x0396 }, + { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, + { 0x07c9, 0x0399 }, + { 0x07ca, 0x039a }, + { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, + { 0x07cd, 0x039d }, + { 0x07ce, 0x039e }, + { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, + { 0x07d1, 0x03a1 }, + { 0x07d2, 0x03a3 }, + { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, + { 0x07d6, 0x03a6 }, + { 0x07d7, 0x03a7 }, + { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, + { 0x07e1, 0x03b1 }, + { 0x07e2, 0x03b2 }, + { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, + { 0x07e5, 0x03b5 }, + { 0x07e6, 0x03b6 }, + { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, + { 0x07e9, 0x03b9 }, + { 0x07ea, 0x03ba }, + { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, + { 0x07ed, 0x03bd }, + { 0x07ee, 0x03be }, + { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, + { 0x07f1, 0x03c1 }, + { 0x07f2, 0x03c3 }, + { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, + { 0x07f5, 0x03c5 }, + { 0x07f6, 0x03c6 }, + { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, + { 0x07f9, 0x03c9 }, + { 0x08a1, 0x23b7 }, + { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, + { 0x08a4, 0x2320 }, + { 0x08a5, 0x2321 }, + { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, + { 0x08a8, 0x23a3 }, + { 0x08a9, 0x23a4 }, + { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, + { 0x08ac, 0x239d }, + { 0x08ad, 0x239e }, + { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, + { 0x08b0, 0x23ac }, + { 0x08bc, 0x2264 }, + { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, + { 0x08bf, 0x222b }, + { 0x08c0, 0x2234 }, + { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, + { 0x08c5, 0x2207 }, + { 0x08c8, 0x223c }, + { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, + { 0x08ce, 0x21d2 }, + { 0x08cf, 0x2261 }, + { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, + { 0x08db, 0x2283 }, + { 0x08dc, 0x2229 }, + { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, + { 0x08df, 0x2228 }, + { 0x08ef, 0x2202 }, + { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, + { 0x08fc, 0x2191 }, + { 0x08fd, 0x2192 }, + { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, + { 0x09e1, 0x2592 }, + { 0x09e2, 0x2409 }, + { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, + { 0x09e5, 0x240a }, + { 0x09e8, 0x2424 }, + { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, + { 0x09eb, 0x2510 }, + { 0x09ec, 0x250c }, + { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, + { 0x09ef, 0x23ba }, + { 0x09f0, 0x23bb }, + { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, + { 0x09f3, 0x23bd }, + { 0x09f4, 0x251c }, + { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, + { 0x09f7, 0x252c }, + { 0x09f8, 0x2502 }, + { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, + { 0x0aa3, 0x2004 }, + { 0x0aa4, 0x2005 }, + { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, + { 0x0aa7, 0x2009 }, + { 0x0aa8, 0x200a }, + { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, + { 0x0aae, 0x2026 }, + { 0x0aaf, 0x2025 }, + { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, + { 0x0ab2, 0x2155 }, + { 0x0ab3, 0x2156 }, + { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, + { 0x0ab6, 0x2159 }, + { 0x0ab7, 0x215a }, + { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, + { 0x0abc, 0x2329 }, + { 0x0abe, 0x232a }, + { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, + { 0x0ac5, 0x215d }, + { 0x0ac6, 0x215e }, + { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, + { 0x0acc, 0x25c1 }, + { 0x0acd, 0x25b7 }, + { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, + { 0x0ad0, 0x2018 }, + { 0x0ad1, 0x2019 }, + { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, + { 0x0ad4, 0x211e }, + { 0x0ad6, 0x2032 }, + { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, + { 0x0adb, 0x25ac }, + { 0x0adc, 0x25c0 }, + { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, + { 0x0adf, 0x25ae }, + { 0x0ae0, 0x25e6 }, + { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, + { 0x0ae3, 0x25b3 }, + { 0x0ae4, 0x25bd }, + { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, + { 0x0ae7, 0x25aa }, + { 0x0ae8, 0x25b2 }, + { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, + { 0x0aeb, 0x261e }, + { 0x0aec, 0x2663 }, + { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, + { 0x0af0, 0x2720 }, + { 0x0af1, 0x2020 }, + { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, + { 0x0af4, 0x2717 }, + { 0x0af5, 0x266f }, + { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, + { 0x0af8, 0x2640 }, + { 0x0af9, 0x260e }, + { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, + { 0x0afc, 0x2038 }, + { 0x0afd, 0x201a }, + { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, + { 0x0ba6, 0x003e }, + { 0x0ba8, 0x2228 }, + { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, + { 0x0bc2, 0x22a5 }, + { 0x0bc3, 0x2229 }, + { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, + { 0x0bca, 0x2218 }, + { 0x0bcc, 0x2395 }, + { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, + { 0x0bd3, 0x2308 }, + { 0x0bd6, 0x222a }, + { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, + { 0x0bdc, 0x22a2 }, + { 0x0bfc, 0x22a3 }, + { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, + { 0x0ce1, 0x05d1 }, + { 0x0ce2, 0x05d2 }, + { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, + { 0x0ce5, 0x05d5 }, + { 0x0ce6, 0x05d6 }, + { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, + { 0x0ce9, 0x05d9 }, + { 0x0cea, 0x05da }, + { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, + { 0x0ced, 0x05dd }, + { 0x0cee, 0x05de }, + { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, + { 0x0cf1, 0x05e1 }, + { 0x0cf2, 0x05e2 }, + { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, + { 0x0cf5, 0x05e5 }, + { 0x0cf6, 0x05e6 }, + { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, + { 0x0cf9, 0x05e9 }, + { 0x0cfa, 0x05ea }, + { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, + { 0x0da3, 0x0e03 }, + { 0x0da4, 0x0e04 }, + { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, + { 0x0da7, 0x0e07 }, + { 0x0da8, 0x0e08 }, + { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, + { 0x0dab, 0x0e0b }, + { 0x0dac, 0x0e0c }, + { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, + { 0x0daf, 0x0e0f }, + { 0x0db0, 0x0e10 }, + { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, + { 0x0db3, 0x0e13 }, + { 0x0db4, 0x0e14 }, + { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, + { 0x0db7, 0x0e17 }, + { 0x0db8, 0x0e18 }, + { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, + { 0x0dbb, 0x0e1b }, + { 0x0dbc, 0x0e1c }, + { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, + { 0x0dbf, 0x0e1f }, + { 0x0dc0, 0x0e20 }, + { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, + { 0x0dc3, 0x0e23 }, + { 0x0dc4, 0x0e24 }, + { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, + { 0x0dc7, 0x0e27 }, + { 0x0dc8, 0x0e28 }, + { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, + { 0x0dcb, 0x0e2b }, + { 0x0dcc, 0x0e2c }, + { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, + { 0x0dcf, 0x0e2f }, + { 0x0dd0, 0x0e30 }, + { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, + { 0x0dd3, 0x0e33 }, + { 0x0dd4, 0x0e34 }, + { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, + { 0x0dd7, 0x0e37 }, + { 0x0dd8, 0x0e38 }, + { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, + { 0x0ddf, 0x0e3f }, + { 0x0de0, 0x0e40 }, + { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, + { 0x0de3, 0x0e43 }, + { 0x0de4, 0x0e44 }, + { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, + { 0x0de7, 0x0e47 }, + { 0x0de8, 0x0e48 }, + { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, + { 0x0deb, 0x0e4b }, + { 0x0dec, 0x0e4c }, + { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, + { 0x0df1, 0x0e51 }, + { 0x0df2, 0x0e52 }, + { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, + { 0x0df5, 0x0e55 }, + { 0x0df6, 0x0e56 }, + { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, + { 0x0df9, 0x0e59 }, + { 0x0ea1, 0x3131 }, + { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, + { 0x0ea4, 0x3134 }, + { 0x0ea5, 0x3135 }, + { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, + { 0x0ea8, 0x3138 }, + { 0x0ea9, 0x3139 }, + { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, + { 0x0eac, 0x313c }, + { 0x0ead, 0x313d }, + { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, + { 0x0eb0, 0x3140 }, + { 0x0eb1, 0x3141 }, + { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, + { 0x0eb4, 0x3144 }, + { 0x0eb5, 0x3145 }, + { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, + { 0x0eb8, 0x3148 }, + { 0x0eb9, 0x3149 }, + { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, + { 0x0ebc, 0x314c }, + { 0x0ebd, 0x314d }, + { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, + { 0x0ec0, 0x3150 }, + { 0x0ec1, 0x3151 }, + { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, + { 0x0ec4, 0x3154 }, + { 0x0ec5, 0x3155 }, + { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, + { 0x0ec8, 0x3158 }, + { 0x0ec9, 0x3159 }, + { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, + { 0x0ecc, 0x315c }, + { 0x0ecd, 0x315d }, + { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, + { 0x0ed0, 0x3160 }, + { 0x0ed1, 0x3161 }, + { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, + { 0x0ed4, 0x11a8 }, + { 0x0ed5, 0x11a9 }, + { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, + { 0x0ed8, 0x11ac }, + { 0x0ed9, 0x11ad }, + { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, + { 0x0edc, 0x11b0 }, + { 0x0edd, 0x11b1 }, + { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, + { 0x0ee0, 0x11b4 }, + { 0x0ee1, 0x11b5 }, + { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, + { 0x0ee4, 0x11b8 }, + { 0x0ee5, 0x11b9 }, + { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, + { 0x0ee8, 0x11bc }, + { 0x0ee9, 0x11bd }, + { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, + { 0x0eec, 0x11c0 }, + { 0x0eed, 0x11c1 }, + { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, + { 0x0ef0, 0x3171 }, + { 0x0ef1, 0x3178 }, + { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, + { 0x0ef4, 0x3184 }, + { 0x0ef5, 0x3186 }, + { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, + { 0x0ef8, 0x11eb }, + { 0x0ef9, 0x11f0 }, + { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, + { 0x13a4, 0x20ac }, + { 0x13bc, 0x0152 }, + { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, + { 0x20ac, 0x20ac }, + { 0xfe50, '`' }, + { 0xfe51, 0x00b4 }, + { 0xfe52, '^' }, + { 0xfe53, '~' }, + { 0xfe54, 0x00af }, + { 0xfe55, 0x02d8 }, + { 0xfe56, 0x02d9 }, + { 0xfe57, 0x00a8 }, + { 0xfe58, 0x02da }, + { 0xfe59, 0x02dd }, + { 0xfe5a, 0x02c7 }, + { 0xfe5b, 0x00b8 }, + { 0xfe5c, 0x02db }, + { 0xfe5d, 0x037a }, + { 0xfe5e, 0x309b }, + { 0xfe5f, 0x309c }, + { 0xfe63, '/' }, + { 0xfe64, 0x02bc }, + { 0xfe65, 0x02bd }, + { 0xfe66, 0x02f5 }, + { 0xfe67, 0x02f3 }, + { 0xfe68, 0x02cd }, + { 0xfe69, 0xa788 }, + { 0xfe6a, 0x02f7 }, + { 0xfe6e, ',' }, + { 0xfe6f, 0x00a4 }, + { 0xfe80, 'a' }, /* XK_dead_a */ + { 0xfe81, 'A' }, /* XK_dead_A */ + { 0xfe82, 'e' }, /* XK_dead_e */ + { 0xfe83, 'E' }, /* XK_dead_E */ + { 0xfe84, 'i' }, /* XK_dead_i */ + { 0xfe85, 'I' }, /* XK_dead_I */ + { 0xfe86, 'o' }, /* XK_dead_o */ + { 0xfe87, 'O' }, /* XK_dead_O */ + { 0xfe88, 'u' }, /* XK_dead_u */ + { 0xfe89, 'U' }, /* XK_dead_U */ + { 0xfe8a, 0x0259 }, + { 0xfe8b, 0x018f }, + { 0xfe8c, 0x00b5 }, + { 0xfe90, '_' }, + { 0xfe91, 0x02c8 }, + { 0xfe92, 0x02cc }, + { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, + { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, + { 0xffab /*XKB_KEY_KP_Add*/, '+' }, + { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, + { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, + { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, + { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, + { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } +}; + +_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { + _SOKOL_UNUSED(display); + _sapp.x11.error_code = event->error_code; + return 0; +} + +_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { + _sapp.x11.error_code = Success; + XSetErrorHandler(_sapp_x11_error_handler); +} + +_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { + XSync(_sapp.x11.display, False); + XSetErrorHandler(NULL); +} + +_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { + _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); + _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); + _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); + _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); + _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); + _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); + _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); + _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); + if (_sapp.drop.enabled) { + _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); + _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); + _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); + _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); + _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); + _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); + _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); + _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); + _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); + _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); + } + + /* check Xi extension for raw mouse input */ + if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { + _sapp.x11.xi.major = 2; + _sapp.x11.xi.minor = 0; + if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { + _sapp.x11.xi.available = true; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { + /* from GLFW: + + NOTE: Default to the display-wide DPI as we don't currently have a policy + for which monitor a window is considered to be on + + _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * + 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); + + NOTE: Basing the scale on Xft.dpi where available should provide the most + consistent user experience (matches Qt, Gtk, etc), although not + always the most accurate one + */ + bool dpi_ok = false; + char* rms = XResourceManagerString(_sapp.x11.display); + if (rms) { + XrmDatabase db = XrmGetStringDatabase(rms); + if (db) { + XrmValue value; + char* type = NULL; + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { + if (type && strcmp(type, "String") == 0) { + _sapp.x11.dpi = atof(value.addr); + dpi_ok = true; + } + } + XrmDestroyDatabase(db); + } + } + // fallback if querying DPI had failed: assume the standard DPI 96.0f + if (!dpi_ok) { + _sapp.x11.dpi = 96.0f; + _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); + } +} + +#if defined(_SAPP_GLX) + +_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { + if (extensions) { + return _sapp_glx_has_ext(ext, extensions); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) +{ + if (_sapp.glx.GetProcAddress) { + return (void*) _sapp.glx.GetProcAddress(procname); + } + else if (_sapp.glx.GetProcAddressARB) { + return (void*) _sapp.glx.GetProcAddressARB(procname); + } + else { + return dlsym(_sapp.glx.libgl, procname); + } +} + +_SOKOL_PRIVATE void _sapp_glx_init() { + const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; + for (int i = 0; sonames[i]; i++) { + _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); + if (_sapp.glx.libgl) { + break; + } + } + if (!_sapp.glx.libgl) { + _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); + } + _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); + _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); + _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); + _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); + _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); + _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); + _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); + _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); + _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); + _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); + _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); + _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); + _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); + _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); + if (!_sapp.glx.GetFBConfigs || + !_sapp.glx.GetFBConfigAttrib || + !_sapp.glx.GetClientString || + !_sapp.glx.QueryExtension || + !_sapp.glx.QueryVersion || + !_sapp.glx.DestroyContext || + !_sapp.glx.MakeCurrent || + !_sapp.glx.SwapBuffers || + !_sapp.glx.QueryExtensionsString || + !_sapp.glx.CreateWindow || + !_sapp.glx.DestroyWindow || + !_sapp.glx.GetProcAddress || + !_sapp.glx.GetProcAddressARB || + !_sapp.glx.GetVisualFromFBConfig) + { + _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); + } + + if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { + _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); + } + if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { + _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); + } + if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { + _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); + } + const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); + if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { + _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); + _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; + } + if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { + _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); + _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; + } + _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); + if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { + _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); + _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; + } + _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); +} + +_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { + int value; + _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); + return value; +} + +_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { + GLXFBConfig* native_configs; + _sapp_gl_fbconfig* usable_configs; + const _sapp_gl_fbconfig* closest; + int i, native_count, usable_count; + const char* vendor; + bool trust_window_bit = true; + + /* HACK: This is a (hopefully temporary) workaround for Chromium + (VirtualBox GL) not setting the window bit on any GLXFBConfigs + */ + vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); + if (vendor && strcmp(vendor, "Chromium") == 0) { + trust_window_bit = false; + } + + native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); + if (!native_configs || !native_count) { + _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); + } + + usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); + usable_count = 0; + for (i = 0; i < native_count; i++) { + const GLXFBConfig n = native_configs[i]; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + + /* Only consider RGBA GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { + continue; + } + /* Only consider window GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { + if (trust_window_bit) { + continue; + } + } + u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); + u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); + u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); + u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); + u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); + u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); + if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { + u->doublebuffer = true; + } + if (_sapp.glx.ARB_multisample) { + u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); + } + u->handle = (uintptr_t) n; + usable_count++; + } + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + GLXFBConfig result = 0; + if (closest) { + result = (GLXFBConfig) closest->handle; + } + XFree(native_configs); + _sapp_free(usable_configs); + return result; +} + +_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native) { + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); + if (!result) { + _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); + } + *visual = result->visual; + *depth = result->depth; + XFree(result); +} + +_SOKOL_PRIVATE void _sapp_glx_create_context(void) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native){ + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { + _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); + } + _sapp_x11_grab_error_handler(); + const int attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + 0, 0 + }; + _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); + if (!_sapp.glx.ctx) { + _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); + } + _sapp_x11_release_error_handler(); + _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); + if (!_sapp.glx.window) { + _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { + if (_sapp.glx.window) { + _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); + _sapp.glx.window = 0; + } + if (_sapp.glx.ctx) { + _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); + _sapp.glx.ctx = 0; + } +} + +_SOKOL_PRIVATE void _sapp_glx_make_current(void) { + _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); +} + +_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { + _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); +} + +_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { + _sapp_glx_make_current(); + if (_sapp.glx.EXT_swap_control) { + _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); + } + else if (_sapp.glx.MESA_swap_control) { + _sapp.glx.SwapIntervalMESA(interval); + } +} + +#endif /* _SAPP_GLX */ + +_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { + XEvent event; + _sapp_clear(&event, sizeof(event)); + + event.type = ClientMessage; + event.xclient.window = _sapp.x11.window; + event.xclient.format = 32; + event.xclient.message_type = type; + event.xclient.data.l[0] = a; + event.xclient.data.l[1] = b; + event.xclient.data.l[2] = c; + event.xclient.data.l[3] = d; + event.xclient.data.l[4] = e; + + XSendEvent(_sapp.x11.display, _sapp.x11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); +} + +_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { + XWindowAttributes attribs; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); + _sapp.window_width = attribs.width; + _sapp.window_height = attribs.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; +} + +_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { + /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ + if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { + if (enable) { + const int _NET_WM_STATE_ADD = 1; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_ADD, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + else { + const int _NET_WM_STATE_REMOVE = 0; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_REMOVE, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { + SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); + const int w = 16; + const int h = 16; + XcursorImage* img = XcursorImageCreate(w, h); + SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); + img->xhot = 0; + img->yhot = 0; + const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); + _sapp_clear(img->pixels, num_bytes); + _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); +} + + _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.x11.display); + if (theme) { + XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); + if (img) { + _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.cursors[cursor]) { + _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); + const int size = XcursorGetDefaultSize(_sapp.x11.display); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); + _sapp_x11_create_hidden_cursor(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + if (_sapp.x11.hidden_cursor) { + XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); + _sapp.x11.hidden_cursor = 0; + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + if (_sapp.x11.cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); + _sapp.x11.cursors[i] = 0; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { + _sapp.fullscreen = !_sapp.fullscreen; + _sapp_x11_set_fullscreen(_sapp.fullscreen); + _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (shown) { + if (_sapp.x11.cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); + } + else { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } + } + else { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + if (_sapp.mouse.locked) { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISetMask(mask, XI_RawMotion); + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XGrabPointer(_sapp.x11.display, // display + _sapp.x11.window, // grab_window + True, // owner_events + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask + GrabModeAsync, // pointer_mode + GrabModeAsync, // keyboard_mode + _sapp.x11.window, // confine_to + _sapp.x11.hidden_cursor, // cursor + CurrentTime); // time + } + else { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[] = { 0 }; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); + XUngrabPointer(_sapp.x11.display, CurrentTime); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { + Xutf8SetWMProperties(_sapp.x11.display, + _sapp.x11.window, + _sapp.window_title, _sapp.window_title, + NULL, 0, NULL, NULL, NULL); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 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*) _sapp_malloc_clear((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); + _sapp_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; + _sapp_clear(&wa, sizeof(wa)); + const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; + wa.colormap = _sapp.x11.colormap; + wa.border_pixel = 0; + wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | + ExposureMask | FocusChangeMask | VisibilityChangeMask | + EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + + int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); + int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); + int window_width = _sapp.window_width; + int window_height = _sapp.window_height; + if (0 == window_width) { + window_width = (display_width * 4) / 5; + } + if (0 == window_height) { + window_height = (display_height * 4) / 5; + } + int window_xpos = (display_width - window_width) / 2; + int window_ypos = (display_height - window_height) / 2; + if (window_xpos < 0) { + window_xpos = 0; + } + if (window_ypos < 0) { + window_ypos = 0; + } + _sapp_x11_grab_error_handler(); + _sapp.x11.window = XCreateWindow(_sapp.x11.display, + _sapp.x11.root, + window_xpos, + window_ypos, + (uint32_t)window_width, + (uint32_t)window_height, + 0, /* border width */ + depth, /* color depth */ + InputOutput, + visual, + wamask, + &wa); + _sapp_x11_release_error_handler(); + if (!_sapp.x11.window) { + _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); + } + Atom protocols[] = { + _sapp.x11.WM_DELETE_WINDOW + }; + XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); + + XSizeHints* hints = XAllocSizeHints(); + hints->flags = (PWinGravity | PPosition | PSize); + hints->win_gravity = StaticGravity; + hints->x = window_xpos; + hints->y = window_ypos; + hints->width = window_width; + hints->height = window_height; + XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); + XFree(hints); + + /* announce support for drag'n'drop */ + if (_sapp.drop.enabled) { + const Atom version = _SAPP_X11_XDND_VERSION; + XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); + } + _sapp_x11_update_window_title(); + _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { + if (_sapp.x11.window) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XDestroyWindow(_sapp.x11.display, _sapp.x11.window); + _sapp.x11.window = 0; + } + if (_sapp.x11.colormap) { + XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); + _sapp.x11.colormap = 0; + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { + XWindowAttributes wa; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); + return wa.map_state == IsViewable; +} + +_SOKOL_PRIVATE void _sapp_x11_show_window(void) { + if (!_sapp_x11_window_visible()) { + XMapWindow(_sapp.x11.display, _sapp.x11.window); + XRaiseWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + XGetWindowProperty(_sapp.x11.display, + window, + property, + 0, + LONG_MAX, + False, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + return itemCount; +} + +_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { + int result = WithdrawnState; + struct { + CARD32 state; + Window icon; + } *state = NULL; + + if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { + result = (int)state->state; + } + if (state) { + XFree(state); + } + return result; +} + +_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; + } + if (x11_mods & ControlMask) { + mods |= SAPP_MODIFIER_CTRL; + } + if (x11_mods & Mod1Mask) { + mods |= SAPP_MODIFIER_ALT; + } + 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; +} + +_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { + switch (event->xbutton.button) { + case Button1: return SAPP_MOUSEBUTTON_LEFT; + case Button2: return SAPP_MOUSEBUTTON_MIDDLE; + case Button3: return SAPP_MOUSEBUTTON_RIGHT; + default: return SAPP_MOUSEBUTTON_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y) { + if (!_sapp.mouse.locked) { + const float new_x = (float) x; + const float new_y = (float) y; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = mods; + _sapp.event.scroll_x = x; + _sapp.event.scroll_y = y; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = chr; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { + int dummy; + KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); + SOKOL_ASSERT(keysyms); + KeySym keysym = keysyms[0]; + XFree(keysyms); + switch (keysym) { + case XK_Escape: return SAPP_KEYCODE_ESCAPE; + case XK_Tab: return SAPP_KEYCODE_TAB; + case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; + case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; + case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; + case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; + case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ + case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ + case XK_Meta_R: + case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; + case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; + case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; + case XK_Menu: return SAPP_KEYCODE_MENU; + case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; + case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; + case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; + case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; + case XK_Pause: return SAPP_KEYCODE_PAUSE; + case XK_Delete: return SAPP_KEYCODE_DELETE; + case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; + case XK_Return: return SAPP_KEYCODE_ENTER; + case XK_Home: return SAPP_KEYCODE_HOME; + case XK_End: return SAPP_KEYCODE_END; + case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; + case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; + case XK_Insert: return SAPP_KEYCODE_INSERT; + case XK_Left: return SAPP_KEYCODE_LEFT; + case XK_Right: return SAPP_KEYCODE_RIGHT; + case XK_Down: return SAPP_KEYCODE_DOWN; + case XK_Up: return SAPP_KEYCODE_UP; + case XK_F1: return SAPP_KEYCODE_F1; + case XK_F2: return SAPP_KEYCODE_F2; + case XK_F3: return SAPP_KEYCODE_F3; + case XK_F4: return SAPP_KEYCODE_F4; + case XK_F5: return SAPP_KEYCODE_F5; + case XK_F6: return SAPP_KEYCODE_F6; + case XK_F7: return SAPP_KEYCODE_F7; + case XK_F8: return SAPP_KEYCODE_F8; + case XK_F9: return SAPP_KEYCODE_F9; + case XK_F10: return SAPP_KEYCODE_F10; + case XK_F11: return SAPP_KEYCODE_F11; + case XK_F12: return SAPP_KEYCODE_F12; + case XK_F13: return SAPP_KEYCODE_F13; + case XK_F14: return SAPP_KEYCODE_F14; + case XK_F15: return SAPP_KEYCODE_F15; + case XK_F16: return SAPP_KEYCODE_F16; + case XK_F17: return SAPP_KEYCODE_F17; + case XK_F18: return SAPP_KEYCODE_F18; + case XK_F19: return SAPP_KEYCODE_F19; + case XK_F20: return SAPP_KEYCODE_F20; + case XK_F21: return SAPP_KEYCODE_F21; + case XK_F22: return SAPP_KEYCODE_F22; + case XK_F23: return SAPP_KEYCODE_F23; + case XK_F24: return SAPP_KEYCODE_F24; + case XK_F25: return SAPP_KEYCODE_F25; + + case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; + case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; + case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; + case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; + + case XK_KP_Insert: return SAPP_KEYCODE_KP_0; + case XK_KP_End: return SAPP_KEYCODE_KP_1; + case XK_KP_Down: return SAPP_KEYCODE_KP_2; + case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; + case XK_KP_Left: return SAPP_KEYCODE_KP_4; + case XK_KP_Begin: return SAPP_KEYCODE_KP_5; + case XK_KP_Right: return SAPP_KEYCODE_KP_6; + case XK_KP_Home: return SAPP_KEYCODE_KP_7; + case XK_KP_Up: return SAPP_KEYCODE_KP_8; + case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; + case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + + case XK_a: return SAPP_KEYCODE_A; + case XK_b: return SAPP_KEYCODE_B; + case XK_c: return SAPP_KEYCODE_C; + case XK_d: return SAPP_KEYCODE_D; + case XK_e: return SAPP_KEYCODE_E; + case XK_f: return SAPP_KEYCODE_F; + case XK_g: return SAPP_KEYCODE_G; + case XK_h: return SAPP_KEYCODE_H; + case XK_i: return SAPP_KEYCODE_I; + case XK_j: return SAPP_KEYCODE_J; + case XK_k: return SAPP_KEYCODE_K; + case XK_l: return SAPP_KEYCODE_L; + case XK_m: return SAPP_KEYCODE_M; + case XK_n: return SAPP_KEYCODE_N; + case XK_o: return SAPP_KEYCODE_O; + case XK_p: return SAPP_KEYCODE_P; + case XK_q: return SAPP_KEYCODE_Q; + case XK_r: return SAPP_KEYCODE_R; + case XK_s: return SAPP_KEYCODE_S; + case XK_t: return SAPP_KEYCODE_T; + case XK_u: return SAPP_KEYCODE_U; + case XK_v: return SAPP_KEYCODE_V; + case XK_w: return SAPP_KEYCODE_W; + case XK_x: return SAPP_KEYCODE_X; + case XK_y: return SAPP_KEYCODE_Y; + case XK_z: return SAPP_KEYCODE_Z; + case XK_1: return SAPP_KEYCODE_1; + case XK_2: return SAPP_KEYCODE_2; + case XK_3: return SAPP_KEYCODE_3; + case XK_4: return SAPP_KEYCODE_4; + case XK_5: return SAPP_KEYCODE_5; + case XK_6: return SAPP_KEYCODE_6; + case XK_7: return SAPP_KEYCODE_7; + case XK_8: return SAPP_KEYCODE_8; + case XK_9: return SAPP_KEYCODE_9; + case XK_0: return SAPP_KEYCODE_0; + case XK_space: return SAPP_KEYCODE_SPACE; + case XK_minus: return SAPP_KEYCODE_MINUS; + case XK_equal: return SAPP_KEYCODE_EQUAL; + case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; + case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; + case XK_backslash: return SAPP_KEYCODE_BACKSLASH; + case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; + case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; + case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; + case XK_comma: return SAPP_KEYCODE_COMMA; + case XK_period: return SAPP_KEYCODE_PERIOD; + case XK_slash: return SAPP_KEYCODE_SLASH; + case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ + default: return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { + int min = 0; + int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; + int mid; + + /* First check for Latin-1 characters (1:1 mapping) */ + if ((keysym >= 0x0020 && keysym <= 0x007e) || + (keysym >= 0x00a0 && keysym <= 0x00ff)) + { + return keysym; + } + + /* Also check for directly encoded 24-bit UCS characters */ + if ((keysym & 0xff000000) == 0x01000000) { + return keysym & 0x00ffffff; + } + + /* Binary search in table */ + while (max >= min) { + mid = (min + max) / 2; + if (_sapp_x11_keysymtab[mid].keysym < keysym) { + min = mid + 1; + } + else if (_sapp_x11_keysymtab[mid].keysym > keysym) { + max = mid - 1; + } + else { + return _sapp_x11_keysymtab[mid].ucs; + } + } + + /* No matching Unicode value found */ + return -1; +} + +_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { + SOKOL_ASSERT(src); + SOKOL_ASSERT(_sapp.drop.buffer); + + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + + /* + src is (potentially percent-encoded) string made of one or multiple paths + separated by \r\n, each path starting with 'file://' + */ + bool err = false; + int src_count = 0; + char src_chr = 0; + char* dst_ptr = _sapp.drop.buffer; + const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 + while (0 != (src_chr = *src++)) { + src_count++; + char dst_chr = 0; + /* check leading 'file://' */ + if (src_count <= 7) { + if (((src_count == 1) && (src_chr != 'f')) || + ((src_count == 2) && (src_chr != 'i')) || + ((src_count == 3) && (src_chr != 'l')) || + ((src_count == 4) && (src_chr != 'e')) || + ((src_count == 5) && (src_chr != ':')) || + ((src_count == 6) && (src_chr != '/')) || + ((src_count == 7) && (src_chr != '/'))) + { + _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); + err = true; + break; + } + } + else if (src_chr == '\r') { + // skip + } + else if (src_chr == '\n') { + src_count = 0; + _sapp.drop.num_files++; + // too many files is not an error + if (_sapp.drop.num_files >= _sapp.drop.max_files) { + break; + } + dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; + dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); + } + else if ((src_chr == '%') && src[0] && src[1]) { + // a percent-encoded byte (most likely UTF-8 multibyte sequence) + const char digits[3] = { src[0], src[1], 0 }; + src += 2; + dst_chr = (char) strtol(digits, 0, 16); + } + else { + dst_chr = src_chr; + } + if (dst_chr) { + // dst_end_ptr already has adjustment for terminating zero + if (dst_ptr < dst_end_ptr) { + *dst_ptr++ = dst_chr; + } + else { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + err = true; + break; + } + } + } + if (err) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + return false; + } + else { + return true; + } +} + +// XLib manual says keycodes are in the range [8, 255] inclusive. +// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html +static bool _sapp_x11_keycodes[256]; + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { + Bool filtered = XFilterEvent(event, None); + switch (event->type) { + case GenericEvent: + if (_sapp.mouse.locked && _sapp.x11.xi.available) { + if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { + if (XGetEventData(_sapp.x11.display, &event->xcookie)) { + if (event->xcookie.evtype == XI_RawMotion) { + XIRawEvent* re = (XIRawEvent*) event->xcookie.data; + if (re->valuators.mask_len) { + const double* values = re->raw_values; + if (XIMaskIsSet(re->valuators.mask, 0)) { + _sapp.mouse.dx = (float) *values; + values++; + } + if (XIMaskIsSet(re->valuators.mask, 1)) { + _sapp.mouse.dy = (float) *values; + } + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } + } + XFreeEventData(_sapp.x11.display, &event->xcookie); + } + } + } + break; + case FocusIn: + // NOTE: ignoring 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: ignoring 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: + { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; + _sapp_x11_keycodes[keycode & 0xFF] = true; + 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); + } + KeySym keysym; + XLookupString(&event->xkey, NULL, 0, &keysym, NULL); + int32_t chr = _sapp_x11_keysym_to_unicode(keysym); + if (chr > 0) { + _sapp_x11_char_event((uint32_t)chr, repeat, mods); + } + } + break; + case KeyRelease: + { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + _sapp_x11_keycodes[keycode & 0xFF] = false; + if (key != SAPP_KEYCODE_INVALID) { + 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); + } + } + break; + case ButtonPress: + { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + 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); + } + else { + /* might be a scroll event */ + switch (event->xbutton.button) { + case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; + case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; + case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; + case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; + } + } + } + break; + case ButtonRelease: + { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + 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); + } + } + break; + case EnterNotify: + /* don't send enter/leave events while mouse button held down */ + if (0 == _sapp.x11.mouse_buttons) { + _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y); + _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_update(event->xcrossing.x, event->xcrossing.y); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); + } + break; + case MotionNotify: + if (!_sapp.mouse.locked) { + _sapp_x11_mouse_update(event->xmotion.x, event->xmotion.y); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } + break; + case ConfigureNotify: + if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { + _sapp.window_width = event->xconfigure.width; + _sapp.window_height = event->xconfigure.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); + } + break; + case PropertyNotify: + if (event->xproperty.state == PropertyNewValue) { + if (event->xproperty.atom == _sapp.x11.WM_STATE) { + const int state = _sapp_x11_get_window_state(); + if (state != _sapp.x11.window_state) { + _sapp.x11.window_state = state; + if (state == IconicState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else if (state == NormalState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + } + break; + case ClientMessage: + if (filtered) { + return; + } + if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { + const Atom protocol = (Atom)event->xclient.data.l[0]; + if (protocol == _sapp.x11.WM_DELETE_WINDOW) { + _sapp.quit_requested = true; + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { + const bool is_list = 0 != (event->xclient.data.l[1] & 1); + _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; + _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; + _sapp.x11.xdnd.format = None; + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + uint32_t count = 0; + Atom* formats = 0; + if (is_list) { + count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); + } + else { + count = 3; + formats = (Atom*) event->xclient.data.l + 2; + } + for (uint32_t i = 0; i < count; i++) { + if (formats[i] == _sapp.x11.xdnd.text_uri_list) { + _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; + break; + } + } + if (is_list && formats) { + XFree(formats); + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + Time time = CurrentTime; + if (_sapp.x11.xdnd.format) { + if (_sapp.x11.xdnd.version >= 1) { + time = (Time)event->xclient.data.l[2]; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.xdnd.format, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.window, + time); + } + else if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = 0; // drag was rejected + reply.xclient.data.l[2] = None; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } + else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { + /* drag operation has moved over the window + FIXME: we could track the mouse position here, but + this isn't implemented on other platforms either so far + */ + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + if (_sapp.x11.xdnd.format) { + /* reply that we are ready to copy the dragged data */ + reply.xclient.data.l[1] = 1; // accept with no rectangle + if (_sapp.x11.xdnd.version >= 2) { + reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; + } + } + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + break; + case SelectionNotify: + if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { + char* data = 0; + uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + if (_sapp.drop.enabled && result) { + if (_sapp_x11_parse_dropped_files_list(data)) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + } + if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = result; + reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } + break; + case DestroyNotify: + break; + } +} + +#if !defined(_SAPP_GLX) + +_SOKOL_PRIVATE void _sapp_egl_init(void) { +#if defined(SOKOL_GLCORE33) + if (!eglBindAPI(EGL_OPENGL_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); + } +#else + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); + } +#endif + + _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); + if (EGL_NO_DISPLAY == _sapp.egl.display) { + _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); + } + + EGLint major, minor; + if (!eglInitialize(_sapp.egl.display, &major, &minor)) { + _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); + } + + EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint config_attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLCORE33) + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + #elif defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES3_BIT, + #else + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + #endif + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0, + EGL_SAMPLES, sample_count, + EGL_NONE, + }; + + EGLConfig egl_configs[32]; + EGLint config_count; + if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) { + _SAPP_PANIC(LINUX_EGL_NO_CONFIGS); + } + + EGLConfig config = egl_configs[0]; + for (int i = 0; i < config_count; ++i) { + EGLConfig c = egl_configs[i]; + EGLint r, g, b, a, d, s, n; + if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) && + (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) { + config = c; + break; + } + } + + EGLint visual_id; + if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) { + _SAPP_PANIC(LINUX_EGL_NO_NATIVE_VISUAL); + } + + XVisualInfo visual_info_template; + _sapp_clear(&visual_info_template, sizeof(visual_info_template)); + visual_info_template.visualid = (VisualID)visual_id; + + int num_visuals; + XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals); + if (!visual_info) { + _SAPP_PANIC(LINUX_EGL_GET_VISUAL_INFO_FAILED); + } + + _sapp_x11_create_window(visual_info->visual, visual_info->depth); + XFree(visual_info); + + _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL); + if (EGL_NO_SURFACE == _sapp.egl.surface) { + _SAPP_PANIC(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED); + } + + EGLint ctx_attrs[] = { + #if defined(SOKOL_GLCORE33) + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #elif defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, + #else + EGL_CONTEXT_CLIENT_VERSION, 2, + #endif + EGL_NONE, + }; + + _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs); + if (EGL_NO_CONTEXT == _sapp.egl.context) { + _SAPP_PANIC(LINUX_EGL_CREATE_CONTEXT_FAILED); + } + + if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) { + _SAPP_PANIC(LINUX_EGL_MAKE_CURRENT_FAILED); + } + + eglSwapInterval(_sapp.egl.display, _sapp.swap_interval); + +#if defined(SOKOL_GLES3) + _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; +#endif +} + +_SOKOL_PRIVATE void _sapp_egl_destroy(void) { + if (_sapp.egl.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (_sapp.egl.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.egl.display, _sapp.egl.context); + _sapp.egl.context = EGL_NO_CONTEXT; + } + + if (_sapp.egl.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.egl.display, _sapp.egl.surface); + _sapp.egl.surface = EGL_NO_SURFACE; + } + + eglTerminate(_sapp.egl.display); + _sapp.egl.display = EGL_NO_DISPLAY; + } +} + +#endif /* _SAPP_GLX */ + +_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { + /* The following lines are here to trigger a linker error instead of an + obscure runtime error if the user has forgotten to add -pthread to + the compiler or linker options. They have no other purpose. + */ + pthread_attr_t pthread_attr; + pthread_attr_init(&pthread_attr); + pthread_attr_destroy(&pthread_attr); + + _sapp_init_state(desc); + _sapp.x11.window_state = NormalState; + + XInitThreads(); + XrmInitialize(); + _sapp.x11.display = XOpenDisplay(NULL); + if (!_sapp.x11.display) { + _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); + } + _sapp.x11.screen = DefaultScreen(_sapp.x11.display); + _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); + XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); + _sapp_x11_query_system_dpi(); + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_cursors(); +#if defined(_SAPP_GLX) + _sapp_glx_init(); + Visual* visual = 0; + int depth = 0; + _sapp_glx_choose_visual(&visual, &depth); + _sapp_x11_create_window(visual, depth); + _sapp_glx_create_context(); + _sapp_glx_swapinterval(_sapp.swap_interval); +#else + _sapp_egl_init(); +#endif + sapp_set_icon(&desc->icon); + _sapp.valid = true; + _sapp_x11_show_window(); + if (_sapp.fullscreen) { + _sapp_x11_set_fullscreen(true); + } + + XFlush(_sapp.x11.display); + while (!_sapp.quit_ordered) { + _sapp_timing_measure(&_sapp.timing); + int count = XPending(_sapp.x11.display); + while (count--) { + XEvent event; + XNextEvent(_sapp.x11.display, &event); + _sapp_x11_process_event(&event); + } + _sapp_frame(); +#if defined(_SAPP_GLX) + _sapp_glx_swap_buffers(); +#else + eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); +#endif + XFlush(_sapp.x11.display); + /* handle quit-requested, either from window or from sapp_request_quit() */ + if (_sapp.quit_requested && !_sapp.quit_ordered) { + /* give user code a chance to intervene */ + _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + } + _sapp_call_cleanup(); +#if defined(_SAPP_GLX) + _sapp_glx_destroy_context(); +#else + _sapp_egl_destroy(); +#endif + _sapp_x11_destroy_window(); + _sapp_x11_destroy_cursors(); + XCloseDisplay(_sapp.x11.display); + _sapp_discard_state(); +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_linux_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_LINUX */ + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +#if defined(SOKOL_NO_ENTRY) +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + #if defined(_SAPP_MACOS) + _sapp_macos_run(desc); + #elif defined(_SAPP_IOS) + _sapp_ios_run(desc); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_run(desc); + #elif defined(_SAPP_WIN32) + _sapp_win32_run(desc); + #elif defined(_SAPP_LINUX) + _sapp_linux_run(desc); + #else + #error "sapp_run() not supported on this platform" + #endif +} + +/* this is just a stub so the linker doesn't complain */ +sapp_desc sokol_main(int argc, char* argv[]) { + _SOKOL_UNUSED(argc); + _SOKOL_UNUSED(argv); + sapp_desc desc; + _sapp_clear(&desc, sizeof(desc)); + return desc; +} +#else +/* likewise, in normal mode, sapp_run() is just an empty stub */ +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + _SOKOL_UNUSED(desc); +} +#endif + +SOKOL_API_IMPL bool sapp_isvalid(void) { + return _sapp.valid; +} + +SOKOL_API_IMPL void* sapp_userdata(void) { + return _sapp.desc.user_data; +} + +SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { + return _sapp.desc; +} + +SOKOL_API_IMPL uint64_t sapp_frame_count(void) { + return _sapp.frame_count; +} + +SOKOL_API_IMPL double sapp_frame_duration(void) { + return _sapp_timing_get_avg(&_sapp.timing); +} + +SOKOL_API_IMPL int sapp_width(void) { + return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; +} + +SOKOL_API_IMPL float sapp_widthf(void) { + return (float)sapp_width(); +} + +SOKOL_API_IMPL int sapp_height(void) { + return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; +} + +SOKOL_API_IMPL float sapp_heightf(void) { + return (float)sapp_height(); +} + +SOKOL_API_IMPL int sapp_color_format(void) { + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + switch (_sapp.emsc.wgpu.render_format) { + case WGPUTextureFormat_RGBA8Unorm: + return _SAPP_PIXELFORMAT_RGBA8; + case WGPUTextureFormat_BGRA8Unorm: + return _SAPP_PIXELFORMAT_BGRA8; + default: + SOKOL_UNREACHABLE; + return 0; + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + return _SAPP_PIXELFORMAT_BGRA8; + #else + return _SAPP_PIXELFORMAT_RGBA8; + #endif +} + +SOKOL_API_IMPL int sapp_depth_format(void) { + return _SAPP_PIXELFORMAT_DEPTH_STENCIL; +} + +SOKOL_API_IMPL int sapp_sample_count(void) { + return _sapp.sample_count; +} + +SOKOL_API_IMPL bool sapp_high_dpi(void) { + return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); +} + +SOKOL_API_IMPL float sapp_dpi_scale(void) { + return _sapp.dpi_scale; +} + +SOKOL_APP_IMPL const void* sapp_egl_get_display(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.display; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.display; + #else + return 0; + #endif +} + +SOKOL_APP_IMPL const void* sapp_egl_get_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.context; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL bool sapp_gles2(void) { + return _sapp.gles2_fallback; +} + +SOKOL_API_IMPL void sapp_show_keyboard(bool show) { + #if defined(_SAPP_IOS) + _sapp_ios_show_keyboard(show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_show_keyboard(show); + #elif defined(_SAPP_ANDROID) + _sapp_android_show_keyboard(show); + #else + _SOKOL_UNUSED(show); + #endif +} + +SOKOL_API_IMPL bool sapp_keyboard_shown(void) { + return _sapp.onscreen_keyboard_shown; +} + +SOKOL_API_IMPL bool sapp_is_fullscreen(void) { + return _sapp.fullscreen; +} + +SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { + #if defined(_SAPP_MACOS) + _sapp_macos_toggle_fullscreen(); + #elif defined(_SAPP_WIN32) + _sapp_win32_toggle_fullscreen(); + #elif defined(_SAPP_LINUX) + _sapp_x11_toggle_fullscreen(); + #endif +} + +/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ +SOKOL_API_IMPL void sapp_show_mouse(bool show) { + if (_sapp.mouse.shown != show) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); + #endif + _sapp.mouse.shown = show; + } +} + +SOKOL_API_IMPL bool sapp_mouse_shown(void) { + return _sapp.mouse.shown; +} + +SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { + #if defined(_SAPP_MACOS) + _sapp_macos_lock_mouse(lock); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_lock_mouse(lock); + #elif defined(_SAPP_WIN32) + _sapp_win32_lock_mouse(lock); + #elif defined(_SAPP_LINUX) + _sapp_x11_lock_mouse(lock); + #else + _sapp.mouse.locked = lock; + #endif +} + +SOKOL_API_IMPL bool sapp_mouse_locked(void) { + return _sapp.mouse.locked; +} + +SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.mouse.current_cursor != cursor) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); + #endif + _sapp.mouse.current_cursor = cursor; + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + +SOKOL_API_IMPL void sapp_request_quit(void) { + _sapp.quit_requested = true; +} + +SOKOL_API_IMPL void sapp_cancel_quit(void) { + _sapp.quit_requested = false; +} + +SOKOL_API_IMPL void sapp_quit(void) { + _sapp.quit_ordered = true; +} + +SOKOL_API_IMPL void sapp_consume_event(void) { + _sapp.event_consumed = true; +} + +/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ +SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(str); + #if defined(_SAPP_MACOS) + _sapp_macos_set_clipboard_string(str); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_clipboard_string(str); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_clipboard_string(str); + #else + /* not implemented */ + #endif + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +} + +SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { + if (!_sapp.clipboard.enabled) { + return ""; + } + #if defined(_SAPP_MACOS) + return _sapp_macos_get_clipboard_string(); + #elif defined(_SAPP_EMSCRIPTEN) + return _sapp.clipboard.buffer; + #elif defined(_SAPP_WIN32) + return _sapp_win32_get_clipboard_string(); + #else + /* not implemented */ + return _sapp.clipboard.buffer; + #endif +} + +SOKOL_API_IMPL void sapp_set_window_title(const char* title) { + SOKOL_ASSERT(title); + _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); + #if defined(_SAPP_MACOS) + _sapp_macos_update_window_title(); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_window_title(); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_window_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; +} + +SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + SOKOL_ASSERT(_sapp.drop.buffer); + if (!_sapp.drop.enabled) { + return ""; + } + if ((index < 0) || (index >= _sapp.drop.max_files)) { + return ""; + } + return (const char*) _sapp_dropped_file_path_ptr(index); +} + +SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + #if defined(_SAPP_EMSCRIPTEN) + if (!_sapp.drop.enabled) { + return 0; + } + return sapp_js_dropped_file_size(index); + #else + (void)index; + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT(request); + SOKOL_ASSERT(request->callback); + SOKOL_ASSERT(request->buffer.ptr); + SOKOL_ASSERT(request->buffer.size > 0); + #if defined(_SAPP_EMSCRIPTEN) + const int index = request->dropped_file_index; + sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; + if ((index < 0) || (index >= _sapp.drop.num_files)) { + error_code = SAPP_HTML5_FETCH_ERROR_OTHER; + } + if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { + error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; + } + if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { + _sapp_emsc_invoke_fetch_cb(index, + false, // success + (int)error_code, + request->callback, + 0, // fetched_size + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } + else { + sapp_js_fetch_dropped_file(index, + request->callback, + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } + #else + (void)request; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.mtl_device; + #else + const void* obj = (__bridge const void*) _sapp.ios.mtl_device; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_macos_get_window(void) { + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_ios_get_window(void) { + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device_context; + #else + return 0; + #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) + if (_sapp.d3d11.msaa_rtv) { + return _sapp.d3d11.msaa_rtv; + } + else { + return _sapp.d3d11.rtv; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.dsv; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_WIN32) + return _sapp.win32.hwnd; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.emsc.wgpu.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + return (const void*) _sapp.emsc.wgpu.msaa_view; + } + else { + return (const void*) _sapp.emsc.wgpu.swapchain_view; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + return (const void*) _sapp.emsc.wgpu.swapchain_view; + } + else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.emsc.wgpu.depth_stencil_view; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { + // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() + // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) + #if defined(_SAPP_ANDROID) + return (void*)_sapp.android.activity; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { + _sapp.html5_ask_leave_site = ask; +} + +#endif /* SOKOL_APP_IMPL */ diff --git a/src/sokol/sokol_app.h.rej b/src/sokol/sokol_app.h.rej new file mode 100644 index 000000000..66ba5506d --- /dev/null +++ b/src/sokol/sokol_app.h.rej @@ -0,0 +1,19 @@ +*************** +*** 8091,8098 **** + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); +- dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; +- dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { +--- 8101,8108 ---- + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); ++ dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; ++ dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { diff --git a/src/sokol/sokol_app.patch b/src/sokol/sokol_app.patch new file mode 100644 index 000000000..09f8056e4 --- /dev/null +++ b/src/sokol/sokol_app.patch @@ -0,0 +1,328 @@ +diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h +index e9f90787..5af91c24 100644 +--- a/src/sokol/sokol_app.h ++++ b/src/sokol/sokol_app.h +@@ -1076,6 +1076,7 @@ typedef enum sapp_keycode { + SAPP_KEYCODE_RIGHT_ALT = 346, + SAPP_KEYCODE_RIGHT_SUPER = 347, + SAPP_KEYCODE_MENU = 348, ++ SAPP_KEYCODE_BACK = 349, + } sapp_keycode; + + typedef struct sapp_touchpoint { +@@ -1277,6 +1278,7 @@ SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); + SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); + /* iOS: get bridged pointer to iOS UIWindow */ + SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); ++SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); + + /* D3D11: get pointer to ID3D11Device object */ + SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); +@@ -1300,6 +1302,7 @@ SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + + /* Android: get native activity handle */ + SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); ++SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); + + #ifdef __cplusplus + } /* extern "C" */ +@@ -7558,6 +7561,10 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { + _sapp.android.context = context; + return true; + } ++SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ ++ //eglSwapInterval(_sapp.android.display,0); ++} ++ + + _SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { + if (_sapp.android.display != EGL_NO_DISPLAY) { +@@ -7701,6 +7708,9 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + if (!_sapp_events_enabled()) { + return false; + } ++ if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; ++ if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; ++ + int32_t action_idx = AMotionEvent_getAction(e); + int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; + sapp_event_type type = SAPP_EVENTTYPE_INVALID; +@@ -7739,8 +7749,8 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); +- dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; +- dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; ++ dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; ++ dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { +@@ -7753,19 +7763,225 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + return true; + } + ++_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { ++ switch (scancode) { ++ case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; ++ case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; ++ case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; ++ case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; ++ case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; ++ case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; ++ case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; ++ case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; ++ case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; ++ case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; ++ case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; ++ case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; ++ case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; ++ case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; ++ case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; ++ case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; ++ case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; ++ case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; ++ case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; ++ case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; ++ case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; ++ case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; ++ case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; ++ case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; ++ case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; ++ case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; ++ case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; ++ case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; ++ case AKEYCODE_F1: return SAPP_KEYCODE_F1; ++ case AKEYCODE_F2: return SAPP_KEYCODE_F2; ++ case AKEYCODE_F3: return SAPP_KEYCODE_F3; ++ case AKEYCODE_F4: return SAPP_KEYCODE_F4; ++ case AKEYCODE_F5: return SAPP_KEYCODE_F5; ++ case AKEYCODE_F6: return SAPP_KEYCODE_F6; ++ case AKEYCODE_F7: return SAPP_KEYCODE_F7; ++ case AKEYCODE_F8: return SAPP_KEYCODE_F8; ++ case AKEYCODE_F9: return SAPP_KEYCODE_F9; ++ case AKEYCODE_F10: return SAPP_KEYCODE_F10; ++ case AKEYCODE_F11: return SAPP_KEYCODE_F11; ++ case AKEYCODE_F12: return SAPP_KEYCODE_F12; ++ case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; ++ case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; ++ case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; ++ case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; ++ case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; ++ case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; ++ case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; ++ case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; ++ case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; ++ case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; ++ case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; ++ case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; ++ case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; ++ case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; ++ case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; ++ case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; ++ case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; ++ case AKEYCODE_A: return SAPP_KEYCODE_A; ++ case AKEYCODE_B: return SAPP_KEYCODE_B; ++ case AKEYCODE_C: return SAPP_KEYCODE_C; ++ case AKEYCODE_D: return SAPP_KEYCODE_D; ++ case AKEYCODE_E: return SAPP_KEYCODE_E; ++ case AKEYCODE_F: return SAPP_KEYCODE_F; ++ case AKEYCODE_G: return SAPP_KEYCODE_G; ++ case AKEYCODE_H: return SAPP_KEYCODE_H; ++ case AKEYCODE_I: return SAPP_KEYCODE_I; ++ case AKEYCODE_J: return SAPP_KEYCODE_J; ++ case AKEYCODE_K: return SAPP_KEYCODE_K; ++ case AKEYCODE_L: return SAPP_KEYCODE_L; ++ case AKEYCODE_M: return SAPP_KEYCODE_M; ++ case AKEYCODE_N: return SAPP_KEYCODE_N; ++ case AKEYCODE_O: return SAPP_KEYCODE_O; ++ case AKEYCODE_P: return SAPP_KEYCODE_P; ++ case AKEYCODE_Q: return SAPP_KEYCODE_Q; ++ case AKEYCODE_R: return SAPP_KEYCODE_R; ++ case AKEYCODE_S: return SAPP_KEYCODE_S; ++ case AKEYCODE_T: return SAPP_KEYCODE_T; ++ case AKEYCODE_U: return SAPP_KEYCODE_U; ++ case AKEYCODE_V: return SAPP_KEYCODE_V; ++ case AKEYCODE_W: return SAPP_KEYCODE_W; ++ case AKEYCODE_X: return SAPP_KEYCODE_X; ++ case AKEYCODE_Y: return SAPP_KEYCODE_Y; ++ case AKEYCODE_Z: return SAPP_KEYCODE_Z; ++ case AKEYCODE_1: return SAPP_KEYCODE_1; ++ case AKEYCODE_2: return SAPP_KEYCODE_2; ++ case AKEYCODE_3: return SAPP_KEYCODE_3; ++ case AKEYCODE_4: return SAPP_KEYCODE_4; ++ case AKEYCODE_5: return SAPP_KEYCODE_5; ++ case AKEYCODE_6: return SAPP_KEYCODE_6; ++ case AKEYCODE_7: return SAPP_KEYCODE_7; ++ case AKEYCODE_8: return SAPP_KEYCODE_8; ++ case AKEYCODE_9: return SAPP_KEYCODE_9; ++ case AKEYCODE_0: return SAPP_KEYCODE_0; ++ case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; ++ case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; ++ case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; ++ case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; ++ case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; ++ case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; ++ case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; ++ case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; ++ case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; ++ case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; ++ case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; ++ case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; ++ /* Android Buttons */ ++ case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; ++ default: return SAPP_KEYCODE_INVALID; ++ } ++ return SAPP_KEYCODE_INVALID; ++} ++_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { ++ uint32_t meta_state = AKeyEvent_getMetaState(e); ++ uint32_t mods = 0; ++ if (meta_state& AMETA_SHIFT_ON) { ++ mods |= SAPP_MODIFIER_SHIFT; ++ } ++ if (meta_state& AMETA_CTRL_ON) { ++ mods |= SAPP_MODIFIER_CTRL; ++ } ++ if (meta_state& AMETA_ALT_ON) { ++ mods |= SAPP_MODIFIER_ALT; ++ } ++ if (meta_state& AMETA_META_ON) { ++ mods |= SAPP_MODIFIER_SUPER; ++ } ++ return mods; ++} ++int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) ++{ ++ ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); ++ // Attaches the current thread to the JVM. ++ JavaVM *javaVM = activity->vm; ++ JNIEnv *jniEnv = activity->env; ++ ++ jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); ++ if(result == JNI_ERR){ ++ return 0; ++ } ++ ++ jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); ++ int unicodeKey; ++ ++ if(metaState == 0){ ++ jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); ++ jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); ++ jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); ++ unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); ++ }else{ ++ jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); ++ jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); ++ jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); ++ unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); ++ } ++ ++ (*javaVM)->DetachCurrentThread(javaVM); ++ ++ return unicodeKey; ++} ++_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { ++ if (_sapp_events_enabled() ) { ++ _sapp_init_event(SAPP_EVENTTYPE_CHAR); ++ _sapp.event.modifiers = _sapp_android_mods(e); ++ _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); ++ _sapp.event.key_repeat = repeat; ++ _sapp_call_event(&_sapp.event); ++ } ++} + _SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { + return false; + } +- if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { +- /* FIXME: this should be hooked into a "really quit?" mechanism +- so the app can ask the user for confirmation, this is currently +- generally missing in sokol_app.h +- */ +- _sapp_android_shutdown(); +- return true; ++ if (!_sapp_events_enabled()) { ++ return false; + } +- return false; ++ int32_t action = AKeyEvent_getAction(e); ++ // Don't process soft keyboard commands as they are not reliable through this interface. ++ if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; ++ // Don't relay key press events from joysticks or game pads as Sokol key down events ++ if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || ++ (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { ++ return false; ++ } ++ sapp_event_type type = SAPP_EVENTTYPE_INVALID; ++ switch (action) { ++ ++ case AKEY_EVENT_ACTION_DOWN : ++ type = SAPP_EVENTTYPE_KEY_DOWN; ++ break; ++ case AKEY_EVENT_ACTION_UP: ++ type = SAPP_EVENTTYPE_KEY_UP; ++ break; ++ default: ++ break; ++ } ++ if (type == SAPP_EVENTTYPE_INVALID) { ++ return false; ++ } ++ bool repeat = AKeyEvent_getRepeatCount(e)>0; ++ _sapp_init_event(type); ++ _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); ++ _sapp.event.modifiers = _sapp_android_mods(e); ++ _sapp.event.key_repeat = repeat; ++ _sapp_call_event(&_sapp.event); ++ if(type==SAPP_EVENTTYPE_KEY_DOWN){ ++ _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); ++ } ++ /* check if a CLIPBOARD_PASTED event must be sent too */ ++ if (_sapp.clipboard.enabled && ++ (type == SAPP_EVENTTYPE_KEY_DOWN) && ++ (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && ++ (_sapp.event.key_code == SAPP_KEYCODE_V)) ++ { ++ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); ++ _sapp_call_event(&_sapp.event); ++ } ++ return _sapp.event.key_code != SAPP_KEYCODE_INVALID; + } + + _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { +@@ -7930,6 +8146,16 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { + bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); + process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; + } ++ /* handle quit-requested, either from window or from sapp_request_quit() */ ++ if (_sapp.quit_requested && !_sapp.quit_ordered) { ++ /* give user code a chance to intervene */ ++ _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); ++ /* if user code hasn't intervened, quit the app */ ++ if (_sapp.quit_requested) { ++ _sapp.quit_ordered = true; ++ } ++ } ++ if(_sapp.quit_ordered) _sapp_android_shutdown(); + } + + /* cleanup thread */ +@@ -10618,6 +10844,16 @@ SOKOL_API_IMPL const void* sapp_ios_get_window(void) { + return 0; + #endif + } ++SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ ++ #if defined(_SAPP_IOS) ++ const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; ++ SOKOL_ASSERT(obj); ++ return obj; ++ #else ++ return 0; ++ #endif ++} ++ + + SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { + SOKOL_ASSERT(_sapp.valid); From b5a0dfd0db46110d93d3b53edeb98ac95ced270b Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 21:35:47 +0100 Subject: [PATCH 16/20] Revert sapp_html5_fetch_request to try to fix web build --- changes.patch | 7903 +++++++++++++++++++++++++++++++++++++++++ src/sokol/sokol_app.h | 9 +- 2 files changed, 7908 insertions(+), 4 deletions(-) create mode 100644 changes.patch diff --git a/changes.patch b/changes.patch new file mode 100644 index 000000000..b1c1a5bcb --- /dev/null +++ b/changes.patch @@ -0,0 +1,7903 @@ +diff --git a/src/sokol/sokol_gfx.h b/src/sokol/sokol_gfx.h +index 8727666..ecd79d9 100644 +--- a/src/sokol/sokol_gfx.h ++++ b/src/sokol/sokol_gfx.h +@@ -39,15 +39,15 @@ + + Optionally provide the following defines with your own implementations: + +- SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) +- SOKOL_MALLOC(s) - your own malloc function (default: malloc(s)) +- SOKOL_FREE(p) - your own free function (default: free(p)) +- SOKOL_LOG(msg) - your own logging function (default: puts(msg)) +- SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) +- SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) +- SOKOL_API_DECL - same as SOKOL_GFX_API_DECL +- SOKOL_API_IMPL - public function implementation prefix (default: -) +- SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) ++ SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) ++ SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) ++ SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) ++ SOKOL_API_DECL - same as SOKOL_GFX_API_DECL ++ SOKOL_API_IMPL - public function implementation prefix (default: -) ++ SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) ++ SOKOL_EXTERNAL_GL_LOADER - indicates that you're using your own GL loader, in this case ++ sokol_gfx.h will not include any platform GL headers and disable ++ the integrated Win32 GL loader + + If sokol_gfx.h is compiled as a DLL, define the following before + including the declaration or implementation: +@@ -81,6 +81,7 @@ + offline shader cross-compiler, see here: + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + ++ + STEP BY STEP + ============ + --- to initialize sokol_gfx, after creating a window and a 3D-API +@@ -88,6 +89,30 @@ + + sg_setup(const sg_desc*) + ++ Depending on the selected 3D backend, sokol-gfx requires some ++ information, like a device pointer framebuffer pixel formats ++ and so on. If you are using sokol_app.h for the window system ++ glue, you can use a helper function provided in the sokol_glue.h ++ header: ++ ++ #include "sokol_gfx.h" ++ #include "sokol_app.h" ++ #include "sokol_glue.h" ++ //... ++ sg_setup(&(sg_desc){ ++ .context = sapp_sgcontext(), ++ }); ++ ++ To get any logging output for errors and from the validation layer, you ++ need to provide a logging callback. Easiest way is through sokol_log.h: ++ ++ #include "sokol_log.h" ++ //... ++ sg_setup(&(sg_desc){ ++ //... ++ .logger.func = slog_func, ++ }); ++ + --- create resource objects (at least buffers, shaders and pipelines, + and optionally images and passes): + +@@ -126,7 +151,10 @@ + + --- optionally update shader uniform data with: + +- sg_apply_uniforms(sg_shader_stage stage, int ub_index, const void* data, int num_bytes) ++ sg_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range* data) ++ ++ Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout ++ of the uniform data passed into sg_apply_uniforms(). + + --- kick off a draw call with: + +@@ -166,7 +194,7 @@ + + sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) + +- ...or if you want to specifiy the viewport rectangle with float values: ++ ...or if you want to specify the viewport rectangle with float values: + + sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) + +@@ -243,6 +271,11 @@ + + bool sg_query_buffer_overflow(sg_buffer buf) + ++ You can manually check to see if an overflow would occur before adding ++ any data to a buffer by calling ++ ++ bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size) ++ + NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of + data will be 4-byte aligned in the destination buffer. This means + that there will be gaps in index buffers containing 16-bit indices +@@ -268,22 +301,25 @@ + by calling sg_query_desc(). This will return an sg_desc struct with + the default values patched in instead of any zero-initialized values + +- --- you can inspect various internal resource attributes via: +- +- sg_buffer_info sg_query_buffer_info(sg_buffer buf) +- sg_image_info sg_query_image_info(sg_image img) +- sg_shader_info sg_query_shader_info(sg_shader shd) +- sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) +- sg_pass_info sg_query_pass_info(sg_pass pass) ++ --- you can get a desc struct matching the creation attributes of a ++ specific resource object via: + +- ...please note that the returned info-structs are tied quite closely +- to sokol_gfx.h internals, and may change more often than other +- public API functions and structs. ++ sg_buffer_desc sg_query_buffer_desc(sg_buffer buf) ++ sg_image_desc sg_query_image_desc(sg_image img) ++ sg_shader_desc sq_query_shader_desc(sg_shader shd) ++ sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip) ++ sg_pass_desc sg_query_pass_desc(sg_pass pass) + +- --- you can ask at runtime what backend sokol_gfx.h has been compiled +- for, or whether the GLES3 backend had to fall back to GLES2 with: ++ ...but NOTE that the returned desc structs may be incomplete, only ++ creation attributes that are kept around internally after resource ++ creation will be filled in, and in some cases (like shaders) that's ++ very little. Any missing attributes will be set to zero. The returned ++ desc structs might still be useful as partial blueprint for creating ++ similar resources if filled up with the missing attributes. + +- sg_backend sg_query_backend(void) ++ Calling the query-desc functions on an invalid resource will return ++ completely zeroed structs (it makes sense to check the resource state ++ with sg_query_*_state() first) + + --- you can query the default resource creation parameters through the functions + +@@ -298,6 +334,24 @@ + will be replaced with their concrete values in the returned desc + struct. + ++ --- you can inspect various internal resource runtime values via: ++ ++ sg_buffer_info sg_query_buffer_info(sg_buffer buf) ++ sg_image_info sg_query_image_info(sg_image img) ++ sg_shader_info sg_query_shader_info(sg_shader shd) ++ sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) ++ sg_pass_info sg_query_pass_info(sg_pass pass) ++ ++ ...please note that the returned info-structs are tied quite closely ++ to sokol_gfx.h internals, and may change more often than other ++ public API functions and structs. ++ ++ --- you can ask at runtime what backend sokol_gfx.h has been compiled ++ for, or whether the GLES3 backend had to fall back to GLES2 with: ++ ++ sg_backend sg_query_backend(void) ++ ++ + ON INITIALIZATION: + ================== + When calling sg_setup(), a pointer to an sg_desc struct must be provided +@@ -331,30 +385,129 @@ + a convenience function to get a sg_context_desc struct filled out + with context information provided by sokol_app.h + +- See the documention block of the sg_desc struct below for more information. ++ See the documentation block of the sg_desc struct below for more information. ++ ++ ++ UNIFORM DATA LAYOUT: ++ ==================== ++ NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry ++ about the following details. ++ ++ The data that's passed into the sg_apply_uniforms() function must adhere to ++ specific layout rules so that the GPU shader finds the uniform block ++ items at the right offset. ++ ++ For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform ++ blocks, but not about the internal layout. The data will just be copied into ++ a uniform/constant buffer in a single operation and it's up you to arrange the ++ CPU-side layout so that it matches the GPU side layout. This also means that with ++ the D3D11 and Metal backends you are not limited to a 'cross-platform' subset ++ of uniform variable types. ++ ++ If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here. ++ ++ For the GL backends, the internal layout of uniform blocks matters though, ++ and you are limited to a small number of uniform variable types. This is ++ because sokol-gfx must be able to locate the uniform block members in order ++ to upload them to the GPU with glUniformXXX() calls. ++ ++ To describe the uniform block layout to sokol-gfx, the following information ++ must be passed to the sg_make_shader() call in the sg_shader_desc struct: ++ ++ - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or ++ SG_UNIFORMLAYOUT_STD140) ++ - a list of the uniform block members types in the correct order they ++ appear on the CPU side ++ ++ For example if the GLSL shader has the following uniform declarations: ++ ++ uniform mat4 mvp; ++ uniform vec2 offset0; ++ uniform vec2 offset1; ++ uniform vec2 offset2; ++ ++ ...and on the CPU side, there's a similar C struct: ++ ++ typedef struct { ++ float mvp[16]; ++ float offset0[2]; ++ float offset1[2]; ++ float offset2[2]; ++ } params_t; ++ ++ ...the uniform block description in the sg_shader_desc must look like this: ++ ++ sg_shader_desc desc = { ++ .vs.uniform_blocks[0] = { ++ .size = sizeof(params_t), ++ .layout = SG_UNIFORMLAYOUT_NATIVE, // this is the default and can be omitted ++ .uniforms = { ++ // order must be the same as in 'params_t': ++ [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 }, ++ [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 }, ++ [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 }, ++ [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 }, ++ } ++ } ++ }; ++ ++ With this information sokol-gfx can now compute the correct offsets of the data items ++ within the uniform block struct. ++ ++ The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used, ++ but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is ++ described in the next section: ++ ++ ++ CROSS-BACKEND COMMON UNIFORM DATA LAYOUT ++ ======================================== ++ For cross-platform / cross-3D-backend code it is important that the same uniform block ++ layout on the CPU side can be used for all sokol-gfx backends. To achieve this, ++ a common subset of the std140 layout must be used: ++ ++ - The uniform block layout hint in sg_shader_desc must be explicitly set to ++ SG_UNIFORMLAYOUT_STD140. ++ - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums): ++ - float => SG_UNIFORMTYPE_FLOAT ++ - vec2 => SG_UNIFORMTYPE_FLOAT2 ++ - vec3 => SG_UNIFORMTYPE_FLOAT3 ++ - vec4 => SG_UNIFORMTYPE_FLOAT4 ++ - int => SG_UNIFORMTYPE_INT ++ - ivec2 => SG_UNIFORMTYPE_INT2 ++ - ivec3 => SG_UNIFORMTYPE_INT3 ++ - ivec4 => SG_UNIFORMTYPE_INT4 ++ - mat4 => SG_UNIFORMTYPE_MAT4 ++ - Alignment for those types must be as follows (in bytes): ++ - float => 4 ++ - vec2 => 8 ++ - vec3 => 16 ++ - vec4 => 16 ++ - int => 4 ++ - ivec2 => 8 ++ - ivec3 => 16 ++ - ivec4 => 16 ++ - mat4 => 16 ++ - Arrays are only allowed for the following types: vec4, int4, mat4. ++ ++ Note that the HLSL cbuffer layout rules are slightly different from the ++ std140 layout rules, this means that the cbuffer declarations in HLSL code ++ must be tweaked so that the layout is compatible with std140. ++ ++ The by far easiest way to tacke the common uniform block layout problem is ++ to use the sokol-shdc shader cross-compiler tool! ++ + + BACKEND-SPECIFIC TOPICS: + ======================== +- --- the GL backends need to know about the internal structure of uniform +- blocks, and the texture sampler-name and -type: +- +- typedef struct { +- float mvp[16]; // model-view-projection matrix +- float offset0[2]; // some 2D vectors +- float offset1[2]; +- float offset2[2]; +- } params_t; ++ --- The GL backends need to know about the internal structure of uniform ++ blocks, and the texture sampler-name and -type. The uniform layout details ++ are described in the UNIFORM DATA LAYOUT section above. + + // uniform block structure and texture image definition in sg_shader_desc: + sg_shader_desc desc = { + // uniform block description (size and internal structure) + .vs.uniform_blocks[0] = { +- .size = sizeof(params_t), +- .uniforms = { +- [0] = { .name="mvp", .type=SG_UNIFORMTYPE_MAT4 }, +- [1] = { .name="offset0", .type=SG_UNIFORMTYPE_VEC2 }, +- ... +- } ++ ... + }, + // one texture on the fragment-shader-stage, GLES2/WebGL needs name and image type + .fs.images[0] = { .name="tex", .type=SG_IMAGETYPE_ARRAY } +@@ -431,6 +584,7 @@ + } + }; + ++ + WORKING WITH CONTEXTS + ===================== + sokol-gfx allows to switch between different rendering contexts and +@@ -474,6 +628,7 @@ + + https://github.com/floooh/sokol-samples/blob/master/glfw/multiwindow-glfw.c + ++ + TRACE HOOKS: + ============ + sokol_gfx.h optionally allows to install "trace hook" callbacks for +@@ -503,6 +658,7 @@ + imgui/sokol_gfx_imgui.h header which implements a realtime + debugging UI for sokol_gfx.h on top of Dear ImGui. + ++ + A NOTE ON PORTABLE PACKED VERTEX FORMATS: + ========================================= + There are two things to consider when using packed +@@ -531,9 +687,11 @@ + - SG_VERTEXFORMAT_SHORT2 + - SG_VERTEXFORMAT_SHORT4 + +- - WebGL/GLES2 cannot use integer vertex shader inputs (int or ivecn) ++ - WebGL/GLES2 cannot use integer vertex shader inputs (int or ivecn) or the following: + +- - SG_VERTEXFORMAT_UINT10_N2 is not supported on WebGL/GLES2 ++ - SG_VERTEXFORMAT_UINT10_N2 ++ - SG_VERTEXFORMAT_HALF2, SG_VERTEXFORMAT_HALF4 ++ (commonly supported extension: OES_vertex_half_float) + + So for a vertex input layout which works on all platforms, only use the following + vertex formats, and if needed "expand" the normalized vertex shader +@@ -551,10 +709,278 @@ + - SG_VERTEXFORMAT_SHORT4N, + - SG_VERTEXFORMAT_USHORT4N + +- TODO: +- ==== +- - talk about asynchronous resource creation + ++ MEMORY ALLOCATION OVERRIDE ++ ========================== ++ You can override the memory allocation functions at initialization time ++ like this: ++ ++ void* my_alloc(size_t size, void* user_data) { ++ return malloc(size); ++ } ++ ++ void my_free(void* ptr, void* user_data) { ++ free(ptr); ++ } ++ ++ ... ++ sg_setup(&(sg_desc){ ++ // ... ++ .allocator = { ++ .alloc = my_alloc, ++ .free = my_free, ++ .user_data = ..., ++ } ++ }); ++ ... ++ ++ If no overrides are provided, malloc and free will be used. ++ ++ This only affects memory allocation calls done by sokol_gfx.h ++ itself though, not any allocations in OS libraries. ++ ++ ERROR REPORTING AND LOGGING ++ =========================== ++ To get any logging information at all you need to provide a logging callback in the setup call ++ the easiest way is to use sokol_log.h: ++ ++ #include "sokol_log.h" ++ ++ sg_setup(&(sg_desc){ .logger.func = slog_func }); ++ ++ To override logging with your own callback, first write a logging function like this: ++ ++ void my_log(const char* tag, // e.g. 'sg' ++ uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info ++ uint32_t log_item_id, // SG_LOGITEM_* ++ const char* message_or_null, // a message string, may be nullptr in release mode ++ uint32_t line_nr, // line number in sokol_gfx.h ++ const char* filename_or_null, // source filename, may be nullptr in release mode ++ void* user_data) ++ { ++ ... ++ } ++ ++ ...and then setup sokol-gfx like this: ++ ++ sg_setup(&(sg_desc){ ++ .logger = { ++ .func = my_log, ++ .user_data = my_user_data, ++ } ++ }); ++ ++ The provided logging function must be reentrant (e.g. be callable from ++ different threads). ++ ++ If you don't want to provide your own custom logger it is highly recommended to use ++ the standard logger in sokol_log.h instead, otherwise you won't see any warnings or ++ errors. ++ ++ ++ COMMIT LISTENERS ++ ================ ++ It's possible to hook callback functions into sokol-gfx which are called from ++ inside sg_commit() in unspecified order. This is mainly useful for libraries ++ that build on top of sokol_gfx.h to be notified about the end/start of a frame. ++ ++ To add a commit listener, call: ++ ++ static void my_commit_listener(void* user_data) { ++ ... ++ } ++ ++ bool success = sg_add_commit_listener((sg_commit_listener){ ++ .func = my_commit_listener, ++ .user_data = ..., ++ }); ++ ++ The function returns false if the internal array of commit listeners is full, ++ or the same commit listener had already been added. ++ ++ If the function returns true, my_commit_listener() will be called each frame ++ from inside sg_commit(). ++ ++ By default, 1024 distinct commit listeners can be added, but this number ++ can be tweaked in the sg_setup() call: ++ ++ sg_setup(&(sg_desc){ ++ .max_commit_listeners = 2048, ++ }); ++ ++ An sg_commit_listener item is equal to another if both the function ++ pointer and user_data field are equal. ++ ++ To remove a commit listener: ++ ++ bool success = sg_remove_commit_listener((sg_commit_listener){ ++ .func = my_commit_listener, ++ .user_data = ..., ++ }); ++ ++ ...where the .func and .user_data field are equal to a previous ++ sg_add_commit_listener() call. The function returns true if the commit ++ listener item was found and removed, and false otherwise. ++ ++ ++ RESOURCE CREATION AND DESTRUCTION IN DETAIL ++ =========================================== ++ The 'vanilla' way to create resource objects is with the 'make functions': ++ ++ sg_buffer sg_make_buffer(const sg_buffer_desc* desc) ++ sg_image sg_make_image(const sg_image_desc* desc) ++ sg_shader sg_make_shader(const sg_shader_desc* desc) ++ sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) ++ sg_pass sg_make_pass(const sg_pass_desc* desc) ++ ++ This will result in one of three cases: ++ ++ 1. The returned handle is invalid. This happens when there are no more ++ free slots in the resource pool for this resource type. An invalid ++ handle is associated with the INVALID resource state, for instance: ++ ++ sg_buffer buf = sg_make_buffer(...) ++ if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) { ++ // buffer pool is exhausted ++ } ++ ++ 2. The returned handle is valid, but creating the underlying resource ++ has failed for some reason. This results in a resource object in the ++ FAILED state. The reason *why* resource creation has failed differ ++ by resource type. Look for log messages with more details. A failed ++ resource state can be checked with: ++ ++ sg_buffer buf = sg_make_buffer(...) ++ if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) { ++ // creating the resource has failed ++ } ++ ++ 3. And finally, if everything goes right, the returned resource is ++ in resource state VALID and ready to use. This can be checked ++ with: ++ ++ sg_buffer buf = sg_make_buffer(...) ++ if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) { ++ // creating the resource has failed ++ } ++ ++ When calling the 'make functions', the created resource goes through a number ++ of states: ++ ++ - INITIAL: the resource slot associated with the new resource is currently ++ free (technically, there is no resource yet, just an empty pool slot) ++ - ALLOC: a handle for the new resource has been allocated, this just means ++ a pool slot has been reserved. ++ - VALID or FAILED: in VALID state any 3D API backend resource objects have ++ been successfully created, otherwise if anything went wrong, the resource ++ will be in FAILED state. ++ ++ Sometimes it makes sense to first grab a handle, but initialize the ++ underlying resource at a later time. For instance when loading data ++ asynchronously from a slow data source, you may know what buffers and ++ textures are needed at an early stage of the loading process, but actually ++ loading the buffer or texture content can only be completed at a later time. ++ ++ For such situations, sokol-gfx resource objects can be created in two steps. ++ You can allocate a handle upfront with one of the 'alloc functions': ++ ++ sg_buffer sg_alloc_buffer(void) ++ sg_image sg_alloc_image(void) ++ sg_shader sg_alloc_shader(void) ++ sg_pipeline sg_alloc_pipeline(void) ++ sg_pass sg_alloc_pass(void) ++ ++ This will return a handle with the underlying resource object in the ++ ALLOC state: ++ ++ sg_image img = sg_alloc_image(); ++ if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) { ++ // allocating an image handle has succeeded, otherwise ++ // the image pool is full ++ } ++ ++ Such an 'incomplete' handle can be used in most sokol-gfx rendering functions ++ without doing any harm, sokol-gfx will simply skip any rendering operation ++ that involve resources which are not in VALID state. ++ ++ At a later time (for instance once the texture has completed loading ++ asynchronously), the resource creation can be completed by calling one of ++ the 'init functions', those functions take an existing resource handle and ++ 'desc struct': ++ ++ void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc) ++ void sg_init_image(sg_image img, const sg_image_desc* desc) ++ void sg_init_shader(sg_shader shd, const sg_shader_desc* desc) ++ void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc) ++ void sg_init_pass(sg_pass pass, const sg_pass_desc* desc) ++ ++ The init functions expect a resource in ALLOC state, and after the function ++ returns, the resource will be either in VALID or FAILED state. Calling ++ an 'alloc function' followed by the matching 'init function' is fully ++ equivalent with calling the 'make function' alone. ++ ++ Destruction can also happen as a two-step process. The 'uninit functions' ++ will put a resource object from the VALID or FAILED state back into the ++ ALLOC state: ++ ++ void sg_uninit_buffer(sg_buffer buf) ++ void sg_uninit_image(sg_image img) ++ void sg_uninit_shader(sg_shader shd) ++ void sg_uninit_pipeline(sg_pipeline pip) ++ void sg_uninit_pass(sg_pass pass) ++ ++ Calling the 'uninit functions' with a resource that is not in the VALID or ++ FAILED state is a no-op. ++ ++ To finally free the pool slot for recycling call the 'dealloc functions': ++ ++ void sg_dealloc_buffer(sg_buffer buf) ++ void sg_dealloc_image(sg_image img) ++ void sg_dealloc_shader(sg_shader shd) ++ void sg_dealloc_pipeline(sg_pipeline pip) ++ void sg_dealloc_pass(sg_pass pass) ++ ++ Calling the 'dealloc functions' on a resource that's not in ALLOC state is ++ a no-op, but will generate a warning log message. ++ ++ Calling an 'uninit function' and 'dealloc function' in sequence is equivalent ++ with calling the associated 'destroy function': ++ ++ void sg_destroy_buffer(sg_buffer buf) ++ void sg_destroy_image(sg_image img) ++ void sg_destroy_shader(sg_shader shd) ++ void sg_destroy_pipeline(sg_pipeline pip) ++ void sg_destroy_pass(sg_pass pass) ++ ++ The 'destroy functions' can be called on resources in any state and generally ++ do the right thing (for instance if the resource is in ALLOC state, the destroy ++ function will be equivalent to the 'dealloc function' and skip the 'uninit part'). ++ ++ And finally to close the circle, the 'fail functions' can be called to manually ++ put a resource in ALLOC state into the FAILED state: ++ ++ sg_fail_buffer(sg_buffer buf) ++ sg_fail_image(sg_image img) ++ sg_fail_shader(sg_shader shd) ++ sg_fail_pipeline(sg_pipeline pip) ++ sg_fail_pass(sg_pass pass) ++ ++ This is recommended if anything went wrong outside of sokol-gfx during asynchronous ++ resource creation (for instance the file loading operation failed). In this case, ++ the 'fail function' should be called instead of the 'init function'. ++ ++ Calling a 'fail function' on a resource that's not in ALLOC state is a no-op, ++ but will generate a warning log message. ++ ++ NOTE: that two-step resource creation usually only makes sense for buffers ++ and images, but not for shaders, pipelines or passes. Most notably, trying ++ to create a pipeline object with a shader that's not in VALID state will ++ trigger a validation layer error, or if the validation layer is disabled, ++ result in a pipeline object in FAILED state. Same when trying to create ++ a pass object with image invalid image objects. ++ ++ LICENSE ++ ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog +@@ -644,7 +1070,7 @@ typedef struct sg_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:4202) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ ++#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ + #endif + #if defined(__cplusplus) + #define SG_RANGE(x) sg_range{ &x, sizeof(x) } +@@ -777,6 +1203,7 @@ typedef enum sg_pixel_format { + SG_PIXELFORMAT_RG16SI, + SG_PIXELFORMAT_RG16F, + SG_PIXELFORMAT_RGBA8, ++ SG_PIXELFORMAT_SRGB8A8, + SG_PIXELFORMAT_RGBA8SN, + SG_PIXELFORMAT_RGBA8UI, + SG_PIXELFORMAT_RGBA8SI, +@@ -820,6 +1247,8 @@ typedef enum sg_pixel_format { + SG_PIXELFORMAT_ETC2_RG11, + SG_PIXELFORMAT_ETC2_RG11SN, + ++ SG_PIXELFORMAT_RGB9E5, ++ + _SG_PIXELFORMAT_NUM, + _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF + } sg_pixel_format; +@@ -868,7 +1297,9 @@ 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) ++ int gl_max_combined_texture_image_units; // <= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (only on GL backends) + } sg_limits; + + /* +@@ -1168,6 +1599,8 @@ typedef enum sg_vertex_format { + SG_VERTEXFORMAT_SHORT4N, + SG_VERTEXFORMAT_USHORT4N, + SG_VERTEXFORMAT_UINT10_N2, ++ SG_VERTEXFORMAT_HALF2, ++ SG_VERTEXFORMAT_HALF4, + _SG_VERTEXFORMAT_NUM, + _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF + } sg_vertex_format; +@@ -1204,11 +1637,57 @@ typedef enum sg_uniform_type { + SG_UNIFORMTYPE_FLOAT2, + SG_UNIFORMTYPE_FLOAT3, + SG_UNIFORMTYPE_FLOAT4, ++ SG_UNIFORMTYPE_INT, ++ SG_UNIFORMTYPE_INT2, ++ SG_UNIFORMTYPE_INT3, ++ SG_UNIFORMTYPE_INT4, + SG_UNIFORMTYPE_MAT4, + _SG_UNIFORMTYPE_NUM, + _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF + } sg_uniform_type; + ++/* ++ sg_uniform_layout ++ ++ A hint for the interior memory layout of uniform blocks. This is ++ only really relevant for the GL backend where the internal layout ++ of uniform blocks must be known to sokol-gfx. For all other backends the ++ internal memory layout of uniform blocks doesn't matter, sokol-gfx ++ will just pass uniform data as a single memory blob to the ++ 3D backend. ++ ++ SG_UNIFORMLAYOUT_NATIVE (default) ++ Native layout means that a 'backend-native' memory layout ++ is used. For the GL backend this means that uniforms ++ are packed tightly in memory (e.g. there are no padding ++ bytes). ++ ++ SG_UNIFORMLAYOUT_STD140 ++ The memory layout is a subset of std140. Arrays are only ++ allowed for the FLOAT4, INT4 and MAT4. Alignment is as ++ is as follows: ++ ++ FLOAT, INT: 4 byte alignment ++ FLOAT2, INT2: 8 byte alignment ++ FLOAT3, INT3: 16 byte alignment(!) ++ FLOAT4, INT4: 16 byte alignment ++ MAT4: 16 byte alignment ++ FLOAT4[], INT4[]: 16 byte alignment ++ ++ The overall size of the uniform block must be a multiple ++ of 16. ++ ++ For more information search for 'UNIFORM DATA LAYOUT' in the documentation block ++ at the start of the header. ++*/ ++typedef enum sg_uniform_layout { ++ _SG_UNIFORMLAYOUT_DEFAULT, /* value 0 reserved for default-init */ ++ SG_UNIFORMLAYOUT_NATIVE, /* default: layout depends on currently active backend */ ++ SG_UNIFORMLAYOUT_STD140, /* std140: memory layout according to std140 */ ++ _SG_UNIFORMLAYOUT_NUM, ++ _SG_UNIFORMLAYOUT_FORCE_U32 = 0x7FFFFFFF ++} sg_uniform_layout; ++ + /* + sg_cull_mode + +@@ -1710,6 +2189,7 @@ typedef struct sg_image_desc { + defaults are "vs_4_0" and "ps_4_0") + - reflection info for each uniform block used by the shader stage: + - the size of the uniform block in bytes ++ - a memory layout hint (native vs std140, only required for GL backends) + - reflection info for each uniform block member (only required for GL backends): + - member name + - member type (SG_UNIFORMTYPE_xxx) +@@ -1742,6 +2222,7 @@ typedef struct sg_shader_uniform_desc { + + typedef struct sg_shader_uniform_block_desc { + size_t size; ++ sg_uniform_layout layout; + sg_shader_uniform_desc uniforms[SG_MAX_UB_MEMBERS]; + } sg_shader_uniform_block_desc; + +@@ -1811,9 +2292,9 @@ typedef struct sg_shader_desc { + .enabled: false + .front/back: + .compare: SG_COMPAREFUNC_ALWAYS ++ .fail_op: SG_STENCILOP_KEEP + .depth_fail_op: SG_STENCILOP_KEEP + .pass_op: SG_STENCILOP_KEEP +- .compare: SG_COMPAREFUNC_ALWAYS + .read_mask: 0 + .write_mask: 0 + .ref: 0 +@@ -2075,12 +2556,10 @@ typedef struct sg_image_info { + uint32_t upd_frame_index; /* frame index of last sg_update_image() */ + int num_slots; /* number of renaming-slots for dynamically updated images */ + int active_slot; /* currently active write-slot for dynamically updated images */ +- int width; /* image width */ +- int height; /* image height */ + } sg_image_info; + + typedef struct sg_shader_info { +- sg_slot_info slot; /* resoure pool slot info */ ++ sg_slot_info slot; /* resource pool slot info */ + } sg_shader_info; + + typedef struct sg_pipeline_info { +@@ -2091,6 +2570,198 @@ typedef struct sg_pass_info { + sg_slot_info slot; /* resource pool slot info */ + } sg_pass_info; + ++/* ++ sg_log_item ++ ++ An enum with a unique item for each log message, warning, error ++ and validation layer message. ++*/ ++#define _SG_LOG_ITEMS \ ++ _SG_LOGITEM_XMACRO(OK, "Ok") \ ++ _SG_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ ++ _SG_LOGITEM_XMACRO(GL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_3D_TEXTURES_NOT_SUPPORTED, "3d textures not supported (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_ARRAY_TEXTURES_NOT_SUPPORTED, "array textures not supported (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_SHADER_COMPILATION_FAILED, "shader compilation failed (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_INCOMPLETE, "framebuffer completeness check failed (gl)") \ ++ _SG_LOGITEM_XMACRO(GL_MSAA_FRAMEBUFFER_INCOMPLETE, "completeness check failed for msaa resolve framebuffer (gl)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 2d-, cube- or array-texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_FAILED, "CreateTexture2D() failed for 2d-, cube- or array-texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_SRV_FAILED, "CreateShaderResourceView() failed for 2d-, cube- or array-texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 3D texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_FAILED, "CreateTexture3D() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_SRV_FAILED, "CreateShaderResourceView() failed for 3d texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_MSAA_TEXTURE_FAILED, "CreateTexture2D() failed for MSAA render target texture (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_SAMPLER_STATE_FAILED, "CreateSamplerState() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED, "loading d3dcompiler_47.dll failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_FAILED, "shader compilation failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_OUTPUT, "") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_CONSTANT_BUFFER_FAILED, "CreateBuffer() failed for uniform constant buffer (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_INPUT_LAYOUT_FAILED, "CreateInputLayout() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_RASTERIZER_STATE_FAILED, "CreateRasterizerState() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED, "CreateDepthStencilState() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_BLEND_STATE_FAILED, "CreateBlendState() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_RTV_FAILED, "CreateRenderTargetView() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_CREATE_DSV_FAILED, "CreateDepthStencilView() failed (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \ ++ _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \ ++ _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \ ++ _SG_LOGITEM_XMACRO(METAL_VERTEX_SHADER_ENTRY_NOT_FOUND, "vertex shader entry function not found (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND, "fragment shader entry not found (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \ ++ _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \ ++ _SG_LOGITEM_XMACRO(WGPU_MAP_UNIFORM_BUFFER_FAILED, "mapping uniform buffer failed (wgpu)") \ ++ _SG_LOGITEM_XMACRO(WGPU_STAGING_BUFFER_FULL_COPY_TO_BUFFER, "per frame staging buffer full when copying to buffer (wgpu)") \ ++ _SG_LOGITEM_XMACRO(WGPU_STAGING_BUFFER_FULL_COPY_TO_TEXTURE, "per frame staging buffer full when copying to texture (wgpu)") \ ++ _SG_LOGITEM_XMACRO(WGPU_RESET_STATE_CACHE_FIXME, "_sg_wgpu_reset_state_cache: fixme") \ ++ _SG_LOGITEM_XMACRO(WGPU_ACTIVATE_CONTEXT_FIXME, "_sg_wgpu_activate_context: fixme") \ ++ _SG_LOGITEM_XMACRO(UNINIT_BUFFER_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in buffer uninit (must be same as for creation)") \ ++ _SG_LOGITEM_XMACRO(UNINIT_IMAGE_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in image uninit (must be same as for creation)") \ ++ _SG_LOGITEM_XMACRO(UNINIT_SHADER_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in shader uninit (must be same as for creation)") \ ++ _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in pipeline uninit (must be same as for creation)") \ ++ _SG_LOGITEM_XMACRO(UNINIT_PASS_ACTIVE_CONTEXT_MISMATCH, "active context mismatch in pass uninit (must be same as for creation)") \ ++ _SG_LOGITEM_XMACRO(IDENTICAL_COMMIT_LISTENER, "attempting to add identical commit listener") \ ++ _SG_LOGITEM_XMACRO(COMMIT_LISTENER_ARRAY_FULL, "commit listener array full") \ ++ _SG_LOGITEM_XMACRO(TRACE_HOOKS_NOT_ENABLED, "sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined") \ ++ _SG_LOGITEM_XMACRO(DEALLOC_BUFFER_INVALID_STATE, "sg_dealloc_buffer(): buffer must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(DEALLOC_IMAGE_INVALID_STATE, "sg_dealloc_image(): image must be in alloc state") \ ++ _SG_LOGITEM_XMACRO(DEALLOC_SHADER_INVALID_STATE, "sg_dealloc_shader(): shader must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(DEALLOC_PIPELINE_INVALID_STATE, "sg_dealloc_pipeline(): pipeline must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(DEALLOC_PASS_INVALID_STATE, "sg_dealloc_pass(): pass must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(INIT_BUFFER_INVALID_STATE, "sg_init_buffer(): buffer must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(INIT_IMAGE_INVALID_STATE, "sg_init_image(): image must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(INIT_SHADER_INVALID_STATE, "sg_init_shader(): shader must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(INIT_PIPELINE_INVALID_STATE, "sg_init_pipeline(): pipeline must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(INIT_PASS_INVALID_STATE, "sg_init_pass(): pass must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID or FAILED state") \ ++ _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID or FAILED state") \ ++ _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID or FAILED state") \ ++ _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID or FAILED state") \ ++ _SG_LOGITEM_XMACRO(UNINIT_PASS_INVALID_STATE, "sg_uninit_pass(): pass must be in VALID or FAILED state") \ ++ _SG_LOGITEM_XMACRO(FAIL_BUFFER_INVALID_STATE, "sg_fail_buffer(): buffer must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(FAIL_IMAGE_INVALID_STATE, "sg_fail_image(): image must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(FAIL_SHADER_INVALID_STATE, "sg_fail_shader(): shader must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(FAIL_PIPELINE_INVALID_STATE, "sg_fail_pipeline(): pipeline must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(FAIL_PASS_INVALID_STATE, "sg_fail_pass(): pass must be in ALLOC state") \ ++ _SG_LOGITEM_XMACRO(BUFFER_POOL_EXHAUSTED, "buffer pool exhausted") \ ++ _SG_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ ++ _SG_LOGITEM_XMACRO(SHADER_POOL_EXHAUSTED, "shader pool exhausted") \ ++ _SG_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted") \ ++ _SG_LOGITEM_XMACRO(PASS_POOL_EXHAUSTED, "pass pool exhausted") \ ++ _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_CANARY, "sg_buffer_desc not initialized") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_SIZE, "sg_buffer_desc.size cannot be 0") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA, "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA_SIZE, "immutable buffer data size differs from buffer size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_NO_DATA, "dynamic/stream usage buffers cannot be initialized with data") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_NODATA, "sg_image_data: no data (.ptr and/or .size is zero)") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_DATA_SIZE, "sg_image_data: data size doesn't match expected surface size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_CANARY, "sg_image_desc not initialized") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_WIDTH, "sg_image_desc.width must be > 0") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_HEIGHT, "sg_image_desc.height must be > 0") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_PIXELFORMAT, "invalid pixel format for render-target image") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, "invalid pixel format for non-render-target image") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, "non-render-target images cannot be multisampled") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, "MSAA not supported for this pixel format") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_IMMUTABLE, "render target images must be SG_USAGE_IMMUTABLE") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_NO_DATA, "render target images cannot be initialized with data") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_INJECTED_NO_DATA, "images with injected textures cannot be initialized with data") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA, "dynamic/stream images cannot be initialized with data") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE, "compressed images must be immutable") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_CANARY, "sg_shader_desc not initialized") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SOURCE, "shader source code required") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_BYTECODE, "shader byte code required") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, "shader source or byte code required") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UBS, "shader uniform blocks must occupy continuous slots") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, "uniform block members must occupy continuous slots") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_UB_MEMBERS, "GL backend requires uniform block member declarations") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_MEMBER_NAME, "uniform block member name missing") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, "size of uniform block members doesn't match uniform block size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_ARRAY_COUNT, "uniform array count must be >= 1") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_STD140_ARRAY_TYPE, "uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_IMGS, "shader images must occupy continuous slots") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMG_NAME, "GL backend requires uniform block member names") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_NAMES, "GLES2 backend requires vertex attribute names") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_SEMANTICS, "D3D11 backend requires vertex attribute semantics") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, "vertex attribute name/semantic string too long (max len 16)") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_CANARY, "sg_pipeline_desc not initialized") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER, "sg_pipeline_desc.shader missing or invalid") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_ATTRS, "sg_pipeline_desc.layout.attrs is empty or not continuous") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_NAME, "GLES2/WebGL missing vertex attribute name in shader") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_CANARY, "sg_pass_desc not initialized") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_NO_COLOR_ATTS, "sg_pass_desc.color_attachments[0] must be valid") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS, "color attachments must occupy continuous slots") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE, "pass attachment image is not valid") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_MIPLEVEL, "pass attachment mip level is bigger than image has mipmaps") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_FACE, "pass attachment image is cubemap, but face index is too big") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_LAYER, "pass attachment image is array texture, but layer index is too big") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_SLICE, "pass attachment image is 3d texture, but slice value is too big") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_NO_RT, "pass attachment image must be render targets") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT, "pass color-attachment images must have a renderable pixel format") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT, "pass depth-attachment image must have depth pixel format") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_SIZES, "all pass attachments must have the same size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS, "all pass attachments must have the same sample count") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_PASS, "sg_begin_pass: pass must be valid") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_IMAGE, "sg_begin_pass: one or more attachment images are not valid") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID_ID, "sg_apply_pipeline: invalid pipeline id provided") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_EXISTS, "sg_apply_pipeline: pipeline object no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID, "sg_apply_pipeline: pipeline object not in valid state") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_EXISTS, "sg_apply_pipeline: shader object no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_VALID, "sg_apply_pipeline: shader object not in valid state") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATT_COUNT, "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_FORMAT, "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTH_FORMAT, "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APIP_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE, "sg_apply_bindings: must be called after sg_apply_pipeline") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_EXISTS, "sg_apply_bindings: currently applied pipeline object no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_VALID, "sg_apply_bindings: currently applied pipeline object not in valid state") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBS, "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_EXISTS, "sg_apply_bindings: vertex buffer no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_TYPE, "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer slot is overflown") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_IB, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_EXISTS, "sg_apply_bindings: index buffer no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_TYPE, "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMGS, "sg_apply_bindings: vertex shader image count doesn't match sg_shader_desc") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_EXISTS, "sg_apply_bindings: vertex shader image no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_TYPES, "sg_apply_bindings: one or more vertex shader image types don't match sg_shader_desc") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMGS, "sg_apply_bindings: fragment shader image count doesn't match sg_shader_desc") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_EXISTS, "sg_apply_bindings: fragment shader image no longer alive") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_TYPES, "sg_apply_bindings: one or more fragment shader image types don't match sg_shader_desc") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_UB_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size exceeds declared uniform block size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_APPEND, "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_USAGE, "sg_append_buffer: cannot append to immutable buffer") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_SIZE, "sg_append_buffer: overall appended size is bigger than buffer size") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_UPDATE, "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_USAGE, "sg_update_image: cannot update immutable image") \ ++ _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_ONCE, "sg_update_image: only one update allowed per image and frame") \ ++ _SG_LOGITEM_XMACRO(VALIDATION_FAILED, "validation layer checks failed") \ ++ ++#define _SG_LOGITEM_XMACRO(item,msg) SG_LOGITEM_##item, ++typedef enum sg_log_item { ++ _SG_LOG_ITEMS ++} sg_log_item; ++#undef _SG_LOGITEM_XMACRO ++ + /* + sg_desc + +@@ -2112,9 +2783,15 @@ typedef struct sg_pass_info { + .pipeline_pool_size 64 + .pass_pool_size 16 + .context_pool_size 16 +- .sampler_cache_size 64 + .uniform_buffer_size 4 MB (4*1024*1024) + .staging_buffer_size 8 MB (8*1024*1024) ++ .sampler_cache_size 64 ++ .max_commit_listeners 1024 ++ .disable_validation false ++ ++ .allocator.alloc 0 (in this case, malloc() will be called) ++ .allocator.free 0 (in this case, free() will be called) ++ .allocator.user_data 0 + + .context.color_format: default value depends on selected backend: + all GL backends: SG_PIXELFORMAT_RGBA8 +@@ -2171,7 +2848,7 @@ typedef struct sg_pass_info { + ID3D11DepthStencilView object of the default framebuffer, + this function will be called in sg_begin_pass() when rendering + to the default framebuffer +- .context.metal.user_data ++ .context.d3d11.user_data + optional user data pointer passed to the userdata versions of + callback functions + +@@ -2192,7 +2869,7 @@ typedef struct sg_pass_info { + .context.wgpu.depth_stencil_view_userdata_cb + callback to get current default-pass depth-stencil-surface WGPUTextureView + the pixel format of the default WGPUTextureView must be WGPUTextureFormat_Depth24Plus8 +- .context.metal.user_data ++ .context.wgpu.user_data + optional user data pointer passed to the userdata versions of + callback functions + +@@ -2246,6 +2923,56 @@ typedef struct sg_context_desc { + sg_wgpu_context_desc wgpu; + } sg_context_desc; + ++/* ++ sg_commit_listener ++ ++ Used with function sg_add_commit_listener() to add a callback ++ which will be called in sg_commit(). This is useful for libraries ++ building on top of sokol-gfx to be notified about when a frame ++ ends (instead of having to guess, or add a manual 'new-frame' ++ function. ++*/ ++typedef struct sg_commit_listener { ++ void (*func)(void* user_data); ++ void* user_data; ++} sg_commit_listener; ++ ++/* ++ sg_allocator ++ ++ Used in sg_desc to provide custom memory-alloc and -free functions ++ to sokol_gfx.h. If memory management should be overridden, both the ++ alloc and free function must be provided (e.g. it's not valid to ++ override one function but not the other). ++*/ ++typedef struct sg_allocator { ++ void* (*alloc)(size_t size, void* user_data); ++ void (*free)(void* ptr, void* user_data); ++ void* user_data; ++} sg_allocator; ++ ++/* ++ sg_logger ++ ++ Used in sg_desc to provide a logging function. Please be aware ++ that without logging function, sokol-gfx will be completely ++ silent, e.g. it will not report errors, warnings and ++ validation layer messages. For maximum error verbosity, ++ compile in debug mode (e.g. NDEBUG *not* defined) and install ++ a logger (for instance the standard logging function from sokol_log.h). ++*/ ++typedef struct sg_logger { ++ void (*func)( ++ const char* tag, // always "sg" ++ uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info ++ uint32_t log_item_id, // SG_LOGITEM_* ++ const char* message_or_null, // a message string, may be nullptr in release mode ++ uint32_t line_nr, // line number in sokol_gfx.h ++ const char* filename_or_null, // source filename, may be nullptr in release mode ++ void* user_data); ++ void* user_data; ++} sg_logger; ++ + typedef struct sg_desc { + uint32_t _start_canary; + int buffer_pool_size; +@@ -2257,6 +2984,10 @@ typedef struct sg_desc { + int uniform_buffer_size; + int staging_buffer_size; + int sampler_cache_size; ++ int max_commit_listeners; ++ bool disable_validation; // disable validation layer even in debug mode, useful for tests ++ sg_allocator allocator; ++ sg_logger logger; // optional log function override + sg_context_desc context; + uint32_t _end_canary; + } sg_desc; +@@ -2269,6 +3000,8 @@ SOKOL_GFX_API_DECL void sg_reset_state_cache(void); + SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks); + SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name); + SOKOL_GFX_API_DECL void sg_pop_debug_group(void); ++SOKOL_GFX_API_DECL bool sg_add_commit_listener(sg_commit_listener listener); ++SOKOL_GFX_API_DECL bool sg_remove_commit_listener(sg_commit_listener listener); + + /* resource creation, destruction and updating */ + SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc); +@@ -2285,6 +3018,7 @@ SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data); + SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data); + SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data); + SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf); ++SOKOL_GFX_API_DECL bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size); + + /* rendering functions */ + SOKOL_GFX_API_DECL void sg_begin_default_pass(const sg_pass_action* pass_action, int width, int height); +@@ -2319,6 +3053,12 @@ SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img); + SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd); + SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip); + SOKOL_GFX_API_DECL sg_pass_info sg_query_pass_info(sg_pass pass); ++/* get desc structs matching a specific resource (NOTE that not all creation attributes may be provided) */ ++SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf); ++SOKOL_GFX_API_DECL sg_image_desc sg_query_image_desc(sg_image img); ++SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_desc(sg_shader shd); ++SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip); ++SOKOL_GFX_API_DECL sg_pass_desc sg_query_pass_desc(sg_pass pass); + /* get resource creation desc struct with their default values replaced */ + SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc); + SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc); +@@ -2332,26 +3072,26 @@ SOKOL_GFX_API_DECL sg_image sg_alloc_image(void); + SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void); + SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void); + SOKOL_GFX_API_DECL sg_pass sg_alloc_pass(void); +-SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf_id); +-SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img_id); +-SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd_id); +-SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip_id); +-SOKOL_GFX_API_DECL void sg_dealloc_pass(sg_pass pass_id); +-SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc); +-SOKOL_GFX_API_DECL void sg_init_image(sg_image img_id, const sg_image_desc* desc); +-SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc); +-SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc); +-SOKOL_GFX_API_DECL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc); +-SOKOL_GFX_API_DECL bool sg_uninit_buffer(sg_buffer buf_id); +-SOKOL_GFX_API_DECL bool sg_uninit_image(sg_image img_id); +-SOKOL_GFX_API_DECL bool sg_uninit_shader(sg_shader shd_id); +-SOKOL_GFX_API_DECL bool sg_uninit_pipeline(sg_pipeline pip_id); +-SOKOL_GFX_API_DECL bool sg_uninit_pass(sg_pass pass_id); +-SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf_id); +-SOKOL_GFX_API_DECL void sg_fail_image(sg_image img_id); +-SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd_id); +-SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip_id); +-SOKOL_GFX_API_DECL void sg_fail_pass(sg_pass pass_id); ++SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf); ++SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img); ++SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd); ++SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip); ++SOKOL_GFX_API_DECL void sg_dealloc_pass(sg_pass pass); ++SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc); ++SOKOL_GFX_API_DECL void sg_init_image(sg_image img, const sg_image_desc* desc); ++SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd, const sg_shader_desc* desc); ++SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc); ++SOKOL_GFX_API_DECL void sg_init_pass(sg_pass pass, const sg_pass_desc* desc); ++SOKOL_GFX_API_DECL void sg_uninit_buffer(sg_buffer buf); ++SOKOL_GFX_API_DECL void sg_uninit_image(sg_image img); ++SOKOL_GFX_API_DECL void sg_uninit_shader(sg_shader shd); ++SOKOL_GFX_API_DECL void sg_uninit_pipeline(sg_pipeline pip); ++SOKOL_GFX_API_DECL void sg_uninit_pass(sg_pass pass); ++SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf); ++SOKOL_GFX_API_DECL void sg_fail_image(sg_image img); ++SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd); ++SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip); ++SOKOL_GFX_API_DECL void sg_fail_pass(sg_pass pass); + + /* rendering contexts (optional) */ + SOKOL_GFX_API_DECL sg_context sg_setup_context(void); +@@ -2409,53 +3149,42 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ + #endif + #endif // SOKOL_GFX_INCLUDED + +-/*--- IMPLEMENTATION ---------------------------------------------------------*/ ++// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ ++// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ++// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ ++// ++// >>implementation + #ifdef SOKOL_GFX_IMPL + #define SOKOL_GFX_IMPL_INCLUDED (1) + + #if !(defined(SOKOL_GLCORE33)||defined(SOKOL_GLES2)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) + #error "Please select a backend with SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND" + #endif +-#include /* memset */ +-#include /* FLT_MAX */ ++#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) ++#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sg_desc.allocator to override memory allocation functions" ++#endif ++ ++#include // malloc, free ++#include // memset ++#include // FLT_MAX + + #ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL + #endif + #ifndef SOKOL_DEBUG + #ifndef NDEBUG +- #define SOKOL_DEBUG (1) ++ #define SOKOL_DEBUG + #endif + #endif + #ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) + #endif +-#ifndef SOKOL_VALIDATE_BEGIN +- #define SOKOL_VALIDATE_BEGIN() _sg_validate_begin() +-#endif +-#ifndef SOKOL_VALIDATE +- #define SOKOL_VALIDATE(cond, err) _sg_validate((cond), err) +-#endif +-#ifndef SOKOL_VALIDATE_END +- #define SOKOL_VALIDATE_END() _sg_validate_end() +-#endif + #ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) + #endif +-#ifndef SOKOL_MALLOC +- #include +- #define SOKOL_MALLOC(s) malloc(s) +- #define SOKOL_FREE(p) free(p) +-#endif +-#ifndef SOKOL_LOG +- #ifdef SOKOL_DEBUG +- #include +- #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } +- #else +- #define SOKOL_LOG(s) +- #endif +-#endif + + #ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) +@@ -2506,9 +3235,335 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #endif + +-#if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) ++#if defined(SOKOL_D3D11) ++ #ifndef D3D11_NO_HELPERS ++ #define D3D11_NO_HELPERS ++ #endif ++ #ifndef WIN32_LEAN_AND_MEAN ++ #define WIN32_LEAN_AND_MEAN ++ #endif ++ #ifndef NOMINMAX ++ #define NOMINMAX ++ #endif ++ #include ++ #include ++ #ifdef _MSC_VER ++ #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) ++ #pragma comment (lib, "WindowsApp") ++ #else ++ #pragma comment (lib, "kernel32") ++ #pragma comment (lib, "user32") ++ #pragma comment (lib, "dxgi") ++ #pragma comment (lib, "d3d11") ++ #endif ++ #endif ++#elif defined(SOKOL_METAL) ++ // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting ++ #if !defined(__cplusplus) ++ #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) ++ #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" ++ #endif ++ #endif ++ #include ++ #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE ++ #define _SG_TARGET_MACOS (1) ++ #else ++ #define _SG_TARGET_IOS (1) ++ #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR ++ #define _SG_TARGET_IOS_SIMULATOR (1) ++ #endif ++ #endif ++ #import ++#elif defined(SOKOL_WGPU) ++ #if defined(__EMSCRIPTEN__) ++ #include ++ #else ++ #include ++ #endif ++#elif defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + #define _SOKOL_ANY_GL (1) + ++ // include platform specific GL headers (or on Win32: use an embedded GL loader) ++ #if !defined(SOKOL_EXTERNAL_GL_LOADER) ++ #if defined(_WIN32) ++ #if defined(SOKOL_GLCORE33) && !defined(SOKOL_EXTERNAL_GL_LOADER) ++ #ifndef WIN32_LEAN_AND_MEAN ++ #define WIN32_LEAN_AND_MEAN ++ #endif ++ #ifndef NOMINMAX ++ #define NOMINMAX ++ #endif ++ #include ++ #define _SOKOL_USE_WIN32_GL_LOADER (1) ++ #pragma comment (lib, "kernel32") // GetProcAddress() ++ #endif ++ #elif defined(__APPLE__) ++ #include ++ #ifndef GL_SILENCE_DEPRECATION ++ #define GL_SILENCE_DEPRECATION ++ #endif ++ #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE ++ #include ++ #else ++ #include ++ #include ++ #endif ++ #elif defined(__EMSCRIPTEN__) || defined(__ANDROID__) ++ #if defined(SOKOL_GLES3) ++ #include ++ #elif defined(SOKOL_GLES2) ++ #ifndef GL_EXT_PROTOTYPES ++ #define GL_GLEXT_PROTOTYPES ++ #endif ++ #include ++ #include ++ #endif ++ #elif defined(__linux__) || defined(__unix__) ++ #define GL_GLEXT_PROTOTYPES ++ #include ++ #endif ++ #endif ++ ++ // optional GL loader definitions (only on Win32) ++ #if defined(_SOKOL_USE_WIN32_GL_LOADER) ++ #define __gl_h_ 1 ++ #define __gl32_h_ 1 ++ #define __gl31_h_ 1 ++ #define __GL_H__ 1 ++ #define __glext_h_ 1 ++ #define __GLEXT_H_ 1 ++ #define __gltypes_h_ 1 ++ #define __glcorearb_h_ 1 ++ #define __gl_glcorearb_h_ 1 ++ #define GL_APIENTRY APIENTRY ++ ++ typedef unsigned int GLenum; ++ typedef unsigned int GLuint; ++ typedef int GLsizei; ++ typedef char GLchar; ++ typedef ptrdiff_t GLintptr; ++ typedef ptrdiff_t GLsizeiptr; ++ typedef double GLclampd; ++ typedef unsigned short GLushort; ++ typedef unsigned char GLubyte; ++ typedef unsigned char GLboolean; ++ typedef uint64_t GLuint64; ++ typedef double GLdouble; ++ typedef unsigned short GLhalf; ++ typedef float GLclampf; ++ typedef unsigned int GLbitfield; ++ typedef signed char GLbyte; ++ typedef short GLshort; ++ typedef void GLvoid; ++ typedef int64_t GLint64; ++ typedef float GLfloat; ++ typedef struct __GLsync * GLsync; ++ typedef int GLint; ++ #define GL_INT_2_10_10_10_REV 0x8D9F ++ #define GL_R32F 0x822E ++ #define GL_PROGRAM_POINT_SIZE 0x8642 ++ #define GL_STENCIL_ATTACHMENT 0x8D20 ++ #define GL_DEPTH_ATTACHMENT 0x8D00 ++ #define GL_COLOR_ATTACHMENT2 0x8CE2 ++ #define GL_COLOR_ATTACHMENT0 0x8CE0 ++ #define GL_R16F 0x822D ++ #define GL_COLOR_ATTACHMENT22 0x8CF6 ++ #define GL_DRAW_FRAMEBUFFER 0x8CA9 ++ #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 ++ #define GL_NUM_EXTENSIONS 0x821D ++ #define GL_INFO_LOG_LENGTH 0x8B84 ++ #define GL_VERTEX_SHADER 0x8B31 ++ #define GL_INCR 0x1E02 ++ #define GL_DYNAMIC_DRAW 0x88E8 ++ #define GL_STATIC_DRAW 0x88E4 ++ #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 ++ #define GL_TEXTURE_CUBE_MAP 0x8513 ++ #define GL_FUNC_SUBTRACT 0x800A ++ #define GL_FUNC_REVERSE_SUBTRACT 0x800B ++ #define GL_CONSTANT_COLOR 0x8001 ++ #define GL_DECR_WRAP 0x8508 ++ #define GL_R8 0x8229 ++ #define GL_LINEAR_MIPMAP_LINEAR 0x2703 ++ #define GL_ELEMENT_ARRAY_BUFFER 0x8893 ++ #define GL_SHORT 0x1402 ++ #define GL_DEPTH_TEST 0x0B71 ++ #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 ++ #define GL_LINK_STATUS 0x8B82 ++ #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 ++ #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E ++ #define GL_RGBA16F 0x881A ++ #define GL_CONSTANT_ALPHA 0x8003 ++ #define GL_READ_FRAMEBUFFER 0x8CA8 ++ #define GL_TEXTURE0 0x84C0 ++ #define GL_TEXTURE_MIN_LOD 0x813A ++ #define GL_CLAMP_TO_EDGE 0x812F ++ #define GL_UNSIGNED_SHORT_5_6_5 0x8363 ++ #define GL_TEXTURE_WRAP_R 0x8072 ++ #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 ++ #define GL_NEAREST_MIPMAP_NEAREST 0x2700 ++ #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 ++ #define GL_SRC_ALPHA_SATURATE 0x0308 ++ #define GL_STREAM_DRAW 0x88E0 ++ #define GL_ONE 1 ++ #define GL_NEAREST_MIPMAP_LINEAR 0x2702 ++ #define GL_RGB10_A2 0x8059 ++ #define GL_RGBA8 0x8058 ++ #define GL_SRGB8_ALPHA8 0x8C43 ++ #define GL_COLOR_ATTACHMENT1 0x8CE1 ++ #define GL_RGBA4 0x8056 ++ #define GL_RGB8 0x8051 ++ #define GL_ARRAY_BUFFER 0x8892 ++ #define GL_STENCIL 0x1802 ++ #define GL_TEXTURE_2D 0x0DE1 ++ #define GL_DEPTH 0x1801 ++ #define GL_FRONT 0x0404 ++ #define GL_STENCIL_BUFFER_BIT 0x00000400 ++ #define GL_REPEAT 0x2901 ++ #define GL_RGBA 0x1908 ++ #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 ++ #define GL_DECR 0x1E03 ++ #define GL_FRAGMENT_SHADER 0x8B30 ++ #define GL_FLOAT 0x1406 ++ #define GL_TEXTURE_MAX_LOD 0x813B ++ #define GL_DEPTH_COMPONENT 0x1902 ++ #define GL_ONE_MINUS_DST_ALPHA 0x0305 ++ #define GL_COLOR 0x1800 ++ #define GL_TEXTURE_2D_ARRAY 0x8C1A ++ #define GL_TRIANGLES 0x0004 ++ #define GL_UNSIGNED_BYTE 0x1401 ++ #define GL_TEXTURE_MAG_FILTER 0x2800 ++ #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 ++ #define GL_NONE 0 ++ #define GL_SRC_COLOR 0x0300 ++ #define GL_BYTE 0x1400 ++ #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A ++ #define GL_LINE_STRIP 0x0003 ++ #define GL_TEXTURE_3D 0x806F ++ #define GL_CW 0x0900 ++ #define GL_LINEAR 0x2601 ++ #define GL_RENDERBUFFER 0x8D41 ++ #define GL_GEQUAL 0x0206 ++ #define GL_COLOR_BUFFER_BIT 0x00004000 ++ #define GL_RGBA32F 0x8814 ++ #define GL_BLEND 0x0BE2 ++ #define GL_ONE_MINUS_SRC_ALPHA 0x0303 ++ #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 ++ #define GL_TEXTURE_WRAP_T 0x2803 ++ #define GL_TEXTURE_WRAP_S 0x2802 ++ #define GL_TEXTURE_MIN_FILTER 0x2801 ++ #define GL_LINEAR_MIPMAP_NEAREST 0x2701 ++ #define GL_EXTENSIONS 0x1F03 ++ #define GL_NO_ERROR 0 ++ #define GL_REPLACE 0x1E01 ++ #define GL_KEEP 0x1E00 ++ #define GL_CCW 0x0901 ++ #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 ++ #define GL_RGB 0x1907 ++ #define GL_TRIANGLE_STRIP 0x0005 ++ #define GL_FALSE 0 ++ #define GL_ZERO 0 ++ #define GL_CULL_FACE 0x0B44 ++ #define GL_INVERT 0x150A ++ #define GL_INT 0x1404 ++ #define GL_UNSIGNED_INT 0x1405 ++ #define GL_UNSIGNED_SHORT 0x1403 ++ #define GL_NEAREST 0x2600 ++ #define GL_SCISSOR_TEST 0x0C11 ++ #define GL_LEQUAL 0x0203 ++ #define GL_STENCIL_TEST 0x0B90 ++ #define GL_DITHER 0x0BD0 ++ #define GL_DEPTH_COMPONENT16 0x81A5 ++ #define GL_EQUAL 0x0202 ++ #define GL_FRAMEBUFFER 0x8D40 ++ #define GL_RGB5 0x8050 ++ #define GL_LINES 0x0001 ++ #define GL_DEPTH_BUFFER_BIT 0x00000100 ++ #define GL_SRC_ALPHA 0x0302 ++ #define GL_INCR_WRAP 0x8507 ++ #define GL_LESS 0x0201 ++ #define GL_MULTISAMPLE 0x809D ++ #define GL_FRAMEBUFFER_BINDING 0x8CA6 ++ #define GL_BACK 0x0405 ++ #define GL_ALWAYS 0x0207 ++ #define GL_FUNC_ADD 0x8006 ++ #define GL_ONE_MINUS_DST_COLOR 0x0307 ++ #define GL_NOTEQUAL 0x0205 ++ #define GL_DST_COLOR 0x0306 ++ #define GL_COMPILE_STATUS 0x8B81 ++ #define GL_RED 0x1903 ++ #define GL_COLOR_ATTACHMENT3 0x8CE3 ++ #define GL_DST_ALPHA 0x0304 ++ #define GL_RGB5_A1 0x8057 ++ #define GL_GREATER 0x0204 ++ #define GL_POLYGON_OFFSET_FILL 0x8037 ++ #define GL_TRUE 1 ++ #define GL_NEVER 0x0200 ++ #define GL_POINTS 0x0000 ++ #define GL_ONE_MINUS_SRC_COLOR 0x0301 ++ #define GL_MIRRORED_REPEAT 0x8370 ++ #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D ++ #define GL_R11F_G11F_B10F 0x8C3A ++ #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B ++ #define GL_RGB9_E5 0x8C3D ++ #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E ++ #define GL_RGBA32UI 0x8D70 ++ #define GL_RGB32UI 0x8D71 ++ #define GL_RGBA16UI 0x8D76 ++ #define GL_RGB16UI 0x8D77 ++ #define GL_RGBA8UI 0x8D7C ++ #define GL_RGB8UI 0x8D7D ++ #define GL_RGBA32I 0x8D82 ++ #define GL_RGB32I 0x8D83 ++ #define GL_RGBA16I 0x8D88 ++ #define GL_RGB16I 0x8D89 ++ #define GL_RGBA8I 0x8D8E ++ #define GL_RGB8I 0x8D8F ++ #define GL_RED_INTEGER 0x8D94 ++ #define GL_RG 0x8227 ++ #define GL_RG_INTEGER 0x8228 ++ #define GL_R8 0x8229 ++ #define GL_R16 0x822A ++ #define GL_RG8 0x822B ++ #define GL_RG16 0x822C ++ #define GL_R16F 0x822D ++ #define GL_R32F 0x822E ++ #define GL_RG16F 0x822F ++ #define GL_RG32F 0x8230 ++ #define GL_R8I 0x8231 ++ #define GL_R8UI 0x8232 ++ #define GL_R16I 0x8233 ++ #define GL_R16UI 0x8234 ++ #define GL_R32I 0x8235 ++ #define GL_R32UI 0x8236 ++ #define GL_RG8I 0x8237 ++ #define GL_RG8UI 0x8238 ++ #define GL_RG16I 0x8239 ++ #define GL_RG16UI 0x823A ++ #define GL_RG32I 0x823B ++ #define GL_RG32UI 0x823C ++ #define GL_RGBA_INTEGER 0x8D99 ++ #define GL_R8_SNORM 0x8F94 ++ #define GL_RG8_SNORM 0x8F95 ++ #define GL_RGB8_SNORM 0x8F96 ++ #define GL_RGBA8_SNORM 0x8F97 ++ #define GL_R16_SNORM 0x8F98 ++ #define GL_RG16_SNORM 0x8F99 ++ #define GL_RGB16_SNORM 0x8F9A ++ #define GL_RGBA16_SNORM 0x8F9B ++ #define GL_RGBA16 0x805B ++ #define GL_MAX_TEXTURE_SIZE 0x0D33 ++ #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C ++ #define GL_MAX_3D_TEXTURE_SIZE 0x8073 ++ #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF ++ #define GL_MAX_VERTEX_ATTRIBS 0x8869 ++ #define GL_CLAMP_TO_BORDER 0x812D ++ #define GL_TEXTURE_BORDER_COLOR 0x1004 ++ #define GL_CURRENT_PROGRAM 0x8B8D ++ #define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB ++ #define GL_UNPACK_ALIGNMENT 0x0CF5 ++ #define GL_FRAMEBUFFER_SRGB 0x8DB9 ++ #endif ++ + #ifndef GL_UNSIGNED_INT_2_10_10_10_REV + #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 + #endif +@@ -2595,77 +3650,35 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ + #endif + + #ifdef SOKOL_GLES2 +- # ifdef GL_ANGLE_instanced_arrays +- # define SOKOL_INSTANCING_ENABLED +- # define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedANGLE(mode, first, count, instancecount) +- # define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedANGLE(mode, count, type, indices, instancecount) +- # define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorANGLE(index, divisor) +- # elif defined(GL_EXT_draw_instanced) && defined(GL_EXT_instanced_arrays) +- # define SOKOL_INSTANCING_ENABLED +- # define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedEXT(mode, first, count, instancecount) +- # define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedEXT(mode, count, type, indices, instancecount) +- # define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorEXT(index, divisor) +- # else +- # define SOKOL_GLES2_INSTANCING_ERROR "Select GL_ANGLE_instanced_arrays or (GL_EXT_draw_instanced & GL_EXT_instanced_arrays) to enable instancing in GLES2" +- # define glDrawArraysInstanced(mode, first, count, instancecount) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) +- # define glDrawElementsInstanced(mode, count, type, indices, instancecount) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) +- # define glVertexAttribDivisor(index, divisor) SOKOL_ASSERT(0 && SOKOL_GLES2_INSTANCING_ERROR) +- # endif +- #else +- # define SOKOL_INSTANCING_ENABLED +- #endif +- #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } +- +-#elif defined(SOKOL_D3D11) +- #ifndef D3D11_NO_HELPERS +- #define D3D11_NO_HELPERS +- #endif +- #ifndef WIN32_LEAN_AND_MEAN +- #define WIN32_LEAN_AND_MEAN +- #endif +- #ifndef NOMINMAX +- #define NOMINMAX +- #endif +- #include +- #include +- #ifdef _MSC_VER +- #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +- #pragma comment (lib, "WindowsApp") +- #else +- #pragma comment (lib, "kernel32") +- #pragma comment (lib, "user32") +- #pragma comment (lib, "dxgi") +- #pragma comment (lib, "d3d11") +- #pragma comment (lib, "dxguid") +- #endif +- #endif +-#elif defined(SOKOL_METAL) +- // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting +- #if !defined(__cplusplus) +- #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) +- #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" +- #endif +- #endif +- #include +- #import +- #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE +- #define _SG_TARGET_MACOS (1) +- #else +- #define _SG_TARGET_IOS (1) +- #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR +- #define _SG_TARGET_IOS_SIMULATOR (1) ++ #ifdef GL_ANGLE_instanced_arrays ++ #define _SOKOL_GL_INSTANCING_ENABLED ++ #define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedANGLE(mode, first, count, instancecount) ++ #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedANGLE(mode, count, type, indices, instancecount) ++ #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorANGLE(index, divisor) ++ #elif defined(GL_EXT_draw_instanced) && defined(GL_EXT_instanced_arrays) ++ #define _SOKOL_GL_INSTANCING_ENABLED ++ #define glDrawArraysInstanced(mode, first, count, instancecount) glDrawArraysInstancedEXT(mode, first, count, instancecount) ++ #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedEXT(mode, count, type, indices, instancecount) ++ #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorEXT(index, divisor) ++ #else ++ #define _SOKOL_GLES2_INSTANCING_ERROR "Select GL_ANGLE_instanced_arrays or (GL_EXT_draw_instanced & GL_EXT_instanced_arrays) to enable instancing in GLES2" ++ #define glDrawArraysInstanced(mode, first, count, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) ++ #define glDrawElementsInstanced(mode, count, type, indices, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) ++ #define glVertexAttribDivisor(index, divisor) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) + #endif +- #endif +-#elif defined(SOKOL_WGPU) +- #if defined(__EMSCRIPTEN__) +- #include + #else +- #include ++ #define _SOKOL_GL_INSTANCING_ENABLED + #endif ++ #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } + #endif + +-/*=== COMMON BACKEND STUFF ===================================================*/ +- ++// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ ██ ██████ ██ ██ ██ ██ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ ██ ██ ██ ██████ ██████ ██ ███████ ++// ++// >>structs + /* resource pool slots */ + typedef struct { + uint32_t id; +@@ -2688,6 +3701,7 @@ enum { + _SG_DEFAULT_SAMPLER_CACHE_CAPACITY = 64, + _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, + _SG_DEFAULT_STAGING_SIZE = 8 * 1024 * 1024, ++ _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024, + }; + + /* fixed-size string */ +@@ -2698,36 +3712,62 @@ typedef struct { + /* helper macros */ + #define _sg_def(val, def) (((val) == 0) ? (def) : (val)) + #define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) +-#define _sg_min(a,b) ((ab)?a:b) +-#define _sg_clamp(v,v0,v1) ((vv1)?(v1):(v))) +-#define _sg_fequal(val,cmp,delta) (((val-cmp)> -delta)&&((val-cmp)(b))?(a):(b)) ++#define _sg_clamp(v,v0,v1) (((v)<(v0))?(v0):(((v)>(v1))?(v1):(v))) ++#define _sg_fequal(val,cmp,delta) ((((val)-(cmp))> -(delta))&&(((val)-(cmp))<(delta))) ++ ++_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size); ++_SOKOL_PRIVATE void _sg_free(void* ptr); ++_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size); + + typedef struct { +- int size; +- int append_pos; +- bool append_overflow; +- sg_buffer_type type; +- sg_usage usage; ++ sg_filter min_filter; ++ sg_filter mag_filter; ++ sg_wrap wrap_u; ++ sg_wrap wrap_v; ++ sg_wrap wrap_w; ++ sg_border_color border_color; ++ uint32_t max_anisotropy; ++ int min_lod; /* orig min/max_lod is float, this is int(min/max_lod*1000.0) */ ++ int max_lod; ++ uintptr_t sampler_handle; ++} _sg_sampler_cache_item_t; ++ ++typedef struct { ++ int capacity; ++ int num_items; ++ _sg_sampler_cache_item_t* items; ++} _sg_sampler_cache_t; ++ ++typedef struct { ++ int size; ++ int append_pos; ++ bool append_overflow; + uint32_t update_frame_index; + uint32_t append_frame_index; + int num_slots; + int active_slot; ++ sg_buffer_type type; ++ sg_usage usage; + } _sg_buffer_common_t; + + _SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { + cmn->size = (int)desc->size; + cmn->append_pos = 0; + cmn->append_overflow = false; +- cmn->type = desc->type; +- cmn->usage = desc->usage; + cmn->update_frame_index = 0; + cmn->append_frame_index = 0; +- cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; ++ cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; ++ cmn->type = desc->type; ++ cmn->usage = desc->usage; + } + + typedef struct { ++ uint32_t upd_frame_index; ++ int num_slots; ++ int active_slot; + sg_image_type type; + bool render_target; + int width; +@@ -2744,12 +3784,14 @@ typedef struct { + sg_wrap wrap_w; + sg_border_color border_color; + uint32_t max_anisotropy; +- uint32_t upd_frame_index; +- int num_slots; +- int active_slot; ++ float min_lod; ++ float max_lod; + } _sg_image_common_t; + + _SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) { ++ cmn->upd_frame_index = 0; ++ cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; ++ cmn->active_slot = 0; + cmn->type = desc->type; + cmn->render_target = desc->render_target; + cmn->width = desc->width; +@@ -2766,14 +3808,13 @@ _SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_imag + cmn->wrap_w = desc->wrap_w; + cmn->border_color = desc->border_color; + cmn->max_anisotropy = desc->max_anisotropy; +- cmn->upd_frame_index = 0; +- cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; +- cmn->active_slot = 0; ++ cmn->min_lod = desc->min_lod; ++ cmn->max_lod = desc->max_lod; + } + + typedef struct { + size_t size; +-} _sg_uniform_block_t; ++} _sg_shader_uniform_block_t; + + typedef struct { + sg_image_type image_type; +@@ -2783,7 +3824,7 @@ typedef struct { + typedef struct { + int num_uniform_blocks; + int num_images; +- _sg_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; ++ _sg_shader_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; + _sg_shader_image_t images[SG_MAX_SHADERSTAGE_IMAGES]; + } _sg_shader_stage_t; + +@@ -2818,36 +3859,44 @@ _SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_sh + } + + typedef struct { ++ bool vertex_layout_valid[SG_MAX_SHADERSTAGE_BUFFERS]; ++ bool use_instanced_draw; + sg_shader shader_id; ++ sg_layout_desc layout; ++ sg_depth_state depth; ++ sg_stencil_state stencil; ++ int color_count; ++ sg_color_state colors[SG_MAX_COLOR_ATTACHMENTS]; ++ sg_primitive_type primitive_type; + sg_index_type index_type; +- bool vertex_layout_valid[SG_MAX_SHADERSTAGE_BUFFERS]; +- int color_attachment_count; +- sg_pixel_format color_formats[SG_MAX_COLOR_ATTACHMENTS]; +- sg_pixel_format depth_format; ++ sg_cull_mode cull_mode; ++ sg_face_winding face_winding; + int sample_count; +- float depth_bias; +- float depth_bias_slope_scale; +- float depth_bias_clamp; + sg_color blend_color; ++ bool alpha_to_coverage_enabled; + } _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); +- cmn->shader_id = desc->shader; +- cmn->index_type = desc->index_type; ++ SOKOL_ASSERT((desc->color_count >= 1) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS)); + for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { + cmn->vertex_layout_valid[i] = false; + } +- cmn->color_attachment_count = desc->color_count; +- for (int i = 0; i < cmn->color_attachment_count; i++) { +- cmn->color_formats[i] = desc->colors[i].pixel_format; +- } +- cmn->depth_format = desc->depth.pixel_format; ++ cmn->use_instanced_draw = false; ++ cmn->shader_id = desc->shader; ++ cmn->layout = desc->layout; ++ cmn->depth = desc->depth; ++ cmn->stencil = desc->stencil; ++ cmn->color_count = desc->color_count; ++ for (int i = 0; i < desc->color_count; i++) { ++ cmn->colors[i] = desc->colors[i]; ++ } ++ cmn->primitive_type = desc->primitive_type; ++ cmn->index_type = desc->index_type; ++ cmn->cull_mode = desc->cull_mode; ++ cmn->face_winding = desc->face_winding; + cmn->sample_count = desc->sample_count; +- cmn->depth_bias = desc->depth.bias; +- cmn->depth_bias_slope_scale = desc->depth.bias_slope_scale; +- cmn->depth_bias_clamp = desc->depth.bias_clamp; + cmn->blend_color = desc->blend_color; ++ cmn->alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; + } + + typedef struct { +@@ -2884,107 +3933,6 @@ _SOKOL_PRIVATE void _sg_pass_common_init(_sg_pass_common_t* cmn, const sg_pass_d + } + } + +-/*=== GENERIC SAMPLER CACHE ==================================================*/ +- +-/* +- this is used by the Metal and WGPU backends to reduce the +- number of sampler state objects created through the backend API +-*/ +-typedef struct { +- sg_filter min_filter; +- sg_filter mag_filter; +- sg_wrap wrap_u; +- sg_wrap wrap_v; +- sg_wrap wrap_w; +- sg_border_color border_color; +- uint32_t max_anisotropy; +- int min_lod; /* orig min/max_lod is float, this is int(min/max_lod*1000.0) */ +- int max_lod; +- uintptr_t sampler_handle; +-} _sg_sampler_cache_item_t; +- +-typedef struct { +- int capacity; +- int num_items; +- _sg_sampler_cache_item_t* items; +-} _sg_sampler_cache_t; +- +-_SOKOL_PRIVATE void _sg_smpcache_init(_sg_sampler_cache_t* cache, int capacity) { +- SOKOL_ASSERT(cache && (capacity > 0)); +- memset(cache, 0, sizeof(_sg_sampler_cache_t)); +- cache->capacity = capacity; +- const size_t size = (size_t)cache->capacity * sizeof(_sg_sampler_cache_item_t); +- cache->items = (_sg_sampler_cache_item_t*) SOKOL_MALLOC(size); +- SOKOL_ASSERT(cache->items); +- memset(cache->items, 0, size); +-} +- +-_SOKOL_PRIVATE void _sg_smpcache_discard(_sg_sampler_cache_t* cache) { +- SOKOL_ASSERT(cache && cache->items); +- SOKOL_FREE(cache->items); +- cache->items = 0; +- cache->num_items = 0; +- cache->capacity = 0; +-} +- +-_SOKOL_PRIVATE int _sg_smpcache_minlod_int(float min_lod) { +- return (int) (min_lod * 1000.0f); +-} +- +-_SOKOL_PRIVATE int _sg_smpcache_maxlod_int(float max_lod) { +- return (int) (_sg_clamp(max_lod, 0.0f, 1000.0f) * 1000.0f); +-} +- +-_SOKOL_PRIVATE int _sg_smpcache_find_item(const _sg_sampler_cache_t* cache, const sg_image_desc* img_desc) { +- /* return matching sampler cache item index or -1 */ +- SOKOL_ASSERT(cache && cache->items); +- SOKOL_ASSERT(img_desc); +- const int min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); +- const int max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); +- for (int i = 0; i < cache->num_items; i++) { +- const _sg_sampler_cache_item_t* item = &cache->items[i]; +- if ((img_desc->min_filter == item->min_filter) && +- (img_desc->mag_filter == item->mag_filter) && +- (img_desc->wrap_u == item->wrap_u) && +- (img_desc->wrap_v == item->wrap_v) && +- (img_desc->wrap_w == item->wrap_w) && +- (img_desc->max_anisotropy == item->max_anisotropy) && +- (img_desc->border_color == item->border_color) && +- (min_lod == item->min_lod) && +- (max_lod == item->max_lod)) +- { +- return i; +- } +- } +- /* fallthrough: no matching cache item found */ +- return -1; +-} +- +-_SOKOL_PRIVATE void _sg_smpcache_add_item(_sg_sampler_cache_t* cache, const sg_image_desc* img_desc, uintptr_t sampler_handle) { +- SOKOL_ASSERT(cache && cache->items); +- SOKOL_ASSERT(img_desc); +- SOKOL_ASSERT(cache->num_items < cache->capacity); +- const int item_index = cache->num_items++; +- _sg_sampler_cache_item_t* item = &cache->items[item_index]; +- item->min_filter = img_desc->min_filter; +- item->mag_filter = img_desc->mag_filter; +- item->wrap_u = img_desc->wrap_u; +- item->wrap_v = img_desc->wrap_v; +- item->wrap_w = img_desc->wrap_w; +- item->border_color = img_desc->border_color; +- item->max_anisotropy = img_desc->max_anisotropy; +- item->min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); +- item->max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); +- item->sampler_handle = sampler_handle; +-} +- +-_SOKOL_PRIVATE uintptr_t _sg_smpcache_sampler(_sg_sampler_cache_t* cache, int item_index) { +- SOKOL_ASSERT(cache && cache->items); +- SOKOL_ASSERT(item_index < cache->num_items); +- return cache->items[item_index].sampler_handle; +-} +- +-/*=== DUMMY BACKEND DECLARATIONS =============================================*/ + #if defined(SOKOL_DUMMY_BACKEND) + typedef struct { + _sg_slot_t slot; +@@ -3031,7 +3979,6 @@ typedef struct { + } _sg_dummy_context_t; + typedef _sg_dummy_context_t _sg_context_t; + +-/*== GL BACKEND DECLARATIONS =================================================*/ + #elif defined(_SOKOL_ANY_GL) + typedef struct { + _sg_slot_t slot; +@@ -3059,7 +4006,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; + +@@ -3157,6 +4104,8 @@ typedef struct { + GLuint texture; + } _sg_gl_texture_bind_slot; + ++#define _SG_GL_IMAGE_CACHE_SIZE (SG_MAX_SHADERSTAGE_IMAGES * SG_NUM_SHADER_STAGES) ++ + typedef struct { + sg_depth_state depth; + sg_stencil_state stencil; +@@ -3174,7 +4123,7 @@ typedef struct { + GLuint stored_vertex_buffer; + GLuint stored_index_buffer; + GLuint prog; +- _sg_gl_texture_bind_slot textures[SG_MAX_SHADERSTAGE_IMAGES]; ++ _sg_gl_texture_bind_slot textures[_SG_GL_IMAGE_CACHE_SIZE]; + _sg_gl_texture_bind_slot stored_texture; + int cur_ib_offset; + GLenum cur_primitive_type; +@@ -3196,10 +4145,11 @@ typedef struct { + _sg_gl_state_cache_t cache; + bool ext_anisotropic; + GLint max_anisotropy; +- GLint max_combined_texture_image_units; ++ #if _SOKOL_USE_WIN32_GL_LOADER ++ HINSTANCE opengl32_dll; ++ #endif + } _sg_gl_backend_t; + +-/*== D3D11 BACKEND DECLARATIONS ==============================================*/ + #elif defined(SOKOL_D3D11) + + typedef struct { +@@ -3303,6 +4253,7 @@ typedef struct { + void* user_data; + bool in_pass; + bool use_indexed_draw; ++ bool use_instanced_draw; + int cur_width; + int cur_height; + int num_rtvs; +@@ -3316,19 +4267,10 @@ 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; + +-/*=== METAL BACKEND DECLARATIONS =============================================*/ + #elif defined(SOKOL_METAL) + + #if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR) +@@ -3464,11 +4406,11 @@ typedef struct { + id device; + id cmd_queue; + id cmd_buffer; ++ id present_cmd_buffer; + id cmd_encoder; + id uniform_buffers[SG_NUM_INFLIGHT_FRAMES]; + } _sg_mtl_backend_t; + +-/*=== WGPU BACKEND DECLARATIONS ==============================================*/ + #elif defined(SOKOL_WGPU) + + #define _SG_WGPU_STAGING_ALIGN (256) +@@ -3600,7 +4542,8 @@ typedef struct { + } _sg_wgpu_backend_t; + #endif + +-/*=== RESOURCE POOL DECLARATIONS =============================================*/ ++// POOL STRUCTS ++ + + /* this *MUST* remain 0 */ + #define _SG_INVALID_SLOT_INDEX (0) +@@ -3627,131 +4570,11 @@ typedef struct { + _sg_context_t* contexts; + } _sg_pools_t; + +-/*=== VALIDATION LAYER DECLARATIONS ==========================================*/ +-typedef enum { +- /* special case 'validation was successful' */ +- _SG_VALIDATE_SUCCESS, +- +- /* buffer creation */ +- _SG_VALIDATE_BUFFERDESC_CANARY, +- _SG_VALIDATE_BUFFERDESC_SIZE, +- _SG_VALIDATE_BUFFERDESC_DATA, +- _SG_VALIDATE_BUFFERDESC_DATA_SIZE, +- _SG_VALIDATE_BUFFERDESC_NO_DATA, +- +- /* image creation */ +- _SG_VALIDATE_IMAGEDESC_CANARY, +- _SG_VALIDATE_IMAGEDESC_WIDTH, +- _SG_VALIDATE_IMAGEDESC_HEIGHT, +- _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT, +- _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, +- _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, +- _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, +- _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE, +- _SG_VALIDATE_IMAGEDESC_RT_NO_DATA, +- _SG_VALIDATE_IMAGEDESC_DATA, +- _SG_VALIDATE_IMAGEDESC_NO_DATA, +- +- /* shader creation */ +- _SG_VALIDATE_SHADERDESC_CANARY, +- _SG_VALIDATE_SHADERDESC_SOURCE, +- _SG_VALIDATE_SHADERDESC_BYTECODE, +- _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, +- _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, +- _SG_VALIDATE_SHADERDESC_NO_CONT_UBS, +- _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS, +- _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, +- _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS, +- _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME, +- _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, +- _SG_VALIDATE_SHADERDESC_IMG_NAME, +- _SG_VALIDATE_SHADERDESC_ATTR_NAMES, +- _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS, +- _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, +- +- /* pipeline creation */ +- _SG_VALIDATE_PIPELINEDESC_CANARY, +- _SG_VALIDATE_PIPELINEDESC_SHADER, +- _SG_VALIDATE_PIPELINEDESC_NO_ATTRS, +- _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, +- _SG_VALIDATE_PIPELINEDESC_ATTR_NAME, +- _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, +- +- /* pass creation */ +- _SG_VALIDATE_PASSDESC_CANARY, +- _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS, +- _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS, +- _SG_VALIDATE_PASSDESC_IMAGE, +- _SG_VALIDATE_PASSDESC_MIPLEVEL, +- _SG_VALIDATE_PASSDESC_FACE, +- _SG_VALIDATE_PASSDESC_LAYER, +- _SG_VALIDATE_PASSDESC_SLICE, +- _SG_VALIDATE_PASSDESC_IMAGE_NO_RT, +- _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT, +- _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT, +- _SG_VALIDATE_PASSDESC_IMAGE_SIZES, +- _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS, +- +- /* sg_begin_pass validation */ +- _SG_VALIDATE_BEGINPASS_PASS, +- _SG_VALIDATE_BEGINPASS_IMAGE, +- +- /* sg_apply_pipeline validation */ +- _SG_VALIDATE_APIP_PIPELINE_VALID_ID, +- _SG_VALIDATE_APIP_PIPELINE_EXISTS, +- _SG_VALIDATE_APIP_PIPELINE_VALID, +- _SG_VALIDATE_APIP_SHADER_EXISTS, +- _SG_VALIDATE_APIP_SHADER_VALID, +- _SG_VALIDATE_APIP_ATT_COUNT, +- _SG_VALIDATE_APIP_COLOR_FORMAT, +- _SG_VALIDATE_APIP_DEPTH_FORMAT, +- _SG_VALIDATE_APIP_SAMPLE_COUNT, +- +- /* sg_apply_bindings validation */ +- _SG_VALIDATE_ABND_PIPELINE, +- _SG_VALIDATE_ABND_PIPELINE_EXISTS, +- _SG_VALIDATE_ABND_PIPELINE_VALID, +- _SG_VALIDATE_ABND_VBS, +- _SG_VALIDATE_ABND_VB_EXISTS, +- _SG_VALIDATE_ABND_VB_TYPE, +- _SG_VALIDATE_ABND_VB_OVERFLOW, +- _SG_VALIDATE_ABND_NO_IB, +- _SG_VALIDATE_ABND_IB, +- _SG_VALIDATE_ABND_IB_EXISTS, +- _SG_VALIDATE_ABND_IB_TYPE, +- _SG_VALIDATE_ABND_IB_OVERFLOW, +- _SG_VALIDATE_ABND_VS_IMGS, +- _SG_VALIDATE_ABND_VS_IMG_EXISTS, +- _SG_VALIDATE_ABND_VS_IMG_TYPES, +- _SG_VALIDATE_ABND_FS_IMGS, +- _SG_VALIDATE_ABND_FS_IMG_EXISTS, +- _SG_VALIDATE_ABND_FS_IMG_TYPES, +- +- /* sg_apply_uniforms validation */ +- _SG_VALIDATE_AUB_NO_PIPELINE, +- _SG_VALIDATE_AUB_NO_UB_AT_SLOT, +- _SG_VALIDATE_AUB_SIZE, +- +- /* sg_update_buffer validation */ +- _SG_VALIDATE_UPDATEBUF_USAGE, +- _SG_VALIDATE_UPDATEBUF_SIZE, +- _SG_VALIDATE_UPDATEBUF_ONCE, +- _SG_VALIDATE_UPDATEBUF_APPEND, +- +- /* sg_append_buffer validation */ +- _SG_VALIDATE_APPENDBUF_USAGE, +- _SG_VALIDATE_APPENDBUF_SIZE, +- _SG_VALIDATE_APPENDBUF_UPDATE, +- +- /* sg_update_image validation */ +- _SG_VALIDATE_UPDIMG_USAGE, +- _SG_VALIDATE_UPDIMG_NOTENOUGHDATA, +- _SG_VALIDATE_UPDIMG_SIZE, +- _SG_VALIDATE_UPDIMG_COMPRESSED, +- _SG_VALIDATE_UPDIMG_ONCE +-} _sg_validate_error_t; +- +-/*=== GENERIC BACKEND STATE ==================================================*/ ++typedef struct { ++ int num; // number of allocated commit listener items ++ int upper; // the current upper index (no valid items past this point) ++ sg_commit_listener* items; ++} _sg_commit_listeners_t; + + typedef struct { + bool valid; +@@ -3764,7 +4587,7 @@ typedef struct { + bool bindings_valid; + bool next_draw_valid; + #if defined(SOKOL_DEBUG) +- _sg_validate_error_t validate_error; ++ sg_log_item validate_error; + #endif + _sg_pools_t pools; + sg_backend backend; +@@ -3783,76 +4606,351 @@ typedef struct { + #if defined(SOKOL_TRACE_HOOKS) + sg_trace_hooks hooks; + #endif ++ _sg_commit_listeners_t commit_listeners; + } _sg_state_t; + static _sg_state_t _sg; + +-/*-- helper functions --------------------------------------------------------*/ ++// ███████ █████ ███ ███ ██████ ██ ███████ ██████ ██████ █████ ██████ ██ ██ ███████ ++// ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ ███████ ██ ████ ██ ██████ ██ █████ ██████ ██ ███████ ██ ███████ █████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ ██ ██ ██ ██ ██ ███████ ███████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ++// ++// >>sampler cache ++/* ++ this is used by the Metal and WGPU backends to reduce the ++ number of sampler state objects created through the backend API ++*/ ++_SOKOL_PRIVATE void _sg_smpcache_init(_sg_sampler_cache_t* cache, int capacity) { ++ SOKOL_ASSERT(cache && (capacity > 0)); ++ _sg_clear(cache, sizeof(_sg_sampler_cache_t)); ++ cache->capacity = capacity; ++ const size_t size = (size_t)cache->capacity * sizeof(_sg_sampler_cache_item_t); ++ cache->items = (_sg_sampler_cache_item_t*) _sg_malloc_clear(size); ++} + +-_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { +- return 0 == str->buf[0]; ++_SOKOL_PRIVATE void _sg_smpcache_discard(_sg_sampler_cache_t* cache) { ++ SOKOL_ASSERT(cache && cache->items); ++ _sg_free(cache->items); ++ cache->items = 0; ++ cache->num_items = 0; ++ cache->capacity = 0; + } + +-_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { +- return &str->buf[0]; ++_SOKOL_PRIVATE int _sg_smpcache_minlod_int(float min_lod) { ++ return (int) (min_lod * 1000.0f); + } + +-_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { +- SOKOL_ASSERT(dst); +- if (src) { +- #if defined(_MSC_VER) +- strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); +- #else +- strncpy(dst->buf, src, _SG_STRING_SIZE); +- #endif +- dst->buf[_SG_STRING_SIZE-1] = 0; +- } +- else { +- memset(dst->buf, 0, _SG_STRING_SIZE); +- } ++_SOKOL_PRIVATE int _sg_smpcache_maxlod_int(float max_lod) { ++ return (int) (_sg_clamp(max_lod, 0.0f, 1000.0f) * 1000.0f); + } + +-/* return byte size of a vertex format */ +-_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { +- switch (fmt) { +- case SG_VERTEXFORMAT_FLOAT: return 4; +- case SG_VERTEXFORMAT_FLOAT2: return 8; +- case SG_VERTEXFORMAT_FLOAT3: return 12; +- case SG_VERTEXFORMAT_FLOAT4: return 16; +- case SG_VERTEXFORMAT_BYTE4: return 4; +- case SG_VERTEXFORMAT_BYTE4N: return 4; +- case SG_VERTEXFORMAT_UBYTE4: return 4; +- case SG_VERTEXFORMAT_UBYTE4N: return 4; +- case SG_VERTEXFORMAT_SHORT2: return 4; +- case SG_VERTEXFORMAT_SHORT2N: return 4; +- case SG_VERTEXFORMAT_USHORT2N: return 4; +- case SG_VERTEXFORMAT_SHORT4: return 8; +- case SG_VERTEXFORMAT_SHORT4N: return 8; +- case SG_VERTEXFORMAT_USHORT4N: return 8; +- case SG_VERTEXFORMAT_UINT10_N2: return 4; +- case SG_VERTEXFORMAT_INVALID: return 0; +- default: +- SOKOL_UNREACHABLE; +- return -1; ++_SOKOL_PRIVATE int _sg_smpcache_find_item(const _sg_sampler_cache_t* cache, const sg_image_desc* img_desc) { ++ /* return matching sampler cache item index or -1 */ ++ SOKOL_ASSERT(cache && cache->items); ++ SOKOL_ASSERT(img_desc); ++ const int min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); ++ const int max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); ++ for (int i = 0; i < cache->num_items; i++) { ++ const _sg_sampler_cache_item_t* item = &cache->items[i]; ++ if ((img_desc->min_filter == item->min_filter) && ++ (img_desc->mag_filter == item->mag_filter) && ++ (img_desc->wrap_u == item->wrap_u) && ++ (img_desc->wrap_v == item->wrap_v) && ++ (img_desc->wrap_w == item->wrap_w) && ++ (img_desc->max_anisotropy == item->max_anisotropy) && ++ (img_desc->border_color == item->border_color) && ++ (min_lod == item->min_lod) && ++ (max_lod == item->max_lod)) ++ { ++ return i; ++ } + } ++ /* fallthrough: no matching cache item found */ ++ return -1; + } + +-/* return the byte size of a shader uniform */ +-_SOKOL_PRIVATE int _sg_uniform_size(sg_uniform_type type, int count) { +- switch (type) { +- case SG_UNIFORMTYPE_INVALID: return 0; +- case SG_UNIFORMTYPE_FLOAT: return 4 * count; +- case SG_UNIFORMTYPE_FLOAT2: return 8 * count; +- case SG_UNIFORMTYPE_FLOAT3: return 12 * count; /* FIXME: std140??? */ +- case SG_UNIFORMTYPE_FLOAT4: return 16 * count; +- case SG_UNIFORMTYPE_MAT4: return 64 * count; +- default: +- SOKOL_UNREACHABLE; +- return -1; +- } ++_SOKOL_PRIVATE void _sg_smpcache_add_item(_sg_sampler_cache_t* cache, const sg_image_desc* img_desc, uintptr_t sampler_handle) { ++ SOKOL_ASSERT(cache && cache->items); ++ SOKOL_ASSERT(img_desc); ++ SOKOL_ASSERT(cache->num_items < cache->capacity); ++ const int item_index = cache->num_items++; ++ _sg_sampler_cache_item_t* item = &cache->items[item_index]; ++ item->min_filter = img_desc->min_filter; ++ item->mag_filter = img_desc->mag_filter; ++ item->wrap_u = img_desc->wrap_u; ++ item->wrap_v = img_desc->wrap_v; ++ item->wrap_w = img_desc->wrap_w; ++ item->border_color = img_desc->border_color; ++ item->max_anisotropy = img_desc->max_anisotropy; ++ item->min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); ++ item->max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); ++ item->sampler_handle = sampler_handle; + } + +-/* return true if pixel format is a compressed format */ +-_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { ++_SOKOL_PRIVATE uintptr_t _sg_smpcache_sampler(_sg_sampler_cache_t* cache, int item_index) { ++ SOKOL_ASSERT(cache && cache->items); ++ SOKOL_ASSERT(item_index < cache->num_items); ++ return cache->items[item_index].sampler_handle; ++} ++ ++// ██ ██████ ██████ ██████ ██ ███ ██ ██████ ++// ██ ██ ██ ██ ██ ██ ████ ██ ██ ++// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ ++// ++// >>logging ++#if defined(SOKOL_DEBUG) ++#define _SG_LOGITEM_XMACRO(item,msg) #item ": " msg, ++static const char* _sg_log_messages[] = { ++ _SG_LOG_ITEMS ++}; ++#undef _SG_LOGITEM_XMACRO ++#endif // SOKOL_DEBUG ++ ++#define _SG_PANIC(code) _sg_log(SG_LOGITEM_ ##code, 0, 0, __LINE__) ++#define _SG_ERROR(code) _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__) ++#define _SG_WARN(code) _sg_log(SG_LOGITEM_ ##code, 2, 0, __LINE__) ++#define _SG_INFO(code) _sg_log(SG_LOGITEM_ ##code, 3, 0, __LINE__) ++#define _SG_LOGMSG(code,msg) _sg_log(SG_LOGITEM_ ##code, 3, msg, __LINE__) ++#define _SG_VALIDATE(cond,code) if (!(cond)){ _sg.validate_error = SG_LOGITEM_ ##code; _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__); } ++ ++static void _sg_log(sg_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { ++ if (_sg.desc.logger.func) { ++ const char* filename = 0; ++ #if defined(SOKOL_DEBUG) ++ filename = __FILE__; ++ if (0 == msg) { ++ msg = _sg_log_messages[log_item]; ++ } ++ #endif ++ _sg.desc.logger.func("sg", log_level, log_item, msg, line_nr, filename, _sg.desc.logger.user_data); ++ } ++ else { ++ // for log level PANIC it would be 'undefined behaviour' to continue ++ if (log_level == 0) { ++ abort(); ++ } ++ } ++} ++ ++// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ ++// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ ++// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ ++// ++// >>memory ++ ++// a helper macro to clear a struct with potentially ARC'ed ObjC references ++#if defined(SOKOL_METAL) ++ #if defined(__cplusplus) ++ #define _SG_CLEAR_ARC_STRUCT(type, item) { item = type(); } ++ #else ++ #define _SG_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } ++ #endif ++#else ++ #define _SG_CLEAR_ARC_STRUCT(type, item) { _sg_clear(&item, sizeof(item)); } ++#endif ++ ++_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) { ++ SOKOL_ASSERT(ptr && (size > 0)); ++ memset(ptr, 0, size); ++} ++ ++_SOKOL_PRIVATE void* _sg_malloc(size_t size) { ++ SOKOL_ASSERT(size > 0); ++ void* ptr; ++ if (_sg.desc.allocator.alloc) { ++ ptr = _sg.desc.allocator.alloc(size, _sg.desc.allocator.user_data); ++ } ++ else { ++ ptr = malloc(size); ++ } ++ if (0 == ptr) { ++ _SG_PANIC(MALLOC_FAILED); ++ } ++ return ptr; ++} ++ ++_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) { ++ void* ptr = _sg_malloc(size); ++ _sg_clear(ptr, size); ++ return ptr; ++} ++ ++_SOKOL_PRIVATE void _sg_free(void* ptr) { ++ if (_sg.desc.allocator.free) { ++ _sg.desc.allocator.free(ptr, _sg.desc.allocator.user_data); ++ } ++ else { ++ free(ptr); ++ } ++} ++ ++_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { ++ return 0 == str->buf[0]; ++} ++ ++_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { ++ return &str->buf[0]; ++} ++ ++_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { ++ SOKOL_ASSERT(dst); ++ if (src) { ++ #if defined(_MSC_VER) ++ strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); ++ #else ++ strncpy(dst->buf, src, _SG_STRING_SIZE); ++ #endif ++ dst->buf[_SG_STRING_SIZE-1] = 0; ++ } ++ else { ++ _sg_clear(dst->buf, _SG_STRING_SIZE); ++ } ++} ++ ++// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███████ █████ ██ ██████ █████ ██████ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ ++// ++// >>helpers ++_SOKOL_PRIVATE uint32_t _sg_align_u32(uint32_t val, uint32_t align) { ++ SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0)); ++ return (val + (align - 1)) & ~(align - 1); ++} ++ ++/* return byte size of a vertex format */ ++_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { ++ switch (fmt) { ++ case SG_VERTEXFORMAT_FLOAT: return 4; ++ case SG_VERTEXFORMAT_FLOAT2: return 8; ++ case SG_VERTEXFORMAT_FLOAT3: return 12; ++ case SG_VERTEXFORMAT_FLOAT4: return 16; ++ case SG_VERTEXFORMAT_BYTE4: return 4; ++ case SG_VERTEXFORMAT_BYTE4N: return 4; ++ case SG_VERTEXFORMAT_UBYTE4: return 4; ++ case SG_VERTEXFORMAT_UBYTE4N: return 4; ++ case SG_VERTEXFORMAT_SHORT2: return 4; ++ case SG_VERTEXFORMAT_SHORT2N: return 4; ++ case SG_VERTEXFORMAT_USHORT2N: return 4; ++ case SG_VERTEXFORMAT_SHORT4: return 8; ++ case SG_VERTEXFORMAT_SHORT4N: return 8; ++ case SG_VERTEXFORMAT_USHORT4N: return 8; ++ case SG_VERTEXFORMAT_UINT10_N2: return 4; ++ case SG_VERTEXFORMAT_HALF2: return 4; ++ case SG_VERTEXFORMAT_HALF4: return 8; ++ case SG_VERTEXFORMAT_INVALID: return 0; ++ default: ++ SOKOL_UNREACHABLE; ++ return -1; ++ } ++} ++ ++_SOKOL_PRIVATE uint32_t _sg_uniform_alignment(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { ++ if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { ++ return 1; ++ } ++ else { ++ SOKOL_ASSERT(array_count > 0); ++ if (array_count == 1) { ++ switch (type) { ++ case SG_UNIFORMTYPE_FLOAT: ++ case SG_UNIFORMTYPE_INT: ++ return 4; ++ case SG_UNIFORMTYPE_FLOAT2: ++ case SG_UNIFORMTYPE_INT2: ++ return 8; ++ case SG_UNIFORMTYPE_FLOAT3: ++ case SG_UNIFORMTYPE_FLOAT4: ++ case SG_UNIFORMTYPE_INT3: ++ case SG_UNIFORMTYPE_INT4: ++ return 16; ++ case SG_UNIFORMTYPE_MAT4: ++ return 16; ++ default: ++ SOKOL_UNREACHABLE; ++ return 1; ++ } ++ } ++ else { ++ return 16; ++ } ++ } ++} ++ ++_SOKOL_PRIVATE uint32_t _sg_uniform_size(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { ++ SOKOL_ASSERT(array_count > 0); ++ if (array_count == 1) { ++ switch (type) { ++ case SG_UNIFORMTYPE_FLOAT: ++ case SG_UNIFORMTYPE_INT: ++ return 4; ++ case SG_UNIFORMTYPE_FLOAT2: ++ case SG_UNIFORMTYPE_INT2: ++ return 8; ++ case SG_UNIFORMTYPE_FLOAT3: ++ case SG_UNIFORMTYPE_INT3: ++ return 12; ++ case SG_UNIFORMTYPE_FLOAT4: ++ case SG_UNIFORMTYPE_INT4: ++ return 16; ++ case SG_UNIFORMTYPE_MAT4: ++ return 64; ++ default: ++ SOKOL_UNREACHABLE; ++ return 0; ++ } ++ } ++ else { ++ if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { ++ switch (type) { ++ case SG_UNIFORMTYPE_FLOAT: ++ case SG_UNIFORMTYPE_INT: ++ return 4 * (uint32_t)array_count; ++ case SG_UNIFORMTYPE_FLOAT2: ++ case SG_UNIFORMTYPE_INT2: ++ return 8 * (uint32_t)array_count; ++ case SG_UNIFORMTYPE_FLOAT3: ++ case SG_UNIFORMTYPE_INT3: ++ return 12 * (uint32_t)array_count; ++ case SG_UNIFORMTYPE_FLOAT4: ++ case SG_UNIFORMTYPE_INT4: ++ return 16 * (uint32_t)array_count; ++ case SG_UNIFORMTYPE_MAT4: ++ return 64 * (uint32_t)array_count; ++ default: ++ SOKOL_UNREACHABLE; ++ return 0; ++ } ++ } ++ else { ++ switch (type) { ++ case SG_UNIFORMTYPE_FLOAT: ++ case SG_UNIFORMTYPE_FLOAT2: ++ case SG_UNIFORMTYPE_FLOAT3: ++ case SG_UNIFORMTYPE_FLOAT4: ++ case SG_UNIFORMTYPE_INT: ++ case SG_UNIFORMTYPE_INT2: ++ case SG_UNIFORMTYPE_INT3: ++ case SG_UNIFORMTYPE_INT4: ++ return 16 * (uint32_t)array_count; ++ case SG_UNIFORMTYPE_MAT4: ++ return 64 * (uint32_t)array_count; ++ default: ++ SOKOL_UNREACHABLE; ++ return 0; ++ } ++ } ++ } ++} ++ ++/* return true if pixel format is a compressed format */ ++_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC2_RGBA: +@@ -3927,12 +5025,14 @@ _SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) { + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RGBA8: ++ case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_RGBA8SI: + case SG_PIXELFORMAT_BGRA8: + case SG_PIXELFORMAT_RGB10A2: + case SG_PIXELFORMAT_RG11B10F: ++ case SG_PIXELFORMAT_RGB9E5: + return 4; + + case SG_PIXELFORMAT_RG32UI: +@@ -3961,7 +5061,21 @@ _SOKOL_PRIVATE int _sg_roundup(int val, int round_to) { + } + + /* return row pitch for an image ++ + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp ++ ++ For the special PVRTC pitch computation, see: ++ GL extension requirement (https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt) ++ ++ Quote: ++ ++ 6) How is the imageSize argument calculated for the CompressedTexImage2D ++ and CompressedTexSubImage2D functions. ++ ++ Resolution: For PVRTC 4BPP formats the imageSize is calculated as: ++ ( max(width, 8) * max(height, 8) * 4 + 7) / 8 ++ For PVRTC 2BPP formats the imageSize is calculated as: ++ ( max(width, 16) * max(height, 8) * 2 + 7) / 8 + */ + _SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) { + int pitch; +@@ -3989,23 +5103,11 @@ _SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) + break; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +- { +- const int block_size = 4*4; +- const int bpp = 4; +- int width_blocks = width / 4; +- width_blocks = width_blocks < 2 ? 2 : width_blocks; +- pitch = width_blocks * ((block_size * bpp) / 8); +- } ++ pitch = (_sg_max(width, 8) * 4 + 7) / 8; + break; + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +- { +- const int block_size = 8*4; +- const int bpp = 2; +- int width_blocks = width / 4; +- width_blocks = width_blocks < 2 ? 2 : width_blocks; +- pitch = width_blocks * ((block_size * bpp) / 8); +- } ++ pitch = (_sg_max(width, 16) * 2 + 7) / 8; + break; + default: + pitch = width * _sg_pixelformat_bytesize(fmt); +@@ -4034,11 +5136,19 @@ _SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) { + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: ++ num_rows = ((height + 3) / 4); ++ break; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +- num_rows = ((height + 3) / 4); ++ /* NOTE: this is most likely not correct because it ignores any ++ PVCRTC block size, but multiplied with _sg_row_pitch() ++ it gives the correct surface pitch. ++ ++ See: https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt ++ */ ++ num_rows = ((_sg_max(height, 8) + 7) / 8) * 8; + break; + default: + num_rows = height; +@@ -4143,7 +5253,13 @@ _SOKOL_PRIVATE void _sg_resolve_default_pass_action(const sg_pass_action* from, + } + } + +-/*== DUMMY BACKEND IMPL ======================================================*/ ++// ██████ ██ ██ ███ ███ ███ ███ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ ██████ ██ ██ ██ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>dummy backend + #if defined(SOKOL_DUMMY_BACKEND) + + _SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) { +@@ -4175,7 +5291,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_context(_sg_context_t* ctx) { + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_dummy_discard_context(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + _SOKOL_UNUSED(ctx); + } +@@ -4191,7 +5307,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, cons + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_dummy_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SOKOL_UNUSED(buf); + } +@@ -4202,7 +5318,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_dummy_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SOKOL_UNUSED(img); + } +@@ -4213,7 +5329,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, cons + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_dummy_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SOKOL_UNUSED(shd); + } +@@ -4233,7 +5349,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_dummy_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SOKOL_UNUSED(pip); + } +@@ -4265,7 +5381,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pass(_sg_pass_t* pass, _sg_ima + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_dummy_destroy_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_dummy_discard_pass(_sg_pass_t* pass) { + SOKOL_ASSERT(pass); + _SOKOL_UNUSED(pass); + } +@@ -4377,9 +5493,163 @@ _SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data + } + } + +-/*== GL BACKEND ==============================================================*/ ++// ██████ ██████ ███████ ███ ██ ██████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ ██ ██████ █████ ██ ██ ██ ██ ███ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ ██ ███████ ██ ████ ██████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>opengl backend + #elif defined(_SOKOL_ANY_GL) + ++// optional GL loader for win32 ++#if defined(_SOKOL_USE_WIN32_GL_LOADER) ++ ++// X Macro list of GL function names and signatures ++#define _SG_GL_FUNCS \ ++ _SG_XMACRO(glBindVertexArray, void, (GLuint array)) \ ++ _SG_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ ++ _SG_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ ++ _SG_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ ++ _SG_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ ++ _SG_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ ++ _SG_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ ++ _SG_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ ++ _SG_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ ++ _SG_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ ++ _SG_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ ++ _SG_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ ++ _SG_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ ++ _SG_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ ++ _SG_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ ++ _SG_XMACRO(glUniform1iv, void, (GLint location, GLsizei count, const GLint * value)) \ ++ _SG_XMACRO(glUniform2iv, void, (GLint location, GLsizei count, const GLint * value)) \ ++ _SG_XMACRO(glUniform3iv, void, (GLint location, GLsizei count, const GLint * value)) \ ++ _SG_XMACRO(glUniform4iv, void, (GLint location, GLsizei count, const GLint * value)) \ ++ _SG_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ ++ _SG_XMACRO(glUseProgram, void, (GLuint program)) \ ++ _SG_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ ++ _SG_XMACRO(glLinkProgram, void, (GLuint program)) \ ++ _SG_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ ++ _SG_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ ++ _SG_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ ++ _SG_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ ++ _SG_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ ++ _SG_XMACRO(glDeleteShader, void, (GLuint shader)) \ ++ _SG_XMACRO(glDeleteProgram, void, (GLuint program)) \ ++ _SG_XMACRO(glCompileShader, void, (GLuint shader)) \ ++ _SG_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ ++ _SG_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ ++ _SG_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ ++ _SG_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ ++ _SG_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ ++ _SG_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ ++ _SG_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ ++ _SG_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ ++ _SG_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ ++ _SG_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ ++ _SG_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ ++ _SG_XMACRO(glActiveTexture, void, (GLenum texture)) \ ++ _SG_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ ++ _SG_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ ++ _SG_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ ++ _SG_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ ++ _SG_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ ++ _SG_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ ++ _SG_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ ++ _SG_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ ++ _SG_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ ++ _SG_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ ++ _SG_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ ++ _SG_XMACRO(glCreateShader, GLuint, (GLenum type)) \ ++ _SG_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ ++ _SG_XMACRO(glClearDepth, void, (GLdouble depth)) \ ++ _SG_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ ++ _SG_XMACRO(glCreateProgram, GLuint, (void)) \ ++ _SG_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ ++ _SG_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ ++ _SG_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ ++ _SG_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ ++ _SG_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ ++ _SG_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ ++ _SG_XMACRO(glDisable, void, (GLenum cap)) \ ++ _SG_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ ++ _SG_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ ++ _SG_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ ++ _SG_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ ++ _SG_XMACRO(glDepthMask, void, (GLboolean flag)) \ ++ _SG_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ ++ _SG_XMACRO(glClearStencil, void, (GLint s)) \ ++ _SG_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ ++ _SG_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ ++ _SG_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ ++ _SG_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ ++ _SG_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ ++ _SG_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ ++ _SG_XMACRO(glEnable, void, (GLenum cap)) \ ++ _SG_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ ++ _SG_XMACRO(glStencilMask, void, (GLuint mask)) \ ++ _SG_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ ++ _SG_XMACRO(glGetError, GLenum, (void)) \ ++ _SG_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ ++ _SG_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ ++ _SG_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ ++ _SG_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, const GLfloat* params)) \ ++ _SG_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ ++ _SG_XMACRO(glDepthFunc, void, (GLenum func)) \ ++ _SG_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ ++ _SG_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ ++ _SG_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ ++ _SG_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ ++ _SG_XMACRO(glReadBuffer, void, (GLenum src)) \ ++ _SG_XMACRO(glReadPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data)) \ ++ _SG_XMACRO(glClear, void, (GLbitfield mask)) \ ++ _SG_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ ++ _SG_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ ++ _SG_XMACRO(glFrontFace, void, (GLenum mode)) \ ++ _SG_XMACRO(glCullFace, void, (GLenum mode)) \ ++ _SG_XMACRO(glPixelStorei, void, (GLenum pname, GLint param)) ++ ++// generate GL function pointer typedefs ++#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; ++_SG_GL_FUNCS ++#undef _SG_XMACRO ++ ++// generate GL function pointers ++#define _SG_XMACRO(name, ret, args) static PFN_ ## name name; ++_SG_GL_FUNCS ++#undef _SG_XMACRO ++ ++// helper function to lookup GL functions in GL DLL ++typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR); ++_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) { ++ void* proc_addr = (void*) wgl_getprocaddress(name); ++ if (0 == proc_addr) { ++ proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name); ++ } ++ SOKOL_ASSERT(proc_addr); ++ return proc_addr; ++} ++ ++// populate GL function pointers ++_SOKOL_PRIVATE void _sg_gl_load_opengl(void) { ++ SOKOL_ASSERT(0 == _sg.gl.opengl32_dll); ++ _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll"); ++ SOKOL_ASSERT(_sg.gl.opengl32_dll); ++ _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress"); ++ SOKOL_ASSERT(wgl_getprocaddress); ++ #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress); ++ _SG_GL_FUNCS ++ #undef _SG_XMACRO ++} ++ ++_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) { ++ SOKOL_ASSERT(_sg.gl.opengl32_dll); ++ FreeLibrary(_sg.gl.opengl32_dll); ++ _sg.gl.opengl32_dll = 0; ++} ++#endif // _SOKOL_USE_WIN32_GL_LOADER ++ + /*-- type translation --------------------------------------------------------*/ + _SOKOL_PRIVATE GLenum _sg_gl_buffer_target(sg_buffer_type t) { + switch (t) { +@@ -4435,6 +5705,8 @@ _SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) { + case SG_VERTEXFORMAT_SHORT4N: return 4; + case SG_VERTEXFORMAT_USHORT4N: return 4; + case SG_VERTEXFORMAT_UINT10_N2: return 4; ++ case SG_VERTEXFORMAT_HALF2: return 2; ++ case SG_VERTEXFORMAT_HALF4: return 4; + default: SOKOL_UNREACHABLE; return 0; + } + } +@@ -4462,6 +5734,9 @@ _SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) { + return GL_UNSIGNED_SHORT; + case SG_VERTEXFORMAT_UINT10_N2: + return GL_UNSIGNED_INT_2_10_10_10_REV; ++ case SG_VERTEXFORMAT_HALF2: ++ case SG_VERTEXFORMAT_HALF4: ++ return GL_HALF_FLOAT; + default: + SOKOL_UNREACHABLE; return 0; + } +@@ -4593,6 +5868,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RGBA8: ++ case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_BGRA8: + return GL_UNSIGNED_BYTE; +@@ -4638,6 +5914,8 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { + return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_PIXELFORMAT_RG11B10F: + return GL_UNSIGNED_INT_10F_11F_11F_REV; ++ case SG_PIXELFORMAT_RGB9E5: ++ return GL_UNSIGNED_INT_5_9_9_9_REV; + #endif + case SG_PIXELFORMAT_DEPTH: + return GL_UNSIGNED_SHORT; +@@ -4690,6 +5968,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { + return GL_RG_INTEGER; + #endif + case SG_PIXELFORMAT_RGBA8: ++ case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: +@@ -4707,6 +5986,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { + return GL_RGBA_INTEGER; + #endif + case SG_PIXELFORMAT_RG11B10F: ++ case SG_PIXELFORMAT_RGB9E5: + return GL_RGB; + case SG_PIXELFORMAT_DEPTH: + return GL_DEPTH_COMPONENT; +@@ -4790,11 +6070,13 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) { + case SG_PIXELFORMAT_RG16SI: return GL_RG16I; + case SG_PIXELFORMAT_RG16F: return GL_RG16F; + case SG_PIXELFORMAT_RGBA8: return GL_RGBA8; ++ case SG_PIXELFORMAT_SRGB8A8: return GL_SRGB8_ALPHA8; + case SG_PIXELFORMAT_RGBA8SN: return GL_RGBA8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return GL_RGBA8UI; + case SG_PIXELFORMAT_RGBA8SI: return GL_RGBA8I; + case SG_PIXELFORMAT_RGB10A2: return GL_RGB10_A2; + case SG_PIXELFORMAT_RG11B10F: return GL_R11F_G11F_B10F; ++ case SG_PIXELFORMAT_RGB9E5: return GL_RGB9_E5; + case SG_PIXELFORMAT_RG32UI: return GL_RG32UI; + case SG_PIXELFORMAT_RG32SI: return GL_RG32I; + case SG_PIXELFORMAT_RG32F: return GL_RG32F; +@@ -4895,6 +6177,7 @@ _SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + #if !defined(SOKOL_GLES2) + if (!_sg.gl.gles2) { ++ _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); +@@ -4907,6 +6190,7 @@ _SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { + if (!_sg.gl.gles2) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); ++ _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #if !defined(SOKOL_GLES3) +@@ -5095,6 +6379,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); +@@ -5115,7 +6402,7 @@ _SOKOL_PRIVATE void _sg_gl_init_limits(void) { + } + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int); + _SG_GL_CHECK_ERROR(); +- _sg.gl.max_combined_texture_image_units = gl_int; ++ _sg.limits.gl_max_combined_texture_image_units = gl_int; + } + + #if defined(SOKOL_GLCORE33) +@@ -5335,7 +6622,7 @@ _SOKOL_PRIVATE void _sg_gl_init_caps_gles2(void) { + } + + _sg.features.origin_top_left = false; +- #if defined(SOKOL_INSTANCING_ENABLED) ++ #if defined(_SOKOL_GL_INSTANCING_ENABLED) + _sg.features.instancing = has_instancing; + #endif + _sg.features.multiple_render_targets = false; +@@ -5437,7 +6724,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) { + } + } + +-/* called when from _sg_gl_destroy_buffer() */ ++/* called when _sg_gl_deinit_buffer() */ + _SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) { + if (buf == _sg.gl.cache.vertex_buffer) { + _sg.gl.cache.vertex_buffer = 0; +@@ -5468,7 +6755,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) { + } + + _SOKOL_PRIVATE void _sg_gl_cache_clear_texture_bindings(bool force) { +- for (int i = 0; (i < SG_MAX_SHADERSTAGE_IMAGES) && (i < _sg.gl.max_combined_texture_image_units); i++) { ++ for (int i = 0; (i < _SG_GL_IMAGE_CACHE_SIZE) && (i < _sg.limits.gl_max_combined_texture_image_units); i++) { + if (force || (_sg.gl.cache.textures[i].texture != 0)) { + GLenum gl_texture_slot = (GLenum) (GL_TEXTURE0 + i); + glActiveTexture(gl_texture_slot); +@@ -5492,8 +6779,8 @@ _SOKOL_PRIVATE void _sg_gl_cache_bind_texture(int slot_index, GLenum target, GLu + target=0 will unbind the previous binding, texture=0 will clear + the new binding + */ +- SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); +- if (slot_index >= _sg.gl.max_combined_texture_image_units) { ++ SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); ++ if (slot_index >= _sg.limits.gl_max_combined_texture_image_units) { + return; + } + _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[slot_index]; +@@ -5513,12 +6800,12 @@ _SOKOL_PRIVATE void _sg_gl_cache_bind_texture(int slot_index, GLenum target, GLu + } + + _SOKOL_PRIVATE void _sg_gl_cache_store_texture_binding(int slot_index) { +- SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); ++ SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); + _sg.gl.cache.stored_texture = _sg.gl.cache.textures[slot_index]; + } + + _SOKOL_PRIVATE void _sg_gl_cache_restore_texture_binding(int slot_index) { +- SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); ++ SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_IMAGE_CACHE_SIZE)); + _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.stored_texture; + if (slot->texture != 0) { + /* we only care restoring valid ids */ +@@ -5531,7 +6818,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_restore_texture_binding(int slot_index) { + + /* called from _sg_gl_destroy_texture() */ + _SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture(GLuint tex) { +- for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { ++ for (int i = 0; i < _SG_GL_IMAGE_CACHE_SIZE; i++) { + _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[i]; + if (tex == slot->texture) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i)); +@@ -5546,7 +6833,7 @@ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture(GLuint tex) { + } + } + +-/* called from _sg_gl_destroy_shader() */ ++/* called from _sg_gl_discard_shader() */ + _SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { + if (prog == _sg.gl.cache.prog) { + _sg.gl.cache.prog = 0; +@@ -5554,6 +6841,14 @@ _SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { + } + } + ++/* called from _sg_gl_discard_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(); +@@ -5563,7 +6858,7 @@ _SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { + _SG_GL_CHECK_ERROR(); + } + #endif +- memset(&_sg.gl.cache, 0, sizeof(_sg.gl.cache)); ++ _sg_clear(&_sg.gl.cache, sizeof(_sg.gl.cache)); + _sg_gl_cache_clear_buffer_bindings(true); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_clear_texture_bindings(true); +@@ -5645,6 +6940,10 @@ _SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { + _sg.gl.gles2 = false; + #endif + ++ #if defined(_SOKOL_USE_WIN32_GL_LOADER) ++ _sg_gl_load_opengl(); ++ #endif ++ + /* clear initial GL error state */ + #if defined(SOKOL_DEBUG) + while (glGetError() != GL_NO_ERROR); +@@ -5666,6 +6965,9 @@ _SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { + _SOKOL_PRIVATE void _sg_gl_discard_backend(void) { + SOKOL_ASSERT(_sg.gl.valid); + _sg.gl.valid = false; ++ #if defined(_SOKOL_USE_WIN32_GL_LOADER) ++ _sg_gl_unload_opengl(); ++ #endif + } + + _SOKOL_PRIVATE void _sg_gl_activate_context(_sg_context_t* ctx) { +@@ -5690,10 +6992,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_context(_sg_context_t* ctx) { + _SG_GL_CHECK_ERROR(); + } + #endif ++ // incoming texture data is generally expected to be packed tightly ++ glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_gl_discard_context(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + #if !defined(SOKOL_GLES2) + if (!_sg.gl.gles2) { +@@ -5722,6 +7026,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); +@@ -5737,7 +7042,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const s + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_gl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { +@@ -5765,16 +7070,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ + + /* check if texture format is support */ + if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) { +- SOKOL_LOG("texture format not supported by GL context\n"); ++ _SG_ERROR(GL_TEXTURE_FORMAT_NOT_SUPPORTED); + return SG_RESOURCESTATE_FAILED; + } + /* check for optional texture types */ + if ((img->cmn.type == SG_IMAGETYPE_3D) && !_sg.features.imagetype_3d) { +- SOKOL_LOG("3D textures not supported by GL context\n"); ++ _SG_ERROR(GL_3D_TEXTURES_NOT_SUPPORTED); + return SG_RESOURCESTATE_FAILED; + } + if ((img->cmn.type == SG_IMAGETYPE_ARRAY) && !_sg.features.imagetype_array) { +- SOKOL_LOG("array textures not supported by GL context\n"); ++ _SG_ERROR(GL_ARRAY_TEXTURES_NOT_SUPPORTED); + return SG_RESOURCESTATE_FAILED; + } + +@@ -5892,7 +7197,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ + gl_img_target = _sg_gl_cubeface_target(face_index); + } + const GLvoid* data_ptr = desc->data.subimage[face_index][mip_index].ptr; +- const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; + int mip_width = img->cmn.width >> mip_index; + if (mip_width == 0) { + mip_width = 1; +@@ -5903,6 +7207,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ + } + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + if (is_compressed) { ++ const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; + glCompressedTexImage2D(gl_img_target, mip_index, gl_internal_format, + mip_width, mip_height, 0, data_size, data_ptr); + } +@@ -5922,6 +7227,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ + mip_depth = 1; + } + if (is_compressed) { ++ const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; + glCompressedTexImage3D(gl_img_target, mip_index, gl_internal_format, + mip_width, mip_height, mip_depth, 0, data_size, data_ptr); + } +@@ -5942,7 +7248,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_ + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_gl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { +@@ -5975,10 +7281,11 @@ _SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* s + GLint log_len = 0; + glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { +- GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); ++ GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf); +- SOKOL_LOG(log_buf); +- SOKOL_FREE(log_buf); ++ _SG_ERROR(GL_SHADER_COMPILATION_FAILED); ++ _SG_LOGMSG(GL_SHADER_COMPILATION_FAILED, log_buf); ++ _sg_free(log_buf); + } + glDeleteShader(gl_shd); + gl_shd = 0; +@@ -6018,10 +7325,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s + GLint log_len = 0; + glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { +- GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); ++ GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf); +- SOKOL_LOG(log_buf); +- SOKOL_FREE(log_buf); ++ _SG_ERROR(GL_SHADER_LINKING_FAILED); ++ _SG_LOGMSG(GL_SHADER_LINKING_FAILED, log_buf); ++ _sg_free(log_buf); + } + glDeleteProgram(gl_prog); + return SG_RESOURCESTATE_FAILED; +@@ -6038,17 +7346,20 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s + SOKOL_ASSERT(ub_desc->size > 0); + _sg_gl_uniform_block_t* ub = &gl_stage->uniform_blocks[ub_index]; + SOKOL_ASSERT(ub->num_uniforms == 0); +- int cur_uniform_offset = 0; ++ uint32_t cur_uniform_offset = 0; + for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { + const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } ++ const uint32_t u_align = _sg_uniform_alignment(u_desc->type, u_desc->array_count, ub_desc->layout); ++ const uint32_t u_size = _sg_uniform_size(u_desc->type, u_desc->array_count, ub_desc->layout); ++ cur_uniform_offset = _sg_align_u32(cur_uniform_offset, u_align); + _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); ++ cur_uniform_offset += u_size; + if (u_desc->name) { + u->gl_loc = glGetUniformLocation(gl_prog, u_desc->name); + } +@@ -6057,7 +7368,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s + } + ub->num_uniforms++; + } ++ if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { ++ cur_uniform_offset = _sg_align_u32(cur_uniform_offset, 16); ++ } + SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset); ++ _SOKOL_UNUSED(cur_uniform_offset); + } + } + +@@ -6093,7 +7408,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_gl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SG_GL_CHECK_ERROR(); + if (shd->gl.prog) { +@@ -6150,6 +7465,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg + } + else { + gl_attr->divisor = (int8_t) step_rate; ++ pip->cmn.use_instanced_draw = true; + } + SOKOL_ASSERT(l_desc->stride > 0); + gl_attr->stride = (uint8_t) l_desc->stride; +@@ -6160,17 +7476,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg + pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; + } + else { +- SOKOL_LOG("Vertex attribute not found in shader: "); +- SOKOL_LOG(_sg_strptr(&shd->gl.attrs[attr_index].name)); ++ _SG_ERROR(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER); ++ _SG_LOGMSG(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, _sg_strptr(&shd->gl.attrs[attr_index].name)); + } + } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_gl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); +- _SOKOL_UNUSED(pip); +- /* empty */ ++ _sg_gl_cache_invalidate_pipeline(pip); + } + + /* +@@ -6267,7 +7582,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ + + /* check if framebuffer is complete */ + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +- SOKOL_LOG("Framebuffer completeness check failed!\n"); ++ _SG_ERROR(GL_FRAMEBUFFER_INCOMPLETE); + return SG_RESOURCESTATE_FAILED; + } + +@@ -6314,7 +7629,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ + } + /* check if framebuffer is complete */ + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +- SOKOL_LOG("Framebuffer completeness check failed (msaa resolve buffer)!\n"); ++ _SG_ERROR(GL_MSAA_FRAMEBUFFER_INCOMPLETE); + return SG_RESOURCESTATE_FAILED; + } + /* setup color attachments for the framebuffer */ +@@ -6334,8 +7649,9 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_ + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_gl_destroy_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_gl_discard_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); +@@ -6383,15 +7699,29 @@ _SOKOL_PRIVATE void _sg_gl_begin_pass(_sg_pass_t* pass, const sg_pass_action* ac + /* number of color attachments */ + const int num_color_atts = pass ? pass->cmn.num_color_atts : 1; + +- /* bind the render pass framebuffer */ ++ // bind the render pass framebuffer ++ // ++ // FIXME: Disabling SRGB conversion for the default framebuffer is ++ // a crude hack to make behaviour for sRGB render target textures ++ // identical with the Metal and D3D11 swapchains created by sokol-app. ++ // ++ // This will need a cleaner solution (e.g. allowing to configure ++ // sokol_app.h with an sRGB or RGB framebuffer. + if (pass) { +- /* offscreen pass */ ++ // offscreen pass + SOKOL_ASSERT(pass->gl.fb); ++ #if defined(SOKOL_GLCORE33) ++ glEnable(GL_FRAMEBUFFER_SRGB); ++ #endif + glBindFramebuffer(GL_FRAMEBUFFER, pass->gl.fb); ++ + } + else { +- /* default pass */ ++ // default pass + SOKOL_ASSERT(_sg.gl.cur_context); ++ #if defined(SOKOL_GLCORE33) ++ glDisable(GL_FRAMEBUFFER_SRGB); ++ #endif + glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.cur_context->default_framebuffer); + } + glViewport(0, 0, w, h); +@@ -6696,7 +8026,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { + } + + /* standalone state */ +- for (GLuint i = 0; i < (GLuint)pip->cmn.color_attachment_count; i++) { ++ for (GLuint i = 0; i < (GLuint)pip->cmn.color_count; i++) { + if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) { + const sg_color_mask cm = pip->gl.color_write_mask[i]; + _sg.gl.cache.color_write_mask[i] = cm; +@@ -6836,7 +8166,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_bindings( + glVertexAttribPointer(attr_index, attr->size, attr->type, + attr->normalized, attr->stride, + (const GLvoid*)(GLintptr)vb_offset); +- #ifdef SOKOL_INSTANCING_ENABLED ++ #if defined(_SOKOL_GL_INSTANCING_ENABLED) + if (_sg.features.instancing) { + glVertexAttribDivisor(attr_index, (GLuint)attr->divisor); + } +@@ -6878,24 +8208,37 @@ _SOKOL_PRIVATE void _sg_gl_apply_uniforms(sg_shader_stage stage_index, int ub_in + if (u->gl_loc == -1) { + continue; + } +- GLfloat* ptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); ++ GLfloat* fptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); ++ GLint* iptr = (GLint*) (((uint8_t*)data->ptr) + u->offset); + switch (u->type) { + case SG_UNIFORMTYPE_INVALID: + break; + case SG_UNIFORMTYPE_FLOAT: +- glUniform1fv(u->gl_loc, u->count, ptr); ++ glUniform1fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT2: +- glUniform2fv(u->gl_loc, u->count, ptr); ++ glUniform2fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT3: +- glUniform3fv(u->gl_loc, u->count, ptr); ++ glUniform3fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT4: +- glUniform4fv(u->gl_loc, u->count, ptr); ++ glUniform4fv(u->gl_loc, u->count, fptr); ++ break; ++ case SG_UNIFORMTYPE_INT: ++ glUniform1iv(u->gl_loc, u->count, iptr); ++ break; ++ case SG_UNIFORMTYPE_INT2: ++ glUniform2iv(u->gl_loc, u->count, iptr); ++ break; ++ case SG_UNIFORMTYPE_INT3: ++ glUniform3iv(u->gl_loc, u->count, iptr); ++ break; ++ case SG_UNIFORMTYPE_INT4: ++ glUniform4iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_MAT4: +- glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, ptr); ++ glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, fptr); + break; + default: + SOKOL_UNREACHABLE; +@@ -6905,6 +8248,7 @@ _SOKOL_PRIVATE void _sg_gl_apply_uniforms(sg_shader_stage stage_index, int ub_in + } + + _SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances) { ++ SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); + const GLenum i_type = _sg.gl.cache.cur_index_type; + const GLenum p_type = _sg.gl.cache.cur_primitive_type; + if (0 != i_type) { +@@ -6912,25 +8256,25 @@ _SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_inst + const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4; + const int ib_offset = _sg.gl.cache.cur_ib_offset; + const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset); +- if (num_instances == 1) { +- glDrawElements(p_type, num_elements, i_type, indices); +- } +- else { ++ if (_sg.gl.cache.cur_pipeline->cmn.use_instanced_draw) { + if (_sg.features.instancing) { + glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); + } + } ++ else { ++ glDrawElements(p_type, num_elements, i_type, indices); ++ } + } + else { + /* non-indexed rendering */ +- if (num_instances == 1) { +- glDrawArrays(p_type, base_element, num_elements); +- } +- else { ++ if (_sg.gl.cache.cur_pipeline->cmn.use_instanced_draw) { + if (_sg.features.instancing) { + glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); + } + } ++ else { ++ glDrawArrays(p_type, base_element, num_elements); ++ } + } + } + +@@ -7035,7 +8379,13 @@ _SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* d + _sg_gl_cache_restore_texture_binding(0); + } + +-/*== D3D11 BACKEND IMPLEMENTATION ============================================*/ ++// ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ ██ █████ ██ ██ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ ██████ ██████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>d3d11 backend + #elif defined(SOKOL_D3D11) + + #if defined(__cplusplus) +@@ -7411,6 +8761,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) { +@@ -7462,12 +8820,14 @@ _SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_pixel_format(sg_pixel_format fmt) { + case SG_PIXELFORMAT_RG16SI: return DXGI_FORMAT_R16G16_SINT; + case SG_PIXELFORMAT_RG16F: return DXGI_FORMAT_R16G16_FLOAT; + case SG_PIXELFORMAT_RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; ++ case SG_PIXELFORMAT_SRGB8A8: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case SG_PIXELFORMAT_RGBA8SN: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_PIXELFORMAT_RGBA8SI: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_PIXELFORMAT_BGRA8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case SG_PIXELFORMAT_RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_PIXELFORMAT_RG11B10F: return DXGI_FORMAT_R11G11B10_FLOAT; ++ case SG_PIXELFORMAT_RGB9E5: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; + case SG_PIXELFORMAT_RG32UI: return DXGI_FORMAT_R32G32_UINT; + case SG_PIXELFORMAT_RG32SI: return DXGI_FORMAT_R32G32_SINT; + case SG_PIXELFORMAT_RG32F: return DXGI_FORMAT_R32G32_FLOAT; +@@ -7583,6 +8943,8 @@ _SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) { + case SG_VERTEXFORMAT_SHORT4N: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_VERTEXFORMAT_USHORT4N: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM; ++ case SG_VERTEXFORMAT_HALF2: return DXGI_FORMAT_R16G16_FLOAT; ++ case SG_VERTEXFORMAT_HALF4: return DXGI_FORMAT_R16G16B16A16_FLOAT; + default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; + } + } +@@ -7749,21 +9111,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) { +@@ -7782,7 +9130,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_context(_sg_context_t* ctx) { + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_context(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + _SOKOL_UNUSED(ctx); + /* empty */ +@@ -7799,27 +9147,29 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, cons + } + else { + D3D11_BUFFER_DESC d3d11_desc; +- memset(&d3d11_desc, 0, sizeof(d3d11_desc)); ++ _sg_clear(&d3d11_desc, sizeof(d3d11_desc)); + d3d11_desc.ByteWidth = (UINT)buf->cmn.size; + d3d11_desc.Usage = _sg_d3d11_usage(buf->cmn.usage); + d3d11_desc.BindFlags = buf->cmn.type == SG_BUFFERTYPE_VERTEXBUFFER ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER; + d3d11_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(buf->cmn.usage); + D3D11_SUBRESOURCE_DATA* init_data_ptr = 0; + D3D11_SUBRESOURCE_DATA init_data; +- memset(&init_data, 0, sizeof(init_data)); ++ _sg_clear(&init_data, sizeof(init_data)); + if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { + SOKOL_ASSERT(desc->data.ptr); + init_data.pSysMem = desc->data.ptr; + init_data_ptr = &init_data; + } + HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_desc, init_data_ptr, &buf->d3d11.buf); +- _SOKOL_UNUSED(hr); +- SOKOL_ASSERT(SUCCEEDED(hr) && buf->d3d11.buf); ++ if (!(SUCCEEDED(hr) && buf->d3d11.buf)) { ++ _SG_ERROR(D3D11_CREATE_BUFFER_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + if (buf->d3d11.buf) { + _sg_d3d11_Release(buf->d3d11.buf); +@@ -7860,7 +9210,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + SOKOL_ASSERT(!img->d3d11.tex2d && !img->d3d11.tex3d && !img->d3d11.texds && !img->d3d11.texmsaa); + SOKOL_ASSERT(!img->d3d11.srv && !img->d3d11.smp); + HRESULT hr; +- _SOKOL_UNUSED(hr); + + _sg_image_common_init(&img->cmn, desc); + const bool injected = (0 != desc->d3d11_texture) || (0 != desc->d3d11_shader_resource_view); +@@ -7872,11 +9221,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + /* create only a depth-texture */ + SOKOL_ASSERT(!injected); + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { +- SOKOL_LOG("trying to create a D3D11 depth-texture with unsupported pixel format\n"); ++ _SG_ERROR(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + D3D11_TEXTURE2D_DESC d3d11_desc; +- memset(&d3d11_desc, 0, sizeof(d3d11_desc)); ++ _sg_clear(&d3d11_desc, sizeof(d3d11_desc)); + d3d11_desc.Width = (UINT)img->cmn.width; + d3d11_desc.Height = (UINT)img->cmn.height; + d3d11_desc.MipLevels = 1; +@@ -7887,7 +9236,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; + d3d11_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_desc, NULL, &img->d3d11.texds); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texds); ++ if (!(SUCCEEDED(hr) && img->d3d11.texds)) { ++ _SG_ERROR(D3D11_CREATE_DEPTH_TEXTURE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + else { + /* create (or inject) color texture and shader-resource-view */ +@@ -7925,7 +9277,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + /* if not injected, create texture */ + if (0 == img->d3d11.tex2d) { + D3D11_TEXTURE2D_DESC d3d11_tex_desc; +- memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); ++ _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; +@@ -7948,8 +9300,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); + } + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { +- /* trying to create a texture format that's not supported by D3D */ +- SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n"); ++ _SG_ERROR(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + d3d11_tex_desc.SampleDesc.Count = 1; +@@ -7957,13 +9308,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0; + + hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex2d); ++ if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) { ++ _SG_ERROR(D3D11_CREATE_2D_TEXTURE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + + /* ...and similar, if not injected, create shader-resource-view */ + if (0 == img->d3d11.srv) { + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; +- memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); ++ _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = img->d3d11.format; + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: +@@ -7983,7 +9337,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); ++ if (!(SUCCEEDED(hr) && img->d3d11.srv)) { ++ _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + } + else { +@@ -8006,7 +9363,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + + if (0 == img->d3d11.tex3d) { + D3D11_TEXTURE3D_DESC d3d11_tex_desc; +- memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); ++ _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices; +@@ -8025,29 +9382,34 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); + } + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { +- /* trying to create a texture format that's not supported by D3D */ +- SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n"); ++ _SG_ERROR(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex3d); ++ if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) { ++ _SG_ERROR(D3D11_CREATE_3D_TEXTURE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + + if (0 == img->d3d11.srv) { + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; +- memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); ++ _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = img->d3d11.format; + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + d3d11_srv_desc.Texture3D.MipLevels = (UINT)img->cmn.num_mipmaps; + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex3d, &d3d11_srv_desc, &img->d3d11.srv); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); ++ if (!(SUCCEEDED(hr) && img->d3d11.srv)) { ++ _SG_ERROR(D3D11_CREATE_3D_SRV_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + } + + /* also need to create a separate MSAA render target texture? */ + if (msaa) { + D3D11_TEXTURE2D_DESC d3d11_tex_desc; +- memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); ++ _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.MipLevels = 1; +@@ -8059,12 +9421,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; + d3d11_tex_desc.SampleDesc.Quality = (UINT)D3D11_STANDARD_MULTISAMPLE_PATTERN; + hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, NULL, &img->d3d11.texmsaa); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texmsaa); ++ if (!(SUCCEEDED(hr) && img->d3d11.texmsaa)) { ++ _SG_ERROR(D3D11_CREATE_MSAA_TEXTURE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + + /* sampler state object, note D3D11 implements an internal shared-pool for sampler objects */ + D3D11_SAMPLER_DESC d3d11_smp_desc; +- memset(&d3d11_smp_desc, 0, sizeof(d3d11_smp_desc)); ++ _sg_clear(&d3d11_smp_desc, sizeof(d3d11_smp_desc)); + d3d11_smp_desc.Filter = _sg_d3d11_filter(img->cmn.min_filter, img->cmn.mag_filter, img->cmn.max_anisotropy); + d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(img->cmn.wrap_u); + d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(img->cmn.wrap_v); +@@ -8088,12 +9453,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const + d3d11_smp_desc.MinLOD = desc->min_lod; + d3d11_smp_desc.MaxLOD = desc->max_lod; + hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &img->d3d11.smp); +- SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.smp); ++ if (!(SUCCEEDED(hr) && img->d3d11.smp)) { ++ _SG_ERROR(D3D11_CREATE_SAMPLER_STATE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + if (img->d3d11.tex2d) { + _sg_d3d11_Release(img->d3d11.tex2d); +@@ -8125,7 +9493,7 @@ _SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) { + _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll"); + if (0 == _sg.d3d11.d3dcompiler_dll) { + /* don't attempt to load missing DLL in the future */ +- SOKOL_LOG("failed to load d3dcompiler_47.dll!\n"); ++ _SG_ERROR(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED); + _sg.d3d11.d3dcompiler_dll_load_failed = true; + return false; + } +@@ -8162,8 +9530,12 @@ _SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_stage_desc* st + 0, /* Flags2 */ + &output, /* ppCode */ + &errors_or_warnings); /* ppErrorMsgs */ ++ if (FAILED(hr)) { ++ _SG_ERROR(D3D11_SHADER_COMPILATION_FAILED); ++ } + if (errors_or_warnings) { +- SOKOL_LOG((LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); ++ _SG_WARN(D3D11_SHADER_COMPILATION_OUTPUT); ++ _SG_LOGMSG(D3D11_SHADER_COMPILATION_OUTPUT, (LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); + _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL; + } + if (FAILED(hr)) { +@@ -8180,7 +9552,6 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.vs_blob); + HRESULT hr; +- _SOKOL_UNUSED(hr); + + _sg_shader_common_init(&shd->cmn, desc); + +@@ -8195,17 +9566,20 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons + _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; + _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; + for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { +- const _sg_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; ++ const _sg_shader_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; + + /* create a D3D constant buffer for each uniform block */ + SOKOL_ASSERT(0 == d3d11_stage->cbufs[ub_index]); + D3D11_BUFFER_DESC cb_desc; +- memset(&cb_desc, 0, sizeof(cb_desc)); ++ _sg_clear(&cb_desc, sizeof(cb_desc)); + cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16); + cb_desc.Usage = D3D11_USAGE_DEFAULT; + cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &d3d11_stage->cbufs[ub_index]); +- SOKOL_ASSERT(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index]); ++ if (!(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index])) { ++ _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + } + +@@ -8241,7 +9615,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons + /* need to store the vertex shader byte code, this is needed later in sg_create_pipeline */ + if (vs_succeeded && fs_succeeded) { + shd->d3d11.vs_blob_length = vs_length; +- shd->d3d11.vs_blob = SOKOL_MALLOC((size_t)vs_length); ++ shd->d3d11.vs_blob = _sg_malloc((size_t)vs_length); + SOKOL_ASSERT(shd->d3d11.vs_blob); + memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length); + result = SG_RESOURCESTATE_VALID; +@@ -8256,7 +9630,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons + return result; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + if (shd->d3d11.vs) { + _sg_d3d11_Release(shd->d3d11.vs); +@@ -8265,7 +9639,7 @@ _SOKOL_PRIVATE void _sg_d3d11_destroy_shader(_sg_shader_t* shd) { + _sg_d3d11_Release(shd->d3d11.fs); + } + if (shd->d3d11.vs_blob) { +- SOKOL_FREE(shd->d3d11.vs_blob); ++ _sg_free(shd->d3d11.vs_blob); + } + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; +@@ -8293,9 +9667,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, + + /* create input layout object */ + HRESULT hr; +- _SOKOL_UNUSED(hr); + D3D11_INPUT_ELEMENT_DESC d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]; +- memset(d3d11_comps, 0, sizeof(d3d11_comps)); ++ _sg_clear(d3d11_comps, sizeof(d3d11_comps)); + int attr_index = 0; + for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +@@ -8315,6 +9688,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, + d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func); + if (SG_VERTEXSTEP_PER_INSTANCE == step_func) { + d3d11_comp->InstanceDataStepRate = (UINT)step_rate; ++ pip->cmn.use_instanced_draw = true; + } + pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; + } +@@ -8334,27 +9708,33 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, + shd->d3d11.vs_blob, /* pShaderByteCodeWithInputSignature */ + shd->d3d11.vs_blob_length, /* BytecodeLength */ + &pip->d3d11.il); +- SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.il); ++ if (!(SUCCEEDED(hr) && pip->d3d11.il)) { ++ _SG_ERROR(D3D11_CREATE_INPUT_LAYOUT_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + + /* create rasterizer state */ + D3D11_RASTERIZER_DESC rs_desc; +- memset(&rs_desc, 0, sizeof(rs_desc)); ++ _sg_clear(&rs_desc, sizeof(rs_desc)); + rs_desc.FillMode = D3D11_FILL_SOLID; + rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode); + rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW; +- rs_desc.DepthBias = (INT) pip->cmn.depth_bias; +- rs_desc.DepthBiasClamp = pip->cmn.depth_bias_clamp; +- rs_desc.SlopeScaledDepthBias = pip->cmn.depth_bias_slope_scale; ++ rs_desc.DepthBias = (INT) pip->cmn.depth.bias; ++ rs_desc.DepthBiasClamp = pip->cmn.depth.bias_clamp; ++ rs_desc.SlopeScaledDepthBias = pip->cmn.depth.bias_slope_scale; + rs_desc.DepthClipEnable = TRUE; + rs_desc.ScissorEnable = TRUE; + rs_desc.MultisampleEnable = desc->sample_count > 1; + rs_desc.AntialiasedLineEnable = FALSE; + hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs); +- SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.rs); ++ if (!(SUCCEEDED(hr) && pip->d3d11.rs)) { ++ _SG_ERROR(D3D11_CREATE_RASTERIZER_STATE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + + /* create depth-stencil state */ + D3D11_DEPTH_STENCIL_DESC dss_desc; +- memset(&dss_desc, 0, sizeof(dss_desc)); ++ _sg_clear(&dss_desc, sizeof(dss_desc)); + dss_desc.DepthEnable = TRUE; + dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; + dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare); +@@ -8372,11 +9752,14 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, + dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op); + dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare); + hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss); +- SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.dss); ++ if (!(SUCCEEDED(hr) && pip->d3d11.dss)) { ++ _SG_ERROR(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + + /* create blend state */ + D3D11_BLEND_DESC bs_desc; +- memset(&bs_desc, 0, sizeof(bs_desc)); ++ _sg_clear(&bs_desc, sizeof(bs_desc)); + bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled; + bs_desc.IndependentBlendEnable = TRUE; + { +@@ -8403,13 +9786,19 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, + } + } + hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs); +- SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.bs); +- ++ if (!(SUCCEEDED(hr) && pip->d3d11.bs)) { ++ _SG_ERROR(D3D11_CREATE_BLEND_STATE_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_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); + } +@@ -8447,7 +9836,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima + ID3D11Resource* d3d11_res = 0; + const bool is_msaa = att_img->cmn.sample_count > 1; + D3D11_RENDER_TARGET_VIEW_DESC d3d11_rtv_desc; +- memset(&d3d11_rtv_desc, 0, sizeof(d3d11_rtv_desc)); ++ _sg_clear(&d3d11_rtv_desc, sizeof(d3d11_rtv_desc)); + d3d11_rtv_desc.Format = att_img->d3d11.format; + if ((att_img->cmn.type == SG_IMAGETYPE_2D) || is_msaa) { + if (is_msaa) { +@@ -8477,8 +9866,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima + } + SOKOL_ASSERT(d3d11_res); + HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, d3d11_res, &d3d11_rtv_desc, &pass->d3d11.color_atts[i].rtv); +- _SOKOL_UNUSED(hr); +- SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv); ++ if (!(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv)) { ++ _SG_ERROR(D3D11_CREATE_RTV_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + + /* optional depth-stencil image */ +@@ -8496,7 +9887,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima + + /* create D3D11 depth-stencil-view */ + D3D11_DEPTH_STENCIL_VIEW_DESC d3d11_dsv_desc; +- memset(&d3d11_dsv_desc, 0, sizeof(d3d11_dsv_desc)); ++ _sg_clear(&d3d11_dsv_desc, sizeof(d3d11_dsv_desc)); + d3d11_dsv_desc.Format = att_img->d3d11.format; + const bool is_msaa = att_img->cmn.sample_count > 1; + if (is_msaa) { +@@ -8508,14 +9899,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima + ID3D11Resource* d3d11_res = (ID3D11Resource*) att_img->d3d11.texds; + SOKOL_ASSERT(d3d11_res); + HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, d3d11_res, &d3d11_dsv_desc, &pass->d3d11.ds_att.dsv); +- _SOKOL_UNUSED(hr); +- SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv); ++ if (!(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv)) { ++ _SG_ERROR(D3D11_CREATE_DSV_FAILED); ++ return SG_RESOURCESTATE_FAILED; ++ } + } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_d3d11_destroy_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_d3d11_discard_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); +@@ -8585,7 +9979,7 @@ _SOKOL_PRIVATE void _sg_d3d11_begin_pass(_sg_pass_t* pass, const sg_pass_action* + + /* set viewport and scissor rect to cover whole screen */ + D3D11_VIEWPORT vp; +- memset(&vp, 0, sizeof(vp)); ++ _sg_clear(&vp, sizeof(vp)); + vp.Width = (FLOAT) w; + vp.Height = (FLOAT) h; + vp.MaxDepth = 1.0f; +@@ -8690,6 +10084,7 @@ _SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) { + _sg.d3d11.cur_pipeline = pip; + _sg.d3d11.cur_pipeline_id.id = pip->slot.id; + _sg.d3d11.use_indexed_draw = (pip->d3d11.index_format != DXGI_FORMAT_UNKNOWN); ++ _sg.d3d11.use_instanced_draw = pip->cmn.use_instanced_draw; + + _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs); + _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref); +@@ -8774,19 +10169,19 @@ _SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(sg_shader_stage stage_index, int ub + _SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.d3d11.in_pass); + if (_sg.d3d11.use_indexed_draw) { +- if (1 == num_instances) { +- _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); ++ if (_sg.d3d11.use_instanced_draw) { ++ _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); + } + else { +- _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); ++ _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); + } + } + else { +- if (1 == num_instances) { +- _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); ++ if (_sg.d3d11.use_instanced_draw) { ++ _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); + } + else { +- _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); ++ _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); + } + } + } +@@ -8801,10 +10196,13 @@ _SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* d + SOKOL_ASSERT(buf->d3d11.buf); + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); +- _SOKOL_UNUSED(hr); +- SOKOL_ASSERT(SUCCEEDED(hr)); +- memcpy(d3d11_msr.pData, data->ptr, data->size); +- _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); ++ if (SUCCEEDED(hr)) { ++ memcpy(d3d11_msr.pData, data->ptr, data->size); ++ _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); ++ } ++ else { ++ _SG_ERROR(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED); ++ } + } + + _SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +@@ -8814,12 +10212,15 @@ _SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* da + D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr); +- _SOKOL_UNUSED(hr); +- SOKOL_ASSERT(SUCCEEDED(hr)); +- uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; +- memcpy(dst_ptr, data->ptr, data->size); +- _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); +- /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backend */ ++ if (SUCCEEDED(hr)) { ++ uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; ++ memcpy(dst_ptr, data->ptr, data->size); ++ _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); ++ } ++ else { ++ _SG_ERROR(D3D11_MAP_FOR_APPEND_BUFFER_FAILED); ++ } ++ /* NOTE: this alignment is a requirement from WebGPU, but we want identical behaviour across all backend */ + return _sg_roundup((int)data->size, 4); + } + +@@ -8839,7 +10240,6 @@ _SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; + UINT subres_index = 0; + HRESULT hr; +- _SOKOL_UNUSED(hr); + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int slice_index = 0; slice_index < num_slices; slice_index++) { +@@ -8853,38 +10253,46 @@ _SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* slice_ptr = ((const uint8_t*)subimg_data->ptr) + slice_offset; + hr = _sg_d3d11_Map(_sg.d3d11.ctx, d3d11_res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); +- SOKOL_ASSERT(SUCCEEDED(hr)); +- /* FIXME: need to handle difference in depth-pitch for 3D textures as well! */ +- if (src_pitch == (int)d3d11_msr.RowPitch) { +- memcpy(d3d11_msr.pData, slice_ptr, slice_size); ++ if (SUCCEEDED(hr)) { ++ /* FIXME: need to handle difference in depth-pitch for 3D textures as well! */ ++ if (src_pitch == (int)d3d11_msr.RowPitch) { ++ memcpy(d3d11_msr.pData, slice_ptr, slice_size); ++ } ++ else { ++ SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); ++ const uint8_t* src_ptr = slice_ptr; ++ uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; ++ for (int row_index = 0; row_index < mip_height; row_index++) { ++ memcpy(dst_ptr, src_ptr, (size_t)src_pitch); ++ src_ptr += src_pitch; ++ dst_ptr += d3d11_msr.RowPitch; ++ } ++ } ++ _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index); + } + else { +- SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); +- const uint8_t* src_ptr = slice_ptr; +- uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; +- for (int row_index = 0; row_index < mip_height; row_index++) { +- memcpy(dst_ptr, src_ptr, (size_t)src_pitch); +- src_ptr += src_pitch; +- dst_ptr += d3d11_msr.RowPitch; +- } ++ _SG_ERROR(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED); + } +- _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index); + } + } + } + } + +-/*== METAL BACKEND IMPLEMENTATION ============================================*/ ++// ███ ███ ███████ ████████ █████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ ████ ██ █████ ██ ███████ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ███████ ██ ██ ██ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>metal backend + #elif defined(SOKOL_METAL) + + #if __has_feature(objc_arc) + #define _SG_OBJC_RETAIN(obj) { } + #define _SG_OBJC_RELEASE(obj) { obj = nil; } +-#define _SG_OBJC_RELEASE_WITH_NULL(obj) { obj = [NSNull null]; } + #else + #define _SG_OBJC_RETAIN(obj) { [obj retain]; } + #define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +-#define _SG_OBJC_RELEASE_WITH_NULL(obj) { [obj release]; obj = [NSNull null]; } + #endif + + /*-- enum translation functions ----------------------------------------------*/ +@@ -8900,13 +10308,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; +@@ -8939,6 +10351,8 @@ _SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) { + case SG_VERTEXFORMAT_SHORT4N: return MTLVertexFormatShort4Normalized; + case SG_VERTEXFORMAT_USHORT4N: return MTLVertexFormatUShort4Normalized; + case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized; ++ case SG_VERTEXFORMAT_HALF2: return MTLVertexFormatHalf2; ++ case SG_VERTEXFORMAT_HALF4: return MTLVertexFormatHalf4; + default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0; + } + } +@@ -8978,12 +10392,14 @@ _SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) { + case SG_PIXELFORMAT_RG16SI: return MTLPixelFormatRG16Sint; + case SG_PIXELFORMAT_RG16F: return MTLPixelFormatRG16Float; + case SG_PIXELFORMAT_RGBA8: return MTLPixelFormatRGBA8Unorm; ++ case SG_PIXELFORMAT_SRGB8A8: return MTLPixelFormatRGBA8Unorm_sRGB; + case SG_PIXELFORMAT_RGBA8SN: return MTLPixelFormatRGBA8Snorm; + case SG_PIXELFORMAT_RGBA8UI: return MTLPixelFormatRGBA8Uint; + case SG_PIXELFORMAT_RGBA8SI: return MTLPixelFormatRGBA8Sint; + case SG_PIXELFORMAT_BGRA8: return MTLPixelFormatBGRA8Unorm; + case SG_PIXELFORMAT_RGB10A2: return MTLPixelFormatRGB10A2Unorm; + case SG_PIXELFORMAT_RG11B10F: return MTLPixelFormatRG11B10Float; ++ case SG_PIXELFORMAT_RGB9E5: return MTLPixelFormatRGB9E5Float; + case SG_PIXELFORMAT_RG32UI: return MTLPixelFormatRG32Uint; + case SG_PIXELFORMAT_RG32SI: return MTLPixelFormatRG32Sint; + case SG_PIXELFORMAT_RG32F: return MTLPixelFormatRG32Float; +@@ -9231,7 +10647,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { + SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots); + /* a queue of currently free slot indices */ + _sg.mtl.idpool.free_queue_top = 0; +- _sg.mtl.idpool.free_queue = (int*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); ++ _sg.mtl.idpool.free_queue = (int*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); + /* pool slot 0 is reserved! */ + for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) { + _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i; +@@ -9242,7 +10658,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { + */ + _sg.mtl.idpool.release_queue_front = 0; + _sg.mtl.idpool.release_queue_back = 0; +- _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); ++ _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); + for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { + _sg.mtl.idpool.release_queue[i].frame_index = 0; + _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX; +@@ -9250,8 +10666,8 @@ _SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { + } + + _SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) { +- SOKOL_FREE(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; +- SOKOL_FREE(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; ++ _sg_free(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; ++ _sg_free(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; + _SG_OBJC_RELEASE(_sg.mtl.idpool.pool); + } + +@@ -9276,6 +10692,7 @@ _SOKOL_PRIVATE int _sg_mtl_add_resource(id res) { + return _SG_MTL_INVALID_SLOT_INDEX; + } + const int slot_index = _sg_mtl_alloc_pool_slot(); ++ // NOTE: the NSMutableArray will take ownership of its items + SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res; + return slot_index; +@@ -9315,8 +10732,11 @@ _SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) { + /* safe to release this resource */ + const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index; + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); ++ /* note: the NSMutableArray takes ownership of its items, assigning an NSNull object will ++ release the object, no matter if using ARC or not ++ */ + SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]); +- _SG_OBJC_RELEASE_WITH_NULL(_sg.mtl.idpool.pool[(NSUInteger)slot_index]); ++ _sg.mtl.idpool.pool[(NSUInteger)slot_index] = [NSNull null]; + /* put the now free pool index back on the free queue */ + _sg_mtl_free_pool_slot(slot_index); + /* reset the release queue slot and advance the back index */ +@@ -9381,13 +10801,14 @@ _SOKOL_PRIVATE int _sg_mtl_create_sampler(id mtl_device, const sg_ima + id mtl_sampler = [mtl_device newSamplerStateWithDescriptor:mtl_desc]; + _SG_OBJC_RELEASE(mtl_desc); + int sampler_handle = _sg_mtl_add_resource(mtl_sampler); ++ _SG_OBJC_RELEASE(mtl_sampler); + _sg_smpcache_add_item(&_sg.mtl.sampler_cache, img_desc, (uintptr_t)sampler_handle); + return sampler_handle; + } + } + + _SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) { +- memset(&_sg.mtl.state_cache, 0, sizeof(_sg.mtl.state_cache)); ++ _sg_clear(&_sg.mtl.state_cache, sizeof(_sg.mtl.state_cache)); + } + + /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */ +@@ -9467,6 +10888,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) { + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); ++ _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); +@@ -9474,9 +10896,11 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + #if defined(_SG_TARGET_MACOS) ++ _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #else ++ _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #endif +@@ -9552,14 +10976,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(); +@@ -9588,6 +11008,7 @@ _SOKOL_PRIVATE void _sg_mtl_discard_backend(void) { + } + /* NOTE: MTLCommandBuffer and MTLRenderCommandEncoder are auto-released */ + _sg.mtl.cmd_buffer = nil; ++ _sg.mtl.present_cmd_buffer = nil; + _sg.mtl.cmd_encoder = nil; + } + +@@ -9622,7 +11043,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_context(_sg_context_t* ctx) { + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_mtl_discard_context(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + _SOKOL_UNUSED(ctx); + /* empty */ +@@ -9654,11 +11075,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const + } + } + buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf); ++ _SG_OBJC_RELEASE(mtl_buf); + } + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_mtl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + /* it's valid to call release resource with '0' */ +@@ -9676,22 +11098,31 @@ _SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unr + const uint8_t* data_ptr = (const uint8_t*)data->subimage[face_index][mip_index].ptr; + const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); + const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); +- /* special case PVRTC formats: bytePerRow must be 0 */ ++ /* special case PVRTC formats: bytePerRow and bytesPerImage must be 0 */ + int bytes_per_row = 0; +- int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); ++ int bytes_per_slice = 0; + if (!_sg_mtl_is_pvrtc(img->cmn.pixel_format)) { + bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); ++ bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + } ++ /* bytesPerImage special case: https://developer.apple.com/documentation/metal/mtltexture/1515679-replaceregion ++ ++ "Supply a nonzero value only when you copy data to a MTLTextureType3D type texture" ++ */ + MTLRegion region; ++ int bytes_per_image; + if (img->cmn.type == SG_IMAGETYPE_3D) { + const int mip_depth = _sg_max(img->cmn.num_slices >> mip_index, 1); + region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth); ++ bytes_per_image = bytes_per_slice; + /* FIXME: apparently the minimal bytes_per_image size for 3D texture + is 4 KByte... somehow need to handle this */ + } + else { + region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height); ++ bytes_per_image = 0; + } ++ + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + const int mtl_slice_index = (img->cmn.type == SG_IMAGETYPE_CUBE) ? face_index : slice_index; + const int slice_offset = slice_index * bytes_per_slice; +@@ -9701,7 +11132,7 @@ _SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unr + slice:(NSUInteger)mtl_slice_index + withBytes:data_ptr + slice_offset + bytesPerRow:(NSUInteger)bytes_per_row +- bytesPerImage:(NSUInteger)bytes_per_slice]; ++ bytesPerImage:(NSUInteger)bytes_per_image]; + } + } + } +@@ -9727,7 +11158,7 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, + mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type); + mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format); + if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) { +- SOKOL_LOG("Unsupported texture pixel format!\n"); ++ _SG_ERROR(METAL_TEXTURE_FORMAT_NOT_SUPPORTED); + return false; + } + mtl_desc.width = (NSUInteger)img->cmn.width; +@@ -9746,18 +11177,21 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, + mtl_desc.arrayLength = 1; + } + mtl_desc.usage = MTLTextureUsageShaderRead; ++ if (img->cmn.render_target) { ++ mtl_desc.usage |= MTLTextureUsageRenderTarget; ++ } ++ 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; + } + +@@ -9765,11 +11199,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; + } +@@ -9777,11 +11208,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; +@@ -9828,6 +11256,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg + id tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; + SOKOL_ASSERT(nil != tex); + img->mtl.depth_tex = _sg_mtl_add_resource(tex); ++ _SG_OBJC_RELEASE(tex); + } + else { + /* create the color texture +@@ -9854,6 +11283,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg + } + } + img->mtl.tex[slot] = _sg_mtl_add_resource(tex); ++ _SG_OBJC_RELEASE(tex); + } + + /* if MSAA color render target, create an additional MSAA render-surface texture */ +@@ -9861,6 +11291,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg + _sg_mtl_init_texdesc_rt_msaa(mtl_desc, img); + id tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; + img->mtl.msaa_tex = _sg_mtl_add_resource(tex); ++ _SG_OBJC_RELEASE(tex); + } + + /* create (possibly shared) sampler state */ +@@ -9870,7 +11301,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_mtl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + /* it's valid to call release resource with a 'null resource' */ + for (int slot = 0; slot < img->cmn.num_slots; slot++) { +@@ -9889,7 +11320,8 @@ _SOKOL_PRIVATE id _sg_mtl_compile_library(const char* src) { + error:&err + ]; + if (err) { +- SOKOL_LOG([err.localizedDescription UTF8String]); ++ _SG_ERROR(METAL_SHADER_COMPILATION_FAILED); ++ _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + return lib; + } +@@ -9899,7 +11331,8 @@ _SOKOL_PRIVATE id _sg_mtl_library_from_bytecode(const void* ptr, siz + dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + id lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err]; + if (err) { +- SOKOL_LOG([err.localizedDescription UTF8String]); ++ _SG_ERROR(METAL_SHADER_CREATION_FAILED); ++ _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + _SG_OBJC_RELEASE(lib_data); + return lib; +@@ -9910,19 +11343,19 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const + + _sg_shader_common_init(&shd->cmn, desc); + +- /* create metal libray objects and lookup entry functions */ +- id vs_lib; +- id fs_lib; +- id vs_func; +- id fs_func; ++ /* create metal library objects and lookup entry functions */ ++ id vs_lib = nil; ++ id fs_lib = nil; ++ id vs_func = nil; ++ id fs_func = nil; + const char* vs_entry = desc->vs.entry; + const char* fs_entry = desc->fs.entry; + if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { + /* separate byte code provided */ + vs_lib = _sg_mtl_library_from_bytecode(desc->vs.bytecode.ptr, desc->vs.bytecode.size); + fs_lib = _sg_mtl_library_from_bytecode(desc->fs.bytecode.ptr, desc->fs.bytecode.size); +- if (nil == vs_lib || nil == fs_lib) { +- return SG_RESOURCESTATE_FAILED; ++ if ((nil == vs_lib) || (nil == fs_lib)) { ++ goto failed; + } + vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; + fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; +@@ -9931,32 +11364,50 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const + /* separate sources provided */ + vs_lib = _sg_mtl_compile_library(desc->vs.source); + fs_lib = _sg_mtl_compile_library(desc->fs.source); +- if (nil == vs_lib || nil == fs_lib) { +- return SG_RESOURCESTATE_FAILED; ++ if ((nil == vs_lib) || (nil == fs_lib)) { ++ goto failed; + } + vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; + fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; + } + else { +- return SG_RESOURCESTATE_FAILED; ++ goto failed; + } + if (nil == vs_func) { +- SOKOL_LOG("vertex shader entry function not found\n"); +- return SG_RESOURCESTATE_FAILED; ++ _SG_ERROR(METAL_VERTEX_SHADER_ENTRY_NOT_FOUND); ++ goto failed; + } + if (nil == fs_func) { +- SOKOL_LOG("fragment shader entry function not found\n"); +- return SG_RESOURCESTATE_FAILED; ++ _SG_ERROR(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND); ++ goto failed; + } + /* it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index */ + shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib = _sg_mtl_add_resource(vs_lib); ++ _SG_OBJC_RELEASE(vs_lib); + shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib = _sg_mtl_add_resource(fs_lib); ++ _SG_OBJC_RELEASE(fs_lib); + shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func = _sg_mtl_add_resource(vs_func); ++ _SG_OBJC_RELEASE(vs_func); + shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func = _sg_mtl_add_resource(fs_func); ++ _SG_OBJC_RELEASE(fs_func); + return SG_RESOURCESTATE_VALID; ++failed: ++ if (vs_lib != nil) { ++ _SG_OBJC_RELEASE(vs_lib); ++ } ++ if (fs_lib != nil) { ++ _SG_OBJC_RELEASE(fs_lib); ++ } ++ if (vs_func != nil) { ++ _SG_OBJC_RELEASE(vs_func); ++ } ++ if (fs_func != nil) { ++ _SG_OBJC_RELEASE(fs_func); ++ } ++ return SG_RESOURCESTATE_FAILED; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + /* it is valid to call _sg_mtl_release_resource with a 'null resource' */ + _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); +@@ -10003,6 +11454,10 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s + vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_desc->stride; + vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_desc->step_func); + vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_desc->step_rate; ++ if (SG_VERTEXSTEP_PER_INSTANCE == l_desc->step_func) { ++ // NOTE: not actually used in _sg_mtl_draw() ++ pip->cmn.use_instanced_draw = true; ++ } + } + } + +@@ -10013,7 +11468,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s + rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); + SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func); +- rp_desc.sampleCount = (NSUInteger)desc->sample_count; ++ rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count; + rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; + rp_desc.alphaToOneEnabled = NO; + rp_desc.rasterizationEnabled = YES; +@@ -10047,7 +11502,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s + _SG_OBJC_RELEASE(rp_desc); + if (nil == mtl_rps) { + SOKOL_ASSERT(err); +- SOKOL_LOG([err.localizedDescription UTF8String]); ++ _SG_ERROR(METAL_CREATE_RPS_FAILED); ++ _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]); + return SG_RESOURCESTATE_FAILED; + } + +@@ -10073,14 +11529,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s + ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask; + ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask; + } ++ // FIXME: can this actually fail? + id mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc]; + _SG_OBJC_RELEASE(ds_desc); + pip->mtl.rps = _sg_mtl_add_resource(mtl_rps); ++ _SG_OBJC_RELEASE(mtl_rps); + pip->mtl.dss = _sg_mtl_add_resource(mtl_dss); ++ _SG_OBJC_RELEASE(mtl_dss); + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_mtl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + /* it's valid to call release resource with a 'null resource' */ + _sg_mtl_release_resource(_sg.mtl.frame_index, pip->mtl.rps); +@@ -10116,7 +11575,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pass(_sg_pass_t* pass, _sg_image + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_mtl_destroy_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_mtl_discard_pass(_sg_pass_t* pass) { + SOKOL_ASSERT(pass); + _SOKOL_UNUSED(pass); + } +@@ -10144,11 +11603,30 @@ _SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* a + _sg.mtl.cur_height = h; + _sg_mtl_clear_state_cache(); + +- /* if this is the first pass in the frame, create a command buffer */ ++ /* ++ if this is the first pass in the frame, create command buffers ++ ++ NOTE: we're creating two command buffers here, one with unretained references ++ for storing the regular commands, and one with retained references for ++ storing the presentDrawable call (this needs to hold on the drawable until ++ presentation has happened - and the easiest way to do this is to let the ++ command buffer manage the lifetime of the drawable). ++ ++ Also see: https://github.com/floooh/sokol/issues/762 ++ */ + if (nil == _sg.mtl.cmd_buffer) { ++ SOKOL_ASSERT(nil == _sg.mtl.present_cmd_buffer); + /* 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.present_cmd_buffer = [_sg.mtl.cmd_queue commandBuffer]; ++ [_sg.mtl.cmd_buffer enqueue]; ++ [_sg.mtl.present_cmd_buffer enqueue]; ++ [_sg.mtl.present_cmd_buffer addCompletedHandler:^(id cmd_buf) { ++ // NOTE: this code is called on a different thread! ++ _SOKOL_UNUSED(cmd_buf); ++ dispatch_semaphore_signal(_sg.mtl.sem); ++ }]; + } + + /* if this is first pass in frame, get uniform buffer base pointer */ +@@ -10235,10 +11713,12 @@ _SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* a + SOKOL_ASSERT(ds_att_img->mtl.depth_tex != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.depthAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); + pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.action); ++ pass_desc.depthAttachment.storeAction = MTLStoreActionStore; + pass_desc.depthAttachment.clearDepth = action->depth.value; + if (_sg_is_depth_stencil_format(ds_att_img->cmn.pixel_format)) { + pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); + pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.action); ++ pass_desc.stencilAttachment.storeAction = MTLStoreActionStore; + pass_desc.stencilAttachment.clearStencil = action->stencil.value; + } + } +@@ -10282,10 +11762,7 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { + SOKOL_ASSERT(_sg.mtl.drawable_cb || _sg.mtl.drawable_userdata_cb); + 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 ++ SOKOL_ASSERT(nil != _sg.mtl.present_cmd_buffer); + + /* present, commit and signal semaphore when done */ + id cur_drawable = nil; +@@ -10295,12 +11772,11 @@ _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.present_cmd_buffer presentDrawable:cur_drawable]; ++ } + [_sg.mtl.cmd_buffer commit]; ++ [_sg.mtl.present_cmd_buffer commit]; + + /* garbage-collect resources pending for release */ + _sg_mtl_garbage_collect(_sg.mtl.frame_index); +@@ -10314,6 +11790,7 @@ _SOKOL_PRIVATE void _sg_mtl_commit(void) { + _sg.mtl.cur_ub_base_ptr = 0; + /* NOTE: MTLCommandBuffer is autoreleased */ + _sg.mtl.cmd_buffer = nil; ++ _sg.mtl.present_cmd_buffer = nil; + } + + _SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +@@ -10375,7 +11852,7 @@ _SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) { + [_sg.mtl.cmd_encoder setCullMode:pip->mtl.cull_mode]; + [_sg.mtl.cmd_encoder setFrontFacingWinding:pip->mtl.winding]; + [_sg.mtl.cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; +- [_sg.mtl.cmd_encoder setDepthBias:pip->cmn.depth_bias slopeScale:pip->cmn.depth_bias_slope_scale clamp:pip->cmn.depth_bias_clamp]; ++ [_sg.mtl.cmd_encoder setDepthBias:pip->cmn.depth.bias slopeScale:pip->cmn.depth.bias_slope_scale clamp:pip->cmn.depth.bias_clamp]; + SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)]; + SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX); +@@ -10467,7 +11944,7 @@ _SOKOL_PRIVATE void _sg_mtl_apply_uniforms(sg_shader_stage stage_index, int ub_i + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->shader->slot.id == _sg.mtl.state_cache.cur_pipeline->cmn.shader_id.id); + SOKOL_ASSERT(ub_index < _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); +- SOKOL_ASSERT(data->size <= _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); ++ SOKOL_ASSERT(data->size == _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); + + /* copy to global uniform buffer, record offset into cmd encoder, and advance offset */ + uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset]; +@@ -10550,7 +12027,13 @@ _SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* + _sg_mtl_copy_image_data(img, mtl_tex, data); + } + +-/*== WEBGPU BACKEND IMPLEMENTATION ===========================================*/ ++// ██ ██ ███████ ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ █ ██ █████ ██████ ██ ███ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ███ ███ ███████ ██████ ██████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>webgpu backend + #elif defined(SOKOL_WGPU) + + _SOKOL_PRIVATE WGPUBufferUsageFlags _sg_wgpu_buffer_usage(sg_buffer_type t, sg_usage u) { +@@ -10680,6 +12163,8 @@ _SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) { + case SG_VERTEXFORMAT_SHORT4: return WGPUVertexFormat_Short4; + case SG_VERTEXFORMAT_SHORT4N: return WGPUVertexFormat_Short4Norm; + case SG_VERTEXFORMAT_USHORT4N: return WGPUVertexFormat_UShort4Norm; ++ case SG_VERTEXFORMAT_HALF2: return WGPUVertexFormat_Half2; ++ case SG_VERTEXFORMAT_HALF3: return WGPUVertexFormat_Half4; + /* FIXME! UINT10_N2 */ + case SG_VERTEXFORMAT_UINT10_N2: + default: +@@ -10768,6 +12253,8 @@ _SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) { + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: ++ case SG_PIXELFORMAT_SRGB8A8: ++ case SG_PIXELFORMAT_RGB9E5: + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +@@ -10915,6 +12402,7 @@ _SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); ++ /* FIXME: missing SG_PIXELFORMAT_RG11B10F */ + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); +@@ -10966,14 +12454,14 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { + _sg.wgpu.ub.num_bytes = desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; + + WGPUBufferDescriptor ub_desc; +- memset(&ub_desc, 0, sizeof(ub_desc)); ++ _sg_clear(&ub_desc, sizeof(ub_desc)); + ub_desc.size = _sg.wgpu.ub.num_bytes; + ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst; + _sg.wgpu.ub.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc); + SOKOL_ASSERT(_sg.wgpu.ub.buf); + + WGPUBindGroupLayoutBinding ub_bglb_desc[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; +- memset(ub_bglb_desc, 0, sizeof(ub_bglb_desc)); ++ _sg_clear(ub_bglb_desc, sizeof(ub_bglb_desc)); + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + WGPUShaderStage vis = (stage_index == SG_SHADERSTAGE_VS) ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +@@ -10986,14 +12474,14 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { + } + + WGPUBindGroupLayoutDescriptor ub_bgl_desc; +- memset(&ub_bgl_desc, 0, sizeof(ub_bgl_desc)); ++ _sg_clear(&ub_bgl_desc, sizeof(ub_bgl_desc)); + ub_bgl_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; + ub_bgl_desc.bindings = &ub_bglb_desc[0][0]; + _sg.wgpu.ub.bindgroup_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &ub_bgl_desc); + SOKOL_ASSERT(_sg.wgpu.ub.bindgroup_layout); + + WGPUBindGroupBinding ub_bgb[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; +- memset(ub_bgb, 0, sizeof(ub_bgb)); ++ _sg_clear(ub_bgb, sizeof(ub_bgb)); + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + int bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; +@@ -11004,7 +12492,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { + } + } + WGPUBindGroupDescriptor bg_desc; +- memset(&bg_desc, 0, sizeof(bg_desc)); ++ _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = _sg.wgpu.ub.bindgroup_layout; + bg_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; + bg_desc.bindings = &ub_bgb[0][0]; +@@ -11040,7 +12528,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_mapped_callback(WGPUBufferMapAsyncStatus sta + } + /* FIXME: better handling for this */ + if (WGPUBufferMapAsyncStatus_Success != status) { +- SOKOL_LOG("Mapping uniform buffer failed!\n"); ++ _SG_ERROR(WGPU_MAP_UNIFORM_BUFFER_FAILED); + SOKOL_ASSERT(false); + } + SOKOL_ASSERT(data && (data_len == _sg.wgpu.ub.num_bytes)); +@@ -11060,7 +12548,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_next_frame(bool first_frame) { + + /* rewind per-frame offsets */ + _sg.wgpu.ub.offset = 0; +- memset(&_sg.wgpu.ub.bind_offsets, 0, sizeof(_sg.wgpu.ub.bind_offsets)); ++ _sg_clear(&_sg.wgpu.ub.bind_offsets, sizeof(_sg.wgpu.ub.bind_offsets)); + + /* check if a mapped staging buffer is available, otherwise create one */ + for (int i = 0; i < _sg.wgpu.ub.stage.num; i++) { +@@ -11076,7 +12564,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_next_frame(bool first_frame) { + const int cur = _sg.wgpu.ub.stage.cur; + + WGPUBufferDescriptor desc; +- memset(&desc, 0, sizeof(desc)); ++ _sg_clear(&desc, sizeof(desc)); + desc.size = _sg.wgpu.ub.num_bytes; + desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; + WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); +@@ -11128,13 +12616,13 @@ _SOKOL_PRIVATE uint32_t _sg_wgpu_copy_image_data(WGPUBuffer stg_buf, uint8_t* st + const uint32_t num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; + const sg_pixel_format fmt = img->cmn.pixel_format; + WGPUBufferCopyView src_view; +- memset(&src_view, 0, sizeof(src_view)); ++ _sg_clear(&src_view, sizeof(src_view)); + src_view.buffer = stg_buf; + WGPUTextureCopyView dst_view; +- memset(&dst_view, 0, sizeof(dst_view)); ++ _sg_clear(&dst_view, sizeof(dst_view)); + dst_view.texture = img->wgpu.tex; + WGPUExtent3D extent; +- memset(&extent, 0, sizeof(extent)); ++ _sg_clear(&extent, sizeof(extent)); + + for (uint32_t face_index = 0; face_index < num_faces; face_index++) { + for (uint32_t mip_index = 0; mip_index < (uint32_t)img->cmn.num_mipmaps; mip_index++) { +@@ -11274,7 +12762,7 @@ _SOKOL_PRIVATE void _sg_wgpu_staging_next_frame(bool first_frame) { + const int cur = _sg.wgpu.staging.cur; + + WGPUBufferDescriptor desc; +- memset(&desc, 0, sizeof(desc)); ++ _sg_clear(&desc, sizeof(desc)); + desc.size = _sg.wgpu.staging.num_bytes; + desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; + WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); +@@ -11299,7 +12787,7 @@ _SOKOL_PRIVATE uint32_t _sg_wgpu_staging_copy_to_buffer(WGPUBuffer dst_buf, uint + SOKOL_ASSERT(data_num_bytes > 0); + uint32_t copy_num_bytes = _sg_roundup(data_num_bytes, 4); + if ((_sg.wgpu.staging.offset + copy_num_bytes) >= _sg.wgpu.staging.num_bytes) { +- SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_buffer())!\n"); ++ _SG_ERROR(WGPU_STAGING_BUFFER_FULL_COPY_TO_BUFFER); + return false; + } + const int cur = _sg.wgpu.staging.cur; +@@ -11318,7 +12806,7 @@ _SOKOL_PRIVATE bool _sg_wgpu_staging_copy_to_texture(_sg_image_t* img, const sg_ + SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); + uint32_t num_bytes = _sg_wgpu_image_data_buffer_size(img); + if ((_sg.wgpu.staging.offset + num_bytes) >= _sg.wgpu.staging.num_bytes) { +- SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_texture)!\n"); ++ _SG_ERROR(WGPU_STAGING_BUFFER_FULL_COPY_TO_TEXTURE); + return false; + } + const int cur = _sg.wgpu.staging.cur; +@@ -11367,7 +12855,7 @@ _SOKOL_PRIVATE WGPUSampler _sg_wgpu_create_sampler(const sg_image_desc* img_desc + /* create a new WGPU sampler and add to sampler cache */ + /* FIXME: anisotropic filtering not supported? */ + WGPUSamplerDescriptor smp_desc; +- memset(&smp_desc, 0, sizeof(smp_desc)); ++ _sg_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.addressModeU = _sg_wgpu_sampler_addrmode(img_desc->wrap_u); + smp_desc.addressModeV = _sg_wgpu_sampler_addrmode(img_desc->wrap_v); + smp_desc.addressModeW = _sg_wgpu_sampler_addrmode(img_desc->wrap_w); +@@ -11417,11 +12905,11 @@ _SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { + + /* create an empty bind group for shader stages without bound images */ + WGPUBindGroupLayoutDescriptor bgl_desc; +- memset(&bgl_desc, 0, sizeof(bgl_desc)); ++ _sg_clear(&bgl_desc, sizeof(bgl_desc)); + WGPUBindGroupLayout empty_bgl = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + SOKOL_ASSERT(empty_bgl); + WGPUBindGroupDescriptor bg_desc; +- memset(&bg_desc, 0, sizeof(bg_desc)); ++ _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = empty_bgl; + _sg.wgpu.empty_bind_group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(_sg.wgpu.empty_bind_group); +@@ -11429,7 +12917,7 @@ _SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { + + /* create initial per-frame command encoders */ + WGPUCommandEncoderDescriptor cmd_enc_desc; +- memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); ++ _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); + _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); + _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); +@@ -11456,7 +12944,7 @@ _SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { + } + + _SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) { +- SOKOL_LOG("_sg_wgpu_reset_state_cache: FIXME\n"); ++ _SG_WARN(WGPU_RESET_STATE_CACHE_FIXME); + } + + _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) { +@@ -11465,14 +12953,14 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) { + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_context(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + _SOKOL_UNUSED(ctx); + } + + _SOKOL_PRIVATE void _sg_wgpu_activate_context(_sg_context_t* ctx) { + (void)ctx; +- SOKOL_LOG("_sg_wgpu_activate_context: FIXME\n"); ++ _SG_WARN(WGPU_ACTIVATE_CONTEXT_FIXME); + } + + _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +@@ -11485,7 +12973,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const + } + else { + WGPUBufferDescriptor wgpu_buf_desc; +- memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); ++ _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); + wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(buf->cmn.type, buf->cmn.usage); + wgpu_buf_desc.size = buf->cmn.size; + if (SG_USAGE_IMMUTABLE == buf->cmn.usage) { +@@ -11503,7 +12991,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + WGPUBuffer wgpu_buf = buf->wgpu.buf; + if (0 != wgpu_buf) { +@@ -11543,7 +13031,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s + const bool injected = (0 != desc->wgpu_texture); + const bool is_msaa = desc->sample_count > 1; + WGPUTextureDescriptor wgpu_tex_desc; +- memset(&wgpu_tex_desc, 0, sizeof(wgpu_tex_desc)); ++ _sg_clear(&wgpu_tex_desc, sizeof(wgpu_tex_desc)); + _sg_wgpu_init_texdesc_common(&wgpu_tex_desc, desc); + if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { + SOKOL_ASSERT(img->cmn.render_target); +@@ -11576,7 +13064,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s + /* copy content into texture via a throw-away staging buffer */ + if (desc->usage == SG_USAGE_IMMUTABLE && !desc->render_target) { + WGPUBufferDescriptor wgpu_buf_desc; +- memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); ++ _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); + wgpu_buf_desc.size = _sg_wgpu_image_data_buffer_size(img); + wgpu_buf_desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_CopyDst; + WGPUCreateBufferMappedResult map = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &wgpu_buf_desc); +@@ -11591,7 +13079,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s + + /* create texture view object */ + WGPUTextureViewDescriptor wgpu_view_desc; +- memset(&wgpu_view_desc, 0, sizeof(wgpu_view_desc)); ++ _sg_clear(&wgpu_view_desc, sizeof(wgpu_view_desc)); + wgpu_view_desc.dimension = _sg_wgpu_tex_viewdim(desc->type); + img->wgpu.tex_view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_view_desc); + +@@ -11617,7 +13105,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const s + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + if (img->wgpu.tex) { + wgpuTextureRelease(img->wgpu.tex); +@@ -11677,7 +13165,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const + + _sg_strcpy(&wgpu_stage->entry, stage_desc->entry); + WGPUShaderModuleDescriptor wgpu_shdmod_desc; +- memset(&wgpu_shdmod_desc, 0, sizeof(wgpu_shdmod_desc)); ++ _sg_clear(&wgpu_shdmod_desc, sizeof(wgpu_shdmod_desc)); + wgpu_shdmod_desc.codeSize = stage_desc->bytecode.size >> 2; + wgpu_shdmod_desc.code = (const uint32_t*) stage_desc->bytecode.ptr; + wgpu_stage->module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); +@@ -11692,7 +13180,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const + num_imgs = _SG_WGPU_MAX_SHADERSTAGE_IMAGES; + } + WGPUBindGroupLayoutBinding bglb_desc[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; +- memset(bglb_desc, 0, sizeof(bglb_desc)); ++ _sg_clear(bglb_desc, sizeof(bglb_desc)); + for (int img_index = 0; img_index < num_imgs; img_index++) { + /* texture- and sampler-bindings */ + WGPUBindGroupLayoutBinding* tex_desc = &bglb_desc[img_index*2 + 0]; +@@ -11709,7 +13197,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const + smp_desc->type = WGPUBindingType_Sampler; + } + WGPUBindGroupLayoutDescriptor img_bgl_desc; +- memset(&img_bgl_desc, 0, sizeof(img_bgl_desc)); ++ _sg_clear(&img_bgl_desc, sizeof(img_bgl_desc)); + img_bgl_desc.bindingCount = num_imgs * 2; + img_bgl_desc.bindings = &bglb_desc[0]; + wgpu_stage->bind_group_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &img_bgl_desc); +@@ -11718,7 +13206,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const + return success ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; +@@ -11748,15 +13236,15 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + shd->wgpu.stage[SG_SHADERSTAGE_FS].bind_group_layout + }; + WGPUPipelineLayoutDescriptor pl_desc; +- memset(&pl_desc, 0, sizeof(pl_desc)); ++ _sg_clear(&pl_desc, sizeof(pl_desc)); + pl_desc.bindGroupLayoutCount = 3; + pl_desc.bindGroupLayouts = &pip_bgl[0]; + WGPUPipelineLayout pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &pl_desc); + + WGPUVertexBufferLayoutDescriptor vb_desc[SG_MAX_SHADERSTAGE_BUFFERS]; +- memset(&vb_desc, 0, sizeof(vb_desc)); ++ _sg_clear(&vb_desc, sizeof(vb_desc)); + WGPUVertexAttributeDescriptor va_desc[SG_MAX_SHADERSTAGE_BUFFERS][SG_MAX_VERTEX_ATTRIBUTES]; +- memset(&va_desc, 0, sizeof(va_desc)); ++ _sg_clear(&va_desc, sizeof(va_desc)); + int vb_idx = 0; + for (; vb_idx < SG_MAX_SHADERSTAGE_BUFFERS; vb_idx++) { + const sg_buffer_layout_desc* src_vb_desc = &desc->layout.buffers[vb_idx]; +@@ -11786,13 +13274,13 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + vb_desc[vb_idx].attributes = &va_desc[vb_idx][0]; + } + WGPUVertexStateDescriptor vx_state_desc; +- memset(&vx_state_desc, 0, sizeof(vx_state_desc)); ++ _sg_clear(&vx_state_desc, sizeof(vx_state_desc)); + vx_state_desc.indexFormat = _sg_wgpu_indexformat(desc->index_type); + vx_state_desc.vertexBufferCount = vb_idx; + vx_state_desc.vertexBuffers = vb_desc; + + WGPURasterizationStateDescriptor rs_desc; +- memset(&rs_desc, 0, sizeof(rs_desc)); ++ _sg_clear(&rs_desc, sizeof(rs_desc)); + rs_desc.frontFace = _sg_wgpu_frontface(desc->face_winding); + rs_desc.cullMode = _sg_wgpu_cullmode(desc->cull_mode); + rs_desc.depthBias = (int32_t) desc->depth.bias; +@@ -11800,7 +13288,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + rs_desc.depthBiasSlopeScale = desc->depth.bias_slope_scale; + + WGPUDepthStencilStateDescriptor ds_desc; +- memset(&ds_desc, 0, sizeof(ds_desc)); ++ _sg_clear(&ds_desc, sizeof(ds_desc)); + ds_desc.format = _sg_wgpu_textureformat(desc->depth.pixel_format); + ds_desc.depthWriteEnabled = desc->depth.write_enabled; + ds_desc.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare); +@@ -11816,12 +13304,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + ds_desc.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op); + + WGPUProgrammableStageDescriptor fs_desc; +- memset(&fs_desc, 0, sizeof(fs_desc)); ++ _sg_clear(&fs_desc, sizeof(fs_desc)); + fs_desc.module = shd->wgpu.stage[SG_SHADERSTAGE_FS].module; + fs_desc.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; + + WGPUColorStateDescriptor cs_desc[SG_MAX_COLOR_ATTACHMENTS]; +- memset(cs_desc, 0, sizeof(cs_desc)); ++ _sg_clear(cs_desc, sizeof(cs_desc)); + for (uint32_t i = 0; i < desc->color_count; i++) { + SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); + cs_desc[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format); +@@ -11835,7 +13323,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + } + + WGPURenderPipelineDescriptor pip_desc; +- memset(&pip_desc, 0, sizeof(pip_desc)); ++ _sg_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.layout = pip_layout; + pip_desc.vertexStage.module = shd->wgpu.stage[SG_SHADERSTAGE_VS].module; + pip_desc.vertexStage.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; +@@ -11857,8 +13345,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _ + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_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; +@@ -11884,7 +13376,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag + /* create a render-texture-view to render into the right sub-surface */ + const bool is_msaa = img->cmn.sample_count > 1; + WGPUTextureViewDescriptor view_desc; +- memset(&view_desc, 0, sizeof(view_desc)); ++ _sg_clear(&view_desc, sizeof(view_desc)); + view_desc.baseMipLevel = is_msaa ? 0 : att_desc->mip_level; + view_desc.mipLevelCount = 1; + view_desc.baseArrayLayer = is_msaa ? 0 : att_desc->slice; +@@ -11915,7 +13407,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag + SOKOL_ASSERT(0 == att_desc->mip_level); + SOKOL_ASSERT(0 == att_desc->slice); + WGPUTextureViewDescriptor view_desc; +- memset(&view_desc, 0, sizeof(view_desc)); ++ _sg_clear(&view_desc, sizeof(view_desc)); + WGPUTexture wgpu_tex = ds_img->wgpu.tex; + SOKOL_ASSERT(wgpu_tex); + pass->wgpu.ds_att.render_tex_view = wgpuTextureCreateView(wgpu_tex, &view_desc); +@@ -11924,7 +13416,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_imag + return SG_RESOURCESTATE_VALID; + } + +-_SOKOL_PRIVATE void _sg_wgpu_destroy_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_wgpu_discard_pass(_sg_pass_t* pass) { + SOKOL_ASSERT(pass); + for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { + if (pass->wgpu.color_atts[i].render_tex_view) { +@@ -11971,9 +13463,9 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* + SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); + if (pass) { + WGPURenderPassDescriptor wgpu_pass_desc; +- memset(&wgpu_pass_desc, 0, sizeof(wgpu_pass_desc)); ++ _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); + WGPURenderPassColorAttachmentDescriptor wgpu_color_att_desc[SG_MAX_COLOR_ATTACHMENTS]; +- memset(&wgpu_color_att_desc, 0, sizeof(wgpu_color_att_desc)); ++ _sg_clear(&wgpu_color_att_desc, sizeof(wgpu_color_att_desc)); + SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_VALID); + for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { + const _sg_wgpu_attachment_t* wgpu_att = &pass->wgpu.color_atts[i]; +@@ -11992,7 +13484,7 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* + wgpu_pass_desc.colorAttachments = &wgpu_color_att_desc[0]; + if (pass->wgpu.ds_att.image) { + WGPURenderPassDepthStencilAttachmentDescriptor wgpu_ds_att_desc; +- memset(&wgpu_ds_att_desc, 0, sizeof(wgpu_ds_att_desc)); ++ _sg_clear(&wgpu_ds_att_desc, sizeof(wgpu_ds_att_desc)); + wgpu_ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); + wgpu_ds_att_desc.clearDepth = action->depth.value; + wgpu_ds_att_desc.stencilLoadOp = _sg_wgpu_load_op(action->stencil.action); +@@ -12009,9 +13501,9 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* + WGPUTextureView wgpu_depth_stencil_view = _sg.wgpu.depth_stencil_view_cb ? _sg.wgpu.depth_stencil_view_cb() : _sg.wgpu.depth_stencil_view_userdata_cb(_sg.wgpu.user_data); + + WGPURenderPassDescriptor pass_desc; +- memset(&pass_desc, 0, sizeof(pass_desc)); ++ _sg_clear(&pass_desc, sizeof(pass_desc)); + WGPURenderPassColorAttachmentDescriptor color_att_desc; +- memset(&color_att_desc, 0, sizeof(color_att_desc)); ++ _sg_clear(&color_att_desc, sizeof(color_att_desc)); + color_att_desc.loadOp = _sg_wgpu_load_op(action->colors[0].action); + color_att_desc.clearColor.r = action->colors[0].value.r; + color_att_desc.clearColor.g = action->colors[0].value.g; +@@ -12022,7 +13514,7 @@ _SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* + pass_desc.colorAttachmentCount = 1; + pass_desc.colorAttachments = &color_att_desc; + WGPURenderPassDepthStencilAttachmentDescriptor ds_att_desc; +- memset(&ds_att_desc, 0, sizeof(ds_att_desc)); ++ _sg_clear(&ds_att_desc, sizeof(ds_att_desc)); + ds_att_desc.attachment = wgpu_depth_stencil_view; + SOKOL_ASSERT(0 != ds_att_desc.attachment); + ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); +@@ -12064,7 +13556,7 @@ _SOKOL_PRIVATE void _sg_wgpu_commit(void) { + WGPUCommandBuffer cmd_bufs[2]; + + WGPUCommandBufferDescriptor cmd_buf_desc; +- memset(&cmd_buf_desc, 0, sizeof(cmd_buf_desc)); ++ _sg_clear(&cmd_buf_desc, sizeof(cmd_buf_desc)); + cmd_bufs[0] = wgpuCommandEncoderFinish(_sg.wgpu.staging_cmd_enc, &cmd_buf_desc); + SOKOL_ASSERT(cmd_bufs[0]); + wgpuCommandEncoderRelease(_sg.wgpu.staging_cmd_enc); +@@ -12082,7 +13574,7 @@ _SOKOL_PRIVATE void _sg_wgpu_commit(void) { + + /* create a new render- and staging-command-encoders for next frame */ + WGPUCommandEncoderDescriptor cmd_enc_desc; +- memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); ++ _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); + _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + +@@ -12143,7 +13635,7 @@ _SOKOL_PRIVATE WGPUBindGroup _sg_wgpu_create_images_bindgroup(WGPUBindGroupLayou + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(num_imgs <= _SG_WGPU_MAX_SHADERSTAGE_IMAGES); + WGPUBindGroupBinding img_bgb[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; +- memset(&img_bgb, 0, sizeof(img_bgb)); ++ _sg_clear(&img_bgb, sizeof(img_bgb)); + for (int img_index = 0; img_index < num_imgs; img_index++) { + WGPUBindGroupBinding* tex_bdg = &img_bgb[img_index*2 + 0]; + WGPUBindGroupBinding* smp_bdg = &img_bgb[img_index*2 + 1]; +@@ -12153,7 +13645,7 @@ _SOKOL_PRIVATE WGPUBindGroup _sg_wgpu_create_images_bindgroup(WGPUBindGroupLayou + smp_bdg->sampler = imgs[img_index]->wgpu.sampler; + } + WGPUBindGroupDescriptor bg_desc; +- memset(&bg_desc, 0, sizeof(bg_desc)); ++ _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = bgl; + bg_desc.bindingCount = 2 * num_imgs; + bg_desc.bindings = &img_bgb[0]; +@@ -12269,7 +13761,13 @@ _SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* + } + #endif + +-/*== BACKEND API WRAPPERS ====================================================*/ ++// ██████ ███████ ███ ██ ███████ ██████ ██ ██████ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ ++// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ++// ██ ███ █████ ██ ██ ██ █████ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ ███████ ██ ████ ███████ ██ ██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ ++// ++// >>generic backend + static inline void _sg_setup_backend(const sg_desc* desc) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_setup_backend(desc); +@@ -12350,17 +13848,17 @@ static inline sg_resource_state _sg_create_context(_sg_context_t* ctx) { + #endif + } + +-static inline void _sg_destroy_context(_sg_context_t* ctx) { ++static inline void _sg_discard_context(_sg_context_t* ctx) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_context(ctx); ++ _sg_gl_discard_context(ctx); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_context(ctx); ++ _sg_mtl_discard_context(ctx); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_context(ctx); ++ _sg_d3d11_discard_context(ctx); + #elif defined(SOKOL_WGPU) +- _sg_wgpu_destroy_context(ctx); ++ _sg_wgpu_discard_context(ctx); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_context(ctx); ++ _sg_dummy_discard_context(ctx); + #else + #error("INVALID BACKEND"); + #endif +@@ -12382,17 +13880,17 @@ static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_bu + #endif + } + +-static inline void _sg_destroy_buffer(_sg_buffer_t* buf) { ++static inline void _sg_discard_buffer(_sg_buffer_t* buf) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_buffer(buf); ++ _sg_gl_discard_buffer(buf); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_buffer(buf); ++ _sg_mtl_discard_buffer(buf); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_buffer(buf); ++ _sg_d3d11_discard_buffer(buf); + #elif defined(SOKOL_WGPU) +- _sg_wgpu_destroy_buffer(buf); ++ _sg_wgpu_discard_buffer(buf); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_buffer(buf); ++ _sg_dummy_discard_buffer(buf); + #else + #error("INVALID BACKEND"); + #endif +@@ -12414,17 +13912,17 @@ static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_imag + #endif + } + +-static inline void _sg_destroy_image(_sg_image_t* img) { ++static inline void _sg_discard_image(_sg_image_t* img) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_image(img); ++ _sg_gl_discard_image(img); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_image(img); ++ _sg_mtl_discard_image(img); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_image(img); ++ _sg_d3d11_discard_image(img); + #elif defined(SOKOL_WGPU) +- _sg_wgpu_destroy_image(img); ++ _sg_wgpu_discard_image(img); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_image(img); ++ _sg_dummy_discard_image(img); + #else + #error("INVALID BACKEND"); + #endif +@@ -12446,17 +13944,17 @@ static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_sh + #endif + } + +-static inline void _sg_destroy_shader(_sg_shader_t* shd) { ++static inline void _sg_discard_shader(_sg_shader_t* shd) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_shader(shd); ++ _sg_gl_discard_shader(shd); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_shader(shd); ++ _sg_mtl_discard_shader(shd); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_shader(shd); ++ _sg_d3d11_discard_shader(shd); + #elif defined(SOKOL_WGPU) +- _sg_wgpu_destroy_shader(shd); ++ _sg_wgpu_discard_shader(shd); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_shader(shd); ++ _sg_dummy_discard_shader(shd); + #else + #error("INVALID BACKEND"); + #endif +@@ -12478,17 +13976,17 @@ static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, _sg_sha + #endif + } + +-static inline void _sg_destroy_pipeline(_sg_pipeline_t* pip) { ++static inline void _sg_discard_pipeline(_sg_pipeline_t* pip) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_pipeline(pip); ++ _sg_gl_discard_pipeline(pip); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_pipeline(pip); ++ _sg_mtl_discard_pipeline(pip); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_pipeline(pip); ++ _sg_d3d11_discard_pipeline(pip); + #elif defined(SOKOL_WGPU) +- _sg_wgpu_destroy_pipeline(pip); ++ _sg_wgpu_discard_pipeline(pip); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_pipeline(pip); ++ _sg_dummy_discard_pipeline(pip); + #else + #error("INVALID BACKEND"); + #endif +@@ -12510,17 +14008,17 @@ static inline sg_resource_state _sg_create_pass(_sg_pass_t* pass, _sg_image_t** + #endif + } + +-static inline void _sg_destroy_pass(_sg_pass_t* pass) { ++static inline void _sg_discard_pass(_sg_pass_t* pass) { + #if defined(_SOKOL_ANY_GL) +- _sg_gl_destroy_pass(pass); ++ _sg_gl_discard_pass(pass); + #elif defined(SOKOL_METAL) +- _sg_mtl_destroy_pass(pass); ++ _sg_mtl_discard_pass(pass); + #elif defined(SOKOL_D3D11) +- _sg_d3d11_destroy_pass(pass); ++ _sg_d3d11_discard_pass(pass); + #elif defined(SOKOL_WGPU) +- return _sg_wgpu_destroy_pass(pass); ++ return _sg_wgpu_discard_pass(pass); + #elif defined(SOKOL_DUMMY_BACKEND) +- _sg_dummy_destroy_pass(pass); ++ _sg_dummy_discard_pass(pass); + #else + #error("INVALID BACKEND"); + #endif +@@ -12756,8 +14254,13 @@ static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) + #endif + } + +-/*== RESOURCE POOLS ==========================================================*/ +- ++// ██████ ██████ ██████ ██ ++// ██ ██ ██ ██ ██ ██ ██ ++// ██████ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ++// ██ ██████ ██████ ███████ ++// ++// >>pool + _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */ +@@ -12765,12 +14268,9 @@ _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { + pool->queue_top = 0; + /* generation counters indexable by pool slot index, slot 0 is reserved */ + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; +- pool->gen_ctrs = (uint32_t*) SOKOL_MALLOC(gen_ctrs_size); +- SOKOL_ASSERT(pool->gen_ctrs); +- memset(pool->gen_ctrs, 0, gen_ctrs_size); ++ pool->gen_ctrs = (uint32_t*)_sg_malloc_clear(gen_ctrs_size); + /* it's not a bug to only reserve 'num' here */ +- pool->free_queue = (int*) SOKOL_MALLOC(sizeof(int) * (size_t)num); +- SOKOL_ASSERT(pool->free_queue); ++ pool->free_queue = (int*) _sg_malloc_clear(sizeof(int) * (size_t)num); + /* never allocate the zero-th pool item since the invalid id is 0 */ + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; +@@ -12780,10 +14280,10 @@ _SOKOL_PRIVATE void _sg_init_pool(_sg_pool_t* pool, int num) { + _SOKOL_PRIVATE void _sg_discard_pool(_sg_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); +- SOKOL_FREE(pool->free_queue); ++ _sg_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); +- SOKOL_FREE(pool->gen_ctrs); ++ _sg_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +@@ -12820,53 +14320,53 @@ _SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) { + + _SOKOL_PRIVATE void _sg_reset_slot(_sg_slot_t* slot) { + SOKOL_ASSERT(slot); +- memset(slot, 0, sizeof(_sg_slot_t)); ++ _sg_clear(slot, sizeof(_sg_slot_t)); + } + +-_SOKOL_PRIVATE void _sg_reset_buffer(_sg_buffer_t* buf) { ++_SOKOL_PRIVATE void _sg_reset_buffer_to_alloc_state(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _sg_slot_t slot = buf->slot; +- memset(buf, 0, sizeof(_sg_buffer_t)); ++ _sg_clear(buf, sizeof(_sg_buffer_t)); + buf->slot = slot; + buf->slot.state = SG_RESOURCESTATE_ALLOC; + } + +-_SOKOL_PRIVATE void _sg_reset_image(_sg_image_t* img) { ++_SOKOL_PRIVATE void _sg_reset_image_to_alloc_state(_sg_image_t* img) { + SOKOL_ASSERT(img); + _sg_slot_t slot = img->slot; +- memset(img, 0, sizeof(_sg_image_t)); ++ _sg_clear(img, sizeof(_sg_image_t)); + img->slot = slot; + img->slot.state = SG_RESOURCESTATE_ALLOC; + } + +-_SOKOL_PRIVATE void _sg_reset_shader(_sg_shader_t* shd) { ++_SOKOL_PRIVATE void _sg_reset_shader_to_alloc_state(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_slot_t slot = shd->slot; +- memset(shd, 0, sizeof(_sg_shader_t)); ++ _sg_clear(shd, sizeof(_sg_shader_t)); + shd->slot = slot; + shd->slot.state = SG_RESOURCESTATE_ALLOC; + } + +-_SOKOL_PRIVATE void _sg_reset_pipeline(_sg_pipeline_t* pip) { ++_SOKOL_PRIVATE void _sg_reset_pipeline_to_alloc_state(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_slot_t slot = pip->slot; +- memset(pip, 0, sizeof(_sg_pipeline_t)); ++ _sg_clear(pip, sizeof(_sg_pipeline_t)); + pip->slot = slot; + pip->slot.state = SG_RESOURCESTATE_ALLOC; + } + +-_SOKOL_PRIVATE void _sg_reset_pass(_sg_pass_t* pass) { ++_SOKOL_PRIVATE void _sg_reset_pass_to_alloc_state(_sg_pass_t* pass) { + SOKOL_ASSERT(pass); + _sg_slot_t slot = pass->slot; +- memset(pass, 0, sizeof(_sg_pass_t)); ++ _sg_clear(pass, sizeof(_sg_pass_t)); + pass->slot = slot; + pass->slot.state = SG_RESOURCESTATE_ALLOC; + } + +-_SOKOL_PRIVATE void _sg_reset_context(_sg_context_t* ctx) { ++_SOKOL_PRIVATE void _sg_reset_context_to_alloc_state(_sg_context_t* ctx) { + SOKOL_ASSERT(ctx); + _sg_slot_t slot = ctx->slot; +- memset(ctx, 0, sizeof(_sg_context_t)); ++ _sg_clear(ctx, sizeof(_sg_context_t)); + ctx->slot = slot; + ctx->slot.state = SG_RESOURCESTATE_ALLOC; + } +@@ -12878,54 +14378,42 @@ _SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) { + SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->buffer_pool, desc->buffer_pool_size); + size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size; +- p->buffers = (_sg_buffer_t*) SOKOL_MALLOC(buffer_pool_byte_size); +- SOKOL_ASSERT(p->buffers); +- memset(p->buffers, 0, buffer_pool_byte_size); ++ p->buffers = (_sg_buffer_t*) _sg_malloc_clear(buffer_pool_byte_size); + + SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->image_pool, desc->image_pool_size); + size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size; +- p->images = (_sg_image_t*) SOKOL_MALLOC(image_pool_byte_size); +- SOKOL_ASSERT(p->images); +- memset(p->images, 0, image_pool_byte_size); ++ p->images = (_sg_image_t*) _sg_malloc_clear(image_pool_byte_size); + + SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->shader_pool, desc->shader_pool_size); + size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size; +- p->shaders = (_sg_shader_t*) SOKOL_MALLOC(shader_pool_byte_size); +- SOKOL_ASSERT(p->shaders); +- memset(p->shaders, 0, shader_pool_byte_size); ++ p->shaders = (_sg_shader_t*) _sg_malloc_clear(shader_pool_byte_size); + + SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->pipeline_pool, desc->pipeline_pool_size); + size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size; +- p->pipelines = (_sg_pipeline_t*) SOKOL_MALLOC(pipeline_pool_byte_size); +- SOKOL_ASSERT(p->pipelines); +- memset(p->pipelines, 0, pipeline_pool_byte_size); ++ p->pipelines = (_sg_pipeline_t*) _sg_malloc_clear(pipeline_pool_byte_size); + + SOKOL_ASSERT((desc->pass_pool_size > 0) && (desc->pass_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->pass_pool, desc->pass_pool_size); + size_t pass_pool_byte_size = sizeof(_sg_pass_t) * (size_t)p->pass_pool.size; +- p->passes = (_sg_pass_t*) SOKOL_MALLOC(pass_pool_byte_size); +- SOKOL_ASSERT(p->passes); +- memset(p->passes, 0, pass_pool_byte_size); ++ p->passes = (_sg_pass_t*) _sg_malloc_clear(pass_pool_byte_size); + + SOKOL_ASSERT((desc->context_pool_size > 0) && (desc->context_pool_size < _SG_MAX_POOL_SIZE)); + _sg_init_pool(&p->context_pool, desc->context_pool_size); + size_t context_pool_byte_size = sizeof(_sg_context_t) * (size_t)p->context_pool.size; +- p->contexts = (_sg_context_t*) SOKOL_MALLOC(context_pool_byte_size); +- SOKOL_ASSERT(p->contexts); +- memset(p->contexts, 0, context_pool_byte_size); ++ p->contexts = (_sg_context_t*) _sg_malloc_clear(context_pool_byte_size); + } + + _SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { + SOKOL_ASSERT(p); +- SOKOL_FREE(p->contexts); p->contexts = 0; +- SOKOL_FREE(p->passes); p->passes = 0; +- SOKOL_FREE(p->pipelines); p->pipelines = 0; +- SOKOL_FREE(p->shaders); p->shaders = 0; +- SOKOL_FREE(p->images); p->images = 0; +- SOKOL_FREE(p->buffers); p->buffers = 0; ++ _sg_free(p->contexts); p->contexts = 0; ++ _sg_free(p->passes); p->passes = 0; ++ _sg_free(p->pipelines); p->pipelines = 0; ++ _sg_free(p->shaders); p->shaders = 0; ++ _sg_free(p->images); p->images = 0; ++ _sg_free(p->buffers); p->buffers = 0; + _sg_discard_pool(&p->context_pool); + _sg_discard_pool(&p->pass_pool); + _sg_discard_pool(&p->pipeline_pool); +@@ -13070,7 +14558,7 @@ _SOKOL_PRIVATE _sg_context_t* _sg_lookup_context(const _sg_pools_t* p, uint32_t + return 0; + } + +-_SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { ++_SOKOL_PRIVATE void _sg_discard_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + /* this is a bit dumb since it loops over all pool slots to + find the occupied slots, on the other hand it is only ever + executed at shutdown +@@ -13082,7 +14570,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + if (p->buffers[i].slot.ctx_id == ctx_id) { + sg_resource_state state = p->buffers[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +- _sg_destroy_buffer(&p->buffers[i]); ++ _sg_discard_buffer(&p->buffers[i]); + } + } + } +@@ -13090,7 +14578,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + if (p->images[i].slot.ctx_id == ctx_id) { + sg_resource_state state = p->images[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +- _sg_destroy_image(&p->images[i]); ++ _sg_discard_image(&p->images[i]); + } + } + } +@@ -13098,7 +14586,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + if (p->shaders[i].slot.ctx_id == ctx_id) { + sg_resource_state state = p->shaders[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +- _sg_destroy_shader(&p->shaders[i]); ++ _sg_discard_shader(&p->shaders[i]); + } + } + } +@@ -13106,7 +14594,7 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + if (p->pipelines[i].slot.ctx_id == ctx_id) { + sg_resource_state state = p->pipelines[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +- _sg_destroy_pipeline(&p->pipelines[i]); ++ _sg_discard_pipeline(&p->pipelines[i]); + } + } + } +@@ -13114,160 +14602,32 @@ _SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { + if (p->passes[i].slot.ctx_id == ctx_id) { + sg_resource_state state = p->passes[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +- _sg_destroy_pass(&p->passes[i]); ++ _sg_discard_pass(&p->passes[i]); + } + } + } + } + +-/*== VALIDATION LAYER ========================================================*/ +-#if defined(SOKOL_DEBUG) +-/* return a human readable string for an _sg_validate_error */ +-_SOKOL_PRIVATE const char* _sg_validate_string(_sg_validate_error_t err) { +- switch (err) { +- /* buffer creation validation errors */ +- case _SG_VALIDATE_BUFFERDESC_CANARY: return "sg_buffer_desc not initialized"; +- case _SG_VALIDATE_BUFFERDESC_SIZE: return "sg_buffer_desc.size cannot be 0"; +- case _SG_VALIDATE_BUFFERDESC_DATA: return "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)"; +- case _SG_VALIDATE_BUFFERDESC_DATA_SIZE: return "immutable buffer data size differs from buffer size"; +- case _SG_VALIDATE_BUFFERDESC_NO_DATA: return "dynamic/stream usage buffers cannot be initialized with data"; +- +- /* image creation validation errros */ +- case _SG_VALIDATE_IMAGEDESC_CANARY: return "sg_image_desc not initialized"; +- case _SG_VALIDATE_IMAGEDESC_WIDTH: return "sg_image_desc.width must be > 0"; +- case _SG_VALIDATE_IMAGEDESC_HEIGHT: return "sg_image_desc.height must be > 0"; +- case _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT: return "invalid pixel format for render-target image"; +- case _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT: return "invalid pixel format for non-render-target image"; +- case _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT: return "non-render-target images cannot be multisampled"; +- case _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT: return "MSAA not supported for this pixel format"; +- case _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE: return "render target images must be SG_USAGE_IMMUTABLE"; +- case _SG_VALIDATE_IMAGEDESC_RT_NO_DATA: return "render target images cannot be initialized with data"; +- case _SG_VALIDATE_IMAGEDESC_DATA: return "missing or invalid data for immutable image"; +- case _SG_VALIDATE_IMAGEDESC_NO_DATA: return "dynamic/stream usage images cannot be initialized with data"; +- +- /* shader creation */ +- case _SG_VALIDATE_SHADERDESC_CANARY: return "sg_shader_desc not initialized"; +- case _SG_VALIDATE_SHADERDESC_SOURCE: return "shader source code required"; +- case _SG_VALIDATE_SHADERDESC_BYTECODE: return "shader byte code required"; +- case _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE: return "shader source or byte code required"; +- case _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE: return "shader byte code length (in bytes) required"; +- case _SG_VALIDATE_SHADERDESC_NO_CONT_UBS: return "shader uniform blocks must occupy continuous slots"; +- case _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS: return "uniform block members must occupy continuous slots"; +- case _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS: return "GL backend requires uniform block member declarations"; +- case _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME: return "uniform block member name missing"; +- case _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH: return "size of uniform block members doesn't match uniform block size"; +- case _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS: return "shader images must occupy continuous slots"; +- case _SG_VALIDATE_SHADERDESC_IMG_NAME: return "GL backend requires uniform block member names"; +- case _SG_VALIDATE_SHADERDESC_ATTR_NAMES: return "GLES2 backend requires vertex attribute names"; +- case _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS: return "D3D11 backend requires vertex attribute semantics"; +- case _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG: return "vertex attribute name/semantic string too long (max len 16)"; +- +- /* pipeline creation */ +- case _SG_VALIDATE_PIPELINEDESC_CANARY: return "sg_pipeline_desc not initialized"; +- case _SG_VALIDATE_PIPELINEDESC_SHADER: return "sg_pipeline_desc.shader missing or invalid"; +- case _SG_VALIDATE_PIPELINEDESC_NO_ATTRS: return "sg_pipeline_desc.layout.attrs is empty or not continuous"; +- case _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4: return "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4"; +- case _SG_VALIDATE_PIPELINEDESC_ATTR_NAME: return "GLES2/WebGL missing vertex attribute name in shader"; +- case _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS: return "D3D11 missing vertex attribute semantics in shader"; +- +- /* pass creation */ +- case _SG_VALIDATE_PASSDESC_CANARY: return "sg_pass_desc not initialized"; +- case _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS: return "sg_pass_desc.color_attachments[0] must be valid"; +- case _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS: return "color attachments must occupy continuous slots"; +- case _SG_VALIDATE_PASSDESC_IMAGE: return "pass attachment image is not valid"; +- case _SG_VALIDATE_PASSDESC_MIPLEVEL: return "pass attachment mip level is bigger than image has mipmaps"; +- case _SG_VALIDATE_PASSDESC_FACE: return "pass attachment image is cubemap, but face index is too big"; +- case _SG_VALIDATE_PASSDESC_LAYER: return "pass attachment image is array texture, but layer index is too big"; +- case _SG_VALIDATE_PASSDESC_SLICE: return "pass attachment image is 3d texture, but slice value is too big"; +- case _SG_VALIDATE_PASSDESC_IMAGE_NO_RT: return "pass attachment image must be render targets"; +- case _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT: return "pass color-attachment images must have a renderable pixel format"; +- case _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT: return "pass depth-attachment image must have depth pixel format"; +- case _SG_VALIDATE_PASSDESC_IMAGE_SIZES: return "all pass attachments must have the same size"; +- case _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS: return "all pass attachments must have the same sample count"; +- +- /* sg_begin_pass */ +- case _SG_VALIDATE_BEGINPASS_PASS: return "sg_begin_pass: pass must be valid"; +- case _SG_VALIDATE_BEGINPASS_IMAGE: return "sg_begin_pass: one or more attachment images are not valid"; +- +- /* sg_apply_pipeline */ +- case _SG_VALIDATE_APIP_PIPELINE_VALID_ID: return "sg_apply_pipeline: invalid pipeline id provided"; +- case _SG_VALIDATE_APIP_PIPELINE_EXISTS: return "sg_apply_pipeline: pipeline object no longer alive"; +- case _SG_VALIDATE_APIP_PIPELINE_VALID: return "sg_apply_pipeline: pipeline object not in valid state"; +- case _SG_VALIDATE_APIP_SHADER_EXISTS: return "sg_apply_pipeline: shader object no longer alive"; +- case _SG_VALIDATE_APIP_SHADER_VALID: return "sg_apply_pipeline: shader object not in valid state"; +- case _SG_VALIDATE_APIP_ATT_COUNT: return "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments"; +- case _SG_VALIDATE_APIP_COLOR_FORMAT: return "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format"; +- case _SG_VALIDATE_APIP_DEPTH_FORMAT: return "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format"; +- case _SG_VALIDATE_APIP_SAMPLE_COUNT: return "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count"; +- +- /* sg_apply_bindings */ +- case _SG_VALIDATE_ABND_PIPELINE: return "sg_apply_bindings: must be called after sg_apply_pipeline"; +- case _SG_VALIDATE_ABND_PIPELINE_EXISTS: return "sg_apply_bindings: currently applied pipeline object no longer alive"; +- case _SG_VALIDATE_ABND_PIPELINE_VALID: return "sg_apply_bindings: currently applied pipeline object not in valid state"; +- case _SG_VALIDATE_ABND_VBS: return "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts"; +- case _SG_VALIDATE_ABND_VB_EXISTS: return "sg_apply_bindings: vertex buffer no longer alive"; +- case _SG_VALIDATE_ABND_VB_TYPE: return "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER"; +- case _SG_VALIDATE_ABND_VB_OVERFLOW: return "sg_apply_bindings: buffer in vertex buffer slot is overflown"; +- case _SG_VALIDATE_ABND_NO_IB: return "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided"; +- case _SG_VALIDATE_ABND_IB: return "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided"; +- case _SG_VALIDATE_ABND_IB_EXISTS: return "sg_apply_bindings: index buffer no longer alive"; +- case _SG_VALIDATE_ABND_IB_TYPE: return "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER"; +- case _SG_VALIDATE_ABND_IB_OVERFLOW: return "sg_apply_bindings: buffer in index buffer slot is overflown"; +- case _SG_VALIDATE_ABND_VS_IMGS: return "sg_apply_bindings: vertex shader image count doesn't match sg_shader_desc"; +- case _SG_VALIDATE_ABND_VS_IMG_EXISTS: return "sg_apply_bindings: vertex shader image no longer alive"; +- case _SG_VALIDATE_ABND_VS_IMG_TYPES: return "sg_apply_bindings: one or more vertex shader image types don't match sg_shader_desc"; +- case _SG_VALIDATE_ABND_FS_IMGS: return "sg_apply_bindings: fragment shader image count doesn't match sg_shader_desc"; +- case _SG_VALIDATE_ABND_FS_IMG_EXISTS: return "sg_apply_bindings: fragment shader image no longer alive"; +- case _SG_VALIDATE_ABND_FS_IMG_TYPES: return "sg_apply_bindings: one or more fragment shader image types don't match sg_shader_desc"; +- +- /* sg_apply_uniforms */ +- case _SG_VALIDATE_AUB_NO_PIPELINE: return "sg_apply_uniforms: must be called after sg_apply_pipeline()"; +- case _SG_VALIDATE_AUB_NO_UB_AT_SLOT: return "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot"; +- case _SG_VALIDATE_AUB_SIZE: return "sg_apply_uniforms: data size exceeds declared uniform block size"; +- +- /* sg_update_buffer */ +- case _SG_VALIDATE_UPDATEBUF_USAGE: return "sg_update_buffer: cannot update immutable buffer"; +- case _SG_VALIDATE_UPDATEBUF_SIZE: return "sg_update_buffer: update size is bigger than buffer size"; +- case _SG_VALIDATE_UPDATEBUF_ONCE: return "sg_update_buffer: only one update allowed per buffer and frame"; +- case _SG_VALIDATE_UPDATEBUF_APPEND: return "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame"; +- +- /* sg_append_buffer */ +- case _SG_VALIDATE_APPENDBUF_USAGE: return "sg_append_buffer: cannot append to immutable buffer"; +- case _SG_VALIDATE_APPENDBUF_SIZE: return "sg_append_buffer: overall appended size is bigger than buffer size"; +- case _SG_VALIDATE_APPENDBUF_UPDATE: return "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame"; +- +- /* sg_update_image */ +- case _SG_VALIDATE_UPDIMG_USAGE: return "sg_update_image: cannot update immutable image"; +- case _SG_VALIDATE_UPDIMG_NOTENOUGHDATA: return "sg_update_image: not enough subimage data provided"; +- case _SG_VALIDATE_UPDIMG_SIZE: return "sg_update_image: provided subimage data size too big"; +- case _SG_VALIDATE_UPDIMG_COMPRESSED: return "sg_update_image: cannot update images with compressed format"; +- case _SG_VALIDATE_UPDIMG_ONCE: return "sg_update_image: only one update allowed per image and frame"; +- +- default: return "unknown validation error"; +- } +-} +-#endif /* defined(SOKOL_DEBUG) */ +- +-/*-- validation checks -------------------------------------------------------*/ ++// ██ ██ █████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ++// ██ ██ ███████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ████ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ██ ████ ++// ++// >>validation + #if defined(SOKOL_DEBUG) + _SOKOL_PRIVATE void _sg_validate_begin(void) { +- _sg.validate_error = _SG_VALIDATE_SUCCESS; +-} +- +-_SOKOL_PRIVATE void _sg_validate(bool cond, _sg_validate_error_t err) { +- if (!cond) { +- _sg.validate_error = err; +- SOKOL_LOG(_sg_validate_string(err)); +- } ++ _sg.validate_error = SG_LOGITEM_OK; + } + + _SOKOL_PRIVATE bool _sg_validate_end(void) { +- if (_sg.validate_error != _SG_VALIDATE_SUCCESS) { ++ if (_sg.validate_error != SG_LOGITEM_OK) { + #if !defined(SOKOL_VALIDATE_NON_FATAL) +- SOKOL_LOG("^^^^ VALIDATION FAILED, TERMINATING ^^^^"); +- SOKOL_ASSERT(false); ++ _SG_PANIC(VALIDATION_FAILED); ++ return false; ++ #else ++ return false; + #endif +- return false; + } + else { + return true; +@@ -13280,23 +14640,51 @@ _SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) { + _SOKOL_UNUSED(desc); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(desc); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); +- SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); +- SOKOL_VALIDATE(desc->size > 0, _SG_VALIDATE_BUFFERDESC_SIZE); ++ _sg_validate_begin(); ++ _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_BUFFERDESC_CANARY); ++ _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_BUFFERDESC_CANARY); ++ _SG_VALIDATE(desc->size > 0, VALIDATE_BUFFERDESC_SIZE); + bool injected = (0 != desc->gl_buffers[0]) || + (0 != desc->mtl_buffers[0]) || + (0 != desc->d3d11_buffer) || + (0 != desc->wgpu_buffer); + if (!injected && (desc->usage == SG_USAGE_IMMUTABLE)) { +- SOKOL_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), _SG_VALIDATE_BUFFERDESC_DATA); +- SOKOL_VALIDATE(desc->size == desc->data.size, _SG_VALIDATE_BUFFERDESC_DATA_SIZE); ++ _SG_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), VALIDATE_BUFFERDESC_DATA); ++ _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_DATA_SIZE); + } + else { +- SOKOL_VALIDATE(0 == desc->data.ptr, _SG_VALIDATE_BUFFERDESC_NO_DATA); ++ _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_NO_DATA); ++ } ++ return _sg_validate_end(); ++ #endif ++} ++ ++_SOKOL_PRIVATE void _sg_validate_image_data(const sg_image_data* data, sg_pixel_format fmt, int width, int height, int num_faces, int num_mips, int num_slices) { ++ #if !defined(SOKOL_DEBUG) ++ _SOKOL_UNUSED(data); ++ _SOKOL_UNUSED(fmt); ++ _SOKOL_UNUSED(width); ++ _SOKOL_UNUSED(height); ++ _SOKOL_UNUSED(num_faces); ++ _SOKOL_UNUSED(num_mips); ++ _SOKOL_UNUSED(num_slices); ++ #else ++ for (int face_index = 0; face_index < num_faces; face_index++) { ++ for (int mip_index = 0; mip_index < num_mips; mip_index++) { ++ const bool has_data = data->subimage[face_index][mip_index].ptr != 0; ++ const bool has_size = data->subimage[face_index][mip_index].size > 0; ++ _SG_VALIDATE(has_data && has_size, VALIDATE_IMAGEDATA_NODATA); ++ const int mip_width = _sg_max(width >> mip_index, 1); ++ const int mip_height = _sg_max(height >> mip_index, 1); ++ const int bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1); ++ const int expected_size = bytes_per_slice * num_slices; ++ _SG_VALIDATE(expected_size == (int)data->subimage[face_index][mip_index].size, VALIDATE_IMAGEDATA_DATA_SIZE); ++ } + } +- return SOKOL_VALIDATE_END(); + #endif + } + +@@ -13305,12 +14693,15 @@ _SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { + _SOKOL_UNUSED(desc); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(desc); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); +- SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); +- SOKOL_VALIDATE(desc->width > 0, _SG_VALIDATE_IMAGEDESC_WIDTH); +- SOKOL_VALIDATE(desc->height > 0, _SG_VALIDATE_IMAGEDESC_HEIGHT); ++ _sg_validate_begin(); ++ _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_IMAGEDESC_CANARY); ++ _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_IMAGEDESC_CANARY); ++ _SG_VALIDATE(desc->width > 0, VALIDATE_IMAGEDESC_WIDTH); ++ _SG_VALIDATE(desc->height > 0, VALIDATE_IMAGEDESC_HEIGHT); + const sg_pixel_format fmt = desc->pixel_format; + const sg_usage usage = desc->usage; + const bool injected = (0 != desc->gl_textures[0]) || +@@ -13319,47 +14710,56 @@ _SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { + (0 != desc->wgpu_texture); + if (desc->render_target) { + SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); +- SOKOL_VALIDATE(_sg.formats[fmt].render, _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT); ++ _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_RT_PIXELFORMAT); + /* on GLES2, sample count for render targets is completely ignored */ + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + if (!_sg.gl.gles2) { + #endif + if (desc->sample_count > 1) { +- SOKOL_VALIDATE(_sg.features.msaa_render_targets && _sg.formats[fmt].msaa, _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); ++ _SG_VALIDATE(_sg.features.msaa_render_targets && _sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); + } + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + } + #endif +- SOKOL_VALIDATE(usage == SG_USAGE_IMMUTABLE, _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE); +- SOKOL_VALIDATE(desc->data.subimage[0][0].ptr==0, _SG_VALIDATE_IMAGEDESC_RT_NO_DATA); ++ _SG_VALIDATE(usage == SG_USAGE_IMMUTABLE, VALIDATE_IMAGEDESC_RT_IMMUTABLE); ++ _SG_VALIDATE(desc->data.subimage[0][0].ptr==0, VALIDATE_IMAGEDESC_RT_NO_DATA); + } + else { +- SOKOL_VALIDATE(desc->sample_count <= 1, _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); ++ _SG_VALIDATE(desc->sample_count <= 1, VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); + const bool valid_nonrt_fmt = !_sg_is_valid_rendertarget_depth_format(fmt); +- SOKOL_VALIDATE(valid_nonrt_fmt, _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); +- /* FIXME: should use the same "expected size" computation as in _sg_validate_update_image() here */ +- if (!injected && (usage == SG_USAGE_IMMUTABLE)) { +- const int num_faces = desc->type == SG_IMAGETYPE_CUBE ? 6:1; +- const int num_mips = desc->num_mipmaps; +- for (int face_index = 0; face_index < num_faces; face_index++) { +- for (int mip_index = 0; mip_index < num_mips; mip_index++) { +- const bool has_data = desc->data.subimage[face_index][mip_index].ptr != 0; +- const bool has_size = desc->data.subimage[face_index][mip_index].size > 0; +- SOKOL_VALIDATE(has_data && has_size, _SG_VALIDATE_IMAGEDESC_DATA); +- } +- } ++ _SG_VALIDATE(valid_nonrt_fmt, VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); ++ const bool is_compressed = _sg_is_compressed_pixel_format(desc->pixel_format); ++ const bool is_immutable = (usage == SG_USAGE_IMMUTABLE); ++ if (is_compressed) { ++ _SG_VALIDATE(is_immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); ++ } ++ if (!injected && is_immutable) { ++ // image desc must have valid data ++ _sg_validate_image_data(&desc->data, ++ desc->pixel_format, ++ desc->width, ++ desc->height, ++ (desc->type == SG_IMAGETYPE_CUBE) ? 6 : 1, ++ desc->num_mipmaps, ++ desc->num_slices); + } + else { ++ // image desc must not have data + for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + const bool no_data = 0 == desc->data.subimage[face_index][mip_index].ptr; + const bool no_size = 0 == desc->data.subimage[face_index][mip_index].size; +- SOKOL_VALIDATE(no_data && no_size, _SG_VALIDATE_IMAGEDESC_NO_DATA); ++ if (injected) { ++ _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA); ++ } ++ if (!is_immutable) { ++ _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA); ++ } + } + } + } + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13368,44 +14768,47 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + _SOKOL_UNUSED(desc); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(desc); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); +- SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); ++ _sg_validate_begin(); ++ _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY); ++ _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY); + #if defined(SOKOL_GLES2) +- SOKOL_VALIDATE(0 != desc->attrs[0].name, _SG_VALIDATE_SHADERDESC_ATTR_NAMES); ++ _SG_VALIDATE(0 != desc->attrs[0].name, VALIDATE_SHADERDESC_ATTR_NAMES); + #elif defined(SOKOL_D3D11) +- SOKOL_VALIDATE(0 != desc->attrs[0].sem_name, _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS); ++ _SG_VALIDATE(0 != desc->attrs[0].sem_name, VALIDATE_SHADERDESC_ATTR_SEMANTICS); + #endif + #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + /* on GL, must provide shader source code */ +- SOKOL_VALIDATE(0 != desc->vs.source, _SG_VALIDATE_SHADERDESC_SOURCE); +- SOKOL_VALIDATE(0 != desc->fs.source, _SG_VALIDATE_SHADERDESC_SOURCE); ++ _SG_VALIDATE(0 != desc->vs.source, VALIDATE_SHADERDESC_SOURCE); ++ _SG_VALIDATE(0 != desc->fs.source, VALIDATE_SHADERDESC_SOURCE); + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + /* on Metal or D3D11, must provide shader source code or byte code */ +- SOKOL_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); +- SOKOL_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); ++ _SG_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); ++ _SG_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); + #elif defined(SOKOL_WGPU) + /* on WGPU byte code must be provided */ +- SOKOL_VALIDATE((0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); +- SOKOL_VALIDATE((0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); ++ _SG_VALIDATE((0 != desc->vs.bytecode.ptr), VALIDATE_SHADERDESC_BYTECODE); ++ _SG_VALIDATE((0 != desc->fs.bytecode.ptr), VALIDATE_SHADERDESC_BYTECODE); + #else + /* Dummy Backend, don't require source or bytecode */ + #endif + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (desc->attrs[i].name) { +- SOKOL_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); ++ _SG_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + if (desc->attrs[i].sem_name) { +- SOKOL_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); ++ _SG_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + } + /* if shader byte code, the size must also be provided */ + if (0 != desc->vs.bytecode.ptr) { +- SOKOL_VALIDATE(desc->vs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); ++ _SG_VALIDATE(desc->vs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->fs.bytecode.ptr) { +- SOKOL_VALIDATE(desc->fs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); ++ _SG_VALIDATE(desc->fs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == 0)? &desc->vs : &desc->fs; +@@ -13413,28 +14816,41 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; + if (ub_desc->size > 0) { +- SOKOL_VALIDATE(uniform_blocks_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UBS); ++ _SG_VALIDATE(uniform_blocks_continuous, VALIDATE_SHADERDESC_NO_CONT_UBS); ++ #if defined(_SOKOL_ANY_GL) + bool uniforms_continuous = true; +- int uniform_offset = 0; ++ uint32_t uniform_offset = 0; + int num_uniforms = 0; + for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { + const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; + if (u_desc->type != SG_UNIFORMTYPE_INVALID) { +- SOKOL_VALIDATE(uniforms_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); ++ _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); + #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +- SOKOL_VALIDATE(0 != u_desc->name, _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME); ++ _SG_VALIDATE(0 != u_desc->name, VALIDATE_SHADERDESC_UB_MEMBER_NAME); + #endif + const int array_count = u_desc->array_count; +- uniform_offset += _sg_uniform_size(u_desc->type, array_count); ++ _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UB_ARRAY_COUNT); ++ const uint32_t u_align = _sg_uniform_alignment(u_desc->type, array_count, ub_desc->layout); ++ const uint32_t u_size = _sg_uniform_size(u_desc->type, array_count, ub_desc->layout); ++ uniform_offset = _sg_align_u32(uniform_offset, u_align); ++ uniform_offset += u_size; + num_uniforms++; ++ // with std140, arrays are only allowed for FLOAT4, INT4, MAT4 ++ if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { ++ if (array_count > 1) { ++ _SG_VALIDATE((u_desc->type == SG_UNIFORMTYPE_FLOAT4) || (u_desc->type == SG_UNIFORMTYPE_INT4) || (u_desc->type == SG_UNIFORMTYPE_MAT4), VALIDATE_SHADERDESC_UB_STD140_ARRAY_TYPE); ++ } ++ } + } + else { + uniforms_continuous = false; + } + } +- #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +- SOKOL_VALIDATE((size_t)uniform_offset == ub_desc->size, _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH); +- SOKOL_VALIDATE(num_uniforms > 0, _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS); ++ if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { ++ uniform_offset = _sg_align_u32(uniform_offset, 16); ++ } ++ _SG_VALIDATE((size_t)uniform_offset == ub_desc->size, VALIDATE_SHADERDESC_UB_SIZE_MISMATCH); ++ _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_NO_UB_MEMBERS); + #endif + } + else { +@@ -13445,9 +14861,9 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { + const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; + if (img_desc->image_type != _SG_IMAGETYPE_DEFAULT) { +- SOKOL_VALIDATE(images_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS); ++ _SG_VALIDATE(images_continuous, VALIDATE_SHADERDESC_NO_CONT_IMGS); + #if defined(SOKOL_GLES2) +- SOKOL_VALIDATE(0 != img_desc->name, _SG_VALIDATE_SHADERDESC_IMG_NAME); ++ _SG_VALIDATE(0 != img_desc->name, VALIDATE_SHADERDESC_IMG_NAME); + #endif + } + else { +@@ -13455,7 +14871,7 @@ _SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + } + } + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13464,23 +14880,26 @@ _SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { + _SOKOL_UNUSED(desc); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(desc); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); +- SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); +- SOKOL_VALIDATE(desc->shader.id != SG_INVALID_ID, _SG_VALIDATE_PIPELINEDESC_SHADER); ++ _sg_validate_begin(); ++ _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PIPELINEDESC_CANARY); ++ _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PIPELINEDESC_CANARY); ++ _SG_VALIDATE(desc->shader.id != SG_INVALID_ID, VALIDATE_PIPELINEDESC_SHADER); + for (int buf_index = 0; buf_index < SG_MAX_SHADERSTAGE_BUFFERS; buf_index++) { + const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[buf_index]; + if (l_desc->stride == 0) { + continue; + } +- SOKOL_VALIDATE((l_desc->stride & 3) == 0, _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); ++ _SG_VALIDATE((l_desc->stride & 3) == 0, VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); + } +- SOKOL_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); ++ _SG_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, VALIDATE_PIPELINEDESC_NO_ATTRS); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); +- SOKOL_VALIDATE(0 != shd, _SG_VALIDATE_PIPELINEDESC_SHADER); ++ _SG_VALIDATE(0 != shd, VALIDATE_PIPELINEDESC_SHADER); + if (shd) { +- SOKOL_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PIPELINEDESC_SHADER); ++ _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PIPELINEDESC_SHADER); + bool attrs_cont = true; + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +@@ -13488,18 +14907,18 @@ _SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { + attrs_cont = false; + continue; + } +- SOKOL_VALIDATE(attrs_cont, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); ++ _SG_VALIDATE(attrs_cont, VALIDATE_PIPELINEDESC_NO_ATTRS); + SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); + #if defined(SOKOL_GLES2) + /* on GLES2, vertex attribute names must be provided */ +- SOKOL_VALIDATE(!_sg_strempty(&shd->gl.attrs[attr_index].name), _SG_VALIDATE_PIPELINEDESC_ATTR_NAME); ++ _SG_VALIDATE(!_sg_strempty(&shd->gl.attrs[attr_index].name), VALIDATE_PIPELINEDESC_ATTR_NAME); + #elif defined(SOKOL_D3D11) + /* on D3D11, semantic names (and semantic indices) must be provided */ +- SOKOL_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); ++ _SG_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); + #endif + } + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13508,68 +14927,71 @@ _SOKOL_PRIVATE bool _sg_validate_pass_desc(const sg_pass_desc* desc) { + _SOKOL_UNUSED(desc); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(desc); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); +- SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); ++ _sg_validate_begin(); ++ _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PASSDESC_CANARY); ++ _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PASSDESC_CANARY); + bool atts_cont = true; + int width = -1, height = -1, sample_count = -1; + for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { + const sg_pass_attachment_desc* att = &desc->color_attachments[att_index]; + if (att->image.id == SG_INVALID_ID) { +- SOKOL_VALIDATE(att_index > 0, _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS); ++ _SG_VALIDATE(att_index > 0, VALIDATE_PASSDESC_NO_COLOR_ATTS); + atts_cont = false; + continue; + } +- SOKOL_VALIDATE(atts_cont, _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS); ++ _SG_VALIDATE(atts_cont, VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); + SOKOL_ASSERT(img); +- SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); +- SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); ++ _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PASSDESC_IMAGE); ++ _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_PASSDESC_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { +- SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); ++ _SG_VALIDATE(att->slice < 6, VALIDATE_PASSDESC_FACE); + } + else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { +- SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); ++ _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_LAYER); + } + else if (img->cmn.type == SG_IMAGETYPE_3D) { +- SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); ++ _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_SLICE); + } +- SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); ++ _SG_VALIDATE(img->cmn.render_target, VALIDATE_PASSDESC_IMAGE_NO_RT); + if (att_index == 0) { + width = img->cmn.width >> att->mip_level; + height = img->cmn.height >> att->mip_level; + sample_count = img->cmn.sample_count; + } + else { +- SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +- SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +- SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); ++ _SG_VALIDATE(width == img->cmn.width >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); ++ _SG_VALIDATE(height == img->cmn.height >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); ++ _SG_VALIDATE(sample_count == img->cmn.sample_count, VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); + } +- SOKOL_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT); ++ _SG_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT); + } + if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { + const sg_pass_attachment_desc* att = &desc->depth_stencil_attachment; + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); + SOKOL_ASSERT(img); +- SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); +- SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); ++ _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PASSDESC_IMAGE); ++ _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_PASSDESC_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { +- SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); ++ _SG_VALIDATE(att->slice < 6, VALIDATE_PASSDESC_FACE); + } + else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { +- SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); ++ _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_LAYER); + } + else if (img->cmn.type == SG_IMAGETYPE_3D) { +- SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); ++ _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_PASSDESC_SLICE); + } +- SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); +- SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +- SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +- SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); +- SOKOL_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT); ++ _SG_VALIDATE(img->cmn.render_target, VALIDATE_PASSDESC_IMAGE_NO_RT); ++ _SG_VALIDATE(width == img->cmn.width >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); ++ _SG_VALIDATE(height == img->cmn.height >> att->mip_level, VALIDATE_PASSDESC_IMAGE_SIZES); ++ _SG_VALIDATE(sample_count == img->cmn.sample_count, VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); ++ _SG_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT); + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13578,24 +15000,27 @@ _SOKOL_PRIVATE bool _sg_validate_begin_pass(_sg_pass_t* pass) { + _SOKOL_UNUSED(pass); + return true; + #else +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(pass->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_PASS); ++ if (_sg.desc.disable_validation) { ++ return true; ++ } ++ _sg_validate_begin(); ++ _SG_VALIDATE(pass->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_PASS); + + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + const _sg_pass_attachment_t* att = &pass->cmn.color_atts[i]; + const _sg_image_t* img = _sg_pass_color_image(pass, i); + if (img) { +- SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); +- SOKOL_VALIDATE(img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); ++ _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_IMAGE); ++ _SG_VALIDATE(img->slot.id == att->image_id.id, VALIDATE_BEGINPASS_IMAGE); + } + } + const _sg_image_t* ds_img = _sg_pass_ds_image(pass); + if (ds_img) { + const _sg_pass_attachment_t* att = &pass->cmn.ds_att; +- SOKOL_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); +- SOKOL_VALIDATE(ds_img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); ++ _SG_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_IMAGE); ++ _SG_VALIDATE(ds_img->slot.id == att->image_id.id, VALIDATE_BEGINPASS_IMAGE); + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13604,45 +15029,48 @@ _SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) { + _SOKOL_UNUSED(pip_id); + return true; + #else +- SOKOL_VALIDATE_BEGIN(); ++ if (_sg.desc.disable_validation) { ++ return true; ++ } ++ _sg_validate_begin(); + /* the pipeline object must be alive and valid */ +- SOKOL_VALIDATE(pip_id.id != SG_INVALID_ID, _SG_VALIDATE_APIP_PIPELINE_VALID_ID); ++ _SG_VALIDATE(pip_id.id != SG_INVALID_ID, VALIDATE_APIP_PIPELINE_VALID_ID); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +- SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_APIP_PIPELINE_EXISTS); ++ _SG_VALIDATE(pip != 0, VALIDATE_APIP_PIPELINE_EXISTS); + if (!pip) { +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + } +- SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_PIPELINE_VALID); ++ _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_VALID); + /* the pipeline's shader must be alive and valid */ + SOKOL_ASSERT(pip->shader); +- SOKOL_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, _SG_VALIDATE_APIP_SHADER_EXISTS); +- SOKOL_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_SHADER_VALID); ++ _SG_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, VALIDATE_APIP_SHADER_EXISTS); ++ _SG_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_SHADER_VALID); + /* check that pipeline attributes match current pass attributes */ + const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, _sg.cur_pass.id); + if (pass) { + /* an offscreen pass */ +- SOKOL_VALIDATE(pip->cmn.color_attachment_count == pass->cmn.num_color_atts, _SG_VALIDATE_APIP_ATT_COUNT); +- for (int i = 0; i < pip->cmn.color_attachment_count; i++) { ++ _SG_VALIDATE(pip->cmn.color_count == pass->cmn.num_color_atts, VALIDATE_APIP_ATT_COUNT); ++ for (int i = 0; i < pip->cmn.color_count; i++) { + const _sg_image_t* att_img = _sg_pass_color_image(pass, i); +- SOKOL_VALIDATE(pip->cmn.color_formats[i] == att_img->cmn.pixel_format, _SG_VALIDATE_APIP_COLOR_FORMAT); +- SOKOL_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); ++ _SG_VALIDATE(pip->cmn.colors[i].pixel_format == att_img->cmn.pixel_format, VALIDATE_APIP_COLOR_FORMAT); ++ _SG_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } + const _sg_image_t* att_dsimg = _sg_pass_ds_image(pass); + if (att_dsimg) { +- SOKOL_VALIDATE(pip->cmn.depth_format == att_dsimg->cmn.pixel_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); ++ _SG_VALIDATE(pip->cmn.depth.pixel_format == att_dsimg->cmn.pixel_format, VALIDATE_APIP_DEPTH_FORMAT); + } + else { +- SOKOL_VALIDATE(pip->cmn.depth_format == SG_PIXELFORMAT_NONE, _SG_VALIDATE_APIP_DEPTH_FORMAT); ++ _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTH_FORMAT); + } + } + else { + /* default pass */ +- SOKOL_VALIDATE(pip->cmn.color_attachment_count == 1, _SG_VALIDATE_APIP_ATT_COUNT); +- SOKOL_VALIDATE(pip->cmn.color_formats[0] == _sg.desc.context.color_format, _SG_VALIDATE_APIP_COLOR_FORMAT); +- SOKOL_VALIDATE(pip->cmn.depth_format == _sg.desc.context.depth_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); +- SOKOL_VALIDATE(pip->cmn.sample_count == _sg.desc.context.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); ++ _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_ATT_COUNT); ++ _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.desc.context.color_format, VALIDATE_APIP_COLOR_FORMAT); ++ _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.desc.context.depth_format, VALIDATE_APIP_DEPTH_FORMAT); ++ _SG_VALIDATE(pip->cmn.sample_count == _sg.desc.context.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13651,52 +15079,55 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { + _SOKOL_UNUSED(bindings); + return true; + #else +- SOKOL_VALIDATE_BEGIN(); ++ if (_sg.desc.disable_validation) { ++ return true; ++ } ++ _sg_validate_begin(); + + /* a pipeline object must have been applied */ +- SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_ABND_PIPELINE); ++ _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_ABND_PIPELINE); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); +- SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_ABND_PIPELINE_EXISTS); ++ _SG_VALIDATE(pip != 0, VALIDATE_ABND_PIPELINE_EXISTS); + if (!pip) { +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + } +- SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_ABND_PIPELINE_VALID); ++ _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_VALID); + SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + + /* has expected vertex buffers, and vertex buffers still exist */ + for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { + if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { +- SOKOL_VALIDATE(pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); ++ _SG_VALIDATE(pip->cmn.vertex_layout_valid[i], VALIDATE_ABND_VBS); + /* buffers in vertex-buffer-slots must be of type SG_BUFFERTYPE_VERTEXBUFFER */ + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); +- SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_VB_EXISTS); ++ _SG_VALIDATE(buf != 0, VALIDATE_ABND_VB_EXISTS); + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { +- SOKOL_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_VB_TYPE); +- SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_VB_OVERFLOW); ++ _SG_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, VALIDATE_ABND_VB_TYPE); ++ _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VB_OVERFLOW); + } + } + else { + /* vertex buffer provided in a slot which has no vertex layout in pipeline */ +- SOKOL_VALIDATE(!pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); ++ _SG_VALIDATE(!pip->cmn.vertex_layout_valid[i], VALIDATE_ABND_VBS); + } + } + + /* index buffer expected or not, and index buffer still exists */ + if (pip->cmn.index_type == SG_INDEXTYPE_NONE) { + /* pipeline defines non-indexed rendering, but index buffer provided */ +- SOKOL_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, _SG_VALIDATE_ABND_IB); ++ _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_IB); + } + else { + /* pipeline defines indexed rendering, but no index buffer provided */ +- SOKOL_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, _SG_VALIDATE_ABND_NO_IB); ++ _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_NO_IB); + } + if (bindings->index_buffer.id != SG_INVALID_ID) { + /* buffer in index-buffer-slot must be of type SG_BUFFERTYPE_INDEXBUFFER */ + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); +- SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_IB_EXISTS); ++ _SG_VALIDATE(buf != 0, VALIDATE_ABND_IB_EXISTS); + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { +- SOKOL_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_IB_TYPE); +- SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_IB_OVERFLOW); ++ _SG_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, VALIDATE_ABND_IB_TYPE); ++ _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IB_OVERFLOW); + } + } + +@@ -13704,15 +15135,15 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS]; + if (bindings->vs_images[i].id != SG_INVALID_ID) { +- SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); ++ _SG_VALIDATE(i < stage->num_images, VALIDATE_ABND_VS_IMGS); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->vs_images[i].id); +- SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_VS_IMG_EXISTS); ++ _SG_VALIDATE(img != 0, VALIDATE_ABND_VS_IMG_EXISTS); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { +- SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_VS_IMG_TYPES); ++ _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_VS_IMG_TYPES); + } + } + else { +- SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); ++ _SG_VALIDATE(i >= stage->num_images, VALIDATE_ABND_VS_IMGS); + } + } + +@@ -13720,18 +15151,18 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS]; + if (bindings->fs_images[i].id != SG_INVALID_ID) { +- SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); ++ _SG_VALIDATE(i < stage->num_images, VALIDATE_ABND_FS_IMGS); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->fs_images[i].id); +- SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_FS_IMG_EXISTS); ++ _SG_VALIDATE(img != 0, VALIDATE_ABND_FS_IMG_EXISTS); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { +- SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_FS_IMG_TYPES); ++ _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_FS_IMG_TYPES); + } + } + else { +- SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); ++ _SG_VALIDATE(i >= stage->num_images, VALIDATE_ABND_FS_IMGS); + } + } +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13742,22 +15173,25 @@ _SOKOL_PRIVATE bool _sg_validate_apply_uniforms(sg_shader_stage stage_index, int + _SOKOL_UNUSED(data); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT((stage_index == SG_SHADERSTAGE_VS) || (stage_index == SG_SHADERSTAGE_FS)); + SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_AUB_NO_PIPELINE); ++ _sg_validate_begin(); ++ _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_AUB_NO_PIPELINE); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); + SOKOL_ASSERT(pip && (pip->slot.id == _sg.cur_pipeline.id)); + SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + + /* check that there is a uniform block at 'stage' and 'ub_index' */ + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index]; +- SOKOL_VALIDATE(ub_index < stage->num_uniform_blocks, _SG_VALIDATE_AUB_NO_UB_AT_SLOT); ++ _SG_VALIDATE(ub_index < stage->num_uniform_blocks, VALIDATE_AUB_NO_UB_AT_SLOT); + + /* check that the provided data size doesn't exceed the uniform block size */ +- SOKOL_VALIDATE(data->size <= stage->uniform_blocks[ub_index].size, _SG_VALIDATE_AUB_SIZE); ++ _SG_VALIDATE(data->size == stage->uniform_blocks[ub_index].size, VALIDATE_AUB_SIZE); + +- return SOKOL_VALIDATE_END(); ++ return _sg_validate_end(); + #endif + } + +@@ -13767,13 +15201,16 @@ _SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg + _SOKOL_UNUSED(data); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(buf && data && data->ptr); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDATEBUF_USAGE); +- SOKOL_VALIDATE(buf->cmn.size >= (int)data->size, _SG_VALIDATE_UPDATEBUF_SIZE); +- SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_ONCE); +- SOKOL_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_APPEND); +- return SOKOL_VALIDATE_END(); ++ _sg_validate_begin(); ++ _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDATEBUF_USAGE); ++ _SG_VALIDATE(buf->cmn.size >= (int)data->size, VALIDATE_UPDATEBUF_SIZE); ++ _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_ONCE); ++ _SG_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_APPEND); ++ return _sg_validate_end(); + #endif + } + +@@ -13783,12 +15220,15 @@ _SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg + _SOKOL_UNUSED(data); + return true; + #else ++ if (_sg.desc.disable_validation) { ++ return true; ++ } + SOKOL_ASSERT(buf && data && data->ptr); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_APPENDBUF_USAGE); +- SOKOL_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), _SG_VALIDATE_APPENDBUF_SIZE); +- SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_APPENDBUF_UPDATE); +- return SOKOL_VALIDATE_END(); ++ _sg_validate_begin(); ++ _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_APPENDBUF_USAGE); ++ _SG_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), VALIDATE_APPENDBUF_SIZE); ++ _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_APPENDBUF_UPDATE); ++ return _sg_validate_end(); + #endif + } + +@@ -13798,28 +15238,31 @@ _SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_i + _SOKOL_UNUSED(data); + return true; + #else +- SOKOL_ASSERT(img && data); +- SOKOL_VALIDATE_BEGIN(); +- SOKOL_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDIMG_USAGE); +- SOKOL_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, _SG_VALIDATE_UPDIMG_ONCE); +- SOKOL_VALIDATE(!_sg_is_compressed_pixel_format(img->cmn.pixel_format), _SG_VALIDATE_UPDIMG_COMPRESSED); +- const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1; +- const int num_mips = img->cmn.num_mipmaps; +- for (int face_index = 0; face_index < num_faces; face_index++) { +- for (int mip_index = 0; mip_index < num_mips; mip_index++) { +- SOKOL_VALIDATE(0 != data->subimage[face_index][mip_index].ptr, _SG_VALIDATE_UPDIMG_NOTENOUGHDATA); +- const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); +- const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); +- const int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); +- const int expected_size = bytes_per_slice * img->cmn.num_slices; +- SOKOL_VALIDATE(data->subimage[face_index][mip_index].size <= (size_t)expected_size, _SG_VALIDATE_UPDIMG_SIZE); +- } ++ if (_sg.desc.disable_validation) { ++ return true; + } +- return SOKOL_VALIDATE_END(); ++ SOKOL_ASSERT(img && data); ++ _sg_validate_begin(); ++ _SG_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDIMG_USAGE); ++ _SG_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, VALIDATE_UPDIMG_ONCE); ++ _sg_validate_image_data(data, ++ img->cmn.pixel_format, ++ img->cmn.width, ++ img->cmn.height, ++ (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1, ++ img->cmn.num_mipmaps, ++ img->cmn.num_slices); ++ return _sg_validate_end(); + #endif + } + +-/*== fill in desc default values =============================================*/ ++// ██████ ███████ ███████ ██████ ██ ██ ██████ ██████ ███████ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ █████ ███████ ██ ██ ██ ██ ██████ ██ █████ ███████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██ ███████ ███████ ██████ ██████ ██ ██ ██████ ███████ ███████ ++// ++// >>resources + _SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) { + sg_buffer_desc def = *desc; + def.type = _sg_def(def.type, SG_BUFFERTYPE_VERTEXBUFFER); +@@ -13882,6 +15325,7 @@ _SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* des + if (0 == ub_desc->size) { + break; + } ++ ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE); + for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { + sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { +@@ -13951,7 +15395,7 @@ _SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_des + + /* resolve vertex layout strides and offsets */ + int auto_offset[SG_MAX_SHADERSTAGE_BUFFERS]; +- memset(auto_offset, 0, sizeof(auto_offset)); ++ _sg_clear(auto_offset, sizeof(auto_offset)); + bool use_auto_offset = true; + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + /* to use computed offsets, *all* attr offsets must be 0 */ +@@ -13987,7 +15431,6 @@ _SOKOL_PRIVATE sg_pass_desc _sg_pass_desc_defaults(const sg_pass_desc* desc) { + return def; + } + +-/*== allocate/initialize resource private functions ==========================*/ + _SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { + sg_buffer res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool); +@@ -13995,8 +15438,9 @@ _SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { + res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index); + } + else { +- /* pool is exhausted */ + res.id = SG_INVALID_ID; ++ _SG_ERROR(BUFFER_POOL_EXHAUSTED); ++ _SG_TRACE_NOARGS(err_buffer_pool_exhausted); + } + return res; + } +@@ -14008,8 +15452,9 @@ _SOKOL_PRIVATE sg_image _sg_alloc_image(void) { + res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index); + } + else { +- /* pool is exhausted */ + res.id = SG_INVALID_ID; ++ _SG_ERROR(IMAGE_POOL_EXHAUSTED); ++ _SG_TRACE_NOARGS(err_image_pool_exhausted); + } + return res; + } +@@ -14021,8 +15466,9 @@ _SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) { + res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index); + } + else { +- /* pool is exhausted */ + res.id = SG_INVALID_ID; ++ _SG_ERROR(SHADER_POOL_EXHAUSTED); ++ _SG_TRACE_NOARGS(err_shader_pool_exhausted); + } + return res; + } +@@ -14034,8 +15480,9 @@ _SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) { + res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index); + } + else { +- /* pool is exhausted */ + res.id = SG_INVALID_ID; ++ _SG_ERROR(PIPELINE_POOL_EXHAUSTED); ++ _SG_TRACE_NOARGS(err_pipeline_pool_exhausted); + } + return res; + } +@@ -14047,56 +15494,46 @@ _SOKOL_PRIVATE sg_pass _sg_alloc_pass(void) { + res.id = _sg_slot_alloc(&_sg.pools.pass_pool, &_sg.pools.passes[slot_index].slot, slot_index); + } + else { +- /* pool is exhausted */ + res.id = SG_INVALID_ID; ++ _SG_ERROR(PASS_POOL_EXHAUSTED); ++ _SG_TRACE_NOARGS(err_pass_pool_exhausted); + } + return res; + } + +-_SOKOL_PRIVATE void _sg_dealloc_buffer(sg_buffer buf_id) { +- SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); +- _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +- SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_dealloc_buffer(_sg_buffer_t* buf) { ++ SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC) && (buf->slot.id != SG_INVALID_ID)); ++ _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf->slot.id)); + _sg_reset_slot(&buf->slot); +- _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf_id.id)); + } + +-_SOKOL_PRIVATE void _sg_dealloc_image(sg_image img_id) { +- SOKOL_ASSERT(img_id.id != SG_INVALID_ID); +- _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +- SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_dealloc_image(_sg_image_t* img) { ++ SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC) && (img->slot.id != SG_INVALID_ID)); ++ _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img->slot.id)); + _sg_reset_slot(&img->slot); +- _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img_id.id)); + } + +-_SOKOL_PRIVATE void _sg_dealloc_shader(sg_shader shd_id) { +- SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); +- _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +- SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_dealloc_shader(_sg_shader_t* shd) { ++ SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC) && (shd->slot.id != SG_INVALID_ID)); ++ _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd->slot.id)); + _sg_reset_slot(&shd->slot); +- _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd_id.id)); + } + +-_SOKOL_PRIVATE void _sg_dealloc_pipeline(sg_pipeline pip_id) { +- SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); +- _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +- SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_dealloc_pipeline(_sg_pipeline_t* pip) { ++ SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC) && (pip->slot.id != SG_INVALID_ID)); ++ _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip->slot.id)); + _sg_reset_slot(&pip->slot); +- _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip_id.id)); + } + +-_SOKOL_PRIVATE void _sg_dealloc_pass(sg_pass pass_id) { +- SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); +- _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +- SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_dealloc_pass(_sg_pass_t* pass) { ++ SOKOL_ASSERT(pass && (pass->slot.state == SG_RESOURCESTATE_ALLOC) && (pass->slot.id != SG_INVALID_ID)); ++ _sg_pool_free_index(&_sg.pools.pass_pool, _sg_slot_index(pass->slot.id)); + _sg_reset_slot(&pass->slot); +- _sg_pool_free_index(&_sg.pools.pass_pool, _sg_slot_index(pass_id.id)); + } + +-_SOKOL_PRIVATE void _sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { +- SOKOL_ASSERT(buf_id.id != SG_INVALID_ID && desc); +- _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +- SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_init_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { ++ SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); ++ SOKOL_ASSERT(desc); + buf->slot.ctx_id = _sg.active_context.id; + if (_sg_validate_buffer_desc(desc)) { + buf->slot.state = _sg_create_buffer(buf, desc); +@@ -14107,10 +15544,9 @@ _SOKOL_PRIVATE void _sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED)); + } + +-_SOKOL_PRIVATE void _sg_init_image(sg_image img_id, const sg_image_desc* desc) { +- SOKOL_ASSERT(img_id.id != SG_INVALID_ID && desc); +- _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +- SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_init_image(_sg_image_t* img, const sg_image_desc* desc) { ++ SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); ++ SOKOL_ASSERT(desc); + img->slot.ctx_id = _sg.active_context.id; + if (_sg_validate_image_desc(desc)) { + img->slot.state = _sg_create_image(img, desc); +@@ -14121,10 +15557,9 @@ _SOKOL_PRIVATE void _sg_init_image(sg_image img_id, const sg_image_desc* desc) { + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED)); + } + +-_SOKOL_PRIVATE void _sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { +- SOKOL_ASSERT(shd_id.id != SG_INVALID_ID && desc); +- _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +- SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_init_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { ++ SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); ++ SOKOL_ASSERT(desc); + shd->slot.ctx_id = _sg.active_context.id; + if (_sg_validate_shader_desc(desc)) { + shd->slot.state = _sg_create_shader(shd, desc); +@@ -14135,10 +15570,9 @@ _SOKOL_PRIVATE void _sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); + } + +-_SOKOL_PRIVATE void _sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { +- SOKOL_ASSERT(pip_id.id != SG_INVALID_ID && desc); +- _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +- SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); ++_SOKOL_PRIVATE void _sg_init_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { ++ SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); ++ SOKOL_ASSERT(desc); + pip->slot.ctx_id = _sg.active_context.id; + if (_sg_validate_pipeline_desc(desc)) { + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); +@@ -14155,10 +15589,9 @@ _SOKOL_PRIVATE void _sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED)); + } + +-_SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { +- SOKOL_ASSERT(pass_id.id != SG_INVALID_ID && desc); +- _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++_SOKOL_PRIVATE void _sg_init_pass(_sg_pass_t* pass, const sg_pass_desc* desc) { + SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); ++ SOKOL_ASSERT(desc); + pass->slot.ctx_id = _sg.active_context.id; + if (_sg_validate_pass_desc(desc)) { + /* lookup pass attachment image pointers */ +@@ -14166,8 +15599,10 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (desc->color_attachments[i].image.id) { + att_imgs[i] = _sg_lookup_image(&_sg.pools, desc->color_attachments[i].image.id); +- /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ +- SOKOL_ASSERT(att_imgs[i] && att_imgs[i]->slot.state == SG_RESOURCESTATE_VALID); ++ if (!(att_imgs[i] && att_imgs[i]->slot.state == SG_RESOURCESTATE_VALID)) { ++ pass->slot.state = SG_RESOURCESTATE_FAILED; ++ return; ++ } + } + else { + att_imgs[i] = 0; +@@ -14176,8 +15611,10 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { + const int ds_att_index = SG_MAX_COLOR_ATTACHMENTS; + if (desc->depth_stencil_attachment.image.id) { + att_imgs[ds_att_index] = _sg_lookup_image(&_sg.pools, desc->depth_stencil_attachment.image.id); +- /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ +- SOKOL_ASSERT(att_imgs[ds_att_index] && att_imgs[ds_att_index]->slot.state == SG_RESOURCESTATE_VALID); ++ if (!(att_imgs[ds_att_index] && att_imgs[ds_att_index]->slot.state == SG_RESOURCESTATE_VALID)) { ++ pass->slot.state = SG_RESOURCESTATE_FAILED; ++ return; ++ } + } + else { + att_imgs[ds_att_index] = 0; +@@ -14190,129 +15627,185 @@ _SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { + SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID)||(pass->slot.state == SG_RESOURCESTATE_FAILED)); + } + +-_SOKOL_PRIVATE bool _sg_uninit_buffer(sg_buffer buf_id) { +- _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +- if (buf) { +- if (buf->slot.ctx_id == _sg.active_context.id) { +- _sg_destroy_buffer(buf); +- _sg_reset_buffer(buf); +- return true; +- } +- else { +- SOKOL_LOG("_sg_uninit_buffer: active context mismatch (must be same as for creation)"); +- _SG_TRACE_NOARGS(err_context_mismatch); +- } ++_SOKOL_PRIVATE void _sg_uninit_buffer(_sg_buffer_t* buf) { ++ SOKOL_ASSERT(buf && ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED))); ++ if (buf->slot.ctx_id == _sg.active_context.id) { ++ _sg_discard_buffer(buf); ++ _sg_reset_buffer_to_alloc_state(buf); ++ } ++ else { ++ _SG_WARN(UNINIT_BUFFER_ACTIVE_CONTEXT_MISMATCH); ++ _SG_TRACE_NOARGS(err_context_mismatch); + } +- return false; + } + +-_SOKOL_PRIVATE bool _sg_uninit_image(sg_image img_id) { +- _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +- if (img) { +- if (img->slot.ctx_id == _sg.active_context.id) { +- _sg_destroy_image(img); +- _sg_reset_image(img); +- return true; +- } +- else { +- SOKOL_LOG("_sg_uninit_image: active context mismatch (must be same as for creation)"); +- _SG_TRACE_NOARGS(err_context_mismatch); +- } ++_SOKOL_PRIVATE void _sg_uninit_image(_sg_image_t* img) { ++ SOKOL_ASSERT(img && ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED))); ++ if (img->slot.ctx_id == _sg.active_context.id) { ++ _sg_discard_image(img); ++ _sg_reset_image_to_alloc_state(img); ++ } ++ else { ++ _SG_WARN(UNINIT_IMAGE_ACTIVE_CONTEXT_MISMATCH); ++ _SG_TRACE_NOARGS(err_context_mismatch); + } +- return false; + } + +-_SOKOL_PRIVATE bool _sg_uninit_shader(sg_shader shd_id) { +- _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +- if (shd) { +- if (shd->slot.ctx_id == _sg.active_context.id) { +- _sg_destroy_shader(shd); +- _sg_reset_shader(shd); +- return true; +- } +- else { +- SOKOL_LOG("_sg_uninit_shader: active context mismatch (must be same as for creation)"); +- _SG_TRACE_NOARGS(err_context_mismatch); ++_SOKOL_PRIVATE void _sg_uninit_shader(_sg_shader_t* shd) { ++ SOKOL_ASSERT(shd && ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED))); ++ if (shd->slot.ctx_id == _sg.active_context.id) { ++ _sg_discard_shader(shd); ++ _sg_reset_shader_to_alloc_state(shd); ++ } ++ else { ++ _SG_WARN(UNINIT_SHADER_ACTIVE_CONTEXT_MISMATCH); ++ _SG_TRACE_NOARGS(err_context_mismatch); ++ } ++} ++ ++_SOKOL_PRIVATE void _sg_uninit_pipeline(_sg_pipeline_t* pip) { ++ SOKOL_ASSERT(pip && ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED))); ++ if (pip->slot.ctx_id == _sg.active_context.id) { ++ _sg_discard_pipeline(pip); ++ _sg_reset_pipeline_to_alloc_state(pip); ++ } ++ else { ++ _SG_WARN(UNINIT_PIPELINE_ACTIVE_CONTEXT_MISMATCH); ++ _SG_TRACE_NOARGS(err_context_mismatch); ++ } ++} ++ ++_SOKOL_PRIVATE void _sg_uninit_pass(_sg_pass_t* pass) { ++ SOKOL_ASSERT(pass && ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED))); ++ if (pass->slot.ctx_id == _sg.active_context.id) { ++ _sg_discard_pass(pass); ++ _sg_reset_pass_to_alloc_state(pass); ++ } ++ else { ++ _SG_WARN(UNINIT_PASS_ACTIVE_CONTEXT_MISMATCH); ++ _SG_TRACE_NOARGS(err_context_mismatch); ++ } ++} ++ ++_SOKOL_PRIVATE void _sg_setup_commit_listeners(const sg_desc* desc) { ++ SOKOL_ASSERT(desc->max_commit_listeners > 0); ++ SOKOL_ASSERT(0 == _sg.commit_listeners.items); ++ SOKOL_ASSERT(0 == _sg.commit_listeners.num); ++ SOKOL_ASSERT(0 == _sg.commit_listeners.upper); ++ _sg.commit_listeners.num = desc->max_commit_listeners; ++ const size_t size = (size_t)_sg.commit_listeners.num * sizeof(sg_commit_listener); ++ _sg.commit_listeners.items = (sg_commit_listener*)_sg_malloc_clear(size); ++} ++ ++_SOKOL_PRIVATE void _sg_discard_commit_listeners(void) { ++ SOKOL_ASSERT(0 != _sg.commit_listeners.items); ++ _sg_free(_sg.commit_listeners.items); ++ _sg.commit_listeners.items = 0; ++} ++ ++_SOKOL_PRIVATE void _sg_notify_commit_listeners(void) { ++ SOKOL_ASSERT(_sg.commit_listeners.items); ++ for (int i = 0; i < _sg.commit_listeners.upper; i++) { ++ const sg_commit_listener* listener = &_sg.commit_listeners.items[i]; ++ if (listener->func) { ++ listener->func(listener->user_data); + } + } +- return false; + } + +-_SOKOL_PRIVATE bool _sg_uninit_pipeline(sg_pipeline pip_id) { +- _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +- if (pip) { +- if (pip->slot.ctx_id == _sg.active_context.id) { +- _sg_destroy_pipeline(pip); +- _sg_reset_pipeline(pip); +- return true; ++_SOKOL_PRIVATE bool _sg_add_commit_listener(const sg_commit_listener* new_listener) { ++ SOKOL_ASSERT(new_listener && new_listener->func); ++ SOKOL_ASSERT(_sg.commit_listeners.items); ++ // first check if the listener hadn't been added already ++ for (int i = 0; i < _sg.commit_listeners.upper; i++) { ++ const sg_commit_listener* slot = &_sg.commit_listeners.items[i]; ++ if ((slot->func == new_listener->func) && (slot->user_data == new_listener->user_data)) { ++ _SG_ERROR(IDENTICAL_COMMIT_LISTENER); ++ return false; + } +- else { +- SOKOL_LOG("_sg_uninit_pipeline: active context mismatch (must be same as for creation)"); +- _SG_TRACE_NOARGS(err_context_mismatch); ++ } ++ // first try to plug a hole ++ sg_commit_listener* slot = 0; ++ for (int i = 0; i < _sg.commit_listeners.upper; i++) { ++ if (_sg.commit_listeners.items[i].func == 0) { ++ slot = &_sg.commit_listeners.items[i]; ++ break; + } + } +- return false; ++ if (!slot) { ++ // append to end ++ if (_sg.commit_listeners.upper < _sg.commit_listeners.num) { ++ slot = &_sg.commit_listeners.items[_sg.commit_listeners.upper++]; ++ } ++ } ++ if (!slot) { ++ _SG_ERROR(COMMIT_LISTENER_ARRAY_FULL); ++ return false; ++ } ++ *slot = *new_listener; ++ return true; + } + +-_SOKOL_PRIVATE bool _sg_uninit_pass(sg_pass pass_id) { +- _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +- if (pass) { +- if (pass->slot.ctx_id == _sg.active_context.id) { +- _sg_destroy_pass(pass); +- _sg_reset_pass(pass); ++_SOKOL_PRIVATE bool _sg_remove_commit_listener(const sg_commit_listener* listener) { ++ SOKOL_ASSERT(listener && listener->func); ++ SOKOL_ASSERT(_sg.commit_listeners.items); ++ for (int i = 0; i < _sg.commit_listeners.upper; i++) { ++ sg_commit_listener* slot = &_sg.commit_listeners.items[i]; ++ // both the function pointer and user data must match! ++ if ((slot->func == listener->func) && (slot->user_data == listener->user_data)) { ++ slot->func = 0; ++ slot->user_data = 0; ++ // NOTE: since _sg_add_commit_listener() already catches duplicates, ++ // we don't need to worry about them here + return true; + } +- else { +- SOKOL_LOG("_sg_uninit_pass: active context mismatch (must be same as for creation)"); +- _SG_TRACE_NOARGS(err_context_mismatch); +- } + } + return false; + } + +-/*== PUBLIC API FUNCTIONS ====================================================*/ +- +-#if defined(SOKOL_METAL) +- // this is ARC compatible +- #if defined(__cplusplus) +- #define _SG_CLEAR(type, item) { item = (type) { }; } +- #else +- #define _SG_CLEAR(type, item) { item = (type) { 0 }; } +- #endif +-#else +- #define _SG_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } +-#endif +- +-SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { +- SOKOL_ASSERT(desc); +- SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); +- _SG_CLEAR(_sg_state_t, _sg); +- _sg.desc = *desc; +- +- /* replace zero-init items with their default values ++_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) { ++ /* + NOTE: on WebGPU, the default color pixel format MUST be provided, + cannot be a default compile-time constant. + */ ++ sg_desc res = *desc; + #if defined(SOKOL_WGPU) +- SOKOL_ASSERT(SG_PIXELFORMAT_NONE != _sg.desc.context.color_format); ++ SOKOL_ASSERT(SG_PIXELFORMAT_NONE != res.context.color_format); + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) +- _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_BGRA8); ++ res.context.color_format = _sg_def(res.context.color_format, SG_PIXELFORMAT_BGRA8); + #else +- _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_RGBA8); +- #endif +- _sg.desc.context.depth_format = _sg_def(_sg.desc.context.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); +- _sg.desc.context.sample_count = _sg_def(_sg.desc.context.sample_count, 1); +- _sg.desc.buffer_pool_size = _sg_def(_sg.desc.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); +- _sg.desc.image_pool_size = _sg_def(_sg.desc.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); +- _sg.desc.shader_pool_size = _sg_def(_sg.desc.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); +- _sg.desc.pipeline_pool_size = _sg_def(_sg.desc.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); +- _sg.desc.pass_pool_size = _sg_def(_sg.desc.pass_pool_size, _SG_DEFAULT_PASS_POOL_SIZE); +- _sg.desc.context_pool_size = _sg_def(_sg.desc.context_pool_size, _SG_DEFAULT_CONTEXT_POOL_SIZE); +- _sg.desc.uniform_buffer_size = _sg_def(_sg.desc.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); +- _sg.desc.staging_buffer_size = _sg_def(_sg.desc.staging_buffer_size, _SG_DEFAULT_STAGING_SIZE); +- _sg.desc.sampler_cache_size = _sg_def(_sg.desc.sampler_cache_size, _SG_DEFAULT_SAMPLER_CACHE_CAPACITY); ++ res.context.color_format = _sg_def(res.context.color_format, SG_PIXELFORMAT_RGBA8); ++ #endif ++ res.context.depth_format = _sg_def(res.context.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); ++ res.context.sample_count = _sg_def(res.context.sample_count, 1); ++ res.buffer_pool_size = _sg_def(res.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); ++ res.image_pool_size = _sg_def(res.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); ++ res.shader_pool_size = _sg_def(res.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); ++ res.pipeline_pool_size = _sg_def(res.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); ++ res.pass_pool_size = _sg_def(res.pass_pool_size, _SG_DEFAULT_PASS_POOL_SIZE); ++ res.context_pool_size = _sg_def(res.context_pool_size, _SG_DEFAULT_CONTEXT_POOL_SIZE); ++ res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); ++ res.staging_buffer_size = _sg_def(res.staging_buffer_size, _SG_DEFAULT_STAGING_SIZE); ++ res.sampler_cache_size = _sg_def(res.sampler_cache_size, _SG_DEFAULT_SAMPLER_CACHE_CAPACITY); ++ res.max_commit_listeners = _sg_def(res.max_commit_listeners, _SG_DEFAULT_MAX_COMMIT_LISTENERS); ++ return res; ++} + ++// ██████ ██ ██ ██████ ██ ██ ██████ ++// ██ ██ ██ ██ ██ ██ ██ ██ ██ ++// ██████ ██ ██ ██████ ██ ██ ██ ++// ██ ██ ██ ██ ██ ██ ██ ██ ++// ██ ██████ ██████ ███████ ██ ██████ ++// ++// >>public ++SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { ++ SOKOL_ASSERT(desc); ++ SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); ++ SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); ++ _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); ++ _sg.desc = _sg_desc_defaults(desc); + _sg_setup_pools(&_sg.pools, &_sg.desc); ++ _sg_setup_commit_listeners(&_sg.desc); + _sg.frame_index = 1; + _sg_setup_backend(&_sg.desc); + _sg.valid = true; +@@ -14327,13 +15820,14 @@ SOKOL_API_IMPL void sg_shutdown(void) { + if (_sg.active_context.id != SG_INVALID_ID) { + _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, _sg.active_context.id); + if (ctx) { +- _sg_destroy_all_resources(&_sg.pools, _sg.active_context.id); +- _sg_destroy_context(ctx); ++ _sg_discard_all_resources(&_sg.pools, _sg.active_context.id); ++ _sg_discard_context(ctx); + } + } + _sg_discard_backend(); ++ _sg_discard_commit_listeners(); + _sg_discard_pools(&_sg.pools); +- _sg.valid = false; ++ _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); + } + + SOKOL_API_IMPL bool sg_isvalid(void) { +@@ -14388,11 +15882,11 @@ SOKOL_API_IMPL sg_context sg_setup_context(void) { + + SOKOL_API_IMPL void sg_discard_context(sg_context ctx_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_destroy_all_resources(&_sg.pools, ctx_id.id); ++ _sg_discard_all_resources(&_sg.pools, ctx_id.id); + _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, ctx_id.id); + if (ctx) { +- _sg_destroy_context(ctx); +- _sg_reset_context(ctx); ++ _sg_discard_context(ctx); ++ _sg_reset_context_to_alloc_state(ctx); + _sg_reset_slot(&ctx->slot); + _sg_pool_free_index(&_sg.pools.context_pool, _sg_slot_index(ctx_id.id)); + } +@@ -14417,7 +15911,7 @@ SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace + _sg.hooks = *trace_hooks; + #else + static sg_trace_hooks old_hooks; +- SOKOL_LOG("sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined!"); ++ _SG_WARN(TRACE_HOOKS_NOT_ENABLED); + #endif + return old_hooks; + } +@@ -14459,152 +15953,302 @@ SOKOL_API_IMPL sg_pass sg_alloc_pass(void) { + + SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_dealloc_buffer(buf_id); ++ _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ if (buf) { ++ if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_buffer(buf); ++ } ++ else { ++ _SG_ERROR(DEALLOC_BUFFER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(dealloc_buffer, buf_id); + } + + SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_dealloc_image(img_id); ++ _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); ++ if (img) { ++ if (img->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_image(img); ++ } ++ else { ++ _SG_ERROR(DEALLOC_IMAGE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(dealloc_image, img_id); + } + + SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_dealloc_shader(shd_id); ++ _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); ++ if (shd) { ++ if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_shader(shd); ++ } ++ else { ++ _SG_ERROR(DEALLOC_SHADER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(dealloc_shader, shd_id); + } + + SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_dealloc_pipeline(pip_id); ++ _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); ++ if (pip) { ++ if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_pipeline(pip); ++ } ++ else { ++ _SG_ERROR(DEALLOC_PIPELINE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(dealloc_pipeline, pip_id); + } + + SOKOL_API_IMPL void sg_dealloc_pass(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); +- _sg_dealloc_pass(pass_id); ++ _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++ if (pass) { ++ if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_pass(pass); ++ } ++ else { ++ _SG_ERROR(DEALLOC_PASS_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(dealloc_pass, pass_id); + } + + SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); +- _sg_init_buffer(buf_id, &desc_def); ++ _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ if (buf) { ++ if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_init_buffer(buf, &desc_def); ++ SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); ++ } ++ else { ++ _SG_ERROR(INIT_BUFFER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def); + } + + SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc_def = _sg_image_desc_defaults(desc); +- _sg_init_image(img_id, &desc_def); ++ _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); ++ if (img) { ++ if (img->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_init_image(img, &desc_def); ++ SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); ++ } ++ else { ++ _SG_ERROR(INIT_IMAGE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(init_image, img_id, &desc_def); + } + + SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); +- _sg_init_shader(shd_id, &desc_def); ++ _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); ++ if (shd) { ++ if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_init_shader(shd, &desc_def); ++ SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); ++ } ++ else { ++ _SG_ERROR(INIT_SHADER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(init_shader, shd_id, &desc_def); + } + + SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); +- _sg_init_pipeline(pip_id, &desc_def); ++ _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); ++ if (pip) { ++ if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_init_pipeline(pip, &desc_def); ++ SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); ++ } ++ else { ++ _SG_ERROR(INIT_PIPELINE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def); + } + + SOKOL_API_IMPL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); +- _sg_init_pass(pass_id, &desc_def); ++ _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++ if (pass) { ++ if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_init_pass(pass, &desc_def); ++ SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)); ++ } ++ else { ++ _SG_ERROR(INIT_PASS_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(init_pass, pass_id, &desc_def); + } + +-SOKOL_API_IMPL bool sg_uninit_buffer(sg_buffer buf_id) { ++SOKOL_API_IMPL void sg_uninit_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); +- bool res = _sg_uninit_buffer(buf_id); ++ _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ if (buf) { ++ if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_buffer(buf); ++ SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ else { ++ _SG_ERROR(UNINIT_BUFFER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(uninit_buffer, buf_id); +- return res; + } + +-SOKOL_API_IMPL bool sg_uninit_image(sg_image img_id) { ++SOKOL_API_IMPL void sg_uninit_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); +- bool res = _sg_uninit_image(img_id); ++ _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); ++ if (img) { ++ if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_image(img); ++ SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ else { ++ _SG_ERROR(UNINIT_IMAGE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(uninit_image, img_id); +- return res; + } + +-SOKOL_API_IMPL bool sg_uninit_shader(sg_shader shd_id) { ++SOKOL_API_IMPL void sg_uninit_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); +- bool res = _sg_uninit_shader(shd_id); ++ _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); ++ if (shd) { ++ if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_shader(shd); ++ SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ else { ++ _SG_ERROR(UNINIT_SHADER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(uninit_shader, shd_id); +- return res; + } + +-SOKOL_API_IMPL bool sg_uninit_pipeline(sg_pipeline pip_id) { ++SOKOL_API_IMPL void sg_uninit_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); +- bool res = _sg_uninit_pipeline(pip_id); ++ _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); ++ if (pip) { ++ if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_pipeline(pip); ++ SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ else { ++ _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(uninit_pipeline, pip_id); +- return res; + } + +-SOKOL_API_IMPL bool sg_uninit_pass(sg_pass pass_id) { ++SOKOL_API_IMPL void sg_uninit_pass(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); +- bool res = _sg_uninit_pass(pass_id); ++ _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++ if (pass) { ++ if ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_pass(pass); ++ SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ else { ++ _SG_ERROR(UNINIT_PASS_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(uninit_pass, pass_id); +- return res; + } + + /*-- set allocated resource to failed state ----------------------------------*/ + SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); +- SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +- SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +- buf->slot.ctx_id = _sg.active_context.id; +- buf->slot.state = SG_RESOURCESTATE_FAILED; ++ if (buf) { ++ if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { ++ buf->slot.ctx_id = _sg.active_context.id; ++ buf->slot.state = SG_RESOURCESTATE_FAILED; ++ } ++ else { ++ _SG_ERROR(FAIL_BUFFER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(fail_buffer, buf_id); + } + + SOKOL_API_IMPL void sg_fail_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); +- SOKOL_ASSERT(img_id.id != SG_INVALID_ID); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +- SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +- img->slot.ctx_id = _sg.active_context.id; +- img->slot.state = SG_RESOURCESTATE_FAILED; ++ if (img) { ++ if (img->slot.state == SG_RESOURCESTATE_ALLOC) { ++ img->slot.ctx_id = _sg.active_context.id; ++ img->slot.state = SG_RESOURCESTATE_FAILED; ++ } ++ else { ++ _SG_ERROR(FAIL_IMAGE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(fail_image, img_id); + } + + SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); +- SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +- SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +- shd->slot.ctx_id = _sg.active_context.id; +- shd->slot.state = SG_RESOURCESTATE_FAILED; ++ if (shd) { ++ if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { ++ shd->slot.ctx_id = _sg.active_context.id; ++ shd->slot.state = SG_RESOURCESTATE_FAILED; ++ } ++ else { ++ _SG_ERROR(FAIL_SHADER_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(fail_shader, shd_id); + } + + SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); +- SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +- SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +- pip->slot.ctx_id = _sg.active_context.id; +- pip->slot.state = SG_RESOURCESTATE_FAILED; ++ if (pip) { ++ if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { ++ pip->slot.ctx_id = _sg.active_context.id; ++ pip->slot.state = SG_RESOURCESTATE_FAILED; ++ } ++ else { ++ _SG_ERROR(FAIL_PIPELINE_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(fail_pipeline, pip_id); + } + + SOKOL_API_IMPL void sg_fail_pass(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); +- SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); + _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +- SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); +- pass->slot.ctx_id = _sg.active_context.id; +- pass->slot.state = SG_RESOURCESTATE_FAILED; ++ if (pass) { ++ if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { ++ pass->slot.ctx_id = _sg.active_context.id; ++ pass->slot.state = SG_RESOURCESTATE_FAILED; ++ } ++ else { ++ _SG_ERROR(FAIL_PASS_INVALID_STATE); ++ } ++ } + _SG_TRACE_ARGS(fail_pass, pass_id); + } + +@@ -14651,11 +16295,10 @@ SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) { + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); + sg_buffer buf_id = _sg_alloc_buffer(); + if (buf_id.id != SG_INVALID_ID) { +- _sg_init_buffer(buf_id, &desc_def); +- } +- else { +- SOKOL_LOG("buffer pool exhausted!"); +- _SG_TRACE_NOARGS(err_buffer_pool_exhausted); ++ _sg_buffer_t* buf = _sg_buffer_at(&_sg.pools, buf_id.id); ++ SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); ++ _sg_init_buffer(buf, &desc_def); ++ SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id); + return buf_id; +@@ -14667,11 +16310,10 @@ SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) { + sg_image_desc desc_def = _sg_image_desc_defaults(desc); + sg_image img_id = _sg_alloc_image(); + if (img_id.id != SG_INVALID_ID) { +- _sg_init_image(img_id, &desc_def); +- } +- else { +- SOKOL_LOG("image pool exhausted!"); +- _SG_TRACE_NOARGS(err_image_pool_exhausted); ++ _sg_image_t* img = _sg_image_at(&_sg.pools, img_id.id); ++ SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); ++ _sg_init_image(img, &desc_def); ++ SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_image, &desc_def, img_id); + return img_id; +@@ -14683,11 +16325,10 @@ SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) { + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); + sg_shader shd_id = _sg_alloc_shader(); + if (shd_id.id != SG_INVALID_ID) { +- _sg_init_shader(shd_id, &desc_def); +- } +- else { +- SOKOL_LOG("shader pool exhausted!"); +- _SG_TRACE_NOARGS(err_shader_pool_exhausted); ++ _sg_shader_t* shd = _sg_shader_at(&_sg.pools, shd_id.id); ++ SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); ++ _sg_init_shader(shd, &desc_def); ++ SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_shader, &desc_def, shd_id); + return shd_id; +@@ -14699,11 +16340,10 @@ SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) { + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); + sg_pipeline pip_id = _sg_alloc_pipeline(); + if (pip_id.id != SG_INVALID_ID) { +- _sg_init_pipeline(pip_id, &desc_def); +- } +- else { +- SOKOL_LOG("pipeline pool exhausted!"); +- _SG_TRACE_NOARGS(err_pipeline_pool_exhausted); ++ _sg_pipeline_t* pip = _sg_pipeline_at(&_sg.pools, pip_id.id); ++ SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); ++ _sg_init_pipeline(pip, &desc_def); ++ SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id); + return pip_id; +@@ -14715,11 +16355,10 @@ SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) { + sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); + sg_pass pass_id = _sg_alloc_pass(); + if (pass_id.id != SG_INVALID_ID) { +- _sg_init_pass(pass_id, &desc_def); +- } +- else { +- SOKOL_LOG("pass pool exhausted!"); +- _SG_TRACE_NOARGS(err_pass_pool_exhausted); ++ _sg_pass_t* pass = _sg_pass_at(&_sg.pools, pass_id.id); ++ SOKOL_ASSERT(pass && (pass->slot.state == SG_RESOURCESTATE_ALLOC)); ++ _sg_init_pass(pass, &desc_def); ++ SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_pass, &desc_def, pass_id); + return pass_id; +@@ -14729,40 +16368,80 @@ SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) { + SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_buffer, buf_id); +- if (_sg_uninit_buffer(buf_id)) { +- _sg_dealloc_buffer(buf_id); ++ _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ if (buf) { ++ if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_buffer(buf); ++ SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_buffer(buf); ++ SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_INITIAL); ++ } + } + } + + SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_image, img_id); +- if (_sg_uninit_image(img_id)) { +- _sg_dealloc_image(img_id); ++ _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); ++ if (img) { ++ if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_image(img); ++ SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ if (img->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_image(img); ++ SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_INITIAL); ++ } + } + } + + SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_shader, shd_id); +- if (_sg_uninit_shader(shd_id)) { +- _sg_dealloc_shader(shd_id); ++ _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); ++ if (shd) { ++ if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_shader(shd); ++ SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_shader(shd); ++ SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_INITIAL); ++ } + } + } + + SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_pipeline, pip_id); +- if (_sg_uninit_pipeline(pip_id)) { +- _sg_dealloc_pipeline(pip_id); ++ _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); ++ if (pip) { ++ if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_pipeline(pip); ++ SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_pipeline(pip); ++ SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_INITIAL); ++ } + } + } + + SOKOL_API_IMPL void sg_destroy_pass(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_pass, pass_id); +- if (_sg_uninit_pass(pass_id)) { +- _sg_dealloc_pass(pass_id); ++ _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++ if (pass) { ++ if ((pass->slot.state == SG_RESOURCESTATE_VALID) || (pass->slot.state == SG_RESOURCESTATE_FAILED)) { ++ _sg_uninit_pass(pass); ++ SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_ALLOC); ++ } ++ if (pass->slot.state == SG_RESOURCESTATE_ALLOC) { ++ _sg_dealloc_pass(pass); ++ SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_INITIAL); ++ } + } + } + +@@ -14942,6 +16621,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); +@@ -14954,7 +16634,7 @@ SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instance + SOKOL_ASSERT(num_instances >= 0); + #if defined(SOKOL_DEBUG) + if (!_sg.bindings_valid) { +- SOKOL_LOG("attempting to draw without resource bindings"); ++ _SG_WARN(DRAW_WITHOUT_BINDINGS); + } + #endif + if (!_sg.pass_valid) { +@@ -14996,6 +16676,7 @@ SOKOL_API_IMPL void sg_end_pass(void) { + SOKOL_API_IMPL void sg_commit(void) { + SOKOL_ASSERT(_sg.valid); + _sg_commit(); ++ _sg_notify_commit_listeners(); + _SG_TRACE_NOARGS(commit); + _sg.frame_index++; + } +@@ -15067,6 +16748,23 @@ SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) { + return result; + } + ++SOKOL_API_IMPL bool sg_query_buffer_will_overflow(sg_buffer buf_id, size_t size) { ++ SOKOL_ASSERT(_sg.valid); ++ _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ bool result = false; ++ if (buf) { ++ int append_pos = buf->cmn.append_pos; ++ /* rewind append cursor in a new frame */ ++ if (buf->cmn.append_frame_index != _sg.frame_index) { ++ append_pos = 0; ++ } ++ if ((append_pos + _sg_roundup((int)size, 4)) > buf->cmn.size) { ++ result = true; ++ } ++ } ++ return result; ++} ++ + SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +@@ -15092,10 +16790,20 @@ SOKOL_API_IMPL void sg_pop_debug_group(void) { + _SG_TRACE_NOARGS(pop_debug_group); + } + ++SOKOL_API_IMPL bool sg_add_commit_listener(sg_commit_listener listener) { ++ SOKOL_ASSERT(_sg.valid); ++ return _sg_add_commit_listener(&listener); ++} ++ ++SOKOL_API_IMPL bool sg_remove_commit_listener(sg_commit_listener listener) { ++ SOKOL_ASSERT(_sg.valid); ++ return _sg_remove_commit_listener(&listener); ++} ++ + SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_info info; +- memset(&info, 0, sizeof(info)); ++ _sg_clear(&info, sizeof(info)); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + info.slot.state = buf->slot.state; +@@ -15119,12 +16827,13 @@ SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { + SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_info info; +- memset(&info, 0, sizeof(info)); ++ _sg_clear(&info, sizeof(info)); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + 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; +@@ -15132,8 +16841,6 @@ SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { + info.num_slots = img->cmn.num_slots; + info.active_slot = img->cmn.active_slot; + #endif +- info.width = img->cmn.width; +- info.height = img->cmn.height; + } + return info; + } +@@ -15141,7 +16848,7 @@ SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { + SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_info info; +- memset(&info, 0, sizeof(info)); ++ _sg_clear(&info, sizeof(info)); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + info.slot.state = shd->slot.state; +@@ -15154,7 +16861,7 @@ SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { + SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_info info; +- memset(&info, 0, sizeof(info)); ++ _sg_clear(&info, sizeof(info)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + info.slot.state = pip->slot.state; +@@ -15167,7 +16874,7 @@ SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_API_IMPL sg_pass_info sg_query_pass_info(sg_pass pass_id) { + SOKOL_ASSERT(_sg.valid); + sg_pass_info info; +- memset(&info, 0, sizeof(info)); ++ _sg_clear(&info, sizeof(info)); + const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); + if (pass) { + info.slot.state = pass->slot.state; +@@ -15177,6 +16884,115 @@ SOKOL_API_IMPL sg_pass_info sg_query_pass_info(sg_pass pass_id) { + return info; + } + ++SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf_id) { ++ SOKOL_ASSERT(_sg.valid); ++ sg_buffer_desc desc; ++ _sg_clear(&desc, sizeof(desc)); ++ const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); ++ if (buf) { ++ desc.size = (size_t)buf->cmn.size; ++ desc.type = buf->cmn.type; ++ desc.usage = buf->cmn.usage; ++ } ++ return desc; ++} ++ ++SOKOL_API_IMPL sg_image_desc sg_query_image_desc(sg_image img_id) { ++ SOKOL_ASSERT(_sg.valid); ++ sg_image_desc desc; ++ _sg_clear(&desc, sizeof(desc)); ++ const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); ++ if (img) { ++ desc.type = img->cmn.type; ++ desc.render_target = img->cmn.render_target; ++ desc.width = img->cmn.width; ++ desc.height = img->cmn.height; ++ desc.num_slices = img->cmn.num_slices; ++ desc.num_mipmaps = img->cmn.num_mipmaps; ++ desc.usage = img->cmn.usage; ++ desc.pixel_format = img->cmn.pixel_format; ++ desc.sample_count = img->cmn.sample_count; ++ desc.min_filter = img->cmn.min_filter; ++ desc.mag_filter = img->cmn.mag_filter; ++ desc.wrap_u = img->cmn.wrap_u; ++ desc.wrap_v = img->cmn.wrap_v; ++ desc.wrap_w = img->cmn.wrap_w; ++ desc.border_color = img->cmn.border_color; ++ desc.max_anisotropy = img->cmn.max_anisotropy; ++ desc.min_lod = img->cmn.min_lod; ++ desc.max_lod = img->cmn.max_lod; ++ } ++ return desc; ++} ++ ++SOKOL_API_IMPL sg_shader_desc sg_query_shader_desc(sg_shader shd_id) { ++ SOKOL_ASSERT(_sg.valid); ++ sg_shader_desc desc; ++ _sg_clear(&desc, sizeof(desc)); ++ const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); ++ if (shd) { ++ for (int stage_idx = 0; stage_idx < SG_NUM_SHADER_STAGES; stage_idx++) { ++ sg_shader_stage_desc* stage_desc = (stage_idx == 0) ? &desc.vs : &desc.fs; ++ const _sg_shader_stage_t* stage = &shd->cmn.stage[stage_idx]; ++ for (int ub_idx = 0; ub_idx < stage->num_uniform_blocks; ub_idx++) { ++ sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_idx]; ++ const _sg_shader_uniform_block_t* ub = &stage->uniform_blocks[ub_idx]; ++ ub_desc->size = ub->size; ++ } ++ for (int img_idx = 0; img_idx < stage->num_images; img_idx++) { ++ sg_shader_image_desc* img_desc = &stage_desc->images[img_idx]; ++ const _sg_shader_image_t* img = &stage->images[img_idx]; ++ img_desc->image_type = img->image_type; ++ img_desc->sampler_type = img->sampler_type; ++ } ++ } ++ } ++ return desc; ++} ++ ++SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip_id) { ++ SOKOL_ASSERT(_sg.valid); ++ sg_pipeline_desc desc; ++ _sg_clear(&desc, sizeof(desc)); ++ const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); ++ if (pip) { ++ desc.shader = pip->cmn.shader_id; ++ desc.layout = pip->cmn.layout; ++ desc.depth = pip->cmn.depth; ++ desc.stencil = pip->cmn.stencil; ++ desc.color_count = pip->cmn.color_count; ++ for (int i = 0; i < pip->cmn.color_count; i++) { ++ desc.colors[i] = pip->cmn.colors[i]; ++ } ++ desc.primitive_type = pip->cmn.primitive_type; ++ desc.index_type = pip->cmn.index_type; ++ desc.cull_mode = pip->cmn.cull_mode; ++ desc.face_winding = pip->cmn.face_winding; ++ desc.sample_count = pip->cmn.sample_count; ++ desc.blend_color = pip->cmn.blend_color; ++ desc.alpha_to_coverage_enabled = pip->cmn.alpha_to_coverage_enabled; ++ } ++ return desc; ++} ++ ++SOKOL_API_IMPL sg_pass_desc sg_query_pass_desc(sg_pass pass_id) { ++ SOKOL_ASSERT(_sg.valid); ++ sg_pass_desc desc; ++ _sg_clear(&desc, sizeof(desc)); ++ const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); ++ if (pass) { ++ for (int i = 0; i < pass->cmn.num_color_atts; i++) { ++ desc.color_attachments[i].image = pass->cmn.color_atts[i].image_id; ++ desc.color_attachments[i].mip_level = pass->cmn.color_atts[i].mip_level; ++ desc.color_attachments[i].slice = pass->cmn.color_atts[i].slice; ++ } ++ desc.depth_stencil_attachment.image = pass->cmn.ds_att.image_id; ++ desc.depth_stencil_attachment.mip_level = pass->cmn.ds_att.mip_level; ++ desc.depth_stencil_attachment.slice = pass->cmn.ds_att.slice; ++ } ++ return desc; ++} ++ + SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_buffer_desc_defaults(desc); diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index b585063f7..51d9b1272 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -1675,10 +1675,11 @@ typedef struct sapp_html5_fetch_response { } sapp_html5_fetch_response; typedef struct sapp_html5_fetch_request { - int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 - void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) - sapp_range buffer; // ptr/size of a memory buffer to load the data into - void* user_data; // optional userdata pointer + int dropped_file_index; /* 0..sapp_get_num_dropped_files()-1 */ + void (*callback)(const sapp_html5_fetch_response*); /* response callback function pointer (required) */ + void* buffer_ptr; /* pointer to buffer to load data into */ + uint32_t buffer_size; /* size in bytes of buffer */ + void* user_data; /* optional userdata pointer */ } sapp_html5_fetch_request; /* From acde71ee4936ffb5a71ff64b032116ead3a0bbd1 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 21:47:29 +0100 Subject: [PATCH 17/20] Fix sapp_html5_fetch_request member names --- src/sokol/sokol_app.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index 51d9b1272..48d76a215 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -11536,15 +11536,15 @@ SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request SOKOL_ASSERT(_sapp.drop.enabled); SOKOL_ASSERT(request); SOKOL_ASSERT(request->callback); - SOKOL_ASSERT(request->buffer.ptr); - SOKOL_ASSERT(request->buffer.size > 0); + SOKOL_ASSERT(request->buffer_ptr); + SOKOL_ASSERT(request->buffer_size > 0); #if defined(_SAPP_EMSCRIPTEN) const int index = request->dropped_file_index; sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; if ((index < 0) || (index >= _sapp.drop.num_files)) { error_code = SAPP_HTML5_FETCH_ERROR_OTHER; } - if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { + if (sapp_html5_get_dropped_file_size(index) > request->buffer_size) { error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; } if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { @@ -11553,15 +11553,15 @@ SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request (int)error_code, request->callback, 0, // fetched_size - (void*)request->buffer.ptr, - request->buffer.size, + (void*)request->buffer_ptr, + request->buffer_size, request->user_data); } else { sapp_js_fetch_dropped_file(index, request->callback, - (void*)request->buffer.ptr, - request->buffer.size, + (void*)request->buffer_ptr, + request->buffer_size, request->user_data); } #else From f0976277c5cc783008dd215e92db6e286922e00f Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 22:02:50 +0100 Subject: [PATCH 18/20] Fix sapp_html5_fetch_response --- src/sokol/sokol_app.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index 48d76a215..6ed433425 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -1666,12 +1666,13 @@ typedef enum sapp_html5_fetch_error { } sapp_html5_fetch_error; typedef struct sapp_html5_fetch_response { - bool succeeded; // true if the loading operation has succeeded + bool succeeded; /* true if the loading operation has succeeded */ sapp_html5_fetch_error error_code; - int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) - sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) - sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) - void* user_data; // user-provided user data pointer + int file_index; /* index of the dropped file (0..sapp_get_num_dropped_filed()-1) */ + uint32_t fetched_size; /* size in bytes of loaded data */ + void* buffer_ptr; /* pointer to user-provided buffer which contains the loaded data */ + uint32_t buffer_size; /* size of user-provided buffer (buffer_size >= fetched_size) */ + void* user_data; /* user-provided user data pointer */ } sapp_html5_fetch_response; typedef struct sapp_html5_fetch_request { @@ -4702,8 +4703,8 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int response.file_index = index; response.data.ptr = buf_ptr; response.data.size = fetched_size; - response.buffer.ptr = buf_ptr; - response.buffer.size = buf_size; + response.buffer_ptr = buf_ptr; + response.buffer_size = buf_size; response.user_data = user_data; callback(&response); } From 615d01ab1d9b7ded27e9c7f55c8cdbd1ff86e289 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Tue, 18 Feb 2025 22:09:59 +0100 Subject: [PATCH 19/20] Revert _sapp_emsc_invoke_fetch_cb --- src/sokol/sokol_app.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h index 6ed433425..beda253ed 100644 --- a/src/sokol/sokol_app.h +++ b/src/sokol/sokol_app.h @@ -4697,12 +4697,11 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { sapp_html5_fetch_response response; - _sapp_clear(&response, sizeof(response)); + memset(&response, 0, sizeof(response)); response.succeeded = (0 != success); response.error_code = (sapp_html5_fetch_error) error_code; response.file_index = index; - response.data.ptr = buf_ptr; - response.data.size = fetched_size; + response.fetched_size = fetched_size; response.buffer_ptr = buf_ptr; response.buffer_size = buf_size; response.user_data = user_data; From b9c247503a14806e682e371b427fe7daefef6250 Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Fri, 21 Feb 2025 11:18:36 +0100 Subject: [PATCH 20/20] Cleanup extra files from patching --- src/sokol/sokol_app.h.base | 10967 --------------------------------- src/sokol/sokol_app.h.orig | 11529 ----------------------------------- src/sokol/sokol_app.h.rej | 19 - src/sokol/sokol_app.patch | 328 - 4 files changed, 22843 deletions(-) delete mode 100644 src/sokol/sokol_app.h.base delete mode 100644 src/sokol/sokol_app.h.orig delete mode 100644 src/sokol/sokol_app.h.rej delete mode 100644 src/sokol/sokol_app.patch diff --git a/src/sokol/sokol_app.h.base b/src/sokol/sokol_app.h.base deleted file mode 100644 index 5af91c241..000000000 --- a/src/sokol/sokol_app.h.base +++ /dev/null @@ -1,10967 +0,0 @@ -#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) -#define SOKOL_APP_IMPL -#endif -#ifndef SOKOL_APP_INCLUDED -/* - sokol_app.h -- cross-platform application wrapper - - Project URL: https://github.com/floooh/sokol - - Do this: - #define SOKOL_IMPL or - #define SOKOL_APP_IMPL - before you include this file in *one* C or C++ file to create the - implementation. - - In the same place define one of the following to select the 3D-API - which should be initialized by sokol_app.h (this must also match - the backend selected for sokol_gfx.h if both are used in the same - project): - - #define SOKOL_GLCORE33 - #define SOKOL_GLES2 - #define SOKOL_GLES3 - #define SOKOL_D3D11 - #define SOKOL_METAL - #define SOKOL_WGPU - - Optionally provide the following defines with your own implementations: - - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_LOG(msg) - your own logging function (default: puts(msg)) - SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) - SOKOL_ABORT() - called after an unrecoverable error (default: abort()) - SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain - SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function - SOKOL_APP_API_DECL - public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_APP_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - SOKOL_CALLOC - your own calloc function (default: calloc(n, s)) - SOKOL_FREE - your own free function (default: free(p)) - - Optionally define the following to force debug checks and validations - even in release mode: - - SOKOL_DEBUG - by default this is defined if _DEBUG is defined - - If sokol_app.h is compiled as a DLL, define the following before - including the declaration or implementation: - - SOKOL_DLL - - On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) - or __declspec(dllimport) as needed. - - If you use sokol_app.h together with sokol_gfx.h, include both headers - in the implementation source file, and include sokol_app.h before - sokol_gfx.h since sokol_app.h will also include the required 3D-API - headers. - - On Windows, a minimal 'GL header' and function loader is integrated which - contains just enough of GL for sokol_gfx.h. If you want to use your own - GL header-generator/loader instead, define SOKOL_WIN32_NO_GL_LOADER - before including the implementation part of sokol_app.h. - - To make use of the integrated GL loader, simply include the sokol_app.h - implementation before the sokol_gfx.h implementation. - - 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/) - - iOS onscreen keyboard support 'inspired' by libgdx. - - Link with the following system libraries: - - - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit - - on macOS with GL: Cocoa, QuartzCore, OpenGL - - on iOS with Metal: Foundation, UIKit, Metal, MetalKit - - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit - - on Linux: X11, Xi, Xcursor, GL, dl, pthread, m(?) - - on Android: GLESv3, EGL, log, android - - on Windows: no action needed, libs are defined in-source via pragma-comment-lib - - 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 - - Building for UWP requires a recent Visual Studio toolchain and Windows SDK - (at least VS2019 and Windows SDK 10.0.19041.0). When the UWP backend is - selected, the sokol_app.h implementation must be compiled as C++17. - - On macOS and iOS, the implementation must be compiled as Objective-C. - - FEATURE OVERVIEW - ================ - sokol_app.h provides a minimalistic cross-platform API which - implements the 'application-wrapper' parts of a 3D application: - - - a common application entry function - - creates a window and 3D-API context/device with a 'default framebuffer' - - makes the rendered frame visible - - provides keyboard-, mouse- and low-level touch-events - - platforms: MacOS, iOS, HTML5, Win32, Linux, Android (TODO: RaspberryPi) - - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 - - FEATURE/PLATFORM MATRIX - ======================= - | Windows | macOS | Linux | iOS | Android | UWP | Raspi | HTML5 - --------------------+---------+-------+-------+-------+---------+------+-------+------- - gl 3.x | YES | YES | YES | --- | --- | --- | --- | --- - gles2/webgl | --- | --- | --- | YES | YES | --- | TODO | YES - gles3/webgl2 | --- | --- | --- | YES | YES | --- | --- | YES - metal | --- | YES | --- | YES | --- | --- | --- | --- - d3d11 | YES | --- | --- | --- | --- | YES | --- | --- - KEY_DOWN | YES | YES | YES | SOME | TODO | YES | TODO | YES - KEY_UP | YES | YES | YES | SOME | TODO | YES | TODO | YES - CHAR | YES | YES | YES | YES | TODO | YES | TODO | YES - MOUSE_DOWN | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_UP | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_SCROLL | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_MOVE | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_ENTER | YES | YES | YES | --- | --- | YES | TODO | YES - MOUSE_LEAVE | YES | YES | YES | --- | --- | YES | TODO | YES - TOUCHES_BEGAN | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_MOVED | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_ENDED | --- | --- | --- | YES | YES | TODO | --- | YES - TOUCHES_CANCELLED | --- | --- | --- | YES | YES | TODO | --- | YES - RESIZED | YES | YES | YES | YES | YES | YES | --- | YES - ICONIFIED | YES | YES | YES | --- | --- | YES | --- | --- - RESTORED | YES | YES | YES | --- | --- | YES | --- | --- - SUSPENDED | --- | --- | --- | YES | YES | YES | --- | TODO - RESUMED | --- | --- | --- | YES | YES | YES | --- | TODO - QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | TODO | YES - UPDATE_CURSOR | YES | YES | TODO | --- | --- | TODO | --- | TODO - IME | TODO | TODO? | TODO | ??? | TODO | --- | ??? | ??? - key repeat flag | YES | YES | YES | --- | --- | YES | TODO | YES - windowed | YES | YES | YES | --- | --- | YES | TODO | YES - fullscreen | YES | YES | YES | YES | YES | YES | TODO | --- - mouse hide | YES | YES | YES | --- | --- | YES | TODO | TODO - mouse lock | YES | YES | YES | --- | --- | TODO | TODO | YES - screen keyboard | --- | --- | --- | YES | TODO | TODO | --- | YES - swap interval | YES | YES | YES | YES | TODO | --- | TODO | YES - high-dpi | YES | YES | TODO | YES | YES | YES | TODO | YES - clipboard | YES | YES | TODO | --- | --- | TODO | --- | YES - MSAA | YES | YES | YES | YES | YES | TODO | TODO | YES - drag'n'drop | YES | YES | YES | --- | --- | TODO | TODO | YES - - TODO - ==== - - Linux: - - clipboard support - - UWP: - - clipboard, mouselock - - sapp_consume_event() on non-web platforms? - - STEP BY STEP - ============ - --- Add a sokol_main() function to your code which returns a sapp_desc structure - with initialization parameters and callback function pointers. This - function is called very early, usually at the start of the - platform's entry function (e.g. main or WinMain). You should do as - little as possible here, since the rest of your code might be called - from another thread (this depends on the platform): - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - .width = 640, - .height = 480, - .init_cb = my_init_func, - .frame_cb = my_frame_func, - .cleanup_cb = my_cleanup_func, - .event_cb = my_event_func, - ... - }; - } - - There are many more setup parameters, but these are the most important. - For a complete list search for the sapp_desc structure declaration - below. - - DO NOT call any sokol-app function from inside sokol_main(), since - sokol-app will not be initialized at this point. - - The .width and .height parameters are the preferred size of the 3D - rendering canvas. The actual size may differ from this depending on - platform and other circumstances. Also the canvas size may change at - any time (for instance when the user resizes the application window, - or rotates the mobile device). - - All provided function callbacks will be called from the same thread, - but this may be different from the thread where sokol_main() was called. - - .init_cb (void (*)(void)) - This function is called once after the application window, - 3D rendering context and swap chain have been created. The - function takes no arguments and has no return value. - .frame_cb (void (*)(void)) - This is the per-frame callback, which is usually called 60 - times per second. This is where your application would update - most of its state and perform all rendering. - .cleanup_cb (void (*)(void)) - The cleanup callback is called once right before the application - quits. - .event_cb (void (*)(const sapp_event* event)) - The event callback is mainly for input handling, but is also - used to communicate other types of events to the application. Keep the - event_cb struct member zero-initialized if your application doesn't require - event handling. - .fail_cb (void (*)(const char* msg)) - The fail callback is called when a fatal error is encountered - during start which doesn't allow the program to continue. - Providing a callback here gives you a chance to show an error message - to the user. The default behaviour is SOKOL_LOG(msg) - - As you can see, those 'standard callbacks' don't have a user_data - argument, so any data that needs to be preserved between callbacks - must live in global variables. If keeping state in global variables - is not an option, there's an alternative set of callbacks with - an additional user_data pointer argument: - - .user_data (void*) - The user-data argument for the callbacks below - .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)) - 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. - - The function sapp_userdata() can be used to query the user_data - pointer provided in the sapp_desc struct. - - You can also call sapp_query_desc() to get a copy of the - original sapp_desc structure. - - NOTE that there's also an alternative compile mode where sokol_app.h - doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. - - --- Implement the initialization callback function (init_cb), this is called - once after the rendering surface, 3D API and swap chain have been - initialized by sokol_app. All sokol-app functions can be called - from inside the initialization callback, the most useful functions - at this point are: - - int sapp_width(void) - int sapp_height(void) - Returns the current width and height of the default framebuffer in pixels, - this may change from one frame to the next, and it may be different - from the initial size provided in the sapp_desc struct. - - float sapp_widthf(void) - float sapp_heightf(void) - These are alternatives to sapp_width() and sapp_height() which return - the default framebuffer size as float values instead of integer. This - may help to prevent casting back and forth between int and float - in more strongly typed languages than C and C++. - - int sapp_color_format(void) - int sapp_depth_format(void) - The color and depth-stencil pixelformats of the default framebuffer, - as integer values which are compatible with sokol-gfx's - sg_pixel_format enum (so that they can be plugged directly in places - where sg_pixel_format is expected). Possible values are: - - 23 == SG_PIXELFORMAT_RGBA8 - 27 == SG_PIXELFORMAT_BGRA8 - 41 == SG_PIXELFORMAT_DEPTH - 42 == SG_PIXELFORMAT_DEPTH_STENCIL - - int sapp_sample_count(void) - Return the MSAA sample count of the default framebuffer. - - bool sapp_gles2(void) - Returns true if a GLES2 or WebGL context has been created. This - is useful when a GLES3/WebGL2 context was requested but is not - available so that sokol_app.h had to fallback to GLES2/WebGL. - - const void* sapp_metal_get_device(void) - const void* sapp_metal_get_renderpass_descriptor(void) - const void* sapp_metal_get_drawable(void) - If the Metal backend has been selected, these functions return pointers - to various Metal API objects required for rendering, otherwise - they return a null pointer. These void pointers are actually - Objective-C ids converted with a (ARC) __bridge cast so that - the ids can be tunnel through C code. Also note that the returned - pointers to the renderpass-descriptor and drawable may change from one - frame to the next, only the Metal device object is guaranteed to - stay the same. - - const void* sapp_macos_get_window(void) - On macOS, get the NSWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_ios_get_window(void) - On iOS, get the UIWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_win32_get_hwnd(void) - On Windows, get the window's HWND, otherwise a null pointer. The - HWND has been cast to a void pointer in order to be tunneled - through code which doesn't include Windows.h. - - const void* sapp_d3d11_get_device(void) - const void* sapp_d3d11_get_device_context(void) - const void* sapp_d3d11_get_render_target_view(void) - const void* sapp_d3d11_get_depth_stencil_view(void) - Similar to the sapp_metal_* functions, the sapp_d3d11_* functions - return pointers to D3D11 API objects required for rendering, - only if the D3D11 backend has been selected. Otherwise they - return a null pointer. Note that the returned pointers to the - render-target-view and depth-stencil-view may change from one - frame to the next! - - const void* sapp_wgpu_get_device(void) - const void* sapp_wgpu_get_render_view(void) - const void* sapp_wgpu_get_resolve_view(void) - const void* sapp_wgpu_get_depth_stencil_view(void) - These are the WebGPU-specific functions to get the WebGPU - objects and values required for rendering. If sokol_app.h - is not compiled with SOKOL_WGPU, these functions return null. - - const void* sapp_android_get_native_activity(void); - On Android, get the native activity ANativeActivity pointer, otherwise - a null pointer. - - --- Implement the frame-callback function, this function will be called - on the same thread as the init callback, but might be on a different - thread than the sokol_main() function. Note that the size of - the rendering framebuffer might have changed since the frame callback - was called last. Call the functions sapp_width() and sapp_height() - each frame to get the current size. - - --- Optionally implement the event-callback to handle input events. - sokol-app provides the following type of input events: - - a 'virtual key' was pressed down or released - - a single text character was entered (provided as UTF-32 code point) - - a mouse button was pressed down or released (left, right, middle) - - mouse-wheel or 2D scrolling events - - the mouse was moved - - the mouse has entered or left the application window boundaries - - low-level, portable multi-touch events (began, moved, ended, cancelled) - - the application window was resized, iconified or restored - - the application was suspended or restored (on mobile platforms) - - the user or application code has asked to quit the application - - a string was pasted to the system clipboard - - one or more files have been dropped onto the application window - - To explicitly 'consume' an event and prevent that the event is - forwarded for further handling to the operating system, call - sapp_consume_event() from inside the event handler (NOTE that - this behaviour is currently only implemented for some HTML5 - events, support for other platforms and event types will - be added as needed, please open a github ticket and/or provide - a PR if needed). - - NOTE: Do *not* call any 3D API rendering functions in the event - callback function, since the 3D API context may not be active when the - event callback is called (it may work on some platforms and 3D APIs, - but not others, and the exact behaviour may change between - sokol-app versions). - - --- Implement the cleanup-callback function, this is called once - after the user quits the application (see the section - "APPLICATION QUIT" for detailed information on quitting - behaviour, and how to intercept a pending quit - for instance to show a - "Really Quit?" dialog box). Note that the cleanup-callback isn't - guaranteed to be called on the web and mobile platforms. - - MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) - ================================================ - In normal mouse mode, no mouse movement events are reported when the - mouse leaves the windows client area or hits the screen border (whether - it's one or the other depends on the platform), and the mouse move events - (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in - framebuffer pixels in the sapp_event items mouse_x and mouse_y, and - relative movement in framebuffer pixels in the sapp_event items mouse_dx - and mouse_dy. - - To get continuous mouse movement (also when the mouse leaves the window - client area or hits the screen border), activate mouse-lock mode - by calling: - - sapp_lock_mouse(true) - - When mouse lock is activated, the mouse pointer is hidden, the - reported absolute mouse position (sapp_event.mouse_x/y) appears - frozen, and the relative mouse movement in sapp_event.mouse_dx/dy - no longer has a direct relation to framebuffer pixels but instead - uses "raw mouse input" (what "raw mouse input" exactly means also - differs by platform). - - To deactivate mouse lock and return to normal mouse mode, call - - sapp_lock_mouse(false) - - And finally, to check if mouse lock is currently active, call - - if (sapp_mouse_locked()) { ... } - - On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() - functions work as expected (mouse lock is activated or deactivated - immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() - also immediately returns the new state after sapp_lock_mouse() - is called. - - On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave - differently, as dictated by the limitations of the HTML5 Pointer Lock API: - - - sapp_lock_mouse(true) can be called at any time, but it will - only take effect in a 'short-lived input event handler of a specific - type', meaning when one of the following events happens: - - SAPP_EVENTTYPE_MOUSE_DOWN - - SAPP_EVENTTYPE_MOUSE_UP - - SAPP_EVENTTYPE_MOUSE_SCROLL - - SAPP_EVENTYTPE_KEY_UP - - SAPP_EVENTTYPE_KEY_DOWN - - The mouse lock/unlock action on the web platform is asynchronous, - this means that sapp_mouse_locked() won't immediately return - the new status after calling sapp_lock_mouse(), instead the - reported status will only change when the pointer lock has actually - been activated or deactivated in the browser. - - On the web, mouse lock can be deactivated by the user at any time - by pressing the Esc key. When this happens, sokol_app.h behaves - the same as if sapp_lock_mouse(false) is called. - - For things like camera manipulation it's most straightforward to lock - and unlock the mouse right from the sokol_app.h event handler, for - instance the following code enters and leaves mouse lock when the - left mouse button is pressed and released, and then uses the relative - movement information to manipulate a camera (taken from the - cgltf-sapp.c sample in the sokol-samples repository - at https://github.com/floooh/sokol-samples): - - static void input(const sapp_event* ev) { - switch (ev->type) { - case SAPP_EVENTTYPE_MOUSE_DOWN: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(true); - } - break; - - case SAPP_EVENTTYPE_MOUSE_UP: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(false); - } - break; - - case SAPP_EVENTTYPE_MOUSE_MOVE: - if (sapp_mouse_locked()) { - cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); - } - break; - - default: - break; - } - } - - CLIPBOARD SUPPORT - ================= - Applications can send and receive UTF-8 encoded text data from and to the - system clipboard. By default, clipboard support is disabled and - must be enabled at startup via the following sapp_desc struct - members: - - sapp_desc.enable_clipboard - set to true to enable clipboard support - sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes - - Enabling the clipboard will dynamically allocate a clipboard buffer - for UTF-8 encoded text data of the requested size in bytes, the default - size is 8 KBytes. Strings that don't fit into the clipboard buffer - (including the terminating zero) will be silently clipped, so it's - important that you provide a big enough clipboard size for your - use case. - - To send data to the clipboard, call sapp_set_clipboard_string() with - a pointer to an UTF-8 encoded, null-terminated C-string. - - NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be - called from inside a 'short-lived event handler', and there are a few - other HTML5-specific caveats to workaround. You'll basically have to - tinker until it works in all browsers :/ (maybe the situation will - improve when all browsers agree on and implement the new - HTML5 navigator.clipboard API). - - To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED - event in your event handler function, and then call sapp_get_clipboard_string() - to obtain the pasted UTF-8 encoded text. - - NOTE that behaviour of sapp_get_clipboard_string() is slightly different - depending on platform: - - - on the HTML5 platform, the internal clipboard buffer will only be updated - right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, - and sapp_get_clipboard_string() will simply return the current content - of the clipboard buffer - - on 'native' platforms, the call to sapp_get_clipboard_string() will - update the internal clipboard buffer with the most recent data - from the system clipboard - - Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, - and then call sapp_get_clipboard_string() right in the event handler. - - The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app - as follows: - - - on macOS: when the Cmd+V key is pressed down - - on HTML5: when the browser sends a 'paste' event to the global 'window' object - - on all other platforms: when the Ctrl+V key is pressed down - - DRAG AND DROP SUPPORT - ===================== - PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 - and on the native desktop platforms (Win32, Linux and macOS) because - of security-related restrictions in the HTML5 drag'n'drop API. The - WASM/HTML5 specifics are described at the end of this documentation - section: - - Like clipboard support, drag'n'drop support must be explicitly enabled - at startup in the sapp_desc struct. - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - ... - }; - } - - You can also adjust the maximum number of files that are accepted - in a drop operation, and the maximum path length in bytes if needed: - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - .max_dropped_files = 8, // default is 1 - .max_dropped_file_path_length = 8192, // in bytes, default is 2048 - ... - }; - } - - When drag'n'drop is enabled, the event callback will be invoked with an - event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on - the application window. - - After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the - number of dropped files, and their absolute paths by calling separate - functions: - - void on_event(const sapp_event* ev) { - if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { - - // the mouse position where the drop happened - float x = ev->mouse_x; - float y = ev->mouse_y; - - // get the number of files and their paths like this: - const int num_dropped_files = sapp_get_num_dropped_files(); - for (int i = 0; i < num_dropped_files; i++) { - const char* path = sapp_get_dropped_file_path(i); - ... - } - } - } - - The returned file paths are UTF-8 encoded strings. - - You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() - anywhere, also outside the event handler callback, but be aware that the - file path strings will be overwritten with the next drop operation. - - In any case, sapp_get_dropped_file_path() will never return a null pointer, - instead an empty string "" will be returned if the drag'n'drop feature - hasn't been enabled, the last drop-operation failed, or the file path index - is out of range. - - Drag'n'drop caveats: - - - if more files are dropped in a single drop-action - than sapp_desc.max_dropped_files, the additional - files will be silently ignored - - if any of the file paths is longer than - sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 - encoding) the entire drop operation will be silently ignored (this - needs some sort of error feedback in the future) - - no mouse positions are reported while the drag is in - process, this may change in the future - - Drag'n'drop on HTML5/WASM: - - The HTML5 drag'n'drop API doesn't return file paths, but instead - black-box 'file objects' which must be used to load the content - of dropped files. This is the reason why sokol_app.h adds two - HTML5-specific functions to the drag'n'drop API: - - uint32_t sapp_html5_get_dropped_file_size(int index) - Returns the size in bytes of a dropped file. - - void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) - Asynchronously loads the content of a dropped file into a - provided memory buffer (which must be big enough to hold - the file content) - - To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED - event is received: - - sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ - .dropped_file_index = 0, - .callback = fetch_cb - .buffer_ptr = buf, - .buffer_size = buf_size, - .user_data = ... - }); - - Make sure that the memory pointed to by 'buf' stays valid until the - callback function is called! - - As result of the asynchronous loading operation (no matter if succeeded or - failed) the 'fetch_cb' function will be called: - - void fetch_cb(const sapp_html5_fetch_response* response) { - // IMPORTANT: check if the loading operation actually succeeded: - if (response->succeeded) { - // the size of the loaded file: - const uint32_t num_bytes = response->fetched_size; - // and the pointer to the data (same as 'buf' in the fetch-call): - const void* ptr = response->buffer_ptr; - } - else { - // on error check the error code: - switch (response->error_code) { - case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: - ... - break; - case SAPP_HTML5_FETCH_ERROR_OTHER: - ... - break; - } - } - } - - Check the droptest-sapp example for a real-world example which works - both on native platforms and the web: - - https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c - - HIGH-DPI RENDERING - ================== - You can set the sapp_desc.high_dpi flag during initialization to request - a full-resolution framebuffer on HighDPI displays. The default behaviour - is sapp_desc.high_dpi=false, this means that the application will - render to a lower-resolution framebuffer on HighDPI displays and the - rendered content will be upscaled by the window system composer. - - In a HighDPI scenario, you still request the same window size during - sokol_main(), but the framebuffer sizes returned by sapp_width() - and sapp_height() will be scaled up according to the DPI scaling - ratio. You can also get a DPI scaling factor with the function - sapp_dpi_scale(). - - Here's an example on a Mac with Retina display: - - sapp_desc sokol_main() { - return (sapp_desc) { - .width = 640, - .height = 480, - .high_dpi = true, - ... - }; - } - - The functions sapp_width(), sapp_height() and sapp_dpi_scale() will - return the following values: - - sapp_width -> 1280 - sapp_height -> 960 - sapp_dpi_scale -> 2.0 - - If the high_dpi flag is false, or you're not running on a Retina display, - the values would be: - - sapp_width -> 640 - sapp_height -> 480 - sapp_dpi_scale -> 1.0 - - APPLICATION QUIT - ================ - Without special quit handling, a sokol_app.h application will quit - 'gracefully' when the user clicks the window close-button unless a - platform's application model prevents this (e.g. on web or mobile). - 'Graceful exit' means that the application-provided cleanup callback will - be called before the application quits. - - On native desktop platforms sokol_app.h provides more control over the - application-quit-process. It's possible to initiate a 'programmatic quit' - from the application code, and a quit initiated by the application user can - be intercepted (for instance to show a custom dialog box). - - This 'programmatic quit protocol' is implemented through 3 functions - and 1 event: - - - sapp_quit(): This function simply quits the application without - giving the user a chance to intervene. Usually this might - be called when the user clicks the 'Ok' button in a 'Really Quit?' - dialog box - - sapp_request_quit(): Calling sapp_request_quit() will send the - event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler - callback, giving the user code a chance to intervene and cancel the - pending quit process (for instance to show a 'Really Quit?' dialog - box). If the event handler callback does nothing, the application - will be quit as usual. To prevent this, call the function - sapp_cancel_quit() from inside the event handler. - - sapp_cancel_quit(): Cancels a pending quit request, either initiated - by the user clicking the window close button, or programmatically - by calling sapp_request_quit(). The only place where calling this - function makes sense is from inside the event handler callback when - the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. - - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user - clicks the window's close button or application code calls the - sapp_request_quit() function. The event handler callback code can handle - this event by calling sapp_cancel_quit() to cancel the quit. - If the event is ignored, the application will quit as usual. - - On the web platform, the quit behaviour differs from native platforms, - because of web-specific restrictions: - - A `programmatic quit` initiated by calling sapp_quit() or - sapp_request_quit() will work as described above: the cleanup callback is - called, platform-specific cleanup is performed (on the web - this means that JS event handlers are unregisters), and then - the request-animation-loop will be exited. However that's all. The - web page itself will continue to exist (e.g. it's not possible to - programmatically close the browser tab). - - On the web it's also not possible to run custom code when the user - closes a brower tab, so it's not possible to prevent this with a - fancy custom dialog box. - - Instead the standard "Leave Site?" dialog box can be activated (or - deactivated) with the following function: - - sapp_html5_ask_leave_site(bool ask); - - The initial state of the associated internal flag can be provided - at startup via sapp_desc.html5_ask_leave_site. - - This feature should only be used sparingly in critical situations - for - instance when the user would loose data - since popping up modal dialog - boxes is considered quite rude in the web world. Note that there's no way - to customize the content of this dialog box or run any code as a result - of the user's decision. Also note that the user must have interacted with - the site before the dialog box will appear. These are all security measures - to prevent fishing. - - The Dear ImGui HighDPI sample contains example code of how to - implement a 'Really Quit?' dialog box with Dear ImGui (native desktop - platforms only), and for showing the hardwired "Leave Site?" dialog box - when running on the web platform: - - https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html - - FULLSCREEN - ========== - If the sapp_desc.fullscreen flag is true, sokol-app will try to create - a fullscreen window on platforms with a 'proper' window system - (mobile devices will always use fullscreen). The implementation details - depend on the target platform, in general sokol-app will use a - 'soft approach' which doesn't interfere too much with the platform's - window system (for instance borderless fullscreen window instead of - a 'real' fullscreen mode). Such details might change over time - as sokol-app is adapted for different needs. - - The most important effect of fullscreen mode to keep in mind is that - the requested canvas width and height will be ignored for the initial - window size, calling sapp_width() and sapp_height() will instead return - the resolution of the fullscreen canvas (however the provided size - might still be used for the non-fullscreen window, in case the user can - switch back from fullscreen- to windowed-mode). - - To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). - - To check if the application window is currently in fullscreen mode, - call sapp_is_fullscreen(). - - ONSCREEN KEYBOARD - ================= - On some platforms which don't provide a physical keyboard, sokol-app - can display the platform's integrated onscreen keyboard for text - input. To request that the onscreen keyboard is shown, call - - sapp_show_keyboard(true); - - Likewise, to hide the keyboard call: - - sapp_show_keyboard(false); - - Note that on the web platform, the keyboard can only be shown from - inside an input handler. On such platforms, sapp_show_keyboard() - will only work as expected when it is called from inside the - sokol-app event callback function. When called from other places, - an internal flag will be set, and the onscreen keyboard will be - called at the next 'legal' opportunity (when the next input event - is handled). - - OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) - ====================================================== - In its default configuration, sokol_app.h "hijacks" the platform's - standard main() function. This was done because different platforms - have different main functions which are not compatible with - C's main() (for instance WinMain on Windows has completely different - arguments). However, this "main hijacking" posed a problem for - usage scenarios like integrating sokol_app.h with other languages than - C or C++, so an alternative SOKOL_NO_ENTRY mode has been added - in which the user code provides the platform's main function: - - - define SOKOL_NO_ENTRY before including the sokol_app.h implementation - - do *not* provide a sokol_main() function - - instead provide the standard main() function of the platform - - from the main function, call the function ```sapp_run()``` which - takes a pointer to an ```sapp_desc``` structure. - - ```sapp_run()``` takes over control and calls the provided init-, frame-, - shutdown- and event-callbacks just like in the default model, it - will only return when the application quits (or not at all on some - platforms, like emscripten) - - NOTE: SOKOL_NO_ENTRY is currently not supported on Android. - - WINDOWS CONSOLE OUTPUT - ====================== - On Windows, regular windowed applications don't show any stdout/stderr text - output, which can be a bit of a hassle for printf() debugging or generally - logging text to the console. Also, console output by default uses a local - codepage setting and thus international UTF-8 encoded text is printed - as garbage. - - To help with these issues, sokol_app.h can be configured at startup - via the following Windows-specific sapp_desc flags: - - sapp_desc.win32_console_utf8 (default: false) - When set to true, the output console codepage will be switched - to UTF-8 (and restored to the original codepage on exit) - - sapp_desc.win32_console_attach (default: false) - When set to true, stdout and stderr will be attached to the - console of the parent process (if the parent process actually - has a console). This means that if the application was started - in a command line window, stdout and stderr output will be printed - to the terminal, just like a regular command line program. But if - the application is started via double-click, it will behave like - a regular UI application, and stdout/stderr will not be visible. - - sapp_desc.win32_console_create (default: false) - When set to true, a new console window will be created and - stdout/stderr will be redirected to that console window. It - doesn't matter if the application is started from the command - line or via double-click. - - TEMP NOTE DUMP - ============== - - onscreen keyboard support on Android requires Java :(, should we even bother? - - sapp_desc needs a bool whether to initialize depth-stencil surface - - GL context initialization needs more control (at least what GL version to initialize) - - application icon - - the UPDATE_CURSOR event currently behaves differently between Win32 and OSX - (Win32 sends the event each frame when the mouse moves and is inside the window - client area, OSX sends it only once when the mouse enters the client area) - - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy - at the latest but should do it earlier, in onStop, as an app is "killable" after onStop - on Android Honeycomb and later (it can't be done at the moment as the app may be started - again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) - - - LICENSE - ======= - zlib/libpng license - - Copyright (c) 2018 Andre Weissflog - - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ -#define SOKOL_APP_INCLUDED (1) -#include -#include - -#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) -#define SOKOL_APP_API_DECL SOKOL_API_DECL -#endif -#ifndef SOKOL_APP_API_DECL -#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) -#define SOKOL_APP_API_DECL __declspec(dllexport) -#elif defined(_WIN32) && defined(SOKOL_DLL) -#define SOKOL_APP_API_DECL __declspec(dllimport) -#else -#define SOKOL_APP_API_DECL extern -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -enum { - SAPP_MAX_TOUCHPOINTS = 8, - SAPP_MAX_MOUSEBUTTONS = 3, - SAPP_MAX_KEYCODES = 512, -}; - -typedef enum sapp_event_type { - SAPP_EVENTTYPE_INVALID, - SAPP_EVENTTYPE_KEY_DOWN, - SAPP_EVENTTYPE_KEY_UP, - SAPP_EVENTTYPE_CHAR, - SAPP_EVENTTYPE_MOUSE_DOWN, - SAPP_EVENTTYPE_MOUSE_UP, - SAPP_EVENTTYPE_MOUSE_SCROLL, - SAPP_EVENTTYPE_MOUSE_MOVE, - SAPP_EVENTTYPE_MOUSE_ENTER, - SAPP_EVENTTYPE_MOUSE_LEAVE, - SAPP_EVENTTYPE_TOUCHES_BEGAN, - SAPP_EVENTTYPE_TOUCHES_MOVED, - SAPP_EVENTTYPE_TOUCHES_ENDED, - SAPP_EVENTTYPE_TOUCHES_CANCELLED, - SAPP_EVENTTYPE_RESIZED, - SAPP_EVENTTYPE_ICONIFIED, - SAPP_EVENTTYPE_RESTORED, - SAPP_EVENTTYPE_SUSPENDED, - SAPP_EVENTTYPE_RESUMED, - SAPP_EVENTTYPE_UPDATE_CURSOR, - SAPP_EVENTTYPE_QUIT_REQUESTED, - SAPP_EVENTTYPE_CLIPBOARD_PASTED, - SAPP_EVENTTYPE_FILES_DROPPED, - _SAPP_EVENTTYPE_NUM, - _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF -} sapp_event_type; - -/* key codes are the same names and values as GLFW */ -typedef enum sapp_keycode { - SAPP_KEYCODE_INVALID = 0, - SAPP_KEYCODE_SPACE = 32, - SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ - SAPP_KEYCODE_COMMA = 44, /* , */ - SAPP_KEYCODE_MINUS = 45, /* - */ - SAPP_KEYCODE_PERIOD = 46, /* . */ - SAPP_KEYCODE_SLASH = 47, /* / */ - SAPP_KEYCODE_0 = 48, - SAPP_KEYCODE_1 = 49, - SAPP_KEYCODE_2 = 50, - SAPP_KEYCODE_3 = 51, - SAPP_KEYCODE_4 = 52, - SAPP_KEYCODE_5 = 53, - SAPP_KEYCODE_6 = 54, - SAPP_KEYCODE_7 = 55, - SAPP_KEYCODE_8 = 56, - SAPP_KEYCODE_9 = 57, - SAPP_KEYCODE_SEMICOLON = 59, /* ; */ - SAPP_KEYCODE_EQUAL = 61, /* = */ - SAPP_KEYCODE_A = 65, - SAPP_KEYCODE_B = 66, - SAPP_KEYCODE_C = 67, - SAPP_KEYCODE_D = 68, - SAPP_KEYCODE_E = 69, - SAPP_KEYCODE_F = 70, - SAPP_KEYCODE_G = 71, - SAPP_KEYCODE_H = 72, - SAPP_KEYCODE_I = 73, - SAPP_KEYCODE_J = 74, - SAPP_KEYCODE_K = 75, - SAPP_KEYCODE_L = 76, - SAPP_KEYCODE_M = 77, - SAPP_KEYCODE_N = 78, - SAPP_KEYCODE_O = 79, - SAPP_KEYCODE_P = 80, - SAPP_KEYCODE_Q = 81, - SAPP_KEYCODE_R = 82, - SAPP_KEYCODE_S = 83, - SAPP_KEYCODE_T = 84, - SAPP_KEYCODE_U = 85, - SAPP_KEYCODE_V = 86, - SAPP_KEYCODE_W = 87, - SAPP_KEYCODE_X = 88, - SAPP_KEYCODE_Y = 89, - SAPP_KEYCODE_Z = 90, - SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ - SAPP_KEYCODE_BACKSLASH = 92, /* \ */ - SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ - SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ - SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ - SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ - SAPP_KEYCODE_ESCAPE = 256, - SAPP_KEYCODE_ENTER = 257, - SAPP_KEYCODE_TAB = 258, - SAPP_KEYCODE_BACKSPACE = 259, - SAPP_KEYCODE_INSERT = 260, - SAPP_KEYCODE_DELETE = 261, - SAPP_KEYCODE_RIGHT = 262, - SAPP_KEYCODE_LEFT = 263, - SAPP_KEYCODE_DOWN = 264, - SAPP_KEYCODE_UP = 265, - SAPP_KEYCODE_PAGE_UP = 266, - SAPP_KEYCODE_PAGE_DOWN = 267, - SAPP_KEYCODE_HOME = 268, - SAPP_KEYCODE_END = 269, - SAPP_KEYCODE_CAPS_LOCK = 280, - SAPP_KEYCODE_SCROLL_LOCK = 281, - SAPP_KEYCODE_NUM_LOCK = 282, - SAPP_KEYCODE_PRINT_SCREEN = 283, - SAPP_KEYCODE_PAUSE = 284, - SAPP_KEYCODE_F1 = 290, - SAPP_KEYCODE_F2 = 291, - SAPP_KEYCODE_F3 = 292, - SAPP_KEYCODE_F4 = 293, - SAPP_KEYCODE_F5 = 294, - SAPP_KEYCODE_F6 = 295, - SAPP_KEYCODE_F7 = 296, - SAPP_KEYCODE_F8 = 297, - SAPP_KEYCODE_F9 = 298, - SAPP_KEYCODE_F10 = 299, - SAPP_KEYCODE_F11 = 300, - SAPP_KEYCODE_F12 = 301, - SAPP_KEYCODE_F13 = 302, - SAPP_KEYCODE_F14 = 303, - SAPP_KEYCODE_F15 = 304, - SAPP_KEYCODE_F16 = 305, - SAPP_KEYCODE_F17 = 306, - SAPP_KEYCODE_F18 = 307, - SAPP_KEYCODE_F19 = 308, - SAPP_KEYCODE_F20 = 309, - SAPP_KEYCODE_F21 = 310, - SAPP_KEYCODE_F22 = 311, - SAPP_KEYCODE_F23 = 312, - SAPP_KEYCODE_F24 = 313, - SAPP_KEYCODE_F25 = 314, - SAPP_KEYCODE_KP_0 = 320, - SAPP_KEYCODE_KP_1 = 321, - SAPP_KEYCODE_KP_2 = 322, - SAPP_KEYCODE_KP_3 = 323, - SAPP_KEYCODE_KP_4 = 324, - SAPP_KEYCODE_KP_5 = 325, - SAPP_KEYCODE_KP_6 = 326, - SAPP_KEYCODE_KP_7 = 327, - SAPP_KEYCODE_KP_8 = 328, - SAPP_KEYCODE_KP_9 = 329, - SAPP_KEYCODE_KP_DECIMAL = 330, - SAPP_KEYCODE_KP_DIVIDE = 331, - SAPP_KEYCODE_KP_MULTIPLY = 332, - SAPP_KEYCODE_KP_SUBTRACT = 333, - SAPP_KEYCODE_KP_ADD = 334, - SAPP_KEYCODE_KP_ENTER = 335, - SAPP_KEYCODE_KP_EQUAL = 336, - SAPP_KEYCODE_LEFT_SHIFT = 340, - SAPP_KEYCODE_LEFT_CONTROL = 341, - SAPP_KEYCODE_LEFT_ALT = 342, - SAPP_KEYCODE_LEFT_SUPER = 343, - SAPP_KEYCODE_RIGHT_SHIFT = 344, - SAPP_KEYCODE_RIGHT_CONTROL = 345, - SAPP_KEYCODE_RIGHT_ALT = 346, - SAPP_KEYCODE_RIGHT_SUPER = 347, - SAPP_KEYCODE_MENU = 348, - SAPP_KEYCODE_BACK = 349, -} sapp_keycode; - -typedef struct sapp_touchpoint { - uintptr_t identifier; - float pos_x; - float pos_y; - bool changed; -} sapp_touchpoint; - -typedef enum sapp_mousebutton { - SAPP_MOUSEBUTTON_LEFT = 0x0, - SAPP_MOUSEBUTTON_RIGHT = 0x1, - SAPP_MOUSEBUTTON_MIDDLE = 0x2, - SAPP_MOUSEBUTTON_INVALID = 0x100, -} sapp_mousebutton; - -enum { - SAPP_MODIFIER_SHIFT = 0x1, - SAPP_MODIFIER_CTRL = 0x2, - SAPP_MODIFIER_ALT = 0x4, - SAPP_MODIFIER_SUPER = 0x8 -}; - -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; - int window_height; - int framebuffer_width; - int framebuffer_height; -} sapp_event; - -typedef struct sapp_desc { - 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 (*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) */ - - /* 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 */ -} sapp_desc; - -/* HTML5 specific: request and response structs for - asynchronously loading dropped-file content. -*/ -typedef enum sapp_html5_fetch_error { - SAPP_HTML5_FETCH_ERROR_NO_ERROR, - SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, - SAPP_HTML5_FETCH_ERROR_OTHER, -} sapp_html5_fetch_error; - -typedef struct sapp_html5_fetch_response { - bool succeeded; /* true if the loading operation has succeeded */ - sapp_html5_fetch_error error_code; - int file_index; /* index of the dropped file (0..sapp_get_num_dropped_filed()-1) */ - uint32_t fetched_size; /* size in bytes of loaded data */ - void* buffer_ptr; /* pointer to user-provided buffer which contains the loaded data */ - uint32_t buffer_size; /* size of user-provided buffer (buffer_size >= fetched_size) */ - void* user_data; /* user-provided user data pointer */ -} sapp_html5_fetch_response; - -typedef struct sapp_html5_fetch_request { - int dropped_file_index; /* 0..sapp_get_num_dropped_files()-1 */ - void (*callback)(const sapp_html5_fetch_response*); /* response callback function pointer (required) */ - void* buffer_ptr; /* pointer to buffer to load data into */ - uint32_t buffer_size; /* size in bytes of buffer */ - void* user_data; /* optional userdata pointer */ -} sapp_html5_fetch_request; - -/* user-provided functions */ -extern sapp_desc sokol_main(int argc, char* argv[]); - -/* returns true after sokol-app has been initialized */ -SOKOL_APP_API_DECL bool sapp_isvalid(void); -/* returns the current framebuffer width in pixels */ -SOKOL_APP_API_DECL int sapp_width(void); -/* same as sapp_width(), but returns float */ -SOKOL_APP_API_DECL float sapp_widthf(void); -/* returns the current framebuffer height in pixels */ -SOKOL_APP_API_DECL int sapp_height(void); -/* same as sapp_height(), but returns float */ -SOKOL_APP_API_DECL float sapp_heightf(void); -/* get default framebuffer color pixel format */ -SOKOL_APP_API_DECL int sapp_color_format(void); -/* get default framebuffer depth pixel format */ -SOKOL_APP_API_DECL int sapp_depth_format(void); -/* get default framebuffer sample count */ -SOKOL_APP_API_DECL int sapp_sample_count(void); -/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ -SOKOL_APP_API_DECL bool sapp_high_dpi(void); -/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ -SOKOL_APP_API_DECL float sapp_dpi_scale(void); -/* show or hide the mobile device onscreen keyboard */ -SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); -/* return true if the mobile device onscreen keyboard is currently shown */ -SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); -/* query fullscreen mode */ -SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); -/* toggle fullscreen mode */ -SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL void sapp_show_mouse(bool show); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL bool sapp_mouse_shown(); -/* enable/disable mouse-pointer-lock mode */ -SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); -/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ -SOKOL_APP_API_DECL bool sapp_mouse_locked(void); -/* return the userdata pointer optionally provided in sapp_desc */ -SOKOL_APP_API_DECL void* sapp_userdata(void); -/* return a copy of the sapp_desc structure */ -SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); -/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ -SOKOL_APP_API_DECL void sapp_request_quit(void); -/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ -SOKOL_APP_API_DECL void sapp_cancel_quit(void); -/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ -SOKOL_APP_API_DECL void sapp_quit(void); -/* call from inside event callback to consume the current event (don't forward to platform) */ -SOKOL_APP_API_DECL void sapp_consume_event(void); -/* get the current frame counter (for comparison with sapp_event.frame_count) */ -SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); -/* write string into clipboard */ -SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); -/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ -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); -/* 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 */ -SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); - -/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ -SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); - -/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ -SOKOL_APP_API_DECL bool sapp_gles2(void); - -/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ -SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); -/* HTML5: get byte size of a dropped file */ -SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); -/* HTML5: asynchronously load the content of a dropped file */ -SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); - -/* Metal: get bridged pointer to Metal device object */ -SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); -/* Metal: get bridged pointer to this frame's renderpass descriptor */ -SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); -/* Metal: get bridged pointer to current drawable */ -SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); -/* macOS: get bridged pointer to macOS NSWindow */ -SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); -/* iOS: get bridged pointer to iOS UIWindow */ -SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); -SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); - -/* D3D11: get pointer to ID3D11Device object */ -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 ID3D11RenderTargetView object */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void); -/* D3D11: get pointer to ID3D11DepthStencilView */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); -/* Win32: get the HWND window handle */ -SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); - -/* WebGPU: get WGPUDevice handle */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); -/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); -/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); -/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); - -/* Android: get native activity handle */ -SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); -SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); - -#ifdef __cplusplus -} /* extern "C" */ - -/* reference-based equivalents for C++ */ -inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } - -#endif - -// this WinRT specific hack is required when wWinMain is in a static library -#if defined(_MSC_VER) && defined(UNICODE) -#include -#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -#pragma comment(linker, "/include:wWinMain") -#endif -#endif - -#endif // SOKOL_APP_INCLUDED - -/*-- IMPLEMENTATION ----------------------------------------------------------*/ -#ifdef SOKOL_APP_IMPL -#define SOKOL_APP_IMPL_INCLUDED (1) - -#include // memset -#include // size_t - -/* check if the config defines are alright */ -#if defined(__APPLE__) - // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting - #if !defined(__cplusplus) - #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) - #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" - #endif - #endif - #define _SAPP_APPLE (1) - #include - #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE - /* MacOS */ - #define _SAPP_MACOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") - #endif - #else - /* iOS or iOS Simulator */ - #define _SAPP_IOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) - #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") - #endif - #endif -#elif defined(__EMSCRIPTEN__) - /* emscripten (asm.js or wasm) */ - #define _SAPP_EMSCRIPTEN (1) - #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU) - #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU") - #endif -#elif defined(_WIN32) - /* Windows (D3D11 or GL) */ - #include - #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) - #define _SAPP_UWP (1) - #if !defined(SOKOL_D3D11) - #error("sokol_app.h: unknown 3D API selected for UWP, must be SOKOL_D3D11") - #endif - #if !defined(__cplusplus) - #error("sokol_app.h: UWP bindings require C++/17") - #endif - #else - #define _SAPP_WIN32 (1) - #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") - #endif - #endif -#elif defined(__ANDROID__) - /* Android */ - #define _SAPP_ANDROID (1) - #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) - #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2") - #endif - #if defined(SOKOL_NO_ENTRY) - #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") - #endif -#elif defined(__linux__) || defined(__unix__) - /* Linux */ - #define _SAPP_LINUX (1) - #if !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33") - #endif -#else -#error "sokol_app.h: Unknown platform" -#endif - -#ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL -#endif -#ifndef SOKOL_DEBUG - #ifndef NDEBUG - #define SOKOL_DEBUG (1) - #endif -#endif -#ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) -#endif -#ifndef SOKOL_UNREACHABLE - #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) -#endif -#if !defined(SOKOL_CALLOC) || !defined(SOKOL_FREE) - #include -#endif -#if !defined(SOKOL_CALLOC) - #define SOKOL_CALLOC(n,s) calloc(n,s) -#endif -#if !defined(SOKOL_FREE) - #define SOKOL_FREE(p) free(p) -#endif -#ifndef SOKOL_LOG - #ifdef SOKOL_DEBUG - #if defined(__ANDROID__) - #include - #define SOKOL_LOG(s) { SOKOL_ASSERT(s); __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s); } - #else - #include - #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } - #endif - #else - #define SOKOL_LOG(s) - #endif -#endif -#ifndef SOKOL_ABORT - #include - #define SOKOL_ABORT() abort() -#endif -#ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) || defined(__clang__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif -#endif -#ifndef _SOKOL_UNUSED - #define _SOKOL_UNUSED(x) (void)(x) -#endif - -/*== PLATFORM SPECIFIC INCLUDES AND DEFINES ==================================*/ -#if defined(_SAPP_APPLE) - #if defined(SOKOL_METAL) - #import - #import - #endif - #if defined(_SAPP_MACOS) - #if !defined(SOKOL_METAL) - #ifndef GL_SILENCE_DEPRECATION - #define GL_SILENCE_DEPRECATION - #endif - #include - #include - #endif - #elif defined(_SAPP_IOS) - #import - #if !defined(SOKOL_METAL) - #import - #include - #include - #endif - #endif -#elif defined(_SAPP_EMSCRIPTEN) - #if defined(SOKOL_GLES3) - #include - #elif defined(SOKOL_GLES2) - #ifndef GL_EXT_PROTOTYPES - #define GL_GLEXT_PROTOTYPES - #endif - #include - #include - #elif defined(SOKOL_WGPU) - #include - #endif - #include - #include -#elif defined(_SAPP_WIN32) - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ - #pragma warning(disable:4054) /* 'type cast': from function pointer */ - #pragma warning(disable:4055) /* 'type cast': from data pointer */ - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ - #pragma warning(disable:4996) /* 'freopen': This function or variable may be unsafe. */ - #endif - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #ifndef NOMINMAX - #define NOMINMAX - #endif - #include - #include - #include - #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem - #if defined(SOKOL_WIN32_FORCE_MAIN) - #pragma comment (linker, "/subsystem:console") - #else - #pragma comment (linker, "/subsystem:windows") - #endif - #endif - #include /* freopen() */ - - #pragma comment (lib, "kernel32") - #pragma comment (lib, "user32") - #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ - #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) - #ifndef D3D11_NO_HELPERS - #define D3D11_NO_HELPERS - #endif - #include - #include - // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it - #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) - #endif - #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ - #define WM_MOUSEHWHEEL (0x020E) - #endif -#elif defined(_SAPP_UWP) - #ifndef NOMINMAX - #define NOMINMAX - #endif - - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4054) /* 'type cast': from function pointer */ - #pragma warning(disable:4055) /* 'type cast': from data pointer */ - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ - #endif - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #include - #include - #include - - #pragma comment (lib, "WindowsApp") - #pragma comment (lib, "dxguid") -#elif defined(_SAPP_ANDROID) - #include - #include - #include - #include - #include - #if defined(SOKOL_GLES3) - #include - #else - #ifndef GL_EXT_PROTOTYPES - #define GL_GLEXT_PROTOTYPES - #endif - #include - #include - #endif -#elif defined(_SAPP_LINUX) - #define GL_GLEXT_PROTOTYPES - #include - #include - #include - #include - #include - #include - #include - #include - #include /* CARD32 */ - #include - #include /* dlopen, dlsym, dlclose */ - #include /* LONG_MAX */ - #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ -#endif - -/*== MACOS DECLARATIONS ======================================================*/ -#if defined(_SAPP_MACOS) -@interface _sapp_macos_app_delegate : NSObject -@end -@interface _sapp_macos_window : NSWindow -@end -@interface _sapp_macos_window_delegate : NSObject -@end -#if defined(SOKOL_METAL) - @interface _sapp_macos_view : MTKView - @end -#elif defined(SOKOL_GLCORE33) - @interface _sapp_macos_view : NSOpenGLView - - (void)timerFired:(id)sender; - @end -#endif // SOKOL_GLCORE33 - -typedef struct { - uint32_t flags_changed_store; - uint8_t mouse_buttons; - NSWindow* window; - NSTrackingArea* tracking_area; - _sapp_macos_app_delegate* app_dlg; - _sapp_macos_window_delegate* win_dlg; - _sapp_macos_view* view; - #if defined(SOKOL_METAL) - id mtl_device; - #endif -} _sapp_macos_t; - -#endif // _SAPP_MACOS - -/*== IOS DECLARATIONS ========================================================*/ -#if defined(_SAPP_IOS) - -@interface _sapp_app_delegate : NSObject -@end -@interface _sapp_textfield_dlg : NSObject -- (void)keyboardWasShown:(NSNotification*)notif; -- (void)keyboardWillBeHidden:(NSNotification*)notif; -- (void)keyboardDidChangeFrame:(NSNotification*)notif; -@end -#if defined(SOKOL_METAL) - @interface _sapp_ios_view : MTKView; - @end -#else - @interface _sapp_ios_view : GLKView - @end -#endif - -typedef struct { - UIWindow* window; - _sapp_ios_view* view; - UITextField* textfield; - _sapp_textfield_dlg* textfield_dlg; - #if defined(SOKOL_METAL) - UIViewController* view_ctrl; - id mtl_device; - #else - GLKViewController* view_ctrl; - EAGLContext* eagl_ctx; - #endif - bool suspended; -} _sapp_ios_t; - -#endif // _SAPP_IOS - -/*== EMSCRIPTEN DECLARATIONS =================================================*/ -#if defined(_SAPP_EMSCRIPTEN) - -#if defined(SOKOL_WGPU) -typedef struct { - int state; - WGPUDevice device; - WGPUSwapChain swapchain; - WGPUTextureFormat render_format; - WGPUTexture msaa_tex; - WGPUTexture depth_stencil_tex; - WGPUTextureView swapchain_view; - WGPUTextureView msaa_view; - WGPUTextureView depth_stencil_view; -} _sapp_wgpu_t; -#endif - -typedef struct { - bool textfield_created; - bool wants_show_keyboard; - bool wants_hide_keyboard; - bool mouse_lock_requested; - #if defined(SOKOL_WGPU) - _sapp_wgpu_t wgpu; - #endif -} _sapp_emsc_t; -#endif // _SAPP_EMSCRIPTEN - -/*== WIN32 DECLARATIONS ======================================================*/ -#if defined(SOKOL_D3D11) && (defined(_SAPP_WIN32) || defined(_SAPP_UWP)) -typedef struct { - ID3D11Device* device; - ID3D11DeviceContext* device_context; - ID3D11Texture2D* rt; - ID3D11RenderTargetView* rtv; - ID3D11Texture2D* msaa_rt; - ID3D11RenderTargetView* msaa_rtv; - ID3D11Texture2D* ds; - ID3D11DepthStencilView* dsv; - DXGI_SWAP_CHAIN_DESC swap_chain_desc; - IDXGISwapChain* swap_chain; -} _sapp_d3d11_t; -#endif - -/*== WIN32 DECLARATIONS ======================================================*/ -#if defined(_SAPP_WIN32) - -#ifndef DPI_ENUMS_DECLARED -typedef enum PROCESS_DPI_AWARENESS -{ - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 -} PROCESS_DPI_AWARENESS; -typedef enum MONITOR_DPI_TYPE { - MDT_EFFECTIVE_DPI = 0, - MDT_ANGULAR_DPI = 1, - MDT_RAW_DPI = 2, - MDT_DEFAULT = MDT_EFFECTIVE_DPI -} MONITOR_DPI_TYPE; -#endif /*DPI_ENUMS_DECLARED*/ - -typedef struct { - bool aware; - float content_scale; - float window_scale; - float mouse_scale; -} _sapp_win32_dpi_t; - -typedef struct { - HWND hwnd; - HDC dc; - UINT orig_codepage; - LONG mouse_locked_x, mouse_locked_y; - bool is_win10_or_greater; - bool in_create_window; - bool iconified; - bool mouse_tracked; - uint8_t mouse_capture_mask; - _sapp_win32_dpi_t dpi; - bool raw_input_mousepos_valid; - LONG raw_input_mousepos_x; - LONG raw_input_mousepos_y; - uint8_t raw_input_data[256]; -} _sapp_win32_t; - -#if defined(SOKOL_GLCORE33) -#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_NO_ACCELERATION_ARB 0x2025 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_SAMPLES_ARB 0x2042 -#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define ERROR_INVALID_VERSION_ARB 0x2095 -#define ERROR_INVALID_PROFILE_ARB 0x2096 -#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 -typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); -typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); -typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); -typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); -typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); -typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); -typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); -typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); - -typedef struct { - HINSTANCE opengl32; - HGLRC gl_ctx; - PFN_wglCreateContext CreateContext; - PFN_wglDeleteContext DeleteContext; - PFN_wglGetProcAddress GetProcAddress; - PFN_wglGetCurrentDC GetCurrentDC; - PFN_wglMakeCurrent MakeCurrent; - PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; - PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; - PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; - PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - bool ext_swap_control; - bool arb_multisample; - bool arb_pixel_format; - bool arb_create_context; - bool arb_create_context_profile; - HWND msg_hwnd; - HDC msg_dc; -} _sapp_wgl_t; -#endif // SOKOL_GLCORE33 - -#endif // _SAPP_WIN32 - -/*== UWP DECLARATIONS ======================================================*/ -#if defined(_SAPP_UWP) - -typedef struct { - float content_scale; - float window_scale; - float mouse_scale; -} _sapp_uwp_dpi_t; - -typedef struct { - bool mouse_tracked; - uint8_t mouse_buttons; - _sapp_uwp_dpi_t dpi; -} _sapp_uwp_t; - -#endif // _SAPP_UWP - -/*== ANDROID DECLARATIONS ====================================================*/ - -#if defined(_SAPP_ANDROID) -typedef enum { - _SOKOL_ANDROID_MSG_CREATE, - _SOKOL_ANDROID_MSG_RESUME, - _SOKOL_ANDROID_MSG_PAUSE, - _SOKOL_ANDROID_MSG_FOCUS, - _SOKOL_ANDROID_MSG_NO_FOCUS, - _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, - _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, - _SOKOL_ANDROID_MSG_DESTROY, -} _sapp_android_msg_t; - -typedef struct { - pthread_t thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - int read_from_main_fd; - int write_from_main_fd; -} _sapp_android_pt_t; - -typedef struct { - ANativeWindow* window; - AInputQueue* input; -} _sapp_android_resources_t; - -typedef struct { - ANativeActivity* activity; - _sapp_android_pt_t pt; - _sapp_android_resources_t pending; - _sapp_android_resources_t current; - ALooper* looper; - bool is_thread_started; - bool is_thread_stopping; - bool is_thread_stopped; - bool has_created; - bool has_resumed; - bool has_focus; - EGLConfig config; - EGLDisplay display; - EGLContext context; - EGLSurface surface; -} _sapp_android_t; - -#endif // _SAPP_ANDROID - -/*== LINUX DECLARATIONS ======================================================*/ -#if defined(_SAPP_LINUX) - -#define _SAPP_X11_XDND_VERSION (5) - -#define GLX_VENDOR 1 -#define GLX_RGBA_BIT 0x00000001 -#define GLX_WINDOW_BIT 0x00000001 -#define GLX_DRAWABLE_TYPE 0x8010 -#define GLX_RENDER_TYPE 0x8011 -#define GLX_DOUBLEBUFFER 5 -#define GLX_RED_SIZE 8 -#define GLX_GREEN_SIZE 9 -#define GLX_BLUE_SIZE 10 -#define GLX_ALPHA_SIZE 11 -#define GLX_DEPTH_SIZE 12 -#define GLX_STENCIL_SIZE 13 -#define GLX_SAMPLES 0x186a1 -#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define GLX_CONTEXT_FLAGS_ARB 0x2094 - -typedef XID GLXWindow; -typedef XID GLXDrawable; -typedef struct __GLXFBConfig* GLXFBConfig; -typedef struct __GLXcontext* GLXContext; -typedef void (*__GLXextproc)(void); - -typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); -typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); -typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); -typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); -typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); -typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); -typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); -typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); -typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); -typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const GLubyte *procName); -typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); -typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); -typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); -typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); - -typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); -typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); - -typedef struct { - bool available; - int major_opcode; - int event_base; - int error_base; - int major; - int minor; -} _sapp_xi_t; - -typedef struct { - int version; - Window source; - Atom format; - Atom XdndAware; - Atom XdndEnter; - Atom XdndPosition; - Atom XdndStatus; - Atom XdndActionCopy; - Atom XdndDrop; - Atom XdndFinished; - Atom XdndSelection; - Atom XdndTypeList; - Atom text_uri_list; -} _sapp_xdnd_t; - -typedef struct { - uint8_t mouse_buttons; - Display* display; - int screen; - Window root; - Colormap colormap; - Window window; - Cursor hidden_cursor; - int window_state; - float dpi; - unsigned char error_code; - Atom UTF8_STRING; - Atom WM_PROTOCOLS; - Atom WM_DELETE_WINDOW; - Atom WM_STATE; - Atom NET_WM_NAME; - Atom NET_WM_ICON_NAME; - Atom NET_WM_STATE; - Atom NET_WM_STATE_FULLSCREEN; - _sapp_xi_t xi; - _sapp_xdnd_t xdnd; -} _sapp_x11_t; - -typedef struct { - void* libgl; - int major; - int minor; - int event_base; - int error_base; - GLXContext ctx; - GLXWindow window; - - // GLX 1.3 functions - PFNGLXGETFBCONFIGSPROC GetFBConfigs; - PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; - PFNGLXGETCLIENTSTRINGPROC GetClientString; - PFNGLXQUERYEXTENSIONPROC QueryExtension; - PFNGLXQUERYVERSIONPROC QueryVersion; - PFNGLXDESTROYCONTEXTPROC DestroyContext; - PFNGLXMAKECURRENTPROC MakeCurrent; - PFNGLXSWAPBUFFERSPROC SwapBuffers; - PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; - PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; - PFNGLXCREATEWINDOWPROC CreateWindow; - PFNGLXDESTROYWINDOWPROC DestroyWindow; - - // GLX 1.4 and extension functions - PFNGLXGETPROCADDRESSPROC GetProcAddress; - PFNGLXGETPROCADDRESSPROC GetProcAddressARB; - PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; - PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - - // extension availability - bool EXT_swap_control; - bool MESA_swap_control; - bool ARB_multisample; - bool ARB_create_context; - bool ARB_create_context_profile; -} _sapp_glx_t; - -#endif // _SAPP_LINUX - -/*== COMMON DECLARATIONS =====================================================*/ - -/* helper macros */ -#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) -#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) - -#define _SAPP_MAX_TITLE_LENGTH (128) -/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ -#define _SAPP_PIXELFORMAT_RGBA8 (23) -#define _SAPP_PIXELFORMAT_BGRA8 (27) -#define _SAPP_PIXELFORMAT_DEPTH (41) -#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (42) - -#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) - // this is ARC compatible - #if defined(__cplusplus) - #define _SAPP_CLEAR(type, item) { item = (type) { }; } - #else - #define _SAPP_CLEAR(type, item) { item = (type) { 0 }; } - #endif -#else - #define _SAPP_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } -#endif - -typedef struct { - bool enabled; - int buf_size; - char* buffer; -} _sapp_clipboard_t; - -typedef struct { - bool enabled; - int max_files; - int max_path_length; - int num_files; - int buf_size; - char* buffer; -} _sapp_drop_t; - -typedef struct { - float x, y; - float dx, dy; - bool shown; - bool locked; - bool pos_valid; -} _sapp_mouse_t; - -typedef struct { - sapp_desc desc; - bool valid; - bool fullscreen; - bool gles2_fallback; - bool first_frame; - bool init_called; - bool cleanup_called; - bool quit_requested; - bool quit_ordered; - bool event_consumed; - bool html5_ask_leave_site; - bool onscreen_keyboard_shown; - int window_width; - int window_height; - int framebuffer_width; - int framebuffer_height; - int sample_count; - int swap_interval; - float dpi_scale; - uint64_t frame_count; - sapp_event event; - _sapp_mouse_t mouse; - _sapp_clipboard_t clipboard; - _sapp_drop_t drop; - #if defined(_SAPP_MACOS) - _sapp_macos_t macos; - #elif defined(_SAPP_IOS) - _sapp_ios_t ios; - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_t emsc; - #elif defined(_SAPP_WIN32) - _sapp_win32_t win32; - #if defined(SOKOL_D3D11) - _sapp_d3d11_t d3d11; - #elif defined(SOKOL_GLCORE33) - _sapp_wgl_t wgl; - #endif - #elif defined(_SAPP_UWP) - _sapp_uwp_t uwp; - #if defined(SOKOL_D3D11) - _sapp_d3d11_t d3d11; - #endif - #elif defined(_SAPP_ANDROID) - _sapp_android_t android; - #elif defined(_SAPP_LINUX) - _sapp_x11_t x11; - _sapp_glx_t glx; - #endif - char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; - char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ - wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ - sapp_keycode keycodes[SAPP_MAX_KEYCODES]; -} _sapp_t; -static _sapp_t _sapp; - -/*=== OPTIONAL MINI GL LOADER FOR WIN32/WGL ==================================*/ -#if defined(_SAPP_WIN32) && defined(SOKOL_GLCORE33) && !defined(SOKOL_WIN32_NO_GL_LOADER) -#define __gl_h_ 1 -#define __gl32_h_ 1 -#define __gl31_h_ 1 -#define __GL_H__ 1 -#define __glext_h_ 1 -#define __GLEXT_H_ 1 -#define __gltypes_h_ 1 -#define __glcorearb_h_ 1 -#define __gl_glcorearb_h_ 1 -#define GL_APIENTRY APIENTRY - -typedef unsigned int GLenum; -typedef unsigned int GLuint; -typedef int GLsizei; -typedef char GLchar; -typedef ptrdiff_t GLintptr; -typedef ptrdiff_t GLsizeiptr; -typedef double GLclampd; -typedef unsigned short GLushort; -typedef unsigned char GLubyte; -typedef unsigned char GLboolean; -typedef uint64_t GLuint64; -typedef double GLdouble; -typedef unsigned short GLhalf; -typedef float GLclampf; -typedef unsigned int GLbitfield; -typedef signed char GLbyte; -typedef short GLshort; -typedef void GLvoid; -typedef int64_t GLint64; -typedef float GLfloat; -typedef struct __GLsync * GLsync; -typedef int GLint; -#define GL_INT_2_10_10_10_REV 0x8D9F -#define GL_R32F 0x822E -#define GL_PROGRAM_POINT_SIZE 0x8642 -#define GL_STENCIL_ATTACHMENT 0x8D20 -#define GL_DEPTH_ATTACHMENT 0x8D00 -#define GL_COLOR_ATTACHMENT2 0x8CE2 -#define GL_COLOR_ATTACHMENT0 0x8CE0 -#define GL_R16F 0x822D -#define GL_COLOR_ATTACHMENT22 0x8CF6 -#define GL_DRAW_FRAMEBUFFER 0x8CA9 -#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 -#define GL_NUM_EXTENSIONS 0x821D -#define GL_INFO_LOG_LENGTH 0x8B84 -#define GL_VERTEX_SHADER 0x8B31 -#define GL_INCR 0x1E02 -#define GL_DYNAMIC_DRAW 0x88E8 -#define GL_STATIC_DRAW 0x88E4 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 -#define GL_TEXTURE_CUBE_MAP 0x8513 -#define GL_FUNC_SUBTRACT 0x800A -#define GL_FUNC_REVERSE_SUBTRACT 0x800B -#define GL_CONSTANT_COLOR 0x8001 -#define GL_DECR_WRAP 0x8508 -#define GL_R8 0x8229 -#define GL_LINEAR_MIPMAP_LINEAR 0x2703 -#define GL_ELEMENT_ARRAY_BUFFER 0x8893 -#define GL_SHORT 0x1402 -#define GL_DEPTH_TEST 0x0B71 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 -#define GL_LINK_STATUS 0x8B82 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 -#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E -#define GL_RGBA16F 0x881A -#define GL_CONSTANT_ALPHA 0x8003 -#define GL_READ_FRAMEBUFFER 0x8CA8 -#define GL_TEXTURE0 0x84C0 -#define GL_TEXTURE_MIN_LOD 0x813A -#define GL_CLAMP_TO_EDGE 0x812F -#define GL_UNSIGNED_SHORT_5_6_5 0x8363 -#define GL_TEXTURE_WRAP_R 0x8072 -#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 -#define GL_NEAREST_MIPMAP_NEAREST 0x2700 -#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#define GL_SRC_ALPHA_SATURATE 0x0308 -#define GL_STREAM_DRAW 0x88E0 -#define GL_ONE 1 -#define GL_NEAREST_MIPMAP_LINEAR 0x2702 -#define GL_RGB10_A2 0x8059 -#define GL_RGBA8 0x8058 -#define GL_COLOR_ATTACHMENT1 0x8CE1 -#define GL_RGBA4 0x8056 -#define GL_RGB8 0x8051 -#define GL_ARRAY_BUFFER 0x8892 -#define GL_STENCIL 0x1802 -#define GL_TEXTURE_2D 0x0DE1 -#define GL_DEPTH 0x1801 -#define GL_FRONT 0x0404 -#define GL_STENCIL_BUFFER_BIT 0x00000400 -#define GL_REPEAT 0x2901 -#define GL_RGBA 0x1908 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 -#define GL_DECR 0x1E03 -#define GL_FRAGMENT_SHADER 0x8B30 -#define GL_FLOAT 0x1406 -#define GL_TEXTURE_MAX_LOD 0x813B -#define GL_DEPTH_COMPONENT 0x1902 -#define GL_ONE_MINUS_DST_ALPHA 0x0305 -#define GL_COLOR 0x1800 -#define GL_TEXTURE_2D_ARRAY 0x8C1A -#define GL_TRIANGLES 0x0004 -#define GL_UNSIGNED_BYTE 0x1401 -#define GL_TEXTURE_MAG_FILTER 0x2800 -#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 -#define GL_NONE 0 -#define GL_SRC_COLOR 0x0300 -#define GL_BYTE 0x1400 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A -#define GL_LINE_STRIP 0x0003 -#define GL_TEXTURE_3D 0x806F -#define GL_CW 0x0900 -#define GL_LINEAR 0x2601 -#define GL_RENDERBUFFER 0x8D41 -#define GL_GEQUAL 0x0206 -#define GL_COLOR_BUFFER_BIT 0x00004000 -#define GL_RGBA32F 0x8814 -#define GL_BLEND 0x0BE2 -#define GL_ONE_MINUS_SRC_ALPHA 0x0303 -#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 -#define GL_TEXTURE_WRAP_T 0x2803 -#define GL_TEXTURE_WRAP_S 0x2802 -#define GL_TEXTURE_MIN_FILTER 0x2801 -#define GL_LINEAR_MIPMAP_NEAREST 0x2701 -#define GL_EXTENSIONS 0x1F03 -#define GL_NO_ERROR 0 -#define GL_REPLACE 0x1E01 -#define GL_KEEP 0x1E00 -#define GL_CCW 0x0901 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 -#define GL_RGB 0x1907 -#define GL_TRIANGLE_STRIP 0x0005 -#define GL_FALSE 0 -#define GL_ZERO 0 -#define GL_CULL_FACE 0x0B44 -#define GL_INVERT 0x150A -#define GL_INT 0x1404 -#define GL_UNSIGNED_INT 0x1405 -#define GL_UNSIGNED_SHORT 0x1403 -#define GL_NEAREST 0x2600 -#define GL_SCISSOR_TEST 0x0C11 -#define GL_LEQUAL 0x0203 -#define GL_STENCIL_TEST 0x0B90 -#define GL_DITHER 0x0BD0 -#define GL_DEPTH_COMPONENT16 0x81A5 -#define GL_EQUAL 0x0202 -#define GL_FRAMEBUFFER 0x8D40 -#define GL_RGB5 0x8050 -#define GL_LINES 0x0001 -#define GL_DEPTH_BUFFER_BIT 0x00000100 -#define GL_SRC_ALPHA 0x0302 -#define GL_INCR_WRAP 0x8507 -#define GL_LESS 0x0201 -#define GL_MULTISAMPLE 0x809D -#define GL_FRAMEBUFFER_BINDING 0x8CA6 -#define GL_BACK 0x0405 -#define GL_ALWAYS 0x0207 -#define GL_FUNC_ADD 0x8006 -#define GL_ONE_MINUS_DST_COLOR 0x0307 -#define GL_NOTEQUAL 0x0205 -#define GL_DST_COLOR 0x0306 -#define GL_COMPILE_STATUS 0x8B81 -#define GL_RED 0x1903 -#define GL_COLOR_ATTACHMENT3 0x8CE3 -#define GL_DST_ALPHA 0x0304 -#define GL_RGB5_A1 0x8057 -#define GL_GREATER 0x0204 -#define GL_POLYGON_OFFSET_FILL 0x8037 -#define GL_TRUE 1 -#define GL_NEVER 0x0200 -#define GL_POINTS 0x0000 -#define GL_ONE_MINUS_SRC_COLOR 0x0301 -#define GL_MIRRORED_REPEAT 0x8370 -#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D -#define GL_R11F_G11F_B10F 0x8C3A -#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B -#define GL_RGBA32UI 0x8D70 -#define GL_RGB32UI 0x8D71 -#define GL_RGBA16UI 0x8D76 -#define GL_RGB16UI 0x8D77 -#define GL_RGBA8UI 0x8D7C -#define GL_RGB8UI 0x8D7D -#define GL_RGBA32I 0x8D82 -#define GL_RGB32I 0x8D83 -#define GL_RGBA16I 0x8D88 -#define GL_RGB16I 0x8D89 -#define GL_RGBA8I 0x8D8E -#define GL_RGB8I 0x8D8F -#define GL_RED_INTEGER 0x8D94 -#define GL_RG 0x8227 -#define GL_RG_INTEGER 0x8228 -#define GL_R8 0x8229 -#define GL_R16 0x822A -#define GL_RG8 0x822B -#define GL_RG16 0x822C -#define GL_R16F 0x822D -#define GL_R32F 0x822E -#define GL_RG16F 0x822F -#define GL_RG32F 0x8230 -#define GL_R8I 0x8231 -#define GL_R8UI 0x8232 -#define GL_R16I 0x8233 -#define GL_R16UI 0x8234 -#define GL_R32I 0x8235 -#define GL_R32UI 0x8236 -#define GL_RG8I 0x8237 -#define GL_RG8UI 0x8238 -#define GL_RG16I 0x8239 -#define GL_RG16UI 0x823A -#define GL_RG32I 0x823B -#define GL_RG32UI 0x823C -#define GL_RGBA_INTEGER 0x8D99 -#define GL_R8_SNORM 0x8F94 -#define GL_RG8_SNORM 0x8F95 -#define GL_RGB8_SNORM 0x8F96 -#define GL_RGBA8_SNORM 0x8F97 -#define GL_R16_SNORM 0x8F98 -#define GL_RG16_SNORM 0x8F99 -#define GL_RGB16_SNORM 0x8F9A -#define GL_RGBA16_SNORM 0x8F9B -#define GL_RGBA16 0x805B -#define GL_MAX_TEXTURE_SIZE 0x0D33 -#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C -#define GL_MAX_3D_TEXTURE_SIZE 0x8073 -#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF -#define GL_MAX_VERTEX_ATTRIBS 0x8869 -#define GL_CLAMP_TO_BORDER 0x812D -#define GL_TEXTURE_BORDER_COLOR 0x1004 -#define GL_CURRENT_PROGRAM 0x8B8D - -// X Macro list of GL function names and signatures -#define _SAPP_GL_FUNCS \ - _SAPP_XMACRO(glBindVertexArray, void, (GLuint array)) \ - _SAPP_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ - _SAPP_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ - _SAPP_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ - _SAPP_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ - _SAPP_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ - _SAPP_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ - _SAPP_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ - _SAPP_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ - _SAPP_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ - _SAPP_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ - _SAPP_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glUseProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ - _SAPP_XMACRO(glLinkProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ - _SAPP_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ - _SAPP_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ - _SAPP_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ - _SAPP_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ - _SAPP_XMACRO(glDeleteShader, void, (GLuint shader)) \ - _SAPP_XMACRO(glDeleteProgram, void, (GLuint program)) \ - _SAPP_XMACRO(glCompileShader, void, (GLuint shader)) \ - _SAPP_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ - _SAPP_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ - _SAPP_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ - _SAPP_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ - _SAPP_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ - _SAPP_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ - _SAPP_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ - _SAPP_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ - _SAPP_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ - _SAPP_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ - _SAPP_XMACRO(glActiveTexture, void, (GLenum texture)) \ - _SAPP_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ - _SAPP_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ - _SAPP_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ - _SAPP_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ - _SAPP_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ - _SAPP_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ - _SAPP_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ - _SAPP_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ - _SAPP_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ - _SAPP_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glCreateShader, GLuint, (GLenum type)) \ - _SAPP_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glClearDepth, void, (GLdouble depth)) \ - _SAPP_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ - _SAPP_XMACRO(glCreateProgram, GLuint, (void)) \ - _SAPP_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ - _SAPP_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ - _SAPP_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ - _SAPP_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ - _SAPP_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ - _SAPP_XMACRO(glDisable, void, (GLenum cap)) \ - _SAPP_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ - _SAPP_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ - _SAPP_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ - _SAPP_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ - _SAPP_XMACRO(glDepthMask, void, (GLboolean flag)) \ - _SAPP_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ - _SAPP_XMACRO(glClearStencil, void, (GLint s)) \ - _SAPP_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ - _SAPP_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ - _SAPP_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ - _SAPP_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ - _SAPP_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ - _SAPP_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ - _SAPP_XMACRO(glEnable, void, (GLenum cap)) \ - _SAPP_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ - _SAPP_XMACRO(glStencilMask, void, (GLuint mask)) \ - _SAPP_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ - _SAPP_XMACRO(glGetError, GLenum, (void)) \ - _SAPP_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ - _SAPP_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ - _SAPP_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ - _SAPP_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, GLfloat* params)) \ - _SAPP_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ - _SAPP_XMACRO(glDepthFunc, void, (GLenum func)) \ - _SAPP_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ - _SAPP_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ - _SAPP_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ - _SAPP_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ - _SAPP_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ - _SAPP_XMACRO(glReadBuffer, void, (GLenum src)) \ - _SAPP_XMACRO(glClear, void, (GLbitfield mask)) \ - _SAPP_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ - _SAPP_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ - _SAPP_XMACRO(glFrontFace, void, (GLenum mode)) \ - _SAPP_XMACRO(glCullFace, void, (GLenum mode)) - -// generate GL function pointer typedefs -#define _SAPP_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; -_SAPP_GL_FUNCS -#undef _SAPP_XMACRO - -// generate GL function pointers -#define _SAPP_XMACRO(name, ret, args) static PFN_ ## name name; -_SAPP_GL_FUNCS -#undef _SAPP_XMACRO - -// helper function to lookup GL functions in GL DLL -_SOKOL_PRIVATE void* _sapp_win32_glgetprocaddr(const char* name) { - void* proc_addr = (void*) _sapp.wgl.GetProcAddress(name); - if (0 == proc_addr) { - proc_addr = (void*) GetProcAddress(_sapp.wgl.opengl32, name); - } - SOKOL_ASSERT(proc_addr); - return proc_addr; -} - -// populate GL function pointers -_SOKOL_PRIVATE void _sapp_win32_gl_loadfuncs(void) { - SOKOL_ASSERT(_sapp.wgl.GetProcAddress); - SOKOL_ASSERT(_sapp.wgl.opengl32); - #define _SAPP_XMACRO(name, ret, args) name = (PFN_ ## name) _sapp_win32_glgetprocaddr(#name); - _SAPP_GL_FUNCS - #undef _SAPP_XMACRO -} - -#endif // _SAPP_WIN32 && SOKOL_GLCORE33 && !SOKOL_WIN32_NO_GL_LOADER - -/*=== PRIVATE HELPER FUNCTIONS ===============================================*/ -_SOKOL_PRIVATE void _sapp_fail(const char* msg) { - if (_sapp.desc.fail_cb) { - _sapp.desc.fail_cb(msg); - } - else if (_sapp.desc.fail_userdata_cb) { - _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data); - } - else { - SOKOL_LOG(msg); - } - SOKOL_ABORT(); -} - -_SOKOL_PRIVATE void _sapp_call_init(void) { - if (_sapp.desc.init_cb) { - _sapp.desc.init_cb(); - } - else if (_sapp.desc.init_userdata_cb) { - _sapp.desc.init_userdata_cb(_sapp.desc.user_data); - } - _sapp.init_called = true; -} - -_SOKOL_PRIVATE void _sapp_call_frame(void) { - if (_sapp.init_called && !_sapp.cleanup_called) { - if (_sapp.desc.frame_cb) { - _sapp.desc.frame_cb(); - } - else if (_sapp.desc.frame_userdata_cb) { - _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); - } - } -} - -_SOKOL_PRIVATE void _sapp_call_cleanup(void) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.cleanup_cb) { - _sapp.desc.cleanup_cb(); - } - else if (_sapp.desc.cleanup_userdata_cb) { - _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); - } - _sapp.cleanup_called = true; - } -} - -_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.event_cb) { - _sapp.desc.event_cb(e); - } - else if (_sapp.desc.event_userdata_cb) { - _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); - } - } - if (_sapp.event_consumed) { - _sapp.event_consumed = false; - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { - SOKOL_ASSERT(_sapp.drop.buffer); - SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); - int offset = index * _sapp.drop.max_path_length; - SOKOL_ASSERT(offset < _sapp.drop.buf_size); - return &_sapp.drop.buffer[offset]; -} - -/* Copy a string into a fixed size buffer with guaranteed zero- - termination. - - Return false if the string didn't fit into the buffer and had to be clamped. - - FIXME: Currently UTF-8 strings might become invalid if the string - is clamped, because the last zero-byte might be written into - the middle of a multi-byte sequence. -*/ -_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { - SOKOL_ASSERT(src && dst && (max_len > 0)); - char* const end = &(dst[max_len-1]); - char c = 0; - for (int i = 0; i < max_len; i++) { - c = *src; - if (c != 0) { - src++; - } - *dst++ = c; - } - /* truncated? */ - if (c != 0) { - *end = 0; - return false; - } - else { - return true; - } -} - -_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* in_desc) { - sapp_desc desc = *in_desc; - desc.width = _sapp_def(desc.width, 640); - desc.height = _sapp_def(desc.height, 480); - desc.sample_count = _sapp_def(desc.sample_count, 1); - desc.swap_interval = _sapp_def(desc.swap_interval, 1); - desc.html5_canvas_name = _sapp_def(desc.html5_canvas_name, "canvas"); - desc.clipboard_size = _sapp_def(desc.clipboard_size, 8192); - desc.max_dropped_files = _sapp_def(desc.max_dropped_files, 1); - desc.max_dropped_file_path_length = _sapp_def(desc.max_dropped_file_path_length, 2048); - desc.window_title = _sapp_def(desc.window_title, "sokol_app"); - return desc; -} - -_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { - _SAPP_CLEAR(_sapp_t, _sapp); - _sapp.desc = _sapp_desc_defaults(desc); - _sapp.first_frame = true; - _sapp.window_width = _sapp.desc.width; - _sapp.window_height = _sapp.desc.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp.sample_count = _sapp.desc.sample_count; - _sapp.swap_interval = _sapp.desc.swap_interval; - _sapp.html5_canvas_selector[0] = '#'; - _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); - _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; - _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; - _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; - if (_sapp.clipboard.enabled) { - _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; - _sapp.clipboard.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.clipboard.buf_size); - } - _sapp.drop.enabled = _sapp.desc.enable_dragndrop; - if (_sapp.drop.enabled) { - _sapp.drop.max_files = _sapp.desc.max_dropped_files; - _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; - _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; - _sapp.drop.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.drop.buf_size); - } - _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); - _sapp.desc.window_title = _sapp.window_title; - _sapp.dpi_scale = 1.0f; - _sapp.fullscreen = _sapp.desc.fullscreen; - _sapp.mouse.shown = true; -} - -_SOKOL_PRIVATE void _sapp_discard_state(void) { - if (_sapp.clipboard.enabled) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - SOKOL_FREE((void*)_sapp.clipboard.buffer); - } - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - SOKOL_FREE((void*)_sapp.drop.buffer); - } - _SAPP_CLEAR(_sapp_t, _sapp); -} - -_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { - memset(&_sapp.event, 0, sizeof(_sapp.event)); - _sapp.event.type = type; - _sapp.event.frame_count = _sapp.frame_count; - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - _sapp.event.window_width = _sapp.window_width; - _sapp.event.window_height = _sapp.window_height; - _sapp.event.framebuffer_width = _sapp.framebuffer_width; - _sapp.event.framebuffer_height = _sapp.framebuffer_height; - _sapp.event.mouse_x = _sapp.mouse.x; - _sapp.event.mouse_y = _sapp.mouse.y; - _sapp.event.mouse_dx = _sapp.mouse.dx; - _sapp.event.mouse_dy = _sapp.mouse.dy; -} - -_SOKOL_PRIVATE bool _sapp_events_enabled(void) { - /* only send events when an event callback is set, and the init function was called */ - return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; -} - -_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { - if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { - return _sapp.keycodes[scan_code]; - } - else { - return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - memset(_sapp.drop.buffer, 0, (size_t)_sapp.drop.buf_size); - } -} - -_SOKOL_PRIVATE void _sapp_frame(void) { - if (_sapp.first_frame) { - _sapp.first_frame = false; - _sapp_call_init(); - } - _sapp_call_frame(); - _sapp.frame_count++; -} - -/*== MacOS/iOS ===============================================================*/ -#if defined(_SAPP_APPLE) - -#if __has_feature(objc_arc) -#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } -#else -#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } -#endif - -/*== MacOS ===================================================================*/ -#if defined(_SAPP_MACOS) - -_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { - _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; - _sapp.keycodes[0x12] = SAPP_KEYCODE_1; - _sapp.keycodes[0x13] = SAPP_KEYCODE_2; - _sapp.keycodes[0x14] = SAPP_KEYCODE_3; - _sapp.keycodes[0x15] = SAPP_KEYCODE_4; - _sapp.keycodes[0x17] = SAPP_KEYCODE_5; - _sapp.keycodes[0x16] = SAPP_KEYCODE_6; - _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; - _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; - _sapp.keycodes[0x19] = SAPP_KEYCODE_9; - _sapp.keycodes[0x00] = SAPP_KEYCODE_A; - _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; - _sapp.keycodes[0x08] = SAPP_KEYCODE_C; - _sapp.keycodes[0x02] = SAPP_KEYCODE_D; - _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; - _sapp.keycodes[0x03] = SAPP_KEYCODE_F; - _sapp.keycodes[0x05] = SAPP_KEYCODE_G; - _sapp.keycodes[0x04] = SAPP_KEYCODE_H; - _sapp.keycodes[0x22] = SAPP_KEYCODE_I; - _sapp.keycodes[0x26] = SAPP_KEYCODE_J; - _sapp.keycodes[0x28] = SAPP_KEYCODE_K; - _sapp.keycodes[0x25] = SAPP_KEYCODE_L; - _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; - _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; - _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; - _sapp.keycodes[0x23] = SAPP_KEYCODE_P; - _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01] = SAPP_KEYCODE_S; - _sapp.keycodes[0x11] = SAPP_KEYCODE_T; - _sapp.keycodes[0x20] = SAPP_KEYCODE_U; - _sapp.keycodes[0x09] = SAPP_KEYCODE_V; - _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; - _sapp.keycodes[0x07] = SAPP_KEYCODE_X; - _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; - _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x77] = SAPP_KEYCODE_END; - _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; - _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; -} - -_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.view); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); - #endif - _SAPP_OBJC_RELEASE(_sapp.macos.window); -} - -_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_macos_init_keytable(); - [NSApplication sharedApplication]; - NSApp.activationPolicy = NSApplicationActivationPolicyRegular; - _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 -} - -/* MacOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_macos_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_SOKOL_PRIVATE uint32_t _sapp_macos_mod(NSEventModifierFlags f) { - uint32_t m = 0; - if (f & NSEventModifierFlagShift) { - m |= SAPP_MODIFIER_SHIFT; - } - if (f & NSEventModifierFlagControl) { - m |= SAPP_MODIFIER_CTRL; - } - if (f & NSEventModifierFlagOption) { - m |= SAPP_MODIFIER_ALT; - } - if (f & NSEventModifierFlagCommand) { - m |= SAPP_MODIFIER_SUPER; - } - return m; -} - -_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { - #if defined(SOKOL_METAL) - const NSRect fb_rect = [_sapp.macos.view bounds]; - _sapp.framebuffer_width = fb_rect.size.width * _sapp.dpi_scale; - _sapp.framebuffer_height = fb_rect.size.height * _sapp.dpi_scale; - #elif defined(SOKOL_GLCORE33) - const NSRect fb_rect = [_sapp.macos.view convertRectToBacking:[_sapp.macos.view frame]]; - _sapp.framebuffer_width = fb_rect.size.width; - _sapp.framebuffer_height = fb_rect.size.height; - #endif - const NSRect bounds = [_sapp.macos.view bounds]; - _sapp.window_width = bounds.size.width; - _sapp.window_height = bounds.size.height; - if (_sapp.framebuffer_width == 0) { - _sapp.framebuffer_width = 1; - } - if (_sapp.framebuffer_height == 0) { - _sapp.framebuffer_height = 1; - } - if (_sapp.window_width == 0) { - _sapp.window_width = 1; - } - if (_sapp.window_height == 0) { - _sapp.window_height = 1; - } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; - - /* also make sure the MTKView drawable size is uptodate */ - #if defined(SOKOL_METAL) - CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.macos.view.drawableSize = drawable_size; - #endif -} - -_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { - /* NOTE: the _sapp.fullscreen flag is also notified by the - windowDidEnterFullscreen / windowDidExitFullscreen - event handlers - */ - _sapp.fullscreen = !_sapp.fullscreen; - [_sapp.macos.window toggleFullScreen:nil]; -} - -_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { - @autoreleasepool { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; - [pasteboard setString:@(str) forType:NSPasteboardTypeString]; - } -} - -_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - @autoreleasepool { - _sapp.clipboard.buffer[0] = 0; - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { - return _sapp.clipboard.buffer; - } - NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; - if (!str) { - return _sapp.clipboard.buffer; - } - _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - } - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { - [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; -} - -_SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) { - if (!_sapp.mouse.locked) { - const NSPoint mouse_pos = event.locationInWindow; - float new_x = mouse_pos.x * _sapp.dpi_scale; - float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; - /* don't update dx/dy in the very first update */ - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { - /* NOTE: this function is only called when the mouse visibility actually changes */ - if (visible) { - CGDisplayShowCursor(kCGDirectMainDisplay); - } - else { - CGDisplayHideCursor(kCGDirectMainDisplay); - } -} - -_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - /* - NOTE that this code doesn't warp the mouse cursor to the window - center as everybody else does it. This lead to a spike in the - *second* mouse-moved event after the warp happened. The - mouse centering doesn't seem to be required (mouse-moved events - are reported correctly even when the cursor is at an edge of the screen). - - NOTE also that the hide/show of the mouse cursor should properly - stack with calls to sapp_show_mouse() - */ - if (_sapp.mouse.locked) { - CGAssociateMouseAndMouseCursorPosition(NO); - CGDisplayHideCursor(kCGDirectMainDisplay); - } - else { - CGDisplayShowCursor(kCGDirectMainDisplay); - CGAssociateMouseAndMouseCursorPosition(YES); - } -} - -_SOKOL_PRIVATE void _sapp_macos_frame(void) { - _sapp_frame(); - if (_sapp.quit_requested || _sapp.quit_ordered) { - [_sapp.macos.window performClose:nil]; - } -} - -@implementation _sapp_macos_app_delegate -- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { - _SOKOL_UNUSED(aNotification); - if (_sapp.fullscreen) { - NSRect screen_rect = NSScreen.mainScreen.frame; - _sapp.window_width = screen_rect.size.width; - _sapp.window_height = screen_rect.size.height; - } - if (_sapp.desc.high_dpi) { - _sapp.framebuffer_width = 2 * _sapp.window_width; - _sapp.framebuffer_height = 2 * _sapp.window_height; - } - else { - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; - const NSUInteger style = - NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | - NSWindowStyleMaskResizable; - NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); - _sapp.macos.window = [[_sapp_macos_window alloc] - initWithContentRect:window_rect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO]; - _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate - _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; - _sapp.macos.window.acceptsMouseMovedEvents = YES; - _sapp.macos.window.restorable = YES; - - _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; - _sapp.macos.window.delegate = _sapp.macos.win_dlg; - #if defined(SOKOL_METAL) - _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.macos.view = [[_sapp_macos_view alloc] init]; - [_sapp.macos.view updateTrackingAreas]; - _sapp.macos.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; - _sapp.macos.view.device = _sapp.macos.mtl_device; - _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; - _sapp.macos.view.autoResizeDrawable = false; - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; - #elif defined(SOKOL_GLCORE33) - NSOpenGLPixelFormatAttribute attrs[32]; - int i = 0; - attrs[i++] = NSOpenGLPFAAccelerated; - attrs[i++] = NSOpenGLPFADoubleBuffer; - attrs[i++] = NSOpenGLPFAOpenGLProfile; attrs[i++] = NSOpenGLProfileVersion3_2Core; - attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; - attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; - if (_sapp.sample_count > 1) { - attrs[i++] = NSOpenGLPFAMultisample; - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; - attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; - } - else { - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; - } - attrs[i++] = 0; - NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - SOKOL_ASSERT(glpixelformat_obj != nil); - - _sapp.macos.view = [[_sapp_macos_view alloc] - initWithFrame:window_rect - pixelFormat:glpixelformat_obj]; - _SAPP_OBJC_RELEASE(glpixelformat_obj); - [_sapp.macos.view updateTrackingAreas]; - if (_sapp.desc.high_dpi) { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; - } - else { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; - } - - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - - NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 - target:_sapp.macos.view - selector:@selector(timerFired:) - userInfo:nil - repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; - timer_obj = nil; - #endif - _sapp.valid = true; - if (_sapp.fullscreen) { - /* on GL, this already toggles a rendered frame, so set the valid flag before */ - [_sapp.macos.window toggleFullScreen:self]; - } - else { - [_sapp.macos.window center]; - } - [_sapp.macos.window makeKeyAndOrderFront:nil]; - _sapp_macos_update_dimensions(); - [NSEvent setMouseCoalescingEnabled:NO]; -} - -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { - _SOKOL_UNUSED(sender); - return YES; -} - -- (void)applicationWillTerminate:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_call_cleanup(); - _sapp_macos_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_macos_window_delegate -- (BOOL)windowShouldClose:(id)sender { - _SOKOL_UNUSED(sender); - /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a chance to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - return YES; - } - else { - return NO; - } -} - -- (void)windowDidResize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_update_dimensions(); - if (!_sapp.first_frame) { - _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); - } -} - -- (void)windowDidMiniaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); -} - -- (void)windowDidDeminiaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); -} - -- (void)windowDidEnterFullScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp.fullscreen = true; -} - -- (void)windowDidExitFullScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp.fullscreen = false; -} -@end - -@implementation _sapp_macos_window -- (instancetype)initWithContentRect:(NSRect)contentRect - styleMask:(NSWindowStyleMask)style - backing:(NSBackingStoreType)backingStoreType - defer:(BOOL)flag { - if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; - #endif - } - return self; -} - -- (NSDragOperation)draggingEntered:(id)sender { - return NSDragOperationCopy; -} - -- (NSDragOperation)draggingUpdated:(id)sender { - return NSDragOperationCopy; -} - -- (BOOL)performDragOperation:(id)sender { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - NSPasteboard *pboard = [sender draggingPasteboard]; - if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : pboard.pasteboardItems.count; - bool drop_failed = false; - for (int i = 0; i < _sapp.drop.num_files; i++) { - NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; - if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); - drop_failed = true; - break; - } - } - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } - return YES; - } - #endif - return NO; -} -@end - -@implementation _sapp_macos_view -#if defined(SOKOL_GLCORE33) -/* NOTE: this is a hack/fix when the initial window size has been clipped by - macOS because it didn't fit on the screen, in that case the - frame size of the window is reported wrong if low-dpi rendering - was requested (instead the high-dpi dimensions are returned) - until the window is resized for the first time. - - Hooking into reshape and getting the frame dimensions seems to report - the correct dimensions. -*/ -- (void)reshape { - _sapp_macos_update_dimensions(); - [super reshape]; -} -- (void)timerFired:(id)sender { - _SOKOL_UNUSED(sender); - [self setNeedsDisplay:YES]; -} -- (void)prepareOpenGL { - [super prepareOpenGL]; - GLint swapInt = 1; - NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; - [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; - [ctx makeCurrentContext]; -} -#endif - -_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - const NSEventMask mask = NSEventMaskLeftMouseDown | - NSEventMaskLeftMouseUp| - NSEventMaskRightMouseDown | - NSEventMaskRightMouseUp | - NSEventMaskMouseMoved | - NSEventMaskLeftMouseDragged | - NSEventMaskRightMouseDragged | - NSEventMaskMouseEntered | - NSEventMaskMouseExited | - NSEventMaskKeyDown | - NSEventMaskKeyUp | - NSEventMaskCursorUpdate | - NSEventMaskScrollWheel | - NSEventMaskTabletPoint | - NSEventMaskTabletProximity | - NSEventMaskOtherMouseDown | - NSEventMaskOtherMouseUp | - NSEventMaskOtherMouseDragged | - NSEventMaskPressure | - NSEventMaskDirectTouch; - @autoreleasepool { - for (;;) { - // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, - // see: https://github.com/floooh/sokol/issues/486 - NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; - if (event == nil) { - break; - } - [NSApp sendEvent:event]; - } - } -} - -- (void)drawRect:(NSRect)rect { - _SOKOL_UNUSED(rect); - /* Catch any last-moment input events */ - _sapp_macos_poll_input_events(); - _sapp_macos_frame(); - #if !defined(SOKOL_METAL) - [[_sapp.macos.view openGLContext] flushBuffer]; - #endif -} - -- (BOOL)isOpaque { - return YES; -} -- (BOOL)canBecomeKeyView { - return YES; -} -- (BOOL)acceptsFirstResponder { - return YES; -} -- (void)updateTrackingAreas { - if (_sapp.macos.tracking_area != nil) { - [self removeTrackingArea:_sapp.macos.tracking_area]; - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - } - const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingEnabledDuringMouseDrag | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect | - NSTrackingAssumeInside; - _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - [self addTrackingArea:_sapp.macos.tracking_area]; - [super updateTrackingAreas]; -} -- (void)mouseEntered:(NSEvent*)event { - _sapp_macos_update_mouse(event); - /* don't send mouse enter/leave while dragging (so that it behaves the same as - 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)); - } -} -- (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)); - } -} -- (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_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.scroll_x = dx; - _sapp.event.scroll_y = dy; - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyDown:(NSEvent*)event { - if (_sapp_events_enabled()) { - const uint32_t mods = _sapp_macos_mod(event.modifierFlags); - /* 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 - */ - const sapp_keycode key_code = _sapp_translate_key(event.keyCode); - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); - if (0 != (mods & SAPP_MODIFIER_SUPER)) { - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods); - } - const NSString* chars = event.characters; - const NSUInteger len = chars.length; - if (len > 0) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = mods; - for (NSUInteger i = 0; i < len; i++) { - const unichar codepoint = [chars characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) { - continue; - } - _sapp.event.char_code = codepoint; - _sapp.event.key_repeat = event.isARepeat; - _sapp_call_event(&_sapp.event); - } - } - /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ - if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyUp:(NSEvent*)event { - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, - _sapp_translate_key(event.keyCode), - event.isARepeat, - _sapp_macos_mod(event.modifierFlags)); -} -- (void)flagsChanged:(NSEvent*)event { - const uint32_t old_f = _sapp.macos.flags_changed_store; - const uint32_t new_f = event.modifierFlags; - _sapp.macos.flags_changed_store = new_f; - sapp_keycode key_code = SAPP_KEYCODE_INVALID; - bool down = false; - if ((new_f ^ old_f) & NSEventModifierFlagShift) { - key_code = SAPP_KEYCODE_LEFT_SHIFT; - down = 0 != (new_f & NSEventModifierFlagShift); - } - if ((new_f ^ old_f) & NSEventModifierFlagControl) { - key_code = SAPP_KEYCODE_LEFT_CONTROL; - down = 0 != (new_f & NSEventModifierFlagControl); - } - if ((new_f ^ old_f) & NSEventModifierFlagOption) { - key_code = SAPP_KEYCODE_LEFT_ALT; - down = 0 != (new_f & NSEventModifierFlagOption); - } - if ((new_f ^ old_f) & NSEventModifierFlagCommand) { - key_code = SAPP_KEYCODE_LEFT_SUPER; - down = 0 != (new_f & NSEventModifierFlagCommand); - } - if (key_code != SAPP_KEYCODE_INVALID) { - _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, - key_code, - false, - _sapp_macos_mod(event.modifierFlags)); - } -} -- (void)cursorUpdate:(NSEvent*)event { - _SOKOL_UNUSED(event); - if (_sapp.desc.user_cursor) { - _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - } -} -@end - -#endif /* MacOS */ - -/*== iOS =====================================================================*/ -#if defined(_SAPP_IOS) - -_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); - _SAPP_OBJC_RELEASE(_sapp.ios.textfield); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); - #else - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); - #endif - _SAPP_OBJC_RELEASE(_sapp.ios.view); - _SAPP_OBJC_RELEASE(_sapp.ios.window); -} - -_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { - _sapp_init_state(desc); - static int argc = 1; - static char* argv[] = { (char*)"sokol_app" }; - UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); -} - -/* iOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_ios_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - NSEnumerator* enumerator = event.allTouches.objectEnumerator; - UITouch* ios_touch; - while ((ios_touch = [enumerator nextObject])) { - if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { - CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; - sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; - cur_point->identifier = (uintptr_t) ios_touch; - cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; - cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; - cur_point->changed = [touches containsObject:ios_touch]; - } - } - if (_sapp.event.num_touches > 0) { - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.window_width = (int) screen_rect.size.width; - _sapp.window_height = (int) screen_rect.size.height; - int cur_fb_width, cur_fb_height; - #if defined(SOKOL_METAL) - const CGSize fb_size = _sapp.ios.view.drawableSize; - cur_fb_width = (int) fb_size.width; - cur_fb_height = (int) fb_size.height; - #else - cur_fb_width = (int) _sapp.ios.view.drawableWidth; - cur_fb_height = (int) _sapp.ios.view.drawableHeight; - #endif - const bool dim_changed = - (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - _sapp.framebuffer_width = cur_fb_width; - _sapp.framebuffer_height = cur_fb_height; - SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; - if (dim_changed && !_sapp.first_frame) { - _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); - } -} - -_SOKOL_PRIVATE void _sapp_ios_frame(void) { - _sapp_ios_update_dimensions(); - _sapp_frame(); -} - -_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { - /* if not happened yet, create an invisible text field */ - if (nil == _sapp.ios.textfield) { - _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; - _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; - _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; - _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; - _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; - _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; - _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; - _sapp.ios.textfield.hidden = YES; - _sapp.ios.textfield.text = @"x"; - _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; - [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; - - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWasShown:) - name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWillBeHidden:) - name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardDidChangeFrame:) - name:UIKeyboardDidChangeFrameNotification object:nil]; - } - if (shown) { - /* setting the text field as first responder brings up the onscreen keyboard */ - [_sapp.ios.textfield becomeFirstResponder]; - } - else { - [_sapp.ios.textfield resignFirstResponder]; - } -} - -@implementation _sapp_app_delegate -- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; - _sapp.window_width = screen_rect.size.width; - _sapp.window_height = screen_rect.size.height; - if (_sapp.desc.high_dpi) { - _sapp.framebuffer_width = 2 * _sapp.window_width; - _sapp.framebuffer_height = 2 * _sapp.window_height; - } - else { - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - } - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; - #if defined(SOKOL_METAL) - _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.ios.view = [[_sapp_ios_view alloc] init]; - _sapp.ios.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; - _sapp.ios.view.device = _sapp.ios.mtl_device; - _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; - if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = 2.0; - } - else { - _sapp.ios.view.contentScaleFactor = 1.0; - } - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - _sapp.ios.view_ctrl = [[UIViewController alloc] init]; - _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #else - if (_sapp.desc.gl_force_gles2) { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - _sapp.gles2_fallback = true; - } - else { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; - if (_sapp.ios.eagl_ctx == nil) { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - _sapp.gles2_fallback = true; - } - } - _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; - _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; - _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; - _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; - _sapp.ios.view.drawableMultisample = GLKViewDrawableMultisampleNone; /* FIXME */ - _sapp.ios.view.context = _sapp.ios.eagl_ctx; - _sapp.ios.view.enableSetNeedsDisplay = NO; - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = 2.0; - } - else { - _sapp.ios.view.contentScaleFactor = 1.0; - } - _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.view_ctrl.preferredFramesPerSecond = 60 / _sapp.swap_interval; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #endif - [_sapp.ios.window makeKeyAndVisible]; - - _sapp.valid = true; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - if (!_sapp.ios.suspended) { - _sapp.ios.suspended = true; - _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); - } -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - if (_sapp.ios.suspended) { - _sapp.ios.suspended = false; - _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); - } -} - -/* NOTE: this method will rarely ever be called, iOS application - which are terminated by the user are usually killed via signal 9 - by the operating system. -*/ -- (void)applicationWillTerminate:(UIApplication *)application { - _SOKOL_UNUSED(application); - _sapp_call_cleanup(); - _sapp_ios_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_textfield_dlg -- (void)keyboardWasShown:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = true; - /* query the keyboard's size, and modify the content view's size */ - if (_sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (void)keyboardWillBeHidden:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = false; - if (_sapp.desc.ios_keyboard_resizes_canvas) { - _sapp.ios.view.frame = UIScreen.mainScreen.bounds; - } -} -- (void)keyboardDidChangeFrame:(NSNotification*)notif { - /* this is for the case when the screen rotation changes while the keyboard is open */ - if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { - if (_sapp_events_enabled()) { - const NSUInteger len = string.length; - if (len > 0) { - for (NSUInteger i = 0; i < len; i++) { - unichar c = [string characterAtIndex:i]; - if (c >= 32) { - /* ignore surrogates for now */ - if ((c < 0xD800) || (c > 0xDFFF)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = c; - _sapp_call_event(&_sapp.event); - } - } - if (c <= 32) { - sapp_keycode k = SAPP_KEYCODE_INVALID; - switch (c) { - case 10: k = SAPP_KEYCODE_ENTER; break; - case 32: k = SAPP_KEYCODE_SPACE; break; - default: break; - } - if (k != SAPP_KEYCODE_INVALID) { - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - } - } - } - } - else { - /* this was a backspace */ - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - } - } - return NO; -} -@end - -@implementation _sapp_ios_view -- (void)drawRect:(CGRect)rect { - _SOKOL_UNUSED(rect); - _sapp_ios_frame(); -} -- (BOOL)isOpaque { - return YES; -} -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); -} -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); -} -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); -} -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); -} -@end -#endif /* TARGET_OS_IPHONE */ - -#endif /* _SAPP_APPLE */ - -/*== EMSCRIPTEN ==============================================================*/ -#if defined(_SAPP_EMSCRIPTEN) - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); - -/* this function is called from a JS event handler when the user hides - the onscreen keyboard pressing the 'dismiss keyboard key' -*/ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { - _sapp.onscreen_keyboard_shown = false; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { - if (_sapp.clipboard.enabled) { - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ -EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { - return _sapp.html5_ask_leave_site ? 1 : 0; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { - if (!_sapp.drop.enabled) { - return; - } - if (num < 0) { - num = 0; - } - if (num > _sapp.drop.max_files) { - num = _sapp.drop.max_files; - } - _sapp.drop.num_files = num; - _sapp_clear_drop_buffer(); -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { - /* NOTE: name is only the filename part, not a path */ - if (!_sapp.drop.enabled) { - return; - } - if (0 == name) { - return; - } - SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); - if ((i < 0) || (i >= _sapp.drop.num_files)) { - return; - } - if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long!\n"); - _sapp.drop.num_files = 0; - } -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { - if (!_sapp.drop.enabled) { - return; - } - if (0 == _sapp.drop.num_files) { - /* there was an error copying the filenames */ - _sapp_clear_drop_buffer(); - return; - - } - if (_sapp_events_enabled()) { - _sapp.mouse.x = (float)x * _sapp.dpi_scale; - _sapp.mouse.y = (float)y * _sapp.dpi_scale; - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { - sapp_html5_fetch_response response; - memset(&response, 0, sizeof(response)); - response.succeeded = (0 != success); - response.error_code = (sapp_html5_fetch_error) error_code; - response.file_index = index; - response.fetched_size = fetched_size; - response.buffer_ptr = buf_ptr; - response.buffer_size = buf_size; - response.user_data = user_data; - callback(&response); -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* Javascript helper functions for mobile virtual keyboard input */ -EM_JS(void, sapp_js_create_textfield, (void), { - var _sapp_inp = document.createElement("input"); - _sapp_inp.type = "text"; - _sapp_inp.id = "_sokol_app_input_element"; - _sapp_inp.autocapitalize = "none"; - _sapp_inp.addEventListener("focusout", function(_sapp_event) { - __sapp_emsc_notify_keyboard_hidden() - - }); - document.body.append(_sapp_inp); -}); - -EM_JS(void, sapp_js_focus_textfield, (void), { - document.getElementById("_sokol_app_input_element").focus(); -}); - -EM_JS(void, sapp_js_unfocus_textfield, (void), { - document.getElementById("_sokol_app_input_element").blur(); -}); - -EM_JS(void, sapp_js_add_beforeunload_listener, (void), { - Module.sokol_beforeunload = function(event) { - if (__sapp_html5_get_ask_leave_site() != 0) { - event.preventDefault(); - event.returnValue = ' '; - } - }; - window.addEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { - window.removeEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_add_clipboard_listener, (void), { - Module.sokol_paste = function(event) { - var pasted_str = event.clipboardData.getData('text'); - ccall('_sapp_emsc_onpaste', 'void', ['string'], [pasted_str]); - }; - window.addEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_remove_clipboard_listener, (void), { - window.removeEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { - var str = UTF8ToString(c_str); - var ta = document.createElement('textarea'); - ta.setAttribute('autocomplete', 'off'); - ta.setAttribute('autocorrect', 'off'); - ta.setAttribute('autocapitalize', 'off'); - ta.setAttribute('spellcheck', 'false'); - ta.style.left = -100 + 'px'; - ta.style.top = -100 + 'px'; - ta.style.height = 1; - ta.style.width = 1; - ta.value = str; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); -}); - -_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { - sapp_js_write_clipboard(str); -} - -EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { - Module.sokol_drop_files = []; - var canvas_name = UTF8ToString(canvas_name_cstr); - var canvas = document.getElementById(canvas_name); - Module.sokol_dragenter = function(event) { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragleave = function(event) { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragover = function(event) { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_drop = function(event) { - event.stopPropagation(); - event.preventDefault(); - var files = event.dataTransfer.files; - Module.sokol_dropped_files = files; - __sapp_emsc_begin_drop(files.length); - var i; - for (i = 0; i < files.length; i++) { - ccall('_sapp_emsc_drop', 'void', ['number', 'string'], [i, files[i].name]); - } - // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect - __sapp_emsc_end_drop(event.clientX, event.clientY); - }; - canvas.addEventListener('dragenter', Module.sokol_dragenter, false); - canvas.addEventListener('dragleave', Module.sokol_dragleave, false); - canvas.addEventListener('dragover', Module.sokol_dragover, false); - canvas.addEventListener('drop', Module.sokol_drop, false); -}); - -EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { - if ((index < 0) || (index >= Module.sokol_dropped_files.length)) { - return 0; - } - else { - return Module.sokol_dropped_files[index].size; - } -}); - -EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { - var reader = new FileReader(); - reader.onload = function(loadEvent) { - var content = loadEvent.target.result; - if (content.byteLength > buf_size) { - // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL - __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); - } - else { - HEAPU8.set(new Uint8Array(content), buf_ptr); - __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); - } - }; - reader.onerror = function() { - // SAPP_HTML5_FETCH_ERROR_OTHER - __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); - }; - reader.readAsArrayBuffer(Module.sokol_dropped_files[index]); -}); - -EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { - var canvas_name = UTF8ToString(canvas_name_cstr); - var canvas = document.getElementById(canvas_name); - canvas.removeEventListener('dragenter', Module.sokol_dragenter); - canvas.removeEventListener('dragleave', Module.sokol_dragleave); - canvas.removeEventListener('dragover', Module.sokol_dragover); - canvas.removeEventListener('drop', Module.sokol_drop); -}); - -/* called from the emscripten event handler to update the keyboard visibility - state, this must happen from an JS input event handler, otherwise - the request will be ignored by the browser -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { - if (_sapp.emsc.wants_show_keyboard) { - /* create input text field on demand */ - if (!_sapp.emsc.textfield_created) { - _sapp.emsc.textfield_created = true; - sapp_js_create_textfield(); - } - /* focus the text input field, this will bring up the keyboard */ - _sapp.onscreen_keyboard_shown = true; - _sapp.emsc.wants_show_keyboard = false; - sapp_js_focus_textfield(); - } - if (_sapp.emsc.wants_hide_keyboard) { - /* unfocus the text input field */ - if (_sapp.emsc.textfield_created) { - _sapp.onscreen_keyboard_shown = false; - _sapp.emsc.wants_hide_keyboard = false; - sapp_js_unfocus_textfield(); - } - } -} - -/* actually showing the onscreen keyboard must be initiated from a JS - input event handler, so we'll just keep track of the desired - state, and the actual state change will happen with the next input event -*/ -_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { - if (show) { - _sapp.emsc.wants_show_keyboard = true; - } - else { - _sapp.emsc.wants_hide_keyboard = true; - } -} - -EM_JS(void, sapp_js_pointer_init, (const char* c_str_target), { - // lookup and store canvas object by name - var target_str = UTF8ToString(c_str_target); - Module.sapp_emsc_target = document.getElementById(target_str); - if (!Module.sapp_emsc_target) { - console.log("sokol_app.h: invalid target:" + target_str); - } - if (!Module.sapp_emsc_target.requestPointerLock) { - console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); - } -}); - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = emsc_event->isActive; - return EM_TRUE; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = false; - _sapp.emsc.mouse_lock_requested = false; - return true; -} - -EM_JS(void, sapp_js_request_pointerlock, (void), { - if (Module.sapp_emsc_target) { - if (Module.sapp_emsc_target.requestPointerLock) { - Module.sapp_emsc_target.requestPointerLock(); - } - } -}); - -EM_JS(void, sapp_js_exit_pointerlock, (void), { - if (document.exitPointerLock) { - document.exitPointerLock(); - } -}); - -_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { - if (lock) { - /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ - _sapp.emsc.mouse_lock_requested = true; - } - else { - /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ - _sapp.emsc.mouse_lock_requested = false; - sapp_js_exit_pointerlock(); - } -} - -/* called from inside event handlers to check if mouse lock had been requested, - and if yes, actually enter mouse lock. -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { - if (_sapp.emsc.mouse_lock_requested) { - _sapp.emsc.mouse_lock_requested = false; - sapp_js_request_pointerlock(); - } -} - -#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 EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { - _SOKOL_UNUSED(event_type); - _SOKOL_UNUSED(user_data); - double w, h; - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - /* The above method might report zero when toggling HTML5 fullscreen, - in that case use the window's inner width reported by the - emscripten event. This works ok when toggling *into* fullscreen - but doesn't properly restore the previous canvas size when switching - back from fullscreen. - - In general, due to the HTML5's fullscreen API's flaky nature it is - recommended to use 'soft fullscreen' (stretching the WebGL canvas - over the browser windows client rect) with a CSS definition like this: - - position: absolute; - top: 0px; - left: 0px; - margin: 0px; - border: 0; - width: 100%; - height: 100%; - overflow: hidden; - display: block; - */ - if (w < 1.0) { - w = ui_event->windowInnerWidth; - } - else { - _sapp.window_width = (int) w; - } - if (h < 1.0) { - h = ui_event->windowInnerHeight; - } - else { - _sapp.window_height = (int) h; - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); - SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_WGPU) - /* on WebGPU: recreate size-dependent rendering surfaces */ - _sapp_emsc_wgpu_surfaces_discard(); - _sapp_emsc_wgpu_surfaces_create(); - #endif - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_RESIZED); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - if (_sapp.mouse.locked) { - _sapp.mouse.dx = (float) emsc_event->movementX; - _sapp.mouse.dy = (float) emsc_event->movementY; - } - else { - float new_x = emsc_event->targetX * _sapp.dpi_scale; - float new_y = emsc_event->targetY * _sapp.dpi_scale; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } - if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { - sapp_event_type type; - bool is_button_event = false; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_MOUSEDOWN: - type = SAPP_EVENTTYPE_MOUSE_DOWN; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEUP: - type = SAPP_EVENTTYPE_MOUSE_UP; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEMOVE: - type = SAPP_EVENTTYPE_MOUSE_MOVE; - break; - case EMSCRIPTEN_EVENT_MOUSEENTER: - type = SAPP_EVENTTYPE_MOUSE_ENTER; - break; - case EMSCRIPTEN_EVENT_MOUSELEAVE: - type = SAPP_EVENTTYPE_MOUSE_LEAVE; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - break; - } - 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; - } - if (is_button_event) { - switch (emsc_event->button) { - case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; - case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; - case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; - default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; - } - } - else { - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - } - _sapp_call_event(&_sapp.event); - } - /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */ - if (is_button_event) { - _sapp_emsc_update_mouse_lock_state(); - } - } - _sapp_emsc_update_keyboard_state(); - return true; -} - -_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); - 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; - } - /* see https://github.com/floooh/sokol/issues/339 */ - float scale; - switch (emsc_event->deltaMode) { - case DOM_DELTA_PIXEL: scale = -0.04f; break; - case DOM_DELTA_LINE: scale = -1.33f; break; - case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess - default: scale = -0.1f; break; // shouldn't happen - } - _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; - _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; - _sapp_call_event(&_sapp.event); - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - return true; -} - -_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; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_KEYDOWN: - type = SAPP_EVENTTYPE_KEY_DOWN; - break; - case EMSCRIPTEN_EVENT_KEYUP: - type = SAPP_EVENTTYPE_KEY_UP; - break; - case EMSCRIPTEN_EVENT_KEYPRESS: - type = SAPP_EVENTTYPE_CHAR; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - 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; - } - if (type == SAPP_EVENTTYPE_CHAR) { - _sapp.event.char_code = emsc_event->charCode; - /* workaround to make Cmd+V work on Safari */ - if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { - retval = false; - } - } - else { - _sapp.event.key_code = _sapp_translate_key((int)emsc_event->keyCode); - /* 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 - when the SUPER key is pressed - */ - if ((type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && - (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && - (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) - { - send_keyup_followup = true; - } - /* only forward a certain key ranges to the browser */ - switch (_sapp.event.key_code) { - case SAPP_KEYCODE_WORLD_1: - case SAPP_KEYCODE_WORLD_2: - case SAPP_KEYCODE_ESCAPE: - case SAPP_KEYCODE_ENTER: - case SAPP_KEYCODE_TAB: - case SAPP_KEYCODE_BACKSPACE: - case SAPP_KEYCODE_INSERT: - case SAPP_KEYCODE_DELETE: - case SAPP_KEYCODE_RIGHT: - case SAPP_KEYCODE_LEFT: - case SAPP_KEYCODE_DOWN: - case SAPP_KEYCODE_UP: - case SAPP_KEYCODE_PAGE_UP: - case SAPP_KEYCODE_PAGE_DOWN: - case SAPP_KEYCODE_HOME: - case SAPP_KEYCODE_END: - case SAPP_KEYCODE_CAPS_LOCK: - case SAPP_KEYCODE_SCROLL_LOCK: - case SAPP_KEYCODE_NUM_LOCK: - case SAPP_KEYCODE_PRINT_SCREEN: - case SAPP_KEYCODE_PAUSE: - case SAPP_KEYCODE_F1: - case SAPP_KEYCODE_F2: - case SAPP_KEYCODE_F3: - case SAPP_KEYCODE_F4: - case SAPP_KEYCODE_F5: - case SAPP_KEYCODE_F6: - case SAPP_KEYCODE_F7: - case SAPP_KEYCODE_F8: - case SAPP_KEYCODE_F9: - case SAPP_KEYCODE_F10: - case SAPP_KEYCODE_F11: - case SAPP_KEYCODE_F12: - case SAPP_KEYCODE_F13: - case SAPP_KEYCODE_F14: - case SAPP_KEYCODE_F15: - case SAPP_KEYCODE_F16: - case SAPP_KEYCODE_F17: - case SAPP_KEYCODE_F18: - case SAPP_KEYCODE_F19: - case SAPP_KEYCODE_F20: - case SAPP_KEYCODE_F21: - case SAPP_KEYCODE_F22: - case SAPP_KEYCODE_F23: - case SAPP_KEYCODE_F24: - case SAPP_KEYCODE_F25: - case SAPP_KEYCODE_LEFT_SHIFT: - case SAPP_KEYCODE_LEFT_CONTROL: - case SAPP_KEYCODE_LEFT_ALT: - case SAPP_KEYCODE_LEFT_SUPER: - case SAPP_KEYCODE_RIGHT_SHIFT: - case SAPP_KEYCODE_RIGHT_CONTROL: - case SAPP_KEYCODE_RIGHT_ALT: - case SAPP_KEYCODE_RIGHT_SUPER: - case SAPP_KEYCODE_MENU: - /* consume the event */ - break; - default: - /* forward key to browser */ - retval = false; - break; - } - } - if (_sapp_call_event(&_sapp.event)) { - /* consume event via sapp_consume_event() */ - retval = true; - } - if (send_keyup_followup) { - _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; - if (_sapp_call_event(&_sapp.event)) { - retval = true; - } - } - } - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - return retval; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - bool retval = true; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_TOUCHSTART: - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case EMSCRIPTEN_EVENT_TOUCHMOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case EMSCRIPTEN_EVENT_TOUCHEND: - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case EMSCRIPTEN_EVENT_TOUCHCANCEL: - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - retval = false; - break; - } - 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.num_touches = emsc_event->numTouches; - if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { - _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; - } - for (int i = 0; i < _sapp.event.num_touches; i++) { - const EmscriptenTouchPoint* src = &emsc_event->touches[i]; - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)src->identifier; - dst->pos_x = src->targetX * _sapp.dpi_scale; - dst->pos_y = src->targetY * _sapp.dpi_scale; - dst->changed = src->isChanged; - } - _sapp_call_event(&_sapp.event); - } - } - _sapp_emsc_update_keyboard_state(); - 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; -} - -#if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; - case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; - default: type = SAPP_EVENTTYPE_INVALID; break; - } - if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { - EmscriptenWebGLContextAttributes attrs; - emscripten_webgl_init_context_attributes(&attrs); - attrs.alpha = _sapp.desc.alpha; - attrs.depth = true; - attrs.stencil = true; - attrs.antialias = _sapp.sample_count > 1; - attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; - attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; - attrs.enableExtensionsByDefault = true; - #if defined(SOKOL_GLES3) - if (_sapp.desc.gl_force_gles2) { - attrs.majorVersion = 1; - _sapp.gles2_fallback = true; - } - else { - attrs.majorVersion = 2; - } - #endif - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); - if (!ctx) { - attrs.majorVersion = 1; - ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); - _sapp.gles2_fallback = true; - } - emscripten_webgl_make_context_current(ctx); - - /* some WebGL extension are not enabled automatically by emscripten */ - emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); -} -#endif - -#if defined(SOKOL_WGPU) -#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) -#define _SAPP_EMSC_WGPU_STATE_READY (1) -#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) - -#if defined(__cplusplus) -extern "C" { -#endif -/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); - _sapp.emsc.wgpu.device = (WGPUDevice) device_id; - _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; - _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; -} -#if defined(__cplusplus) -} // extern "C" -#endif - -/* embedded JS function to handle all the asynchronous WebGPU setup */ -EM_JS(void, sapp_js_wgpu_init, (), { - WebGPU.initManagers(); - // FIXME: the extension activation must be more clever here - navigator.gpu.requestAdapter().then(function(adapter) { - console.log("wgpu adapter extensions: " + adapter.extensions); - adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then(function(device) { - var gpuContext = document.getElementById("canvas").getContext("gpupresent"); - console.log("wgpu device extensions: " + adapter.extensions); - gpuContext.getSwapChainPreferredFormat(device).then(function(fmt) { - var swapChainDescriptor = { device: device, format: fmt }; - var swapChain = gpuContext.configureSwapChain(swapChainDescriptor); - var deviceId = WebGPU.mgrDevice.create(device); - var swapChainId = WebGPU.mgrSwapChain.create(swapChain); - var fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); - console.log("wgpu device: " + device); - console.log("wgpu swap chain: " + swapChain); - console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); - __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); - }); - }); - }); -}); - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { - SOKOL_ASSERT(_sapp.emsc.wgpu.device); - SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); - - WGPUTextureDescriptor ds_desc; - memset(&ds_desc, 0, sizeof(ds_desc)); - ds_desc.usage = WGPUTextureUsage_OutputAttachment; - ds_desc.dimension = WGPUTextureDimension_2D; - ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; - ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; - ds_desc.size.depth = 1; - ds_desc.arrayLayerCount = 1; - ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; - ds_desc.mipLevelCount = 1; - ds_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); - _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); - - if (_sapp.sample_count > 1) { - WGPUTextureDescriptor msaa_desc; - memset(&msaa_desc, 0, sizeof(msaa_desc)); - msaa_desc.usage = WGPUTextureUsage_OutputAttachment; - msaa_desc.dimension = WGPUTextureDimension_2D; - msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; - msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; - msaa_desc.size.depth = 1; - msaa_desc.arrayLayerCount = 1; - msaa_desc.format = _sapp.emsc.wgpu.render_format; - msaa_desc.mipLevelCount = 1; - msaa_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); - _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { - if (_sapp.emsc.wgpu.msaa_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); - _sapp.emsc.wgpu.msaa_tex = 0; - } - if (_sapp.emsc.wgpu.msaa_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); - _sapp.emsc.wgpu.msaa_view = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); - _sapp.emsc.wgpu.depth_stencil_tex = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); - _sapp.emsc.wgpu.depth_stencil_view = 0; - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { - if (_sapp.emsc.wgpu.swapchain_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); - } - _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); -} -#endif - -_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - 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); - sapp_js_add_beforeunload_listener(); - if (_sapp.clipboard.enabled) { - sapp_js_add_clipboard_listener(); - } - if (_sapp.drop.enabled) { - sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - #endif -} - -_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); - 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); - sapp_js_remove_beforeunload_listener(); - if (_sapp.clipboard.enabled) { - sapp_js_remove_clipboard_listener(); - } - if (_sapp.drop.enabled) { - sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); - #endif -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { - _SOKOL_UNUSED(time); - _SOKOL_UNUSED(userData); - - #if defined(SOKOL_WGPU) - /* - on WebGPU, the emscripten frame callback will already be called while - the asynchronous WebGPU device and swapchain initialization is still - in progress - */ - switch (_sapp.emsc.wgpu.state) { - case _SAPP_EMSC_WGPU_STATE_INITIAL: - /* async JS init hasn't finished yet */ - break; - case _SAPP_EMSC_WGPU_STATE_READY: - /* perform post-async init stuff */ - _sapp_emsc_wgpu_surfaces_create(); - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; - break; - case _SAPP_EMSC_WGPU_STATE_RUNNING: - /* a regular frame */ - _sapp_emsc_wgpu_next_frame(); - _sapp_frame(); - break; - } - #else - /* WebGL code path */ - _sapp_frame(); - #endif - - /* quit-handling */ - if (_sapp.quit_requested) { - _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - _sapp_call_event(&_sapp.event); - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - _sapp_emsc_unregister_eventhandlers(); - _sapp_call_cleanup(); - _sapp_discard_state(); - return EM_FALSE; - } - return EM_TRUE; -} - -_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; - h = (double) _sapp.desc.height; - } - else { - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.window_width = (int) w; - _sapp.window_height = (int) h; - _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - _sapp_emsc_webgl_init(); - #elif defined(SOKOL_WGPU) - sapp_js_wgpu_init(); - #endif - _sapp.valid = true; - _sapp_emsc_register_eventhandlers(); - - /* start the frame loop */ - emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0); - - /* NOT A BUG: do not call _sapp_discard_state() here, instead this is - called in _sapp_emsc_frame() when the application is ordered to quit - */ -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_emsc_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_EMSCRIPTEN */ - -/*== MISC GL SUPPORT FUNCTIONS ================================================*/ -#if defined(SOKOL_GLCORE33) -typedef struct { - int red_bits; - int green_bits; - int blue_bits; - int alpha_bits; - int depth_bits; - int stencil_bits; - int samples; - bool doublebuffer; - uintptr_t handle; -} _sapp_gl_fbconfig; - -_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { - memset(fbconfig, 0, sizeof(_sapp_gl_fbconfig)); - /* -1 means "don't care" */ - fbconfig->red_bits = -1; - fbconfig->green_bits = -1; - fbconfig->blue_bits = -1; - fbconfig->alpha_bits = -1; - fbconfig->depth_bits = -1; - fbconfig->stencil_bits = -1; - fbconfig->samples = -1; -} - -_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { - int missing, least_missing = 1000000; - int color_diff, least_color_diff = 10000000; - int extra_diff, least_extra_diff = 10000000; - const _sapp_gl_fbconfig* current; - const _sapp_gl_fbconfig* closest = 0; - for (int i = 0; i < count; i++) { - current = alternatives + i; - if (desired->doublebuffer != current->doublebuffer) { - continue; - } - missing = 0; - if (desired->alpha_bits > 0 && current->alpha_bits == 0) { - missing++; - } - if (desired->depth_bits > 0 && current->depth_bits == 0) { - missing++; - } - if (desired->stencil_bits > 0 && current->stencil_bits == 0) { - missing++; - } - if (desired->samples > 0 && current->samples == 0) { - /* Technically, several multisampling buffers could be - involved, but that's a lower level implementation detail and - not important to us here, so we count them as one - */ - missing++; - } - - /* These polynomials make many small channel size differences matter - less than one large channel size difference - Calculate color channel size difference value - */ - color_diff = 0; - if (desired->red_bits != -1) { - color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); - } - if (desired->green_bits != -1) { - color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); - } - if (desired->blue_bits != -1) { - color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); - } - - /* Calculate non-color channel size difference value */ - extra_diff = 0; - if (desired->alpha_bits != -1) { - extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); - } - if (desired->depth_bits != -1) { - extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); - } - if (desired->stencil_bits != -1) { - extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); - } - if (desired->samples != -1) { - extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); - } - - /* Figure out if the current one is better than the best one found so far - Least number of missing buffers is the most important heuristic, - then color buffer size match and lastly size match for other buffers - */ - if (missing < least_missing) { - closest = current; - } - else if (missing == least_missing) { - if ((color_diff < least_color_diff) || - (color_diff == least_color_diff && extra_diff < least_extra_diff)) - { - closest = current; - } - } - if (current == closest) { - least_missing = missing; - least_color_diff = color_diff; - least_extra_diff = extra_diff; - } - } - return closest; -} -#endif - -/*== WINDOWS DESKTOP and UWP====================================================*/ -#if defined(_SAPP_WIN32) || defined(_SAPP_UWP) -_SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - memset(dst, 0, (size_t)dst_num_bytes); - const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); - const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); - if ((dst_needed > 0) && (dst_needed < dst_chars)) { - MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); - return true; - } - else { - /* input string doesn't fit into destination buffer */ - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_uwp_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { - /* same as GLFW */ - _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; - _sapp.keycodes[0x002] = SAPP_KEYCODE_1; - _sapp.keycodes[0x003] = SAPP_KEYCODE_2; - _sapp.keycodes[0x004] = SAPP_KEYCODE_3; - _sapp.keycodes[0x005] = SAPP_KEYCODE_4; - _sapp.keycodes[0x006] = SAPP_KEYCODE_5; - _sapp.keycodes[0x007] = SAPP_KEYCODE_6; - _sapp.keycodes[0x008] = SAPP_KEYCODE_7; - _sapp.keycodes[0x009] = SAPP_KEYCODE_8; - _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; - _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; - _sapp.keycodes[0x030] = SAPP_KEYCODE_B; - _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; - _sapp.keycodes[0x020] = SAPP_KEYCODE_D; - _sapp.keycodes[0x012] = SAPP_KEYCODE_E; - _sapp.keycodes[0x021] = SAPP_KEYCODE_F; - _sapp.keycodes[0x022] = SAPP_KEYCODE_G; - _sapp.keycodes[0x023] = SAPP_KEYCODE_H; - _sapp.keycodes[0x017] = SAPP_KEYCODE_I; - _sapp.keycodes[0x024] = SAPP_KEYCODE_J; - _sapp.keycodes[0x025] = SAPP_KEYCODE_K; - _sapp.keycodes[0x026] = SAPP_KEYCODE_L; - _sapp.keycodes[0x032] = SAPP_KEYCODE_M; - _sapp.keycodes[0x031] = SAPP_KEYCODE_N; - _sapp.keycodes[0x018] = SAPP_KEYCODE_O; - _sapp.keycodes[0x019] = SAPP_KEYCODE_P; - _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x013] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; - _sapp.keycodes[0x014] = SAPP_KEYCODE_T; - _sapp.keycodes[0x016] = SAPP_KEYCODE_U; - _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; - _sapp.keycodes[0x011] = SAPP_KEYCODE_W; - _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; - _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; - _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; - _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; - _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; - _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; - _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; - _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; - _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; - _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; -} -#endif // _SAPP_WIN32 || _SAPP_UWP - -/*== WINDOWS DESKTOP===========================================================*/ -#if defined(_SAPP_WIN32) - -#if defined(SOKOL_D3D11) - -#if defined(__cplusplus) -#define _sapp_d3d11_Release(self) (self)->Release() -#else -#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) -#endif - -#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } - -static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { - #if defined(__cplusplus) - return self->GetBuffer(Buffer, riid, ppSurface); - #else - return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { - #if defined(__cplusplus) - return self->CreateRenderTargetView(pResource, pDesc, ppRTView); - #else - return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { - #if defined(__cplusplus) - return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); - #else - return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { - #if defined(__cplusplus) - return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); - #else - return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); - #endif -} - -static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { - #if defined(__cplusplus) - self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #else - self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #endif -} - -static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { - #if defined(__cplusplus) - return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); - #else - return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); - #endif -} - -static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { - #if defined(__cplusplus) - return self->Present(SyncInterval, Flags); - #else - return self->lpVtbl->Present(self, SyncInterval, Flags); - #endif -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { - DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; - sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; - sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; - sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - sc_desc->BufferDesc.RefreshRate.Numerator = 60; - sc_desc->BufferDesc.RefreshRate.Denominator = 1; - sc_desc->OutputWindow = _sapp.win32.hwnd; - sc_desc->Windowed = true; - if (_sapp.win32.is_win10_or_greater) { - sc_desc->BufferCount = 2; - sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; - } - else { - sc_desc->BufferCount = 1; - sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - } - sc_desc->SampleDesc.Count = 1; - sc_desc->SampleDesc.Quality = 0; - sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; - #if defined(SOKOL_DEBUG) - create_flags |= D3D11_CREATE_DEVICE_DEBUG; - #endif - D3D_FEATURE_LEVEL feature_level; - HRESULT hr = D3D11CreateDeviceAndSwapChain( - NULL, /* pAdapter (use default) */ - D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ - NULL, /* Software */ - create_flags, /* Flags */ - NULL, /* pFeatureLevels */ - 0, /* FeatureLevels */ - D3D11_SDK_VERSION, /* SDKVersion */ - sc_desc, /* pSwapChainDesc */ - &_sapp.d3d11.swap_chain, /* ppSwapChain */ - &_sapp.d3d11.device, /* ppDevice */ - &feature_level, /* pFeatureLevel */ - &_sapp.d3d11.device_context); /* ppImmediateContext */ - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device); -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { - SOKOL_ASSERT(0 == _sapp.d3d11.rt); - SOKOL_ASSERT(0 == _sapp.d3d11.rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.ds); - SOKOL_ASSERT(0 == _sapp.d3d11.dsv); - - HRESULT hr; - - /* view for the swapchain-created framebuffer */ - #ifdef __cplusplus - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); - #else - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &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); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); - - /* common desc for MSAA and depth-stencil texture */ - D3D11_TEXTURE2D_DESC tex_desc; - memset(&tex_desc, 0, sizeof(tex_desc)); - tex_desc.Width = (UINT)_sapp.framebuffer_width; - tex_desc.Height = (UINT)_sapp.framebuffer_height; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Usage = D3D11_USAGE_DEFAULT; - tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; - tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; - tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); - - /* create MSAA texture and view if antialiasing requested */ - if (_sapp.sample_count > 1) { - tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); - hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); - } - - /* texture and view for the depth-stencil-surface */ - tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); - hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); - _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { - if (_sapp.d3d11.swap_chain) { - _sapp_d3d11_destroy_default_render_target(); - _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); - _sapp_d3d11_create_default_render_target(); - } -} - -_SOKOL_PRIVATE void _sapp_d3d11_present(void) { - /* do MSAA resolve if needed */ - if (_sapp.sample_count > 1) { - SOKOL_ASSERT(_sapp.d3d11.rt); - SOKOL_ASSERT(_sapp.d3d11.msaa_rt); - _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); - } - _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, 0); -} - -#endif /* SOKOL_D3D11 */ - -#if defined(SOKOL_GLCORE33) -_SOKOL_PRIVATE void _sapp_wgl_init(void) { - _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); - if (!_sapp.wgl.opengl32) { - _sapp_fail("Failed to load opengl32.dll\n"); - } - SOKOL_ASSERT(_sapp.wgl.opengl32); - _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); - SOKOL_ASSERT(_sapp.wgl.CreateContext); - _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); - SOKOL_ASSERT(_sapp.wgl.DeleteContext); - _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); - SOKOL_ASSERT(_sapp.wgl.GetProcAddress); - _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); - SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); - _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); - SOKOL_ASSERT(_sapp.wgl.MakeCurrent); - - _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, - L"SOKOLAPP", - L"sokol-app message window", - WS_CLIPSIBLINGS|WS_CLIPCHILDREN, - 0, 0, 1, 1, - NULL, NULL, - GetModuleHandleW(NULL), - NULL); - if (!_sapp.wgl.msg_hwnd) { - _sapp_fail("Win32: failed to create helper window!\n"); - } - SOKOL_ASSERT(_sapp.wgl.msg_hwnd); - ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); - MSG msg; - while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); - if (!_sapp.wgl.msg_dc) { - _sapp_fail("Win32: failed to obtain helper window DC!\n"); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { - SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); - DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; - FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; -} - -_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext && extensions); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { - SOKOL_ASSERT(ext); - if (_sapp.wgl.GetExtensionsStringEXT) { - const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - if (_sapp.wgl.GetExtensionsStringARB) { - const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - return false; -} - -_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { - SOKOL_ASSERT(_sapp.wgl.msg_dc); - PIXELFORMATDESCRIPTOR pfd; - memset(&pfd, 0, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 24; - if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { - _sapp_fail("WGL: failed to set pixel format for dummy context\n"); - } - HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); - if (!rc) { - _sapp_fail("WGL: Failed to create dummy context\n"); - } - if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { - _sapp_fail("WGL: Failed to make context current\n"); - } - _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); - _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); - _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); - _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); - _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); - _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); - _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); - _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); - _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); - _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); - _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); - _sapp.wgl.DeleteContext(rc); -} - -_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - int value = 0; - if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { - _sapp_fail("WGL: Failed to retrieve pixel format attribute\n"); - } - return value; -} - -_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { - SOKOL_ASSERT(_sapp.win32.dc); - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - const _sapp_gl_fbconfig* closest; - - int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); - _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); - SOKOL_ASSERT(usable_configs); - int usable_count = 0; - for (int i = 0; i < native_count; i++) { - const int n = i + 1; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { - continue; - } - u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); - u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); - u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); - u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); - u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); - u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); - if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { - u->doublebuffer = true; - } - if (_sapp.wgl.arb_multisample) { - u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); - } - u->handle = (uintptr_t)n; - usable_count++; - } - SOKOL_ASSERT(usable_count > 0); - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - int pixel_format = 0; - if (closest) { - pixel_format = (int) closest->handle; - } - SOKOL_FREE(usable_configs); - return pixel_format; -} - -_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { - int pixel_format = _sapp_wgl_find_pixel_format(); - if (0 == pixel_format) { - _sapp_fail("WGL: Didn't find matching pixel format.\n"); - } - PIXELFORMATDESCRIPTOR pfd; - if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { - _sapp_fail("WGL: Failed to retrieve PFD for selected pixel format!\n"); - } - if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { - _sapp_fail("WGL: Failed to set selected pixel format!\n"); - } - if (!_sapp.wgl.arb_create_context) { - _sapp_fail("WGL: ARB_create_context required!\n"); - } - if (!_sapp.wgl.arb_create_context_profile) { - _sapp_fail("WGL: ARB_create_context_profile required!\n"); - } - const int attrs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - WGL_CONTEXT_MINOR_VERSION_ARB, 3, - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, 0 - }; - _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); - if (!_sapp.wgl.gl_ctx) { - const DWORD err = GetLastError(); - if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { - _sapp_fail("WGL: Driver does not support OpenGL version 3.3\n"); - } - else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { - _sapp_fail("WGL: Driver does not support the requested OpenGL profile"); - } - else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { - _sapp_fail("WGL: The share context is not compatible with the requested context"); - } - else { - _sapp_fail("WGL: Failed to create OpenGL context"); - } - } - _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); - if (_sapp.wgl.ext_swap_control) { - /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ - _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { - SOKOL_ASSERT(_sapp.wgl.gl_ctx); - _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); - _sapp.wgl.gl_ctx = 0; -} - -_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { - SOKOL_ASSERT(_sapp.win32.dc); - /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ - SwapBuffers(_sapp.win32.dc); -} -#endif /* SOKOL_GLCORE33 */ - -_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - memset(dst, 0, (size_t)dst_num_bytes); - const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); - if (bytes_needed <= dst_num_bytes) { - WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { - HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); - MONITORINFO minfo; - memset(&minfo, 0, sizeof(minfo)); - minfo.cbSize = sizeof(MONITORINFO); - GetMonitorInfo(monitor, &minfo); - const RECT mr = minfo.rcMonitor; - const int monitor_w = mr.right - mr.left; - const int monitor_h = mr.bottom - mr.top; - - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD win_style; - RECT rect = { 0, 0, 0, 0 }; - - _sapp.fullscreen = !_sapp.fullscreen; - if (!_sapp.fullscreen) { - win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.desc.width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.desc.height * _sapp.win32.dpi.window_scale); - } - else { - win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.right = monitor_w; - rect.bottom = monitor_h; - } - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - int win_width = rect.right - rect.left; - int win_height = rect.bottom - rect.top; - if (!_sapp.fullscreen) { - rect.left = (monitor_w - win_width) / 2; - rect.top = (monitor_h - win_height) / 2; - } - - SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); - SetWindowPos(_sapp.win32.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); -} - -_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool visible) { - /* NOTE: this function is only called when the mouse visibility actually changes */ - ShowCursor((BOOL)visible); -} - -_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { - if (0 == _sapp.win32.mouse_capture_mask) { - SetCapture(_sapp.win32.hwnd); - } - _sapp.win32.mouse_capture_mask |= btn_mask; -} - -_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { - if (0 != _sapp.win32.mouse_capture_mask) { - _sapp.win32.mouse_capture_mask &= ~btn_mask; - if (0 == _sapp.win32.mouse_capture_mask) { - ReleaseCapture(); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - _sapp_win32_release_mouse(0xFF); - if (_sapp.mouse.locked) { - /* store the current mouse position, so it can be restored when unlocked */ - POINT pos; - BOOL res = GetCursorPos(&pos); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - _sapp.win32.mouse_locked_x = pos.x; - _sapp.win32.mouse_locked_y = pos.y; - - /* while the mouse is locked, make the mouse cursor invisible and - confine the mouse movement to a small rectangle inside our window - (so that we dont miss any mouse up events) - */ - RECT client_rect = { - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y, - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y - }; - ClipCursor(&client_rect); - - /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ - ShowCursor(FALSE); - - /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ - const RAWINPUTDEVICE rid = { - 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC - 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE - 0, // dwFlags - _sapp.win32.hwnd // hwndTarget - }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - SOKOL_LOG("RegisterRawInputDevices() failed (on mouse lock).\n"); - } - /* in case the raw mouse device only supports absolute position reporting, - we need to skip the dx/dy compution for the first WM_INPUT event - */ - _sapp.win32.raw_input_mousepos_valid = false; - } - else { - /* disable raw input for mouse */ - const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - SOKOL_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n"); - } - - /* let the mouse roam freely again */ - ClipCursor(NULL); - ShowCursor(TRUE); - - /* restore the 'pre-locked' mouse position */ - BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - } -} - -/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ -_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { - RECT rect; - 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); - 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; - } - } - else { - _sapp.window_width = _sapp.window_height = 1; - _sapp.framebuffer_width = _sapp.framebuffer_height = 1; - } - return false; -} - -_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { - uint32_t mods = 0; - if (GetKeyState(VK_SHIFT) & (1<<15)) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (GetKeyState(VK_CONTROL) & (1<<15)) { - mods |= SAPP_MODIFIER_CTRL; - } - if (GetKeyState(VK_MENU) & (1<<15)) { - mods |= SAPP_MODIFIER_ALT; - } - if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.mouse_button = btn; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.scroll_x = -x / 30.0f; - _sapp.event.scroll_y = y / 30.0f; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { - if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.key_code = _sapp.keycodes[vk]; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { - if (_sapp_events_enabled() && (c >= 32)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.char_code = c; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { - if (!_sapp.drop.enabled) { - return; - } - _sapp_clear_drop_buffer(); - bool drop_failed = false; - const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); - _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; - for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { - const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; - WCHAR* buffer = (WCHAR*) SOKOL_CALLOC(num_chars, sizeof(WCHAR)); - DragQueryFileW(hdrop, i, buffer, num_chars); - if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n"); - drop_failed = true; - } - SOKOL_FREE(buffer); - } - DragFinish(hdrop); - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } -} - -_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (!_sapp.win32.in_create_window) { - switch (uMsg) { - case WM_CLOSE: - /* only give user a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a change to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - PostQuitMessage(0); - } - return 0; - case WM_SYSCOMMAND: - switch (wParam & 0xFFF0) { - case SC_SCREENSAVE: - case SC_MONITORPOWER: - if (_sapp.fullscreen) { - /* disable screen saver and blanking in fullscreen mode */ - return 0; - } - break; - case SC_KEYMENU: - /* user trying to access menu via ALT */ - return 0; - } - break; - case WM_ERASEBKGND: - return 1; - case WM_SIZE: - { - const bool iconified = wParam == SIZE_MINIMIZED; - if (iconified != _sapp.win32.iconified) { - _sapp.win32.iconified = iconified; - if (iconified) { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - break; - case WM_SETCURSOR: - if (_sapp.desc.user_cursor) { - if (LOWORD(lParam) == HTCLIENT) { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - return 1; - } - } - break; - case WM_LBUTTONDOWN: - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); - _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - /* mouse only reports absolute position - NOTE: THIS IS UNTESTED, it's unclear from reading the - Win32 RawInput docs under which circumstances absolute - positions are sent. - */ - if (_sapp.win32.raw_input_mousepos_valid) { - LONG new_x = raw_mouse_data->data.mouse.lLastX; - LONG new_y = raw_mouse_data->data.mouse.lLastY; - _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); - _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); - _sapp.win32.raw_input_mousepos_x = new_x; - _sapp.win32.raw_input_mousepos_y = new_y; - _sapp.win32.raw_input_mousepos_valid = true; - } - } - else { - /* mouse reports movement delta (this seems to be the common case) */ - _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; - _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; - } - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); - } - break; - - case WM_MOUSELEAVE: - if (!_sapp.mouse.locked) { - _sapp.win32.mouse_tracked = false; - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); - } - break; - case WM_MOUSEWHEEL: - _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); - break; - case WM_MOUSEHWHEEL: - _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); - break; - case WM_CHAR: - _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); - break; - case WM_KEYUP: - case WM_SYSKEYUP: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); - break; - case WM_ENTERSIZEMOVE: - SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); - break; - case WM_EXITSIZEMOVE: - KillTimer(_sapp.win32.hwnd, 1); - break; - case WM_TIMER: - _sapp_frame(); - #if defined(SOKOL_D3D11) - _sapp_d3d11_present(); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* NOTE: resizing the swap-chain during resize leads to a substantial - memory spike (hundreds of megabytes for a few seconds). - - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); - } - */ - break; - case WM_DROPFILES: - _sapp_win32_files_dropped((HDROP)wParam); - break; - default: - break; - } - } - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -_SOKOL_PRIVATE void _sapp_win32_create_window(void) { - WNDCLASSW wndclassw; - memset(&wndclassw, 0, sizeof(wndclassw)); - wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; - wndclassw.hInstance = GetModuleHandleW(NULL); - wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); - wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); - wndclassw.lpszClassName = L"SOKOLAPP"; - RegisterClassW(&wndclassw); - - DWORD win_style; - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - RECT rect = { 0, 0, 0, 0 }; - if (_sapp.fullscreen) { - win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.right = GetSystemMetrics(SM_CXSCREEN); - rect.bottom = GetSystemMetrics(SM_CYSCREEN); - } - else { - win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); - } - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - const int win_width = rect.right - rect.left; - const int win_height = rect.bottom - rect.top; - _sapp.win32.in_create_window = true; - _sapp.win32.hwnd = CreateWindowExW( - win_ex_style, /* dwExStyle */ - L"SOKOLAPP", /* lpClassName */ - _sapp.window_title_wide, /* lpWindowName */ - win_style, /* dwStyle */ - CW_USEDEFAULT, /* X */ - CW_USEDEFAULT, /* Y */ - win_width, /* nWidth */ - win_height, /* nHeight */ - NULL, /* hWndParent */ - NULL, /* hMenu */ - GetModuleHandle(NULL), /* hInstance */ - NULL); /* lParam */ - ShowWindow(_sapp.win32.hwnd, SW_SHOW); - _sapp.win32.in_create_window = false; - _sapp.win32.dc = GetDC(_sapp.win32.hwnd); - SOKOL_ASSERT(_sapp.win32.dc); - _sapp_win32_update_dimensions(); - - DragAcceptFiles(_sapp.win32.hwnd, 1); -} - -_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { - DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; - UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); -} - -_SOKOL_PRIVATE void _sapp_win32_init_console(void) { - if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { - BOOL con_valid = FALSE; - if (_sapp.desc.win32_console_create) { - con_valid = AllocConsole(); - } - else if (_sapp.desc.win32_console_attach) { - con_valid = AttachConsole(ATTACH_PARENT_PROCESS); - } - if (con_valid) { - freopen("CON", "w", stdout); - freopen("CON", "w", stderr); - } - } - if (_sapp.desc.win32_console_utf8) { - _sapp.win32.orig_codepage = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - } -} - -_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { - if (_sapp.desc.win32_console_utf8) { - SetConsoleOutputCP(_sapp.win32.orig_codepage); - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { - - typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); - typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); - typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; - SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; - GETDPIFORMONITOR_T fn_getdpiformonitor = 0; - HINSTANCE user32 = LoadLibraryA("user32.dll"); - if (user32) { - fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); - } - HINSTANCE shcore = LoadLibraryA("shcore.dll"); - if (shcore) { - fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); - fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); - } - if (fn_setprocessdpiawareness) { - /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ - PROCESS_DPI_AWARENESS process_dpi_awareness = PROCESS_SYSTEM_DPI_AWARE; - _sapp.win32.dpi.aware = true; - if (!_sapp.desc.high_dpi) { - process_dpi_awareness = PROCESS_DPI_UNAWARE; - _sapp.win32.dpi.aware = false; - } - fn_setprocessdpiawareness(process_dpi_awareness); - } - else if (fn_setprocessdpiaware) { - fn_setprocessdpiaware(); - _sapp.win32.dpi.aware = true; - } - /* get dpi scale factor for main monitor */ - if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { - POINT pt = { 1, 1 }; - HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); - UINT dpix, dpiy; - HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr)); - /* clamp window scale to an integer factor */ - _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; - } - else { - _sapp.win32.dpi.window_scale = 1.0f; - } - if (_sapp.desc.high_dpi) { - _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; - _sapp.win32.dpi.mouse_scale = 1.0f; - } - else { - _sapp.win32.dpi.content_scale = 1.0f; - _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; - } - _sapp.dpi_scale = _sapp.win32.dpi.content_scale; - if (user32) { - FreeLibrary(user32); - } - if (shcore) { - FreeLibrary(shcore); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { - SOKOL_ASSERT(str); - SOKOL_ASSERT(_sapp.win32.hwnd); - SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); - - wchar_t* wchar_buf = 0; - const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); - HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); - if (!object) { - goto error; - } - wchar_buf = (wchar_t*) GlobalLock(object); - if (!wchar_buf) { - goto error; - } - if (!_sapp_win32_uwp_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { - goto error; - } - GlobalUnlock(wchar_buf); - wchar_buf = 0; - if (!OpenClipboard(_sapp.win32.hwnd)) { - goto error; - } - EmptyClipboard(); - SetClipboardData(CF_UNICODETEXT, object); - CloseClipboard(); - return true; - -error: - if (wchar_buf) { - GlobalUnlock(object); - } - if (object) { - GlobalFree(object); - } - return false; -} - -_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); - SOKOL_ASSERT(_sapp.win32.hwnd); - if (!OpenClipboard(_sapp.win32.hwnd)) { - /* silently ignore any errors and just return the current - content of the local clipboard buffer - */ - return _sapp.clipboard.buffer; - } - HANDLE object = GetClipboardData(CF_UNICODETEXT); - if (!object) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); - if (!wchar_buf) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { - SOKOL_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n"); - } - GlobalUnlock(object); - CloseClipboard(); - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { - _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); -} - -/* don't laugh, but this seems to be the easiest and most robust - way to check if we're running on Win10 - - From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 -*/ -_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { - HMODULE h = GetModuleHandleW(L"kernel32.dll"); - if (NULL != h) { - return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_win32_init_console(); - _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); - _sapp_win32_uwp_init_keytable(); - _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(); - #if defined(SOKOL_D3D11) - _sapp_d3d11_create_device_and_swapchain(); - _sapp_d3d11_create_default_render_target(); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_init(); - _sapp_wgl_load_extensions(); - _sapp_wgl_create_context(); - #if !defined(SOKOL_WIN32_NO_GL_LOADER) - _sapp_win32_gl_loadfuncs(); - #endif - #endif - _sapp.valid = true; - - bool done = false; - while (!(done || _sapp.quit_ordered)) { - MSG msg; - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { - if (WM_QUIT == msg.message) { - done = true; - continue; - } - else { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - _sapp_frame(); - #if defined(SOKOL_D3D11) - _sapp_d3d11_present(); - if (IsIconic(_sapp.win32.hwnd)) { - Sleep((DWORD)(16 * _sapp.swap_interval)); - } - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); - } - if (_sapp.quit_requested) { - PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); - } - } - _sapp_call_cleanup(); - - #if defined(SOKOL_D3D11) - _sapp_d3d11_destroy_default_render_target(); - _sapp_d3d11_destroy_device_and_swapchain(); - #else - _sapp_wgl_destroy_context(); - _sapp_wgl_shutdown(); - #endif - _sapp_win32_destroy_window(); - _sapp_win32_restore_console(); - _sapp_discard_state(); -} - -_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { - int argc = 0; - char** argv = 0; - char* args; - - LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); - if (w_argv == NULL) { - _sapp_fail("Win32: failed to parse command line"); - } else { - size_t size = wcslen(w_command_line) * 4; - argv = (char**) SOKOL_CALLOC(1, ((size_t)argc + 1) * sizeof(char*) + size); - SOKOL_ASSERT(argv); - args = (char*) &argv[argc + 1]; - int n; - for (int i = 0; i < argc; ++i) { - n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); - if (n == 0) { - _sapp_fail("Win32: failed to convert all arguments to utf8"); - break; - } - argv[i] = args; - size -= (size_t)n; - args += n; - } - LocalFree(w_argv); - } - *o_argc = argc; - return argv; -} - -#if !defined(SOKOL_NO_ENTRY) -#if defined(SOKOL_WIN32_FORCE_MAIN) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_win32_run(&desc); - return 0; -} -#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { - _SOKOL_UNUSED(hInstance); - _SOKOL_UNUSED(hPrevInstance); - _SOKOL_UNUSED(lpCmdLine); - _SOKOL_UNUSED(nCmdShow); - int argc_utf8 = 0; - char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); - sapp_desc desc = sokol_main(argc_utf8, argv_utf8); - _sapp_win32_run(&desc); - SOKOL_FREE(argv_utf8); - return 0; -} -#endif /* SOKOL_WIN32_FORCE_MAIN */ -#endif /* SOKOL_NO_ENTRY */ - -#ifdef _MSC_VER - #pragma warning(pop) -#endif - -#endif /* _SAPP_WIN32 */ - -/*== UWP ================================================================*/ -#if defined(_SAPP_UWP) - -// Helper functions -_SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) { - _sapp.uwp.dpi.window_scale = monitor_dpi / 96.0f; - if (_sapp.desc.high_dpi) { - _sapp.uwp.dpi.content_scale = _sapp.uwp.dpi.window_scale; - _sapp.uwp.dpi.mouse_scale = 1.0f * _sapp.uwp.dpi.window_scale; - } - else { - _sapp.uwp.dpi.content_scale = 1.0f; - _sapp.uwp.dpi.mouse_scale = 1.0f; - } - _sapp.dpi_scale = _sapp.uwp.dpi.content_scale; -} - -_SOKOL_PRIVATE void _sapp_uwp_show_mouse(bool visible) { - using namespace winrt::Windows::UI::Core; - - /* NOTE: this function is only called when the mouse visibility actually changes */ - CoreWindow::GetForCurrentThread().PointerCursor(visible ? - CoreCursor(CoreCursorType::Arrow, 0) : - CoreCursor(nullptr)); -} - -_SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) { - using namespace winrt::Windows::System; - using namespace winrt::Windows::UI::Core; - - uint32_t mods = 0; - if ((sender_window.GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_SHIFT; - } - if ((sender_window.GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_CTRL; - } - if ((sender_window.GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { - mods |= SAPP_MODIFIER_ALT; - } - if (((sender_window.GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) || - ((sender_window.GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)) - { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_uwp_mouse_event(sapp_event_type type, sapp_mousebutton btn, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.mouse_button = btn; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_scroll_event(float delta, bool horizontal, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.scroll_x = horizontal ? (-delta / 30.0f) : 0.0f; - _sapp.event.scroll_y = horizontal ? 0.0f : (delta / 30.0f); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_extract_mouse_button_events(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - - // we need to figure out ourselves what mouse buttons have been pressed and released, - // because UWP doesn't properly send down/up mouse button events when multiple buttons - // are pressed down, so we also need to check the mouse button state in other mouse events - // to track what buttons have been pressed down and released - // - auto properties = args.CurrentPoint().Properties(); - const uint8_t lmb_bit = (1 << SAPP_MOUSEBUTTON_LEFT); - const uint8_t rmb_bit = (1 << SAPP_MOUSEBUTTON_RIGHT); - const uint8_t mmb_bit = (1 << SAPP_MOUSEBUTTON_MIDDLE); - uint8_t new_btns = 0; - if (properties.IsLeftButtonPressed()) { - new_btns |= lmb_bit; - } - if (properties.IsRightButtonPressed()) { - new_btns |= rmb_bit; - } - if (properties.IsMiddleButtonPressed()) { - new_btns |= mmb_bit; - } - const uint8_t old_btns = _sapp.uwp.mouse_buttons; - const uint8_t chg_btns = new_btns ^ old_btns; - - _sapp.uwp.mouse_buttons = new_btns; - - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - sapp_mousebutton btn = SAPP_MOUSEBUTTON_INVALID; - if (chg_btns & lmb_bit) { - btn = SAPP_MOUSEBUTTON_LEFT; - type = (new_btns & lmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (chg_btns & rmb_bit) { - btn = SAPP_MOUSEBUTTON_RIGHT; - type = (new_btns & rmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (chg_btns & mmb_bit) { - btn = SAPP_MOUSEBUTTON_MIDDLE; - type = (new_btns & mmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; - } - if (type != SAPP_EVENTTYPE_INVALID) { - _sapp_uwp_mouse_event(type, btn, sender); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_key_event(sapp_event_type type, winrt::Windows::UI::Core::CoreWindow const& sender_window, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto key_status = args.KeyStatus(); - uint32_t ext_scan_code = key_status.ScanCode | (key_status.IsExtendedKey ? 0x100 : 0); - if (_sapp_events_enabled() && (ext_scan_code < SAPP_MAX_KEYCODES)) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.key_code = _sapp.keycodes[ext_scan_code]; - _sapp.event.key_repeat = type == SAPP_EVENTTYPE_KEY_UP ? false : key_status.WasKeyDown; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_uwp_char_event(uint32_t c, bool repeat, winrt::Windows::UI::Core::CoreWindow const& sender_window) { - if (_sapp_events_enabled() && (c >= 32)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_uwp_mods(sender_window); - _sapp.event.char_code = c; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_uwp_toggle_fullscreen(void) { - auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); - _sapp.fullscreen = appView.IsFullScreenMode(); - if (!_sapp.fullscreen) { - appView.TryEnterFullScreenMode(); - } - else { - appView.ExitFullScreenMode(); - } - _sapp.fullscreen = appView.IsFullScreenMode(); -} - -namespace {/* Empty namespace to ensure internal linkage (same as _SOKOL_PRIVATE) */ - -// Controls all the DirectX device resources. -class DeviceResources { -public: - // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. - interface IDeviceNotify { - virtual void OnDeviceLost() = 0; - virtual void OnDeviceRestored() = 0; - }; - - DeviceResources(); - ~DeviceResources(); - void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); - void SetLogicalSize(winrt::Windows::Foundation::Size logicalSize); - void SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation); - void SetDpi(float dpi); - void ValidateDevice(); - void HandleDeviceLost(); - void RegisterDeviceNotify(IDeviceNotify* deviceNotify); - void Trim(); - void Present(); - -private: - - // Swapchain Rotation Matrices (Z-rotation) - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation0 = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation90 = { - 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation180 = { - -1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, -1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation270 = { - 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - void CreateDeviceResources(); - void CreateWindowSizeDependentResources(); - void UpdateRenderTargetSize(); - DXGI_MODE_ROTATION ComputeDisplayRotation(); - bool SdkLayersAvailable(); - - // Direct3D objects. - winrt::com_ptr m_d3dDevice; - winrt::com_ptr m_d3dContext; - winrt::com_ptr m_swapChain; - - // Direct3D rendering objects. Required for 3D. - winrt::com_ptr m_d3dRenderTarget; - winrt::com_ptr m_d3dRenderTargetView; - winrt::com_ptr m_d3dMSAARenderTarget; - winrt::com_ptr m_d3dMSAARenderTargetView; - winrt::com_ptr m_d3dDepthStencil; - winrt::com_ptr m_d3dDepthStencilView; - D3D11_VIEWPORT m_screenViewport = { }; - - // Cached reference to the Window. - winrt::agile_ref< winrt::Windows::UI::Core::CoreWindow> m_window; - - // Cached device properties. - D3D_FEATURE_LEVEL m_d3dFeatureLevel = D3D_FEATURE_LEVEL_9_1; - winrt::Windows::Foundation::Size m_d3dRenderTargetSize = { }; - winrt::Windows::Foundation::Size m_outputSize = { }; - winrt::Windows::Foundation::Size m_logicalSize = { }; - winrt::Windows::Graphics::Display::DisplayOrientations m_nativeOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; - winrt::Windows::Graphics::Display::DisplayOrientations m_currentOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; - float m_dpi = -1.0f; - - // Transforms used for display orientation. - DirectX::XMFLOAT4X4 m_orientationTransform3D; - - // The IDeviceNotify can be held directly as it owns the DeviceResources. - IDeviceNotify* m_deviceNotify = nullptr; -}; - -// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events. -struct App : winrt::implements { -public: - // IFrameworkViewSource Methods - winrt::Windows::ApplicationModel::Core::IFrameworkView CreateView() { return *this; } - - // IFrameworkView Methods. - virtual void Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView); - virtual void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); - virtual void Load(winrt::hstring const& entryPoint); - virtual void Run(); - virtual void Uninitialize(); - -protected: - // Application lifecycle event handlers - void OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args); - void OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args); - void OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args); - - // Window event handlers - void OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args); - void OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args); - - // Navigation event handlers - void OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args); - - // Input event handlers - void OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); - void OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); - void OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args); - - // Pointer event handlers - void OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - void OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); - - // DisplayInformation event handlers. - void OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - void OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - void OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); - -private: - std::unique_ptr m_deviceResources; - bool m_windowVisible = true; -}; - -DeviceResources::DeviceResources() { - CreateDeviceResources(); -} - -DeviceResources::~DeviceResources() { - // Cleanup Sokol Context - _sapp.d3d11.device = nullptr; - _sapp.d3d11.device_context = nullptr; -} - -void DeviceResources::CreateDeviceResources() { - // This flag adds support for surfaces with a different color channel ordering - // than the API default. It is required for compatibility with Direct2D. - UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; - - #if defined(_DEBUG) - if (SdkLayersAvailable()) { - // If the project is in a debug build, enable debugging via SDK Layers with this flag. - creationFlags |= D3D11_CREATE_DEVICE_DEBUG; - } - #endif - - // This array defines the set of DirectX hardware feature levels this app will support. - // Note the ordering should be preserved. - // Don't forget to declare your application's minimum required feature level in its - // description. All applications are assumed to support 9.1 unless otherwise stated. - D3D_FEATURE_LEVEL featureLevels[] = { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 - }; - - // Create the Direct3D 11 API device object and a corresponding context. - winrt::com_ptr device; - winrt::com_ptr context; - - HRESULT hr = D3D11CreateDevice( - nullptr, // Specify nullptr to use the default adapter. - D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver. - 0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. - creationFlags, // Set debug and Direct2D compatibility flags. - featureLevels, // List of feature levels this app can support. - ARRAYSIZE(featureLevels), // Size of the list above. - D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. - device.put(), // Returns the Direct3D device created. - &m_d3dFeatureLevel, // Returns feature level of device created. - context.put() // Returns the device immediate context. - ); - - if (FAILED(hr)) { - // If the initialization fails, fall back to the WARP device. - // For more information on WARP, see: - // https://go.microsoft.com/fwlink/?LinkId=286690 - winrt::check_hresult( - D3D11CreateDevice( - nullptr, - D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. - 0, - creationFlags, - featureLevels, - ARRAYSIZE(featureLevels), - D3D11_SDK_VERSION, - device.put(), - &m_d3dFeatureLevel, - context.put() - ) - ); - } - - // Store pointers to the Direct3D 11.3 API device and immediate context. - m_d3dDevice = device.as(); - m_d3dContext = context.as(); - - // Setup Sokol Context - _sapp.d3d11.device = m_d3dDevice.get(); - _sapp.d3d11.device_context = m_d3dContext.get(); -} - -void DeviceResources::CreateWindowSizeDependentResources() { - // Cleanup Sokol Context (these are non-owning raw pointers) - _sapp.d3d11.rt = nullptr; - _sapp.d3d11.rtv = nullptr; - _sapp.d3d11.msaa_rt = nullptr; - _sapp.d3d11.msaa_rtv = nullptr; - _sapp.d3d11.ds = nullptr; - _sapp.d3d11.dsv = nullptr; - - // Clear the previous window size specific context. - ID3D11RenderTargetView* nullViews[] = { nullptr }; - m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr); - // these are smart pointers, setting to nullptr will delete the objects - m_d3dRenderTarget = nullptr; - m_d3dRenderTargetView = nullptr; - m_d3dMSAARenderTarget = nullptr; - m_d3dMSAARenderTargetView = nullptr; - m_d3dDepthStencilView = nullptr; - m_d3dDepthStencil = nullptr; - m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr); - - UpdateRenderTargetSize(); - - // The width and height of the swap chain must be based on the window's - // natively-oriented width and height. If the window is not in the native - // orientation, the dimensions must be reversed. - DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation(); - - bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270; - m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width; - m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height; - - if (m_swapChain != nullptr) { - // If the swap chain already exists, resize it. - HRESULT hr = m_swapChain->ResizeBuffers( - 2, // Double-buffered swap chain. - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - DXGI_FORMAT_B8G8R8A8_UNORM, - 0 - ); - - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - // If the device was removed for any reason, a new device and swap chain will need to be created. - HandleDeviceLost(); - - // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method - // and correctly set up the new device. - return; - } - else { - winrt::check_hresult(hr); - } - } - else { - // Otherwise, create a new one using the same adapter as the existing Direct3D device. - DXGI_SCALING scaling = (_sapp.uwp.dpi.content_scale == _sapp.uwp.dpi.window_scale) ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH; - DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 }; - - swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window. - swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height); - swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format. - swapChainDesc.Stereo = false; - swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling. - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency. - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Microsoft Store apps must use this SwapEffect. - swapChainDesc.Flags = 0; - swapChainDesc.Scaling = scaling; - swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - - // This sequence obtains the DXGI factory that was used to create the Direct3D device above. - winrt::com_ptr dxgiDevice = m_d3dDevice.as(); - winrt::com_ptr dxgiAdapter; - winrt::check_hresult(dxgiDevice->GetAdapter(dxgiAdapter.put())); - winrt::com_ptr dxgiFactory; - winrt::check_hresult(dxgiAdapter->GetParent(__uuidof(IDXGIFactory4), dxgiFactory.put_void())); - winrt::com_ptr swapChain; - winrt::check_hresult(dxgiFactory->CreateSwapChainForCoreWindow(m_d3dDevice.get(), m_window.get().as<::IUnknown>().get(), &swapChainDesc, nullptr, swapChain.put())); - m_swapChain = swapChain.as(); - - // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and - // ensures that the application will only render after each VSync, minimizing power consumption. - winrt::check_hresult(dxgiDevice->SetMaximumFrameLatency(1)); - - // Setup Sokol Context - winrt::check_hresult(swapChain->GetDesc(&_sapp.d3d11.swap_chain_desc)); - _sapp.d3d11.swap_chain = m_swapChain.as().detach(); - } - - // Set the proper orientation for the swap chain, and generate 2D and - // 3D matrix transformations for rendering to the rotated swap chain. - // Note the rotation angle for the 2D and 3D transforms are different. - // This is due to the difference in coordinate spaces. Additionally, - // the 3D matrix is specified explicitly to avoid rounding errors. - switch (displayRotation) { - case DXGI_MODE_ROTATION_IDENTITY: - m_orientationTransform3D = m_rotation0; - break; - - case DXGI_MODE_ROTATION_ROTATE90: - m_orientationTransform3D = m_rotation270; - break; - - case DXGI_MODE_ROTATION_ROTATE180: - m_orientationTransform3D = m_rotation180; - break; - - case DXGI_MODE_ROTATION_ROTATE270: - m_orientationTransform3D = m_rotation90; - break; - } - winrt::check_hresult(m_swapChain->SetRotation(displayRotation)); - - // Create a render target view of the swap chain back buffer. - winrt::check_hresult(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_d3dRenderTarget))); - winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dRenderTarget.get(), nullptr, m_d3dRenderTargetView.put())); - - // Create MSAA texture and view if needed - if (_sapp.sample_count > 1) { - CD3D11_TEXTURE2D_DESC1 msaaTexDesc( - DXGI_FORMAT_B8G8R8A8_UNORM, - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - 1, // arraySize - 1, // mipLevels - D3D11_BIND_RENDER_TARGET, - D3D11_USAGE_DEFAULT, - 0, // cpuAccessFlags - _sapp.sample_count, - _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 - ); - winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&msaaTexDesc, nullptr, m_d3dMSAARenderTarget.put())); - winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dMSAARenderTarget.get(), nullptr, m_d3dMSAARenderTargetView.put())); - } - - // Create a depth stencil view for use with 3D rendering if needed. - CD3D11_TEXTURE2D_DESC1 depthStencilDesc( - DXGI_FORMAT_D24_UNORM_S8_UINT, - lround(m_d3dRenderTargetSize.Width), - lround(m_d3dRenderTargetSize.Height), - 1, // This depth stencil view has only one texture. - 1, // Use a single mipmap level. - D3D11_BIND_DEPTH_STENCIL, - D3D11_USAGE_DEFAULT, - 0, // cpuAccessFlag - _sapp.sample_count, - _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 - ); - winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&depthStencilDesc, nullptr, m_d3dDepthStencil.put())); - - CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); - winrt::check_hresult(m_d3dDevice->CreateDepthStencilView(m_d3dDepthStencil.get(), nullptr, m_d3dDepthStencilView.put())); - - // Set sokol window and framebuffer sizes - _sapp.window_width = (int) m_logicalSize.Width; - _sapp.window_height = (int) m_logicalSize.Height; - _sapp.framebuffer_width = lround(m_d3dRenderTargetSize.Width); - _sapp.framebuffer_height = lround(m_d3dRenderTargetSize.Height); - - // Setup Sokol Context - _sapp.d3d11.rt = m_d3dRenderTarget.as().get(); - _sapp.d3d11.rtv = m_d3dRenderTargetView.as().get(); - _sapp.d3d11.ds = m_d3dDepthStencil.as().get(); - _sapp.d3d11.dsv = m_d3dDepthStencilView.get(); - if (_sapp.sample_count > 1) { - _sapp.d3d11.msaa_rt = m_d3dMSAARenderTarget.as().get(); - _sapp.d3d11.msaa_rtv = m_d3dMSAARenderTargetView.as().get(); - } - - // Sokol app is now valid - _sapp.valid = true; -} - -// Determine the dimensions of the render target and whether it will be scaled down. -void DeviceResources::UpdateRenderTargetSize() { - // Calculate the necessary render target size in pixels. - m_outputSize.Width = m_logicalSize.Width * _sapp.uwp.dpi.content_scale; - m_outputSize.Height = m_logicalSize.Height * _sapp.uwp.dpi.content_scale; - - // Prevent zero size DirectX content from being created. - m_outputSize.Width = std::max(m_outputSize.Width, 1.0f); - m_outputSize.Height = std::max(m_outputSize.Height, 1.0f); -} - -// This method is called when the CoreWindow is created (or re-created). -void DeviceResources::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { - auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); - m_window = window; - m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); - m_nativeOrientation = currentDisplayInformation.NativeOrientation(); - m_currentOrientation = currentDisplayInformation.CurrentOrientation(); - m_dpi = currentDisplayInformation.LogicalDpi(); - _sapp_uwp_configure_dpi(m_dpi); - CreateWindowSizeDependentResources(); -} - -// This method is called in the event handler for the SizeChanged event. -void DeviceResources::SetLogicalSize(winrt::Windows::Foundation::Size logicalSize) { - if (m_logicalSize != logicalSize) { - m_logicalSize = logicalSize; - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the DpiChanged event. -void DeviceResources::SetDpi(float dpi) { - if (dpi != m_dpi) { - m_dpi = dpi; - _sapp_uwp_configure_dpi(m_dpi); - // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated. - auto window = m_window.get(); - m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the OrientationChanged event. -void DeviceResources::SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation) { - if (m_currentOrientation != currentOrientation) { - m_currentOrientation = currentOrientation; - CreateWindowSizeDependentResources(); - } -} - -// This method is called in the event handler for the DisplayContentsInvalidated event. -void DeviceResources::ValidateDevice() { - // The D3D Device is no longer valid if the default adapter changed since the device - // was created or if the device has been removed. - - // First, get the information for the default adapter from when the device was created. - winrt::com_ptr dxgiDevice = m_d3dDevice.as< IDXGIDevice3>(); - winrt::com_ptr deviceAdapter; - winrt::check_hresult(dxgiDevice->GetAdapter(deviceAdapter.put())); - winrt::com_ptr deviceFactory; - winrt::check_hresult(deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory))); - winrt::com_ptr previousDefaultAdapter; - winrt::check_hresult(deviceFactory->EnumAdapters1(0, previousDefaultAdapter.put())); - DXGI_ADAPTER_DESC1 previousDesc; - winrt::check_hresult(previousDefaultAdapter->GetDesc1(&previousDesc)); - - // Next, get the information for the current default adapter. - winrt::com_ptr currentFactory; - winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory))); - winrt::com_ptr currentDefaultAdapter; - winrt::check_hresult(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put())); - DXGI_ADAPTER_DESC1 currentDesc; - winrt::check_hresult(currentDefaultAdapter->GetDesc1(¤tDesc)); - - // If the adapter LUIDs don't match, or if the device reports that it has been removed, - // a new D3D device must be created. - if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart || - previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart || - FAILED(m_d3dDevice->GetDeviceRemovedReason())) - { - // Release references to resources related to the old device. - dxgiDevice = nullptr; - deviceAdapter = nullptr; - deviceFactory = nullptr; - previousDefaultAdapter = nullptr; - - // Create a new device and swap chain. - HandleDeviceLost(); - } -} - -// Recreate all device resources and set them back to the current state. -void DeviceResources::HandleDeviceLost() { - m_swapChain = nullptr; - if (m_deviceNotify != nullptr) { - m_deviceNotify->OnDeviceLost(); - } - CreateDeviceResources(); - CreateWindowSizeDependentResources(); - if (m_deviceNotify != nullptr) { - m_deviceNotify->OnDeviceRestored(); - } -} - -// Register our DeviceNotify to be informed on device lost and creation. -void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) { - m_deviceNotify = deviceNotify; -} - -// Call this method when the app suspends. It provides a hint to the driver that the app -// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. -void DeviceResources::Trim() { - m_d3dDevice.as()->Trim(); -} - -// Present the contents of the swap chain to the screen. -void DeviceResources::Present() { - - // MSAA resolve if needed - if (_sapp.sample_count > 1) { - m_d3dContext->ResolveSubresource(m_d3dRenderTarget.get(), 0, m_d3dMSAARenderTarget.get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM); - m_d3dContext->DiscardView1(m_d3dMSAARenderTargetView.get(), nullptr, 0); - } - - // The first argument instructs DXGI to block until VSync, putting the application - // to sleep until the next VSync. This ensures we don't waste any cycles rendering - // frames that will never be displayed to the screen. - DXGI_PRESENT_PARAMETERS parameters = { 0 }; - HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); - - // Discard the contents of the render target. - // This is a valid operation only when the existing contents will be entirely - // overwritten. If dirty or scroll rects are used, this call should be removed. - m_d3dContext->DiscardView1(m_d3dRenderTargetView.get(), nullptr, 0); - - // Discard the contents of the depth stencil. - m_d3dContext->DiscardView1(m_d3dDepthStencilView.get(), nullptr, 0); - - // If the device was removed either by a disconnection or a driver upgrade, we - // must recreate all device resources. - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - HandleDeviceLost(); - } - else { - winrt::check_hresult(hr); - } -} - -// This method determines the rotation between the display device's native orientation and the -// current display orientation. -DXGI_MODE_ROTATION DeviceResources::ComputeDisplayRotation() { - DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED; - - // Note: NativeOrientation can only be Landscape or Portrait even though - // the DisplayOrientations enum has other values. - switch (m_nativeOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - switch (m_currentOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - rotation = DXGI_MODE_ROTATION_IDENTITY; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - rotation = DXGI_MODE_ROTATION_ROTATE270; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE180; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE90; - break; - } - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - switch (m_currentOrientation) { - case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: - rotation = DXGI_MODE_ROTATION_ROTATE90; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: - rotation = DXGI_MODE_ROTATION_IDENTITY; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE270; - break; - - case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: - rotation = DXGI_MODE_ROTATION_ROTATE180; - break; - } - break; - } - return rotation; -} - -// Check for SDK Layer support. -bool DeviceResources::SdkLayersAvailable() { - #if defined(_DEBUG) - HRESULT hr = D3D11CreateDevice( - nullptr, - D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. - 0, - D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. - nullptr, // Any feature level will do. - 0, - D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. - nullptr, // No need to keep the D3D device reference. - nullptr, // No need to know the feature level. - nullptr // No need to keep the D3D device context reference. - ); - return SUCCEEDED(hr); - #else - return false; - #endif -} - -// The first method called when the IFrameworkView is being created. -void App::Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) { - // Register event handlers for app lifecycle. This example includes Activated, so that we - // can make the CoreWindow active and start rendering on the window. - applicationView.Activated({ this, &App::OnActivated }); - - winrt::Windows::ApplicationModel::Core::CoreApplication::Suspending({ this, &App::OnSuspending }); - winrt::Windows::ApplicationModel::Core::CoreApplication::Resuming({ this, &App::OnResuming }); - - // At this point we have access to the device. - // We can create the device-dependent resources. - m_deviceResources = std::make_unique(); -} - -// Called when the CoreWindow object is created (or re-created). -void App::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { - window.SizeChanged({ this, &App::OnWindowSizeChanged }); - window.VisibilityChanged({ this, &App::OnVisibilityChanged }); - - window.KeyDown({ this, &App::OnKeyDown }); - window.KeyUp({ this, &App::OnKeyUp }); - window.CharacterReceived({ this, &App::OnCharacterReceived }); - - window.PointerEntered({ this, &App::OnPointerEntered }); - window.PointerExited({ this, &App::OnPointerExited }); - window.PointerPressed({ this, &App::OnPointerPressed }); - window.PointerReleased({ this, &App::OnPointerReleased }); - window.PointerMoved({ this, &App::OnPointerMoved }); - window.PointerWheelChanged({ this, &App::OnPointerWheelChanged }); - - auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); - - currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged }); - currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged }); - winrt::Windows::Graphics::Display::DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated }); - - winrt::Windows::UI::Core::SystemNavigationManager::GetForCurrentView().BackRequested({ this, &App::OnBackRequested }); - - m_deviceResources->SetWindow(window); -} - -// Initializes scene resources, or loads a previously saved app state. -void App::Load(winrt::hstring const& entryPoint) { - _SOKOL_UNUSED(entryPoint); -} - -// This method is called after the window becomes active. -void App::Run() { - // NOTE: UWP will simply terminate an application, it's not possible to detect when an application is being closed - while (true) { - if (m_windowVisible) { - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent); - _sapp_frame(); - m_deviceResources->Present(); - } - else { - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessOneAndAllPending); - } - } -} - -// Required for IFrameworkView. -// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView -// class is torn down while the app is in the foreground. -void App::Uninitialize() { - // empty -} - -// Application lifecycle event handlers. -void App::OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) { - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(applicationView); - auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); - auto targetSize = winrt::Windows::Foundation::Size((float)_sapp.desc.width, (float)_sapp.desc.height); - appView.SetPreferredMinSize(targetSize); - appView.TryResizeView(targetSize); - - // Disabling this since it can only append the title to the app name (Title - Appname). - // There's no way of just setting a string to be the window title. - //appView.Title(_sapp.window_title_wide); - - // Run() won't start until the CoreWindow is activated. - winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Activate(); - if (_sapp.desc.fullscreen) { - appView.TryEnterFullScreenMode(); - } - _sapp.fullscreen = appView.IsFullScreenMode(); -} - -void App::OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args) { - _SOKOL_UNUSED(sender); - _SOKOL_UNUSED(args); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_SUSPENDED); -} - -void App::OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args) { - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(sender); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESUMED); -} - -void App::OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args) { - _SOKOL_UNUSED(args); - m_deviceResources->SetLogicalSize(winrt::Windows::Foundation::Size(sender.Bounds().Width, sender.Bounds().Height)); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) { - _SOKOL_UNUSED(sender); - m_windowVisible = args.Visible(); - _sapp_win32_uwp_app_event(m_windowVisible ? SAPP_EVENTTYPE_RESTORED : SAPP_EVENTTYPE_ICONIFIED); -} - -void App::OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args) { - _SOKOL_UNUSED(sender); - args.Handled(true); -} - -void App::OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto status = args.KeyStatus(); - _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_DOWN, sender, args); -} - -void App::OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { - auto status = args.KeyStatus(); - _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_UP, sender, args); -} - -void App::OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) { - _sapp_uwp_char_event(args.KeyCode(), args.KeyStatus().WasKeyDown, sender); -} - -void App::OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _SOKOL_UNUSED(args); - _sapp.uwp.mouse_tracked = true; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); -} - -void App::OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _SOKOL_UNUSED(args); - _sapp.uwp.mouse_tracked = false; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, sender); -} - -void App::OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -// NOTE: for some reason this event handler is never called?? -void App::OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -void App::OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - auto position = args.CurrentPoint().Position(); - const float new_x = (float)(int)(position.X * _sapp.uwp.dpi.mouse_scale + 0.5f); - const float new_y = (float)(int)(position.Y * _sapp.uwp.dpi.mouse_scale + 0.5f); - // don't update dx/dy in the very first event - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - if (!_sapp.uwp.mouse_tracked) { - _sapp.uwp.mouse_tracked = true; - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); - } - _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, sender); - - // HACK for detecting multiple mouse button presses - _sapp_uwp_extract_mouse_button_events(sender, args); -} - -void App::OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { - auto properties = args.CurrentPoint().Properties(); - _sapp_uwp_scroll_event((float)properties.MouseWheelDelta(), properties.IsHorizontalMouseWheel(), sender); -} - -void App::OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - m_deviceResources->SetDpi(sender.LogicalDpi()); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - m_deviceResources->SetCurrentOrientation(sender.CurrentOrientation()); - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); -} - -void App::OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { - // NOTE: UNTESTED - _SOKOL_UNUSED(args); - _SOKOL_UNUSED(sender); - m_deviceResources->ValidateDevice(); -} - -} /* End empty namespace */ - -_SOKOL_PRIVATE void _sapp_uwp_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_win32_uwp_init_keytable(); - _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - winrt::Windows::ApplicationModel::Core::CoreApplication::Run(winrt::make()); -} - -#if !defined(SOKOL_NO_ENTRY) -#if defined(UNICODE) -int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { -#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { -#endif - _SOKOL_UNUSED(hInstance); - _SOKOL_UNUSED(hPrevInstance); - _SOKOL_UNUSED(lpCmdLine); - _SOKOL_UNUSED(nCmdShow); - sapp_desc desc = sokol_main(0, nullptr); - _sapp_uwp_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_UWP */ - -/*== Android ================================================================*/ -#if defined(_SAPP_ANDROID) - -/* android loop thread */ -_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { - SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); - - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - return false; - } - if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { - return false; - } - - EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; - const EGLint cfg_attributes[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, alpha_size, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, 0, - EGL_NONE, - }; - EGLConfig available_cfgs[32]; - EGLint cfg_count; - eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); - SOKOL_ASSERT(cfg_count > 0); - SOKOL_ASSERT(cfg_count <= 32); - - /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ - EGLConfig config; - bool exact_cfg_found = false; - for (int i = 0; i < cfg_count; ++i) { - EGLConfig c = available_cfgs[i]; - EGLint r, g, b, a, d; - if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && - r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { - exact_cfg_found = true; - config = c; - break; - } - } - if (!exact_cfg_found) { - config = available_cfgs[0]; - } - - EGLint ctx_attributes[] = { - #if defined(SOKOL_GLES3) - EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, - #else - EGL_CONTEXT_CLIENT_VERSION, 2, - #endif - EGL_NONE, - }; - EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); - if (context == EGL_NO_CONTEXT) { - return false; - } - - _sapp.android.config = config; - _sapp.android.display = display; - _sapp.android.context = context; - return true; -} -SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ - //eglSwapInterval(_sapp.android.display,0); -} - - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { - if (_sapp.android.display != EGL_NO_DISPLAY) { - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - SOKOL_LOG("Destroying egl surface"); - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } - if (_sapp.android.context != EGL_NO_CONTEXT) { - SOKOL_LOG("Destroying egl context"); - eglDestroyContext(_sapp.android.display, _sapp.android.context); - _sapp.android.context = EGL_NO_CONTEXT; - } - SOKOL_LOG("Terminating egl display"); - eglTerminate(_sapp.android.display); - _sapp.android.display = EGL_NO_DISPLAY; - } -} - -_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - /* TODO: set window flags */ - /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ - - /* create egl surface and make it current */ - EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); - if (surface == EGL_NO_SURFACE) { - return false; - } - if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { - return false; - } - _sapp.android.surface = surface; - return true; -} - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { - if (_sapp.android.display == EGL_NO_DISPLAY) { - return; - } - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } -} - -_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - SOKOL_LOG("event_cb()"); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - const int32_t win_w = ANativeWindow_getWidth(window); - const int32_t win_h = ANativeWindow_getHeight(window); - SOKOL_ASSERT(win_w >= 0 && win_h >= 0); - const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); - _sapp.window_width = win_w; - _sapp.window_height = win_h; - if (win_changed || force_update) { - if (!_sapp.desc.high_dpi) { - const int32_t buf_w = win_w / 2; - const int32_t buf_h = win_h / 2; - EGLint format; - EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); - SOKOL_ASSERT(egl_result == EGL_TRUE); - /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions - as the ANativeWindow size results in weird display artefacts, that's - why it's only called when the buffer geometry is different from - the window size - */ - int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); - SOKOL_ASSERT(result == 0); - } - } - - /* query surface size */ - EGLint fb_w, fb_h; - EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); - EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); - SOKOL_ASSERT(egl_result_w == EGL_TRUE); - SOKOL_ASSERT(egl_result_h == EGL_TRUE); - const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); - _sapp.framebuffer_width = fb_w; - _sapp.framebuffer_height = fb_h; - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; - if (win_changed || fb_changed || force_update) { - if (!_sapp.first_frame) { - SOKOL_LOG("SAPP_EVENTTYPE_RESIZED"); - _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_android_cleanup(void) { - SOKOL_LOG("Cleaning up"); - if (_sapp.android.surface != EGL_NO_SURFACE) { - /* egl context is bound, cleanup gracefully */ - if (_sapp.init_called && !_sapp.cleanup_called) { - SOKOL_LOG("cleanup_cb()"); - _sapp_call_cleanup(); - } - } - /* always try to cleanup by destroying egl context */ - _sapp_android_cleanup_egl(); -} - -_SOKOL_PRIVATE void _sapp_android_shutdown(void) { - /* try to cleanup while we still have a surface and can call cleanup_cb() */ - _sapp_android_cleanup(); - /* request exit */ - ANativeActivity_finish(_sapp.android.activity); -} - -_SOKOL_PRIVATE void _sapp_android_frame(void) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - _sapp_android_update_dimensions(_sapp.android.current.window, false); - _sapp_frame(); - eglSwapBuffers(_sapp.android.display, _sapp.android.surface); -} - -_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { - return false; - } - if (!_sapp_events_enabled()) { - return false; - } - if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; - if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; - - int32_t action_idx = AMotionEvent_getAction(e); - int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - SOKOL_LOG("Touch: down"); - case AMOTION_EVENT_ACTION_POINTER_DOWN: - SOKOL_LOG("Touch: ptr down"); - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case AMOTION_EVENT_ACTION_MOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case AMOTION_EVENT_ACTION_UP: - SOKOL_LOG("Touch: up"); - case AMOTION_EVENT_ACTION_POINTER_UP: - SOKOL_LOG("Touch: ptr up"); - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case AMOTION_EVENT_ACTION_CANCEL: - SOKOL_LOG("Touch: cancel"); - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - break; - } - if (type == SAPP_EVENTTYPE_INVALID) { - return false; - } - int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - _sapp_init_event(type); - _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); - if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { - _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; - } - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); - dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; - dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { - dst->changed = (i == idx); - } else { - dst->changed = true; - } - } - _sapp_call_event(&_sapp.event); - return true; -} - -_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { - switch (scancode) { - case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; - case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; - case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; - case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; - case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; - case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; - case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; - case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; - case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; - case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; - case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; - case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; - case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; - case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; - case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; - case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; - case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; - case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; - case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; - case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; - case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; - case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; - case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; - case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; - case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; - case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; - case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; - case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; - case AKEYCODE_F1: return SAPP_KEYCODE_F1; - case AKEYCODE_F2: return SAPP_KEYCODE_F2; - case AKEYCODE_F3: return SAPP_KEYCODE_F3; - case AKEYCODE_F4: return SAPP_KEYCODE_F4; - case AKEYCODE_F5: return SAPP_KEYCODE_F5; - case AKEYCODE_F6: return SAPP_KEYCODE_F6; - case AKEYCODE_F7: return SAPP_KEYCODE_F7; - case AKEYCODE_F8: return SAPP_KEYCODE_F8; - case AKEYCODE_F9: return SAPP_KEYCODE_F9; - case AKEYCODE_F10: return SAPP_KEYCODE_F10; - case AKEYCODE_F11: return SAPP_KEYCODE_F11; - case AKEYCODE_F12: return SAPP_KEYCODE_F12; - case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; - case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; - case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; - case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; - case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; - case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; - case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; - case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; - case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; - case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; - case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; - case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; - case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; - case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; - case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; - case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; - case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; - case AKEYCODE_A: return SAPP_KEYCODE_A; - case AKEYCODE_B: return SAPP_KEYCODE_B; - case AKEYCODE_C: return SAPP_KEYCODE_C; - case AKEYCODE_D: return SAPP_KEYCODE_D; - case AKEYCODE_E: return SAPP_KEYCODE_E; - case AKEYCODE_F: return SAPP_KEYCODE_F; - case AKEYCODE_G: return SAPP_KEYCODE_G; - case AKEYCODE_H: return SAPP_KEYCODE_H; - case AKEYCODE_I: return SAPP_KEYCODE_I; - case AKEYCODE_J: return SAPP_KEYCODE_J; - case AKEYCODE_K: return SAPP_KEYCODE_K; - case AKEYCODE_L: return SAPP_KEYCODE_L; - case AKEYCODE_M: return SAPP_KEYCODE_M; - case AKEYCODE_N: return SAPP_KEYCODE_N; - case AKEYCODE_O: return SAPP_KEYCODE_O; - case AKEYCODE_P: return SAPP_KEYCODE_P; - case AKEYCODE_Q: return SAPP_KEYCODE_Q; - case AKEYCODE_R: return SAPP_KEYCODE_R; - case AKEYCODE_S: return SAPP_KEYCODE_S; - case AKEYCODE_T: return SAPP_KEYCODE_T; - case AKEYCODE_U: return SAPP_KEYCODE_U; - case AKEYCODE_V: return SAPP_KEYCODE_V; - case AKEYCODE_W: return SAPP_KEYCODE_W; - case AKEYCODE_X: return SAPP_KEYCODE_X; - case AKEYCODE_Y: return SAPP_KEYCODE_Y; - case AKEYCODE_Z: return SAPP_KEYCODE_Z; - case AKEYCODE_1: return SAPP_KEYCODE_1; - case AKEYCODE_2: return SAPP_KEYCODE_2; - case AKEYCODE_3: return SAPP_KEYCODE_3; - case AKEYCODE_4: return SAPP_KEYCODE_4; - case AKEYCODE_5: return SAPP_KEYCODE_5; - case AKEYCODE_6: return SAPP_KEYCODE_6; - case AKEYCODE_7: return SAPP_KEYCODE_7; - case AKEYCODE_8: return SAPP_KEYCODE_8; - case AKEYCODE_9: return SAPP_KEYCODE_9; - case AKEYCODE_0: return SAPP_KEYCODE_0; - case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; - case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; - case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; - case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; - case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; - case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; - case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; - case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; - case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; - case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; - case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; - case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; - /* Android Buttons */ - case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; - default: return SAPP_KEYCODE_INVALID; - } - return SAPP_KEYCODE_INVALID; -} -_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { - uint32_t meta_state = AKeyEvent_getMetaState(e); - uint32_t mods = 0; - if (meta_state& AMETA_SHIFT_ON) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (meta_state& AMETA_CTRL_ON) { - mods |= SAPP_MODIFIER_CTRL; - } - if (meta_state& AMETA_ALT_ON) { - mods |= SAPP_MODIFIER_ALT; - } - if (meta_state& AMETA_META_ON) { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} -int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) -{ - ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); - // Attaches the current thread to the JVM. - JavaVM *javaVM = activity->vm; - JNIEnv *jniEnv = activity->env; - - jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); - if(result == JNI_ERR){ - return 0; - } - - jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); - int unicodeKey; - - if(metaState == 0){ - jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); - jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); - jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); - unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); - }else{ - jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); - jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); - jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); - unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); - } - - (*javaVM)->DetachCurrentThread(javaVM); - - return unicodeKey; -} -_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { - if (_sapp_events_enabled() ) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_android_mods(e); - _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} -_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { - return false; - } - if (!_sapp_events_enabled()) { - return false; - } - int32_t action = AKeyEvent_getAction(e); - // Don't process soft keyboard commands as they are not reliable through this interface. - if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; - // Don't relay key press events from joysticks or game pads as Sokol key down events - if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || - (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { - return false; - } - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - switch (action) { - - case AKEY_EVENT_ACTION_DOWN : - type = SAPP_EVENTTYPE_KEY_DOWN; - break; - case AKEY_EVENT_ACTION_UP: - type = SAPP_EVENTTYPE_KEY_UP; - break; - default: - break; - } - if (type == SAPP_EVENTTYPE_INVALID) { - return false; - } - bool repeat = AKeyEvent_getRepeatCount(e)>0; - _sapp_init_event(type); - _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); - _sapp.event.modifiers = _sapp_android_mods(e); - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - if(type==SAPP_EVENTTYPE_KEY_DOWN){ - _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); - } - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - return _sapp.event.key_code != SAPP_KEYCODE_INVALID; -} - -_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { - if ((events & ALOOPER_EVENT_INPUT) == 0) { - SOKOL_LOG("_sapp_android_input_cb() encountered unsupported event"); - return 1; - } - SOKOL_ASSERT(_sapp.android.current.input); - AInputEvent* event = NULL; - while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { - if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { - continue; - } - int32_t handled = 0; - if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { - handled = 1; - } - AInputQueue_finishEvent(_sapp.android.current.input, event, handled); - } - return 1; -} - -_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { - if ((events & ALOOPER_EVENT_INPUT) == 0) { - SOKOL_LOG("_sapp_android_main_cb() encountered unsupported event"); - return 1; - } - - _sapp_android_msg_t msg; - if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { - SOKOL_LOG("Could not write to read_from_main_fd"); - return 1; - } - - pthread_mutex_lock(&_sapp.android.pt.mutex); - switch (msg) { - case _SOKOL_ANDROID_MSG_CREATE: - { - SOKOL_LOG("MSG_CREATE"); - SOKOL_ASSERT(!_sapp.valid); - bool result = _sapp_android_init_egl(); - SOKOL_ASSERT(result); - _sapp.valid = true; - _sapp.android.has_created = true; - } - break; - case _SOKOL_ANDROID_MSG_RESUME: - SOKOL_LOG("MSG_RESUME"); - _sapp.android.has_resumed = true; - _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); - break; - case _SOKOL_ANDROID_MSG_PAUSE: - SOKOL_LOG("MSG_PAUSE"); - _sapp.android.has_resumed = false; - _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); - break; - case _SOKOL_ANDROID_MSG_FOCUS: - SOKOL_LOG("MSG_FOCUS"); - _sapp.android.has_focus = true; - break; - case _SOKOL_ANDROID_MSG_NO_FOCUS: - SOKOL_LOG("MSG_NO_FOCUS"); - _sapp.android.has_focus = false; - break; - case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: - SOKOL_LOG("MSG_SET_NATIVE_WINDOW"); - if (_sapp.android.current.window != _sapp.android.pending.window) { - if (_sapp.android.current.window != NULL) { - _sapp_android_cleanup_egl_surface(); - } - if (_sapp.android.pending.window != NULL) { - SOKOL_LOG("Creating egl surface ..."); - if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { - SOKOL_LOG("... ok!"); - _sapp_android_update_dimensions(_sapp.android.pending.window, true); - } else { - SOKOL_LOG("... failed!"); - _sapp_android_shutdown(); - } - } - } - _sapp.android.current.window = _sapp.android.pending.window; - break; - case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: - SOKOL_LOG("MSG_SET_INPUT_QUEUE"); - if (_sapp.android.current.input != _sapp.android.pending.input) { - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - if (_sapp.android.pending.input != NULL) { - AInputQueue_attachLooper( - _sapp.android.pending.input, - _sapp.android.looper, - ALOOPER_POLL_CALLBACK, - _sapp_android_input_cb, - NULL); /* data */ - } - } - _sapp.android.current.input = _sapp.android.pending.input; - break; - case _SOKOL_ANDROID_MSG_DESTROY: - SOKOL_LOG("MSG_DESTROY"); - _sapp_android_cleanup(); - _sapp.valid = false; - _sapp.android.is_thread_stopping = true; - break; - default: - SOKOL_LOG("Unknown msg type received"); - break; - } - pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ - pthread_mutex_unlock(&_sapp.android.pt.mutex); - return 1; -} - -_SOKOL_PRIVATE bool _sapp_android_should_update(void) { - bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; - bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; - return is_in_front && has_surface; -} - -_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { - SOKOL_ASSERT(_sapp.valid); - /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ - if (shown) { - SOKOL_LOG("Showing keyboard"); - ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); - } else { - SOKOL_LOG("Hiding keyboard"); - ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); - } -} - -_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { - _SOKOL_UNUSED(arg); - SOKOL_LOG("Loop thread started"); - - _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); - ALooper_addFd(_sapp.android.looper, - _sapp.android.pt.read_from_main_fd, - ALOOPER_POLL_CALLBACK, - ALOOPER_EVENT_INPUT, - _sapp_android_main_cb, - NULL); /* data */ - - /* signal start to main thread */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_started = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* main loop */ - while (!_sapp.android.is_thread_stopping) { - /* sokol frame */ - if (_sapp_android_should_update()) { - _sapp_android_frame(); - } - - /* process all events (or stop early if app is requested to quit) */ - bool process_events = true; - while (process_events && !_sapp.android.is_thread_stopping) { - bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); - process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; - } - /* handle quit-requested, either from window or from sapp_request_quit() */ - if (_sapp.quit_requested && !_sapp.quit_ordered) { - /* give user code a chance to intervene */ - _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if(_sapp.quit_ordered) _sapp_android_shutdown(); - } - - /* cleanup thread */ - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - - /* the following causes heap corruption on exit, why?? - ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); - ALooper_release(_sapp.android.looper);*/ - - /* signal "destroyed" */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_stopped = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - SOKOL_LOG("Loop thread done"); - return NULL; -} - -/* android main/ui thread */ -_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { - if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { - SOKOL_LOG("Could not write to write_from_main_fd"); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onStart()"); -} - -_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onResume()"); - _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); -} - -_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { - SOKOL_LOG("NativeActivity onSaveInstanceState()"); - *out_size = 0; - return NULL; -} - -_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { - SOKOL_LOG("NativeActivity onWindowFocusChanged()"); - if (has_focus) { - _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); - } else { - _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onPause()"); - _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); -} - -_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onStop()"); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.window = window; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); - while (_sapp.android.current.window != window) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { - SOKOL_LOG("NativeActivity onNativeWindowCreated()"); - _sapp_android_msg_set_native_window(window); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { - SOKOL_LOG("NativeActivity onNativeWindowDestroyed()"); - _sapp_android_msg_set_native_window(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.input = input; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); - while (_sapp.android.current.input != input) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { - SOKOL_LOG("NativeActivity onInputQueueCreated()"); - _sapp_android_msg_set_input_queue(queue); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { - SOKOL_LOG("NativeActivity onInputQueueDestroyed()"); - _sapp_android_msg_set_input_queue(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onConfigurationChanged()"); - /* see android:configChanges in manifest */ -} - -_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { - SOKOL_LOG("NativeActivity onLowMemory()"); -} - -_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { - /* - * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) - * on my device (Moto X 2nd gen) when the app is removed from the task view - * (TaskStackView: onTaskViewDismissed). - * - * However, if ANativeActivity_finish() is explicitly called from for example - * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? - */ - SOKOL_LOG("NativeActivity onDestroy()"); - - /* send destroy msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); - while (!_sapp.android.is_thread_stopped) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* clean up main thread */ - pthread_cond_destroy(&_sapp.android.pt.cond); - pthread_mutex_destroy(&_sapp.android.pt.mutex); - - close(_sapp.android.pt.read_from_main_fd); - close(_sapp.android.pt.write_from_main_fd); - - SOKOL_LOG("NativeActivity done"); - - /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ - exit(0); -} - -JNIEXPORT -void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { - SOKOL_LOG("NativeActivity onCreate()"); - - sapp_desc desc = sokol_main(0, NULL); - _sapp_init_state(&desc); - - /* start loop thread */ - _sapp.android.activity = activity; - - int pipe_fd[2]; - if (pipe(pipe_fd) != 0) { - SOKOL_LOG("Could not create thread pipe"); - return; - } - _sapp.android.pt.read_from_main_fd = pipe_fd[0]; - _sapp.android.pt.write_from_main_fd = pipe_fd[1]; - - pthread_mutex_init(&_sapp.android.pt.mutex, NULL); - pthread_cond_init(&_sapp.android.pt.cond, NULL); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); - pthread_attr_destroy(&attr); - - /* wait until main loop has started */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - while (!_sapp.android.is_thread_started) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* send create msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); - while (!_sapp.android.has_created) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* register for callbacks */ - activity->callbacks->onStart = _sapp_android_on_start; - activity->callbacks->onResume = _sapp_android_on_resume; - activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; - activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; - activity->callbacks->onPause = _sapp_android_on_pause; - activity->callbacks->onStop = _sapp_android_on_stop; - activity->callbacks->onDestroy = _sapp_android_on_destroy; - activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; - /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ - /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ - activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; - activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; - activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; - /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ - activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; - activity->callbacks->onLowMemory = _sapp_android_on_low_memory; - - SOKOL_LOG("NativeActivity successfully created"); - - /* NOT A BUG: do NOT call sapp_discard_state() */ -} - -#endif /* _SAPP_ANDROID */ - -/*== LINUX ==================================================================*/ -#if defined(_SAPP_LINUX) - -/* see GLFW's xkb_unicode.c */ -static const struct _sapp_x11_codepair { - uint16_t keysym; - uint16_t ucs; -} _sapp_x11_keysymtab[] = { - { 0x01a1, 0x0104 }, - { 0x01a2, 0x02d8 }, - { 0x01a3, 0x0141 }, - { 0x01a5, 0x013d }, - { 0x01a6, 0x015a }, - { 0x01a9, 0x0160 }, - { 0x01aa, 0x015e }, - { 0x01ab, 0x0164 }, - { 0x01ac, 0x0179 }, - { 0x01ae, 0x017d }, - { 0x01af, 0x017b }, - { 0x01b1, 0x0105 }, - { 0x01b2, 0x02db }, - { 0x01b3, 0x0142 }, - { 0x01b5, 0x013e }, - { 0x01b6, 0x015b }, - { 0x01b7, 0x02c7 }, - { 0x01b9, 0x0161 }, - { 0x01ba, 0x015f }, - { 0x01bb, 0x0165 }, - { 0x01bc, 0x017a }, - { 0x01bd, 0x02dd }, - { 0x01be, 0x017e }, - { 0x01bf, 0x017c }, - { 0x01c0, 0x0154 }, - { 0x01c3, 0x0102 }, - { 0x01c5, 0x0139 }, - { 0x01c6, 0x0106 }, - { 0x01c8, 0x010c }, - { 0x01ca, 0x0118 }, - { 0x01cc, 0x011a }, - { 0x01cf, 0x010e }, - { 0x01d0, 0x0110 }, - { 0x01d1, 0x0143 }, - { 0x01d2, 0x0147 }, - { 0x01d5, 0x0150 }, - { 0x01d8, 0x0158 }, - { 0x01d9, 0x016e }, - { 0x01db, 0x0170 }, - { 0x01de, 0x0162 }, - { 0x01e0, 0x0155 }, - { 0x01e3, 0x0103 }, - { 0x01e5, 0x013a }, - { 0x01e6, 0x0107 }, - { 0x01e8, 0x010d }, - { 0x01ea, 0x0119 }, - { 0x01ec, 0x011b }, - { 0x01ef, 0x010f }, - { 0x01f0, 0x0111 }, - { 0x01f1, 0x0144 }, - { 0x01f2, 0x0148 }, - { 0x01f5, 0x0151 }, - { 0x01f8, 0x0159 }, - { 0x01f9, 0x016f }, - { 0x01fb, 0x0171 }, - { 0x01fe, 0x0163 }, - { 0x01ff, 0x02d9 }, - { 0x02a1, 0x0126 }, - { 0x02a6, 0x0124 }, - { 0x02a9, 0x0130 }, - { 0x02ab, 0x011e }, - { 0x02ac, 0x0134 }, - { 0x02b1, 0x0127 }, - { 0x02b6, 0x0125 }, - { 0x02b9, 0x0131 }, - { 0x02bb, 0x011f }, - { 0x02bc, 0x0135 }, - { 0x02c5, 0x010a }, - { 0x02c6, 0x0108 }, - { 0x02d5, 0x0120 }, - { 0x02d8, 0x011c }, - { 0x02dd, 0x016c }, - { 0x02de, 0x015c }, - { 0x02e5, 0x010b }, - { 0x02e6, 0x0109 }, - { 0x02f5, 0x0121 }, - { 0x02f8, 0x011d }, - { 0x02fd, 0x016d }, - { 0x02fe, 0x015d }, - { 0x03a2, 0x0138 }, - { 0x03a3, 0x0156 }, - { 0x03a5, 0x0128 }, - { 0x03a6, 0x013b }, - { 0x03aa, 0x0112 }, - { 0x03ab, 0x0122 }, - { 0x03ac, 0x0166 }, - { 0x03b3, 0x0157 }, - { 0x03b5, 0x0129 }, - { 0x03b6, 0x013c }, - { 0x03ba, 0x0113 }, - { 0x03bb, 0x0123 }, - { 0x03bc, 0x0167 }, - { 0x03bd, 0x014a }, - { 0x03bf, 0x014b }, - { 0x03c0, 0x0100 }, - { 0x03c7, 0x012e }, - { 0x03cc, 0x0116 }, - { 0x03cf, 0x012a }, - { 0x03d1, 0x0145 }, - { 0x03d2, 0x014c }, - { 0x03d3, 0x0136 }, - { 0x03d9, 0x0172 }, - { 0x03dd, 0x0168 }, - { 0x03de, 0x016a }, - { 0x03e0, 0x0101 }, - { 0x03e7, 0x012f }, - { 0x03ec, 0x0117 }, - { 0x03ef, 0x012b }, - { 0x03f1, 0x0146 }, - { 0x03f2, 0x014d }, - { 0x03f3, 0x0137 }, - { 0x03f9, 0x0173 }, - { 0x03fd, 0x0169 }, - { 0x03fe, 0x016b }, - { 0x047e, 0x203e }, - { 0x04a1, 0x3002 }, - { 0x04a2, 0x300c }, - { 0x04a3, 0x300d }, - { 0x04a4, 0x3001 }, - { 0x04a5, 0x30fb }, - { 0x04a6, 0x30f2 }, - { 0x04a7, 0x30a1 }, - { 0x04a8, 0x30a3 }, - { 0x04a9, 0x30a5 }, - { 0x04aa, 0x30a7 }, - { 0x04ab, 0x30a9 }, - { 0x04ac, 0x30e3 }, - { 0x04ad, 0x30e5 }, - { 0x04ae, 0x30e7 }, - { 0x04af, 0x30c3 }, - { 0x04b0, 0x30fc }, - { 0x04b1, 0x30a2 }, - { 0x04b2, 0x30a4 }, - { 0x04b3, 0x30a6 }, - { 0x04b4, 0x30a8 }, - { 0x04b5, 0x30aa }, - { 0x04b6, 0x30ab }, - { 0x04b7, 0x30ad }, - { 0x04b8, 0x30af }, - { 0x04b9, 0x30b1 }, - { 0x04ba, 0x30b3 }, - { 0x04bb, 0x30b5 }, - { 0x04bc, 0x30b7 }, - { 0x04bd, 0x30b9 }, - { 0x04be, 0x30bb }, - { 0x04bf, 0x30bd }, - { 0x04c0, 0x30bf }, - { 0x04c1, 0x30c1 }, - { 0x04c2, 0x30c4 }, - { 0x04c3, 0x30c6 }, - { 0x04c4, 0x30c8 }, - { 0x04c5, 0x30ca }, - { 0x04c6, 0x30cb }, - { 0x04c7, 0x30cc }, - { 0x04c8, 0x30cd }, - { 0x04c9, 0x30ce }, - { 0x04ca, 0x30cf }, - { 0x04cb, 0x30d2 }, - { 0x04cc, 0x30d5 }, - { 0x04cd, 0x30d8 }, - { 0x04ce, 0x30db }, - { 0x04cf, 0x30de }, - { 0x04d0, 0x30df }, - { 0x04d1, 0x30e0 }, - { 0x04d2, 0x30e1 }, - { 0x04d3, 0x30e2 }, - { 0x04d4, 0x30e4 }, - { 0x04d5, 0x30e6 }, - { 0x04d6, 0x30e8 }, - { 0x04d7, 0x30e9 }, - { 0x04d8, 0x30ea }, - { 0x04d9, 0x30eb }, - { 0x04da, 0x30ec }, - { 0x04db, 0x30ed }, - { 0x04dc, 0x30ef }, - { 0x04dd, 0x30f3 }, - { 0x04de, 0x309b }, - { 0x04df, 0x309c }, - { 0x05ac, 0x060c }, - { 0x05bb, 0x061b }, - { 0x05bf, 0x061f }, - { 0x05c1, 0x0621 }, - { 0x05c2, 0x0622 }, - { 0x05c3, 0x0623 }, - { 0x05c4, 0x0624 }, - { 0x05c5, 0x0625 }, - { 0x05c6, 0x0626 }, - { 0x05c7, 0x0627 }, - { 0x05c8, 0x0628 }, - { 0x05c9, 0x0629 }, - { 0x05ca, 0x062a }, - { 0x05cb, 0x062b }, - { 0x05cc, 0x062c }, - { 0x05cd, 0x062d }, - { 0x05ce, 0x062e }, - { 0x05cf, 0x062f }, - { 0x05d0, 0x0630 }, - { 0x05d1, 0x0631 }, - { 0x05d2, 0x0632 }, - { 0x05d3, 0x0633 }, - { 0x05d4, 0x0634 }, - { 0x05d5, 0x0635 }, - { 0x05d6, 0x0636 }, - { 0x05d7, 0x0637 }, - { 0x05d8, 0x0638 }, - { 0x05d9, 0x0639 }, - { 0x05da, 0x063a }, - { 0x05e0, 0x0640 }, - { 0x05e1, 0x0641 }, - { 0x05e2, 0x0642 }, - { 0x05e3, 0x0643 }, - { 0x05e4, 0x0644 }, - { 0x05e5, 0x0645 }, - { 0x05e6, 0x0646 }, - { 0x05e7, 0x0647 }, - { 0x05e8, 0x0648 }, - { 0x05e9, 0x0649 }, - { 0x05ea, 0x064a }, - { 0x05eb, 0x064b }, - { 0x05ec, 0x064c }, - { 0x05ed, 0x064d }, - { 0x05ee, 0x064e }, - { 0x05ef, 0x064f }, - { 0x05f0, 0x0650 }, - { 0x05f1, 0x0651 }, - { 0x05f2, 0x0652 }, - { 0x06a1, 0x0452 }, - { 0x06a2, 0x0453 }, - { 0x06a3, 0x0451 }, - { 0x06a4, 0x0454 }, - { 0x06a5, 0x0455 }, - { 0x06a6, 0x0456 }, - { 0x06a7, 0x0457 }, - { 0x06a8, 0x0458 }, - { 0x06a9, 0x0459 }, - { 0x06aa, 0x045a }, - { 0x06ab, 0x045b }, - { 0x06ac, 0x045c }, - { 0x06ae, 0x045e }, - { 0x06af, 0x045f }, - { 0x06b0, 0x2116 }, - { 0x06b1, 0x0402 }, - { 0x06b2, 0x0403 }, - { 0x06b3, 0x0401 }, - { 0x06b4, 0x0404 }, - { 0x06b5, 0x0405 }, - { 0x06b6, 0x0406 }, - { 0x06b7, 0x0407 }, - { 0x06b8, 0x0408 }, - { 0x06b9, 0x0409 }, - { 0x06ba, 0x040a }, - { 0x06bb, 0x040b }, - { 0x06bc, 0x040c }, - { 0x06be, 0x040e }, - { 0x06bf, 0x040f }, - { 0x06c0, 0x044e }, - { 0x06c1, 0x0430 }, - { 0x06c2, 0x0431 }, - { 0x06c3, 0x0446 }, - { 0x06c4, 0x0434 }, - { 0x06c5, 0x0435 }, - { 0x06c6, 0x0444 }, - { 0x06c7, 0x0433 }, - { 0x06c8, 0x0445 }, - { 0x06c9, 0x0438 }, - { 0x06ca, 0x0439 }, - { 0x06cb, 0x043a }, - { 0x06cc, 0x043b }, - { 0x06cd, 0x043c }, - { 0x06ce, 0x043d }, - { 0x06cf, 0x043e }, - { 0x06d0, 0x043f }, - { 0x06d1, 0x044f }, - { 0x06d2, 0x0440 }, - { 0x06d3, 0x0441 }, - { 0x06d4, 0x0442 }, - { 0x06d5, 0x0443 }, - { 0x06d6, 0x0436 }, - { 0x06d7, 0x0432 }, - { 0x06d8, 0x044c }, - { 0x06d9, 0x044b }, - { 0x06da, 0x0437 }, - { 0x06db, 0x0448 }, - { 0x06dc, 0x044d }, - { 0x06dd, 0x0449 }, - { 0x06de, 0x0447 }, - { 0x06df, 0x044a }, - { 0x06e0, 0x042e }, - { 0x06e1, 0x0410 }, - { 0x06e2, 0x0411 }, - { 0x06e3, 0x0426 }, - { 0x06e4, 0x0414 }, - { 0x06e5, 0x0415 }, - { 0x06e6, 0x0424 }, - { 0x06e7, 0x0413 }, - { 0x06e8, 0x0425 }, - { 0x06e9, 0x0418 }, - { 0x06ea, 0x0419 }, - { 0x06eb, 0x041a }, - { 0x06ec, 0x041b }, - { 0x06ed, 0x041c }, - { 0x06ee, 0x041d }, - { 0x06ef, 0x041e }, - { 0x06f0, 0x041f }, - { 0x06f1, 0x042f }, - { 0x06f2, 0x0420 }, - { 0x06f3, 0x0421 }, - { 0x06f4, 0x0422 }, - { 0x06f5, 0x0423 }, - { 0x06f6, 0x0416 }, - { 0x06f7, 0x0412 }, - { 0x06f8, 0x042c }, - { 0x06f9, 0x042b }, - { 0x06fa, 0x0417 }, - { 0x06fb, 0x0428 }, - { 0x06fc, 0x042d }, - { 0x06fd, 0x0429 }, - { 0x06fe, 0x0427 }, - { 0x06ff, 0x042a }, - { 0x07a1, 0x0386 }, - { 0x07a2, 0x0388 }, - { 0x07a3, 0x0389 }, - { 0x07a4, 0x038a }, - { 0x07a5, 0x03aa }, - { 0x07a7, 0x038c }, - { 0x07a8, 0x038e }, - { 0x07a9, 0x03ab }, - { 0x07ab, 0x038f }, - { 0x07ae, 0x0385 }, - { 0x07af, 0x2015 }, - { 0x07b1, 0x03ac }, - { 0x07b2, 0x03ad }, - { 0x07b3, 0x03ae }, - { 0x07b4, 0x03af }, - { 0x07b5, 0x03ca }, - { 0x07b6, 0x0390 }, - { 0x07b7, 0x03cc }, - { 0x07b8, 0x03cd }, - { 0x07b9, 0x03cb }, - { 0x07ba, 0x03b0 }, - { 0x07bb, 0x03ce }, - { 0x07c1, 0x0391 }, - { 0x07c2, 0x0392 }, - { 0x07c3, 0x0393 }, - { 0x07c4, 0x0394 }, - { 0x07c5, 0x0395 }, - { 0x07c6, 0x0396 }, - { 0x07c7, 0x0397 }, - { 0x07c8, 0x0398 }, - { 0x07c9, 0x0399 }, - { 0x07ca, 0x039a }, - { 0x07cb, 0x039b }, - { 0x07cc, 0x039c }, - { 0x07cd, 0x039d }, - { 0x07ce, 0x039e }, - { 0x07cf, 0x039f }, - { 0x07d0, 0x03a0 }, - { 0x07d1, 0x03a1 }, - { 0x07d2, 0x03a3 }, - { 0x07d4, 0x03a4 }, - { 0x07d5, 0x03a5 }, - { 0x07d6, 0x03a6 }, - { 0x07d7, 0x03a7 }, - { 0x07d8, 0x03a8 }, - { 0x07d9, 0x03a9 }, - { 0x07e1, 0x03b1 }, - { 0x07e2, 0x03b2 }, - { 0x07e3, 0x03b3 }, - { 0x07e4, 0x03b4 }, - { 0x07e5, 0x03b5 }, - { 0x07e6, 0x03b6 }, - { 0x07e7, 0x03b7 }, - { 0x07e8, 0x03b8 }, - { 0x07e9, 0x03b9 }, - { 0x07ea, 0x03ba }, - { 0x07eb, 0x03bb }, - { 0x07ec, 0x03bc }, - { 0x07ed, 0x03bd }, - { 0x07ee, 0x03be }, - { 0x07ef, 0x03bf }, - { 0x07f0, 0x03c0 }, - { 0x07f1, 0x03c1 }, - { 0x07f2, 0x03c3 }, - { 0x07f3, 0x03c2 }, - { 0x07f4, 0x03c4 }, - { 0x07f5, 0x03c5 }, - { 0x07f6, 0x03c6 }, - { 0x07f7, 0x03c7 }, - { 0x07f8, 0x03c8 }, - { 0x07f9, 0x03c9 }, - { 0x08a1, 0x23b7 }, - { 0x08a2, 0x250c }, - { 0x08a3, 0x2500 }, - { 0x08a4, 0x2320 }, - { 0x08a5, 0x2321 }, - { 0x08a6, 0x2502 }, - { 0x08a7, 0x23a1 }, - { 0x08a8, 0x23a3 }, - { 0x08a9, 0x23a4 }, - { 0x08aa, 0x23a6 }, - { 0x08ab, 0x239b }, - { 0x08ac, 0x239d }, - { 0x08ad, 0x239e }, - { 0x08ae, 0x23a0 }, - { 0x08af, 0x23a8 }, - { 0x08b0, 0x23ac }, - { 0x08bc, 0x2264 }, - { 0x08bd, 0x2260 }, - { 0x08be, 0x2265 }, - { 0x08bf, 0x222b }, - { 0x08c0, 0x2234 }, - { 0x08c1, 0x221d }, - { 0x08c2, 0x221e }, - { 0x08c5, 0x2207 }, - { 0x08c8, 0x223c }, - { 0x08c9, 0x2243 }, - { 0x08cd, 0x21d4 }, - { 0x08ce, 0x21d2 }, - { 0x08cf, 0x2261 }, - { 0x08d6, 0x221a }, - { 0x08da, 0x2282 }, - { 0x08db, 0x2283 }, - { 0x08dc, 0x2229 }, - { 0x08dd, 0x222a }, - { 0x08de, 0x2227 }, - { 0x08df, 0x2228 }, - { 0x08ef, 0x2202 }, - { 0x08f6, 0x0192 }, - { 0x08fb, 0x2190 }, - { 0x08fc, 0x2191 }, - { 0x08fd, 0x2192 }, - { 0x08fe, 0x2193 }, - { 0x09e0, 0x25c6 }, - { 0x09e1, 0x2592 }, - { 0x09e2, 0x2409 }, - { 0x09e3, 0x240c }, - { 0x09e4, 0x240d }, - { 0x09e5, 0x240a }, - { 0x09e8, 0x2424 }, - { 0x09e9, 0x240b }, - { 0x09ea, 0x2518 }, - { 0x09eb, 0x2510 }, - { 0x09ec, 0x250c }, - { 0x09ed, 0x2514 }, - { 0x09ee, 0x253c }, - { 0x09ef, 0x23ba }, - { 0x09f0, 0x23bb }, - { 0x09f1, 0x2500 }, - { 0x09f2, 0x23bc }, - { 0x09f3, 0x23bd }, - { 0x09f4, 0x251c }, - { 0x09f5, 0x2524 }, - { 0x09f6, 0x2534 }, - { 0x09f7, 0x252c }, - { 0x09f8, 0x2502 }, - { 0x0aa1, 0x2003 }, - { 0x0aa2, 0x2002 }, - { 0x0aa3, 0x2004 }, - { 0x0aa4, 0x2005 }, - { 0x0aa5, 0x2007 }, - { 0x0aa6, 0x2008 }, - { 0x0aa7, 0x2009 }, - { 0x0aa8, 0x200a }, - { 0x0aa9, 0x2014 }, - { 0x0aaa, 0x2013 }, - { 0x0aae, 0x2026 }, - { 0x0aaf, 0x2025 }, - { 0x0ab0, 0x2153 }, - { 0x0ab1, 0x2154 }, - { 0x0ab2, 0x2155 }, - { 0x0ab3, 0x2156 }, - { 0x0ab4, 0x2157 }, - { 0x0ab5, 0x2158 }, - { 0x0ab6, 0x2159 }, - { 0x0ab7, 0x215a }, - { 0x0ab8, 0x2105 }, - { 0x0abb, 0x2012 }, - { 0x0abc, 0x2329 }, - { 0x0abe, 0x232a }, - { 0x0ac3, 0x215b }, - { 0x0ac4, 0x215c }, - { 0x0ac5, 0x215d }, - { 0x0ac6, 0x215e }, - { 0x0ac9, 0x2122 }, - { 0x0aca, 0x2613 }, - { 0x0acc, 0x25c1 }, - { 0x0acd, 0x25b7 }, - { 0x0ace, 0x25cb }, - { 0x0acf, 0x25af }, - { 0x0ad0, 0x2018 }, - { 0x0ad1, 0x2019 }, - { 0x0ad2, 0x201c }, - { 0x0ad3, 0x201d }, - { 0x0ad4, 0x211e }, - { 0x0ad6, 0x2032 }, - { 0x0ad7, 0x2033 }, - { 0x0ad9, 0x271d }, - { 0x0adb, 0x25ac }, - { 0x0adc, 0x25c0 }, - { 0x0add, 0x25b6 }, - { 0x0ade, 0x25cf }, - { 0x0adf, 0x25ae }, - { 0x0ae0, 0x25e6 }, - { 0x0ae1, 0x25ab }, - { 0x0ae2, 0x25ad }, - { 0x0ae3, 0x25b3 }, - { 0x0ae4, 0x25bd }, - { 0x0ae5, 0x2606 }, - { 0x0ae6, 0x2022 }, - { 0x0ae7, 0x25aa }, - { 0x0ae8, 0x25b2 }, - { 0x0ae9, 0x25bc }, - { 0x0aea, 0x261c }, - { 0x0aeb, 0x261e }, - { 0x0aec, 0x2663 }, - { 0x0aed, 0x2666 }, - { 0x0aee, 0x2665 }, - { 0x0af0, 0x2720 }, - { 0x0af1, 0x2020 }, - { 0x0af2, 0x2021 }, - { 0x0af3, 0x2713 }, - { 0x0af4, 0x2717 }, - { 0x0af5, 0x266f }, - { 0x0af6, 0x266d }, - { 0x0af7, 0x2642 }, - { 0x0af8, 0x2640 }, - { 0x0af9, 0x260e }, - { 0x0afa, 0x2315 }, - { 0x0afb, 0x2117 }, - { 0x0afc, 0x2038 }, - { 0x0afd, 0x201a }, - { 0x0afe, 0x201e }, - { 0x0ba3, 0x003c }, - { 0x0ba6, 0x003e }, - { 0x0ba8, 0x2228 }, - { 0x0ba9, 0x2227 }, - { 0x0bc0, 0x00af }, - { 0x0bc2, 0x22a5 }, - { 0x0bc3, 0x2229 }, - { 0x0bc4, 0x230a }, - { 0x0bc6, 0x005f }, - { 0x0bca, 0x2218 }, - { 0x0bcc, 0x2395 }, - { 0x0bce, 0x22a4 }, - { 0x0bcf, 0x25cb }, - { 0x0bd3, 0x2308 }, - { 0x0bd6, 0x222a }, - { 0x0bd8, 0x2283 }, - { 0x0bda, 0x2282 }, - { 0x0bdc, 0x22a2 }, - { 0x0bfc, 0x22a3 }, - { 0x0cdf, 0x2017 }, - { 0x0ce0, 0x05d0 }, - { 0x0ce1, 0x05d1 }, - { 0x0ce2, 0x05d2 }, - { 0x0ce3, 0x05d3 }, - { 0x0ce4, 0x05d4 }, - { 0x0ce5, 0x05d5 }, - { 0x0ce6, 0x05d6 }, - { 0x0ce7, 0x05d7 }, - { 0x0ce8, 0x05d8 }, - { 0x0ce9, 0x05d9 }, - { 0x0cea, 0x05da }, - { 0x0ceb, 0x05db }, - { 0x0cec, 0x05dc }, - { 0x0ced, 0x05dd }, - { 0x0cee, 0x05de }, - { 0x0cef, 0x05df }, - { 0x0cf0, 0x05e0 }, - { 0x0cf1, 0x05e1 }, - { 0x0cf2, 0x05e2 }, - { 0x0cf3, 0x05e3 }, - { 0x0cf4, 0x05e4 }, - { 0x0cf5, 0x05e5 }, - { 0x0cf6, 0x05e6 }, - { 0x0cf7, 0x05e7 }, - { 0x0cf8, 0x05e8 }, - { 0x0cf9, 0x05e9 }, - { 0x0cfa, 0x05ea }, - { 0x0da1, 0x0e01 }, - { 0x0da2, 0x0e02 }, - { 0x0da3, 0x0e03 }, - { 0x0da4, 0x0e04 }, - { 0x0da5, 0x0e05 }, - { 0x0da6, 0x0e06 }, - { 0x0da7, 0x0e07 }, - { 0x0da8, 0x0e08 }, - { 0x0da9, 0x0e09 }, - { 0x0daa, 0x0e0a }, - { 0x0dab, 0x0e0b }, - { 0x0dac, 0x0e0c }, - { 0x0dad, 0x0e0d }, - { 0x0dae, 0x0e0e }, - { 0x0daf, 0x0e0f }, - { 0x0db0, 0x0e10 }, - { 0x0db1, 0x0e11 }, - { 0x0db2, 0x0e12 }, - { 0x0db3, 0x0e13 }, - { 0x0db4, 0x0e14 }, - { 0x0db5, 0x0e15 }, - { 0x0db6, 0x0e16 }, - { 0x0db7, 0x0e17 }, - { 0x0db8, 0x0e18 }, - { 0x0db9, 0x0e19 }, - { 0x0dba, 0x0e1a }, - { 0x0dbb, 0x0e1b }, - { 0x0dbc, 0x0e1c }, - { 0x0dbd, 0x0e1d }, - { 0x0dbe, 0x0e1e }, - { 0x0dbf, 0x0e1f }, - { 0x0dc0, 0x0e20 }, - { 0x0dc1, 0x0e21 }, - { 0x0dc2, 0x0e22 }, - { 0x0dc3, 0x0e23 }, - { 0x0dc4, 0x0e24 }, - { 0x0dc5, 0x0e25 }, - { 0x0dc6, 0x0e26 }, - { 0x0dc7, 0x0e27 }, - { 0x0dc8, 0x0e28 }, - { 0x0dc9, 0x0e29 }, - { 0x0dca, 0x0e2a }, - { 0x0dcb, 0x0e2b }, - { 0x0dcc, 0x0e2c }, - { 0x0dcd, 0x0e2d }, - { 0x0dce, 0x0e2e }, - { 0x0dcf, 0x0e2f }, - { 0x0dd0, 0x0e30 }, - { 0x0dd1, 0x0e31 }, - { 0x0dd2, 0x0e32 }, - { 0x0dd3, 0x0e33 }, - { 0x0dd4, 0x0e34 }, - { 0x0dd5, 0x0e35 }, - { 0x0dd6, 0x0e36 }, - { 0x0dd7, 0x0e37 }, - { 0x0dd8, 0x0e38 }, - { 0x0dd9, 0x0e39 }, - { 0x0dda, 0x0e3a }, - { 0x0ddf, 0x0e3f }, - { 0x0de0, 0x0e40 }, - { 0x0de1, 0x0e41 }, - { 0x0de2, 0x0e42 }, - { 0x0de3, 0x0e43 }, - { 0x0de4, 0x0e44 }, - { 0x0de5, 0x0e45 }, - { 0x0de6, 0x0e46 }, - { 0x0de7, 0x0e47 }, - { 0x0de8, 0x0e48 }, - { 0x0de9, 0x0e49 }, - { 0x0dea, 0x0e4a }, - { 0x0deb, 0x0e4b }, - { 0x0dec, 0x0e4c }, - { 0x0ded, 0x0e4d }, - { 0x0df0, 0x0e50 }, - { 0x0df1, 0x0e51 }, - { 0x0df2, 0x0e52 }, - { 0x0df3, 0x0e53 }, - { 0x0df4, 0x0e54 }, - { 0x0df5, 0x0e55 }, - { 0x0df6, 0x0e56 }, - { 0x0df7, 0x0e57 }, - { 0x0df8, 0x0e58 }, - { 0x0df9, 0x0e59 }, - { 0x0ea1, 0x3131 }, - { 0x0ea2, 0x3132 }, - { 0x0ea3, 0x3133 }, - { 0x0ea4, 0x3134 }, - { 0x0ea5, 0x3135 }, - { 0x0ea6, 0x3136 }, - { 0x0ea7, 0x3137 }, - { 0x0ea8, 0x3138 }, - { 0x0ea9, 0x3139 }, - { 0x0eaa, 0x313a }, - { 0x0eab, 0x313b }, - { 0x0eac, 0x313c }, - { 0x0ead, 0x313d }, - { 0x0eae, 0x313e }, - { 0x0eaf, 0x313f }, - { 0x0eb0, 0x3140 }, - { 0x0eb1, 0x3141 }, - { 0x0eb2, 0x3142 }, - { 0x0eb3, 0x3143 }, - { 0x0eb4, 0x3144 }, - { 0x0eb5, 0x3145 }, - { 0x0eb6, 0x3146 }, - { 0x0eb7, 0x3147 }, - { 0x0eb8, 0x3148 }, - { 0x0eb9, 0x3149 }, - { 0x0eba, 0x314a }, - { 0x0ebb, 0x314b }, - { 0x0ebc, 0x314c }, - { 0x0ebd, 0x314d }, - { 0x0ebe, 0x314e }, - { 0x0ebf, 0x314f }, - { 0x0ec0, 0x3150 }, - { 0x0ec1, 0x3151 }, - { 0x0ec2, 0x3152 }, - { 0x0ec3, 0x3153 }, - { 0x0ec4, 0x3154 }, - { 0x0ec5, 0x3155 }, - { 0x0ec6, 0x3156 }, - { 0x0ec7, 0x3157 }, - { 0x0ec8, 0x3158 }, - { 0x0ec9, 0x3159 }, - { 0x0eca, 0x315a }, - { 0x0ecb, 0x315b }, - { 0x0ecc, 0x315c }, - { 0x0ecd, 0x315d }, - { 0x0ece, 0x315e }, - { 0x0ecf, 0x315f }, - { 0x0ed0, 0x3160 }, - { 0x0ed1, 0x3161 }, - { 0x0ed2, 0x3162 }, - { 0x0ed3, 0x3163 }, - { 0x0ed4, 0x11a8 }, - { 0x0ed5, 0x11a9 }, - { 0x0ed6, 0x11aa }, - { 0x0ed7, 0x11ab }, - { 0x0ed8, 0x11ac }, - { 0x0ed9, 0x11ad }, - { 0x0eda, 0x11ae }, - { 0x0edb, 0x11af }, - { 0x0edc, 0x11b0 }, - { 0x0edd, 0x11b1 }, - { 0x0ede, 0x11b2 }, - { 0x0edf, 0x11b3 }, - { 0x0ee0, 0x11b4 }, - { 0x0ee1, 0x11b5 }, - { 0x0ee2, 0x11b6 }, - { 0x0ee3, 0x11b7 }, - { 0x0ee4, 0x11b8 }, - { 0x0ee5, 0x11b9 }, - { 0x0ee6, 0x11ba }, - { 0x0ee7, 0x11bb }, - { 0x0ee8, 0x11bc }, - { 0x0ee9, 0x11bd }, - { 0x0eea, 0x11be }, - { 0x0eeb, 0x11bf }, - { 0x0eec, 0x11c0 }, - { 0x0eed, 0x11c1 }, - { 0x0eee, 0x11c2 }, - { 0x0eef, 0x316d }, - { 0x0ef0, 0x3171 }, - { 0x0ef1, 0x3178 }, - { 0x0ef2, 0x317f }, - { 0x0ef3, 0x3181 }, - { 0x0ef4, 0x3184 }, - { 0x0ef5, 0x3186 }, - { 0x0ef6, 0x318d }, - { 0x0ef7, 0x318e }, - { 0x0ef8, 0x11eb }, - { 0x0ef9, 0x11f0 }, - { 0x0efa, 0x11f9 }, - { 0x0eff, 0x20a9 }, - { 0x13a4, 0x20ac }, - { 0x13bc, 0x0152 }, - { 0x13bd, 0x0153 }, - { 0x13be, 0x0178 }, - { 0x20ac, 0x20ac }, - { 0xfe50, '`' }, - { 0xfe51, 0x00b4 }, - { 0xfe52, '^' }, - { 0xfe53, '~' }, - { 0xfe54, 0x00af }, - { 0xfe55, 0x02d8 }, - { 0xfe56, 0x02d9 }, - { 0xfe57, 0x00a8 }, - { 0xfe58, 0x02da }, - { 0xfe59, 0x02dd }, - { 0xfe5a, 0x02c7 }, - { 0xfe5b, 0x00b8 }, - { 0xfe5c, 0x02db }, - { 0xfe5d, 0x037a }, - { 0xfe5e, 0x309b }, - { 0xfe5f, 0x309c }, - { 0xfe63, '/' }, - { 0xfe64, 0x02bc }, - { 0xfe65, 0x02bd }, - { 0xfe66, 0x02f5 }, - { 0xfe67, 0x02f3 }, - { 0xfe68, 0x02cd }, - { 0xfe69, 0xa788 }, - { 0xfe6a, 0x02f7 }, - { 0xfe6e, ',' }, - { 0xfe6f, 0x00a4 }, - { 0xfe80, 'a' }, /* XK_dead_a */ - { 0xfe81, 'A' }, /* XK_dead_A */ - { 0xfe82, 'e' }, /* XK_dead_e */ - { 0xfe83, 'E' }, /* XK_dead_E */ - { 0xfe84, 'i' }, /* XK_dead_i */ - { 0xfe85, 'I' }, /* XK_dead_I */ - { 0xfe86, 'o' }, /* XK_dead_o */ - { 0xfe87, 'O' }, /* XK_dead_O */ - { 0xfe88, 'u' }, /* XK_dead_u */ - { 0xfe89, 'U' }, /* XK_dead_U */ - { 0xfe8a, 0x0259 }, - { 0xfe8b, 0x018f }, - { 0xfe8c, 0x00b5 }, - { 0xfe90, '_' }, - { 0xfe91, 0x02c8 }, - { 0xfe92, 0x02cc }, - { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, - { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, - { 0xffab /*XKB_KEY_KP_Add*/, '+' }, - { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, - { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, - { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, - { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, - { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } -}; - -_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { - _SOKOL_UNUSED(display); - _sapp.x11.error_code = event->error_code; - return 0; -} - -_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { - _sapp.x11.error_code = Success; - XSetErrorHandler(_sapp_x11_error_handler); -} - -_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { - XSync(_sapp.x11.display, False); - XSetErrorHandler(NULL); -} - -_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { - _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); - _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); - _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); - _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); - _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); - _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); - _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); - _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); - if (_sapp.drop.enabled) { - _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); - _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); - _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); - _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); - _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); - _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); - _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); - _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); - _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); - _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); - } - - /* check Xi extension for raw mouse input */ - if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { - _sapp.x11.xi.major = 2; - _sapp.x11.xi.minor = 0; - if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { - _sapp.x11.xi.available = true; - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { - /* from GLFW: - - NOTE: Default to the display-wide DPI as we don't currently have a policy - for which monitor a window is considered to be on - - _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * - 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); - - NOTE: Basing the scale on Xft.dpi where available should provide the most - consistent user experience (matches Qt, Gtk, etc), although not - always the most accurate one - */ - char* rms = XResourceManagerString(_sapp.x11.display); - if (rms) { - XrmDatabase db = XrmGetStringDatabase(rms); - if (db) { - XrmValue value; - char* type = NULL; - if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { - if (type && strcmp(type, "String") == 0) { - _sapp.x11.dpi = atof(value.addr); - } - } - XrmDestroyDatabase(db); - } - } -} - -_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { - if (extensions) { - return _sapp_glx_has_ext(ext, extensions); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) -{ - if (_sapp.glx.GetProcAddress) { - return (void*) _sapp.glx.GetProcAddress((const GLubyte*) procname); - } - else if (_sapp.glx.GetProcAddressARB) { - return (void*) _sapp.glx.GetProcAddressARB((const GLubyte*) procname); - } - else { - return dlsym(_sapp.glx.libgl, procname); - } -} - -_SOKOL_PRIVATE void _sapp_glx_init() { - const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; - for (int i = 0; sonames[i]; i++) { - _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); - if (_sapp.glx.libgl) { - break; - } - } - if (!_sapp.glx.libgl) { - _sapp_fail("GLX: failed to load libGL"); - } - _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); - _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); - _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); - _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); - _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); - _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); - _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); - _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); - _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); - _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); - _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); - _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); - _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); - _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); - if (!_sapp.glx.GetFBConfigs || - !_sapp.glx.GetFBConfigAttrib || - !_sapp.glx.GetClientString || - !_sapp.glx.QueryExtension || - !_sapp.glx.QueryVersion || - !_sapp.glx.DestroyContext || - !_sapp.glx.MakeCurrent || - !_sapp.glx.SwapBuffers || - !_sapp.glx.QueryExtensionsString || - !_sapp.glx.CreateWindow || - !_sapp.glx.DestroyWindow || - !_sapp.glx.GetProcAddress || - !_sapp.glx.GetProcAddressARB || - !_sapp.glx.GetVisualFromFBConfig) - { - _sapp_fail("GLX: failed to load required entry points"); - } - - if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { - _sapp_fail("GLX: GLX extension not found"); - } - if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { - _sapp_fail("GLX: Failed to query GLX version"); - } - if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { - _sapp_fail("GLX: GLX version 1.3 is required"); - } - const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); - if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { - _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); - _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; - } - if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { - _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); - _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; - } - _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); - if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { - _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); - _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; - } - _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); -} - -_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { - int value; - _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); - return value; -} - -_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { - GLXFBConfig* native_configs; - _sapp_gl_fbconfig* usable_configs; - const _sapp_gl_fbconfig* closest; - int i, native_count, usable_count; - const char* vendor; - bool trust_window_bit = true; - - /* HACK: This is a (hopefully temporary) workaround for Chromium - (VirtualBox GL) not setting the window bit on any GLXFBConfigs - */ - vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); - if (vendor && strcmp(vendor, "Chromium") == 0) { - trust_window_bit = false; - } - - native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); - if (!native_configs || !native_count) { - _sapp_fail("GLX: No GLXFBConfigs returned"); - } - - usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); - usable_count = 0; - for (i = 0; i < native_count; i++) { - const GLXFBConfig n = native_configs[i]; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - - /* Only consider RGBA GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { - continue; - } - /* Only consider window GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { - if (trust_window_bit) { - continue; - } - } - u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); - u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); - u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); - u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); - u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); - u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); - if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { - u->doublebuffer = true; - } - if (_sapp.glx.ARB_multisample) { - u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); - } - u->handle = (uintptr_t) n; - usable_count++; - } - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - GLXFBConfig result = 0; - if (closest) { - result = (GLXFBConfig) closest->handle; - } - XFree(native_configs); - SOKOL_FREE(usable_configs); - return result; -} - -_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native) { - _sapp_fail("GLX: Failed to find a suitable GLXFBConfig"); - } - XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); - if (!result) { - _sapp_fail("GLX: Failed to retrieve Visual for GLXFBConfig"); - } - *visual = result->visual; - *depth = result->depth; - XFree(result); -} - -_SOKOL_PRIVATE void _sapp_glx_create_context(void) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native){ - _sapp_fail("GLX: Failed to find a suitable GLXFBConfig (2)"); - } - if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { - _sapp_fail("GLX: ARB_create_context and ARB_create_context_profile required"); - } - _sapp_x11_grab_error_handler(); - const int attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, 3, - GLX_CONTEXT_MINOR_VERSION_ARB, 3, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - 0, 0 - }; - _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); - if (!_sapp.glx.ctx) { - _sapp_fail("GLX: failed to create GL context"); - } - _sapp_x11_release_error_handler(); - _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); - if (!_sapp.glx.window) { - _sapp_fail("GLX: failed to create window"); - } -} - -_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { - if (_sapp.glx.window) { - _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); - _sapp.glx.window = 0; - } - if (_sapp.glx.ctx) { - _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); - _sapp.glx.ctx = 0; - } -} - -_SOKOL_PRIVATE void _sapp_glx_make_current(void) { - _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); -} - -_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { - _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); -} - -_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { - _sapp_glx_make_current(); - if (_sapp.glx.EXT_swap_control) { - _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); - } - else if (_sapp.glx.MESA_swap_control) { - _sapp.glx.SwapIntervalMESA(interval); - } -} - -_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { - XEvent event; - memset(&event, 0, sizeof(event)); - - event.type = ClientMessage; - event.xclient.window = _sapp.x11.window; - event.xclient.format = 32; - event.xclient.message_type = type; - event.xclient.data.l[0] = a; - event.xclient.data.l[1] = b; - event.xclient.data.l[2] = c; - event.xclient.data.l[3] = d; - event.xclient.data.l[4] = e; - - XSendEvent(_sapp.x11.display, _sapp.x11.root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &event); -} - -_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { - XWindowAttributes attribs; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); - _sapp.window_width = attribs.width; - _sapp.window_height = attribs.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; -} - -_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { - /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ - if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { - if (enable) { - const int _NET_WM_STATE_ADD = 1; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_ADD, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - else { - const int _NET_WM_STATE_REMOVE = 0; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_REMOVE, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { - SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); - const int w = 16; - const int h = 16; - XcursorImage* img = XcursorImageCreate(w, h); - SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); - img->xhot = 0; - img->yhot = 0; - const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); - memset(img->pixels, 0, num_bytes); - _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); - XcursorImageDestroy(img); -} - -_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { - _sapp.fullscreen = !_sapp.fullscreen; - _sapp_x11_set_fullscreen(_sapp.fullscreen); - _sapp_x11_query_window_size(); -} - -_SOKOL_PRIVATE void _sapp_x11_show_mouse(bool show) { - if (show) { - XUndefineCursor(_sapp.x11.display, _sapp.x11.window); - } - else { - XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); - } -} - -_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - if (_sapp.mouse.locked) { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISetMask(mask, XI_RawMotion); - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XGrabPointer(_sapp.x11.display, // display - _sapp.x11.window, // grab_window - True, // owner_events - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask - GrabModeAsync, // pointer_mode - GrabModeAsync, // keyboard_mode - _sapp.x11.window, // confine_to - _sapp.x11.hidden_cursor, // cursor - CurrentTime); // time - } - else { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[] = { 0 }; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); - XUngrabPointer(_sapp.x11.display, CurrentTime); - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { - Xutf8SetWMProperties(_sapp.x11.display, - _sapp.x11.window, - _sapp.window_title, _sapp.window_title, - NULL, 0, NULL, NULL, NULL); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - 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; - memset(&wa, 0, sizeof(wa)); - const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; - wa.colormap = _sapp.x11.colormap; - wa.border_pixel = 0; - wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | - PointerMotionMask | ButtonPressMask | ButtonReleaseMask | - ExposureMask | FocusChangeMask | VisibilityChangeMask | - EnterWindowMask | LeaveWindowMask | PropertyChangeMask; - _sapp_x11_grab_error_handler(); - _sapp.x11.window = XCreateWindow(_sapp.x11.display, - _sapp.x11.root, - 0, 0, - (uint32_t)_sapp.window_width, - (uint32_t)_sapp.window_height, - 0, /* border width */ - depth, /* color depth */ - InputOutput, - visual, - wamask, - &wa); - _sapp_x11_release_error_handler(); - if (!_sapp.x11.window) { - _sapp_fail("X11: Failed to create window"); - } - Atom protocols[] = { - _sapp.x11.WM_DELETE_WINDOW - }; - XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); - - XSizeHints* hints = XAllocSizeHints(); - hints->flags |= PWinGravity; - hints->win_gravity = StaticGravity; - XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); - XFree(hints); - - /* announce support for drag'n'drop */ - if (_sapp.drop.enabled) { - const Atom version = _SAPP_X11_XDND_VERSION; - XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); - } - - _sapp_x11_update_window_title(); -} - -_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { - if (_sapp.x11.window) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XDestroyWindow(_sapp.x11.display, _sapp.x11.window); - _sapp.x11.window = 0; - } - if (_sapp.x11.colormap) { - XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); - _sapp.x11.colormap = 0; - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { - XWindowAttributes wa; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); - return wa.map_state == IsViewable; -} - -_SOKOL_PRIVATE void _sapp_x11_show_window(void) { - if (!_sapp_x11_window_visible()) { - XMapWindow(_sapp.x11.display, _sapp.x11.window); - XRaiseWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); - } -} - -_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { - Atom actualType; - int actualFormat; - unsigned long itemCount, bytesAfter; - XGetWindowProperty(_sapp.x11.display, - window, - property, - 0, - LONG_MAX, - False, - type, - &actualType, - &actualFormat, - &itemCount, - &bytesAfter, - value); - return itemCount; -} - -_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { - int result = WithdrawnState; - struct { - CARD32 state; - Window icon; - } *state = NULL; - - if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { - result = (int)state->state; - } - if (state) { - XFree(state); - } - return result; -} - -_SOKOL_PRIVATE uint32_t _sapp_x11_mod(uint32_t x11_mods) { - uint32_t mods = 0; - if (x11_mods & ShiftMask) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (x11_mods & ControlMask) { - mods |= SAPP_MODIFIER_CTRL; - } - if (x11_mods & Mod1Mask) { - mods |= SAPP_MODIFIER_ALT; - } - if (x11_mods & Mod4Mask) { - mods |= SAPP_MODIFIER_SUPER; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { - switch (event->xbutton.button) { - case Button1: return SAPP_MOUSEBUTTON_LEFT; - case Button2: return SAPP_MOUSEBUTTON_MIDDLE; - case Button3: return SAPP_MOUSEBUTTON_RIGHT; - default: return SAPP_MOUSEBUTTON_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = mods; - _sapp.event.scroll_x = x; - _sapp.event.scroll_y = y; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = chr; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { - int dummy; - KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); - SOKOL_ASSERT(keysyms); - KeySym keysym = keysyms[0]; - XFree(keysyms); - switch (keysym) { - case XK_Escape: return SAPP_KEYCODE_ESCAPE; - case XK_Tab: return SAPP_KEYCODE_TAB; - case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; - case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; - case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; - case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; - case XK_Meta_L: - case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; - case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ - case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ - case XK_Meta_R: - case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; - case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; - case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; - case XK_Menu: return SAPP_KEYCODE_MENU; - case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; - case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; - case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; - case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; - case XK_Pause: return SAPP_KEYCODE_PAUSE; - case XK_Delete: return SAPP_KEYCODE_DELETE; - case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; - case XK_Return: return SAPP_KEYCODE_ENTER; - case XK_Home: return SAPP_KEYCODE_HOME; - case XK_End: return SAPP_KEYCODE_END; - case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; - case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; - case XK_Insert: return SAPP_KEYCODE_INSERT; - case XK_Left: return SAPP_KEYCODE_LEFT; - case XK_Right: return SAPP_KEYCODE_RIGHT; - case XK_Down: return SAPP_KEYCODE_DOWN; - case XK_Up: return SAPP_KEYCODE_UP; - case XK_F1: return SAPP_KEYCODE_F1; - case XK_F2: return SAPP_KEYCODE_F2; - case XK_F3: return SAPP_KEYCODE_F3; - case XK_F4: return SAPP_KEYCODE_F4; - case XK_F5: return SAPP_KEYCODE_F5; - case XK_F6: return SAPP_KEYCODE_F6; - case XK_F7: return SAPP_KEYCODE_F7; - case XK_F8: return SAPP_KEYCODE_F8; - case XK_F9: return SAPP_KEYCODE_F9; - case XK_F10: return SAPP_KEYCODE_F10; - case XK_F11: return SAPP_KEYCODE_F11; - case XK_F12: return SAPP_KEYCODE_F12; - case XK_F13: return SAPP_KEYCODE_F13; - case XK_F14: return SAPP_KEYCODE_F14; - case XK_F15: return SAPP_KEYCODE_F15; - case XK_F16: return SAPP_KEYCODE_F16; - case XK_F17: return SAPP_KEYCODE_F17; - case XK_F18: return SAPP_KEYCODE_F18; - case XK_F19: return SAPP_KEYCODE_F19; - case XK_F20: return SAPP_KEYCODE_F20; - case XK_F21: return SAPP_KEYCODE_F21; - case XK_F22: return SAPP_KEYCODE_F22; - case XK_F23: return SAPP_KEYCODE_F23; - case XK_F24: return SAPP_KEYCODE_F24; - case XK_F25: return SAPP_KEYCODE_F25; - - case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; - case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; - case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; - case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; - - case XK_KP_Insert: return SAPP_KEYCODE_KP_0; - case XK_KP_End: return SAPP_KEYCODE_KP_1; - case XK_KP_Down: return SAPP_KEYCODE_KP_2; - case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; - case XK_KP_Left: return SAPP_KEYCODE_KP_4; - case XK_KP_Begin: return SAPP_KEYCODE_KP_5; - case XK_KP_Right: return SAPP_KEYCODE_KP_6; - case XK_KP_Home: return SAPP_KEYCODE_KP_7; - case XK_KP_Up: return SAPP_KEYCODE_KP_8; - case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; - case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; - case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; - case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; - - case XK_a: return SAPP_KEYCODE_A; - case XK_b: return SAPP_KEYCODE_B; - case XK_c: return SAPP_KEYCODE_C; - case XK_d: return SAPP_KEYCODE_D; - case XK_e: return SAPP_KEYCODE_E; - case XK_f: return SAPP_KEYCODE_F; - case XK_g: return SAPP_KEYCODE_G; - case XK_h: return SAPP_KEYCODE_H; - case XK_i: return SAPP_KEYCODE_I; - case XK_j: return SAPP_KEYCODE_J; - case XK_k: return SAPP_KEYCODE_K; - case XK_l: return SAPP_KEYCODE_L; - case XK_m: return SAPP_KEYCODE_M; - case XK_n: return SAPP_KEYCODE_N; - case XK_o: return SAPP_KEYCODE_O; - case XK_p: return SAPP_KEYCODE_P; - case XK_q: return SAPP_KEYCODE_Q; - case XK_r: return SAPP_KEYCODE_R; - case XK_s: return SAPP_KEYCODE_S; - case XK_t: return SAPP_KEYCODE_T; - case XK_u: return SAPP_KEYCODE_U; - case XK_v: return SAPP_KEYCODE_V; - case XK_w: return SAPP_KEYCODE_W; - case XK_x: return SAPP_KEYCODE_X; - case XK_y: return SAPP_KEYCODE_Y; - case XK_z: return SAPP_KEYCODE_Z; - case XK_1: return SAPP_KEYCODE_1; - case XK_2: return SAPP_KEYCODE_2; - case XK_3: return SAPP_KEYCODE_3; - case XK_4: return SAPP_KEYCODE_4; - case XK_5: return SAPP_KEYCODE_5; - case XK_6: return SAPP_KEYCODE_6; - case XK_7: return SAPP_KEYCODE_7; - case XK_8: return SAPP_KEYCODE_8; - case XK_9: return SAPP_KEYCODE_9; - case XK_0: return SAPP_KEYCODE_0; - case XK_space: return SAPP_KEYCODE_SPACE; - case XK_minus: return SAPP_KEYCODE_MINUS; - case XK_equal: return SAPP_KEYCODE_EQUAL; - case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; - case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; - case XK_backslash: return SAPP_KEYCODE_BACKSLASH; - case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; - case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; - case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; - case XK_comma: return SAPP_KEYCODE_COMMA; - case XK_period: return SAPP_KEYCODE_PERIOD; - case XK_slash: return SAPP_KEYCODE_SLASH; - case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ - default: return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { - int min = 0; - int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; - int mid; - - /* First check for Latin-1 characters (1:1 mapping) */ - if ((keysym >= 0x0020 && keysym <= 0x007e) || - (keysym >= 0x00a0 && keysym <= 0x00ff)) - { - return keysym; - } - - /* Also check for directly encoded 24-bit UCS characters */ - if ((keysym & 0xff000000) == 0x01000000) { - return keysym & 0x00ffffff; - } - - /* Binary search in table */ - while (max >= min) { - mid = (min + max) / 2; - if (_sapp_x11_keysymtab[mid].keysym < keysym) { - min = mid + 1; - } - else if (_sapp_x11_keysymtab[mid].keysym > keysym) { - max = mid - 1; - } - else { - return _sapp_x11_keysymtab[mid].ucs; - } - } - - /* No matching Unicode value found */ - return -1; -} - -_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { - SOKOL_ASSERT(src); - SOKOL_ASSERT(_sapp.drop.buffer); - - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - - /* - src is (potentially percent-encoded) string made of one or multiple paths - separated by \r\n, each path starting with 'file://' - */ - bool err = false; - int src_count = 0; - char src_chr = 0; - char* dst_ptr = _sapp.drop.buffer; - const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 - while (0 != (src_chr = *src++)) { - src_count++; - char dst_chr = 0; - /* check leading 'file://' */ - if (src_count <= 7) { - if (((src_count == 1) && (src_chr != 'f')) || - ((src_count == 2) && (src_chr != 'i')) || - ((src_count == 3) && (src_chr != 'l')) || - ((src_count == 4) && (src_chr != 'e')) || - ((src_count == 5) && (src_chr != ':')) || - ((src_count == 6) && (src_chr != '/')) || - ((src_count == 7) && (src_chr != '/'))) - { - SOKOL_LOG("sokol_app.h: dropped file URI doesn't start with file://"); - err = true; - break; - } - } - else if (src_chr == '\r') { - // skip - } - else if (src_chr == '\n') { - src_chr = 0; - src_count = 0; - _sapp.drop.num_files++; - // too many files is not an error - if (_sapp.drop.num_files >= _sapp.drop.max_files) { - break; - } - dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; - dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); - } - else if ((src_chr == '%') && src[0] && src[1]) { - // a percent-encoded byte (most like UTF-8 multibyte sequence) - const char digits[3] = { src[0], src[1], 0 }; - src += 2; - dst_chr = (char) strtol(digits, 0, 16); - } - else { - dst_chr = src_chr; - } - if (dst_chr) { - // dst_end_ptr already has adjustment for terminating zero - if (dst_ptr < dst_end_ptr) { - *dst_ptr++ = dst_chr; - } - else { - SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)"); - err = true; - break; - } - } - } - if (err) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - return false; - } - else { - return true; - } -} - -// XLib manual says keycodes are in the range [8, 255] inclusive. -// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html -static bool _sapp_x11_keycodes[256]; - -_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { - Bool filtered = XFilterEvent(event, None); - switch (event->type) { - case GenericEvent: - if (_sapp.mouse.locked && _sapp.x11.xi.available) { - if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { - if (XGetEventData(_sapp.x11.display, &event->xcookie)) { - if (event->xcookie.evtype == XI_RawMotion) { - XIRawEvent* re = (XIRawEvent*) event->xcookie.data; - if (re->valuators.mask_len) { - const double* values = re->raw_values; - if (XIMaskIsSet(re->valuators.mask, 0)) { - _sapp.mouse.dx = (float) *values; - values++; - } - 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)); - } - } - XFreeEventData(_sapp.x11.display, &event->xcookie); - } - } - } - 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); - } - break; - case KeyPress: - { - int keycode = (int)event->xkey.keycode; - 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); - if (key != SAPP_KEYCODE_INVALID) { - _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); - } - KeySym keysym; - XLookupString(&event->xkey, NULL, 0, &keysym, NULL); - int32_t chr = _sapp_x11_keysym_to_unicode(keysym); - if (chr > 0) { - _sapp_x11_char_event((uint32_t)chr, repeat, mods); - } - } - break; - case KeyRelease: - { - int keycode = (int)event->xkey.keycode; - 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); - _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); - } - } - break; - case ButtonPress: - { - const sapp_mousebutton btn = _sapp_x11_translate_button(event); - const uint32_t mods = _sapp_x11_mod(event->xbutton.state); - if (btn != SAPP_MOUSEBUTTON_INVALID) { - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); - _sapp.x11.mouse_buttons |= (1 << btn); - } - else { - /* might be a scroll event */ - switch (event->xbutton.button) { - case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; - case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; - case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; - case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; - } - } - } - break; - case ButtonRelease: - { - 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)); - _sapp.x11.mouse_buttons &= ~(1 << btn); - } - } - break; - 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)); - } - 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)); - } - break; - case MotionNotify: - if (!_sapp.mouse.locked) { - const float new_x = (float) event->xmotion.x; - const float new_y = (float) event->xmotion.y; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _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)); - } - break; - case ConfigureNotify: - if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { - _sapp.window_width = event->xconfigure.width; - _sapp.window_height = event->xconfigure.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); - } - break; - case PropertyNotify: - if (event->xproperty.state == PropertyNewValue) { - if (event->xproperty.atom == _sapp.x11.WM_STATE) { - const int state = _sapp_x11_get_window_state(); - if (state != _sapp.x11.window_state) { - _sapp.x11.window_state = state; - if (state == IconicState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else if (state == NormalState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - } - break; - case ClientMessage: - if (filtered) { - return; - } - if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { - const Atom protocol = (Atom)event->xclient.data.l[0]; - if (protocol == _sapp.x11.WM_DELETE_WINDOW) { - _sapp.quit_requested = true; - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { - const bool is_list = 0 != (event->xclient.data.l[1] & 1); - _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; - _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _sapp.x11.xdnd.format = None; - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - uint32_t count = 0; - Atom* formats = 0; - if (is_list) { - count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); - } - else { - count = 3; - formats = (Atom*) event->xclient.data.l + 2; - } - for (uint32_t i = 0; i < count; i++) { - if (formats[i] == _sapp.x11.xdnd.text_uri_list) { - _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; - break; - } - } - if (is_list && formats) { - XFree(formats); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - Time time = CurrentTime; - if (_sapp.x11.xdnd.format) { - if (_sapp.x11.xdnd.version >= 1) { - time = (Time)event->xclient.data.l[2]; - } - XConvertSelection(_sapp.x11.display, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.xdnd.format, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.window, - time); - } - else if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - memset(&reply, 0, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.window; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = 0; // drag was rejected - reply.xclient.data.l[2] = None; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { - /* drag operation has moved over the window - FIXME: we could track the mouse position here, but - this isn't implemented on other platforms either so far - */ - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - XEvent reply; - memset(&reply, 0, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - if (_sapp.x11.xdnd.format) { - /* reply that we are ready to copy the dragged data */ - reply.xclient.data.l[1] = 1; // accept with no rectangle - if (_sapp.x11.xdnd.version >= 2) { - reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; - } - } - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - break; - case SelectionNotify: - if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { - char* data = 0; - uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, - event->xselection.property, - event->xselection.target, - (unsigned char**) &data); - if (_sapp.drop.enabled && result) { - if (_sapp_x11_parse_dropped_files_list(data)) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - } - if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - memset(&reply, 0, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.window; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - break; - case DestroyNotify: - break; - } -} - -_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { - /* The following lines are here to trigger a linker error instead of an - obscure runtime error if the user has forgotten to add -pthread to - the compiler or linker options. They have no other purpose. - */ - pthread_attr_t pthread_attr; - pthread_attr_init(&pthread_attr); - pthread_attr_destroy(&pthread_attr); - - _sapp_init_state(desc); - _sapp.x11.window_state = NormalState; - - XInitThreads(); - XrmInitialize(); - _sapp.x11.display = XOpenDisplay(NULL); - if (!_sapp.x11.display) { - _sapp_fail("XOpenDisplay() failed!\n"); - } - _sapp.x11.screen = DefaultScreen(_sapp.x11.display); - _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); - XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); - _sapp_x11_query_system_dpi(); - _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; - _sapp_x11_init_extensions(); - _sapp_x11_create_hidden_cursor(); - _sapp_glx_init(); - Visual* visual = 0; - int depth = 0; - _sapp_glx_choose_visual(&visual, &depth); - _sapp_x11_create_window(visual, depth); - _sapp_glx_create_context(); - _sapp.valid = true; - _sapp_x11_show_window(); - if (_sapp.fullscreen) { - _sapp_x11_set_fullscreen(true); - } - _sapp_x11_query_window_size(); - _sapp_glx_swapinterval(_sapp.swap_interval); - XFlush(_sapp.x11.display); - while (!_sapp.quit_ordered) { - _sapp_glx_make_current(); - int count = XPending(_sapp.x11.display); - while (count--) { - XEvent event; - XNextEvent(_sapp.x11.display, &event); - _sapp_x11_process_event(&event); - } - _sapp_frame(); - _sapp_glx_swap_buffers(); - XFlush(_sapp.x11.display); - /* handle quit-requested, either from window or from sapp_request_quit() */ - if (_sapp.quit_requested && !_sapp.quit_ordered) { - /* give user code a chance to intervene */ - _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - } - _sapp_call_cleanup(); - _sapp_glx_destroy_context(); - _sapp_x11_destroy_window(); - XCloseDisplay(_sapp.x11.display); - _sapp_discard_state(); -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_linux_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_LINUX */ - -/*== PUBLIC API FUNCTIONS ====================================================*/ -#if defined(SOKOL_NO_ENTRY) -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - SOKOL_ASSERT(desc); - #if defined(_SAPP_MACOS) - _sapp_macos_run(desc); - #elif defined(_SAPP_IOS) - _sapp_ios_run(desc); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_run(desc); - #elif defined(_SAPP_WIN32) - _sapp_win32_run(desc); - #elif defined(_SAPP_UWP) - _sapp_uwp_run(desc); - #elif defined(_SAPP_LINUX) - _sapp_linux_run(desc); - #else - // calling sapp_run() directly is not supported on Android) - _sapp_fail("sapp_run() not supported on this platform!"); - #endif -} - -/* this is just a stub so the linker doesn't complain */ -sapp_desc sokol_main(int argc, char* argv[]) { - _SOKOL_UNUSED(argc); - _SOKOL_UNUSED(argv); - sapp_desc desc; - memset(&desc, 0, sizeof(desc)); - return desc; -} -#else -/* likewise, in normal mode, sapp_run() is just an empty stub */ -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - _SOKOL_UNUSED(desc); -} -#endif - -SOKOL_API_IMPL bool sapp_isvalid(void) { - return _sapp.valid; -} - -SOKOL_API_IMPL void* sapp_userdata(void) { - return _sapp.desc.user_data; -} - -SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { - return _sapp.desc; -} - -SOKOL_API_IMPL uint64_t sapp_frame_count(void) { - return _sapp.frame_count; -} - -SOKOL_API_IMPL int sapp_width(void) { - return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; -} - -SOKOL_API_IMPL float sapp_widthf(void) { - return (float)sapp_width(); -} - -SOKOL_API_IMPL int sapp_height(void) { - return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; -} - -SOKOL_API_IMPL float sapp_heightf(void) { - return (float)sapp_height(); -} - -SOKOL_API_IMPL int sapp_color_format(void) { - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - switch (_sapp.emsc.wgpu.render_format) { - case WGPUTextureFormat_RGBA8Unorm: - return _SAPP_PIXELFORMAT_RGBA8; - case WGPUTextureFormat_BGRA8Unorm: - return _SAPP_PIXELFORMAT_BGRA8; - default: - SOKOL_UNREACHABLE; - return 0; - } - #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) - return _SAPP_PIXELFORMAT_BGRA8; - #else - return _SAPP_PIXELFORMAT_RGBA8; - #endif -} - -SOKOL_API_IMPL int sapp_depth_format(void) { - return _SAPP_PIXELFORMAT_DEPTH_STENCIL; -} - -SOKOL_API_IMPL int sapp_sample_count(void) { - return _sapp.sample_count; -} - -SOKOL_API_IMPL bool sapp_high_dpi(void) { - return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); -} - -SOKOL_API_IMPL float sapp_dpi_scale(void) { - return _sapp.dpi_scale; -} - -SOKOL_API_IMPL bool sapp_gles2(void) { - return _sapp.gles2_fallback; -} - -SOKOL_API_IMPL void sapp_show_keyboard(bool show) { - #if defined(_SAPP_IOS) - _sapp_ios_show_keyboard(show); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_show_keyboard(show); - #elif defined(_SAPP_ANDROID) - _sapp_android_show_keyboard(show); - #else - _SOKOL_UNUSED(show); - #endif -} - -SOKOL_API_IMPL bool sapp_keyboard_shown(void) { - return _sapp.onscreen_keyboard_shown; -} - -SOKOL_APP_API_DECL bool sapp_is_fullscreen(void) { - return _sapp.fullscreen; -} - -SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void) { - #if defined(_SAPP_MACOS) - _sapp_macos_toggle_fullscreen(); - #elif defined(_SAPP_WIN32) - _sapp_win32_toggle_fullscreen(); - #elif defined(_SAPP_UWP) - _sapp_uwp_toggle_fullscreen(); - #elif defined(_SAPP_LINUX) - _sapp_x11_toggle_fullscreen(); - #endif -} - -/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ -SOKOL_API_IMPL void sapp_show_mouse(bool show) { - if (_sapp.mouse.shown != show) { - #if defined(_SAPP_MACOS) - _sapp_macos_show_mouse(show); - #elif defined(_SAPP_WIN32) - _sapp_win32_show_mouse(show); - #elif defined(_SAPP_LINUX) - _sapp_x11_show_mouse(show); - #elif defined(_SAPP_UWP) - _sapp_uwp_show_mouse(show); - #endif - _sapp.mouse.shown = show; - } -} - -SOKOL_API_IMPL bool sapp_mouse_shown(void) { - return _sapp.mouse.shown; -} - -SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { - #if defined(_SAPP_MACOS) - _sapp_macos_lock_mouse(lock); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_lock_mouse(lock); - #elif defined(_SAPP_WIN32) - _sapp_win32_lock_mouse(lock); - #elif defined(_SAPP_LINUX) - _sapp_x11_lock_mouse(lock); - #else - _sapp.mouse.locked = lock; - #endif -} - -SOKOL_API_IMPL bool sapp_mouse_locked(void) { - return _sapp.mouse.locked; -} - -SOKOL_API_IMPL void sapp_request_quit(void) { - _sapp.quit_requested = true; -} - -SOKOL_API_IMPL void sapp_cancel_quit(void) { - _sapp.quit_requested = false; -} - -SOKOL_API_IMPL void sapp_quit(void) { - _sapp.quit_ordered = true; -} - -SOKOL_API_IMPL void sapp_consume_event(void) { - _sapp.event_consumed = true; -} - -/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ -SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { - SOKOL_ASSERT(_sapp.clipboard.enabled); - if (!_sapp.clipboard.enabled) { - return; - } - SOKOL_ASSERT(str); - #if defined(_SAPP_MACOS) - _sapp_macos_set_clipboard_string(str); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_set_clipboard_string(str); - #elif defined(_SAPP_WIN32) - _sapp_win32_set_clipboard_string(str); - #else - /* not implemented */ - #endif - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); -} - -SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.enabled); - if (!_sapp.clipboard.enabled) { - return ""; - } - #if defined(_SAPP_MACOS) - return _sapp_macos_get_clipboard_string(); - #elif defined(_SAPP_EMSCRIPTEN) - return _sapp.clipboard.buffer; - #elif defined(_SAPP_WIN32) - return _sapp_win32_get_clipboard_string(); - #else - /* not implemented */ - return _sapp.clipboard.buffer; - #endif -} - -SOKOL_API_IMPL void sapp_set_window_title(const char* title) { - SOKOL_ASSERT(title); - _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); - #if defined(_SAPP_MACOS) - _sapp_macos_update_window_title(); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_window_title(); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_window_title(); - #endif -} - -SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { - SOKOL_ASSERT(_sapp.drop.enabled); - return _sapp.drop.num_files; -} - -SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - SOKOL_ASSERT(_sapp.drop.buffer); - if (!_sapp.drop.enabled) { - return ""; - } - if ((index < 0) || (index >= _sapp.drop.max_files)) { - return ""; - } - return (const char*) _sapp_dropped_file_path_ptr(index); -} - -SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - #if defined(_SAPP_EMSCRIPTEN) - if (!_sapp.drop.enabled) { - return 0; - } - return sapp_js_dropped_file_size(index); - #else - (void)index; - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT(request); - SOKOL_ASSERT(request->callback); - SOKOL_ASSERT(request->buffer_ptr); - SOKOL_ASSERT(request->buffer_size > 0); - #if defined(_SAPP_EMSCRIPTEN) - const int index = request->dropped_file_index; - sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; - if ((index < 0) || (index >= _sapp.drop.num_files)) { - error_code = SAPP_HTML5_FETCH_ERROR_OTHER; - } - if (sapp_html5_get_dropped_file_size(index) > request->buffer_size) { - error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; - } - if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { - _sapp_emsc_invoke_fetch_cb(index, - false, // success - (int)error_code, - request->callback, - 0, // fetched_size - request->buffer_ptr, - request->buffer_size, - request->user_data); - } - else { - sapp_js_fetch_dropped_file(index, - request->callback, - request->buffer_ptr, - request->buffer_size, - request->user_data); - } - #else - (void)request; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.mtl_device; - #else - const void* obj = (__bridge const void*) _sapp.ios.mtl_device; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_macos_get_window(void) { - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_ios_get_window(void) { - #if defined(_SAPP_IOS) - const void* obj = (__bridge const void*) _sapp.ios.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} -SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ - #if defined(_SAPP_IOS) - const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - - -SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device_context; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - if (_sapp.d3d11.msaa_rtv) { - return _sapp.d3d11.msaa_rtv; - } - else { - return _sapp.d3d11.rtv; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.dsv; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_WIN32) - return _sapp.win32.hwnd; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.msaa_view; - } - else { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - else { - return 0; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.depth_stencil_view; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_ANDROID) - return (void*)_sapp.android.activity; - #else - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { - _sapp.html5_ask_leave_site = ask; -} - -#endif /* SOKOL_APP_IMPL */ diff --git a/src/sokol/sokol_app.h.orig b/src/sokol/sokol_app.h.orig deleted file mode 100644 index 4b3d62cc3..000000000 --- a/src/sokol/sokol_app.h.orig +++ /dev/null @@ -1,11529 +0,0 @@ -#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) -#define SOKOL_APP_IMPL -#endif -#ifndef SOKOL_APP_INCLUDED -/* - sokol_app.h -- cross-platform application wrapper - - Project URL: https://github.com/floooh/sokol - - Do this: - #define SOKOL_IMPL or - #define SOKOL_APP_IMPL - before you include this file in *one* C or C++ file to create the - implementation. - - In the same place define one of the following to select the 3D-API - which should be initialized by sokol_app.h (this must also match - the backend selected for sokol_gfx.h if both are used in the same - project): - - #define SOKOL_GLCORE33 - #define SOKOL_GLES2 - #define SOKOL_GLES3 - #define SOKOL_D3D11 - #define SOKOL_METAL - #define SOKOL_WGPU - - Optionally provide the following defines with your own implementations: - - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) - SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain - SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function - SOKOL_APP_API_DECL - public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_APP_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - - Optionally define the following to force debug checks and validations - even in release mode: - - SOKOL_DEBUG - by default this is defined if _DEBUG is defined - - If sokol_app.h is compiled as a DLL, define the following before - including the declaration or implementation: - - SOKOL_DLL - - On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) - or __declspec(dllimport) as needed. - - On Linux, SOKOL_GLCORE33 can use either GLX or EGL. - GLX is default, set SOKOL_FORCE_EGL to override. - - For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp - - 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. - - Link with the following system libraries: - - - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit - - on macOS with GL: Cocoa, QuartzCore, OpenGL - - on iOS with Metal: Foundation, UIKit, Metal, MetalKit - - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit - - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) - - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) - - on Android: GLESv3, EGL, log, android - - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib - - 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 - - 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 - - On macOS and iOS, the implementation must be compiled as Objective-C. - - FEATURE OVERVIEW - ================ - sokol_app.h provides a minimalistic cross-platform API which - implements the 'application-wrapper' parts of a 3D application: - - - a common application entry function - - creates a window and 3D-API context/device with a 'default framebuffer' - - makes the rendered frame visible - - provides keyboard-, mouse- and low-level touch-events - - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android - - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 - - FEATURE/PLATFORM MATRIX - ======================= - | Windows | macOS | Linux | iOS | Android | HTML5 - --------------------+---------+-------+-------+-------+---------+-------- - gl 3.x | YES | YES | YES | --- | --- | --- - gles2/webgl | --- | --- | YES(2)| YES | YES | YES - gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES - metal | --- | YES | --- | YES | --- | --- - d3d11 | YES | --- | --- | --- | --- | --- - KEY_DOWN | YES | YES | YES | SOME | TODO | YES - KEY_UP | YES | YES | YES | SOME | TODO | YES - CHAR | YES | YES | YES | YES | TODO | YES - MOUSE_DOWN | YES | YES | YES | --- | --- | YES - MOUSE_UP | YES | YES | YES | --- | --- | YES - MOUSE_SCROLL | YES | YES | YES | --- | --- | YES - MOUSE_MOVE | YES | YES | YES | --- | --- | YES - MOUSE_ENTER | YES | YES | YES | --- | --- | YES - MOUSE_LEAVE | YES | YES | YES | --- | --- | YES - TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES - TOUCHES_MOVED | --- | --- | --- | YES | YES | YES - TOUCHES_ENDED | --- | --- | --- | YES | YES | YES - TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES - RESIZED | YES | YES | YES | YES | YES | YES - ICONIFIED | YES | YES | YES | --- | --- | --- - RESTORED | YES | YES | YES | --- | --- | --- - FOCUSED | YES | YES | YES | --- | --- | YES - UNFOCUSED | YES | YES | YES | --- | --- | YES - SUSPENDED | --- | --- | --- | YES | YES | TODO - RESUMED | --- | --- | --- | YES | YES | TODO - QUIT_REQUESTED | YES | YES | YES | --- | --- | YES - IME | TODO | TODO? | TODO | ??? | TODO | ??? - key repeat flag | YES | YES | YES | --- | --- | YES - windowed | YES | YES | YES | --- | --- | YES - fullscreen | YES | YES | YES | YES | YES | --- - mouse hide | YES | YES | YES | --- | --- | YES - mouse lock | YES | YES | YES | --- | --- | YES - set cursor type | YES | YES | YES | --- | --- | YES - screen keyboard | --- | --- | --- | YES | TODO | YES - swap interval | YES | YES | YES | YES | TODO | YES - high-dpi | YES | YES | TODO | YES | YES | YES - clipboard | YES | YES | TODO | --- | --- | YES - MSAA | YES | YES | YES | YES | YES | YES - drag'n'drop | YES | YES | YES | --- | --- | YES - window icon | YES | YES(1)| YES | --- | --- | YES - - (1) macOS has no regular window icons, instead the dock icon is changed - (2) supported with EGL only (not GLX) - - STEP BY STEP - ============ - --- Add a sokol_main() function to your code which returns a sapp_desc structure - with initialization parameters and callback function pointers. This - function is called very early, usually at the start of the - platform's entry function (e.g. main or WinMain). You should do as - little as possible here, since the rest of your code might be called - from another thread (this depends on the platform): - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - .width = 640, - .height = 480, - .init_cb = my_init_func, - .frame_cb = my_frame_func, - .cleanup_cb = my_cleanup_func, - .event_cb = my_event_func, - ... - }; - } - - To get any logging output in case of errors you need to provide a log - callback. The easiest way is via sokol_log.h: - - #include "sokol_log.h" - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger.func = slog_func, - }; - } - - There are many more setup parameters, but these are the most important. - For a complete list search for the sapp_desc structure declaration - below. - - DO NOT call any sokol-app function from inside sokol_main(), since - sokol-app will not be initialized at this point. - - The .width and .height parameters are the preferred size of the 3D - rendering canvas. The actual size may differ from this depending on - platform and other circumstances. Also the canvas size may change at - any time (for instance when the user resizes the application window, - or rotates the mobile device). You can just keep .width and .height - zero-initialized to open a default-sized window (what "default-size" - exactly means is platform-specific, but usually it's a size that covers - most of, but not all, of the display). - - All provided function callbacks will be called from the same thread, - but this may be different from the thread where sokol_main() was called. - - .init_cb (void (*)(void)) - This function is called once after the application window, - 3D rendering context and swap chain have been created. The - function takes no arguments and has no return value. - .frame_cb (void (*)(void)) - This is the per-frame callback, which is usually called 60 - times per second. This is where your application would update - most of its state and perform all rendering. - .cleanup_cb (void (*)(void)) - The cleanup callback is called once right before the application - quits. - .event_cb (void (*)(const sapp_event* event)) - The event callback is mainly for input handling, but is also - used to communicate other types of events to the application. Keep the - event_cb struct member zero-initialized if your application doesn't require - event handling. - - As you can see, those 'standard callbacks' don't have a user_data - argument, so any data that needs to be preserved between callbacks - must live in global variables. If keeping state in global variables - is not an option, there's an alternative set of callbacks with - an additional user_data pointer argument: - - .user_data (void*) - The user-data argument for the callbacks below - .init_userdata_cb (void (*)(void* user_data)) - .frame_userdata_cb (void (*)(void* user_data)) - .cleanup_userdata_cb (void (*)(void* user_data)) - .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) - - The function sapp_userdata() can be used to query the user_data - pointer provided in the sapp_desc struct. - - You can also call sapp_query_desc() to get a copy of the - original sapp_desc structure. - - NOTE that there's also an alternative compile mode where sokol_app.h - doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. - - --- Implement the initialization callback function (init_cb), this is called - once after the rendering surface, 3D API and swap chain have been - initialized by sokol_app. All sokol-app functions can be called - from inside the initialization callback, the most useful functions - at this point are: - - int sapp_width(void) - int sapp_height(void) - Returns the current width and height of the default framebuffer in pixels, - this may change from one frame to the next, and it may be different - from the initial size provided in the sapp_desc struct. - - float sapp_widthf(void) - float sapp_heightf(void) - These are alternatives to sapp_width() and sapp_height() which return - the default framebuffer size as float values instead of integer. This - may help to prevent casting back and forth between int and float - in more strongly typed languages than C and C++. - - double sapp_frame_duration(void) - Returns the frame duration in seconds averaged over a number of - frames to smooth out any jittering spikes. - - int sapp_color_format(void) - int sapp_depth_format(void) - The color and depth-stencil pixelformats of the default framebuffer, - as integer values which are compatible with sokol-gfx's - sg_pixel_format enum (so that they can be plugged directly in places - where sg_pixel_format is expected). Possible values are: - - 23 == SG_PIXELFORMAT_RGBA8 - 28 == SG_PIXELFORMAT_BGRA8 - 42 == SG_PIXELFORMAT_DEPTH - 43 == SG_PIXELFORMAT_DEPTH_STENCIL - - int sapp_sample_count(void) - Return the MSAA sample count of the default framebuffer. - - bool sapp_gles2(void) - Returns true if a GLES2 or WebGL context has been created. This - is useful when a GLES3/WebGL2 context was requested but is not - available so that sokol_app.h had to fallback to GLES2/WebGL. - - const void* sapp_metal_get_device(void) - const void* sapp_metal_get_renderpass_descriptor(void) - const void* sapp_metal_get_drawable(void) - If the Metal backend has been selected, these functions return pointers - to various Metal API objects required for rendering, otherwise - they return a null pointer. These void pointers are actually - Objective-C ids converted with a (ARC) __bridge cast so that - the ids can be tunnel through C code. Also note that the returned - pointers to the renderpass-descriptor and drawable may change from one - frame to the next, only the Metal device object is guaranteed to - stay the same. - - const void* sapp_macos_get_window(void) - On macOS, get the NSWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_ios_get_window(void) - On iOS, get the UIWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_win32_get_hwnd(void) - On Windows, get the window's HWND, otherwise a null pointer. The - HWND has been cast to a void pointer in order to be tunneled - through code which doesn't include Windows.h. - - const void* sapp_d3d11_get_device(void) - const void* sapp_d3d11_get_device_context(void) - const void* sapp_d3d11_get_render_target_view(void) - const void* sapp_d3d11_get_depth_stencil_view(void) - Similar to the sapp_metal_* functions, the sapp_d3d11_* functions - return pointers to D3D11 API objects required for rendering, - only if the D3D11 backend has been selected. Otherwise they - return a null pointer. Note that the returned pointers to the - render-target-view and depth-stencil-view may change from one - frame to the next! - - const void* sapp_wgpu_get_device(void) - const void* sapp_wgpu_get_render_view(void) - const void* sapp_wgpu_get_resolve_view(void) - const void* sapp_wgpu_get_depth_stencil_view(void) - These are the WebGPU-specific functions to get the WebGPU - objects and values required for rendering. If sokol_app.h - is not compiled with SOKOL_WGPU, these functions return null. - - const void* sapp_android_get_native_activity(void); - On Android, get the native activity ANativeActivity pointer, otherwise - a null pointer. - - --- Implement the frame-callback function, this function will be called - on the same thread as the init callback, but might be on a different - thread than the sokol_main() function. Note that the size of - the rendering framebuffer might have changed since the frame callback - was called last. Call the functions sapp_width() and sapp_height() - each frame to get the current size. - - --- Optionally implement the event-callback to handle input events. - sokol-app provides the following type of input events: - - a 'virtual key' was pressed down or released - - a single text character was entered (provided as UTF-32 code point) - - a mouse button was pressed down or released (left, right, middle) - - mouse-wheel or 2D scrolling events - - the mouse was moved - - the mouse has entered or left the application window boundaries - - low-level, portable multi-touch events (began, moved, ended, cancelled) - - the application window was resized, iconified or restored - - the application was suspended or restored (on mobile platforms) - - the user or application code has asked to quit the application - - a string was pasted to the system clipboard - - one or more files have been dropped onto the application window - - To explicitly 'consume' an event and prevent that the event is - forwarded for further handling to the operating system, call - sapp_consume_event() from inside the event handler (NOTE that - this behaviour is currently only implemented for some HTML5 - events, support for other platforms and event types will - be added as needed, please open a github ticket and/or provide - a PR if needed). - - NOTE: Do *not* call any 3D API rendering functions in the event - callback function, since the 3D API context may not be active when the - event callback is called (it may work on some platforms and 3D APIs, - but not others, and the exact behaviour may change between - sokol-app versions). - - --- Implement the cleanup-callback function, this is called once - after the user quits the application (see the section - "APPLICATION QUIT" for detailed information on quitting - behaviour, and how to intercept a pending quit - for instance to show a - "Really Quit?" dialog box). Note that the cleanup-callback isn't - guaranteed to be called on the web and mobile platforms. - - MOUSE CURSOR TYPE AND VISIBILITY - ================================ - You can show and hide the mouse cursor with - - void sapp_show_mouse(bool show) - - And to get the current shown status: - - bool sapp_mouse_shown(void) - - NOTE that hiding the mouse cursor is different and independent from - the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when - active (MOUSE LOCK is described below). - - To change the mouse cursor to one of several predefined types, call - the function: - - void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) - - Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore - the standard look. - - To get the currently active mouse cursor type, call: - - sapp_mouse_cursor sapp_get_mouse_cursor(void) - - MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) - ================================================ - In normal mouse mode, no mouse movement events are reported when the - mouse leaves the windows client area or hits the screen border (whether - it's one or the other depends on the platform), and the mouse move events - (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in - framebuffer pixels in the sapp_event items mouse_x and mouse_y, and - relative movement in framebuffer pixels in the sapp_event items mouse_dx - and mouse_dy. - - To get continuous mouse movement (also when the mouse leaves the window - client area or hits the screen border), activate mouse-lock mode - by calling: - - sapp_lock_mouse(true) - - When mouse lock is activated, the mouse pointer is hidden, the - reported absolute mouse position (sapp_event.mouse_x/y) appears - frozen, and the relative mouse movement in sapp_event.mouse_dx/dy - no longer has a direct relation to framebuffer pixels but instead - uses "raw mouse input" (what "raw mouse input" exactly means also - differs by platform). - - To deactivate mouse lock and return to normal mouse mode, call - - sapp_lock_mouse(false) - - And finally, to check if mouse lock is currently active, call - - if (sapp_mouse_locked()) { ... } - - On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() - functions work as expected (mouse lock is activated or deactivated - immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() - also immediately returns the new state after sapp_lock_mouse() - is called. - - On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave - differently, as dictated by the limitations of the HTML5 Pointer Lock API: - - - sapp_lock_mouse(true) can be called at any time, but it will - only take effect in a 'short-lived input event handler of a specific - type', meaning when one of the following events happens: - - SAPP_EVENTTYPE_MOUSE_DOWN - - SAPP_EVENTTYPE_MOUSE_UP - - SAPP_EVENTTYPE_MOUSE_SCROLL - - SAPP_EVENTTYPE_KEY_UP - - SAPP_EVENTTYPE_KEY_DOWN - - The mouse lock/unlock action on the web platform is asynchronous, - this means that sapp_mouse_locked() won't immediately return - the new status after calling sapp_lock_mouse(), instead the - reported status will only change when the pointer lock has actually - been activated or deactivated in the browser. - - On the web, mouse lock can be deactivated by the user at any time - by pressing the Esc key. When this happens, sokol_app.h behaves - the same as if sapp_lock_mouse(false) is called. - - For things like camera manipulation it's most straightforward to lock - and unlock the mouse right from the sokol_app.h event handler, for - instance the following code enters and leaves mouse lock when the - left mouse button is pressed and released, and then uses the relative - movement information to manipulate a camera (taken from the - cgltf-sapp.c sample in the sokol-samples repository - at https://github.com/floooh/sokol-samples): - - static void input(const sapp_event* ev) { - switch (ev->type) { - case SAPP_EVENTTYPE_MOUSE_DOWN: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(true); - } - break; - - case SAPP_EVENTTYPE_MOUSE_UP: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(false); - } - break; - - case SAPP_EVENTTYPE_MOUSE_MOVE: - if (sapp_mouse_locked()) { - cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); - } - break; - - default: - break; - } - } - - CLIPBOARD SUPPORT - ================= - Applications can send and receive UTF-8 encoded text data from and to the - system clipboard. By default, clipboard support is disabled and - must be enabled at startup via the following sapp_desc struct - members: - - sapp_desc.enable_clipboard - set to true to enable clipboard support - sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes - - Enabling the clipboard will dynamically allocate a clipboard buffer - for UTF-8 encoded text data of the requested size in bytes, the default - size is 8 KBytes. Strings that don't fit into the clipboard buffer - (including the terminating zero) will be silently clipped, so it's - important that you provide a big enough clipboard size for your - use case. - - To send data to the clipboard, call sapp_set_clipboard_string() with - a pointer to an UTF-8 encoded, null-terminated C-string. - - NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be - called from inside a 'short-lived event handler', and there are a few - other HTML5-specific caveats to workaround. You'll basically have to - tinker until it works in all browsers :/ (maybe the situation will - improve when all browsers agree on and implement the new - HTML5 navigator.clipboard API). - - To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED - event in your event handler function, and then call sapp_get_clipboard_string() - to obtain the pasted UTF-8 encoded text. - - NOTE that behaviour of sapp_get_clipboard_string() is slightly different - depending on platform: - - - on the HTML5 platform, the internal clipboard buffer will only be updated - right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, - and sapp_get_clipboard_string() will simply return the current content - of the clipboard buffer - - on 'native' platforms, the call to sapp_get_clipboard_string() will - update the internal clipboard buffer with the most recent data - from the system clipboard - - Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, - and then call sapp_get_clipboard_string() right in the event handler. - - The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app - as follows: - - - on macOS: when the Cmd+V key is pressed down - - on HTML5: when the browser sends a 'paste' event to the global 'window' object - - on all other platforms: when the Ctrl+V key is pressed down - - DRAG AND DROP SUPPORT - ===================== - PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 - and on the native desktop platforms (Win32, Linux and macOS) because - of security-related restrictions in the HTML5 drag'n'drop API. The - WASM/HTML5 specifics are described at the end of this documentation - section: - - Like clipboard support, drag'n'drop support must be explicitly enabled - at startup in the sapp_desc struct. - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - ... - }; - } - - You can also adjust the maximum number of files that are accepted - in a drop operation, and the maximum path length in bytes if needed: - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - .max_dropped_files = 8, // default is 1 - .max_dropped_file_path_length = 8192, // in bytes, default is 2048 - ... - }; - } - - When drag'n'drop is enabled, the event callback will be invoked with an - event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on - the application window. - - After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the - number of dropped files, and their absolute paths by calling separate - functions: - - void on_event(const sapp_event* ev) { - if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { - - // the mouse position where the drop happened - float x = ev->mouse_x; - float y = ev->mouse_y; - - // get the number of files and their paths like this: - const int num_dropped_files = sapp_get_num_dropped_files(); - for (int i = 0; i < num_dropped_files; i++) { - const char* path = sapp_get_dropped_file_path(i); - ... - } - } - } - - The returned file paths are UTF-8 encoded strings. - - You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() - anywhere, also outside the event handler callback, but be aware that the - file path strings will be overwritten with the next drop operation. - - In any case, sapp_get_dropped_file_path() will never return a null pointer, - instead an empty string "" will be returned if the drag'n'drop feature - hasn't been enabled, the last drop-operation failed, or the file path index - is out of range. - - Drag'n'drop caveats: - - - if more files are dropped in a single drop-action - than sapp_desc.max_dropped_files, the additional - files will be silently ignored - - if any of the file paths is longer than - sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 - encoding) the entire drop operation will be silently ignored (this - needs some sort of error feedback in the future) - - no mouse positions are reported while the drag is in - process, this may change in the future - - Drag'n'drop on HTML5/WASM: - - The HTML5 drag'n'drop API doesn't return file paths, but instead - black-box 'file objects' which must be used to load the content - of dropped files. This is the reason why sokol_app.h adds two - HTML5-specific functions to the drag'n'drop API: - - uint32_t sapp_html5_get_dropped_file_size(int index) - Returns the size in bytes of a dropped file. - - void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) - Asynchronously loads the content of a dropped file into a - provided memory buffer (which must be big enough to hold - the file content) - - To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED - event is received: - - sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ - .dropped_file_index = 0, - .callback = fetch_cb - .buffer = { - .ptr = buf, - .size = sizeof(buf) - }, - .user_data = ... - }); - - Make sure that the memory pointed to by 'buf' stays valid until the - callback function is called! - - As result of the asynchronous loading operation (no matter if succeeded or - failed) the 'fetch_cb' function will be called: - - void fetch_cb(const sapp_html5_fetch_response* response) { - // IMPORTANT: check if the loading operation actually succeeded: - if (response->succeeded) { - // the size of the loaded file: - const size_t num_bytes = response->data.size; - // and the pointer to the data (same as 'buf' in the fetch-call): - const void* ptr = response->data.ptr; - } - else { - // on error check the error code: - switch (response->error_code) { - case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: - ... - break; - case SAPP_HTML5_FETCH_ERROR_OTHER: - ... - break; - } - } - } - - Check the droptest-sapp example for a real-world example which works - both on native platforms and the web: - - https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c - - HIGH-DPI RENDERING - ================== - You can set the sapp_desc.high_dpi flag during initialization to request - a full-resolution framebuffer on HighDPI displays. The default behaviour - is sapp_desc.high_dpi=false, this means that the application will - render to a lower-resolution framebuffer on HighDPI displays and the - rendered content will be upscaled by the window system composer. - - In a HighDPI scenario, you still request the same window size during - sokol_main(), but the framebuffer sizes returned by sapp_width() - and sapp_height() will be scaled up according to the DPI scaling - ratio. - - Note that on some platforms the DPI scaling factor may change at any - time (for instance when a window is moved from a high-dpi display - to a low-dpi display). - - To query the current DPI scaling factor, call the function: - - float sapp_dpi_scale(void); - - For instance on a Retina Mac, returning the following sapp_desc - struct from sokol_main(): - - sapp_desc sokol_main() { - return (sapp_desc) { - .width = 640, - .height = 480, - .high_dpi = true, - ... - }; - } - - ...the functions the functions sapp_width(), sapp_height() - and sapp_dpi_scale() will return the following values: - - sapp_width: 1280 - sapp_height: 960 - sapp_dpi_scale: 2.0 - - If the high_dpi flag is false, or you're not running on a Retina display, - the values would be: - - sapp_width: 640 - sapp_height: 480 - sapp_dpi_scale: 1.0 - - If the window is moved from the Retina display to a low-dpi external display, - the values would change as follows: - - sapp_width: 1280 => 640 - sapp_height: 960 => 480 - sapp_dpi_scale: 2.0 => 1.0 - - Currently there is no event associated with a DPI change, but an - SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the - framebuffer size changing. - - Per-monitor DPI is currently supported on macOS and Windows. - - APPLICATION QUIT - ================ - Without special quit handling, a sokol_app.h application will quit - 'gracefully' when the user clicks the window close-button unless a - platform's application model prevents this (e.g. on web or mobile). - 'Graceful exit' means that the application-provided cleanup callback will - be called before the application quits. - - On native desktop platforms sokol_app.h provides more control over the - application-quit-process. It's possible to initiate a 'programmatic quit' - from the application code, and a quit initiated by the application user can - be intercepted (for instance to show a custom dialog box). - - This 'programmatic quit protocol' is implemented through 3 functions - and 1 event: - - - sapp_quit(): This function simply quits the application without - giving the user a chance to intervene. Usually this might - be called when the user clicks the 'Ok' button in a 'Really Quit?' - dialog box - - sapp_request_quit(): Calling sapp_request_quit() will send the - event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler - callback, giving the user code a chance to intervene and cancel the - pending quit process (for instance to show a 'Really Quit?' dialog - box). If the event handler callback does nothing, the application - will be quit as usual. To prevent this, call the function - sapp_cancel_quit() from inside the event handler. - - sapp_cancel_quit(): Cancels a pending quit request, either initiated - by the user clicking the window close button, or programmatically - by calling sapp_request_quit(). The only place where calling this - function makes sense is from inside the event handler callback when - the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. - - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user - clicks the window's close button or application code calls the - sapp_request_quit() function. The event handler callback code can handle - this event by calling sapp_cancel_quit() to cancel the quit. - If the event is ignored, the application will quit as usual. - - On the web platform, the quit behaviour differs from native platforms, - because of web-specific restrictions: - - A `programmatic quit` initiated by calling sapp_quit() or - sapp_request_quit() will work as described above: the cleanup callback is - called, platform-specific cleanup is performed (on the web - this means that JS event handlers are unregisters), and then - the request-animation-loop will be exited. However that's all. The - web page itself will continue to exist (e.g. it's not possible to - programmatically close the browser tab). - - On the web it's also not possible to run custom code when the user - closes a browser tab, so it's not possible to prevent this with a - fancy custom dialog box. - - Instead the standard "Leave Site?" dialog box can be activated (or - deactivated) with the following function: - - sapp_html5_ask_leave_site(bool ask); - - The initial state of the associated internal flag can be provided - at startup via sapp_desc.html5_ask_leave_site. - - This feature should only be used sparingly in critical situations - for - instance when the user would loose data - since popping up modal dialog - boxes is considered quite rude in the web world. Note that there's no way - to customize the content of this dialog box or run any code as a result - of the user's decision. Also note that the user must have interacted with - the site before the dialog box will appear. These are all security measures - to prevent fishing. - - The Dear ImGui HighDPI sample contains example code of how to - implement a 'Really Quit?' dialog box with Dear ImGui (native desktop - platforms only), and for showing the hardwired "Leave Site?" dialog box - when running on the web platform: - - https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html - - FULLSCREEN - ========== - If the sapp_desc.fullscreen flag is true, sokol-app will try to create - a fullscreen window on platforms with a 'proper' window system - (mobile devices will always use fullscreen). The implementation details - depend on the target platform, in general sokol-app will use a - 'soft approach' which doesn't interfere too much with the platform's - window system (for instance borderless fullscreen window instead of - a 'real' fullscreen mode). Such details might change over time - as sokol-app is adapted for different needs. - - The most important effect of fullscreen mode to keep in mind is that - the requested canvas width and height will be ignored for the initial - window size, calling sapp_width() and sapp_height() will instead return - the resolution of the fullscreen canvas (however the provided size - might still be used for the non-fullscreen window, in case the user can - switch back from fullscreen- to windowed-mode). - - To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). - - 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 - can display the platform's integrated onscreen keyboard for text - input. To request that the onscreen keyboard is shown, call - - sapp_show_keyboard(true); - - Likewise, to hide the keyboard call: - - sapp_show_keyboard(false); - - Note that on the web platform, the keyboard can only be shown from - inside an input handler. On such platforms, sapp_show_keyboard() - will only work as expected when it is called from inside the - sokol-app event callback function. When called from other places, - an internal flag will be set, and the onscreen keyboard will be - called at the next 'legal' opportunity (when the next input event - is handled). - - OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) - ====================================================== - In its default configuration, sokol_app.h "hijacks" the platform's - standard main() function. This was done because different platforms - have different main functions which are not compatible with - C's main() (for instance WinMain on Windows has completely different - arguments). However, this "main hijacking" posed a problem for - usage scenarios like integrating sokol_app.h with other languages than - C or C++, so an alternative SOKOL_NO_ENTRY mode has been added - in which the user code provides the platform's main function: - - - define SOKOL_NO_ENTRY before including the sokol_app.h implementation - - do *not* provide a sokol_main() function - - instead provide the standard main() function of the platform - - from the main function, call the function ```sapp_run()``` which - takes a pointer to an ```sapp_desc``` structure. - - ```sapp_run()``` takes over control and calls the provided init-, frame-, - shutdown- and event-callbacks just like in the default model, it - will only return when the application quits (or not at all on some - platforms, like emscripten) - - NOTE: SOKOL_NO_ENTRY is currently not supported on Android. - - WINDOWS CONSOLE OUTPUT - ====================== - On Windows, regular windowed applications don't show any stdout/stderr text - output, which can be a bit of a hassle for printf() debugging or generally - logging text to the console. Also, console output by default uses a local - codepage setting and thus international UTF-8 encoded text is printed - as garbage. - - To help with these issues, sokol_app.h can be configured at startup - via the following Windows-specific sapp_desc flags: - - sapp_desc.win32_console_utf8 (default: false) - When set to true, the output console codepage will be switched - to UTF-8 (and restored to the original codepage on exit) - - sapp_desc.win32_console_attach (default: false) - When set to true, stdout and stderr will be attached to the - console of the parent process (if the parent process actually - has a console). This means that if the application was started - in a command line window, stdout and stderr output will be printed - to the terminal, just like a regular command line program. But if - the application is started via double-click, it will behave like - a regular UI application, and stdout/stderr will not be visible. - - sapp_desc.win32_console_create (default: false) - When set to true, a new console window will be created and - stdout/stderr will be redirected to that console window. It - doesn't matter if the application is started from the command - line or via double-click. - - MEMORY ALLOCATION OVERRIDE - ========================== - You can override the memory allocation functions at initialization time - like this: - - void* my_alloc(size_t size, void* user_data) { - return malloc(size); - } - - void my_free(void* ptr, void* user_data) { - free(ptr); - } - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc){ - // ... - .allocator = { - .alloc = my_alloc, - .free = my_free, - .user_data = ..., - } - }; - } - - If no overrides are provided, malloc and free will be used. - - This only affects memory allocation calls done by sokol_app.h - itself though, not any allocations in OS libraries. - - - ERROR REPORTING AND LOGGING - =========================== - To get any logging information at all you need to provide a logging callback in the setup call - the easiest way is to use sokol_log.h: - - #include "sokol_log.h" - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger.func = slog_func, - }; - } - - To override logging with your own callback, first write a logging function like this: - - void my_log(const char* tag, // e.g. 'sapp' - uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info - uint32_t log_item_id, // SAPP_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_app.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data) - { - ... - } - - ...and then setup sokol-app like this: - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger = { - .func = my_log, - .user_data = my_user_data, - } - }; - } - - The provided logging function must be reentrant (e.g. be callable from - different threads). - - If you don't want to provide your own custom logger it is highly recommended to use - the standard logger in sokol_log.h instead, otherwise you won't see any warnings or - errors. - - - TEMP NOTE DUMP - ============== - - onscreen keyboard support on Android requires Java :(, should we even bother? - - sapp_desc needs a bool whether to initialize depth-stencil surface - - GL context initialization needs more control (at least what GL version to initialize) - - application icon - - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy - at the latest but should do it earlier, in onStop, as an app is "killable" after onStop - on Android Honeycomb and later (it can't be done at the moment as the app may be started - again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) - - - LICENSE - ======= - zlib/libpng license - - Copyright (c) 2018 Andre Weissflog - - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ -#define SOKOL_APP_INCLUDED (1) -#include // size_t -#include -#include - -#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) -#define SOKOL_APP_API_DECL SOKOL_API_DECL -#endif -#ifndef SOKOL_APP_API_DECL -#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) -#define SOKOL_APP_API_DECL __declspec(dllexport) -#elif defined(_WIN32) && defined(SOKOL_DLL) -#define SOKOL_APP_API_DECL __declspec(dllimport) -#else -#define SOKOL_APP_API_DECL extern -#endif -#endif - -#ifdef __cplusplus -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, - SAPP_EVENTTYPE_KEY_UP, - SAPP_EVENTTYPE_CHAR, - SAPP_EVENTTYPE_MOUSE_DOWN, - SAPP_EVENTTYPE_MOUSE_UP, - SAPP_EVENTTYPE_MOUSE_SCROLL, - SAPP_EVENTTYPE_MOUSE_MOVE, - SAPP_EVENTTYPE_MOUSE_ENTER, - SAPP_EVENTTYPE_MOUSE_LEAVE, - SAPP_EVENTTYPE_TOUCHES_BEGAN, - SAPP_EVENTTYPE_TOUCHES_MOVED, - SAPP_EVENTTYPE_TOUCHES_ENDED, - SAPP_EVENTTYPE_TOUCHES_CANCELLED, - SAPP_EVENTTYPE_RESIZED, - SAPP_EVENTTYPE_ICONIFIED, - SAPP_EVENTTYPE_RESTORED, - SAPP_EVENTTYPE_FOCUSED, - SAPP_EVENTTYPE_UNFOCUSED, - SAPP_EVENTTYPE_SUSPENDED, - SAPP_EVENTTYPE_RESUMED, - SAPP_EVENTTYPE_QUIT_REQUESTED, - SAPP_EVENTTYPE_CLIPBOARD_PASTED, - SAPP_EVENTTYPE_FILES_DROPPED, - _SAPP_EVENTTYPE_NUM, - _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF -} sapp_event_type; - -/* - 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, - SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ - SAPP_KEYCODE_COMMA = 44, /* , */ - SAPP_KEYCODE_MINUS = 45, /* - */ - SAPP_KEYCODE_PERIOD = 46, /* . */ - SAPP_KEYCODE_SLASH = 47, /* / */ - SAPP_KEYCODE_0 = 48, - SAPP_KEYCODE_1 = 49, - SAPP_KEYCODE_2 = 50, - SAPP_KEYCODE_3 = 51, - SAPP_KEYCODE_4 = 52, - SAPP_KEYCODE_5 = 53, - SAPP_KEYCODE_6 = 54, - SAPP_KEYCODE_7 = 55, - SAPP_KEYCODE_8 = 56, - SAPP_KEYCODE_9 = 57, - SAPP_KEYCODE_SEMICOLON = 59, /* ; */ - SAPP_KEYCODE_EQUAL = 61, /* = */ - SAPP_KEYCODE_A = 65, - SAPP_KEYCODE_B = 66, - SAPP_KEYCODE_C = 67, - SAPP_KEYCODE_D = 68, - SAPP_KEYCODE_E = 69, - SAPP_KEYCODE_F = 70, - SAPP_KEYCODE_G = 71, - SAPP_KEYCODE_H = 72, - SAPP_KEYCODE_I = 73, - SAPP_KEYCODE_J = 74, - SAPP_KEYCODE_K = 75, - SAPP_KEYCODE_L = 76, - SAPP_KEYCODE_M = 77, - SAPP_KEYCODE_N = 78, - SAPP_KEYCODE_O = 79, - SAPP_KEYCODE_P = 80, - SAPP_KEYCODE_Q = 81, - SAPP_KEYCODE_R = 82, - SAPP_KEYCODE_S = 83, - SAPP_KEYCODE_T = 84, - SAPP_KEYCODE_U = 85, - SAPP_KEYCODE_V = 86, - SAPP_KEYCODE_W = 87, - SAPP_KEYCODE_X = 88, - SAPP_KEYCODE_Y = 89, - SAPP_KEYCODE_Z = 90, - SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ - SAPP_KEYCODE_BACKSLASH = 92, /* \ */ - SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ - SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ - SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ - SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ - SAPP_KEYCODE_ESCAPE = 256, - SAPP_KEYCODE_ENTER = 257, - SAPP_KEYCODE_TAB = 258, - SAPP_KEYCODE_BACKSPACE = 259, - SAPP_KEYCODE_INSERT = 260, - SAPP_KEYCODE_DELETE = 261, - SAPP_KEYCODE_RIGHT = 262, - SAPP_KEYCODE_LEFT = 263, - SAPP_KEYCODE_DOWN = 264, - SAPP_KEYCODE_UP = 265, - SAPP_KEYCODE_PAGE_UP = 266, - SAPP_KEYCODE_PAGE_DOWN = 267, - SAPP_KEYCODE_HOME = 268, - SAPP_KEYCODE_END = 269, - SAPP_KEYCODE_CAPS_LOCK = 280, - SAPP_KEYCODE_SCROLL_LOCK = 281, - SAPP_KEYCODE_NUM_LOCK = 282, - SAPP_KEYCODE_PRINT_SCREEN = 283, - SAPP_KEYCODE_PAUSE = 284, - SAPP_KEYCODE_F1 = 290, - SAPP_KEYCODE_F2 = 291, - SAPP_KEYCODE_F3 = 292, - SAPP_KEYCODE_F4 = 293, - SAPP_KEYCODE_F5 = 294, - SAPP_KEYCODE_F6 = 295, - SAPP_KEYCODE_F7 = 296, - SAPP_KEYCODE_F8 = 297, - SAPP_KEYCODE_F9 = 298, - SAPP_KEYCODE_F10 = 299, - SAPP_KEYCODE_F11 = 300, - SAPP_KEYCODE_F12 = 301, - SAPP_KEYCODE_F13 = 302, - SAPP_KEYCODE_F14 = 303, - SAPP_KEYCODE_F15 = 304, - SAPP_KEYCODE_F16 = 305, - SAPP_KEYCODE_F17 = 306, - SAPP_KEYCODE_F18 = 307, - SAPP_KEYCODE_F19 = 308, - SAPP_KEYCODE_F20 = 309, - SAPP_KEYCODE_F21 = 310, - SAPP_KEYCODE_F22 = 311, - SAPP_KEYCODE_F23 = 312, - SAPP_KEYCODE_F24 = 313, - SAPP_KEYCODE_F25 = 314, - SAPP_KEYCODE_KP_0 = 320, - SAPP_KEYCODE_KP_1 = 321, - SAPP_KEYCODE_KP_2 = 322, - SAPP_KEYCODE_KP_3 = 323, - SAPP_KEYCODE_KP_4 = 324, - SAPP_KEYCODE_KP_5 = 325, - SAPP_KEYCODE_KP_6 = 326, - SAPP_KEYCODE_KP_7 = 327, - SAPP_KEYCODE_KP_8 = 328, - SAPP_KEYCODE_KP_9 = 329, - SAPP_KEYCODE_KP_DECIMAL = 330, - SAPP_KEYCODE_KP_DIVIDE = 331, - SAPP_KEYCODE_KP_MULTIPLY = 332, - SAPP_KEYCODE_KP_SUBTRACT = 333, - SAPP_KEYCODE_KP_ADD = 334, - SAPP_KEYCODE_KP_ENTER = 335, - SAPP_KEYCODE_KP_EQUAL = 336, - SAPP_KEYCODE_LEFT_SHIFT = 340, - SAPP_KEYCODE_LEFT_CONTROL = 341, - SAPP_KEYCODE_LEFT_ALT = 342, - SAPP_KEYCODE_LEFT_SUPER = 343, - SAPP_KEYCODE_RIGHT_SHIFT = 344, - SAPP_KEYCODE_RIGHT_CONTROL = 345, - SAPP_KEYCODE_RIGHT_ALT = 346, - SAPP_KEYCODE_RIGHT_SUPER = 347, - SAPP_KEYCODE_MENU = 348, -} sapp_keycode; - -/* - Android specific 'tool type' enum for touch events. This lets the - application check what type of input device was used for - touch events. - - NOTE: the values must remain in sync with the corresponding - Android SDK type, so don't change those. - - See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN -*/ -typedef enum sapp_android_tooltype { - SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN - SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER - SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS - SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE -} sapp_android_tooltype; - -/* - 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; - float pos_y; - sapp_android_tooltype android_tooltype; // only valid on Android - 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, - SAPP_MOUSEBUTTON_MIDDLE = 0x2, - 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, // 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; // 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; // = 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; - -/* - sapp_allocator - - Used in sapp_desc to provide custom memory-alloc and -free functions - to sokol_app.h. If memory management should be overridden, both the - alloc and free function must be provided (e.g. it's not valid to - override one function but not the other). -*/ -typedef struct sapp_allocator { - void* (*alloc)(size_t size, void* user_data); - void (*free)(void* ptr, void* user_data); - void* user_data; -} sapp_allocator; - -/* - sapp_log_item - - Log items are defined via X-Macros and expanded to an enum - 'sapp_log_item', and in debug mode to corresponding - human readable error messages. -*/ -#define _SAPP_LOG_ITEMS \ - _SAPP_LOGITEM_XMACRO(OK, "Ok") \ - _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ - _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0, 3.2 and 4.1)") \ - _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ - _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ - _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ - _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ - _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ - _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ - _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED, "OpenGL 3.2 not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ - _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ - _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ - _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ - _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ - _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ - _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity sucessfully created") \ - _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ - _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ - _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ - -#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, -typedef enum sapp_log_item { - _SAPP_LOG_ITEMS -} sapp_log_item; -#undef _SAPP_LOGITEM_XMACRO - -/* - sapp_logger - - Used in sapp_desc to provide a logging function. Please be aware that - without logging function, sokol-app will be completely silent, e.g. it will - not report errors or warnings. For maximum error verbosity, compile in - debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance - the standard logging function from sokol_log.h). -*/ -typedef struct sapp_logger { - void (*func)( - const char* tag, // always "sapp" - uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info - uint32_t log_item_id, // SAPP_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_app.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data); - void* user_data; -} sapp_logger; - -typedef struct sapp_desc { - 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* 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*); - - 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 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 - sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) - sapp_logger logger; // logging callback override (default: NO LOGGING!) - - /* backend-specific options */ - bool gl_force_gles2; // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available - int gl_major_version; // override GL major and minor version (the default GL version is 3.2) - int gl_minor_version; - 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 -} sapp_desc; - -/* HTML5 specific: request and response structs for - asynchronously loading dropped-file content. -*/ -typedef enum sapp_html5_fetch_error { - SAPP_HTML5_FETCH_ERROR_NO_ERROR, - SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, - SAPP_HTML5_FETCH_ERROR_OTHER, -} sapp_html5_fetch_error; - -typedef struct sapp_html5_fetch_response { - bool succeeded; // true if the loading operation has succeeded - sapp_html5_fetch_error error_code; - int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) - sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) - sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) - void* user_data; // user-provided user data pointer -} sapp_html5_fetch_response; - -typedef struct sapp_html5_fetch_request { - int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 - void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) - sapp_range buffer; // ptr/size of a memory buffer to load the data into - void* user_data; // optional userdata pointer -} sapp_html5_fetch_request; - -/* - sapp_mouse_cursor - - Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) -*/ -typedef enum sapp_mouse_cursor { - SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor - SAPP_MOUSECURSOR_ARROW, - SAPP_MOUSECURSOR_IBEAM, - SAPP_MOUSECURSOR_CROSSHAIR, - SAPP_MOUSECURSOR_POINTING_HAND, - SAPP_MOUSECURSOR_RESIZE_EW, - SAPP_MOUSECURSOR_RESIZE_NS, - SAPP_MOUSECURSOR_RESIZE_NWSE, - SAPP_MOUSECURSOR_RESIZE_NESW, - SAPP_MOUSECURSOR_RESIZE_ALL, - SAPP_MOUSECURSOR_NOT_ALLOWED, - _SAPP_MOUSECURSOR_NUM, -} sapp_mouse_cursor; - -/* user-provided functions */ -extern sapp_desc sokol_main(int argc, char* argv[]); - -/* returns true after sokol-app has been initialized */ -SOKOL_APP_API_DECL bool sapp_isvalid(void); -/* returns the current framebuffer width in pixels */ -SOKOL_APP_API_DECL int sapp_width(void); -/* same as sapp_width(), but returns float */ -SOKOL_APP_API_DECL float sapp_widthf(void); -/* returns the current framebuffer height in pixels */ -SOKOL_APP_API_DECL int sapp_height(void); -/* same as sapp_height(), but returns float */ -SOKOL_APP_API_DECL float sapp_heightf(void); -/* get default framebuffer color pixel format */ -SOKOL_APP_API_DECL int sapp_color_format(void); -/* get default framebuffer depth pixel format */ -SOKOL_APP_API_DECL int sapp_depth_format(void); -/* get default framebuffer sample count */ -SOKOL_APP_API_DECL int sapp_sample_count(void); -/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ -SOKOL_APP_API_DECL bool sapp_high_dpi(void); -/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ -SOKOL_APP_API_DECL float sapp_dpi_scale(void); -/* show or hide the mobile device onscreen keyboard */ -SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); -/* return true if the mobile device onscreen keyboard is currently shown */ -SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); -/* query fullscreen mode */ -SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); -/* toggle fullscreen mode */ -SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL void sapp_show_mouse(bool show); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL bool sapp_mouse_shown(void); -/* enable/disable mouse-pointer-lock mode */ -SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); -/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ -SOKOL_APP_API_DECL bool sapp_mouse_locked(void); -/* set mouse cursor type */ -SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); -/* get current mouse cursor type */ -SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); -/* return the userdata pointer optionally provided in sapp_desc */ -SOKOL_APP_API_DECL void* sapp_userdata(void); -/* return a copy of the sapp_desc structure */ -SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); -/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ -SOKOL_APP_API_DECL void sapp_request_quit(void); -/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ -SOKOL_APP_API_DECL void sapp_cancel_quit(void); -/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ -SOKOL_APP_API_DECL void sapp_quit(void); -/* call from inside event callback to consume the current event (don't forward to platform) */ -SOKOL_APP_API_DECL void sapp_consume_event(void); -/* get the current frame counter (for comparison with sapp_event.frame_count) */ -SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); -/* get an averaged/smoothed frame duration in seconds */ -SOKOL_APP_API_DECL double sapp_frame_duration(void); -/* write string into clipboard */ -SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); -/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ -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 */ -SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); - -/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ -SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); - -/* EGL: get EGLDisplay object */ -SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); -/* EGL: get EGLContext object */ -SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); - -/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ -SOKOL_APP_API_DECL bool sapp_gles2(void); - -/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ -SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); -/* HTML5: get byte size of a dropped file */ -SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); -/* HTML5: asynchronously load the content of a dropped file */ -SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); - -/* Metal: get bridged pointer to Metal device object */ -SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); -/* Metal: get bridged pointer to this frame's renderpass descriptor */ -SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); -/* Metal: get bridged pointer to current drawable */ -SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); -/* macOS: get bridged pointer to macOS NSWindow */ -SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); -/* iOS: get bridged pointer to iOS UIWindow */ -SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); - -/* D3D11: get pointer to ID3D11Device object */ -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 */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); -/* Win32: get the HWND window handle */ -SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); - -/* WebGPU: get WGPUDevice handle */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); -/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); -/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); -/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); - -/* Android: get native activity handle */ -SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); - -#ifdef __cplusplus -} /* extern "C" */ - -/* reference-based equivalents for C++ */ -inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } - -#endif - -// this WinRT specific hack is required when wWinMain is in a static library -#if defined(_MSC_VER) && defined(UNICODE) -#include -#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -#pragma comment(linker, "/include:wWinMain") -#endif -#endif - -#endif // SOKOL_APP_INCLUDED - -// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ -// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ -// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ -// -// >>implementation -#ifdef SOKOL_APP_IMPL -#define SOKOL_APP_IMPL_INCLUDED (1) - -#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) -#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" -#endif - -#include // malloc, free -#include // memset -#include // size_t -#include // roundf - -/* check if the config defines are alright */ -#if defined(__APPLE__) - // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting - #if !defined(__cplusplus) - #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) - #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" - #endif - #endif - #define _SAPP_APPLE (1) - #include - #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE - /* MacOS */ - #define _SAPP_MACOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") - #endif - #else - /* iOS or iOS Simulator */ - #define _SAPP_IOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) - #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") - #endif - #endif -#elif defined(__EMSCRIPTEN__) - /* emscripten (asm.js or wasm) */ - #define _SAPP_EMSCRIPTEN (1) - #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU) - #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU") - #endif -#elif defined(_WIN32) - /* Windows (D3D11 or GL) */ - #define _SAPP_WIN32 (1) - #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") - #endif -#elif defined(__ANDROID__) - /* Android */ - #define _SAPP_ANDROID (1) - #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) - #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2") - #endif - #if defined(SOKOL_NO_ENTRY) - #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") - #endif -#elif defined(__linux__) || defined(__unix__) - /* Linux */ - #define _SAPP_LINUX (1) - #if defined(SOKOL_GLCORE33) - #if !defined(SOKOL_FORCE_EGL) - #define _SAPP_GLX (1) - #endif - #elif !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) - #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3 or SOKOL_GLES2") - #endif -#else -#error "sokol_app.h: Unknown platform" -#endif - -#ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL -#endif -#ifndef SOKOL_DEBUG - #ifndef NDEBUG - #define SOKOL_DEBUG - #endif -#endif -#ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) -#endif -#ifndef SOKOL_UNREACHABLE - #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) -#endif - -#ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) || defined(__clang__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif -#endif -#ifndef _SOKOL_UNUSED - #define _SOKOL_UNUSED(x) (void)(x) -#endif - -#if defined(_SAPP_APPLE) - #if defined(SOKOL_METAL) - #import - #import - #endif - #if defined(_SAPP_MACOS) - #if !defined(SOKOL_METAL) - #ifndef GL_SILENCE_DEPRECATION - #define GL_SILENCE_DEPRECATION - #endif - #include - #endif - #elif defined(_SAPP_IOS) - #import - #if !defined(SOKOL_METAL) - #import - #endif - #endif - #include - #include -#elif defined(_SAPP_EMSCRIPTEN) - #if defined(SOKOL_WGPU) - #include - #endif - #include - #include -#elif defined(_SAPP_WIN32) - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ - #pragma warning(disable:4054) /* 'type cast': from function pointer */ - #pragma warning(disable:4055) /* 'type cast': from data pointer */ - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ - #endif - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #ifndef NOMINMAX - #define NOMINMAX - #endif - #include - #include - #include - #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem - #if defined(SOKOL_WIN32_FORCE_MAIN) - #pragma comment (linker, "/subsystem:console") - #else - #pragma comment (linker, "/subsystem:windows") - #endif - #endif - #include /* freopen_s() */ - #include /* wcslen() */ - - #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") - #endif - - #if defined(SOKOL_D3D11) - #ifndef D3D11_NO_HELPERS - #define D3D11_NO_HELPERS - #endif - #include - #include - // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it - #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) - #endif - #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ - #define WM_MOUSEHWHEEL (0x020E) - #endif - #ifndef WM_DPICHANGED - #define WM_DPICHANGED (0x02E0) - #endif -#elif defined(_SAPP_ANDROID) - #include - #include - #include - #include - #include - #include -#elif defined(_SAPP_LINUX) - #define GL_GLEXT_PROTOTYPES - #include - #include - #include - #include - #include - #include - #include - #include - #include /* XC_* font cursors */ - #include /* CARD32 */ - #if !defined(_SAPP_GLX) - #include - #endif - #include /* dlopen, dlsym, dlclose */ - #include /* LONG_MAX */ - #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ - #include -#endif - -// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ -// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ -// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ -// -// >>frame timing -#define _SAPP_RING_NUM_SLOTS (256) -typedef struct { - int head; - int tail; - double buf[_SAPP_RING_NUM_SLOTS]; -} _sapp_ring_t; - -_SOKOL_PRIVATE int _sapp_ring_idx(int i) { - return i % _SAPP_RING_NUM_SLOTS; -} - -_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { - ring->head = 0; - ring->tail = 0; -} - -_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { - return _sapp_ring_idx(ring->head + 1) == ring->tail; -} - -_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { - return ring->head == ring->tail; -} - -_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { - int count; - if (ring->head >= ring->tail) { - count = ring->head - ring->tail; - } - else { - count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; - } - SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); - return count; -} - -_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { - SOKOL_ASSERT(!_sapp_ring_full(ring)); - ring->buf[ring->head] = val; - ring->head = _sapp_ring_idx(ring->head + 1); -} - -_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { - SOKOL_ASSERT(!_sapp_ring_empty(ring)); - double val = ring->buf[ring->tail]; - ring->tail = _sapp_ring_idx(ring->tail + 1); - return val; -} - -/* - NOTE: - - Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? - A: The value appears to be highly unstable during the first few - seconds, sometimes several frames are dropped in sequence, or - switch between 120 and 60 Hz for a few frames. Simply measuring - and averaging the frame time yielded a more stable frame duration. - Maybe switching to CVDisplayLink would yield better results. - Until then just measure the time. -*/ -typedef struct { - #if defined(_SAPP_APPLE) - struct { - mach_timebase_info_data_t timebase; - uint64_t start; - } mach; - #elif defined(_SAPP_EMSCRIPTEN) - // empty - #elif defined(_SAPP_WIN32) - struct { - LARGE_INTEGER freq; - LARGE_INTEGER start; - } win; - #else // Linux, Android, ... - #ifdef CLOCK_MONOTONIC - #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC - #else - // on some embedded platforms, CLOCK_MONOTONIC isn't defined - #define _SAPP_CLOCK_MONOTONIC (1) - #endif - struct { - uint64_t start; - } posix; - #endif -} _sapp_timestamp_t; - -_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { - int64_t q = value / denom; - int64_t r = value % denom; - return q * numer + r * numer / denom; -} - -_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { - #if defined(_SAPP_APPLE) - mach_timebase_info(&ts->mach.timebase); - ts->mach.start = mach_absolute_time(); - #elif defined(_SAPP_EMSCRIPTEN) - (void)ts; - #elif defined(_SAPP_WIN32) - QueryPerformanceFrequency(&ts->win.freq); - QueryPerformanceCounter(&ts->win.start); - #else - struct timespec tspec; - clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); - ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; - #endif -} - -_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { - #if defined(_SAPP_APPLE) - const uint64_t traw = mach_absolute_time() - ts->mach.start; - const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); - return (double)now / 1000000000.0; - #elif defined(_SAPP_EMSCRIPTEN) - (void)ts; - SOKOL_ASSERT(false); - return 0.0; - #elif defined(_SAPP_WIN32) - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); - return (double)now / 1000000000.0; - #else - struct timespec tspec; - clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); - const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; - return (double)now / 1000000000.0; - #endif -} - -typedef struct { - double last; - double accum; - double avg; - int spike_count; - int num; - _sapp_timestamp_t timestamp; - _sapp_ring_t ring; -} _sapp_timing_t; - -_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { - t->last = 0.0; - t->accum = 0.0; - t->spike_count = 0; - t->num = 0; - _sapp_ring_init(&t->ring); -} - -_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { - t->avg = 1.0 / 60.0; // dummy value until first actual value is available - _sapp_timing_reset(t); - _sapp_timestamp_init(&t->timestamp); -} - -_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { - // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) - double min_dur = 0.0; - double max_dur = 0.1; - // if we have enough samples for a useful average, use a much tighter 'valid window' - if (_sapp_ring_full(&t->ring)) { - min_dur = t->avg * 0.8; - max_dur = t->avg * 1.2; - } - if ((dur < min_dur) || (dur > max_dur)) { - t->spike_count++; - // if there have been many spikes in a row, the display refresh rate - // might have changed, so a timing reset is needed - if (t->spike_count > 20) { - _sapp_timing_reset(t); - } - return; - } - if (_sapp_ring_full(&t->ring)) { - double old_val = _sapp_ring_dequeue(&t->ring); - t->accum -= old_val; - t->num -= 1; - } - _sapp_ring_enqueue(&t->ring, dur); - t->accum += dur; - t->num += 1; - SOKOL_ASSERT(t->num > 0); - t->avg = t->accum / t->num; - t->spike_count = 0; -} - -_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { - t->last = 0.0; -} - -_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { - const double now = _sapp_timestamp_now(&t->timestamp); - if (t->last > 0.0) { - double dur = now - t->last; - _sapp_timing_put(t, dur); - } - t->last = now; -} - -_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { - if (t->last > 0.0) { - double dur = now - t->last; - _sapp_timing_put(t, dur); - } - t->last = now; -} - -_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { - return t->avg; -} - -// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██████ ██ ██ ██ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ██ ██████ ██████ ██ ███████ -// -// >> structs -#if defined(_SAPP_MACOS) -@interface _sapp_macos_app_delegate : NSObject -@end -@interface _sapp_macos_window : NSWindow -@end -@interface _sapp_macos_window_delegate : NSObject -@end -#if defined(SOKOL_METAL) - @interface _sapp_macos_view : MTKView - @end -#elif defined(SOKOL_GLCORE33) - @interface _sapp_macos_view : NSOpenGLView - - (void)timerFired:(id)sender; - @end -#endif // SOKOL_GLCORE33 - -typedef struct { - uint32_t flags_changed_store; - uint8_t mouse_buttons; - NSWindow* window; - NSTrackingArea* tracking_area; - id keyup_monitor; - _sapp_macos_app_delegate* app_dlg; - _sapp_macos_window_delegate* win_dlg; - _sapp_macos_view* view; - NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; - #if defined(SOKOL_METAL) - id mtl_device; - #endif -} _sapp_macos_t; - -#endif // _SAPP_MACOS - -#if defined(_SAPP_IOS) - -@interface _sapp_app_delegate : NSObject -@end -@interface _sapp_textfield_dlg : NSObject -- (void)keyboardWasShown:(NSNotification*)notif; -- (void)keyboardWillBeHidden:(NSNotification*)notif; -- (void)keyboardDidChangeFrame:(NSNotification*)notif; -@end -#if defined(SOKOL_METAL) - @interface _sapp_ios_view : MTKView; - @end -#else - @interface _sapp_ios_view : GLKView - @end -#endif - -typedef struct { - UIWindow* window; - _sapp_ios_view* view; - UITextField* textfield; - _sapp_textfield_dlg* textfield_dlg; - #if defined(SOKOL_METAL) - UIViewController* view_ctrl; - id mtl_device; - #else - GLKViewController* view_ctrl; - EAGLContext* eagl_ctx; - #endif - bool suspended; -} _sapp_ios_t; - -#endif // _SAPP_IOS - -#if defined(_SAPP_EMSCRIPTEN) - -#if defined(SOKOL_WGPU) -typedef struct { - int state; - WGPUDevice device; - WGPUSwapChain swapchain; - WGPUTextureFormat render_format; - WGPUTexture msaa_tex; - WGPUTexture depth_stencil_tex; - WGPUTextureView swapchain_view; - WGPUTextureView msaa_view; - WGPUTextureView depth_stencil_view; -} _sapp_wgpu_t; -#endif - -typedef struct { - bool textfield_created; - 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 -} _sapp_emsc_t; -#endif // _SAPP_EMSCRIPTEN - -#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) -typedef struct { - ID3D11Device* device; - ID3D11DeviceContext* device_context; - ID3D11Texture2D* rt; - ID3D11RenderTargetView* rtv; - ID3D11Texture2D* msaa_rt; - ID3D11RenderTargetView* msaa_rtv; - ID3D11Texture2D* ds; - ID3D11DepthStencilView* dsv; - DXGI_SWAP_CHAIN_DESC swap_chain_desc; - IDXGISwapChain* swap_chain; - IDXGIDevice1* dxgi_device; - bool use_dxgi_frame_stats; - UINT sync_refresh_count; -} _sapp_d3d11_t; -#endif - -#if defined(_SAPP_WIN32) - -#ifndef DPI_ENUMS_DECLARED -typedef enum PROCESS_DPI_AWARENESS -{ - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 -} PROCESS_DPI_AWARENESS; -typedef enum MONITOR_DPI_TYPE { - MDT_EFFECTIVE_DPI = 0, - MDT_ANGULAR_DPI = 1, - MDT_RAW_DPI = 2, - MDT_DEFAULT = MDT_EFFECTIVE_DPI -} MONITOR_DPI_TYPE; -#endif /*DPI_ENUMS_DECLARED*/ - -typedef struct { - bool aware; - float content_scale; - float window_scale; - float mouse_scale; -} _sapp_win32_dpi_t; - -typedef struct { - HWND hwnd; - HMONITOR hmonitor; - HDC dc; - HICON big_icon; - HICON small_icon; - HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; - UINT orig_codepage; - LONG mouse_locked_x, mouse_locked_y; - RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed - bool is_win10_or_greater; - bool in_create_window; - bool iconified; - bool mouse_tracked; - uint8_t mouse_capture_mask; - _sapp_win32_dpi_t dpi; - bool raw_input_mousepos_valid; - LONG raw_input_mousepos_x; - LONG raw_input_mousepos_y; - uint8_t raw_input_data[256]; -} _sapp_win32_t; - -#if defined(SOKOL_GLCORE33) -#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_NO_ACCELERATION_ARB 0x2025 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_SAMPLES_ARB 0x2042 -#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define ERROR_INVALID_VERSION_ARB 0x2095 -#define ERROR_INVALID_PROFILE_ARB 0x2096 -#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 -typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); -typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); -typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); -typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); -typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); -typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); -typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); -typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); - -typedef struct { - HINSTANCE opengl32; - HGLRC gl_ctx; - PFN_wglCreateContext CreateContext; - PFN_wglDeleteContext DeleteContext; - PFN_wglGetProcAddress GetProcAddress; - PFN_wglGetCurrentDC GetCurrentDC; - PFN_wglMakeCurrent MakeCurrent; - PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; - PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; - PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; - PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - bool ext_swap_control; - bool arb_multisample; - bool arb_pixel_format; - bool arb_create_context; - bool arb_create_context_profile; - HWND msg_hwnd; - HDC msg_dc; -} _sapp_wgl_t; -#endif // SOKOL_GLCORE33 - -#endif // _SAPP_WIN32 - -#if defined(_SAPP_ANDROID) -typedef enum { - _SOKOL_ANDROID_MSG_CREATE, - _SOKOL_ANDROID_MSG_RESUME, - _SOKOL_ANDROID_MSG_PAUSE, - _SOKOL_ANDROID_MSG_FOCUS, - _SOKOL_ANDROID_MSG_NO_FOCUS, - _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, - _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, - _SOKOL_ANDROID_MSG_DESTROY, -} _sapp_android_msg_t; - -typedef struct { - pthread_t thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - int read_from_main_fd; - int write_from_main_fd; -} _sapp_android_pt_t; - -typedef struct { - ANativeWindow* window; - AInputQueue* input; -} _sapp_android_resources_t; - -typedef struct { - ANativeActivity* activity; - _sapp_android_pt_t pt; - _sapp_android_resources_t pending; - _sapp_android_resources_t current; - ALooper* looper; - bool is_thread_started; - bool is_thread_stopping; - bool is_thread_stopped; - bool has_created; - bool has_resumed; - bool has_focus; - EGLConfig config; - EGLDisplay display; - EGLContext context; - EGLSurface surface; -} _sapp_android_t; - -#endif // _SAPP_ANDROID - -#if defined(_SAPP_LINUX) - -#define _SAPP_X11_XDND_VERSION (5) - -#define GLX_VENDOR 1 -#define GLX_RGBA_BIT 0x00000001 -#define GLX_WINDOW_BIT 0x00000001 -#define GLX_DRAWABLE_TYPE 0x8010 -#define GLX_RENDER_TYPE 0x8011 -#define GLX_DOUBLEBUFFER 5 -#define GLX_RED_SIZE 8 -#define GLX_GREEN_SIZE 9 -#define GLX_BLUE_SIZE 10 -#define GLX_ALPHA_SIZE 11 -#define GLX_DEPTH_SIZE 12 -#define GLX_STENCIL_SIZE 13 -#define GLX_SAMPLES 0x186a1 -#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define GLX_CONTEXT_FLAGS_ARB 0x2094 - -typedef XID GLXWindow; -typedef XID GLXDrawable; -typedef struct __GLXFBConfig* GLXFBConfig; -typedef struct __GLXcontext* GLXContext; -typedef void (*__GLXextproc)(void); - -typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); -typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); -typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); -typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); -typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); -typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); -typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); -typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); -typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); -typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); -typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); -typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); -typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); -typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); - -typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); -typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); - -typedef struct { - bool available; - int major_opcode; - int event_base; - int error_base; - int major; - int minor; -} _sapp_xi_t; - -typedef struct { - int version; - Window source; - Atom format; - Atom XdndAware; - Atom XdndEnter; - Atom XdndPosition; - Atom XdndStatus; - Atom XdndActionCopy; - Atom XdndDrop; - Atom XdndFinished; - Atom XdndSelection; - Atom XdndTypeList; - Atom text_uri_list; -} _sapp_xdnd_t; - -typedef struct { - uint8_t mouse_buttons; - Display* display; - int screen; - Window root; - Colormap colormap; - Window window; - Cursor hidden_cursor; - Cursor cursors[_SAPP_MOUSECURSOR_NUM]; - int window_state; - float dpi; - unsigned char error_code; - Atom UTF8_STRING; - Atom WM_PROTOCOLS; - Atom WM_DELETE_WINDOW; - 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; - _sapp_xdnd_t xdnd; -} _sapp_x11_t; - -#if defined(_SAPP_GLX) - -typedef struct { - void* libgl; - int major; - int minor; - int event_base; - int error_base; - GLXContext ctx; - GLXWindow window; - - // GLX 1.3 functions - PFNGLXGETFBCONFIGSPROC GetFBConfigs; - PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; - PFNGLXGETCLIENTSTRINGPROC GetClientString; - PFNGLXQUERYEXTENSIONPROC QueryExtension; - PFNGLXQUERYVERSIONPROC QueryVersion; - PFNGLXDESTROYCONTEXTPROC DestroyContext; - PFNGLXMAKECURRENTPROC MakeCurrent; - PFNGLXSWAPBUFFERSPROC SwapBuffers; - PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; - PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; - PFNGLXCREATEWINDOWPROC CreateWindow; - PFNGLXDESTROYWINDOWPROC DestroyWindow; - - // GLX 1.4 and extension functions - PFNGLXGETPROCADDRESSPROC GetProcAddress; - PFNGLXGETPROCADDRESSPROC GetProcAddressARB; - PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; - PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - - // extension availability - bool EXT_swap_control; - bool MESA_swap_control; - bool ARB_multisample; - bool ARB_create_context; - bool ARB_create_context_profile; -} _sapp_glx_t; - -#else - -typedef struct { - EGLDisplay display; - EGLContext context; - EGLSurface surface; -} _sapp_egl_t; - -#endif // _SAPP_GLX - -#endif // _SAPP_LINUX - -/* helper macros */ -#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) -#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) - -#define _SAPP_MAX_TITLE_LENGTH (128) -#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) -#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) -/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ -#define _SAPP_PIXELFORMAT_RGBA8 (23) -#define _SAPP_PIXELFORMAT_BGRA8 (28) -#define _SAPP_PIXELFORMAT_DEPTH (42) -#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (43) - -#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) - // this is ARC compatible - #if defined(__cplusplus) - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } - #else - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } - #endif -#else - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } -#endif - -typedef struct { - bool enabled; - int buf_size; - char* buffer; -} _sapp_clipboard_t; - -typedef struct { - bool enabled; - int max_files; - int max_path_length; - int num_files; - int buf_size; - char* buffer; -} _sapp_drop_t; - -typedef struct { - float x, y; - float dx, dy; - bool shown; - bool locked; - bool pos_valid; - sapp_mouse_cursor current_cursor; -} _sapp_mouse_t; - -typedef struct { - sapp_desc desc; - bool valid; - bool fullscreen; - bool gles2_fallback; - bool first_frame; - bool init_called; - bool cleanup_called; - bool quit_requested; - bool quit_ordered; - bool event_consumed; - bool html5_ask_leave_site; - bool onscreen_keyboard_shown; - int window_width; - int window_height; - int framebuffer_width; - int framebuffer_height; - int sample_count; - int swap_interval; - float dpi_scale; - uint64_t frame_count; - _sapp_timing_t timing; - sapp_event event; - _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) - _sapp_ios_t ios; - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_t emsc; - #elif defined(_SAPP_WIN32) - _sapp_win32_t win32; - #if defined(SOKOL_D3D11) - _sapp_d3d11_t d3d11; - #elif defined(SOKOL_GLCORE33) - _sapp_wgl_t wgl; - #endif - #elif defined(_SAPP_ANDROID) - _sapp_android_t android; - #elif defined(_SAPP_LINUX) - _sapp_x11_t x11; - #if defined(_SAPP_GLX) - _sapp_glx_t glx; - #else - _sapp_egl_t egl; - #endif - #endif - char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; - char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ - wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ - sapp_keycode keycodes[SAPP_MAX_KEYCODES]; -} _sapp_t; -static _sapp_t _sapp; - -// ██ ██████ ██████ ██████ ██ ███ ██ ██████ -// ██ ██ ██ ██ ██ ██ ████ ██ ██ -// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ -// -// >>logging -#if defined(SOKOL_DEBUG) -#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, -static const char* _sapp_log_messages[] = { - _SAPP_LOG_ITEMS -}; -#undef _SAPP_LOGITEM_XMACRO -#endif // SOKOL_DEBUG - -#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) -#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) -#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) -#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) - -static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { - if (_sapp.desc.logger.func) { - const char* filename = 0; - #if defined(SOKOL_DEBUG) - filename = __FILE__; - if (0 == msg) { - msg = _sapp_log_messages[log_item]; - } - #endif - _sapp.desc.logger.func("sapp", log_level, log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); - } - else { - // for log level PANIC it would be 'undefined behaviour' to continue - if (log_level == 0) { - abort(); - } - } -} - -// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ -// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ -// -// >>memory -_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { - SOKOL_ASSERT(ptr && (size > 0)); - memset(ptr, 0, size); -} - -_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { - SOKOL_ASSERT(size > 0); - void* ptr; - if (_sapp.desc.allocator.alloc) { - ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data); - } - else { - ptr = malloc(size); - } - if (0 == ptr) { - _SAPP_PANIC(MALLOC_FAILED); - } - return ptr; -} - -_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { - void* ptr = _sapp_malloc(size); - _sapp_clear(ptr, size); - return ptr; -} - -_SOKOL_PRIVATE void _sapp_free(void* ptr) { - if (_sapp.desc.allocator.free) { - _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data); - } - else { - free(ptr); - } -} - -// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ █████ ██ ██████ █████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ -// -// >>helpers -_SOKOL_PRIVATE void _sapp_call_init(void) { - if (_sapp.desc.init_cb) { - _sapp.desc.init_cb(); - } - else if (_sapp.desc.init_userdata_cb) { - _sapp.desc.init_userdata_cb(_sapp.desc.user_data); - } - _sapp.init_called = true; -} - -_SOKOL_PRIVATE void _sapp_call_frame(void) { - if (_sapp.init_called && !_sapp.cleanup_called) { - if (_sapp.desc.frame_cb) { - _sapp.desc.frame_cb(); - } - else if (_sapp.desc.frame_userdata_cb) { - _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); - } - } -} - -_SOKOL_PRIVATE void _sapp_call_cleanup(void) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.cleanup_cb) { - _sapp.desc.cleanup_cb(); - } - else if (_sapp.desc.cleanup_userdata_cb) { - _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); - } - _sapp.cleanup_called = true; - } -} - -_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.event_cb) { - _sapp.desc.event_cb(e); - } - else if (_sapp.desc.event_userdata_cb) { - _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); - } - } - if (_sapp.event_consumed) { - _sapp.event_consumed = false; - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { - SOKOL_ASSERT(_sapp.drop.buffer); - SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); - int offset = index * _sapp.drop.max_path_length; - SOKOL_ASSERT(offset < _sapp.drop.buf_size); - return &_sapp.drop.buffer[offset]; -} - -/* Copy a string into a fixed size buffer with guaranteed zero- - termination. - - Return false if the string didn't fit into the buffer and had to be clamped. - - FIXME: Currently UTF-8 strings might become invalid if the string - is clamped, because the last zero-byte might be written into - the middle of a multi-byte sequence. -*/ -_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { - SOKOL_ASSERT(src && dst && (max_len > 0)); - char* const end = &(dst[max_len-1]); - char c = 0; - for (int i = 0; i < max_len; i++) { - c = *src; - if (c != 0) { - src++; - } - *dst++ = c; - } - /* truncated? */ - if (c != 0) { - *end = 0; - return false; - } - else { - return true; - } -} - -_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { - SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); - sapp_desc res = *desc; - res.sample_count = _sapp_def(res.sample_count, 1); - res.swap_interval = _sapp_def(res.swap_interval, 1); - // NOTE: can't patch the default for gl_major_version and gl_minor_version - // independently, because a desired version 4.0 would be patched to 4.2 - // (or expressed differently: zero is a valid value for gl_minor_version - // and can't be used to indicate 'default') - if (0 == res.gl_major_version) { - res.gl_major_version = 3; - res.gl_minor_version = 2; - } - res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas"); - res.clipboard_size = _sapp_def(res.clipboard_size, 8192); - res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); - res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); - res.window_title = _sapp_def(res.window_title, "sokol_app"); - return res; -} - -_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { - SOKOL_ASSERT(desc); - SOKOL_ASSERT(desc->width >= 0); - SOKOL_ASSERT(desc->height >= 0); - SOKOL_ASSERT(desc->sample_count >= 0); - SOKOL_ASSERT(desc->swap_interval >= 0); - SOKOL_ASSERT(desc->clipboard_size >= 0); - SOKOL_ASSERT(desc->max_dropped_files >= 0); - SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); - _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); - _sapp.desc = _sapp_desc_defaults(desc); - _sapp.first_frame = true; - // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this - _sapp.window_width = _sapp.desc.width; - _sapp.window_height = _sapp.desc.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp.sample_count = _sapp.desc.sample_count; - _sapp.swap_interval = _sapp.desc.swap_interval; - _sapp.html5_canvas_selector[0] = '#'; - _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); - _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; - _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; - _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; - if (_sapp.clipboard.enabled) { - _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; - _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); - } - _sapp.drop.enabled = _sapp.desc.enable_dragndrop; - if (_sapp.drop.enabled) { - _sapp.drop.max_files = _sapp.desc.max_dropped_files; - _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; - _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; - _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); - } - _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); - _sapp.desc.window_title = _sapp.window_title; - _sapp.dpi_scale = 1.0f; - _sapp.fullscreen = _sapp.desc.fullscreen; - _sapp.mouse.shown = true; - _sapp_timing_init(&_sapp.timing); -} - -_SOKOL_PRIVATE void _sapp_discard_state(void) { - if (_sapp.clipboard.enabled) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - _sapp_free((void*)_sapp.clipboard.buffer); - } - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - _sapp_free((void*)_sapp.drop.buffer); - } - if (_sapp.default_icon_pixels) { - _sapp_free((void*)_sapp.default_icon_pixels); - } - _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); -} - -_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { - _sapp_clear(&_sapp.event, sizeof(_sapp.event)); - _sapp.event.type = type; - _sapp.event.frame_count = _sapp.frame_count; - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - _sapp.event.window_width = _sapp.window_width; - _sapp.event.window_height = _sapp.window_height; - _sapp.event.framebuffer_width = _sapp.framebuffer_width; - _sapp.event.framebuffer_height = _sapp.framebuffer_height; - _sapp.event.mouse_x = _sapp.mouse.x; - _sapp.event.mouse_y = _sapp.mouse.y; - _sapp.event.mouse_dx = _sapp.mouse.dx; - _sapp.event.mouse_dy = _sapp.mouse.dy; -} - -_SOKOL_PRIVATE bool _sapp_events_enabled(void) { - /* only send events when an event callback is set, and the init function was called */ - return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; -} - -_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { - if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { - return _sapp.keycodes[scan_code]; - } - else { - return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); - } -} - -_SOKOL_PRIVATE void _sapp_frame(void) { - if (_sapp.first_frame) { - _sapp.first_frame = false; - _sapp_call_init(); - } - _sapp_call_frame(); - _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) { - _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); - 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*) _sapp_malloc_clear((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); -} - -// █████ ██████ ██████ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██████ ██████ ██ █████ -// ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ███████ ███████ -// -// >>apple -#if defined(_SAPP_APPLE) - -#if __has_feature(objc_arc) -#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } -#else -#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } -#endif - -// ███ ███ █████ ██████ ██████ ███████ -// ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ ███████ ██ ██ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██████ ██████ ███████ -// -// >>macos -#if defined(_SAPP_MACOS) - -_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { - _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; - _sapp.keycodes[0x12] = SAPP_KEYCODE_1; - _sapp.keycodes[0x13] = SAPP_KEYCODE_2; - _sapp.keycodes[0x14] = SAPP_KEYCODE_3; - _sapp.keycodes[0x15] = SAPP_KEYCODE_4; - _sapp.keycodes[0x17] = SAPP_KEYCODE_5; - _sapp.keycodes[0x16] = SAPP_KEYCODE_6; - _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; - _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; - _sapp.keycodes[0x19] = SAPP_KEYCODE_9; - _sapp.keycodes[0x00] = SAPP_KEYCODE_A; - _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; - _sapp.keycodes[0x08] = SAPP_KEYCODE_C; - _sapp.keycodes[0x02] = SAPP_KEYCODE_D; - _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; - _sapp.keycodes[0x03] = SAPP_KEYCODE_F; - _sapp.keycodes[0x05] = SAPP_KEYCODE_G; - _sapp.keycodes[0x04] = SAPP_KEYCODE_H; - _sapp.keycodes[0x22] = SAPP_KEYCODE_I; - _sapp.keycodes[0x26] = SAPP_KEYCODE_J; - _sapp.keycodes[0x28] = SAPP_KEYCODE_K; - _sapp.keycodes[0x25] = SAPP_KEYCODE_L; - _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; - _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; - _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; - _sapp.keycodes[0x23] = SAPP_KEYCODE_P; - _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01] = SAPP_KEYCODE_S; - _sapp.keycodes[0x11] = SAPP_KEYCODE_T; - _sapp.keycodes[0x20] = SAPP_KEYCODE_U; - _sapp.keycodes[0x09] = SAPP_KEYCODE_V; - _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; - _sapp.keycodes[0x07] = SAPP_KEYCODE_X; - _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; - _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x77] = SAPP_KEYCODE_END; - _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; - _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; -} - -_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - if (_sapp.macos.keyup_monitor != nil) { - [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; - // NOTE: removeMonitor also releases the object - _sapp.macos.keyup_monitor = nil; - } - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.view); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); - #endif - _SAPP_OBJC_RELEASE(_sapp.macos.window); -} - -// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) -@interface NSCursor() -+ (id)_windowResizeNorthWestSouthEastCursor; -+ (id)_windowResizeNorthEastSouthWestCursor; -+ (id)_windowResizeNorthSouthCursor; -+ (id)_windowResizeEastWestCursor; -@end - -_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { - _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug - _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; -} - -_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_macos_init_keytable(); - [NSApplication sharedApplication]; - - // 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; - - // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: - NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { - if ([event modifierFlags] & NSEventModifierFlagCommand) { - [[NSApp keyWindow] sendEvent:event]; - } - return event; - }; - _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; - - [NSApp run]; - // NOTE: [NSApp run] never returns, instead cleanup code - // must be put into applicationWillTerminate -} - -/* MacOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_macos_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_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; - } - if (f & NSEventModifierFlagControl) { - m |= SAPP_MODIFIER_CTRL; - } - if (f & NSEventModifierFlagOption) { - m |= SAPP_MODIFIER_ALT; - } - 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; -} - -_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -/* NOTE: unlike the iOS version of this function, the macOS version - can dynamically update the DPI scaling factor when a window is moved - between HighDPI / LowDPI screens. -*/ -_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; - } - else { - _sapp.dpi_scale = 1.0f; - } - const NSRect bounds = [_sapp.macos.view bounds]; - _sapp.window_width = (int)roundf(bounds.size.width); - _sapp.window_height = (int)roundf(bounds.size.height); - #if defined(SOKOL_METAL) - _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); - const CGSize fb_size = _sapp.macos.view.drawableSize; - const int cur_fb_width = (int)roundf(fb_size.width); - const int cur_fb_height = (int)roundf(fb_size.height); - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - #elif defined(SOKOL_GLCORE33) - const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); - const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - _sapp.framebuffer_width = cur_fb_width; - _sapp.framebuffer_height = cur_fb_height; - #endif - if (_sapp.framebuffer_width == 0) { - _sapp.framebuffer_width = 1; - } - if (_sapp.framebuffer_height == 0) { - _sapp.framebuffer_height = 1; - } - if (_sapp.window_width == 0) { - _sapp.window_width = 1; - } - if (_sapp.window_height == 0) { - _sapp.window_height = 1; - } - if (dim_changed) { - #if defined(SOKOL_METAL) - CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.macos.view.drawableSize = drawable_size; - #else - // nothing to do for GL? - #endif - if (!_sapp.first_frame) { - _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { - /* NOTE: the _sapp.fullscreen flag is also notified by the - windowDidEnterFullscreen / windowDidExitFullscreen - event handlers - */ - _sapp.fullscreen = !_sapp.fullscreen; - [_sapp.macos.window toggleFullScreen:nil]; -} - -_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { - @autoreleasepool { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; - [pasteboard setString:@(str) forType:NSPasteboardTypeString]; - } -} - -_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - @autoreleasepool { - _sapp.clipboard.buffer[0] = 0; - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { - return _sapp.clipboard.buffer; - } - NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; - if (!str) { - return _sapp.clipboard.buffer; - } - _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - } - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { - [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; -} - -_SOKOL_PRIVATE void _sapp_macos_mouse_update(NSEvent* event) { - if (!_sapp.mouse.locked) { - const NSPoint mouse_pos = event.locationInWindow; - float new_x = mouse_pos.x * _sapp.dpi_scale; - float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; - if (_sapp.mouse.pos_valid) { - // don't update dx/dy in the very first update - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { - /* NOTE: this function is only called when the mouse visibility actually changes */ - if (visible) { - CGDisplayShowCursor(kCGDirectMainDisplay); - } - else { - CGDisplayHideCursor(kCGDirectMainDisplay); - } -} - -_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - /* - NOTE that this code doesn't warp the mouse cursor to the window - center as everybody else does it. This lead to a spike in the - *second* mouse-moved event after the warp happened. The - mouse centering doesn't seem to be required (mouse-moved events - are reported correctly even when the cursor is at an edge of the screen). - - NOTE also that the hide/show of the mouse cursor should properly - stack with calls to sapp_show_mouse() - */ - if (_sapp.mouse.locked) { - CGAssociateMouseAndMouseCursorPosition(NO); - [NSCursor hide]; - } - else { - [NSCursor unhide]; - CGAssociateMouseAndMouseCursorPosition(YES); - } -} - -_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { - // show/hide cursor only if visibility status has changed (required because show/hide stacks) - if (shown != _sapp.mouse.shown) { - if (shown) { - [NSCursor unhide]; - } - else { - [NSCursor hide]; - } - } - // update cursor type - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (_sapp.macos.cursors[cursor]) { - [_sapp.macos.cursors[cursor] set]; - } - else { - [[NSCursor arrowCursor] set]; - } -} - -_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) { - [_sapp.macos.window performClose:nil]; - } -} - -@implementation _sapp_macos_app_delegate -- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { - _SOKOL_UNUSED(aNotification); - _sapp_macos_init_cursors(); - if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { - // use 4/5 of screen size as default size - NSRect screen_rect = NSScreen.mainScreen.frame; - if (_sapp.window_width == 0) { - _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); - } - if (_sapp.window_height == 0) { - _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f); - } - } - const NSUInteger style = - NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | - NSWindowStyleMaskResizable; - NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); - _sapp.macos.window = [[_sapp_macos_window alloc] - initWithContentRect:window_rect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO]; - _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate - _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; - _sapp.macos.window.acceptsMouseMovedEvents = YES; - _sapp.macos.window.restorable = YES; - - _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; - _sapp.macos.window.delegate = _sapp.macos.win_dlg; - #if defined(SOKOL_METAL) - NSInteger max_fps = 60; - #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) - if (@available(macOS 12.0, *)) { - max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; - } - #endif - _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.macos.view = [[_sapp_macos_view alloc] init]; - [_sapp.macos.view updateTrackingAreas]; - _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.macos.view.device = _sapp.macos.mtl_device; - _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; - _sapp.macos.view.autoResizeDrawable = false; - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; - #elif defined(SOKOL_GLCORE33) - NSOpenGLPixelFormatAttribute attrs[32]; - int i = 0; - attrs[i++] = NSOpenGLPFAAccelerated; - attrs[i++] = NSOpenGLPFADoubleBuffer; - attrs[i++] = NSOpenGLPFAOpenGLProfile; - const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; - switch(glVersion) { - case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; - case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; - case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; - default: - _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); - } - attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; - attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; - if (_sapp.sample_count > 1) { - attrs[i++] = NSOpenGLPFAMultisample; - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; - attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; - } - else { - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; - } - attrs[i++] = 0; - NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - SOKOL_ASSERT(glpixelformat_obj != nil); - - _sapp.macos.view = [[_sapp_macos_view alloc] - initWithFrame:window_rect - pixelFormat:glpixelformat_obj]; - _SAPP_OBJC_RELEASE(glpixelformat_obj); - [_sapp.macos.view updateTrackingAreas]; - if (_sapp.desc.high_dpi) { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; - } - else { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; - } - - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - - NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 - target:_sapp.macos.view - selector:@selector(timerFired:) - userInfo:nil - repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; - timer_obj = nil; - #endif - [_sapp.macos.window center]; - _sapp.valid = true; - if (_sapp.fullscreen) { - /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ - [_sapp.macos.window toggleFullScreen:self]; - } - NSApp.activationPolicy = NSApplicationActivationPolicyRegular; - [NSApp activateIgnoringOtherApps:YES]; - [_sapp.macos.window makeKeyAndOrderFront:nil]; - _sapp_macos_update_dimensions(); - [NSEvent setMouseCoalescingEnabled:NO]; -} - -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { - _SOKOL_UNUSED(sender); - return YES; -} - -- (void)applicationWillTerminate:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_call_cleanup(); - _sapp_macos_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_macos_window_delegate -- (BOOL)windowShouldClose:(id)sender { - _SOKOL_UNUSED(sender); - /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a chance to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - return YES; - } - else { - return NO; - } -} - -- (void)windowDidResize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_update_dimensions(); -} - -- (void)windowDidChangeScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_timing_reset(&_sapp.timing); - _sapp_macos_update_dimensions(); -} - -- (void)windowDidMiniaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); -} - -- (void)windowDidDeminiaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _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; -} - -- (void)windowDidExitFullScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp.fullscreen = false; -} -@end - -@implementation _sapp_macos_window -- (instancetype)initWithContentRect:(NSRect)contentRect - styleMask:(NSWindowStyleMask)style - backing:(NSBackingStoreType)backingStoreType - defer:(BOOL)flag { - if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; - #endif - } - return self; -} - -- (NSDragOperation)draggingEntered:(id)sender { - return NSDragOperationCopy; -} - -- (NSDragOperation)draggingUpdated:(id)sender { - return NSDragOperationCopy; -} - -- (BOOL)performDragOperation:(id)sender { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - NSPasteboard *pboard = [sender draggingPasteboard]; - if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; - bool drop_failed = false; - for (int i = 0; i < _sapp.drop.num_files; i++) { - NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; - if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - drop_failed = true; - break; - } - } - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } - return YES; - } - #endif - return NO; -} -@end - -@implementation _sapp_macos_view -#if defined(SOKOL_GLCORE33) -/* NOTE: this is a hack/fix when the initial window size has been clipped by - macOS because it didn't fit on the screen, in that case the - frame size of the window is reported wrong if low-dpi rendering - was requested (instead the high-dpi dimensions are returned) - until the window is resized for the first time. - - Hooking into reshape and getting the frame dimensions seems to report - the correct dimensions. -*/ -- (void)reshape { - _sapp_macos_update_dimensions(); - [super reshape]; -} -- (void)timerFired:(id)sender { - _SOKOL_UNUSED(sender); - [self setNeedsDisplay:YES]; -} -- (void)prepareOpenGL { - [super prepareOpenGL]; - GLint swapInt = 1; - NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; - [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; - [ctx makeCurrentContext]; -} -#endif - -_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - /* - - NOTE: late event polling temporarily out-commented to check if this - causes infrequent and almost impossible to reproduce problems with the - window close events, see: - https://github.com/floooh/sokol/pull/483#issuecomment-805148815 - - - const NSEventMask mask = NSEventMaskLeftMouseDown | - NSEventMaskLeftMouseUp| - NSEventMaskRightMouseDown | - NSEventMaskRightMouseUp | - NSEventMaskMouseMoved | - NSEventMaskLeftMouseDragged | - NSEventMaskRightMouseDragged | - NSEventMaskMouseEntered | - NSEventMaskMouseExited | - NSEventMaskKeyDown | - NSEventMaskKeyUp | - NSEventMaskCursorUpdate | - NSEventMaskScrollWheel | - NSEventMaskTabletPoint | - NSEventMaskTabletProximity | - NSEventMaskOtherMouseDown | - NSEventMaskOtherMouseUp | - NSEventMaskOtherMouseDragged | - NSEventMaskPressure | - NSEventMaskDirectTouch; - @autoreleasepool { - for (;;) { - // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, - // see: https://github.com/floooh/sokol/issues/486 - NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; - if (event == nil) { - break; - } - [NSApp sendEvent:event]; - } - } - */ -} - -- (void)drawRect:(NSRect)rect { - _SOKOL_UNUSED(rect); - _sapp_timing_measure(&_sapp.timing); - /* Catch any last-moment input events */ - _sapp_macos_poll_input_events(); - @autoreleasepool { - _sapp_macos_frame(); - } - #if !defined(SOKOL_METAL) - [[_sapp.macos.view openGLContext] flushBuffer]; - #endif -} - -- (BOOL)isOpaque { - return YES; -} -- (BOOL)canBecomeKeyView { - return YES; -} -- (BOOL)acceptsFirstResponder { - return YES; -} -- (void)updateTrackingAreas { - if (_sapp.macos.tracking_area != nil) { - [self removeTrackingArea:_sapp.macos.tracking_area]; - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - } - const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingEnabledDuringMouseDrag | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect | - NSTrackingAssumeInside; - _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - [self addTrackingArea:_sapp.macos.tracking_area]; - [super updateTrackingAreas]; -} -- (void)mouseEntered:(NSEvent*)event { - _sapp_macos_mouse_update(event); - /* don't send mouse enter/leave while dragging (so that it behaves the same as - 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_mods(event)); - } -} -- (void)mouseExited:(NSEvent*)event { - _sapp_macos_mouse_update(event); - if (0 == _sapp.macos.mouse_buttons) { - _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); - } -} -- (void)mouseDown:(NSEvent*)event { - _sapp_macos_mouse_update(event); - _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_mods(event); - _sapp.event.scroll_x = dx; - _sapp.event.scroll_y = dy; - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyDown:(NSEvent*)event { - if (_sapp_events_enabled()) { - const uint32_t mods = _sapp_macos_mods(event); - const sapp_keycode key_code = _sapp_translate_key(event.keyCode); - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); - const NSString* chars = event.characters; - const NSUInteger len = chars.length; - if (len > 0) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = mods; - for (NSUInteger i = 0; i < len; i++) { - const unichar codepoint = [chars characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) { - continue; - } - _sapp.event.char_code = codepoint; - _sapp.event.key_repeat = event.isARepeat; - _sapp_call_event(&_sapp.event); - } - } - /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ - if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyUp:(NSEvent*)event { - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, - _sapp_translate_key(event.keyCode), - event.isARepeat, - _sapp_macos_mods(event)); -} -- (void)flagsChanged:(NSEvent*)event { - const uint32_t old_f = _sapp.macos.flags_changed_store; - const uint32_t new_f = (uint32_t)event.modifierFlags; - _sapp.macos.flags_changed_store = new_f; - sapp_keycode key_code = SAPP_KEYCODE_INVALID; - bool down = false; - if ((new_f ^ old_f) & NSEventModifierFlagShift) { - key_code = SAPP_KEYCODE_LEFT_SHIFT; - down = 0 != (new_f & NSEventModifierFlagShift); - } - if ((new_f ^ old_f) & NSEventModifierFlagControl) { - key_code = SAPP_KEYCODE_LEFT_CONTROL; - down = 0 != (new_f & NSEventModifierFlagControl); - } - if ((new_f ^ old_f) & NSEventModifierFlagOption) { - key_code = SAPP_KEYCODE_LEFT_ALT; - down = 0 != (new_f & NSEventModifierFlagOption); - } - if ((new_f ^ old_f) & NSEventModifierFlagCommand) { - key_code = SAPP_KEYCODE_LEFT_SUPER; - down = 0 != (new_f & NSEventModifierFlagCommand); - } - if (key_code != SAPP_KEYCODE_INVALID) { - _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, - key_code, - false, - _sapp_macos_mods(event)); - } -} -@end - -#endif // macOS - -// ██ ██████ ███████ -// ██ ██ ██ ██ -// ██ ██ ██ ███████ -// ██ ██ ██ ██ -// ██ ██████ ███████ -// -// >>ios -#if defined(_SAPP_IOS) - -_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); - _SAPP_OBJC_RELEASE(_sapp.ios.textfield); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); - #else - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); - #endif - _SAPP_OBJC_RELEASE(_sapp.ios.view); - _SAPP_OBJC_RELEASE(_sapp.ios.window); -} - -_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { - _sapp_init_state(desc); - static int argc = 1; - static char* argv[] = { (char*)"sokol_app" }; - UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); -} - -/* iOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_ios_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - NSEnumerator* enumerator = event.allTouches.objectEnumerator; - UITouch* ios_touch; - while ((ios_touch = [enumerator nextObject])) { - if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { - CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; - sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; - cur_point->identifier = (uintptr_t) ios_touch; - cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; - cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; - cur_point->changed = [touches containsObject:ios_touch]; - } - } - if (_sapp.event.num_touches > 0) { - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); - _sapp.window_width = (int)roundf(screen_rect.size.width); - _sapp.window_height = (int)roundf(screen_rect.size.height); - int cur_fb_width, cur_fb_height; - #if defined(SOKOL_METAL) - const CGSize fb_size = _sapp.ios.view.drawableSize; - cur_fb_width = (int)roundf(fb_size.width); - cur_fb_height = (int)roundf(fb_size.height); - #else - cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); - cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); - #endif - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - if (dim_changed) { - #if defined(SOKOL_METAL) - const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.ios.view.drawableSize = drawable_size; - #else - // nothing to do here, GLKView correctly respects the view's contentScaleFactor - #endif - if (!_sapp.first_frame) { - _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_ios_frame(void) { - _sapp_ios_update_dimensions(); - _sapp_frame(); -} - -_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { - /* if not happened yet, create an invisible text field */ - if (nil == _sapp.ios.textfield) { - _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; - _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; - _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; - _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; - _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; - _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; - _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; - _sapp.ios.textfield.hidden = YES; - _sapp.ios.textfield.text = @"x"; - _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; - [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; - - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWasShown:) - name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWillBeHidden:) - name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardDidChangeFrame:) - name:UIKeyboardDidChangeFrameNotification object:nil]; - } - if (shown) { - /* setting the text field as first responder brings up the onscreen keyboard */ - [_sapp.ios.textfield becomeFirstResponder]; - } - else { - [_sapp.ios.textfield resignFirstResponder]; - } -} - -@implementation _sapp_app_delegate -- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; - _sapp.window_width = (int)roundf(screen_rect.size.width); - _sapp.window_height = (int)roundf(screen_rect.size.height); - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; - } - else { - _sapp.dpi_scale = 1.0f; - } - _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); - NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; - #if defined(SOKOL_METAL) - _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.ios.view = [[_sapp_ios_view alloc] init]; - _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.ios.view.device = _sapp.ios.mtl_device; - _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; - /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor - and automatically renders at Retina resolution. We'll disable - autoResize and instead do the resizing in _sapp_ios_update_dimensions() - */ - _sapp.ios.view.autoResizeDrawable = false; - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - _sapp.ios.view_ctrl = [[UIViewController alloc] init]; - _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #else - if (_sapp.desc.gl_force_gles2) { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - _sapp.gles2_fallback = true; - } - else { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; - if (_sapp.ios.eagl_ctx == nil) { - _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - _sapp.gles2_fallback = true; - } - } - _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; - _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; - _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; - _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; - GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; - _sapp.ios.view.drawableMultisample = msaa; - _sapp.ios.view.context = _sapp.ios.eagl_ctx; - _sapp.ios.view.enableSetNeedsDisplay = NO; - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - // on GLKView, contentScaleFactor appears to work just fine! - if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; - } - else { - _sapp.ios.view.contentScaleFactor = 1.0; - } - _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #endif - [_sapp.ios.window makeKeyAndVisible]; - - _sapp.valid = true; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - if (!_sapp.ios.suspended) { - _sapp.ios.suspended = true; - _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); - } -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - if (_sapp.ios.suspended) { - _sapp.ios.suspended = false; - _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); - } -} - -/* NOTE: this method will rarely ever be called, iOS application - which are terminated by the user are usually killed via signal 9 - by the operating system. -*/ -- (void)applicationWillTerminate:(UIApplication *)application { - _SOKOL_UNUSED(application); - _sapp_call_cleanup(); - _sapp_ios_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_textfield_dlg -- (void)keyboardWasShown:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = true; - /* query the keyboard's size, and modify the content view's size */ - if (_sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (void)keyboardWillBeHidden:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = false; - if (_sapp.desc.ios_keyboard_resizes_canvas) { - _sapp.ios.view.frame = UIScreen.mainScreen.bounds; - } -} -- (void)keyboardDidChangeFrame:(NSNotification*)notif { - /* this is for the case when the screen rotation changes while the keyboard is open */ - if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { - if (_sapp_events_enabled()) { - const NSUInteger len = string.length; - if (len > 0) { - for (NSUInteger i = 0; i < len; i++) { - unichar c = [string characterAtIndex:i]; - if (c >= 32) { - /* ignore surrogates for now */ - if ((c < 0xD800) || (c > 0xDFFF)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = c; - _sapp_call_event(&_sapp.event); - } - } - if (c <= 32) { - sapp_keycode k = SAPP_KEYCODE_INVALID; - switch (c) { - case 10: k = SAPP_KEYCODE_ENTER; break; - case 32: k = SAPP_KEYCODE_SPACE; break; - default: break; - } - if (k != SAPP_KEYCODE_INVALID) { - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - } - } - } - } - else { - /* this was a backspace */ - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - } - } - return NO; -} -@end - -@implementation _sapp_ios_view -- (void)drawRect:(CGRect)rect { - _SOKOL_UNUSED(rect); - _sapp_timing_measure(&_sapp.timing); - @autoreleasepool { - _sapp_ios_frame(); - } -} -- (BOOL)isOpaque { - return YES; -} -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); -} -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); -} -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); -} -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); -} -@end -#endif /* TARGET_OS_IPHONE */ - -#endif /* _SAPP_APPLE */ - -// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ -// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ -// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ -// -// >>emscripten -#if defined(_SAPP_EMSCRIPTEN) - -#if defined(EM_JS_DEPS) -EM_JS_DEPS(sokol_app, "$withStackSave,$allocateUTF8OnStack"); -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); - -/* this function is called from a JS event handler when the user hides - the onscreen keyboard pressing the 'dismiss keyboard key' -*/ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { - _sapp.onscreen_keyboard_shown = false; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { - if (_sapp.clipboard.enabled) { - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ -EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { - return _sapp.html5_ask_leave_site ? 1 : 0; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { - if (!_sapp.drop.enabled) { - return; - } - if (num < 0) { - num = 0; - } - if (num > _sapp.drop.max_files) { - num = _sapp.drop.max_files; - } - _sapp.drop.num_files = num; - _sapp_clear_drop_buffer(); -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { - /* NOTE: name is only the filename part, not a path */ - if (!_sapp.drop.enabled) { - return; - } - if (0 == name) { - return; - } - SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); - if ((i < 0) || (i >= _sapp.drop.num_files)) { - return; - } - if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - _sapp.drop.num_files = 0; - } -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { - if (!_sapp.drop.enabled) { - return; - } - if (0 == _sapp.drop.num_files) { - /* there was an error copying the filenames */ - _sapp_clear_drop_buffer(); - return; - - } - if (_sapp_events_enabled()) { - _sapp.mouse.x = (float)x * _sapp.dpi_scale; - _sapp.mouse.y = (float)y * _sapp.dpi_scale; - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { - sapp_html5_fetch_response response; - _sapp_clear(&response, sizeof(response)); - response.succeeded = (0 != success); - response.error_code = (sapp_html5_fetch_error) error_code; - response.file_index = index; - response.data.ptr = buf_ptr; - response.data.size = fetched_size; - response.buffer.ptr = buf_ptr; - response.buffer.size = buf_size; - response.user_data = user_data; - callback(&response); -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* Javascript helper functions for mobile virtual keyboard input */ -EM_JS(void, sapp_js_create_textfield, (void), { - const _sapp_inp = document.createElement("input"); - _sapp_inp.type = "text"; - _sapp_inp.id = "_sokol_app_input_element"; - _sapp_inp.autocapitalize = "none"; - _sapp_inp.addEventListener("focusout", function(_sapp_event) { - __sapp_emsc_notify_keyboard_hidden() - - }); - document.body.append(_sapp_inp); -}); - -EM_JS(void, sapp_js_focus_textfield, (void), { - document.getElementById("_sokol_app_input_element").focus(); -}); - -EM_JS(void, sapp_js_unfocus_textfield, (void), { - document.getElementById("_sokol_app_input_element").blur(); -}); - -EM_JS(void, sapp_js_add_beforeunload_listener, (void), { - Module.sokol_beforeunload = (event) => { - if (__sapp_html5_get_ask_leave_site() != 0) { - event.preventDefault(); - event.returnValue = ' '; - } - }; - window.addEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { - window.removeEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_add_clipboard_listener, (void), { - Module.sokol_paste = (event) => { - const pasted_str = event.clipboardData.getData('text'); - withStackSave(() => { - const cstr = allocateUTF8OnStack(pasted_str); - __sapp_emsc_onpaste(cstr); - }); - }; - window.addEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_remove_clipboard_listener, (void), { - window.removeEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { - const str = UTF8ToString(c_str); - const ta = document.createElement('textarea'); - ta.setAttribute('autocomplete', 'off'); - ta.setAttribute('autocorrect', 'off'); - ta.setAttribute('autocapitalize', 'off'); - ta.setAttribute('spellcheck', 'false'); - ta.style.left = -100 + 'px'; - ta.style.top = -100 + 'px'; - ta.style.height = 1; - ta.style.width = 1; - ta.value = str; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); -}); - -_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { - sapp_js_write_clipboard(str); -} - -EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { - Module.sokol_drop_files = []; - const canvas_name = UTF8ToString(canvas_name_cstr); - const canvas = document.getElementById(canvas_name); - Module.sokol_dragenter = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragleave = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragover = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_drop = (event) => { - event.stopPropagation(); - event.preventDefault(); - const files = event.dataTransfer.files; - Module.sokol_dropped_files = files; - __sapp_emsc_begin_drop(files.length); - for (let i = 0; i < files.length; i++) { - withStackSave(() => { - const cstr = allocateUTF8OnStack(files[i].name); - __sapp_emsc_drop(i, cstr); - }); - } - // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect - __sapp_emsc_end_drop(event.clientX, event.clientY); - }; - canvas.addEventListener('dragenter', Module.sokol_dragenter, false); - canvas.addEventListener('dragleave', Module.sokol_dragleave, false); - canvas.addEventListener('dragover', Module.sokol_dragover, false); - canvas.addEventListener('drop', Module.sokol_drop, false); -}); - -EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { - \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F - const files = Module.sokol_dropped_files; - if ((index < 0) || (index >= files.length)) { - return 0; - } - else { - return files[index].size; - } -}); - -EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { - const reader = new FileReader(); - reader.onload = (loadEvent) => { - const content = loadEvent.target.result; - if (content.byteLength > buf_size) { - // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL - __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); - } - else { - HEAPU8.set(new Uint8Array(content), buf_ptr); - __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); - } - }; - reader.onerror = () => { - // SAPP_HTML5_FETCH_ERROR_OTHER - __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); - }; - \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F - const files = Module.sokol_dropped_files; - reader.readAsArrayBuffer(files[index]); -}); - -EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { - const canvas_name = UTF8ToString(canvas_name_cstr); - const canvas = document.getElementById(canvas_name); - canvas.removeEventListener('dragenter', Module.sokol_dragenter); - canvas.removeEventListener('dragleave', Module.sokol_dragleave); - canvas.removeEventListener('dragover', Module.sokol_dragover); - canvas.removeEventListener('drop', Module.sokol_drop); -}); - -/* called from the emscripten event handler to update the keyboard visibility - state, this must happen from an JS input event handler, otherwise - the request will be ignored by the browser -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { - if (_sapp.emsc.wants_show_keyboard) { - /* create input text field on demand */ - if (!_sapp.emsc.textfield_created) { - _sapp.emsc.textfield_created = true; - sapp_js_create_textfield(); - } - /* focus the text input field, this will bring up the keyboard */ - _sapp.onscreen_keyboard_shown = true; - _sapp.emsc.wants_show_keyboard = false; - sapp_js_focus_textfield(); - } - if (_sapp.emsc.wants_hide_keyboard) { - /* unfocus the text input field */ - if (_sapp.emsc.textfield_created) { - _sapp.onscreen_keyboard_shown = false; - _sapp.emsc.wants_hide_keyboard = false; - sapp_js_unfocus_textfield(); - } - } -} - -/* actually showing the onscreen keyboard must be initiated from a JS - input event handler, so we'll just keep track of the desired - state, and the actual state change will happen with the next input event -*/ -_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { - if (show) { - _sapp.emsc.wants_show_keyboard = true; - } - else { - _sapp.emsc.wants_hide_keyboard = true; - } -} - -EM_JS(void, sapp_js_init, (const char* c_str_target), { - // lookup and store canvas object by name - const target_str = UTF8ToString(c_str_target); - Module.sapp_emsc_target = document.getElementById(target_str); - if (!Module.sapp_emsc_target) { - console.log("sokol_app.h: invalid target:" + target_str); - } - if (!Module.sapp_emsc_target.requestPointerLock) { - console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); - } -}); - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = emsc_event->isActive; - return EM_TRUE; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = false; - _sapp.emsc.mouse_lock_requested = false; - return true; -} - -EM_JS(void, sapp_js_request_pointerlock, (void), { - if (Module.sapp_emsc_target) { - if (Module.sapp_emsc_target.requestPointerLock) { - Module.sapp_emsc_target.requestPointerLock(); - } - } -}); - -EM_JS(void, sapp_js_exit_pointerlock, (void), { - if (document.exitPointerLock) { - document.exitPointerLock(); - } -}); - -_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { - if (lock) { - /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ - _sapp.emsc.mouse_lock_requested = true; - } - else { - /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ - _sapp.emsc.mouse_lock_requested = false; - sapp_js_exit_pointerlock(); - } -} - -/* called from inside event handlers to check if mouse lock had been requested, - and if yes, actually enter mouse lock. -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { - if (_sapp.emsc.mouse_lock_requested) { - _sapp.emsc.mouse_lock_requested = false; - sapp_js_request_pointerlock(); - } -} - -// set mouse cursor type -EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { - if (Module.sapp_emsc_target) { - let cursor; - if (shown === 0) { - cursor = "none"; - } - else switch (cursor_type) { - case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT - case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW - case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM - case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR - case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND - case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW - case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS - case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE - case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW - case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL - case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED - default: cursor = "auto"; break; - } - Module.sapp_emsc_target.style.cursor = cursor; - } -}); - -_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - sapp_js_set_cursor((int)cursor, shown ? 1 : 0); -} - -/* JS helper functions to update browser tab favicon */ -EM_JS(void, sapp_js_clear_favicon, (void), { - const 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), { - const canvas = document.createElement('canvas'); - canvas.width = w; - canvas.height = h; - const ctx = canvas.getContext('2d'); - const img_data = ctx.createImageData(w, h); - img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); - ctx.putImageData(img_data, 0, 0); - const 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); - double w, h; - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - /* The above method might report zero when toggling HTML5 fullscreen, - in that case use the window's inner width reported by the - emscripten event. This works ok when toggling *into* fullscreen - but doesn't properly restore the previous canvas size when switching - back from fullscreen. - - In general, due to the HTML5's fullscreen API's flaky nature it is - recommended to use 'soft fullscreen' (stretching the WebGL canvas - over the browser windows client rect) with a CSS definition like this: - - position: absolute; - top: 0px; - left: 0px; - margin: 0px; - border: 0; - width: 100%; - height: 100%; - overflow: hidden; - display: block; - */ - if (w < 1.0) { - w = ui_event->windowInnerWidth; - } - else { - _sapp.window_width = (int)roundf(w); - } - if (h < 1.0) { - h = ui_event->windowInnerHeight; - } - else { - _sapp.window_height = (int)roundf(h); - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); - SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_WGPU) - /* on WebGPU: recreate size-dependent rendering surfaces */ - _sapp_emsc_wgpu_surfaces_discard(); - _sapp_emsc_wgpu_surfaces_create(); - #endif - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_RESIZED); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_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; - } - else { - float new_x = emsc_event->targetX * _sapp.dpi_scale; - float new_y = emsc_event->targetY * _sapp.dpi_scale; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } - if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { - sapp_event_type type; - bool is_button_event = false; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_MOUSEDOWN: - type = SAPP_EVENTTYPE_MOUSE_DOWN; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEUP: - type = SAPP_EVENTTYPE_MOUSE_UP; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEMOVE: - type = SAPP_EVENTTYPE_MOUSE_MOVE; - break; - case EMSCRIPTEN_EVENT_MOUSEENTER: - type = SAPP_EVENTTYPE_MOUSE_ENTER; - break; - case EMSCRIPTEN_EVENT_MOUSELEAVE: - type = SAPP_EVENTTYPE_MOUSE_LEAVE; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - _sapp_init_event(type); - _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; - case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; - case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; - default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; - } - } - else { - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - } - _sapp_call_event(&_sapp.event); - } - /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */ - if (is_button_event) { - _sapp_emsc_update_mouse_lock_state(); - } - } - _sapp_emsc_update_keyboard_state(); - return true; -} - -_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); - _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) { - case DOM_DELTA_PIXEL: scale = -0.04f; break; - case DOM_DELTA_LINE: scale = -1.33f; break; - case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess - default: scale = -0.1f; break; // shouldn't happen - } - _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; - _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; - _sapp_call_event(&_sapp.event); - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - 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; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_KEYDOWN: - type = SAPP_EVENTTYPE_KEY_DOWN; - break; - case EMSCRIPTEN_EVENT_KEYUP: - type = SAPP_EVENTTYPE_KEY_UP; - break; - case EMSCRIPTEN_EVENT_KEYPRESS: - type = SAPP_EVENTTYPE_CHAR; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - bool send_keyup_followup = false; - _sapp_init_event(type); - _sapp.event.key_repeat = emsc_event->repeat; - _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); - if (type == SAPP_EVENTTYPE_CHAR) { - // FIXME: this doesn't appear to work on Android Chrome - _sapp.event.char_code = emsc_event->charCode; - /* workaround to make Cmd+V work on Safari */ - if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { - retval = false; - } - } - else { - if (0 != emsc_event->code[0]) { - // This code path is for desktop browsers which send untranslated 'physical' key code strings - // (which is what we actually want for key events) - _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); - } else { - // This code path is for mobile browsers which only send localized key code - // strings. Note that the translation will only work for a small subset - // of localization-agnostic keys (like Enter, arrow keys, etc...), but - // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) - _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); - } - - /* 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 - when the SUPER key is pressed - */ - if ((type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && - (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && - (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) - { - send_keyup_followup = true; - } - // only forward keys to the browser (can further be suppressed by sapp_consume_event()) - switch (_sapp.event.key_code) { - case SAPP_KEYCODE_WORLD_1: - case SAPP_KEYCODE_WORLD_2: - case SAPP_KEYCODE_ESCAPE: - case SAPP_KEYCODE_ENTER: - case SAPP_KEYCODE_TAB: - case SAPP_KEYCODE_BACKSPACE: - case SAPP_KEYCODE_INSERT: - case SAPP_KEYCODE_DELETE: - case SAPP_KEYCODE_RIGHT: - case SAPP_KEYCODE_LEFT: - case SAPP_KEYCODE_DOWN: - case SAPP_KEYCODE_UP: - case SAPP_KEYCODE_PAGE_UP: - case SAPP_KEYCODE_PAGE_DOWN: - case SAPP_KEYCODE_HOME: - case SAPP_KEYCODE_END: - case SAPP_KEYCODE_CAPS_LOCK: - case SAPP_KEYCODE_SCROLL_LOCK: - case SAPP_KEYCODE_NUM_LOCK: - case SAPP_KEYCODE_PRINT_SCREEN: - case SAPP_KEYCODE_PAUSE: - case SAPP_KEYCODE_F1: - case SAPP_KEYCODE_F2: - case SAPP_KEYCODE_F3: - case SAPP_KEYCODE_F4: - case SAPP_KEYCODE_F5: - case SAPP_KEYCODE_F6: - case SAPP_KEYCODE_F7: - case SAPP_KEYCODE_F8: - case SAPP_KEYCODE_F9: - case SAPP_KEYCODE_F10: - case SAPP_KEYCODE_F11: - case SAPP_KEYCODE_F12: - case SAPP_KEYCODE_F13: - case SAPP_KEYCODE_F14: - case SAPP_KEYCODE_F15: - case SAPP_KEYCODE_F16: - case SAPP_KEYCODE_F17: - case SAPP_KEYCODE_F18: - case SAPP_KEYCODE_F19: - case SAPP_KEYCODE_F20: - case SAPP_KEYCODE_F21: - case SAPP_KEYCODE_F22: - case SAPP_KEYCODE_F23: - case SAPP_KEYCODE_F24: - case SAPP_KEYCODE_F25: - case SAPP_KEYCODE_LEFT_SHIFT: - case SAPP_KEYCODE_LEFT_CONTROL: - case SAPP_KEYCODE_LEFT_ALT: - case SAPP_KEYCODE_LEFT_SUPER: - case SAPP_KEYCODE_RIGHT_SHIFT: - case SAPP_KEYCODE_RIGHT_CONTROL: - case SAPP_KEYCODE_RIGHT_ALT: - case SAPP_KEYCODE_RIGHT_SUPER: - case SAPP_KEYCODE_MENU: - /* consume the event */ - break; - default: - /* forward key to browser */ - retval = false; - break; - } - } - if (_sapp_call_event(&_sapp.event)) { - // event was consumed via sapp_consume_event() - retval = true; - } - if (send_keyup_followup) { - _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; - if (_sapp_call_event(&_sapp.event)) { - retval = true; - } - } - } - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - return retval; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - bool retval = true; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_TOUCHSTART: - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case EMSCRIPTEN_EVENT_TOUCHMOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case EMSCRIPTEN_EVENT_TOUCHEND: - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case EMSCRIPTEN_EVENT_TOUCHCANCEL: - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - retval = false; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - _sapp_init_event(type); - _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; - } - for (int i = 0; i < _sapp.event.num_touches; i++) { - const EmscriptenTouchPoint* src = &emsc_event->touches[i]; - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)src->identifier; - dst->pos_x = src->targetX * _sapp.dpi_scale; - dst->pos_y = src->targetY * _sapp.dpi_scale; - dst->changed = src->isChanged; - } - _sapp_call_event(&_sapp.event); - } - } - _sapp_emsc_update_keyboard_state(); - return retval; -} - -_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) -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; - case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; - default: type = SAPP_EVENTTYPE_INVALID; break; - } - if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { - EmscriptenWebGLContextAttributes attrs; - emscripten_webgl_init_context_attributes(&attrs); - attrs.alpha = _sapp.desc.alpha; - attrs.depth = true; - attrs.stencil = true; - attrs.antialias = _sapp.sample_count > 1; - attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; - attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; - attrs.enableExtensionsByDefault = true; - #if defined(SOKOL_GLES3) - if (_sapp.desc.gl_force_gles2) { - attrs.majorVersion = 1; - _sapp.gles2_fallback = true; - } - else { - attrs.majorVersion = 2; - } - #endif - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); - if (!ctx) { - attrs.majorVersion = 1; - ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); - _sapp.gles2_fallback = true; - } - emscripten_webgl_make_context_current(ctx); - - /* some WebGL extension are not enabled automatically by emscripten */ - emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); -} -#endif - -#if defined(SOKOL_WGPU) -#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) -#define _SAPP_EMSC_WGPU_STATE_READY (1) -#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) - -#if defined(__cplusplus) -extern "C" { -#endif -/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); - _sapp.emsc.wgpu.device = (WGPUDevice) device_id; - _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; - _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; -} -#if defined(__cplusplus) -} // extern "C" -#endif - -/* embedded JS function to handle all the asynchronous WebGPU setup */ -EM_JS(void, sapp_js_wgpu_init, (), { - WebGPU.initManagers(); - // FIXME: the extension activation must be more clever here - navigator.gpu.requestAdapter().then((adapter) => { - console.log("wgpu adapter extensions: " + adapter.extensions); - adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then((device) => { - var gpuContext = document.getElementById("canvas").getContext("gpupresent"); - console.log("wgpu device extensions: " + adapter.extensions); - gpuContext.getSwapChainPreferredFormat(device).then((fmt) => { - const swapChainDescriptor = { device: device, format: fmt }; - const swapChain = gpuContext.configureSwapChain(swapChainDescriptor); - const deviceId = WebGPU.mgrDevice.create(device); - const swapChainId = WebGPU.mgrSwapChain.create(swapChain); - const fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); - console.log("wgpu device: " + device); - console.log("wgpu swap chain: " + swapChain); - console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); - __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); - }); - }); - }); -}); - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { - SOKOL_ASSERT(_sapp.emsc.wgpu.device); - SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); - - WGPUTextureDescriptor ds_desc; - _sapp_clear(&ds_desc, sizeof(ds_desc)); - ds_desc.usage = WGPUTextureUsage_OutputAttachment; - ds_desc.dimension = WGPUTextureDimension_2D; - ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; - ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; - ds_desc.size.depth = 1; - ds_desc.arrayLayerCount = 1; - ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; - ds_desc.mipLevelCount = 1; - ds_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); - _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); - - if (_sapp.sample_count > 1) { - WGPUTextureDescriptor msaa_desc; - _sapp_clear(&msaa_desc, sizeof(msaa_desc)); - msaa_desc.usage = WGPUTextureUsage_OutputAttachment; - msaa_desc.dimension = WGPUTextureDimension_2D; - msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; - msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; - msaa_desc.size.depth = 1; - msaa_desc.arrayLayerCount = 1; - msaa_desc.format = _sapp.emsc.wgpu.render_format; - msaa_desc.mipLevelCount = 1; - msaa_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); - _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { - if (_sapp.emsc.wgpu.msaa_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); - _sapp.emsc.wgpu.msaa_tex = 0; - } - if (_sapp.emsc.wgpu.msaa_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); - _sapp.emsc.wgpu.msaa_view = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); - _sapp.emsc.wgpu.depth_stencil_tex = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); - _sapp.emsc.wgpu.depth_stencil_view = 0; - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { - if (_sapp.emsc.wgpu.swapchain_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); - } - _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); -} -#endif - -_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - 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(); - } - if (_sapp.drop.enabled) { - sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - #endif -} - -_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); - 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(); - } - if (_sapp.drop.enabled) { - sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); - #endif -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { - _SOKOL_UNUSED(userData); - _sapp_timing_external(&_sapp.timing, time / 1000.0); - - #if defined(SOKOL_WGPU) - /* - on WebGPU, the emscripten frame callback will already be called while - the asynchronous WebGPU device and swapchain initialization is still - in progress - */ - switch (_sapp.emsc.wgpu.state) { - case _SAPP_EMSC_WGPU_STATE_INITIAL: - /* async JS init hasn't finished yet */ - break; - case _SAPP_EMSC_WGPU_STATE_READY: - /* perform post-async init stuff */ - _sapp_emsc_wgpu_surfaces_create(); - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; - break; - case _SAPP_EMSC_WGPU_STATE_RUNNING: - /* a regular frame */ - _sapp_emsc_wgpu_next_frame(); - _sapp_frame(); - break; - } - #else - /* WebGL code path */ - _sapp_frame(); - #endif - - /* quit-handling */ - if (_sapp.quit_requested) { - _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - _sapp_call_event(&_sapp.event); - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - _sapp_emsc_unregister_eventhandlers(); - _sapp_call_cleanup(); - _sapp_discard_state(); - return EM_FALSE; - } - return EM_TRUE; -} - -_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { - _sapp_init_state(desc); - sapp_js_init(&_sapp.html5_canvas_selector[1]); - double w, h; - if (_sapp.desc.html5_canvas_resize) { - w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); - h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); - } - else { - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.window_width = (int)roundf(w); - _sapp.window_height = (int)roundf(h); - _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) - _sapp_emsc_webgl_init(); - #elif defined(SOKOL_WGPU) - sapp_js_wgpu_init(); - #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); - - /* NOT A BUG: do not call _sapp_discard_state() here, instead this is - called in _sapp_emsc_frame() when the application is ordered to quit - */ -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_emsc_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_EMSCRIPTEN */ - -// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ -// -// >>gl helpers -#if defined(SOKOL_GLCORE33) -typedef struct { - int red_bits; - int green_bits; - int blue_bits; - int alpha_bits; - int depth_bits; - int stencil_bits; - int samples; - bool doublebuffer; - uintptr_t handle; -} _sapp_gl_fbconfig; - -_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { - _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); - /* -1 means "don't care" */ - fbconfig->red_bits = -1; - fbconfig->green_bits = -1; - fbconfig->blue_bits = -1; - fbconfig->alpha_bits = -1; - fbconfig->depth_bits = -1; - fbconfig->stencil_bits = -1; - fbconfig->samples = -1; -} - -_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { - int missing, least_missing = 1000000; - int color_diff, least_color_diff = 10000000; - int extra_diff, least_extra_diff = 10000000; - const _sapp_gl_fbconfig* current; - const _sapp_gl_fbconfig* closest = 0; - for (int i = 0; i < count; i++) { - current = alternatives + i; - if (desired->doublebuffer != current->doublebuffer) { - continue; - } - missing = 0; - if (desired->alpha_bits > 0 && current->alpha_bits == 0) { - missing++; - } - if (desired->depth_bits > 0 && current->depth_bits == 0) { - missing++; - } - if (desired->stencil_bits > 0 && current->stencil_bits == 0) { - missing++; - } - if (desired->samples > 0 && current->samples == 0) { - /* Technically, several multisampling buffers could be - involved, but that's a lower level implementation detail and - not important to us here, so we count them as one - */ - missing++; - } - - /* These polynomials make many small channel size differences matter - less than one large channel size difference - Calculate color channel size difference value - */ - color_diff = 0; - if (desired->red_bits != -1) { - color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); - } - if (desired->green_bits != -1) { - color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); - } - if (desired->blue_bits != -1) { - color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); - } - - /* Calculate non-color channel size difference value */ - extra_diff = 0; - if (desired->alpha_bits != -1) { - extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); - } - if (desired->depth_bits != -1) { - extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); - } - if (desired->stencil_bits != -1) { - extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); - } - if (desired->samples != -1) { - extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); - } - - /* Figure out if the current one is better than the best one found so far - Least number of missing buffers is the most important heuristic, - then color buffer size match and lastly size match for other buffers - */ - if (missing < least_missing) { - closest = current; - } - else if (missing == least_missing) { - if ((color_diff < least_color_diff) || - (color_diff == least_color_diff && extra_diff < least_extra_diff)) - { - closest = current; - } - } - if (current == closest) { - least_missing = missing; - least_color_diff = color_diff; - least_extra_diff = extra_diff; - } - } - return closest; -} -#endif - -// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ -// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ -// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ -// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ -// -// >>windows -#if defined(_SAPP_WIN32) -_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - _sapp_clear(dst, (size_t)dst_num_bytes); - const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); - const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); - if ((dst_needed > 0) && (dst_needed < dst_chars)) { - MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); - return true; - } - else { - /* input string doesn't fit into destination buffer */ - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { - /* same as GLFW */ - _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; - _sapp.keycodes[0x002] = SAPP_KEYCODE_1; - _sapp.keycodes[0x003] = SAPP_KEYCODE_2; - _sapp.keycodes[0x004] = SAPP_KEYCODE_3; - _sapp.keycodes[0x005] = SAPP_KEYCODE_4; - _sapp.keycodes[0x006] = SAPP_KEYCODE_5; - _sapp.keycodes[0x007] = SAPP_KEYCODE_6; - _sapp.keycodes[0x008] = SAPP_KEYCODE_7; - _sapp.keycodes[0x009] = SAPP_KEYCODE_8; - _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; - _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; - _sapp.keycodes[0x030] = SAPP_KEYCODE_B; - _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; - _sapp.keycodes[0x020] = SAPP_KEYCODE_D; - _sapp.keycodes[0x012] = SAPP_KEYCODE_E; - _sapp.keycodes[0x021] = SAPP_KEYCODE_F; - _sapp.keycodes[0x022] = SAPP_KEYCODE_G; - _sapp.keycodes[0x023] = SAPP_KEYCODE_H; - _sapp.keycodes[0x017] = SAPP_KEYCODE_I; - _sapp.keycodes[0x024] = SAPP_KEYCODE_J; - _sapp.keycodes[0x025] = SAPP_KEYCODE_K; - _sapp.keycodes[0x026] = SAPP_KEYCODE_L; - _sapp.keycodes[0x032] = SAPP_KEYCODE_M; - _sapp.keycodes[0x031] = SAPP_KEYCODE_N; - _sapp.keycodes[0x018] = SAPP_KEYCODE_O; - _sapp.keycodes[0x019] = SAPP_KEYCODE_P; - _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x013] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; - _sapp.keycodes[0x014] = SAPP_KEYCODE_T; - _sapp.keycodes[0x016] = SAPP_KEYCODE_U; - _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; - _sapp.keycodes[0x011] = SAPP_KEYCODE_W; - _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; - _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; - _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; - _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; - _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; - _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; - _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; - _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; - _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; - _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; -} -#endif // _SAPP_WIN32 - -#if defined(_SAPP_WIN32) - -#if defined(SOKOL_D3D11) - -#if defined(__cplusplus) -#define _sapp_d3d11_Release(self) (self)->Release() -#define _sapp_win32_refiid(iid) iid -#else -#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) -#define _sapp_win32_refiid(iid) &iid -#endif - -#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 const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; -static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; - -static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { - #if defined(__cplusplus) - return self->GetBuffer(Buffer, riid, ppSurface); - #else - return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); - #endif -} - -static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { - #if defined(__cplusplus) - return self->QueryInterface(riid, ppvObject); - #else - return self->lpVtbl->QueryInterface(self, riid, ppvObject); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { - #if defined(__cplusplus) - return self->CreateRenderTargetView(pResource, pDesc, ppRTView); - #else - return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { - #if defined(__cplusplus) - return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); - #else - return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { - #if defined(__cplusplus) - return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); - #else - return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); - #endif -} - -static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { - #if defined(__cplusplus) - self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #else - self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #endif -} - -static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { - #if defined(__cplusplus) - return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); - #else - return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); - #endif -} - -static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { - #if defined(__cplusplus) - return self->Present(SyncInterval, Flags); - #else - return self->lpVtbl->Present(self, SyncInterval, Flags); - #endif -} - -static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { - #if defined(__cplusplus) - return self->GetFrameStatistics(pStats); - #else - return self->lpVtbl->GetFrameStatistics(self, pStats); - #endif -} - -static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { - #if defined(__cplusplus) - return self->SetMaximumFrameLatency(MaxLatency); - #else - return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); - #endif -} - -static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { - #if defined(__cplusplus) - return self->GetAdapter(pAdapter); - #else - return self->lpVtbl->GetAdapter(self, pAdapter); - #endif -} - -static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { - #if defined(__cplusplus) - return self->GetParent(riid, ppParent); - #else - return self->lpVtbl->GetParent(self, riid, ppParent); - #endif -} - -static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { - #if defined(__cplusplus) - return self->MakeWindowAssociation(WindowHandle, Flags); - #else - return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); - #endif -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { - DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; - sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; - sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; - sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - sc_desc->BufferDesc.RefreshRate.Numerator = 60; - sc_desc->BufferDesc.RefreshRate.Denominator = 1; - sc_desc->OutputWindow = _sapp.win32.hwnd; - sc_desc->Windowed = true; - if (_sapp.win32.is_win10_or_greater) { - sc_desc->BufferCount = 2; - sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; - _sapp.d3d11.use_dxgi_frame_stats = true; - } - else { - sc_desc->BufferCount = 1; - sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - _sapp.d3d11.use_dxgi_frame_stats = false; - } - sc_desc->SampleDesc.Count = 1; - sc_desc->SampleDesc.Quality = 0; - sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; - #if defined(SOKOL_DEBUG) - create_flags |= D3D11_CREATE_DEVICE_DEBUG; - #endif - D3D_FEATURE_LEVEL feature_level; - HRESULT hr = D3D11CreateDeviceAndSwapChain( - NULL, /* pAdapter (use default) */ - D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ - NULL, /* Software */ - create_flags, /* Flags */ - NULL, /* pFeatureLevels */ - 0, /* FeatureLevels */ - D3D11_SDK_VERSION, /* SDKVersion */ - sc_desc, /* pSwapChainDesc */ - &_sapp.d3d11.swap_chain, /* ppSwapChain */ - &_sapp.d3d11.device, /* ppDevice */ - &feature_level, /* pFeatureLevel */ - &_sapp.d3d11.device_context); /* ppImmediateContext */ - _SOKOL_UNUSED(hr); - #if defined(SOKOL_DEBUG) - if (!SUCCEEDED(hr)) { - // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the - // 'D3D11 debug layer' stopped working, indicated by the error message: - // === - // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. - // These flags must be removed, or the Windows 10 SDK must be installed. - // Flags include: D3D11_CREATE_DEVICE_DEBUG - // === - // - // ...just retry with the DEBUG flag switched off - _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); - create_flags &= ~D3D11_CREATE_DEVICE_DEBUG; - hr = D3D11CreateDeviceAndSwapChain( - NULL, /* pAdapter (use default) */ - D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ - NULL, /* Software */ - create_flags, /* Flags */ - NULL, /* pFeatureLevels */ - 0, /* FeatureLevels */ - D3D11_SDK_VERSION, /* SDKVersion */ - sc_desc, /* pSwapChainDesc */ - &_sapp.d3d11.swap_chain, /* ppSwapChain */ - &_sapp.d3d11.device, /* ppDevice */ - &feature_level, /* pFeatureLevel */ - &_sapp.d3d11.device_context); /* ppImmediateContext */ - } - #endif - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); - - // minimize frame latency, disable Alt-Enter - hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); - if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { - _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); - IDXGIAdapter* dxgi_adapter = 0; - hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); - if (SUCCEEDED(hr) && dxgi_adapter) { - IDXGIFactory* dxgi_factory = 0; - hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); - if (SUCCEEDED(hr)) { - _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); - _SAPP_SAFE_RELEASE(dxgi_factory); - } - else { - _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); - } - _SAPP_SAFE_RELEASE(dxgi_adapter); - } - else { - _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); - } - } - else { - _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); - _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device); -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { - SOKOL_ASSERT(0 == _sapp.d3d11.rt); - SOKOL_ASSERT(0 == _sapp.d3d11.rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.ds); - SOKOL_ASSERT(0 == _sapp.d3d11.dsv); - - HRESULT hr; - - /* view for the swapchain-created framebuffer */ - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); - hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); - - /* common desc for MSAA and depth-stencil texture */ - D3D11_TEXTURE2D_DESC tex_desc; - _sapp_clear(&tex_desc, sizeof(tex_desc)); - tex_desc.Width = (UINT)_sapp.framebuffer_width; - tex_desc.Height = (UINT)_sapp.framebuffer_height; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Usage = D3D11_USAGE_DEFAULT; - tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; - tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; - tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); - - /* create MSAA texture and view if antialiasing requested */ - if (_sapp.sample_count > 1) { - tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); - hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); - } - - /* texture and view for the depth-stencil-surface */ - tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); - hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); - _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { - if (_sapp.d3d11.swap_chain) { - _sapp_d3d11_destroy_default_render_target(); - _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); - _sapp_d3d11_create_default_render_target(); - } -} - -_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { - /* do MSAA resolve if needed */ - if (_sapp.sample_count > 1) { - SOKOL_ASSERT(_sapp.d3d11.rt); - SOKOL_ASSERT(_sapp.d3d11.msaa_rt); - _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); - } - UINT flags = 0; - if (_sapp.win32.is_win10_or_greater && do_not_wait) { - /* this hack/workaround somewhat improves window-movement and -sizing - responsiveness when rendering is controlled via WM_TIMER during window - move and resize on NVIDIA cards on Win10 with recent drivers. - */ - flags = DXGI_PRESENT_DO_NOT_WAIT; - } - _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); -} - -#endif /* SOKOL_D3D11 */ - -#if defined(SOKOL_GLCORE33) -_SOKOL_PRIVATE void _sapp_wgl_init(void) { - _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); - if (!_sapp.wgl.opengl32) { - _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); - } - SOKOL_ASSERT(_sapp.wgl.opengl32); - _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); - SOKOL_ASSERT(_sapp.wgl.CreateContext); - _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); - SOKOL_ASSERT(_sapp.wgl.DeleteContext); - _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); - SOKOL_ASSERT(_sapp.wgl.GetProcAddress); - _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); - SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); - _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); - SOKOL_ASSERT(_sapp.wgl.MakeCurrent); - - _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, - L"SOKOLAPP", - L"sokol-app message window", - WS_CLIPSIBLINGS|WS_CLIPCHILDREN, - 0, 0, 1, 1, - NULL, NULL, - GetModuleHandleW(NULL), - NULL); - if (!_sapp.wgl.msg_hwnd) { - _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); - } - SOKOL_ASSERT(_sapp.wgl.msg_hwnd); - ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); - MSG msg; - while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); - if (!_sapp.wgl.msg_dc) { - _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { - SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); - DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; - FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; -} - -_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext && extensions); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { - SOKOL_ASSERT(ext); - if (_sapp.wgl.GetExtensionsStringEXT) { - const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - if (_sapp.wgl.GetExtensionsStringARB) { - const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - return false; -} - -_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { - SOKOL_ASSERT(_sapp.wgl.msg_dc); - PIXELFORMATDESCRIPTOR pfd; - _sapp_clear(&pfd, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 24; - if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { - _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); - } - HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); - if (!rc) { - _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); - } - if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { - _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); - } - _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); - _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); - _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); - _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); - _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); - _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); - _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); - _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); - _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); - _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); - _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); - _sapp.wgl.DeleteContext(rc); -} - -_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - int value = 0; - if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { - _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); - } - return value; -} - -_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { - SOKOL_ASSERT(_sapp.win32.dc); - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - const _sapp_gl_fbconfig* closest; - - int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); - _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); - SOKOL_ASSERT(usable_configs); - int usable_count = 0; - for (int i = 0; i < native_count; i++) { - const int n = i + 1; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { - continue; - } - u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); - u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); - u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); - u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); - u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); - u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); - if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { - u->doublebuffer = true; - } - if (_sapp.wgl.arb_multisample) { - u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); - } - u->handle = (uintptr_t)n; - usable_count++; - } - SOKOL_ASSERT(usable_count > 0); - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - int pixel_format = 0; - if (closest) { - pixel_format = (int) closest->handle; - } - _sapp_free(usable_configs); - return pixel_format; -} - -_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { - int pixel_format = _sapp_wgl_find_pixel_format(); - if (0 == pixel_format) { - _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); - } - PIXELFORMATDESCRIPTOR pfd; - if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { - _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); - } - if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { - _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); - } - if (!_sapp.wgl.arb_create_context) { - _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); - } - if (!_sapp.wgl.arb_create_context_profile) { - _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); - } - const int attrs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, - WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, 0 - }; - _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); - if (!_sapp.wgl.gl_ctx) { - const DWORD err = GetLastError(); - if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { - _SAPP_PANIC(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED); - } - else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { - _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); - } - else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { - _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); - } - else { - _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); - } - } - _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); - if (_sapp.wgl.ext_swap_control) { - /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ - _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { - SOKOL_ASSERT(_sapp.wgl.gl_ctx); - _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); - _sapp.wgl.gl_ctx = 0; -} - -_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { - SOKOL_ASSERT(_sapp.win32.dc); - /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ - SwapBuffers(_sapp.win32.dc); -} -#endif /* SOKOL_GLCORE33 */ - -_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - _sapp_clear(dst, (size_t)dst_num_bytes); - const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); - if (bytes_needed <= dst_num_bytes) { - WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); - return true; - } - else { - return false; - } -} - -/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ -_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { - RECT rect; - if (GetClientRect(_sapp.win32.hwnd, &rect)) { - float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; - float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; - _sapp.window_width = (int)roundf(window_width); - _sapp.window_height = (int)roundf(window_height); - int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); - int fb_height = (int)roundf(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; - return true; - } - } - else { - _sapp.window_width = _sapp.window_height = 1; - _sapp.framebuffer_width = _sapp.framebuffer_height = 1; - } - return false; -} - -_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { - HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); - MONITORINFO minfo; - _sapp_clear(&minfo, sizeof(minfo)); - minfo.cbSize = sizeof(MONITORINFO); - GetMonitorInfo(monitor, &minfo); - const RECT mr = minfo.rcMonitor; - const int monitor_w = mr.right - mr.left; - const int monitor_h = mr.bottom - mr.top; - - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD win_style; - RECT rect = { 0, 0, 0, 0 }; - - _sapp.fullscreen = fullscreen; - if (!_sapp.fullscreen) { - win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect = _sapp.win32.stored_window_rect; - } - else { - GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); - win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.left = mr.left; - rect.top = mr.top; - rect.right = rect.left + monitor_w; - rect.bottom = rect.top + monitor_h; - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - } - const int win_w = rect.right - rect.left; - const int win_h = rect.bottom - rect.top; - const int win_x = rect.left; - const int win_y = rect.top; - SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); - SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); -} - -_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { - _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); -} - -_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined - // before windows.h is included, but we can't guarantee that because - // the sokol_app.h implementation may be included with other implementations - // in the same compilation unit - int id = 0; - switch (cursor) { - case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL - case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM - case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS - case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND - case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE - case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS - case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE - case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW - case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL - case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO - default: break; - } - if (id != 0) { - _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); - } - // fallback: default cursor - if (0 == _sapp.win32.cursors[cursor]) { - // 32512 => IDC_ARROW - _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); - } - SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); -} - -_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { - for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { - _sapp_win32_init_cursor((sapp_mouse_cursor)i); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { - POINT pos; - if (!GetCursorPos(&pos)) { - return false; - } - if (WindowFromPoint(pos) != _sapp.win32.hwnd) { - return false; - } - RECT area; - GetClientRect(_sapp.win32.hwnd, &area); - ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); - ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); - return PtInRect(&area, pos) == TRUE; -} - -_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { - // NOTE: when called from WM_SETCURSOR, the area test would be redundant - if (!skip_area_test) { - if (!_sapp_win32_cursor_in_content_area()) { - return; - } - } - if (!shown) { - SetCursor(NULL); - } - else { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); - SetCursor(_sapp.win32.cursors[cursor]); - } -} - -_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { - if (0 == _sapp.win32.mouse_capture_mask) { - SetCapture(_sapp.win32.hwnd); - } - _sapp.win32.mouse_capture_mask |= btn_mask; -} - -_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { - if (0 != _sapp.win32.mouse_capture_mask) { - _sapp.win32.mouse_capture_mask &= ~btn_mask; - if (0 == _sapp.win32.mouse_capture_mask) { - ReleaseCapture(); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - _sapp_win32_release_mouse(0xFF); - if (_sapp.mouse.locked) { - /* store the current mouse position, so it can be restored when unlocked */ - POINT pos; - BOOL res = GetCursorPos(&pos); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - _sapp.win32.mouse_locked_x = pos.x; - _sapp.win32.mouse_locked_y = pos.y; - - /* while the mouse is locked, make the mouse cursor invisible and - confine the mouse movement to a small rectangle inside our window - (so that we don't miss any mouse up events) - */ - RECT client_rect = { - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y, - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y - }; - ClipCursor(&client_rect); - - /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ - ShowCursor(FALSE); - - /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ - const RAWINPUTDEVICE rid = { - 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC - 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE - 0, // dwFlags - _sapp.win32.hwnd // hwndTarget - }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); - } - /* in case the raw mouse device only supports absolute position reporting, - we need to skip the dx/dy compution for the first WM_INPUT event - */ - _sapp.win32.raw_input_mousepos_valid = false; - } - else { - /* disable raw input for mouse */ - const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); - } - - /* let the mouse roam freely again */ - ClipCursor(NULL); - ShowCursor(TRUE); - - /* restore the 'pre-locked' mouse position */ - BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { - const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); - if (cur_monitor != _sapp.win32.hmonitor) { - _sapp.win32.hmonitor = cur_monitor; - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { - uint32_t mods = 0; - if (GetKeyState(VK_SHIFT) & (1<<15)) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (GetKeyState(VK_CONTROL) & (1<<15)) { - mods |= SAPP_MODIFIER_CTRL; - } - if (GetKeyState(VK_MENU) & (1<<15)) { - mods |= SAPP_MODIFIER_ALT; - } - 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; -} - -_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { - if (!_sapp.mouse.locked) { - const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; - const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; - if (_sapp.mouse.pos_valid) { - // don't update dx/dy in the very first event - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.mouse_button = btn; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.scroll_x = -x / 30.0f; - _sapp.event.scroll_y = y / 30.0f; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { - if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.key_code = _sapp.keycodes[vk]; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { - if (_sapp_events_enabled() && (c >= 32)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.char_code = c; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { - /* called on WM_DPICHANGED, which will only be sent to the application - if sapp_desc.high_dpi is true and the Windows version is recent enough - to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 - */ - SOKOL_ASSERT(_sapp.desc.high_dpi); - HINSTANCE user32 = LoadLibraryA("user32.dll"); - if (!user32) { - return; - } - typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); - GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); - if (fn_getdpiforwindow) { - UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); - // NOTE: for high-dpi apps, mouse_scale remains one - _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; - _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; - _sapp.dpi_scale = _sapp.win32.dpi.window_scale; - SetWindowPos(hWnd, 0, - proposed_win_rect->left, - proposed_win_rect->top, - proposed_win_rect->right - proposed_win_rect->left, - proposed_win_rect->bottom - proposed_win_rect->top, - SWP_NOZORDER | SWP_NOACTIVATE); - } - FreeLibrary(user32); -} - -_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { - if (!_sapp.drop.enabled) { - return; - } - _sapp_clear_drop_buffer(); - bool drop_failed = false; - const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); - _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; - for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { - const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; - WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); - DragQueryFileW(hdrop, i, buffer, num_chars); - if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - drop_failed = true; - } - _sapp_free(buffer); - } - DragFinish(hdrop); - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } -} - -_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { - #if defined(SOKOL_D3D11) - // on D3D11, use the more precise DXGI timestamp - if (_sapp.d3d11.use_dxgi_frame_stats) { - DXGI_FRAME_STATISTICS dxgi_stats; - _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); - HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); - if (SUCCEEDED(hr)) { - if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { - if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { - _sapp_timing_discontinuity(&_sapp.timing); - } - _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; - LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; - const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); - _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); - } - return; - } - } - // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason - _sapp_timing_measure(&_sapp.timing); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_timing_measure(&_sapp.timing); - #endif -} - -_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (!_sapp.win32.in_create_window) { - switch (uMsg) { - case WM_CLOSE: - /* only give user a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a change to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - PostQuitMessage(0); - } - return 0; - case WM_SYSCOMMAND: - switch (wParam & 0xFFF0) { - case SC_SCREENSAVE: - case SC_MONITORPOWER: - if (_sapp.fullscreen) { - /* disable screen saver and blanking in fullscreen mode */ - return 0; - } - break; - case SC_KEYMENU: - /* user trying to access menu via ALT */ - return 0; - } - break; - case WM_ERASEBKGND: - return 1; - case WM_SIZE: - { - const bool iconified = wParam == SIZE_MINIMIZED; - if (iconified != _sapp.win32.iconified) { - _sapp.win32.iconified = iconified; - if (iconified) { - _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else { - _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - break; - case WM_SETFOCUS: - _sapp_win32_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_app_event(SAPP_EVENTTYPE_UNFOCUSED); - break; - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT) { - _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); - return TRUE; - } - break; - case WM_DPICHANGED: - { - /* Update window's DPI and size if its moved to another monitor with a different DPI - Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. - */ - _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); - break; - } - case WM_LBUTTONDOWN: - _sapp_win32_mouse_update(lParam); - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); - _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - /* mouse only reports absolute position - NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. - (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). - See: https://github.com/floooh/sokol/issues/806 and - https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) - */ - LONG new_x = raw_mouse_data->data.mouse.lLastX; - LONG new_y = raw_mouse_data->data.mouse.lLastY; - if (_sapp.win32.raw_input_mousepos_valid) { - _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); - _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); - } - _sapp.win32.raw_input_mousepos_x = new_x; - _sapp.win32.raw_input_mousepos_y = new_y; - _sapp.win32.raw_input_mousepos_valid = true; - } - else { - /* mouse reports movement delta (this seems to be the common case) */ - _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; - _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; - } - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); - } - break; - - case WM_MOUSELEAVE: - if (!_sapp.mouse.locked) { - _sapp.win32.mouse_tracked = false; - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); - } - break; - case WM_MOUSEWHEEL: - _sapp_win32_mouse_update(lParam); - _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); - break; - case WM_MOUSEHWHEEL: - _sapp_win32_mouse_update(lParam); - _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); - break; - case WM_CHAR: - _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); - break; - case WM_KEYUP: - case WM_SYSKEYUP: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); - break; - case WM_ENTERSIZEMOVE: - SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); - break; - case WM_EXITSIZEMOVE: - KillTimer(_sapp.win32.hwnd, 1); - break; - case WM_TIMER: - _sapp_win32_timing_measure(); - _sapp_frame(); - #if defined(SOKOL_D3D11) - // present with DXGI_PRESENT_DO_NOT_WAIT - _sapp_d3d11_present(true); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* NOTE: resizing the swap-chain during resize leads to a substantial - memory spike (hundreds of megabytes for a few seconds). - - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); - } - */ - break; - case WM_NCLBUTTONDOWN: - /* workaround for half-second pause when starting to move window - see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ - */ - if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { - POINT point; - GetCursorPos(&point); - ScreenToClient(_sapp.win32.hwnd, &point); - PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); - } - break; - case WM_DROPFILES: - _sapp_win32_files_dropped((HDROP)wParam); - break; - case WM_DISPLAYCHANGE: - // refresh rate might have changed - _sapp_timing_reset(&_sapp.timing); - break; - - default: - break; - } - } - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -_SOKOL_PRIVATE void _sapp_win32_create_window(void) { - WNDCLASSW wndclassw; - _sapp_clear(&wndclassw, sizeof(wndclassw)); - wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; - wndclassw.hInstance = GetModuleHandleW(NULL); - wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); - wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); - wndclassw.lpszClassName = L"SOKOLAPP"; - RegisterClassW(&wndclassw); - - /* NOTE: regardless whether fullscreen is requested or not, a regular - windowed-mode window will always be created first (however in hidden - mode, so that no windowed-mode window pops up before the fullscreen window) - */ - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - RECT rect = { 0, 0, 0, 0 }; - DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); - const bool use_default_width = 0 == _sapp.window_width; - const bool use_default_height = 0 == _sapp.window_height; - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - const int win_width = rect.right - rect.left; - const int win_height = rect.bottom - rect.top; - _sapp.win32.in_create_window = true; - _sapp.win32.hwnd = CreateWindowExW( - win_ex_style, // dwExStyle - L"SOKOLAPP", // lpClassName - _sapp.window_title_wide, // lpWindowName - win_style, // dwStyle - CW_USEDEFAULT, // X - SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! - use_default_width ? CW_USEDEFAULT : win_width, // nWidth - use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) - NULL, // hWndParent - NULL, // hMenu - GetModuleHandle(NULL), // hInstance - NULL); // lParam - _sapp.win32.in_create_window = false; - _sapp.win32.dc = GetDC(_sapp.win32.hwnd); - _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); - SOKOL_ASSERT(_sapp.win32.dc); - - /* this will get the actual windowed-mode window size, if fullscreen - is requested, the set_fullscreen function will then capture the - current window rectangle, which then might be used later to - restore the window position when switching back to windowed - */ - _sapp_win32_update_dimensions(); - if (_sapp.fullscreen) { - _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); - _sapp_win32_update_dimensions(); - } - ShowWindow(_sapp.win32.hwnd, SW_SHOW); - DragAcceptFiles(_sapp.win32.hwnd, 1); -} - -_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { - DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; - 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; - if (_sapp.desc.win32_console_create) { - con_valid = AllocConsole(); - } - else if (_sapp.desc.win32_console_attach) { - con_valid = AttachConsole(ATTACH_PARENT_PROCESS); - } - if (con_valid) { - FILE* res_fp = 0; - errno_t err; - err = freopen_s(&res_fp, "CON", "w", stdout); - (void)err; - err = freopen_s(&res_fp, "CON", "w", stderr); - (void)err; - } - } - if (_sapp.desc.win32_console_utf8) { - _sapp.win32.orig_codepage = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - } -} - -_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { - if (_sapp.desc.win32_console_utf8) { - SetConsoleOutputCP(_sapp.win32.orig_codepage); - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { - - DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); - typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); - typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 - typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); - typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; - SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; - GETDPIFORMONITOR_T fn_getdpiformonitor = 0; - SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; - - HINSTANCE user32 = LoadLibraryA("user32.dll"); - if (user32) { - fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); - fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); - } - HINSTANCE shcore = LoadLibraryA("shcore.dll"); - if (shcore) { - fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); - fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); - } - /* - NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): - - These are different attempts to get DPI handling on Windows right, from oldest - to newest. SetProcessDpiAwarenessContext() is required for the new - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. - */ - if (fn_setprocessdpiawareness) { - if (_sapp.desc.high_dpi) { - /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, - if that fails, fall back to system-dpi-awareness - */ - _sapp.win32.dpi.aware = true; - DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; - if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { - // fallback to system-dpi-aware - fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); - } - } - else { - /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ - _sapp.win32.dpi.aware = false; - fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); - } - } - else if (fn_setprocessdpiaware) { - // fallback for Windows 7 - _sapp.win32.dpi.aware = true; - fn_setprocessdpiaware(); - } - /* get dpi scale factor for main monitor */ - if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { - POINT pt = { 1, 1 }; - HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); - UINT dpix, dpiy; - HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr)); - /* clamp window scale to an integer factor */ - _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; - } - else { - _sapp.win32.dpi.window_scale = 1.0f; - } - if (_sapp.desc.high_dpi) { - _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; - _sapp.win32.dpi.mouse_scale = 1.0f; - } - else { - _sapp.win32.dpi.content_scale = 1.0f; - _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; - } - _sapp.dpi_scale = _sapp.win32.dpi.content_scale; - if (user32) { - FreeLibrary(user32); - } - if (shcore) { - FreeLibrary(shcore); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { - SOKOL_ASSERT(str); - SOKOL_ASSERT(_sapp.win32.hwnd); - SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); - - if (!OpenClipboard(_sapp.win32.hwnd)) { - return false; - } - - HANDLE object = 0; - wchar_t* wchar_buf = 0; - - const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); - object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); - if (NULL == object) { - goto error; - } - wchar_buf = (wchar_t*) GlobalLock(object); - if (NULL == wchar_buf) { - goto error; - } - if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { - goto error; - } - GlobalUnlock(object); - wchar_buf = 0; - EmptyClipboard(); - // NOTE: when successful, SetClipboardData() takes ownership of memory object! - if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { - goto error; - } - CloseClipboard(); - return true; - -error: - if (wchar_buf) { - GlobalUnlock(object); - } - if (object) { - GlobalFree(object); - } - CloseClipboard(); - return false; -} - -_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); - SOKOL_ASSERT(_sapp.win32.hwnd); - if (!OpenClipboard(_sapp.win32.hwnd)) { - /* silently ignore any errors and just return the current - content of the local clipboard buffer - */ - return _sapp.clipboard.buffer; - } - HANDLE object = GetClipboardData(CF_UNICODETEXT); - if (!object) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); - if (!wchar_buf) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { - _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); - } - GlobalUnlock(object); - CloseClipboard(); - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { - _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); -} - -_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { - BITMAPV5HEADER bi; - _sapp_clear(&bi, 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; - _sapp_clear(&icon_info, 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 - - From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 -*/ -_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { - HMODULE h = GetModuleHandleW(L"kernel32.dll"); - if (NULL != h) { - return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_win32_init_console(); - _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); - _sapp_win32_init_keytable(); - _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - _sapp_win32_init_dpi(); - _sapp_win32_init_cursors(); - _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(); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_init(); - _sapp_wgl_load_extensions(); - _sapp_wgl_create_context(); - #endif - _sapp.valid = true; - - bool done = false; - while (!(done || _sapp.quit_ordered)) { - _sapp_win32_timing_measure(); - MSG msg; - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { - if (WM_QUIT == msg.message) { - done = true; - continue; - } - else { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - _sapp_frame(); - #if defined(SOKOL_D3D11) - _sapp_d3d11_present(false); - if (IsIconic(_sapp.win32.hwnd)) { - Sleep((DWORD)(16 * _sapp.swap_interval)); - } - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); - } - /* check if the window monitor has changed, need to reset timing because - the new monitor might have a different refresh rate - */ - if (_sapp_win32_update_monitor()) { - _sapp_timing_reset(&_sapp.timing); - } - if (_sapp.quit_requested) { - PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); - } - } - _sapp_call_cleanup(); - - #if defined(SOKOL_D3D11) - _sapp_d3d11_destroy_default_render_target(); - _sapp_d3d11_destroy_device_and_swapchain(); - #else - _sapp_wgl_destroy_context(); - _sapp_wgl_shutdown(); - #endif - _sapp_win32_destroy_window(); - _sapp_win32_destroy_icons(); - _sapp_win32_restore_console(); - _sapp_discard_state(); -} - -_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { - int argc = 0; - char** argv = 0; - char* args; - - LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); - if (w_argv == NULL) { - // FIXME: chicken egg problem, can't report errors before sokol_main() is called! - } else { - size_t size = wcslen(w_command_line) * 4; - argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); - SOKOL_ASSERT(argv); - args = (char*) &argv[argc + 1]; - int n; - for (int i = 0; i < argc; ++i) { - n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); - if (n == 0) { - // FIXME: chicken egg problem, can't report errors before sokol_main() is called! - break; - } - argv[i] = args; - size -= (size_t)n; - args += n; - } - LocalFree(w_argv); - } - *o_argc = argc; - return argv; -} - -#if !defined(SOKOL_NO_ENTRY) -#if defined(SOKOL_WIN32_FORCE_MAIN) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_win32_run(&desc); - return 0; -} -#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { - _SOKOL_UNUSED(hInstance); - _SOKOL_UNUSED(hPrevInstance); - _SOKOL_UNUSED(lpCmdLine); - _SOKOL_UNUSED(nCmdShow); - int argc_utf8 = 0; - char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); - sapp_desc desc = sokol_main(argc_utf8, argv_utf8); - _sapp_win32_run(&desc); - _sapp_free(argv_utf8); - return 0; -} -#endif /* SOKOL_WIN32_FORCE_MAIN */ -#endif /* SOKOL_NO_ENTRY */ - -#ifdef _MSC_VER - #pragma warning(pop) -#endif - -#endif /* _SAPP_WIN32 */ - -// █████ ███ ██ ██████ ██████ ██████ ██ ██████ -// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ -// -// >>android -#if defined(_SAPP_ANDROID) - -/* android loop thread */ -_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { - SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); - - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - return false; - } - if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { - return false; - } - _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; - - EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; - const EGLint cfg_attributes[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - #if defined(SOKOL_GLES3) - EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2?EGL_OPENGL_ES2_BIT:EGL_OPENGL_ES3_BIT, - #else - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - #endif - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, alpha_size, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, 0, - EGL_NONE, - }; - EGLConfig available_cfgs[32]; - EGLint cfg_count; - eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); - SOKOL_ASSERT(cfg_count > 0); - SOKOL_ASSERT(cfg_count <= 32); - - /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ - EGLConfig config; - bool exact_cfg_found = false; - for (int i = 0; i < cfg_count; ++i) { - EGLConfig c = available_cfgs[i]; - EGLint r, g, b, a, d; - if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && - r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { - exact_cfg_found = true; - config = c; - break; - } - } - if (!exact_cfg_found) { - config = available_cfgs[0]; - } - - EGLint ctx_attributes[] = { - #if defined(SOKOL_GLES3) - EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, - #else - EGL_CONTEXT_CLIENT_VERSION, 2, - #endif - EGL_NONE, - }; - EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); - if (context == EGL_NO_CONTEXT) { - return false; - } - - _sapp.android.config = config; - _sapp.android.display = display; - _sapp.android.context = context; - return true; -} - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { - if (_sapp.android.display != EGL_NO_DISPLAY) { - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } - if (_sapp.android.context != EGL_NO_CONTEXT) { - eglDestroyContext(_sapp.android.display, _sapp.android.context); - _sapp.android.context = EGL_NO_CONTEXT; - } - eglTerminate(_sapp.android.display); - _sapp.android.display = EGL_NO_DISPLAY; - } -} - -_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - /* TODO: set window flags */ - /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ - - /* create egl surface and make it current */ - EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); - if (surface == EGL_NO_SURFACE) { - return false; - } - if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { - return false; - } - _sapp.android.surface = surface; - return true; -} - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { - if (_sapp.android.display == EGL_NO_DISPLAY) { - return; - } - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } -} - -_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - const int32_t win_w = ANativeWindow_getWidth(window); - const int32_t win_h = ANativeWindow_getHeight(window); - SOKOL_ASSERT(win_w >= 0 && win_h >= 0); - const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); - _sapp.window_width = win_w; - _sapp.window_height = win_h; - if (win_changed || force_update) { - if (!_sapp.desc.high_dpi) { - const int32_t buf_w = win_w / 2; - const int32_t buf_h = win_h / 2; - EGLint format; - EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); - SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); - /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions - as the ANativeWindow size results in weird display artefacts, that's - why it's only called when the buffer geometry is different from - the window size - */ - int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); - SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); - } - } - - /* query surface size */ - EGLint fb_w, fb_h; - EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); - EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); - SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); - SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); - const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); - _sapp.framebuffer_width = fb_w; - _sapp.framebuffer_height = fb_h; - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; - if (win_changed || fb_changed || force_update) { - if (!_sapp.first_frame) { - _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_android_cleanup(void) { - if (_sapp.android.surface != EGL_NO_SURFACE) { - /* egl context is bound, cleanup gracefully */ - if (_sapp.init_called && !_sapp.cleanup_called) { - _sapp_call_cleanup(); - } - } - /* always try to cleanup by destroying egl context */ - _sapp_android_cleanup_egl(); -} - -_SOKOL_PRIVATE void _sapp_android_shutdown(void) { - /* try to cleanup while we still have a surface and can call cleanup_cb() */ - _sapp_android_cleanup(); - /* request exit */ - ANativeActivity_finish(_sapp.android.activity); -} - -_SOKOL_PRIVATE void _sapp_android_frame(void) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - _sapp_timing_measure(&_sapp.timing); - _sapp_android_update_dimensions(_sapp.android.current.window, false); - _sapp_frame(); - eglSwapBuffers(_sapp.android.display, _sapp.android.surface); -} - -_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { - return false; - } - if (!_sapp_events_enabled()) { - return false; - } - int32_t action_idx = AMotionEvent_getAction(e); - int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case AMOTION_EVENT_ACTION_MOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case AMOTION_EVENT_ACTION_CANCEL: - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - break; - } - if (type == SAPP_EVENTTYPE_INVALID) { - return false; - } - int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - _sapp_init_event(type); - _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); - if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { - _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; - } - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); - dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; - dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { - dst->changed = (i == idx); - } else { - dst->changed = true; - } - } - _sapp_call_event(&_sapp.event); - return true; -} - -_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { - return false; - } - if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { - /* FIXME: this should be hooked into a "really quit?" mechanism - so the app can ask the user for confirmation, this is currently - generally missing in sokol_app.h - */ - _sapp_android_shutdown(); - return true; - } - return false; -} - -_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { - _SOKOL_UNUSED(fd); - _SOKOL_UNUSED(data); - if ((events & ALOOPER_EVENT_INPUT) == 0) { - _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); - return 1; - } - SOKOL_ASSERT(_sapp.android.current.input); - AInputEvent* event = NULL; - while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { - if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { - continue; - } - int32_t handled = 0; - if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { - handled = 1; - } - AInputQueue_finishEvent(_sapp.android.current.input, event, handled); - } - return 1; -} - -_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { - _SOKOL_UNUSED(data); - if ((events & ALOOPER_EVENT_INPUT) == 0) { - _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); - return 1; - } - - _sapp_android_msg_t msg; - if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { - _SAPP_ERROR(ANDROID_READ_MSG_FAILED); - return 1; - } - - pthread_mutex_lock(&_sapp.android.pt.mutex); - switch (msg) { - case _SOKOL_ANDROID_MSG_CREATE: - { - _SAPP_INFO(ANDROID_MSG_CREATE); - SOKOL_ASSERT(!_sapp.valid); - bool result = _sapp_android_init_egl(); - SOKOL_ASSERT(result); _SOKOL_UNUSED(result); - _sapp.valid = true; - _sapp.android.has_created = true; - } - break; - case _SOKOL_ANDROID_MSG_RESUME: - _SAPP_INFO(ANDROID_MSG_RESUME); - _sapp.android.has_resumed = true; - _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); - break; - case _SOKOL_ANDROID_MSG_PAUSE: - _SAPP_INFO(ANDROID_MSG_PAUSE); - _sapp.android.has_resumed = false; - _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); - break; - case _SOKOL_ANDROID_MSG_FOCUS: - _SAPP_INFO(ANDROID_MSG_FOCUS); - _sapp.android.has_focus = true; - break; - case _SOKOL_ANDROID_MSG_NO_FOCUS: - _SAPP_INFO(ANDROID_MSG_NO_FOCUS); - _sapp.android.has_focus = false; - break; - case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: - _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); - if (_sapp.android.current.window != _sapp.android.pending.window) { - if (_sapp.android.current.window != NULL) { - _sapp_android_cleanup_egl_surface(); - } - if (_sapp.android.pending.window != NULL) { - if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { - _sapp_android_update_dimensions(_sapp.android.pending.window, true); - } else { - _sapp_android_shutdown(); - } - } - } - _sapp.android.current.window = _sapp.android.pending.window; - break; - case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: - _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); - if (_sapp.android.current.input != _sapp.android.pending.input) { - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - if (_sapp.android.pending.input != NULL) { - AInputQueue_attachLooper( - _sapp.android.pending.input, - _sapp.android.looper, - ALOOPER_POLL_CALLBACK, - _sapp_android_input_cb, - NULL); /* data */ - } - } - _sapp.android.current.input = _sapp.android.pending.input; - break; - case _SOKOL_ANDROID_MSG_DESTROY: - _SAPP_INFO(ANDROID_MSG_DESTROY); - _sapp_android_cleanup(); - _sapp.valid = false; - _sapp.android.is_thread_stopping = true; - break; - default: - _SAPP_WARN(ANDROID_UNKNOWN_MSG); - break; - } - pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ - pthread_mutex_unlock(&_sapp.android.pt.mutex); - return 1; -} - -_SOKOL_PRIVATE bool _sapp_android_should_update(void) { - bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; - bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; - return is_in_front && has_surface; -} - -_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { - SOKOL_ASSERT(_sapp.valid); - /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ - if (shown) { - ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); - } else { - ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); - } -} - -_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { - _SOKOL_UNUSED(arg); - _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); - - _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); - ALooper_addFd(_sapp.android.looper, - _sapp.android.pt.read_from_main_fd, - ALOOPER_POLL_CALLBACK, - ALOOPER_EVENT_INPUT, - _sapp_android_main_cb, - NULL); /* data */ - - /* signal start to main thread */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_started = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* main loop */ - while (!_sapp.android.is_thread_stopping) { - /* sokol frame */ - if (_sapp_android_should_update()) { - _sapp_android_frame(); - } - - /* process all events (or stop early if app is requested to quit) */ - bool process_events = true; - while (process_events && !_sapp.android.is_thread_stopping) { - bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); - process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; - } - } - - /* cleanup thread */ - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - - /* the following causes heap corruption on exit, why?? - ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); - ALooper_release(_sapp.android.looper);*/ - - /* signal "destroyed" */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_stopped = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); - return NULL; -} - -/* android main/ui thread */ -_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { - if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { - _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); -} - -_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); - _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); -} - -_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); - *out_size = 0; - return NULL; -} - -_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); - if (has_focus) { - _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); - } else { - _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); - _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); -} - -_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.window = window; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); - while (_sapp.android.current.window != window) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); - _sapp_android_msg_set_native_window(window); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { - _SOKOL_UNUSED(activity); - _SOKOL_UNUSED(window); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); - _sapp_android_msg_set_native_window(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.input = input; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); - while (_sapp.android.current.input != input) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); - _sapp_android_msg_set_input_queue(queue); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { - _SOKOL_UNUSED(activity); - _SOKOL_UNUSED(queue); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); - _sapp_android_msg_set_input_queue(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); - /* see android:configChanges in manifest */ -} - -_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); -} - -_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { - /* - * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) - * on my device (Moto X 2nd gen) when the app is removed from the task view - * (TaskStackView: onTaskViewDismissed). - * - * However, if ANativeActivity_finish() is explicitly called from for example - * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? - */ - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); - - /* send destroy msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); - while (!_sapp.android.is_thread_stopped) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* clean up main thread */ - pthread_cond_destroy(&_sapp.android.pt.cond); - pthread_mutex_destroy(&_sapp.android.pt.mutex); - - close(_sapp.android.pt.read_from_main_fd); - close(_sapp.android.pt.write_from_main_fd); - - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); - - /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ - exit(0); -} - -JNIEXPORT -void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { - _SOKOL_UNUSED(saved_state); - _SOKOL_UNUSED(saved_state_size); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); - - // the NativeActity pointer needs to be available inside sokol_main() - // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() - // will clear the global _sapp_t struct, so we need to initialize the native - // activity pointer twice, once before sokol_main() and once after _sapp_init_state() - _sapp_clear(&_sapp, sizeof(_sapp)); - _sapp.android.activity = activity; - sapp_desc desc = sokol_main(0, NULL); - _sapp_init_state(&desc); - _sapp.android.activity = activity; - - int pipe_fd[2]; - if (pipe(pipe_fd) != 0) { - _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); - return; - } - _sapp.android.pt.read_from_main_fd = pipe_fd[0]; - _sapp.android.pt.write_from_main_fd = pipe_fd[1]; - - pthread_mutex_init(&_sapp.android.pt.mutex, NULL); - pthread_cond_init(&_sapp.android.pt.cond, NULL); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); - pthread_attr_destroy(&attr); - - /* wait until main loop has started */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - while (!_sapp.android.is_thread_started) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* send create msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); - while (!_sapp.android.has_created) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* register for callbacks */ - activity->callbacks->onStart = _sapp_android_on_start; - activity->callbacks->onResume = _sapp_android_on_resume; - activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; - activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; - activity->callbacks->onPause = _sapp_android_on_pause; - activity->callbacks->onStop = _sapp_android_on_stop; - activity->callbacks->onDestroy = _sapp_android_on_destroy; - activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; - /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ - /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ - activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; - activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; - activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; - /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ - activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; - activity->callbacks->onLowMemory = _sapp_android_on_low_memory; - - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); - - /* NOT A BUG: do NOT call sapp_discard_state() */ -} - -#endif /* _SAPP_ANDROID */ - -// ██ ██ ███ ██ ██ ██ ██ ██ -// ██ ██ ████ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ████ ██████ ██ ██ -// -// >>linux -#if defined(_SAPP_LINUX) - -/* see GLFW's xkb_unicode.c */ -static const struct _sapp_x11_codepair { - uint16_t keysym; - uint16_t ucs; -} _sapp_x11_keysymtab[] = { - { 0x01a1, 0x0104 }, - { 0x01a2, 0x02d8 }, - { 0x01a3, 0x0141 }, - { 0x01a5, 0x013d }, - { 0x01a6, 0x015a }, - { 0x01a9, 0x0160 }, - { 0x01aa, 0x015e }, - { 0x01ab, 0x0164 }, - { 0x01ac, 0x0179 }, - { 0x01ae, 0x017d }, - { 0x01af, 0x017b }, - { 0x01b1, 0x0105 }, - { 0x01b2, 0x02db }, - { 0x01b3, 0x0142 }, - { 0x01b5, 0x013e }, - { 0x01b6, 0x015b }, - { 0x01b7, 0x02c7 }, - { 0x01b9, 0x0161 }, - { 0x01ba, 0x015f }, - { 0x01bb, 0x0165 }, - { 0x01bc, 0x017a }, - { 0x01bd, 0x02dd }, - { 0x01be, 0x017e }, - { 0x01bf, 0x017c }, - { 0x01c0, 0x0154 }, - { 0x01c3, 0x0102 }, - { 0x01c5, 0x0139 }, - { 0x01c6, 0x0106 }, - { 0x01c8, 0x010c }, - { 0x01ca, 0x0118 }, - { 0x01cc, 0x011a }, - { 0x01cf, 0x010e }, - { 0x01d0, 0x0110 }, - { 0x01d1, 0x0143 }, - { 0x01d2, 0x0147 }, - { 0x01d5, 0x0150 }, - { 0x01d8, 0x0158 }, - { 0x01d9, 0x016e }, - { 0x01db, 0x0170 }, - { 0x01de, 0x0162 }, - { 0x01e0, 0x0155 }, - { 0x01e3, 0x0103 }, - { 0x01e5, 0x013a }, - { 0x01e6, 0x0107 }, - { 0x01e8, 0x010d }, - { 0x01ea, 0x0119 }, - { 0x01ec, 0x011b }, - { 0x01ef, 0x010f }, - { 0x01f0, 0x0111 }, - { 0x01f1, 0x0144 }, - { 0x01f2, 0x0148 }, - { 0x01f5, 0x0151 }, - { 0x01f8, 0x0159 }, - { 0x01f9, 0x016f }, - { 0x01fb, 0x0171 }, - { 0x01fe, 0x0163 }, - { 0x01ff, 0x02d9 }, - { 0x02a1, 0x0126 }, - { 0x02a6, 0x0124 }, - { 0x02a9, 0x0130 }, - { 0x02ab, 0x011e }, - { 0x02ac, 0x0134 }, - { 0x02b1, 0x0127 }, - { 0x02b6, 0x0125 }, - { 0x02b9, 0x0131 }, - { 0x02bb, 0x011f }, - { 0x02bc, 0x0135 }, - { 0x02c5, 0x010a }, - { 0x02c6, 0x0108 }, - { 0x02d5, 0x0120 }, - { 0x02d8, 0x011c }, - { 0x02dd, 0x016c }, - { 0x02de, 0x015c }, - { 0x02e5, 0x010b }, - { 0x02e6, 0x0109 }, - { 0x02f5, 0x0121 }, - { 0x02f8, 0x011d }, - { 0x02fd, 0x016d }, - { 0x02fe, 0x015d }, - { 0x03a2, 0x0138 }, - { 0x03a3, 0x0156 }, - { 0x03a5, 0x0128 }, - { 0x03a6, 0x013b }, - { 0x03aa, 0x0112 }, - { 0x03ab, 0x0122 }, - { 0x03ac, 0x0166 }, - { 0x03b3, 0x0157 }, - { 0x03b5, 0x0129 }, - { 0x03b6, 0x013c }, - { 0x03ba, 0x0113 }, - { 0x03bb, 0x0123 }, - { 0x03bc, 0x0167 }, - { 0x03bd, 0x014a }, - { 0x03bf, 0x014b }, - { 0x03c0, 0x0100 }, - { 0x03c7, 0x012e }, - { 0x03cc, 0x0116 }, - { 0x03cf, 0x012a }, - { 0x03d1, 0x0145 }, - { 0x03d2, 0x014c }, - { 0x03d3, 0x0136 }, - { 0x03d9, 0x0172 }, - { 0x03dd, 0x0168 }, - { 0x03de, 0x016a }, - { 0x03e0, 0x0101 }, - { 0x03e7, 0x012f }, - { 0x03ec, 0x0117 }, - { 0x03ef, 0x012b }, - { 0x03f1, 0x0146 }, - { 0x03f2, 0x014d }, - { 0x03f3, 0x0137 }, - { 0x03f9, 0x0173 }, - { 0x03fd, 0x0169 }, - { 0x03fe, 0x016b }, - { 0x047e, 0x203e }, - { 0x04a1, 0x3002 }, - { 0x04a2, 0x300c }, - { 0x04a3, 0x300d }, - { 0x04a4, 0x3001 }, - { 0x04a5, 0x30fb }, - { 0x04a6, 0x30f2 }, - { 0x04a7, 0x30a1 }, - { 0x04a8, 0x30a3 }, - { 0x04a9, 0x30a5 }, - { 0x04aa, 0x30a7 }, - { 0x04ab, 0x30a9 }, - { 0x04ac, 0x30e3 }, - { 0x04ad, 0x30e5 }, - { 0x04ae, 0x30e7 }, - { 0x04af, 0x30c3 }, - { 0x04b0, 0x30fc }, - { 0x04b1, 0x30a2 }, - { 0x04b2, 0x30a4 }, - { 0x04b3, 0x30a6 }, - { 0x04b4, 0x30a8 }, - { 0x04b5, 0x30aa }, - { 0x04b6, 0x30ab }, - { 0x04b7, 0x30ad }, - { 0x04b8, 0x30af }, - { 0x04b9, 0x30b1 }, - { 0x04ba, 0x30b3 }, - { 0x04bb, 0x30b5 }, - { 0x04bc, 0x30b7 }, - { 0x04bd, 0x30b9 }, - { 0x04be, 0x30bb }, - { 0x04bf, 0x30bd }, - { 0x04c0, 0x30bf }, - { 0x04c1, 0x30c1 }, - { 0x04c2, 0x30c4 }, - { 0x04c3, 0x30c6 }, - { 0x04c4, 0x30c8 }, - { 0x04c5, 0x30ca }, - { 0x04c6, 0x30cb }, - { 0x04c7, 0x30cc }, - { 0x04c8, 0x30cd }, - { 0x04c9, 0x30ce }, - { 0x04ca, 0x30cf }, - { 0x04cb, 0x30d2 }, - { 0x04cc, 0x30d5 }, - { 0x04cd, 0x30d8 }, - { 0x04ce, 0x30db }, - { 0x04cf, 0x30de }, - { 0x04d0, 0x30df }, - { 0x04d1, 0x30e0 }, - { 0x04d2, 0x30e1 }, - { 0x04d3, 0x30e2 }, - { 0x04d4, 0x30e4 }, - { 0x04d5, 0x30e6 }, - { 0x04d6, 0x30e8 }, - { 0x04d7, 0x30e9 }, - { 0x04d8, 0x30ea }, - { 0x04d9, 0x30eb }, - { 0x04da, 0x30ec }, - { 0x04db, 0x30ed }, - { 0x04dc, 0x30ef }, - { 0x04dd, 0x30f3 }, - { 0x04de, 0x309b }, - { 0x04df, 0x309c }, - { 0x05ac, 0x060c }, - { 0x05bb, 0x061b }, - { 0x05bf, 0x061f }, - { 0x05c1, 0x0621 }, - { 0x05c2, 0x0622 }, - { 0x05c3, 0x0623 }, - { 0x05c4, 0x0624 }, - { 0x05c5, 0x0625 }, - { 0x05c6, 0x0626 }, - { 0x05c7, 0x0627 }, - { 0x05c8, 0x0628 }, - { 0x05c9, 0x0629 }, - { 0x05ca, 0x062a }, - { 0x05cb, 0x062b }, - { 0x05cc, 0x062c }, - { 0x05cd, 0x062d }, - { 0x05ce, 0x062e }, - { 0x05cf, 0x062f }, - { 0x05d0, 0x0630 }, - { 0x05d1, 0x0631 }, - { 0x05d2, 0x0632 }, - { 0x05d3, 0x0633 }, - { 0x05d4, 0x0634 }, - { 0x05d5, 0x0635 }, - { 0x05d6, 0x0636 }, - { 0x05d7, 0x0637 }, - { 0x05d8, 0x0638 }, - { 0x05d9, 0x0639 }, - { 0x05da, 0x063a }, - { 0x05e0, 0x0640 }, - { 0x05e1, 0x0641 }, - { 0x05e2, 0x0642 }, - { 0x05e3, 0x0643 }, - { 0x05e4, 0x0644 }, - { 0x05e5, 0x0645 }, - { 0x05e6, 0x0646 }, - { 0x05e7, 0x0647 }, - { 0x05e8, 0x0648 }, - { 0x05e9, 0x0649 }, - { 0x05ea, 0x064a }, - { 0x05eb, 0x064b }, - { 0x05ec, 0x064c }, - { 0x05ed, 0x064d }, - { 0x05ee, 0x064e }, - { 0x05ef, 0x064f }, - { 0x05f0, 0x0650 }, - { 0x05f1, 0x0651 }, - { 0x05f2, 0x0652 }, - { 0x06a1, 0x0452 }, - { 0x06a2, 0x0453 }, - { 0x06a3, 0x0451 }, - { 0x06a4, 0x0454 }, - { 0x06a5, 0x0455 }, - { 0x06a6, 0x0456 }, - { 0x06a7, 0x0457 }, - { 0x06a8, 0x0458 }, - { 0x06a9, 0x0459 }, - { 0x06aa, 0x045a }, - { 0x06ab, 0x045b }, - { 0x06ac, 0x045c }, - { 0x06ae, 0x045e }, - { 0x06af, 0x045f }, - { 0x06b0, 0x2116 }, - { 0x06b1, 0x0402 }, - { 0x06b2, 0x0403 }, - { 0x06b3, 0x0401 }, - { 0x06b4, 0x0404 }, - { 0x06b5, 0x0405 }, - { 0x06b6, 0x0406 }, - { 0x06b7, 0x0407 }, - { 0x06b8, 0x0408 }, - { 0x06b9, 0x0409 }, - { 0x06ba, 0x040a }, - { 0x06bb, 0x040b }, - { 0x06bc, 0x040c }, - { 0x06be, 0x040e }, - { 0x06bf, 0x040f }, - { 0x06c0, 0x044e }, - { 0x06c1, 0x0430 }, - { 0x06c2, 0x0431 }, - { 0x06c3, 0x0446 }, - { 0x06c4, 0x0434 }, - { 0x06c5, 0x0435 }, - { 0x06c6, 0x0444 }, - { 0x06c7, 0x0433 }, - { 0x06c8, 0x0445 }, - { 0x06c9, 0x0438 }, - { 0x06ca, 0x0439 }, - { 0x06cb, 0x043a }, - { 0x06cc, 0x043b }, - { 0x06cd, 0x043c }, - { 0x06ce, 0x043d }, - { 0x06cf, 0x043e }, - { 0x06d0, 0x043f }, - { 0x06d1, 0x044f }, - { 0x06d2, 0x0440 }, - { 0x06d3, 0x0441 }, - { 0x06d4, 0x0442 }, - { 0x06d5, 0x0443 }, - { 0x06d6, 0x0436 }, - { 0x06d7, 0x0432 }, - { 0x06d8, 0x044c }, - { 0x06d9, 0x044b }, - { 0x06da, 0x0437 }, - { 0x06db, 0x0448 }, - { 0x06dc, 0x044d }, - { 0x06dd, 0x0449 }, - { 0x06de, 0x0447 }, - { 0x06df, 0x044a }, - { 0x06e0, 0x042e }, - { 0x06e1, 0x0410 }, - { 0x06e2, 0x0411 }, - { 0x06e3, 0x0426 }, - { 0x06e4, 0x0414 }, - { 0x06e5, 0x0415 }, - { 0x06e6, 0x0424 }, - { 0x06e7, 0x0413 }, - { 0x06e8, 0x0425 }, - { 0x06e9, 0x0418 }, - { 0x06ea, 0x0419 }, - { 0x06eb, 0x041a }, - { 0x06ec, 0x041b }, - { 0x06ed, 0x041c }, - { 0x06ee, 0x041d }, - { 0x06ef, 0x041e }, - { 0x06f0, 0x041f }, - { 0x06f1, 0x042f }, - { 0x06f2, 0x0420 }, - { 0x06f3, 0x0421 }, - { 0x06f4, 0x0422 }, - { 0x06f5, 0x0423 }, - { 0x06f6, 0x0416 }, - { 0x06f7, 0x0412 }, - { 0x06f8, 0x042c }, - { 0x06f9, 0x042b }, - { 0x06fa, 0x0417 }, - { 0x06fb, 0x0428 }, - { 0x06fc, 0x042d }, - { 0x06fd, 0x0429 }, - { 0x06fe, 0x0427 }, - { 0x06ff, 0x042a }, - { 0x07a1, 0x0386 }, - { 0x07a2, 0x0388 }, - { 0x07a3, 0x0389 }, - { 0x07a4, 0x038a }, - { 0x07a5, 0x03aa }, - { 0x07a7, 0x038c }, - { 0x07a8, 0x038e }, - { 0x07a9, 0x03ab }, - { 0x07ab, 0x038f }, - { 0x07ae, 0x0385 }, - { 0x07af, 0x2015 }, - { 0x07b1, 0x03ac }, - { 0x07b2, 0x03ad }, - { 0x07b3, 0x03ae }, - { 0x07b4, 0x03af }, - { 0x07b5, 0x03ca }, - { 0x07b6, 0x0390 }, - { 0x07b7, 0x03cc }, - { 0x07b8, 0x03cd }, - { 0x07b9, 0x03cb }, - { 0x07ba, 0x03b0 }, - { 0x07bb, 0x03ce }, - { 0x07c1, 0x0391 }, - { 0x07c2, 0x0392 }, - { 0x07c3, 0x0393 }, - { 0x07c4, 0x0394 }, - { 0x07c5, 0x0395 }, - { 0x07c6, 0x0396 }, - { 0x07c7, 0x0397 }, - { 0x07c8, 0x0398 }, - { 0x07c9, 0x0399 }, - { 0x07ca, 0x039a }, - { 0x07cb, 0x039b }, - { 0x07cc, 0x039c }, - { 0x07cd, 0x039d }, - { 0x07ce, 0x039e }, - { 0x07cf, 0x039f }, - { 0x07d0, 0x03a0 }, - { 0x07d1, 0x03a1 }, - { 0x07d2, 0x03a3 }, - { 0x07d4, 0x03a4 }, - { 0x07d5, 0x03a5 }, - { 0x07d6, 0x03a6 }, - { 0x07d7, 0x03a7 }, - { 0x07d8, 0x03a8 }, - { 0x07d9, 0x03a9 }, - { 0x07e1, 0x03b1 }, - { 0x07e2, 0x03b2 }, - { 0x07e3, 0x03b3 }, - { 0x07e4, 0x03b4 }, - { 0x07e5, 0x03b5 }, - { 0x07e6, 0x03b6 }, - { 0x07e7, 0x03b7 }, - { 0x07e8, 0x03b8 }, - { 0x07e9, 0x03b9 }, - { 0x07ea, 0x03ba }, - { 0x07eb, 0x03bb }, - { 0x07ec, 0x03bc }, - { 0x07ed, 0x03bd }, - { 0x07ee, 0x03be }, - { 0x07ef, 0x03bf }, - { 0x07f0, 0x03c0 }, - { 0x07f1, 0x03c1 }, - { 0x07f2, 0x03c3 }, - { 0x07f3, 0x03c2 }, - { 0x07f4, 0x03c4 }, - { 0x07f5, 0x03c5 }, - { 0x07f6, 0x03c6 }, - { 0x07f7, 0x03c7 }, - { 0x07f8, 0x03c8 }, - { 0x07f9, 0x03c9 }, - { 0x08a1, 0x23b7 }, - { 0x08a2, 0x250c }, - { 0x08a3, 0x2500 }, - { 0x08a4, 0x2320 }, - { 0x08a5, 0x2321 }, - { 0x08a6, 0x2502 }, - { 0x08a7, 0x23a1 }, - { 0x08a8, 0x23a3 }, - { 0x08a9, 0x23a4 }, - { 0x08aa, 0x23a6 }, - { 0x08ab, 0x239b }, - { 0x08ac, 0x239d }, - { 0x08ad, 0x239e }, - { 0x08ae, 0x23a0 }, - { 0x08af, 0x23a8 }, - { 0x08b0, 0x23ac }, - { 0x08bc, 0x2264 }, - { 0x08bd, 0x2260 }, - { 0x08be, 0x2265 }, - { 0x08bf, 0x222b }, - { 0x08c0, 0x2234 }, - { 0x08c1, 0x221d }, - { 0x08c2, 0x221e }, - { 0x08c5, 0x2207 }, - { 0x08c8, 0x223c }, - { 0x08c9, 0x2243 }, - { 0x08cd, 0x21d4 }, - { 0x08ce, 0x21d2 }, - { 0x08cf, 0x2261 }, - { 0x08d6, 0x221a }, - { 0x08da, 0x2282 }, - { 0x08db, 0x2283 }, - { 0x08dc, 0x2229 }, - { 0x08dd, 0x222a }, - { 0x08de, 0x2227 }, - { 0x08df, 0x2228 }, - { 0x08ef, 0x2202 }, - { 0x08f6, 0x0192 }, - { 0x08fb, 0x2190 }, - { 0x08fc, 0x2191 }, - { 0x08fd, 0x2192 }, - { 0x08fe, 0x2193 }, - { 0x09e0, 0x25c6 }, - { 0x09e1, 0x2592 }, - { 0x09e2, 0x2409 }, - { 0x09e3, 0x240c }, - { 0x09e4, 0x240d }, - { 0x09e5, 0x240a }, - { 0x09e8, 0x2424 }, - { 0x09e9, 0x240b }, - { 0x09ea, 0x2518 }, - { 0x09eb, 0x2510 }, - { 0x09ec, 0x250c }, - { 0x09ed, 0x2514 }, - { 0x09ee, 0x253c }, - { 0x09ef, 0x23ba }, - { 0x09f0, 0x23bb }, - { 0x09f1, 0x2500 }, - { 0x09f2, 0x23bc }, - { 0x09f3, 0x23bd }, - { 0x09f4, 0x251c }, - { 0x09f5, 0x2524 }, - { 0x09f6, 0x2534 }, - { 0x09f7, 0x252c }, - { 0x09f8, 0x2502 }, - { 0x0aa1, 0x2003 }, - { 0x0aa2, 0x2002 }, - { 0x0aa3, 0x2004 }, - { 0x0aa4, 0x2005 }, - { 0x0aa5, 0x2007 }, - { 0x0aa6, 0x2008 }, - { 0x0aa7, 0x2009 }, - { 0x0aa8, 0x200a }, - { 0x0aa9, 0x2014 }, - { 0x0aaa, 0x2013 }, - { 0x0aae, 0x2026 }, - { 0x0aaf, 0x2025 }, - { 0x0ab0, 0x2153 }, - { 0x0ab1, 0x2154 }, - { 0x0ab2, 0x2155 }, - { 0x0ab3, 0x2156 }, - { 0x0ab4, 0x2157 }, - { 0x0ab5, 0x2158 }, - { 0x0ab6, 0x2159 }, - { 0x0ab7, 0x215a }, - { 0x0ab8, 0x2105 }, - { 0x0abb, 0x2012 }, - { 0x0abc, 0x2329 }, - { 0x0abe, 0x232a }, - { 0x0ac3, 0x215b }, - { 0x0ac4, 0x215c }, - { 0x0ac5, 0x215d }, - { 0x0ac6, 0x215e }, - { 0x0ac9, 0x2122 }, - { 0x0aca, 0x2613 }, - { 0x0acc, 0x25c1 }, - { 0x0acd, 0x25b7 }, - { 0x0ace, 0x25cb }, - { 0x0acf, 0x25af }, - { 0x0ad0, 0x2018 }, - { 0x0ad1, 0x2019 }, - { 0x0ad2, 0x201c }, - { 0x0ad3, 0x201d }, - { 0x0ad4, 0x211e }, - { 0x0ad6, 0x2032 }, - { 0x0ad7, 0x2033 }, - { 0x0ad9, 0x271d }, - { 0x0adb, 0x25ac }, - { 0x0adc, 0x25c0 }, - { 0x0add, 0x25b6 }, - { 0x0ade, 0x25cf }, - { 0x0adf, 0x25ae }, - { 0x0ae0, 0x25e6 }, - { 0x0ae1, 0x25ab }, - { 0x0ae2, 0x25ad }, - { 0x0ae3, 0x25b3 }, - { 0x0ae4, 0x25bd }, - { 0x0ae5, 0x2606 }, - { 0x0ae6, 0x2022 }, - { 0x0ae7, 0x25aa }, - { 0x0ae8, 0x25b2 }, - { 0x0ae9, 0x25bc }, - { 0x0aea, 0x261c }, - { 0x0aeb, 0x261e }, - { 0x0aec, 0x2663 }, - { 0x0aed, 0x2666 }, - { 0x0aee, 0x2665 }, - { 0x0af0, 0x2720 }, - { 0x0af1, 0x2020 }, - { 0x0af2, 0x2021 }, - { 0x0af3, 0x2713 }, - { 0x0af4, 0x2717 }, - { 0x0af5, 0x266f }, - { 0x0af6, 0x266d }, - { 0x0af7, 0x2642 }, - { 0x0af8, 0x2640 }, - { 0x0af9, 0x260e }, - { 0x0afa, 0x2315 }, - { 0x0afb, 0x2117 }, - { 0x0afc, 0x2038 }, - { 0x0afd, 0x201a }, - { 0x0afe, 0x201e }, - { 0x0ba3, 0x003c }, - { 0x0ba6, 0x003e }, - { 0x0ba8, 0x2228 }, - { 0x0ba9, 0x2227 }, - { 0x0bc0, 0x00af }, - { 0x0bc2, 0x22a5 }, - { 0x0bc3, 0x2229 }, - { 0x0bc4, 0x230a }, - { 0x0bc6, 0x005f }, - { 0x0bca, 0x2218 }, - { 0x0bcc, 0x2395 }, - { 0x0bce, 0x22a4 }, - { 0x0bcf, 0x25cb }, - { 0x0bd3, 0x2308 }, - { 0x0bd6, 0x222a }, - { 0x0bd8, 0x2283 }, - { 0x0bda, 0x2282 }, - { 0x0bdc, 0x22a2 }, - { 0x0bfc, 0x22a3 }, - { 0x0cdf, 0x2017 }, - { 0x0ce0, 0x05d0 }, - { 0x0ce1, 0x05d1 }, - { 0x0ce2, 0x05d2 }, - { 0x0ce3, 0x05d3 }, - { 0x0ce4, 0x05d4 }, - { 0x0ce5, 0x05d5 }, - { 0x0ce6, 0x05d6 }, - { 0x0ce7, 0x05d7 }, - { 0x0ce8, 0x05d8 }, - { 0x0ce9, 0x05d9 }, - { 0x0cea, 0x05da }, - { 0x0ceb, 0x05db }, - { 0x0cec, 0x05dc }, - { 0x0ced, 0x05dd }, - { 0x0cee, 0x05de }, - { 0x0cef, 0x05df }, - { 0x0cf0, 0x05e0 }, - { 0x0cf1, 0x05e1 }, - { 0x0cf2, 0x05e2 }, - { 0x0cf3, 0x05e3 }, - { 0x0cf4, 0x05e4 }, - { 0x0cf5, 0x05e5 }, - { 0x0cf6, 0x05e6 }, - { 0x0cf7, 0x05e7 }, - { 0x0cf8, 0x05e8 }, - { 0x0cf9, 0x05e9 }, - { 0x0cfa, 0x05ea }, - { 0x0da1, 0x0e01 }, - { 0x0da2, 0x0e02 }, - { 0x0da3, 0x0e03 }, - { 0x0da4, 0x0e04 }, - { 0x0da5, 0x0e05 }, - { 0x0da6, 0x0e06 }, - { 0x0da7, 0x0e07 }, - { 0x0da8, 0x0e08 }, - { 0x0da9, 0x0e09 }, - { 0x0daa, 0x0e0a }, - { 0x0dab, 0x0e0b }, - { 0x0dac, 0x0e0c }, - { 0x0dad, 0x0e0d }, - { 0x0dae, 0x0e0e }, - { 0x0daf, 0x0e0f }, - { 0x0db0, 0x0e10 }, - { 0x0db1, 0x0e11 }, - { 0x0db2, 0x0e12 }, - { 0x0db3, 0x0e13 }, - { 0x0db4, 0x0e14 }, - { 0x0db5, 0x0e15 }, - { 0x0db6, 0x0e16 }, - { 0x0db7, 0x0e17 }, - { 0x0db8, 0x0e18 }, - { 0x0db9, 0x0e19 }, - { 0x0dba, 0x0e1a }, - { 0x0dbb, 0x0e1b }, - { 0x0dbc, 0x0e1c }, - { 0x0dbd, 0x0e1d }, - { 0x0dbe, 0x0e1e }, - { 0x0dbf, 0x0e1f }, - { 0x0dc0, 0x0e20 }, - { 0x0dc1, 0x0e21 }, - { 0x0dc2, 0x0e22 }, - { 0x0dc3, 0x0e23 }, - { 0x0dc4, 0x0e24 }, - { 0x0dc5, 0x0e25 }, - { 0x0dc6, 0x0e26 }, - { 0x0dc7, 0x0e27 }, - { 0x0dc8, 0x0e28 }, - { 0x0dc9, 0x0e29 }, - { 0x0dca, 0x0e2a }, - { 0x0dcb, 0x0e2b }, - { 0x0dcc, 0x0e2c }, - { 0x0dcd, 0x0e2d }, - { 0x0dce, 0x0e2e }, - { 0x0dcf, 0x0e2f }, - { 0x0dd0, 0x0e30 }, - { 0x0dd1, 0x0e31 }, - { 0x0dd2, 0x0e32 }, - { 0x0dd3, 0x0e33 }, - { 0x0dd4, 0x0e34 }, - { 0x0dd5, 0x0e35 }, - { 0x0dd6, 0x0e36 }, - { 0x0dd7, 0x0e37 }, - { 0x0dd8, 0x0e38 }, - { 0x0dd9, 0x0e39 }, - { 0x0dda, 0x0e3a }, - { 0x0ddf, 0x0e3f }, - { 0x0de0, 0x0e40 }, - { 0x0de1, 0x0e41 }, - { 0x0de2, 0x0e42 }, - { 0x0de3, 0x0e43 }, - { 0x0de4, 0x0e44 }, - { 0x0de5, 0x0e45 }, - { 0x0de6, 0x0e46 }, - { 0x0de7, 0x0e47 }, - { 0x0de8, 0x0e48 }, - { 0x0de9, 0x0e49 }, - { 0x0dea, 0x0e4a }, - { 0x0deb, 0x0e4b }, - { 0x0dec, 0x0e4c }, - { 0x0ded, 0x0e4d }, - { 0x0df0, 0x0e50 }, - { 0x0df1, 0x0e51 }, - { 0x0df2, 0x0e52 }, - { 0x0df3, 0x0e53 }, - { 0x0df4, 0x0e54 }, - { 0x0df5, 0x0e55 }, - { 0x0df6, 0x0e56 }, - { 0x0df7, 0x0e57 }, - { 0x0df8, 0x0e58 }, - { 0x0df9, 0x0e59 }, - { 0x0ea1, 0x3131 }, - { 0x0ea2, 0x3132 }, - { 0x0ea3, 0x3133 }, - { 0x0ea4, 0x3134 }, - { 0x0ea5, 0x3135 }, - { 0x0ea6, 0x3136 }, - { 0x0ea7, 0x3137 }, - { 0x0ea8, 0x3138 }, - { 0x0ea9, 0x3139 }, - { 0x0eaa, 0x313a }, - { 0x0eab, 0x313b }, - { 0x0eac, 0x313c }, - { 0x0ead, 0x313d }, - { 0x0eae, 0x313e }, - { 0x0eaf, 0x313f }, - { 0x0eb0, 0x3140 }, - { 0x0eb1, 0x3141 }, - { 0x0eb2, 0x3142 }, - { 0x0eb3, 0x3143 }, - { 0x0eb4, 0x3144 }, - { 0x0eb5, 0x3145 }, - { 0x0eb6, 0x3146 }, - { 0x0eb7, 0x3147 }, - { 0x0eb8, 0x3148 }, - { 0x0eb9, 0x3149 }, - { 0x0eba, 0x314a }, - { 0x0ebb, 0x314b }, - { 0x0ebc, 0x314c }, - { 0x0ebd, 0x314d }, - { 0x0ebe, 0x314e }, - { 0x0ebf, 0x314f }, - { 0x0ec0, 0x3150 }, - { 0x0ec1, 0x3151 }, - { 0x0ec2, 0x3152 }, - { 0x0ec3, 0x3153 }, - { 0x0ec4, 0x3154 }, - { 0x0ec5, 0x3155 }, - { 0x0ec6, 0x3156 }, - { 0x0ec7, 0x3157 }, - { 0x0ec8, 0x3158 }, - { 0x0ec9, 0x3159 }, - { 0x0eca, 0x315a }, - { 0x0ecb, 0x315b }, - { 0x0ecc, 0x315c }, - { 0x0ecd, 0x315d }, - { 0x0ece, 0x315e }, - { 0x0ecf, 0x315f }, - { 0x0ed0, 0x3160 }, - { 0x0ed1, 0x3161 }, - { 0x0ed2, 0x3162 }, - { 0x0ed3, 0x3163 }, - { 0x0ed4, 0x11a8 }, - { 0x0ed5, 0x11a9 }, - { 0x0ed6, 0x11aa }, - { 0x0ed7, 0x11ab }, - { 0x0ed8, 0x11ac }, - { 0x0ed9, 0x11ad }, - { 0x0eda, 0x11ae }, - { 0x0edb, 0x11af }, - { 0x0edc, 0x11b0 }, - { 0x0edd, 0x11b1 }, - { 0x0ede, 0x11b2 }, - { 0x0edf, 0x11b3 }, - { 0x0ee0, 0x11b4 }, - { 0x0ee1, 0x11b5 }, - { 0x0ee2, 0x11b6 }, - { 0x0ee3, 0x11b7 }, - { 0x0ee4, 0x11b8 }, - { 0x0ee5, 0x11b9 }, - { 0x0ee6, 0x11ba }, - { 0x0ee7, 0x11bb }, - { 0x0ee8, 0x11bc }, - { 0x0ee9, 0x11bd }, - { 0x0eea, 0x11be }, - { 0x0eeb, 0x11bf }, - { 0x0eec, 0x11c0 }, - { 0x0eed, 0x11c1 }, - { 0x0eee, 0x11c2 }, - { 0x0eef, 0x316d }, - { 0x0ef0, 0x3171 }, - { 0x0ef1, 0x3178 }, - { 0x0ef2, 0x317f }, - { 0x0ef3, 0x3181 }, - { 0x0ef4, 0x3184 }, - { 0x0ef5, 0x3186 }, - { 0x0ef6, 0x318d }, - { 0x0ef7, 0x318e }, - { 0x0ef8, 0x11eb }, - { 0x0ef9, 0x11f0 }, - { 0x0efa, 0x11f9 }, - { 0x0eff, 0x20a9 }, - { 0x13a4, 0x20ac }, - { 0x13bc, 0x0152 }, - { 0x13bd, 0x0153 }, - { 0x13be, 0x0178 }, - { 0x20ac, 0x20ac }, - { 0xfe50, '`' }, - { 0xfe51, 0x00b4 }, - { 0xfe52, '^' }, - { 0xfe53, '~' }, - { 0xfe54, 0x00af }, - { 0xfe55, 0x02d8 }, - { 0xfe56, 0x02d9 }, - { 0xfe57, 0x00a8 }, - { 0xfe58, 0x02da }, - { 0xfe59, 0x02dd }, - { 0xfe5a, 0x02c7 }, - { 0xfe5b, 0x00b8 }, - { 0xfe5c, 0x02db }, - { 0xfe5d, 0x037a }, - { 0xfe5e, 0x309b }, - { 0xfe5f, 0x309c }, - { 0xfe63, '/' }, - { 0xfe64, 0x02bc }, - { 0xfe65, 0x02bd }, - { 0xfe66, 0x02f5 }, - { 0xfe67, 0x02f3 }, - { 0xfe68, 0x02cd }, - { 0xfe69, 0xa788 }, - { 0xfe6a, 0x02f7 }, - { 0xfe6e, ',' }, - { 0xfe6f, 0x00a4 }, - { 0xfe80, 'a' }, /* XK_dead_a */ - { 0xfe81, 'A' }, /* XK_dead_A */ - { 0xfe82, 'e' }, /* XK_dead_e */ - { 0xfe83, 'E' }, /* XK_dead_E */ - { 0xfe84, 'i' }, /* XK_dead_i */ - { 0xfe85, 'I' }, /* XK_dead_I */ - { 0xfe86, 'o' }, /* XK_dead_o */ - { 0xfe87, 'O' }, /* XK_dead_O */ - { 0xfe88, 'u' }, /* XK_dead_u */ - { 0xfe89, 'U' }, /* XK_dead_U */ - { 0xfe8a, 0x0259 }, - { 0xfe8b, 0x018f }, - { 0xfe8c, 0x00b5 }, - { 0xfe90, '_' }, - { 0xfe91, 0x02c8 }, - { 0xfe92, 0x02cc }, - { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, - { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, - { 0xffab /*XKB_KEY_KP_Add*/, '+' }, - { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, - { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, - { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, - { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, - { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } -}; - -_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { - _SOKOL_UNUSED(display); - _sapp.x11.error_code = event->error_code; - return 0; -} - -_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { - _sapp.x11.error_code = Success; - XSetErrorHandler(_sapp_x11_error_handler); -} - -_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { - XSync(_sapp.x11.display, False); - XSetErrorHandler(NULL); -} - -_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { - _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); - _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); - _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); - _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); - _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); - _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); - _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); - _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); - _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); - if (_sapp.drop.enabled) { - _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); - _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); - _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); - _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); - _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); - _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); - _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); - _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); - _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); - _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); - } - - /* check Xi extension for raw mouse input */ - if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { - _sapp.x11.xi.major = 2; - _sapp.x11.xi.minor = 0; - if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { - _sapp.x11.xi.available = true; - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { - /* from GLFW: - - NOTE: Default to the display-wide DPI as we don't currently have a policy - for which monitor a window is considered to be on - - _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * - 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); - - NOTE: Basing the scale on Xft.dpi where available should provide the most - consistent user experience (matches Qt, Gtk, etc), although not - always the most accurate one - */ - bool dpi_ok = false; - char* rms = XResourceManagerString(_sapp.x11.display); - if (rms) { - XrmDatabase db = XrmGetStringDatabase(rms); - if (db) { - XrmValue value; - char* type = NULL; - if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { - if (type && strcmp(type, "String") == 0) { - _sapp.x11.dpi = atof(value.addr); - dpi_ok = true; - } - } - XrmDestroyDatabase(db); - } - } - // fallback if querying DPI had failed: assume the standard DPI 96.0f - if (!dpi_ok) { - _sapp.x11.dpi = 96.0f; - _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); - } -} - -#if defined(_SAPP_GLX) - -_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { - if (extensions) { - return _sapp_glx_has_ext(ext, extensions); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) -{ - if (_sapp.glx.GetProcAddress) { - return (void*) _sapp.glx.GetProcAddress(procname); - } - else if (_sapp.glx.GetProcAddressARB) { - return (void*) _sapp.glx.GetProcAddressARB(procname); - } - else { - return dlsym(_sapp.glx.libgl, procname); - } -} - -_SOKOL_PRIVATE void _sapp_glx_init() { - const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; - for (int i = 0; sonames[i]; i++) { - _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); - if (_sapp.glx.libgl) { - break; - } - } - if (!_sapp.glx.libgl) { - _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); - } - _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); - _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); - _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); - _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); - _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); - _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); - _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); - _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); - _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); - _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); - _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); - _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); - _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); - _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); - if (!_sapp.glx.GetFBConfigs || - !_sapp.glx.GetFBConfigAttrib || - !_sapp.glx.GetClientString || - !_sapp.glx.QueryExtension || - !_sapp.glx.QueryVersion || - !_sapp.glx.DestroyContext || - !_sapp.glx.MakeCurrent || - !_sapp.glx.SwapBuffers || - !_sapp.glx.QueryExtensionsString || - !_sapp.glx.CreateWindow || - !_sapp.glx.DestroyWindow || - !_sapp.glx.GetProcAddress || - !_sapp.glx.GetProcAddressARB || - !_sapp.glx.GetVisualFromFBConfig) - { - _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); - } - - if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { - _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); - } - if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { - _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); - } - if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { - _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); - } - const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); - if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { - _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); - _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; - } - if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { - _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); - _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; - } - _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); - if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { - _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); - _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; - } - _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); -} - -_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { - int value; - _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); - return value; -} - -_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { - GLXFBConfig* native_configs; - _sapp_gl_fbconfig* usable_configs; - const _sapp_gl_fbconfig* closest; - int i, native_count, usable_count; - const char* vendor; - bool trust_window_bit = true; - - /* HACK: This is a (hopefully temporary) workaround for Chromium - (VirtualBox GL) not setting the window bit on any GLXFBConfigs - */ - vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); - if (vendor && strcmp(vendor, "Chromium") == 0) { - trust_window_bit = false; - } - - native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); - if (!native_configs || !native_count) { - _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); - } - - usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); - usable_count = 0; - for (i = 0; i < native_count; i++) { - const GLXFBConfig n = native_configs[i]; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - - /* Only consider RGBA GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { - continue; - } - /* Only consider window GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { - if (trust_window_bit) { - continue; - } - } - u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); - u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); - u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); - u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); - u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); - u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); - if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { - u->doublebuffer = true; - } - if (_sapp.glx.ARB_multisample) { - u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); - } - u->handle = (uintptr_t) n; - usable_count++; - } - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - GLXFBConfig result = 0; - if (closest) { - result = (GLXFBConfig) closest->handle; - } - XFree(native_configs); - _sapp_free(usable_configs); - return result; -} - -_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native) { - _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); - } - XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); - if (!result) { - _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); - } - *visual = result->visual; - *depth = result->depth; - XFree(result); -} - -_SOKOL_PRIVATE void _sapp_glx_create_context(void) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native){ - _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); - } - if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { - _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); - } - _sapp_x11_grab_error_handler(); - const int attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, - GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - 0, 0 - }; - _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); - if (!_sapp.glx.ctx) { - _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); - } - _sapp_x11_release_error_handler(); - _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); - if (!_sapp.glx.window) { - _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { - if (_sapp.glx.window) { - _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); - _sapp.glx.window = 0; - } - if (_sapp.glx.ctx) { - _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); - _sapp.glx.ctx = 0; - } -} - -_SOKOL_PRIVATE void _sapp_glx_make_current(void) { - _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); -} - -_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { - _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); -} - -_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { - _sapp_glx_make_current(); - if (_sapp.glx.EXT_swap_control) { - _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); - } - else if (_sapp.glx.MESA_swap_control) { - _sapp.glx.SwapIntervalMESA(interval); - } -} - -#endif /* _SAPP_GLX */ - -_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { - XEvent event; - _sapp_clear(&event, sizeof(event)); - - event.type = ClientMessage; - event.xclient.window = _sapp.x11.window; - event.xclient.format = 32; - event.xclient.message_type = type; - event.xclient.data.l[0] = a; - event.xclient.data.l[1] = b; - event.xclient.data.l[2] = c; - event.xclient.data.l[3] = d; - event.xclient.data.l[4] = e; - - XSendEvent(_sapp.x11.display, _sapp.x11.root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &event); -} - -_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { - XWindowAttributes attribs; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); - _sapp.window_width = attribs.width; - _sapp.window_height = attribs.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; -} - -_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { - /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ - if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { - if (enable) { - const int _NET_WM_STATE_ADD = 1; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_ADD, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - else { - const int _NET_WM_STATE_REMOVE = 0; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_REMOVE, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { - SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); - const int w = 16; - const int h = 16; - XcursorImage* img = XcursorImageCreate(w, h); - SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); - img->xhot = 0; - img->yhot = 0; - const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); - _sapp_clear(img->pixels, num_bytes); - _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); - XcursorImageDestroy(img); -} - - _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - SOKOL_ASSERT(_sapp.x11.display); - if (theme) { - XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); - if (img) { - _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); - XcursorImageDestroy(img); - } - } - if (0 == _sapp.x11.cursors[cursor]) { - _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); - } -} - -_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { - SOKOL_ASSERT(_sapp.x11.display); - const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); - const int size = XcursorGetDefaultSize(_sapp.x11.display); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); - _sapp_x11_create_hidden_cursor(); -} - -_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { - SOKOL_ASSERT(_sapp.x11.display); - if (_sapp.x11.hidden_cursor) { - XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); - _sapp.x11.hidden_cursor = 0; - } - for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { - if (_sapp.x11.cursors[i]) { - XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); - _sapp.x11.cursors[i] = 0; - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { - _sapp.fullscreen = !_sapp.fullscreen; - _sapp_x11_set_fullscreen(_sapp.fullscreen); - _sapp_x11_query_window_size(); -} - -_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (shown) { - if (_sapp.x11.cursors[cursor]) { - XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); - } - else { - XUndefineCursor(_sapp.x11.display, _sapp.x11.window); - } - } - else { - XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - if (_sapp.mouse.locked) { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISetMask(mask, XI_RawMotion); - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XGrabPointer(_sapp.x11.display, // display - _sapp.x11.window, // grab_window - True, // owner_events - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask - GrabModeAsync, // pointer_mode - GrabModeAsync, // keyboard_mode - _sapp.x11.window, // confine_to - _sapp.x11.hidden_cursor, // cursor - CurrentTime); // time - } - else { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[] = { 0 }; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); - XUngrabPointer(_sapp.x11.display, CurrentTime); - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { - Xutf8SetWMProperties(_sapp.x11.display, - _sapp.x11.window, - _sapp.window_title, _sapp.window_title, - NULL, 0, NULL, NULL, NULL); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { - SOKOL_ASSERT((num_images > 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*) _sapp_malloc_clear((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); - _sapp_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; - _sapp_clear(&wa, sizeof(wa)); - const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; - wa.colormap = _sapp.x11.colormap; - wa.border_pixel = 0; - wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | - PointerMotionMask | ButtonPressMask | ButtonReleaseMask | - ExposureMask | FocusChangeMask | VisibilityChangeMask | - EnterWindowMask | LeaveWindowMask | PropertyChangeMask; - - int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); - int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); - int window_width = _sapp.window_width; - int window_height = _sapp.window_height; - if (0 == window_width) { - window_width = (display_width * 4) / 5; - } - if (0 == window_height) { - window_height = (display_height * 4) / 5; - } - int window_xpos = (display_width - window_width) / 2; - int window_ypos = (display_height - window_height) / 2; - if (window_xpos < 0) { - window_xpos = 0; - } - if (window_ypos < 0) { - window_ypos = 0; - } - _sapp_x11_grab_error_handler(); - _sapp.x11.window = XCreateWindow(_sapp.x11.display, - _sapp.x11.root, - window_xpos, - window_ypos, - (uint32_t)window_width, - (uint32_t)window_height, - 0, /* border width */ - depth, /* color depth */ - InputOutput, - visual, - wamask, - &wa); - _sapp_x11_release_error_handler(); - if (!_sapp.x11.window) { - _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); - } - Atom protocols[] = { - _sapp.x11.WM_DELETE_WINDOW - }; - XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); - - XSizeHints* hints = XAllocSizeHints(); - hints->flags = (PWinGravity | PPosition | PSize); - hints->win_gravity = StaticGravity; - hints->x = window_xpos; - hints->y = window_ypos; - hints->width = window_width; - hints->height = window_height; - XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); - XFree(hints); - - /* announce support for drag'n'drop */ - if (_sapp.drop.enabled) { - const Atom version = _SAPP_X11_XDND_VERSION; - XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); - } - _sapp_x11_update_window_title(); - _sapp_x11_query_window_size(); -} - -_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { - if (_sapp.x11.window) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XDestroyWindow(_sapp.x11.display, _sapp.x11.window); - _sapp.x11.window = 0; - } - if (_sapp.x11.colormap) { - XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); - _sapp.x11.colormap = 0; - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { - XWindowAttributes wa; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); - return wa.map_state == IsViewable; -} - -_SOKOL_PRIVATE void _sapp_x11_show_window(void) { - if (!_sapp_x11_window_visible()) { - XMapWindow(_sapp.x11.display, _sapp.x11.window); - XRaiseWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); - } -} - -_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { - Atom actualType; - int actualFormat; - unsigned long itemCount, bytesAfter; - XGetWindowProperty(_sapp.x11.display, - window, - property, - 0, - LONG_MAX, - False, - type, - &actualType, - &actualFormat, - &itemCount, - &bytesAfter, - value); - return itemCount; -} - -_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { - int result = WithdrawnState; - struct { - CARD32 state; - Window icon; - } *state = NULL; - - if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { - result = (int)state->state; - } - if (state) { - XFree(state); - } - return result; -} - -_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; - } - if (x11_mods & ControlMask) { - mods |= SAPP_MODIFIER_CTRL; - } - if (x11_mods & Mod1Mask) { - mods |= SAPP_MODIFIER_ALT; - } - 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; -} - -_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { - switch (event->xbutton.button) { - case Button1: return SAPP_MOUSEBUTTON_LEFT; - case Button2: return SAPP_MOUSEBUTTON_MIDDLE; - case Button3: return SAPP_MOUSEBUTTON_RIGHT; - default: return SAPP_MOUSEBUTTON_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y) { - if (!_sapp.mouse.locked) { - const float new_x = (float) x; - const float new_y = (float) y; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = mods; - _sapp.event.scroll_x = x; - _sapp.event.scroll_y = y; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = chr; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { - int dummy; - KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); - SOKOL_ASSERT(keysyms); - KeySym keysym = keysyms[0]; - XFree(keysyms); - switch (keysym) { - case XK_Escape: return SAPP_KEYCODE_ESCAPE; - case XK_Tab: return SAPP_KEYCODE_TAB; - case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; - case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; - case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; - case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; - case XK_Meta_L: - case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; - case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ - case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ - case XK_Meta_R: - case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; - case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; - case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; - case XK_Menu: return SAPP_KEYCODE_MENU; - case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; - case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; - case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; - case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; - case XK_Pause: return SAPP_KEYCODE_PAUSE; - case XK_Delete: return SAPP_KEYCODE_DELETE; - case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; - case XK_Return: return SAPP_KEYCODE_ENTER; - case XK_Home: return SAPP_KEYCODE_HOME; - case XK_End: return SAPP_KEYCODE_END; - case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; - case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; - case XK_Insert: return SAPP_KEYCODE_INSERT; - case XK_Left: return SAPP_KEYCODE_LEFT; - case XK_Right: return SAPP_KEYCODE_RIGHT; - case XK_Down: return SAPP_KEYCODE_DOWN; - case XK_Up: return SAPP_KEYCODE_UP; - case XK_F1: return SAPP_KEYCODE_F1; - case XK_F2: return SAPP_KEYCODE_F2; - case XK_F3: return SAPP_KEYCODE_F3; - case XK_F4: return SAPP_KEYCODE_F4; - case XK_F5: return SAPP_KEYCODE_F5; - case XK_F6: return SAPP_KEYCODE_F6; - case XK_F7: return SAPP_KEYCODE_F7; - case XK_F8: return SAPP_KEYCODE_F8; - case XK_F9: return SAPP_KEYCODE_F9; - case XK_F10: return SAPP_KEYCODE_F10; - case XK_F11: return SAPP_KEYCODE_F11; - case XK_F12: return SAPP_KEYCODE_F12; - case XK_F13: return SAPP_KEYCODE_F13; - case XK_F14: return SAPP_KEYCODE_F14; - case XK_F15: return SAPP_KEYCODE_F15; - case XK_F16: return SAPP_KEYCODE_F16; - case XK_F17: return SAPP_KEYCODE_F17; - case XK_F18: return SAPP_KEYCODE_F18; - case XK_F19: return SAPP_KEYCODE_F19; - case XK_F20: return SAPP_KEYCODE_F20; - case XK_F21: return SAPP_KEYCODE_F21; - case XK_F22: return SAPP_KEYCODE_F22; - case XK_F23: return SAPP_KEYCODE_F23; - case XK_F24: return SAPP_KEYCODE_F24; - case XK_F25: return SAPP_KEYCODE_F25; - - case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; - case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; - case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; - case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; - - case XK_KP_Insert: return SAPP_KEYCODE_KP_0; - case XK_KP_End: return SAPP_KEYCODE_KP_1; - case XK_KP_Down: return SAPP_KEYCODE_KP_2; - case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; - case XK_KP_Left: return SAPP_KEYCODE_KP_4; - case XK_KP_Begin: return SAPP_KEYCODE_KP_5; - case XK_KP_Right: return SAPP_KEYCODE_KP_6; - case XK_KP_Home: return SAPP_KEYCODE_KP_7; - case XK_KP_Up: return SAPP_KEYCODE_KP_8; - case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; - case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; - case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; - case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; - - case XK_a: return SAPP_KEYCODE_A; - case XK_b: return SAPP_KEYCODE_B; - case XK_c: return SAPP_KEYCODE_C; - case XK_d: return SAPP_KEYCODE_D; - case XK_e: return SAPP_KEYCODE_E; - case XK_f: return SAPP_KEYCODE_F; - case XK_g: return SAPP_KEYCODE_G; - case XK_h: return SAPP_KEYCODE_H; - case XK_i: return SAPP_KEYCODE_I; - case XK_j: return SAPP_KEYCODE_J; - case XK_k: return SAPP_KEYCODE_K; - case XK_l: return SAPP_KEYCODE_L; - case XK_m: return SAPP_KEYCODE_M; - case XK_n: return SAPP_KEYCODE_N; - case XK_o: return SAPP_KEYCODE_O; - case XK_p: return SAPP_KEYCODE_P; - case XK_q: return SAPP_KEYCODE_Q; - case XK_r: return SAPP_KEYCODE_R; - case XK_s: return SAPP_KEYCODE_S; - case XK_t: return SAPP_KEYCODE_T; - case XK_u: return SAPP_KEYCODE_U; - case XK_v: return SAPP_KEYCODE_V; - case XK_w: return SAPP_KEYCODE_W; - case XK_x: return SAPP_KEYCODE_X; - case XK_y: return SAPP_KEYCODE_Y; - case XK_z: return SAPP_KEYCODE_Z; - case XK_1: return SAPP_KEYCODE_1; - case XK_2: return SAPP_KEYCODE_2; - case XK_3: return SAPP_KEYCODE_3; - case XK_4: return SAPP_KEYCODE_4; - case XK_5: return SAPP_KEYCODE_5; - case XK_6: return SAPP_KEYCODE_6; - case XK_7: return SAPP_KEYCODE_7; - case XK_8: return SAPP_KEYCODE_8; - case XK_9: return SAPP_KEYCODE_9; - case XK_0: return SAPP_KEYCODE_0; - case XK_space: return SAPP_KEYCODE_SPACE; - case XK_minus: return SAPP_KEYCODE_MINUS; - case XK_equal: return SAPP_KEYCODE_EQUAL; - case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; - case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; - case XK_backslash: return SAPP_KEYCODE_BACKSLASH; - case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; - case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; - case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; - case XK_comma: return SAPP_KEYCODE_COMMA; - case XK_period: return SAPP_KEYCODE_PERIOD; - case XK_slash: return SAPP_KEYCODE_SLASH; - case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ - default: return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { - int min = 0; - int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; - int mid; - - /* First check for Latin-1 characters (1:1 mapping) */ - if ((keysym >= 0x0020 && keysym <= 0x007e) || - (keysym >= 0x00a0 && keysym <= 0x00ff)) - { - return keysym; - } - - /* Also check for directly encoded 24-bit UCS characters */ - if ((keysym & 0xff000000) == 0x01000000) { - return keysym & 0x00ffffff; - } - - /* Binary search in table */ - while (max >= min) { - mid = (min + max) / 2; - if (_sapp_x11_keysymtab[mid].keysym < keysym) { - min = mid + 1; - } - else if (_sapp_x11_keysymtab[mid].keysym > keysym) { - max = mid - 1; - } - else { - return _sapp_x11_keysymtab[mid].ucs; - } - } - - /* No matching Unicode value found */ - return -1; -} - -_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { - SOKOL_ASSERT(src); - SOKOL_ASSERT(_sapp.drop.buffer); - - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - - /* - src is (potentially percent-encoded) string made of one or multiple paths - separated by \r\n, each path starting with 'file://' - */ - bool err = false; - int src_count = 0; - char src_chr = 0; - char* dst_ptr = _sapp.drop.buffer; - const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 - while (0 != (src_chr = *src++)) { - src_count++; - char dst_chr = 0; - /* check leading 'file://' */ - if (src_count <= 7) { - if (((src_count == 1) && (src_chr != 'f')) || - ((src_count == 2) && (src_chr != 'i')) || - ((src_count == 3) && (src_chr != 'l')) || - ((src_count == 4) && (src_chr != 'e')) || - ((src_count == 5) && (src_chr != ':')) || - ((src_count == 6) && (src_chr != '/')) || - ((src_count == 7) && (src_chr != '/'))) - { - _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); - err = true; - break; - } - } - else if (src_chr == '\r') { - // skip - } - else if (src_chr == '\n') { - src_count = 0; - _sapp.drop.num_files++; - // too many files is not an error - if (_sapp.drop.num_files >= _sapp.drop.max_files) { - break; - } - dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; - dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); - } - else if ((src_chr == '%') && src[0] && src[1]) { - // a percent-encoded byte (most likely UTF-8 multibyte sequence) - const char digits[3] = { src[0], src[1], 0 }; - src += 2; - dst_chr = (char) strtol(digits, 0, 16); - } - else { - dst_chr = src_chr; - } - if (dst_chr) { - // dst_end_ptr already has adjustment for terminating zero - if (dst_ptr < dst_end_ptr) { - *dst_ptr++ = dst_chr; - } - else { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - err = true; - break; - } - } - } - if (err) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - return false; - } - else { - return true; - } -} - -// XLib manual says keycodes are in the range [8, 255] inclusive. -// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html -static bool _sapp_x11_keycodes[256]; - -_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { - Bool filtered = XFilterEvent(event, None); - switch (event->type) { - case GenericEvent: - if (_sapp.mouse.locked && _sapp.x11.xi.available) { - if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { - if (XGetEventData(_sapp.x11.display, &event->xcookie)) { - if (event->xcookie.evtype == XI_RawMotion) { - XIRawEvent* re = (XIRawEvent*) event->xcookie.data; - if (re->valuators.mask_len) { - const double* values = re->raw_values; - if (XIMaskIsSet(re->valuators.mask, 0)) { - _sapp.mouse.dx = (float) *values; - values++; - } - if (XIMaskIsSet(re->valuators.mask, 1)) { - _sapp.mouse.dy = (float) *values; - } - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); - } - } - XFreeEventData(_sapp.x11.display, &event->xcookie); - } - } - } - break; - case FocusIn: - // NOTE: ignoring 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: ignoring 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: - { - int keycode = (int)event->xkey.keycode; - const sapp_keycode key = _sapp_x11_translate_key(keycode); - bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; - _sapp_x11_keycodes[keycode & 0xFF] = true; - 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); - } - KeySym keysym; - XLookupString(&event->xkey, NULL, 0, &keysym, NULL); - int32_t chr = _sapp_x11_keysym_to_unicode(keysym); - if (chr > 0) { - _sapp_x11_char_event((uint32_t)chr, repeat, mods); - } - } - break; - case KeyRelease: - { - int keycode = (int)event->xkey.keycode; - const sapp_keycode key = _sapp_x11_translate_key(keycode); - _sapp_x11_keycodes[keycode & 0xFF] = false; - if (key != SAPP_KEYCODE_INVALID) { - 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); - } - } - break; - case ButtonPress: - { - _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); - const sapp_mousebutton btn = _sapp_x11_translate_button(event); - 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); - } - else { - /* might be a scroll event */ - switch (event->xbutton.button) { - case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; - case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; - case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; - case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; - } - } - } - break; - case ButtonRelease: - { - _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y); - const sapp_mousebutton btn = _sapp_x11_translate_button(event); - if (btn != SAPP_MOUSEBUTTON_INVALID) { - 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); - } - } - break; - case EnterNotify: - /* don't send enter/leave events while mouse button held down */ - if (0 == _sapp.x11.mouse_buttons) { - _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y); - _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_update(event->xcrossing.x, event->xcrossing.y); - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); - } - break; - case MotionNotify: - if (!_sapp.mouse.locked) { - _sapp_x11_mouse_update(event->xmotion.x, event->xmotion.y); - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); - } - break; - case ConfigureNotify: - if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { - _sapp.window_width = event->xconfigure.width; - _sapp.window_height = event->xconfigure.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); - } - break; - case PropertyNotify: - if (event->xproperty.state == PropertyNewValue) { - if (event->xproperty.atom == _sapp.x11.WM_STATE) { - const int state = _sapp_x11_get_window_state(); - if (state != _sapp.x11.window_state) { - _sapp.x11.window_state = state; - if (state == IconicState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else if (state == NormalState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - } - break; - case ClientMessage: - if (filtered) { - return; - } - if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { - const Atom protocol = (Atom)event->xclient.data.l[0]; - if (protocol == _sapp.x11.WM_DELETE_WINDOW) { - _sapp.quit_requested = true; - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { - const bool is_list = 0 != (event->xclient.data.l[1] & 1); - _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; - _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _sapp.x11.xdnd.format = None; - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - uint32_t count = 0; - Atom* formats = 0; - if (is_list) { - count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); - } - else { - count = 3; - formats = (Atom*) event->xclient.data.l + 2; - } - for (uint32_t i = 0; i < count; i++) { - if (formats[i] == _sapp.x11.xdnd.text_uri_list) { - _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; - break; - } - } - if (is_list && formats) { - XFree(formats); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - Time time = CurrentTime; - if (_sapp.x11.xdnd.format) { - if (_sapp.x11.xdnd.version >= 1) { - time = (Time)event->xclient.data.l[2]; - } - XConvertSelection(_sapp.x11.display, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.xdnd.format, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.window, - time); - } - else if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = 0; // drag was rejected - reply.xclient.data.l[2] = None; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { - /* drag operation has moved over the window - FIXME: we could track the mouse position here, but - this isn't implemented on other platforms either so far - */ - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - if (_sapp.x11.xdnd.format) { - /* reply that we are ready to copy the dragged data */ - reply.xclient.data.l[1] = 1; // accept with no rectangle - if (_sapp.x11.xdnd.version >= 2) { - reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; - } - } - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - break; - case SelectionNotify: - if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { - char* data = 0; - uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, - event->xselection.property, - event->xselection.target, - (unsigned char**) &data); - if (_sapp.drop.enabled && result) { - if (_sapp_x11_parse_dropped_files_list(data)) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp_call_event(&_sapp.event); - } - } - } - if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - break; - case DestroyNotify: - break; - } -} - -#if !defined(_SAPP_GLX) - -_SOKOL_PRIVATE void _sapp_egl_init(void) { -#if defined(SOKOL_GLCORE33) - if (!eglBindAPI(EGL_OPENGL_API)) { - _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); - } -#else - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); - } -#endif - - _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); - if (EGL_NO_DISPLAY == _sapp.egl.display) { - _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); - } - - EGLint major, minor; - if (!eglInitialize(_sapp.egl.display, &major, &minor)) { - _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); - } - - EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; - EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; - const EGLint config_attrs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - #if defined(SOKOL_GLCORE33) - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - #elif defined(SOKOL_GLES3) - EGL_RENDERABLE_TYPE, _sapp.desc.gl_force_gles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES3_BIT, - #else - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - #endif - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, alpha_size, - EGL_DEPTH_SIZE, 24, - EGL_STENCIL_SIZE, 8, - EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0, - EGL_SAMPLES, sample_count, - EGL_NONE, - }; - - EGLConfig egl_configs[32]; - EGLint config_count; - if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) { - _SAPP_PANIC(LINUX_EGL_NO_CONFIGS); - } - - EGLConfig config = egl_configs[0]; - for (int i = 0; i < config_count; ++i) { - EGLConfig c = egl_configs[i]; - EGLint r, g, b, a, d, s, n; - if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) && - eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) && - (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) { - config = c; - break; - } - } - - EGLint visual_id; - if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) { - _SAPP_PANIC(LINUX_EGL_NO_NATIVE_VISUAL); - } - - XVisualInfo visual_info_template; - _sapp_clear(&visual_info_template, sizeof(visual_info_template)); - visual_info_template.visualid = (VisualID)visual_id; - - int num_visuals; - XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals); - if (!visual_info) { - _SAPP_PANIC(LINUX_EGL_GET_VISUAL_INFO_FAILED); - } - - _sapp_x11_create_window(visual_info->visual, visual_info->depth); - XFree(visual_info); - - _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL); - if (EGL_NO_SURFACE == _sapp.egl.surface) { - _SAPP_PANIC(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED); - } - - EGLint ctx_attrs[] = { - #if defined(SOKOL_GLCORE33) - EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, - EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, - EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, - #elif defined(SOKOL_GLES3) - EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, - #else - EGL_CONTEXT_CLIENT_VERSION, 2, - #endif - EGL_NONE, - }; - - _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs); - if (EGL_NO_CONTEXT == _sapp.egl.context) { - _SAPP_PANIC(LINUX_EGL_CREATE_CONTEXT_FAILED); - } - - if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) { - _SAPP_PANIC(LINUX_EGL_MAKE_CURRENT_FAILED); - } - - eglSwapInterval(_sapp.egl.display, _sapp.swap_interval); - -#if defined(SOKOL_GLES3) - _sapp.gles2_fallback = _sapp.desc.gl_force_gles2; -#endif -} - -_SOKOL_PRIVATE void _sapp_egl_destroy(void) { - if (_sapp.egl.display != EGL_NO_DISPLAY) { - eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (_sapp.egl.context != EGL_NO_CONTEXT) { - eglDestroyContext(_sapp.egl.display, _sapp.egl.context); - _sapp.egl.context = EGL_NO_CONTEXT; - } - - if (_sapp.egl.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.egl.display, _sapp.egl.surface); - _sapp.egl.surface = EGL_NO_SURFACE; - } - - eglTerminate(_sapp.egl.display); - _sapp.egl.display = EGL_NO_DISPLAY; - } -} - -#endif /* _SAPP_GLX */ - -_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { - /* The following lines are here to trigger a linker error instead of an - obscure runtime error if the user has forgotten to add -pthread to - the compiler or linker options. They have no other purpose. - */ - pthread_attr_t pthread_attr; - pthread_attr_init(&pthread_attr); - pthread_attr_destroy(&pthread_attr); - - _sapp_init_state(desc); - _sapp.x11.window_state = NormalState; - - XInitThreads(); - XrmInitialize(); - _sapp.x11.display = XOpenDisplay(NULL); - if (!_sapp.x11.display) { - _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); - } - _sapp.x11.screen = DefaultScreen(_sapp.x11.display); - _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); - XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); - _sapp_x11_query_system_dpi(); - _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; - _sapp_x11_init_extensions(); - _sapp_x11_create_cursors(); -#if defined(_SAPP_GLX) - _sapp_glx_init(); - Visual* visual = 0; - int depth = 0; - _sapp_glx_choose_visual(&visual, &depth); - _sapp_x11_create_window(visual, depth); - _sapp_glx_create_context(); - _sapp_glx_swapinterval(_sapp.swap_interval); -#else - _sapp_egl_init(); -#endif - sapp_set_icon(&desc->icon); - _sapp.valid = true; - _sapp_x11_show_window(); - if (_sapp.fullscreen) { - _sapp_x11_set_fullscreen(true); - } - - XFlush(_sapp.x11.display); - while (!_sapp.quit_ordered) { - _sapp_timing_measure(&_sapp.timing); - int count = XPending(_sapp.x11.display); - while (count--) { - XEvent event; - XNextEvent(_sapp.x11.display, &event); - _sapp_x11_process_event(&event); - } - _sapp_frame(); -#if defined(_SAPP_GLX) - _sapp_glx_swap_buffers(); -#else - eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); -#endif - XFlush(_sapp.x11.display); - /* handle quit-requested, either from window or from sapp_request_quit() */ - if (_sapp.quit_requested && !_sapp.quit_ordered) { - /* give user code a chance to intervene */ - _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - } - _sapp_call_cleanup(); -#if defined(_SAPP_GLX) - _sapp_glx_destroy_context(); -#else - _sapp_egl_destroy(); -#endif - _sapp_x11_destroy_window(); - _sapp_x11_destroy_cursors(); - XCloseDisplay(_sapp.x11.display); - _sapp_discard_state(); -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_linux_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_LINUX */ - -// ██████ ██ ██ ██████ ██ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██ ██ ██████ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██████ ██████ ███████ ██ ██████ -// -// >>public -#if defined(SOKOL_NO_ENTRY) -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - SOKOL_ASSERT(desc); - #if defined(_SAPP_MACOS) - _sapp_macos_run(desc); - #elif defined(_SAPP_IOS) - _sapp_ios_run(desc); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_run(desc); - #elif defined(_SAPP_WIN32) - _sapp_win32_run(desc); - #elif defined(_SAPP_LINUX) - _sapp_linux_run(desc); - #else - #error "sapp_run() not supported on this platform" - #endif -} - -/* this is just a stub so the linker doesn't complain */ -sapp_desc sokol_main(int argc, char* argv[]) { - _SOKOL_UNUSED(argc); - _SOKOL_UNUSED(argv); - sapp_desc desc; - _sapp_clear(&desc, sizeof(desc)); - return desc; -} -#else -/* likewise, in normal mode, sapp_run() is just an empty stub */ -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - _SOKOL_UNUSED(desc); -} -#endif - -SOKOL_API_IMPL bool sapp_isvalid(void) { - return _sapp.valid; -} - -SOKOL_API_IMPL void* sapp_userdata(void) { - return _sapp.desc.user_data; -} - -SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { - return _sapp.desc; -} - -SOKOL_API_IMPL uint64_t sapp_frame_count(void) { - return _sapp.frame_count; -} - -SOKOL_API_IMPL double sapp_frame_duration(void) { - return _sapp_timing_get_avg(&_sapp.timing); -} - -SOKOL_API_IMPL int sapp_width(void) { - return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; -} - -SOKOL_API_IMPL float sapp_widthf(void) { - return (float)sapp_width(); -} - -SOKOL_API_IMPL int sapp_height(void) { - return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; -} - -SOKOL_API_IMPL float sapp_heightf(void) { - return (float)sapp_height(); -} - -SOKOL_API_IMPL int sapp_color_format(void) { - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - switch (_sapp.emsc.wgpu.render_format) { - case WGPUTextureFormat_RGBA8Unorm: - return _SAPP_PIXELFORMAT_RGBA8; - case WGPUTextureFormat_BGRA8Unorm: - return _SAPP_PIXELFORMAT_BGRA8; - default: - SOKOL_UNREACHABLE; - return 0; - } - #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) - return _SAPP_PIXELFORMAT_BGRA8; - #else - return _SAPP_PIXELFORMAT_RGBA8; - #endif -} - -SOKOL_API_IMPL int sapp_depth_format(void) { - return _SAPP_PIXELFORMAT_DEPTH_STENCIL; -} - -SOKOL_API_IMPL int sapp_sample_count(void) { - return _sapp.sample_count; -} - -SOKOL_API_IMPL bool sapp_high_dpi(void) { - return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); -} - -SOKOL_API_IMPL float sapp_dpi_scale(void) { - return _sapp.dpi_scale; -} - -SOKOL_APP_IMPL const void* sapp_egl_get_display(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_ANDROID) - return _sapp.android.display; - #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) - return _sapp.egl.display; - #else - return 0; - #endif -} - -SOKOL_APP_IMPL const void* sapp_egl_get_context(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_ANDROID) - return _sapp.android.context; - #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) - return _sapp.egl.context; - #else - return 0; - #endif -} - -SOKOL_API_IMPL bool sapp_gles2(void) { - return _sapp.gles2_fallback; -} - -SOKOL_API_IMPL void sapp_show_keyboard(bool show) { - #if defined(_SAPP_IOS) - _sapp_ios_show_keyboard(show); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_show_keyboard(show); - #elif defined(_SAPP_ANDROID) - _sapp_android_show_keyboard(show); - #else - _SOKOL_UNUSED(show); - #endif -} - -SOKOL_API_IMPL bool sapp_keyboard_shown(void) { - return _sapp.onscreen_keyboard_shown; -} - -SOKOL_API_IMPL bool sapp_is_fullscreen(void) { - return _sapp.fullscreen; -} - -SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { - #if defined(_SAPP_MACOS) - _sapp_macos_toggle_fullscreen(); - #elif defined(_SAPP_WIN32) - _sapp_win32_toggle_fullscreen(); - #elif defined(_SAPP_LINUX) - _sapp_x11_toggle_fullscreen(); - #endif -} - -/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ -SOKOL_API_IMPL void sapp_show_mouse(bool show) { - if (_sapp.mouse.shown != show) { - #if defined(_SAPP_MACOS) - _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); - #endif - _sapp.mouse.shown = show; - } -} - -SOKOL_API_IMPL bool sapp_mouse_shown(void) { - return _sapp.mouse.shown; -} - -SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { - #if defined(_SAPP_MACOS) - _sapp_macos_lock_mouse(lock); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_lock_mouse(lock); - #elif defined(_SAPP_WIN32) - _sapp_win32_lock_mouse(lock); - #elif defined(_SAPP_LINUX) - _sapp_x11_lock_mouse(lock); - #else - _sapp.mouse.locked = lock; - #endif -} - -SOKOL_API_IMPL bool sapp_mouse_locked(void) { - return _sapp.mouse.locked; -} - -SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (_sapp.mouse.current_cursor != cursor) { - #if defined(_SAPP_MACOS) - _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); - #endif - _sapp.mouse.current_cursor = cursor; - } -} - -SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { - return _sapp.mouse.current_cursor; -} - -SOKOL_API_IMPL void sapp_request_quit(void) { - _sapp.quit_requested = true; -} - -SOKOL_API_IMPL void sapp_cancel_quit(void) { - _sapp.quit_requested = false; -} - -SOKOL_API_IMPL void sapp_quit(void) { - _sapp.quit_ordered = true; -} - -SOKOL_API_IMPL void sapp_consume_event(void) { - _sapp.event_consumed = true; -} - -/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ -SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { - if (!_sapp.clipboard.enabled) { - return; - } - SOKOL_ASSERT(str); - #if defined(_SAPP_MACOS) - _sapp_macos_set_clipboard_string(str); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_set_clipboard_string(str); - #elif defined(_SAPP_WIN32) - _sapp_win32_set_clipboard_string(str); - #else - /* not implemented */ - #endif - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); -} - -SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { - if (!_sapp.clipboard.enabled) { - return ""; - } - #if defined(_SAPP_MACOS) - return _sapp_macos_get_clipboard_string(); - #elif defined(_SAPP_EMSCRIPTEN) - return _sapp.clipboard.buffer; - #elif defined(_SAPP_WIN32) - return _sapp_win32_get_clipboard_string(); - #else - /* not implemented */ - return _sapp.clipboard.buffer; - #endif -} - -SOKOL_API_IMPL void sapp_set_window_title(const char* title) { - SOKOL_ASSERT(title); - _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); - #if defined(_SAPP_MACOS) - _sapp_macos_update_window_title(); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_window_title(); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_window_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; -} - -SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - SOKOL_ASSERT(_sapp.drop.buffer); - if (!_sapp.drop.enabled) { - return ""; - } - if ((index < 0) || (index >= _sapp.drop.max_files)) { - return ""; - } - return (const char*) _sapp_dropped_file_path_ptr(index); -} - -SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - #if defined(_SAPP_EMSCRIPTEN) - if (!_sapp.drop.enabled) { - return 0; - } - return sapp_js_dropped_file_size(index); - #else - (void)index; - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT(request); - SOKOL_ASSERT(request->callback); - SOKOL_ASSERT(request->buffer.ptr); - SOKOL_ASSERT(request->buffer.size > 0); - #if defined(_SAPP_EMSCRIPTEN) - const int index = request->dropped_file_index; - sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; - if ((index < 0) || (index >= _sapp.drop.num_files)) { - error_code = SAPP_HTML5_FETCH_ERROR_OTHER; - } - if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { - error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; - } - if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { - _sapp_emsc_invoke_fetch_cb(index, - false, // success - (int)error_code, - request->callback, - 0, // fetched_size - (void*)request->buffer.ptr, - request->buffer.size, - request->user_data); - } - else { - sapp_js_fetch_dropped_file(index, - request->callback, - (void*)request->buffer.ptr, - request->buffer.size, - request->user_data); - } - #else - (void)request; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.mtl_device; - #else - const void* obj = (__bridge const void*) _sapp.ios.mtl_device; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_macos_get_window(void) { - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_ios_get_window(void) { - #if defined(_SAPP_IOS) - const void* obj = (__bridge const void*) _sapp.ios.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device_context; - #else - return 0; - #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) - if (_sapp.d3d11.msaa_rtv) { - return _sapp.d3d11.msaa_rtv; - } - else { - return _sapp.d3d11.rtv; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.dsv; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_WIN32) - return _sapp.win32.hwnd; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.msaa_view; - } - else { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - else { - return 0; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.depth_stencil_view; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { - // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() - // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) - #if defined(_SAPP_ANDROID) - return (void*)_sapp.android.activity; - #else - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { - _sapp.html5_ask_leave_site = ask; -} - -#endif /* SOKOL_APP_IMPL */ diff --git a/src/sokol/sokol_app.h.rej b/src/sokol/sokol_app.h.rej deleted file mode 100644 index 66ba5506d..000000000 --- a/src/sokol/sokol_app.h.rej +++ /dev/null @@ -1,19 +0,0 @@ -*************** -*** 8091,8098 **** - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); -- dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; -- dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { ---- 8101,8108 ---- - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); -+ dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; -+ dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { diff --git a/src/sokol/sokol_app.patch b/src/sokol/sokol_app.patch deleted file mode 100644 index 09f8056e4..000000000 --- a/src/sokol/sokol_app.patch +++ /dev/null @@ -1,328 +0,0 @@ -diff --git a/src/sokol/sokol_app.h b/src/sokol/sokol_app.h -index e9f90787..5af91c24 100644 ---- a/src/sokol/sokol_app.h -+++ b/src/sokol/sokol_app.h -@@ -1076,6 +1076,7 @@ typedef enum sapp_keycode { - SAPP_KEYCODE_RIGHT_ALT = 346, - SAPP_KEYCODE_RIGHT_SUPER = 347, - SAPP_KEYCODE_MENU = 348, -+ SAPP_KEYCODE_BACK = 349, - } sapp_keycode; - - typedef struct sapp_touchpoint { -@@ -1277,6 +1278,7 @@ SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); - SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); - /* iOS: get bridged pointer to iOS UIWindow */ - SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); -+SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void); - - /* D3D11: get pointer to ID3D11Device object */ - SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); -@@ -1300,6 +1302,7 @@ SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); - - /* Android: get native activity handle */ - SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); -+SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void); - - #ifdef __cplusplus - } /* extern "C" */ -@@ -7558,6 +7561,10 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { - _sapp.android.context = context; - return true; - } -+SOKOL_APP_API_DECL const void* sapp_android_disable_vsync(void){ -+ //eglSwapInterval(_sapp.android.display,0); -+} -+ - - _SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { - if (_sapp.android.display != EGL_NO_DISPLAY) { -@@ -7701,6 +7708,9 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - if (!_sapp_events_enabled()) { - return false; - } -+ if((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)return false; -+ if((AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK)return false; -+ - int32_t action_idx = AMotionEvent_getAction(e); - int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; - sapp_event_type type = SAPP_EVENTTYPE_INVALID; -@@ -7739,8 +7749,8 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); -- dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; -- dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; -+ dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; -+ dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { -@@ -7753,19 +7763,225 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - return true; - } - -+_SOKOL_PRIVATE sapp_keycode _sapp_android_translate_key(int scancode) { -+ switch (scancode) { -+ case AKEYCODE_ESCAPE: return SAPP_KEYCODE_ESCAPE; -+ case AKEYCODE_TAB: return SAPP_KEYCODE_TAB; -+ case AKEYCODE_SHIFT_LEFT: return SAPP_KEYCODE_LEFT_SHIFT; -+ case AKEYCODE_SHIFT_RIGHT: return SAPP_KEYCODE_RIGHT_SHIFT; -+ case AKEYCODE_CTRL_LEFT: return SAPP_KEYCODE_LEFT_CONTROL; -+ case AKEYCODE_CTRL_RIGHT: return SAPP_KEYCODE_RIGHT_CONTROL; -+ case AKEYCODE_ALT_LEFT: return SAPP_KEYCODE_LEFT_ALT; -+ case AKEYCODE_ALT_RIGHT: return SAPP_KEYCODE_RIGHT_ALT; -+ case AKEYCODE_META_LEFT: return SAPP_KEYCODE_LEFT_SUPER; -+ case AKEYCODE_META_RIGHT: return SAPP_KEYCODE_RIGHT_SUPER; -+ case AKEYCODE_MENU: return SAPP_KEYCODE_MENU; -+ case AKEYCODE_NUM_LOCK: return SAPP_KEYCODE_NUM_LOCK; -+ case AKEYCODE_CAPS_LOCK: return SAPP_KEYCODE_CAPS_LOCK; -+ case AKEYCODE_SYSRQ: return SAPP_KEYCODE_PRINT_SCREEN; -+ case AKEYCODE_SCROLL_LOCK: return SAPP_KEYCODE_SCROLL_LOCK; -+ case AKEYCODE_BREAK: return SAPP_KEYCODE_PAUSE; -+ case AKEYCODE_FORWARD_DEL: return SAPP_KEYCODE_DELETE; -+ case AKEYCODE_DEL: return SAPP_KEYCODE_BACKSPACE; -+ case AKEYCODE_ENTER: return SAPP_KEYCODE_ENTER; -+ case AKEYCODE_MOVE_HOME: return SAPP_KEYCODE_HOME; -+ case AKEYCODE_MOVE_END: return SAPP_KEYCODE_END; -+ case AKEYCODE_PAGE_UP: return SAPP_KEYCODE_PAGE_UP; -+ case AKEYCODE_PAGE_DOWN: return SAPP_KEYCODE_PAGE_DOWN; -+ case AKEYCODE_INSERT: return SAPP_KEYCODE_INSERT; -+ case AKEYCODE_DPAD_LEFT: return SAPP_KEYCODE_LEFT; -+ case AKEYCODE_DPAD_RIGHT: return SAPP_KEYCODE_RIGHT; -+ case AKEYCODE_DPAD_DOWN: return SAPP_KEYCODE_DOWN; -+ case AKEYCODE_DPAD_UP: return SAPP_KEYCODE_UP; -+ case AKEYCODE_F1: return SAPP_KEYCODE_F1; -+ case AKEYCODE_F2: return SAPP_KEYCODE_F2; -+ case AKEYCODE_F3: return SAPP_KEYCODE_F3; -+ case AKEYCODE_F4: return SAPP_KEYCODE_F4; -+ case AKEYCODE_F5: return SAPP_KEYCODE_F5; -+ case AKEYCODE_F6: return SAPP_KEYCODE_F6; -+ case AKEYCODE_F7: return SAPP_KEYCODE_F7; -+ case AKEYCODE_F8: return SAPP_KEYCODE_F8; -+ case AKEYCODE_F9: return SAPP_KEYCODE_F9; -+ case AKEYCODE_F10: return SAPP_KEYCODE_F10; -+ case AKEYCODE_F11: return SAPP_KEYCODE_F11; -+ case AKEYCODE_F12: return SAPP_KEYCODE_F12; -+ case AKEYCODE_NUMPAD_DIVIDE: return SAPP_KEYCODE_KP_DIVIDE; -+ case AKEYCODE_NUMPAD_MULTIPLY: return SAPP_KEYCODE_KP_MULTIPLY; -+ case AKEYCODE_NUMPAD_SUBTRACT: return SAPP_KEYCODE_KP_SUBTRACT; -+ case AKEYCODE_NUMPAD_ADD: return SAPP_KEYCODE_KP_ADD; -+ case AKEYCODE_NUMPAD_0: return SAPP_KEYCODE_KP_0; -+ case AKEYCODE_NUMPAD_1: return SAPP_KEYCODE_KP_1; -+ case AKEYCODE_NUMPAD_2: return SAPP_KEYCODE_KP_2; -+ case AKEYCODE_NUMPAD_3: return SAPP_KEYCODE_KP_3; -+ case AKEYCODE_NUMPAD_4: return SAPP_KEYCODE_KP_4; -+ case AKEYCODE_NUMPAD_5: return SAPP_KEYCODE_KP_5; -+ case AKEYCODE_NUMPAD_6: return SAPP_KEYCODE_KP_6; -+ case AKEYCODE_NUMPAD_7: return SAPP_KEYCODE_KP_7; -+ case AKEYCODE_NUMPAD_8: return SAPP_KEYCODE_KP_8; -+ case AKEYCODE_NUMPAD_9: return SAPP_KEYCODE_KP_9; -+ case AKEYCODE_NUMPAD_DOT: return SAPP_KEYCODE_KP_DECIMAL; -+ case AKEYCODE_NUMPAD_EQUALS: return SAPP_KEYCODE_KP_EQUAL; -+ case AKEYCODE_NUMPAD_ENTER: return SAPP_KEYCODE_KP_ENTER; -+ case AKEYCODE_A: return SAPP_KEYCODE_A; -+ case AKEYCODE_B: return SAPP_KEYCODE_B; -+ case AKEYCODE_C: return SAPP_KEYCODE_C; -+ case AKEYCODE_D: return SAPP_KEYCODE_D; -+ case AKEYCODE_E: return SAPP_KEYCODE_E; -+ case AKEYCODE_F: return SAPP_KEYCODE_F; -+ case AKEYCODE_G: return SAPP_KEYCODE_G; -+ case AKEYCODE_H: return SAPP_KEYCODE_H; -+ case AKEYCODE_I: return SAPP_KEYCODE_I; -+ case AKEYCODE_J: return SAPP_KEYCODE_J; -+ case AKEYCODE_K: return SAPP_KEYCODE_K; -+ case AKEYCODE_L: return SAPP_KEYCODE_L; -+ case AKEYCODE_M: return SAPP_KEYCODE_M; -+ case AKEYCODE_N: return SAPP_KEYCODE_N; -+ case AKEYCODE_O: return SAPP_KEYCODE_O; -+ case AKEYCODE_P: return SAPP_KEYCODE_P; -+ case AKEYCODE_Q: return SAPP_KEYCODE_Q; -+ case AKEYCODE_R: return SAPP_KEYCODE_R; -+ case AKEYCODE_S: return SAPP_KEYCODE_S; -+ case AKEYCODE_T: return SAPP_KEYCODE_T; -+ case AKEYCODE_U: return SAPP_KEYCODE_U; -+ case AKEYCODE_V: return SAPP_KEYCODE_V; -+ case AKEYCODE_W: return SAPP_KEYCODE_W; -+ case AKEYCODE_X: return SAPP_KEYCODE_X; -+ case AKEYCODE_Y: return SAPP_KEYCODE_Y; -+ case AKEYCODE_Z: return SAPP_KEYCODE_Z; -+ case AKEYCODE_1: return SAPP_KEYCODE_1; -+ case AKEYCODE_2: return SAPP_KEYCODE_2; -+ case AKEYCODE_3: return SAPP_KEYCODE_3; -+ case AKEYCODE_4: return SAPP_KEYCODE_4; -+ case AKEYCODE_5: return SAPP_KEYCODE_5; -+ case AKEYCODE_6: return SAPP_KEYCODE_6; -+ case AKEYCODE_7: return SAPP_KEYCODE_7; -+ case AKEYCODE_8: return SAPP_KEYCODE_8; -+ case AKEYCODE_9: return SAPP_KEYCODE_9; -+ case AKEYCODE_0: return SAPP_KEYCODE_0; -+ case AKEYCODE_SPACE: return SAPP_KEYCODE_SPACE; -+ case AKEYCODE_MINUS: return SAPP_KEYCODE_MINUS; -+ case AKEYCODE_EQUALS: return SAPP_KEYCODE_EQUAL; -+ case AKEYCODE_LEFT_BRACKET: return SAPP_KEYCODE_LEFT_BRACKET; -+ case AKEYCODE_RIGHT_BRACKET: return SAPP_KEYCODE_RIGHT_BRACKET; -+ case AKEYCODE_BACKSLASH: return SAPP_KEYCODE_BACKSLASH; -+ case AKEYCODE_SEMICOLON: return SAPP_KEYCODE_SEMICOLON; -+ case AKEYCODE_APOSTROPHE: return SAPP_KEYCODE_APOSTROPHE; -+ case AKEYCODE_GRAVE: return SAPP_KEYCODE_GRAVE_ACCENT; -+ case AKEYCODE_COMMA: return SAPP_KEYCODE_COMMA; -+ case AKEYCODE_PERIOD: return SAPP_KEYCODE_PERIOD; -+ case AKEYCODE_SLASH: return SAPP_KEYCODE_SLASH; -+ /* Android Buttons */ -+ case AKEYCODE_BACK: return SAPP_KEYCODE_BACK; -+ default: return SAPP_KEYCODE_INVALID; -+ } -+ return SAPP_KEYCODE_INVALID; -+} -+_SOKOL_PRIVATE uint32_t _sapp_android_mods(const AInputEvent* e) { -+ uint32_t meta_state = AKeyEvent_getMetaState(e); -+ uint32_t mods = 0; -+ if (meta_state& AMETA_SHIFT_ON) { -+ mods |= SAPP_MODIFIER_SHIFT; -+ } -+ if (meta_state& AMETA_CTRL_ON) { -+ mods |= SAPP_MODIFIER_CTRL; -+ } -+ if (meta_state& AMETA_ALT_ON) { -+ mods |= SAPP_MODIFIER_ALT; -+ } -+ if (meta_state& AMETA_META_ON) { -+ mods |= SAPP_MODIFIER_SUPER; -+ } -+ return mods; -+} -+int _sapp_android_keycode_to_char(int eventType, int keyCode, int metaState) -+{ -+ ANativeActivity* activity =(ANativeActivity*)sapp_android_get_native_activity(); -+ // Attaches the current thread to the JVM. -+ JavaVM *javaVM = activity->vm; -+ JNIEnv *jniEnv = activity->env; -+ -+ jint result = (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL); -+ if(result == JNI_ERR){ -+ return 0; -+ } -+ -+ jclass class_key_event = (*jniEnv)->FindClass(jniEnv,"android/view/KeyEvent"); -+ int unicodeKey; -+ -+ if(metaState == 0){ -+ jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "()I"); -+ jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); -+ jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); -+ unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char); -+ }else{ -+ jmethodID method_get_unicode_char = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "getUnicodeChar", "(I)I"); -+ jmethodID eventConstructor = (*jniEnv)->GetMethodID(jniEnv,class_key_event, "", "(II)V"); -+ jobject eventObj = (*jniEnv)->NewObject(jniEnv,class_key_event, eventConstructor, eventType, keyCode); -+ unicodeKey = (*jniEnv)->CallIntMethod(jniEnv,eventObj, method_get_unicode_char, metaState); -+ } -+ -+ (*javaVM)->DetachCurrentThread(javaVM); -+ -+ return unicodeKey; -+} -+_SOKOL_PRIVATE void _sapp_android_char_event(uint32_t keycode, bool repeat,const AInputEvent* e) { -+ if (_sapp_events_enabled() ) { -+ _sapp_init_event(SAPP_EVENTTYPE_CHAR); -+ _sapp.event.modifiers = _sapp_android_mods(e); -+ _sapp.event.char_code = _sapp_android_keycode_to_char(AInputEvent_getType(e),keycode,AKeyEvent_getMetaState(e)); -+ _sapp.event.key_repeat = repeat; -+ _sapp_call_event(&_sapp.event); -+ } -+} - _SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { - return false; - } -- if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { -- /* FIXME: this should be hooked into a "really quit?" mechanism -- so the app can ask the user for confirmation, this is currently -- generally missing in sokol_app.h -- */ -- _sapp_android_shutdown(); -- return true; -+ if (!_sapp_events_enabled()) { -+ return false; - } -- return false; -+ int32_t action = AKeyEvent_getAction(e); -+ // Don't process soft keyboard commands as they are not reliable through this interface. -+ if((AKeyEvent_getFlags(e)&AKEY_EVENT_FLAG_SOFT_KEYBOARD)==AKEY_EVENT_FLAG_SOFT_KEYBOARD)return true; -+ // Don't relay key press events from joysticks or game pads as Sokol key down events -+ if ((AInputEvent_getSource(e) & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD || -+ (AInputEvent_getSource(e) & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) { -+ return false; -+ } -+ sapp_event_type type = SAPP_EVENTTYPE_INVALID; -+ switch (action) { -+ -+ case AKEY_EVENT_ACTION_DOWN : -+ type = SAPP_EVENTTYPE_KEY_DOWN; -+ break; -+ case AKEY_EVENT_ACTION_UP: -+ type = SAPP_EVENTTYPE_KEY_UP; -+ break; -+ default: -+ break; -+ } -+ if (type == SAPP_EVENTTYPE_INVALID) { -+ return false; -+ } -+ bool repeat = AKeyEvent_getRepeatCount(e)>0; -+ _sapp_init_event(type); -+ _sapp.event.key_code = _sapp_android_translate_key(AKeyEvent_getKeyCode(e)); -+ _sapp.event.modifiers = _sapp_android_mods(e); -+ _sapp.event.key_repeat = repeat; -+ _sapp_call_event(&_sapp.event); -+ if(type==SAPP_EVENTTYPE_KEY_DOWN){ -+ _sapp_android_char_event(AKeyEvent_getKeyCode(e),repeat,e); -+ } -+ /* check if a CLIPBOARD_PASTED event must be sent too */ -+ if (_sapp.clipboard.enabled && -+ (type == SAPP_EVENTTYPE_KEY_DOWN) && -+ (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && -+ (_sapp.event.key_code == SAPP_KEYCODE_V)) -+ { -+ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); -+ _sapp_call_event(&_sapp.event); -+ } -+ return _sapp.event.key_code != SAPP_KEYCODE_INVALID; - } - - _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { -@@ -7930,6 +8146,16 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { - bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); - process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; - } -+ /* handle quit-requested, either from window or from sapp_request_quit() */ -+ if (_sapp.quit_requested && !_sapp.quit_ordered) { -+ /* give user code a chance to intervene */ -+ _sapp_android_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); -+ /* if user code hasn't intervened, quit the app */ -+ if (_sapp.quit_requested) { -+ _sapp.quit_ordered = true; -+ } -+ } -+ if(_sapp.quit_ordered) _sapp_android_shutdown(); - } - - /* cleanup thread */ -@@ -10618,6 +10844,16 @@ SOKOL_API_IMPL const void* sapp_ios_get_window(void) { - return 0; - #endif - } -+SOKOL_APP_API_DECL const void* sapp_ios_get_view_ctrl(void){ -+ #if defined(_SAPP_IOS) -+ const void* obj = (__bridge const void*) _sapp.ios.view_ctrl; -+ SOKOL_ASSERT(obj); -+ return obj; -+ #else -+ return 0; -+ #endif -+} -+ - - SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { - SOKOL_ASSERT(_sapp.valid);