diff --git a/Building.md b/Building.md new file mode 100644 index 0000000..051fbc2 --- /dev/null +++ b/Building.md @@ -0,0 +1,195 @@ +The Wipeout rewrite supports two different platform-backends: +[SDL2](https://github.com/libsdl-org/SDL) and +[Sokol](https://github.com/floooh/sokol). +The only difference in features is that the SDL2 backend supports game +controllers (joysticks, gamepads), while the Sokol backend does not. +The Sokol backend is also only supported on macOS, Linux, Windows and Emscripten. + +## Building For Your Platform + +This project requires [CMake](https://cmake.org) to build and platform-specific +libraries to run. +Consult the following sections for how to acquire them for your platform: + +## *NIX + +Building on *NIX should be as simple as installing CMake, GLEW, and the +necessary platform libraries from your package manager. +For brevity, this guide assumes that the necessary development tools (i.e. a C +complier, make) have already been installed. +The SDL2 platform should only require the `sdl2` library and headers, whilst the +Sokol platform requires the library/headers for: + +- `X11` +- `Xi` +- `Xcursor` +- `ALSA` + +The following snippets list the specific package manager invocations for +popluar *NIX OSs: + +**Debian/Ubuntu** + +```sh +apt install cmake libglew-dev +# For SDL2 +apt install libsdl2-dev +# For Sokol +apt install libx11-dev libxcursor-dev libxi-dev libasound2-dev +``` + +**Fedora** + +```sh +dnf install cmake glew-devel +# For SDL2 +dnf install SDL2-devel +# For Sokol +dnf install libx11-devel libxcursor-devel libxi-devel alsa-lib-devel +``` + +**Arch Linux** + +```sh +pacman -S cmake glew +# For SDL2 +pacman -S sdl2 +# For Sokol +pacman install libx11 libxcursor libxi alsa-lib +``` + +**OpenSUSE** + +```sh +zypper install cmake glew-devel +# For SDL2 +zypper install SDL2-devel +# For Sokol +zypper install libx11-devel libxcursor-devel libxi-devel alsa-lib-devel +``` + +**FreeBSD** + +```sh +pkg install cmake sdl2 +``` + +**OpenBSD** + +```sh +pkg_add cmake sdl2 +``` + +Note that the Sokol platform is not supported on the BSDs, since the Sokol +headers themselves do not support these Operating Systems. + +With the packages installed, you can now setup and build: + +```sh +cmake -S path/to/wipeout-rewrite -B path/to/build-dir +cmake --build path/to/build-dir +``` + +## macOS + +Currently only the SDL2 platform works. +macOS is very picky about the GLSL shader version when compiling with Sokol and +OpenGL3.3; it shouldn't be too difficult to get it working, but will probably +require a bunch of `#ifdefs` for SDL and WASM. +Pull-requests welcome! + +It is recommended to use [Homebrew](https://brew.sh) to fetch the required +software, other solutions (e.g. MacPorts) may work but have not been tested. +Using homebrew, you can install the required software with the following: + +```sh +brew install cmake +# For SDL2 +brew install sdl2 +# Nothing extra needed for Sokol +``` + +With the packages installed, you can now setup and build: + +```sh +cmake -S path/to/wipeout-rewrite -B path/to/build-dir \ + -DCMAKE_PREFIX_PATH="$(brew --prefix sdl2)" +cmake --build path/to/build-dir +``` + +## Windows + +### clang-cl + +Building natively on Windows requires a more complicated setup. The source code +relies on GCC extensions that are not supported by `msvc`, which requires the +use of `clang-cl`. +The simplest way to get a build environment with `clang-cl` is to download and +install [Visual Studio](https://visualstudio.microsoft.com/downloads/) (2022 at +the time of writing) with the "Desktop development with C++" option selected. +Also make sure to select "Clang C++ compiler for Windows" in "Individual +Components" if it hasn't been already. + +The next step is to acquire development versions of SDL2 and GLEW. +The easiest way is to install [vcpkg](https://vcpkg.io) and let Visual Studio's +integration build and install it for you. +Follow the [vcpkg "Getting Started" guide](https://vcpkg.io/en/getting-started) +and integrate it with Visual Studio. + +Finally, open Visual Studio, select "Open a local folder", and navigate to the +directory where you have cloned this repo. +Visual Studio should automatically configure itself to build with CMake, and +build the necessary libraries using vcpkg. +Since this repository contains a `CMakeSettings.json` file, there should already +be CMake configurations listed in the menubar dropdown. +When adding a new configuration, make sure to use the `clang_cl` toolsets. +Select the config you want from the list and build using `F7`, the build +artifacts should be under `path\to\wipeout-rewrite\build`. + +### MSYS2 + +Building with [MSYS2](https://www.msys2.org/) is sightly easier but still +involves a bit of configuration. +Download and install MSYS2 using the installer, and enter a MSYS2 environment +using the start menu. For this guide we're using the `UCRT` environment, but the +others work just as well. + +Install the following packages using `pacman`: + +```sh +pacman -S mingw-w64-ucrt-x86_64-{toolchain,cmake,SDL2,glew} +``` + +With the packages installed, you can now setup and build: + +```sh +cmake -S path/to/wipeout-rewrite -B path/to/build-dir +cmake --build path/to/build-dir +``` + +## Emscripten + +Download and install the [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html), +so that `emcc` and `emcmake` is in your path. +Linux users may find it easier to install using their distro's package manager +if it is available. +Note that only the Sokol platform will work for WebAssembly builds, so make sure +to select it at compile time using `-DPLATFORM=Sokol`. + +With the SDK installed, you can now setup and build: + +```sh +emcmake cmake -S path/to/wipeout-rewrite -B path/to/build-dir +emcmake cmake --build path/to/build-dir +``` + +## Build Flags + +The following is a table for project specific build flags using CMake: + +| Flag | Description | Options | Default | +|------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| `PLATFORM` | The platform to build for. | `SDL2`, `Sokol` | `SDL2` | +| `RENDERER` | Graphics renderer. | `GL` for OpenGL 3, `GLES2` for OpenGL ES 2, `Software` for a pure software renderer. | `GL` | +| `USE_GLVND` | Link against the OpenGL Vendor Neutral Dispatch libraries. | `On`, `Off` | `On`, falling back to `Off` if the libraries aren't found or an OpenGL renderer isn't used. | +| `MINIMAL_BUNDLE` | Do not include the music/intro video when building for the web. | `On`, `Off` | `Off` | diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c29e2f2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,211 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +project(wipeout-rewrite) + +include(GNUInstallDirs) +include(CMakeDependentOption) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") + set(EMSCRIPTEN true) +endif() + +set(platform_options "SDL2" "Sokol") +set(PLATFORM "SDL2" CACHE STRING "Graphics platform to handle input/output") +set_property(CACHE PLATFORM PROPERTY STRINGS "${platform_options}") +if(NOT PLATFORM IN_LIST platform_options) + message(FATAL_ERROR "PLATFORM must be one of ${platform_options}") +endif() + +set(renderer_options "GL" "GLES2" "SOFTWARE") +set(gl_renderers "GL" "GLES2") +set(RENDERER "GL" CACHE STRING "Graphics rendering backend") +set_property(CACHE RENDERER PROPERTY STRINGS "${renderer_options}") +if(NOT RENDERER IN_LIST renderer_options) + message(FATAL_ERROR "RENDERER must be one of ${renderer_options}") +endif() +if(RENDERER IN_LIST gl_renderers) + set(using_gl true) +endif() + +cmake_dependent_option(USE_GLVND "Link against modern GLVND ABIs" OFF "using_gl;LINUX" ON) +cmake_dependent_option(MINIMAL_BUNDLE "Do not include music/movies for web builds" OFF "EMSCRIPTEN" OFF) + +find_package(OpenGL) +find_package(GLEW) +find_package(SDL2) + +set(common_src + src/wipeout/camera.c + src/wipeout/camera.h + src/wipeout/droid.c + src/wipeout/droid.h + src/wipeout/game.c + src/wipeout/game.h + src/wipeout/hud.c + src/wipeout/hud.h + src/wipeout/image.c + src/wipeout/image.h + src/wipeout/ingame_menus.c + src/wipeout/ingame_menus.h + src/wipeout/intro.c + src/wipeout/intro.h + src/wipeout/main_menu.c + src/wipeout/main_menu.h + src/wipeout/menu.c + src/wipeout/menu.h + src/wipeout/object.c + src/wipeout/object.h + src/wipeout/particle.c + src/wipeout/particle.h + src/wipeout/race.c + src/wipeout/race.h + src/wipeout/scene.c + src/wipeout/scene.h + src/wipeout/sfx.c + src/wipeout/sfx.h + src/wipeout/ship.c + src/wipeout/ship.h + src/wipeout/ship_ai.c + src/wipeout/ship_ai.h + src/wipeout/ship_player.c + src/wipeout/ship_player.h + src/wipeout/title.c + src/wipeout/title.h + src/wipeout/track.c + src/wipeout/track.h + src/wipeout/ui.c + src/wipeout/ui.h + src/wipeout/weapon.c + src/wipeout/weapon.h + src/input.c + src/input.h + src/mem.c + src/mem.h + src/platform.h + src/render.h + src/system.c + src/system.h + src/types.c + src/types.h + src/utils.c + src/utils.h + + packaging/windows/wipeout.exe.manifest + packaging/windows/wipeout.rc +) + +add_executable(wipeout WIN32 ${common_src}) +set_property(TARGET wipeout PROPERTY C_STANDARD 11) +target_include_directories(wipeout PRIVATE src) +target_include_directories(wipeout SYSTEM PRIVATE src/libs) +target_compile_options(wipeout PRIVATE + $<$:/W4> + $<$>:-Wall -Wextra> +) + +if(WIN32) + target_compile_definitions(wipeout PRIVATE + "NOMINMAX" + "_USE_MATH_DEFINES" + "_CRT_SECURE_NO_WARNINGS" + ) +elseif(APPLE) + target_compile_definitions(wipeout PRIVATE + "_THREAD_SAFE" + "GL_SILENCE_DEPRECATION" + ) + target_link_libraries(wipeout PUBLIC "-framework Foundation") + set_source_files_properties(src/platform_sokol.c PROPERTIES COMPILE_FLAGS "-x objective-c") + if("${PLATFORM}" STREQUAL Sokol) + target_link_libraries(wipeout PUBLIC + "-framework Cocoa" + "-framework QuartzCore" + "-framework AudioToolbox" + ) + endif() +elseif(EMSCRIPTEN) + # Emscripten's CMake modules don't define targets like the standard + # ones do, so we define them ourselves here. + add_library(GLEW::GLEW INTERFACE IMPORTED) + add_library(OpenGL::GL INTERFACE IMPORTED) + if (NOT TARGET SDL2::Main) + add_library(SDL2::Main INTERFACE IMPORTED) + endif() + set_target_properties(OpenGL::GL PROPERTIES + IMPORTED_LIBNAME "GL" + ) + set_target_properties(GLEW::GLEW PROPERTIES + IMPORTED_LIBNAME "GLEW" + ) + set_target_properties(SDL2::Main PROPERTIES + IMPORTED_LIBNAME "SDL2" + INTERFACE_COMPILE_OPTIONS "SHELL:-s USE_SDL=2" + INTERFACE_LINK_LIBRARIES "SHELL:-s USE_SDL=2" + ) + + target_link_options(wipeout PRIVATE + "SHELL:-s ALLOW_MEMORY_GROWTH=1" + "SHELL:-s ENVIRONMENT=web" + "SHELL:-s FORCE_FILESYSTEM" + "SHELL:--preload-file ${CMAKE_SOURCE_DIR}/wipeout/@/wipeout" + ) + if(MINIMAL_BUNDLE) + target_link_options(wipeout PRIVATE + "SHELL:--exclude-file ${CMAKE_SOURCE_DIR}/wipeout/music" + "SHELL:--exclude-file ${CMAKE_SOURCE_DIR}/intro.mpeg" + ) + endif() + + configure_file("${CMAKE_SOURCE_DIR}/src/wasm-index.html" "game.html" COPYONLY) +elseif(UNIX) + target_link_libraries(wipeout PUBLIC m) + if (PLATFORM STREQUAL "Sokol" AND LINUX) + find_package(Threads REQUIRED) + find_package(X11 REQUIRED) + find_package(ALSA REQUIRED) + target_link_libraries(wipeout PUBLIC + X11::X11 + X11::Xcursor + Threads::Threads + X11::Xi + dl + ALSA::ALSA + ) + endif() +endif() + +if(using_gl) + target_compile_definitions(wipeout PRIVATE "RENDERER_GL") + target_sources(wipeout PRIVATE src/render_gl.c) + + target_include_directories(wipeout PUBLIC ${OPENGL_INCLUDE_DIR}) + if (USE_GLES2) + target_compile_definitions(wipeout PRIVATE "USE_GLES2") + if (TARGET OpenGL::GLES2) + target_link_libraries(wipeout PUBLIC OpenGL::GLES2) + endif() + endif() + + if(USE_GLVND AND TARGET OpenGL::OpenGL) + target_link_libraries(wipeout PUBLIC OpenGL::OpenGL) + else() + target_link_libraries(wipeout PUBLIC OpenGL::GL) + endif() + + if(NOT APPLE) + target_include_directories(wipeout PRIVATE ${GLEW_INCLUDE_DIRS}) + target_link_libraries(wipeout PRIVATE GLEW::GLEW) + endif() +elseif("${RENDERER}" STREQUAL "SOFTWARE") + target_compile_definitions(wipeout PRIVATE "RENDERER_SOFTWARE") + target_sources(wipeout PRIVATE src/render_software.c) +endif() + +if("${PLATFORM}" STREQUAL SDL2) + target_sources(wipeout PRIVATE src/platform_sdl.c) + target_link_libraries(wipeout PUBLIC SDL2::Main) +elseif("${PLATFORM}" STREQUAL Sokol) + target_sources(wipeout PRIVATE src/platform_sokol.c) +endif() + +install(TARGETS wipeout) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..4ec311f --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,71 @@ +{ + "configurations": [ + { + "name": "x64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "intelliSenseMode": "windows-clang-x64" + }, + { + "name": "x64-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + }, + { + "name": "x86-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x86" ] + }, + { + "name": "x86-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x86" ] + }, + { + "name": "arm64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_arm64_x64" ] + }, + { + "name": "arm64-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_arm64_x64" ] + } + ] +} diff --git a/README.md b/README.md index a584d49..d558b27 100644 --- a/README.md +++ b/README.md @@ -6,72 +6,11 @@ Play here: https://phoboslab.org/wipegame/ More info in my blog: https://phoboslab.org/log/2023/08/rewriting-wipeout - ⚠️ Work in progress. Expect bugs. - ## Building -The game currently supports two different platform-backends: [SDL2](https://github.com/libsdl-org/SDL) and [Sokol](https://github.com/floooh/sokol). The only difference in features is that the SDL2 backend supports game controllers (joysticks, gamepads), while the Sokol backend does not. - - -### Linux - -#### Ubuntu - -``` -# for SDL2 backend -apt install libsdl2-dev libglew-dev -make sdl -``` - -``` -# for Sokol backend -apt install libx11-dev libxcursor-dev libxi-dev libasound2-dev -make sokol -``` - -#### Fedora - -``` -# for SDL2 backend -dnf install SDL2-devel glew-devel -make sdl -``` - -``` -# for Sokol backend -dnf install libX11-devel libXi-devel alsa-lib-devel glew-devel libXcursor-devel -make sokol -``` - -### macOS - -Currently only the SDL2 backend works. macOS is very picky about the GLSL shader version when compiling with Sokol and OpenGL3.3; it shouldn't be too difficult to get it working, but will probably require a bunch of `#ifdefs` for SDL and WASM. PRs welcome! - -``` -brew install sdl2 glew -make sdl -``` - -### Windows - -In theory both backends should work on Windows, but the Makefile is missing the proper compiler flags. Please send a PR! - -_todo_ - - -### WASM - -Install [emscripten](https://emscripten.org/) and activate emsdk, so that `emcc` is in your `PATH`. The WASM version automatically -selects the Sokol backend. I'm not sure what needs to be done to make the SDL2 backend work with WASM. - -``` -make wasm -``` - -This builds the minimal version (no music, no intro) as well as the full version. - +See [Building.md](Building.md) for more info. ### Flags diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake new file mode 100644 index 0000000..8649c92 --- /dev/null +++ b/cmake/FindSDL2.cmake @@ -0,0 +1,388 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# Copyright 2019 Amine Ben Hassouna +# Copyright 2000-2019 Kitware, Inc. and Contributors +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: + +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. + +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# * Neither the name of Kitware, Inc. nor the names of Contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[=======================================================================[.rst: +FindSDL2 +-------- + +Locate SDL2 library + +This module defines the following 'IMPORTED' targets: + +:: + + SDL2::Core + The SDL2 library, if found. + Libraries should link to SDL2::Core + + SDL2::Main + The SDL2main library, if found. + Applications should link to SDL2::Main instead of SDL2::Core + + + +This module will set the following variables in your project: + +:: + + SDL2_LIBRARIES, the name of the library to link against + SDL2_INCLUDE_DIRS, where to find SDL.h + SDL2_FOUND, if false, do not try to link to SDL2 + SDL2MAIN_FOUND, if false, do not try to link to SDL2main + SDL2_VERSION_STRING, human-readable string containing the version of SDL2 + + + +This module responds to the following cache variables: + +:: + + SDL2_PATH + Set a custom SDL2 Library path (default: empty) + + SDL2_NO_DEFAULT_PATH + Disable search SDL2 Library in default path. + If SDL2_PATH (default: ON) + Else (default: OFF) + + SDL2_INCLUDE_DIR + SDL2 headers path. + + SDL2_LIBRARY + SDL2 Library (.dll, .so, .a, etc) path. + + SDL2MAIN_LIBRAY + SDL2main Library (.a) path. + + SDL2_BUILDING_LIBRARY + This flag is useful only when linking to SDL2_LIBRARIES insead of + SDL2::Main. It is required only when building a library that links to + SDL2_LIBRARIES, because only applications need main() (No need to also + link to SDL2main). + If this flag is defined, then no SDL2main will be added to SDL2_LIBRARIES + and no SDL2::Main target will be created. + + +Don't forget to include SDLmain.h and SDLmain.m in your project for the +OS X framework based version. (Other versions link to -lSDL2main which +this module will try to find on your behalf.) Also for OS X, this +module will automatically add the -framework Cocoa on your behalf. + + +Additional Note: If you see an empty SDL2_LIBRARY in your project +configuration, it means CMake did not find your SDL2 library +(SDL2.dll, libsdl2.so, SDL2.framework, etc). Set SDL2_LIBRARY to point +to your SDL2 library, and configure again. Similarly, if you see an +empty SDL2MAIN_LIBRARY, you should set this value as appropriate. These +values are used to generate the final SDL2_LIBRARIES variable and the +SDL2::Core and SDL2::Main targets, but when these values are unset, +SDL2_LIBRARIES, SDL2::Core and SDL2::Main does not get created. + + +$SDL2DIR is an environment variable that would correspond to the +./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02 + + + +Created by Amine Ben Hassouna: + Adapt FindSDL.cmake to SDL2 (FindSDL2.cmake). + Add cache variables for more flexibility: + SDL2_PATH, SDL2_NO_DEFAULT_PATH (for details, see doc above). + Mark 'Threads' as a required dependency for non-OSX systems. + Modernize the FindSDL2.cmake module by creating specific targets: + SDL2::Core and SDL2::Main (for details, see doc above). + + +Original FindSDL.cmake module: + Modified by Eric Wing. Added code to assist with automated building + by using environmental variables and providing a more + controlled/consistent search behavior. Added new modifications to + recognize OS X frameworks and additional Unix paths (FreeBSD, etc). + Also corrected the header search path to follow "proper" SDL + guidelines. Added a search for SDLmain which is needed by some + platforms. Added a search for threads which is needed by some + platforms. Added needed compile switches for MinGW. + +On OSX, this will prefer the Framework version (if found) over others. +People will have to manually change the cache value of SDL2_LIBRARY to +override this selection or set the SDL2_PATH variable or the CMake +environment CMAKE_INCLUDE_PATH to modify the search paths. + +Note that the header path has changed from SDL/SDL.h to just SDL.h +This needed to change because "proper" SDL convention is #include +"SDL.h", not . This is done for portability reasons +because not all systems place things in SDL/ (see FreeBSD). +#]=======================================================================] + +# Define options for searching SDL2 Library in a custom path + +set(SDL2_PATH "" CACHE STRING "Custom SDL2 Library path") + +set(_SDL2_NO_DEFAULT_PATH OFF) +if(SDL2_PATH) + set(_SDL2_NO_DEFAULT_PATH ON) +endif() + +set(SDL2_NO_DEFAULT_PATH ${_SDL2_NO_DEFAULT_PATH} + CACHE BOOL "Disable search SDL2 Library in default path") +unset(_SDL2_NO_DEFAULT_PATH) + +set(SDL2_NO_DEFAULT_PATH_CMD) +if(SDL2_NO_DEFAULT_PATH) + set(SDL2_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH) +endif() + +# Search for the SDL2 include directory +find_path(SDL2_INCLUDE_DIR SDL.h + HINTS + ENV SDL2DIR + ${SDL2_NO_DEFAULT_PATH_CMD} + PATH_SUFFIXES SDL2 + # path suffixes to search inside ENV{SDL2DIR} + include/SDL2 include + PATHS ${SDL2_PATH} + DOC "Where the SDL2 headers can be found" + ) + +set(SDL2_INCLUDE_DIRS "${SDL2_INCLUDE_DIR}") + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +# SDL-2.0 is the name used by FreeBSD ports... +# don't confuse it for the version number. +find_library(SDL2_LIBRARY + NAMES SDL2 SDL-2.0 + HINTS + ENV SDL2DIR + ${SDL2_NO_DEFAULT_PATH_CMD} + PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} + PATHS ${SDL2_PATH} + DOC "Where the SDL2 Library can be found" + ) + +set(SDL2_LIBRARIES "${SDL2_LIBRARY}") + +if(NOT SDL2_BUILDING_LIBRARY) + if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL2main for compatibility even though they don't + # necessarily need it. + + if(SDL2_PATH) + set(SDL2MAIN_LIBRARY_PATHS "${SDL2_PATH}") + endif() + + if(NOT SDL2_NO_DEFAULT_PATH) + set(SDL2MAIN_LIBRARY_PATHS + /sw + /opt/local + /opt/csw + /opt + "${SDL2MAIN_LIBRARY_PATHS}" + ) + endif() + + find_library(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + ENV SDL2DIR + ${SDL2_NO_DEFAULT_PATH_CMD} + PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} + PATHS ${SDL2MAIN_LIBRARY_PATHS} + DOC "Where the SDL2main library can be found" + ) + unset(SDL2MAIN_LIBRARY_PATHS) + endif() +endif() + +# SDL2 may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +if(NOT APPLE) + find_package(Threads QUIET) + if(NOT Threads_FOUND) + set(SDL2_THREADS_NOT_FOUND "Could NOT find Threads (Threads is required by SDL2).") + if(SDL2_FIND_REQUIRED) + message(FATAL_ERROR ${SDL2_THREADS_NOT_FOUND}) + else() + if(NOT SDL2_FIND_QUIETLY) + message(STATUS ${SDL2_THREADS_NOT_FOUND}) + endif() + return() + endif() + unset(SDL2_THREADS_NOT_FOUND) + endif() +endif() + +# MinGW needs an additional link flag, -mwindows +# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows +if(MINGW) + set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") +endif() + +if(SDL2_LIBRARY) + # For SDL2main + if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) + list(FIND SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX) + if(_SDL2_MAIN_INDEX EQUAL -1) + set(SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARIES}) + endif() + unset(_SDL2_MAIN_INDEX) + endif() + + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + if(APPLE) + set(SDL2_LIBRARIES ${SDL2_LIBRARIES} -framework Cocoa) + endif() + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + if(NOT APPLE) + set(SDL2_LIBRARIES ${SDL2_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + endif() + + # For MinGW library + if(MINGW) + set(SDL2_LIBRARIES ${MINGW32_LIBRARY} ${SDL2_LIBRARIES}) + endif() + +endif() + +# Read SDL2 version +if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL_version.h") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") + set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) + unset(SDL2_VERSION_MAJOR_LINE) + unset(SDL2_VERSION_MINOR_LINE) + unset(SDL2_VERSION_PATCH_LINE) + unset(SDL2_VERSION_MAJOR) + unset(SDL2_VERSION_MINOR) + unset(SDL2_VERSION_PATCH) +endif() + +include(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 + REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR + VERSION_VAR SDL2_VERSION_STRING) + +if(SDL2MAIN_LIBRARY) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2main + REQUIRED_VARS SDL2MAIN_LIBRARY SDL2_INCLUDE_DIR + VERSION_VAR SDL2_VERSION_STRING) +endif() + + +mark_as_advanced(SDL2_PATH + SDL2_NO_DEFAULT_PATH + SDL2_LIBRARY + SDL2MAIN_LIBRARY + SDL2_INCLUDE_DIR + SDL2_BUILDING_LIBRARY) + + +# SDL2:: targets (SDL2::Core and SDL2::Main) +if(SDL2_FOUND) + + # SDL2::Core target + if(SDL2_LIBRARY AND NOT TARGET SDL2::Core) + add_library(SDL2::Core UNKNOWN IMPORTED) + set_target_properties(SDL2::Core PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}") + + if(APPLE) + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # For more details, please see above. + set_property(TARGET SDL2::Core APPEND PROPERTY + INTERFACE_LINK_OPTIONS -framework Cocoa) + else() + # For threads, as mentioned Apple doesn't need this. + # For more details, please see above. + set_property(TARGET SDL2::Core APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Threads::Threads) + endif() + endif() + + # SDL2::Main target + # Applications should link to SDL2::Main instead of SDL2::Core + # For more details, please see above. + if(NOT SDL2_BUILDING_LIBRARY AND NOT TARGET SDL2::Main) + + if(SDL2_INCLUDE_DIR MATCHES ".framework" OR NOT SDL2MAIN_LIBRARY) + add_library(SDL2::Main INTERFACE IMPORTED) + set_property(TARGET SDL2::Main PROPERTY + INTERFACE_LINK_LIBRARIES SDL2::Core) + elseif(SDL2MAIN_LIBRARY) + # MinGW requires that the mingw32 library is specified before the + # libSDL2main.a static library when linking. + # The SDL2::MainInternal target is used internally to make sure that + # CMake respects this condition. + add_library(SDL2::MainInternal UNKNOWN IMPORTED) + set_property(TARGET SDL2::MainInternal PROPERTY + IMPORTED_LOCATION "${SDL2MAIN_LIBRARY}") + set_property(TARGET SDL2::MainInternal PROPERTY + INTERFACE_LINK_LIBRARIES SDL2::Core) + + add_library(SDL2::Main INTERFACE IMPORTED) + + if(MINGW) + # MinGW needs an additional link flag '-mwindows' and link to mingw32 + set_property(TARGET SDL2::Main PROPERTY + INTERFACE_LINK_LIBRARIES "mingw32" "-mwindows") + endif() + + set_property(TARGET SDL2::Main APPEND PROPERTY + INTERFACE_LINK_LIBRARIES SDL2::MainInternal) + endif() + + endif() +endif() \ No newline at end of file diff --git a/packaging/windows/wipeout.exe.manifest b/packaging/windows/wipeout.exe.manifest new file mode 100644 index 0000000..92c5193 --- /dev/null +++ b/packaging/windows/wipeout.exe.manifest @@ -0,0 +1,8 @@ + + + + + true/pm + + + \ No newline at end of file diff --git a/packaging/windows/wipeout.ico b/packaging/windows/wipeout.ico new file mode 100644 index 0000000..9d3e7e9 Binary files /dev/null and b/packaging/windows/wipeout.ico differ diff --git a/packaging/windows/wipeout.rc b/packaging/windows/wipeout.rc new file mode 100644 index 0000000..f7e4639 --- /dev/null +++ b/packaging/windows/wipeout.rc @@ -0,0 +1,3 @@ +#define MANIFEST_RESOURCE_ID 1 +MANIFEST_RESOURCE_ID RT_MANIFEST "wipeout.exe.manifest" +IDI_ICON1 ICON DISCARDABLE "wipeout.ico" diff --git a/src/input.c b/src/input.c index 21593ad..fa9a841 100644 --- a/src/input.c +++ b/src/input.c @@ -159,16 +159,16 @@ static void *capture_user; static int32_t mouse_x; static int32_t mouse_y; -void input_init() { +void input_init(void) { input_unbind_all(INPUT_LAYER_SYSTEM); input_unbind_all(INPUT_LAYER_USER); } -void input_cleanup() { +void input_cleanup(void) { } -void input_clear() { +void input_clear(void) { clear(actions_pressed); clear(actions_released); } @@ -274,7 +274,7 @@ bool input_released(uint8_t action) { return actions_released[action]; } -vec2_t input_mouse_pos() { +vec2_t input_mouse_pos(void) { return vec2(mouse_x, mouse_y); } diff --git a/src/input.h b/src/input.h index bd1f227..fa8463e 100644 --- a/src/input.h +++ b/src/input.h @@ -156,9 +156,9 @@ typedef enum { typedef void(*input_capture_callback_t) (void *user, button_t button, int32_t ascii_char); -void input_init(); -void input_cleanup(); -void input_clear(); +void input_init(void); +void input_cleanup(void); +void input_clear(void); void input_bind(input_layer_t layer, button_t button, uint8_t action); void input_unbind(input_layer_t layer,button_t button); @@ -175,7 +175,7 @@ void input_capture(input_capture_callback_t cb, void *user); float input_state(uint8_t action); bool input_pressed(uint8_t action); bool input_released(uint8_t action); -vec2_t input_mouse_pos(); +vec2_t input_mouse_pos(void); button_t input_name_to_button(const char *name); const char *input_button_to_name(button_t button); diff --git a/src/libs/sokol_app.h b/src/libs/sokol_app.h index c64efa6..a1f45b6 100644 --- a/src/libs/sokol_app.h +++ b/src/libs/sokol_app.h @@ -19,7 +19,6 @@ project): #define SOKOL_GLCORE33 - #define SOKOL_GLES2 #define SOKOL_GLES3 #define SOKOL_D3D11 #define SOKOL_METAL @@ -28,16 +27,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,6 +47,9 @@ 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 @@ -65,7 +63,8 @@ - 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 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 @@ -76,10 +75,6 @@ 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 @@ -91,53 +86,55 @@ - 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 + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android + - 3D-APIs: Metal, D3D11, GL3.2, 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 - window icon | YES | YES(1)| YES | --- | --- | TODO | TODO | YES + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 3.x | 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 ============ @@ -160,6 +157,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. @@ -171,7 +180,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. @@ -192,11 +204,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 @@ -209,11 +216,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. @@ -243,6 +246,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, @@ -251,18 +258,13 @@ 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. - 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) @@ -356,6 +358,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 @@ -402,7 +430,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 @@ -596,8 +624,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 = ... }); @@ -611,9 +641,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: @@ -644,10 +674,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. - Here's an example on a Mac with Retina display: + 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) { @@ -658,19 +696,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 ================ @@ -722,7 +773,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 @@ -951,15 +1002,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 @@ -1047,9 +1172,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, @@ -1189,6 +1315,23 @@ typedef enum sapp_keycode { 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 @@ -1202,6 +1345,7 @@ 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; @@ -1324,20 +1468,153 @@ typedef struct sapp_icon_desc { 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 (*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 @@ -1347,16 +1624,18 @@ typedef struct sapp_desc { bool fullscreen; // whether the window should be created in fullscreen mode bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) const char* window_title; // the window title as UTF-8 encoded string - bool user_cursor; // if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR bool enable_clipboard; // enable clipboard access, default is false int clipboard_size; // max size of clipboard content in bytes bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false int max_dropped_files; // max number of dropped files to process (default: 1) int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048) sapp_icon_desc icon; // the initial window icon to set + 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 @@ -1378,23 +1657,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[]); @@ -1429,11 +1726,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 */ @@ -1448,6 +1749,8 @@ 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) */ @@ -1464,8 +1767,10 @@ 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); +/* 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); /* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); @@ -1528,12 +1833,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__) @@ -1561,31 +1878,20 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #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") + #if !defined(SOKOL_GLES3) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3 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 + #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") + #if !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3") #endif #if defined(SOKOL_NO_ENTRY) #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") @@ -1593,8 +1899,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) + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3") #endif #else #error "sokol_app.h: Unknown platform" @@ -1605,7 +1915,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 @@ -1615,32 +1925,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 @@ -1652,7 +1937,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 @@ -1671,6 +1955,8 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #import #endif #endif + #include + #include #elif defined(_SAPP_EMSCRIPTEN) #if defined(SOKOL_WGPU) #include @@ -1703,7 +1989,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #pragma comment (linker, "/subsystem:windows") #endif #endif - #include /* freopens() */ + #include /* freopen_s() */ #include /* wcslen() */ #pragma comment (lib, "kernel32") @@ -1727,39 +2013,13 @@ 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 + #ifndef WM_DPICHANGED + #define WM_DPICHANGED (0x02E0) #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 #include @@ -1773,13 +2033,241 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #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 -/*== MACOS DECLARATIONS ======================================================*/ +// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ +// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ +// +// >>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 @@ -1801,9 +2289,11 @@ typedef struct { 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 @@ -1811,7 +2301,6 @@ typedef struct { #endif // _SAPP_MACOS -/*== IOS DECLARATIONS ========================================================*/ #if defined(_SAPP_IOS) @interface _sapp_app_delegate : NSObject @@ -1846,7 +2335,6 @@ typedef struct { #endif // _SAPP_IOS -/*== EMSCRIPTEN DECLARATIONS =================================================*/ #if defined(_SAPP_EMSCRIPTEN) #if defined(SOKOL_WGPU) @@ -1875,8 +2363,7 @@ typedef struct { } _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; @@ -1888,10 +2375,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 @@ -1918,11 +2407,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; @@ -1996,25 +2488,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, @@ -2060,7 +2533,6 @@ typedef struct { #endif // _SAPP_ANDROID -/*== LINUX DECLARATIONS ======================================================*/ #if defined(_SAPP_LINUX) #define _SAPP_X11_XDND_VERSION (5) @@ -2142,6 +2614,7 @@ typedef struct { Colormap colormap; Window window; Cursor hidden_cursor; + Cursor cursors[_SAPP_MOUSECURSOR_NUM]; int window_state; float dpi; unsigned char error_code; @@ -2158,6 +2631,8 @@ typedef struct { _sapp_xdnd_t xdnd; } _sapp_x11_t; +#if defined(_SAPP_GLX) + typedef struct { void* libgl; int major; @@ -2196,30 +2671,40 @@ typedef struct { bool ARB_create_context_profile; } _sapp_glx_t; -#endif // _SAPP_LINUX +#else -/*== COMMON DECLARATIONS =====================================================*/ +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 (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 { @@ -2243,13 +2728,13 @@ typedef struct { 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; @@ -2266,6 +2751,7 @@ 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; @@ -2285,16 +2771,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 */ @@ -2303,20 +2788,94 @@ typedef struct { } _sapp_t; static _sapp_t _sapp; -/*=== 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); +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>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 { - SOKOL_LOG(msg); + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } } - SOKOL_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(); @@ -2406,24 +2965,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; @@ -2437,39 +3012,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) { - SOKOL_FREE((void*)_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; @@ -2500,7 +3076,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); } } @@ -2520,7 +3096,7 @@ _SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) { SOKOL_ASSERT(desc->pixels.size > 0); const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t); if (wh_size != desc->pixels.size) { - SOKOL_LOG("Image data size mismatch (must be width*height*4 bytes)\n"); + _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); return false; } return true; @@ -2574,7 +3150,7 @@ _SOKOL_PRIVATE void _sapp_setup_default_icon(void) { for (int i = 0; i < num_icons; i++) { all_num_pixels += icon_sizes[i] * icon_sizes[i]; } - _sapp.default_icon_pixels = (uint32_t*) SOKOL_CALLOC((size_t)all_num_pixels, sizeof(uint32_t)); + _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; @@ -2676,7 +3252,13 @@ _SOKOL_PRIVATE void _sapp_setup_default_icon(void) { SOKOL_ASSERT(dst == dst_end); } -/*== MacOS/iOS ===============================================================*/ +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>apple #if defined(_SAPP_APPLE) #if __has_feature(objc_arc) @@ -2685,7 +3267,13 @@ _SOKOL_PRIVATE void _sapp_setup_default_icon(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) { @@ -2804,6 +3392,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); @@ -2814,15 +3407,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]; + // 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 @@ -2838,7 +3464,7 @@ int main(int argc, char* argv[]) { #endif /* SOKOL_NO_ENTRY */ _SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) { - const NSEventModifierFlags f = ev.modifierFlags; + const NSEventModifierFlags f = (ev == nil) ? NSEvent.modifierFlags : ev.modifierFlags; const NSUInteger b = NSEvent.pressedMouseButtons; uint32_t m = 0; if (f & NSEventModifierFlagShift) { @@ -2896,18 +3522,31 @@ _SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { between HighDPI / LowDPI screens. */ _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 + 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 = bounds.size.width; - _sapp.window_height = bounds.size.height; + _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; } @@ -2920,17 +3559,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; - - /* NOTE: _sapp_macos_update_dimensions() isn't called each frame, but only - when the window size actually changes, so resizing the MTKView's - in each call is fine even when MTKView doesn't ignore setting an - identical drawableSize. - */ - #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) { @@ -2971,13 +3610,16 @@ _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_from_nspoint(NSPoint mouse_pos, bool clear_dxdy) { 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) { + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } + else 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; } @@ -2987,6 +3629,10 @@ _SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) { } } +_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nsevent(NSEvent* event, bool clear_dxdy) { + _sapp_macos_mouse_update_from_nspoint(event.locationInWindow, clear_dxdy); +} + _SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { /* NOTE: this function is only called when the mouse visibility actually changes */ if (visible) { @@ -3016,14 +3662,34 @@ _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; @@ -3067,20 +3733,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.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); + } } - 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 | @@ -3100,10 +3763,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; @@ -3117,7 +3786,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; @@ -3157,14 +3834,12 @@ _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]; @@ -3211,9 +3886,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 { @@ -3226,6 +3904,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; @@ -3263,19 +3951,21 @@ _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; } } if (!drop_failed) { if (_sapp_events_enabled()) { + _sapp_macos_mouse_update_from_nspoint(sender.draggingLocation, true); _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_macos_mods(nil); _sapp_call_event(&_sapp.event); } } @@ -3292,19 +3982,6 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { @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]; @@ -3322,7 +3999,7 @@ _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 probelms with the + causes infrequent and almost impossible to reproduce problems with the window close events, see: https://github.com/floooh/sokol/pull/483#issuecomment-805148815 @@ -3363,6 +4040,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - (void)drawRect:(NSRect)rect { _SOKOL_UNUSED(rect); + _sapp_timing_measure(&_sapp.timing); /* Catch any last-moment input events */ _sapp_macos_poll_input_events(); @autoreleasepool { @@ -3397,8 +4075,17 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { [self addTrackingArea:_sapp.macos.tracking_area]; [super updateTrackingAreas]; } + +// helper function to make GL context active +static void _sapp_gl_make_current(void) { + #if defined(SOKOL_GLCORE33) + [[_sapp.macos.view openGLContext] makeCurrentContext]; + #endif +} + - (void)mouseEntered:(NSEvent*)event { - _sapp_macos_update_mouse(event); + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); /* don't send mouse enter/leave while dragging (so that it behaves the same as on Windows while SetCapture is active */ @@ -3407,47 +4094,55 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { } } - (void)mouseExited:(NSEvent*)event { - _sapp_macos_update_mouse(event); + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); 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_update_mouse(event); + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, false); _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event)); _sapp.macos.mouse_buttons |= (1< 0) { @@ -3533,6 +4226,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { } } - (void)keyUp:(NSEvent*)event { + _sapp_gl_make_current(); _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, _sapp_translate_key(event.keyCode), event.isARepeat, @@ -3540,7 +4234,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { } - (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; @@ -3567,17 +4261,17 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { _sapp_macos_mods(event)); } } -- (void)cursorUpdate:(NSEvent*)event { - _SOKOL_UNUSED(event); - if (_sapp.desc.user_cursor) { - _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - } -} @end -#endif /* MacOS */ +#endif // macOS -/*== iOS =====================================================================*/ +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios #if defined(_SAPP_IOS) _SOKOL_PRIVATE void _sapp_ios_discard_state(void) { @@ -3641,18 +4335,18 @@ _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.framebuffer_width = (int)(screen_rect.size.width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)(screen_rect.size.height * _sapp.dpi_scale); - _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); @@ -3712,20 +4406,21 @@ _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.dpi_scale = (float) UIScreen.mainScreen.nativeScale; } else { _sapp.dpi_scale = 1.0f; } - _sapp.framebuffer_width = _sapp.window_width * _sapp.dpi_scale; - _sapp.framebuffer_height = _sapp.window_height * _sapp.dpi_scale; + _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; @@ -3742,17 +4437,7 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { _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.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; @@ -3765,14 +4450,14 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { _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]; @@ -3884,6 +4569,7 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { @implementation _sapp_ios_view - (void)drawRect:(CGRect)rect { _SOKOL_UNUSED(rect); + _sapp_timing_measure(&_sapp.timing); @autoreleasepool { _sapp_ios_frame(); } @@ -3908,9 +4594,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 @@ -3966,12 +4662,12 @@ 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; } } -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y, int mods) { if (!_sapp.drop.enabled) { return; } @@ -3987,19 +4683,25 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { _sapp.mouse.dx = 0.0f; _sapp.mouse.dy = 0.0f; _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + // see sapp_js_add_dragndrop_listeners for mods constants + if (mods & 1) { _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; } + if (mods & 2) { _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; } + if (mods & 4) { _sapp.event.modifiers |= SAPP_MODIFIER_ALT; } + if (mods & 8) { _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; } _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)); + _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); } @@ -4010,7 +4712,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"; @@ -4030,7 +4732,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 = ' '; @@ -4044,9 +4746,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); }); @@ -4056,8 +4761,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'); @@ -4079,32 +4784,39 @@ _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); + }); } + let mods = 0; + if (event.shiftKey) { mods |= 1; } + if (event.ctrlKey) { mods |= 2; } + if (event.altKey) { mods |= 4; } + if (event.metaKey) { mods |= 8; } // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect - __sapp_emsc_end_drop(event.clientX, event.clientY); + __sapp_emsc_end_drop(event.clientX, event.clientY, mods); }; canvas.addEventListener('dragenter', Module.sokol_dragenter, false); canvas.addEventListener('dragleave', Module.sokol_dragleave, false); @@ -4113,18 +4825,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); @@ -4134,16 +4848,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); @@ -4189,9 +4905,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); @@ -4253,23 +4969,53 @@ _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), { - var link = document.getElementById('sokol-app-favicon'); + 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), { - var canvas = document.createElement('canvas'); + const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; - var ctx = canvas.getContext('2d'); - var img_data = ctx.createImageData(w, 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); - var new_link = document.createElement('link'); + const new_link = document.createElement('link'); new_link.id = 'sokol-app-favicon'; new_link.rel = 'shortcut icon'; new_link.href = canvas.toDataURL(); @@ -4357,19 +5103,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) @@ -4390,8 +5136,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE if (_sapp.mouse.locked) { _sapp.mouse.dx = (float) emsc_event->movementX; _sapp.mouse.dy = (float) emsc_event->movementY; - } - else { + } else { float new_x = emsc_event->targetX * _sapp.dpi_scale; float new_y = emsc_event->targetY * _sapp.dpi_scale; if (_sapp.mouse.pos_valid) { @@ -4405,6 +5150,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { sapp_event_type type; bool is_button_event = false; + bool clear_dxdy = false; switch (emsc_type) { case EMSCRIPTEN_EVENT_MOUSEDOWN: type = SAPP_EVENTTYPE_MOUSE_DOWN; @@ -4419,14 +5165,20 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE break; case EMSCRIPTEN_EVENT_MOUSEENTER: type = SAPP_EVENTTYPE_MOUSE_ENTER; + clear_dxdy = true; break; case EMSCRIPTEN_EVENT_MOUSELEAVE: type = SAPP_EVENTTYPE_MOUSE_LEAVE; + clear_dxdy = true; break; default: type = SAPP_EVENTTYPE_INVALID; break; } + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } if (type != SAPP_EVENTTYPE_INVALID) { _sapp_init_event(type); _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(emsc_event); @@ -4437,13 +5189,12 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseE case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; } - } - else { + } 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) */ + // 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(); } @@ -4476,6 +5227,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; @@ -4501,6 +5373,7 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard _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)) { @@ -4508,7 +5381,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 @@ -4521,7 +5405,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: @@ -4587,7 +5471,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) { @@ -4648,111 +5532,29 @@ _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; } -#if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +_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_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); @@ -4779,21 +5581,9 @@ _SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { 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 + attrs.majorVersion = 2; 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; - } + // FIXME: error message? emscripten_webgl_make_context_current(ctx); /* some WebGL extension are not enabled automatically by emscripten */ @@ -4825,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 + ")"); @@ -4854,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; @@ -4869,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; @@ -4927,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(); @@ -4934,7 +5726,7 @@ _SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { if (_sapp.drop.enabled) { sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + #if 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 @@ -4956,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(); @@ -4963,15 +5757,15 @@ _SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { if (_sapp.drop.enabled) { sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); } - #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) + #if 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); + _sapp_timing_external(&_sapp.timing, time / 1000.0); #if defined(SOKOL_WGPU) /* @@ -5018,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); @@ -5032,12 +5825,12 @@ _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) + #if defined(SOKOL_GLES3) _sapp_emsc_webgl_init(); #elif defined(SOKOL_WGPU) sapp_js_wgpu_init(); @@ -5063,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; @@ -5078,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; @@ -5172,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)) { @@ -5189,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; @@ -5317,22 +6122,26 @@ _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_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) @@ -5342,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); @@ -5390,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; @@ -5402,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; @@ -5429,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); } @@ -5449,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, _sapp_IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); - #else - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &_sapp_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; @@ -5505,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 */ @@ -5521,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"); @@ -5544,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); @@ -5555,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); } } @@ -5608,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"); @@ -5642,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; } @@ -5653,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++) { @@ -5700,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 @@ -5733,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); @@ -5767,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); @@ -5778,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; @@ -5792,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); } - 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; - } - + 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, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | 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_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) { @@ -5854,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, @@ -5875,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 @@ -5886,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 */ @@ -5899,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); - int fb_width = (int)((float)_sapp.window_width * _sapp.win32.dpi.content_scale); - int fb_height = (int)((float)_sapp.window_height * _sapp.win32.dpi.content_scale); - /* prevent a framebuffer size of 0 when window is minimized */ - if (0 == fb_width) { - fb_width = 1; - } - if (0 == fb_height) { - fb_height = 1; - } - if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { - _sapp.framebuffer_width = fb_width; - _sapp.framebuffer_height = fb_height; - 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) { @@ -5954,6 +6952,21 @@ _SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { 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); @@ -6002,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; @@ -6012,18 +7053,19 @@ _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) { if (_sapp_events_enabled()) { _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_win32_mods(); _sapp_call_event(&_sapp.event); } } @@ -6033,6 +7075,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) { @@ -6043,7 +7113,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; @@ -6075,66 +7145,81 @@ _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) */ @@ -6178,6 +7264,8 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM case WM_MOUSELEAVE: if (!_sapp.mouse.locked) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; _sapp.win32.mouse_tracked = false; _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); } @@ -6206,9 +7294,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(); @@ -6220,13 +7310,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; } @@ -6236,7 +7342,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); @@ -6245,42 +7351,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); } @@ -6312,8 +7426,9 @@ _SOKOL_PRIVATE void _sapp_win32_init_console(void) { if (con_valid) { FILE* res_fp = 0; errno_t err; - err = freopens(&res_fp, "CON", "w", stdout); - err = freopens(&res_fp, "CON", "w", stderr); + err = freopen_s(&res_fp, "CON", "w", stdout); + (void)err; + err = freopen_s(&res_fp, "CON", "w", stderr); (void)err; } } @@ -6331,35 +7446,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; - _sapp.win32.dpi.aware = false; + 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) { @@ -6397,26 +7533,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; @@ -6427,6 +7569,7 @@ error: if (object) { GlobalFree(object); } + CloseClipboard(); return false; } @@ -6450,7 +7593,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(); @@ -6458,13 +7601,13 @@ _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; - memset(&bi, 0, sizeof(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 @@ -6503,7 +7646,7 @@ _SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* d } ICONINFO icon_info; - memset(&icon_info, 0, sizeof(icon_info)); + _sapp_clear(&icon_info, sizeof(icon_info)); icon_info.fIcon = true; icon_info.xHotspot = 0; icon_info.yHotspot = 0; @@ -6560,9 +7703,10 @@ _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) @@ -6578,6 +7722,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { 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) { @@ -6586,12 +7731,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)); } @@ -6604,7 +7749,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); @@ -6632,17 +7783,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; @@ -6672,7 +7823,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 */ @@ -6684,1017 +7835,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; - } - if (0 != (_sapp.uwp.mouse_buttons & (1<= 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 */ @@ -7709,10 +7856,10 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { 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_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, @@ -7749,11 +7896,7 @@ _SOKOL_PRIVATE bool _sapp_android_init_egl(void) { } 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_CONTEXT_CLIENT_VERSION, 3, EGL_NONE, }; EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); @@ -7771,16 +7914,13 @@ _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; } @@ -7821,7 +7961,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); } } @@ -7844,14 +7983,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); } } @@ -7859,26 +7998,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(); } } @@ -7897,6 +8033,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); @@ -7914,22 +8051,17 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { 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: @@ -7949,7 +8081,7 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { 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); @@ -7977,8 +8109,10 @@ _SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { } _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); @@ -7997,14 +8131,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; } @@ -8012,45 +8147,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(); } } @@ -8058,7 +8190,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); @@ -8075,13 +8207,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" */ @@ -8099,17 +8231,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, @@ -8154,34 +8284,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 { @@ -8190,12 +8325,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) { @@ -8209,12 +8346,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); } @@ -8229,22 +8369,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) { @@ -8256,7 +8401,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); @@ -8273,7 +8419,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); @@ -8281,17 +8427,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]; @@ -8339,14 +8491,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 */ @@ -9246,6 +9404,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); @@ -9255,13 +9414,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; @@ -9312,7 +9479,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"); @@ -9343,17 +9510,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)) { @@ -9396,10 +9563,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]; @@ -9447,18 +9614,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; @@ -9468,27 +9635,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); } } @@ -9521,9 +9688,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; @@ -9580,24 +9749,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) { @@ -9667,7 +9889,7 @@ _SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_ const sapp_image_desc* img_desc = &icon_desc->images[i]; long_count += 2 + (img_desc->width * img_desc->height); } - long* icon_data = (long*) SOKOL_CALLOC((size_t)long_count, sizeof(long)); + 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++) { @@ -9689,14 +9911,14 @@ _SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_ PropModeReplace, (unsigned char*)icon_data, long_count); - SOKOL_FREE(icon_data); + _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; @@ -9704,12 +9926,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, @@ -9718,7 +9960,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 @@ -9726,8 +9968,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); @@ -9736,8 +9982,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) { @@ -9877,6 +10123,23 @@ _SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) } } +_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y, bool clear_dxdy) { + if (!_sapp.mouse.locked) { + const float new_x = (float) x; + const float new_y = (float) y; + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } else 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); @@ -10126,7 +10389,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; } @@ -10135,7 +10398,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 @@ -10146,7 +10408,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); @@ -10160,7 +10422,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; } @@ -10206,11 +10468,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: { @@ -10247,6 +10519,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { break; case ButtonPress: { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); 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 @@ -10268,6 +10541,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { break; case ButtonRelease: { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); const sapp_mousebutton btn = _sapp_x11_translate_button(event); if (btn != SAPP_MOUSEBUTTON_INVALID) { uint32_t mods = _sapp_x11_mods(event->xbutton.state); @@ -10281,25 +10555,19 @@ _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_update(event->xcrossing.x, event->xcrossing.y, true); _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, true); _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_update(event->xmotion.x, event->xmotion.y, false); _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); } break; @@ -10383,9 +10651,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; @@ -10404,7 +10672,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; @@ -10430,7 +10698,12 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { (unsigned char**) &data); if (_sapp.drop.enabled && result) { if (_sapp_x11_parse_dropped_files_list(data)) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; if (_sapp_events_enabled()) { + // FIXME: Figure out how to get modifier key state here. + // The XSelection event has no 'state' item, and + // XQueryKeymap() always returns a zeroed array. _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); _sapp_call_event(&_sapp.event); } @@ -10438,9 +10711,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; @@ -10456,6 +10729,139 @@ _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, EGL_OPENGL_ES3_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, 3, + #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); +} + +_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 @@ -10472,7 +10878,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); @@ -10480,24 +10886,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; @@ -10505,7 +10915,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) { @@ -10518,8 +10932,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(); } @@ -10533,7 +10952,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); @@ -10545,13 +10970,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 } @@ -10560,7 +10982,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 @@ -10586,6 +11008,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; } @@ -10636,8 +11062,26 @@ 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 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_API_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 void sapp_show_keyboard(bool show) { @@ -10665,8 +11109,6 @@ SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { _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 @@ -10676,13 +11118,13 @@ SOKOL_API_IMPL 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; } @@ -10710,6 +11152,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; } @@ -10728,7 +11190,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; } @@ -10746,7 +11207,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 ""; } @@ -10838,15 +11298,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) { @@ -10855,15 +11315,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 @@ -11042,7 +11502,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/libs/sokol_time.h b/src/libs/sokol_time.h index 3bf1167..2d4d456 100644 --- a/src/libs/sokol_time.h +++ b/src/libs/sokol_time.h @@ -61,6 +61,8 @@ The main purpose of this function is to remove jitter/inaccuracies from measured frame times, and instead use the display refresh rate as frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() Use the following functions to convert a duration in ticks into useful time units: @@ -77,7 +79,7 @@ Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() MacOS/iOS: mach_absolute_time() - emscripten: performance.now() + emscripten: emscripten_get_now() Linux+others: clock_gettime(CLOCK_MONOTONIC) zlib/libpng license @@ -199,19 +201,13 @@ static _stm_state_t _stm; see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) -_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { +_SOKOL_PRIVATE int64_t _stm_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; } #endif -#if defined(__EMSCRIPTEN__) -EM_JS(double, stm_js_perfnow, (void), { - return performance.now(); -}); -#endif - SOKOL_API_IMPL void stm_setup(void) { memset(&_stm, 0, sizeof(_stm)); _stm.initialized = 0xABCDABCD; @@ -222,7 +218,7 @@ SOKOL_API_IMPL void stm_setup(void) { mach_timebase_info(&_stm.timebase); _stm.start = mach_absolute_time(); #elif defined(__EMSCRIPTEN__) - _stm.start = stm_js_perfnow(); + _stm.start = emscripten_get_now(); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -236,13 +232,12 @@ SOKOL_API_IMPL uint64_t stm_now(void) { #if defined(_WIN32) LARGE_INTEGER qpc_t; QueryPerformanceCounter(&qpc_t); - now = (uint64_t) int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); + now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); #elif defined(__APPLE__) && defined(__MACH__) const uint64_t mach_now = mach_absolute_time() - _stm.start; - now = (uint64_t) int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); + now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); #elif defined(__EMSCRIPTEN__) - double js_now = stm_js_perfnow() - _stm.start; - SOKOL_ASSERT(js_now >= 0.0); + double js_now = emscripten_get_now() - _stm.start; now = (uint64_t) (js_now * 1000000.0); #else struct timespec ts; diff --git a/src/mem.c b/src/mem.c index 3316d73..d209982 100644 --- a/src/mem.c +++ b/src/mem.c @@ -18,7 +18,7 @@ static uint32_t temp_objects_len; // These allocations persist for many frames. The allocator level is reset // whenever we load a new race track or menu in game_set_scene() -void *mem_mark() { +void *mem_mark(void) { return &hunk[bump_len]; } @@ -76,6 +76,6 @@ void mem_temp_free(void *p) { temp_len = remaining_max; } -void mem_temp_check() { +void mem_temp_check(void) { error_if(temp_len != 0, "Temp memory not free: %d object(s)", temp_objects_len); } diff --git a/src/mem.h b/src/mem.h index b909067..b52e305 100644 --- a/src/mem.h +++ b/src/mem.h @@ -7,11 +7,11 @@ #define MEM_HUNK_BYTES (4 * 1024 * 1024) void *mem_bump(uint32_t size); -void *mem_mark(); +void *mem_mark(void); void mem_reset(void *p); void *mem_temp_alloc(uint32_t size); void mem_temp_free(void *p); -void mem_temp_check(); +void mem_temp_check(void); #endif diff --git a/src/platform.h b/src/platform.h index e755b4c..9e93904 100644 --- a/src/platform.h +++ b/src/platform.h @@ -3,10 +3,10 @@ #include "types.h" -void platform_exit(); -vec2i_t platform_screen_size(); -double platform_now(); -bool platform_get_fullscreen(); +void platform_exit(void); +vec2i_t platform_screen_size(void); +double platform_now(void); +bool platform_get_fullscreen(void); void platform_set_fullscreen(bool fullscreen); void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)); diff --git a/src/platform_sdl.c b/src/platform_sdl.c index cf000bc..31e1ee0 100755 --- a/src/platform_sdl.c +++ b/src/platform_sdl.c @@ -48,11 +48,11 @@ uint8_t platform_sdl_axis_map[] = { }; -void platform_exit() { +void platform_exit(void) { wants_to_exit = true; } -SDL_GameController *platform_find_gamepad() { +SDL_GameController *platform_find_gamepad(void) { for (int i = 0; i < SDL_NumJoysticks(); i++) { if (SDL_IsGameController(i)) { return SDL_GameControllerOpen(i); @@ -63,7 +63,7 @@ SDL_GameController *platform_find_gamepad() { } -void platform_pump_events() { +void platform_pump_events(void) { SDL_Event ev; while (SDL_PollEvent(&ev)) { // Detect ALT+Enter press to toggle fullscreen @@ -188,12 +188,12 @@ void platform_pump_events() { } } -double platform_now() { +double platform_now(void) { uint64_t perf_counter = SDL_GetPerformanceCounter(); return (double)perf_counter / (double)perf_freq; } -bool platform_get_fullscreen() { +bool platform_get_fullscreen(void) { return SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN; } @@ -251,7 +251,7 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { #define PLATFORM_WINDOW_FLAGS SDL_WINDOW_OPENGL SDL_GLContext platform_gl; - void platform_video_init() { + void platform_video_init(void) { #if defined(USE_GLES2) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); @@ -262,19 +262,19 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { SDL_GL_SetSwapInterval(1); } - void platform_prepare_frame() { + void platform_prepare_frame(void) { // nothing } - void platform_video_cleanup() { + void platform_video_cleanup(void) { SDL_GL_DeleteContext(platform_gl); } - void platform_end_frame() { + void platform_end_frame(void) { SDL_GL_SwapWindow(window); } - vec2i_t platform_screen_size() { + vec2i_t platform_screen_size(void) { int width, height; SDL_GL_GetDrawableSize(window, &width, &height); return vec2i(width, height); @@ -291,18 +291,18 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { static vec2i_t screen_size = vec2i(0, 0); - void platform_video_init() { + void platform_video_init(void) { renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); } - void platform_video_cleanup() { + void platform_video_cleanup(void) { if (screenbuffer) { SDL_DestroyTexture(screenbuffer); } SDL_DestroyRenderer(renderer); } - void platform_prepare_frame() { + void platform_prepare_frame(void) { if (screen_size.x != screenbuffer_size.x || screen_size.y != screenbuffer_size.y) { if (screenbuffer) { SDL_DestroyTexture(screenbuffer); @@ -313,7 +313,7 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { SDL_LockTexture(screenbuffer, NULL, &screenbuffer_pixels, &screenbuffer_pitch); } - void platform_end_frame() { + void platform_end_frame(void) { screenbuffer_pixels = NULL; SDL_UnlockTexture(screenbuffer); SDL_RenderCopy(renderer, screenbuffer, NULL, NULL); @@ -325,7 +325,7 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { return screenbuffer_pixels; } - vec2i_t platform_screen_size() { + vec2i_t platform_screen_size(void) { int width, height; SDL_GetWindowSize(window, &width, &height); @@ -343,7 +343,7 @@ int main(int argc, char *argv[]) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); // Figure out the absolute asset and userdata paths. These may either be - // supplied at build time through -DPATH_ASSETS=.. and -DPATH_USERDATA=.. + // supplied at build time through -DPATH_ASSETS=.. and -DPATH_USERDATA=.. // or received at runtime from SDL. Note that SDL may return NULL for these. // We fall back to the current directory (i.e. just "") in this case. diff --git a/src/platform_sokol.c b/src/platform_sokol.c index d7122de..e06a332 100644 --- a/src/platform_sokol.c +++ b/src/platform_sokol.c @@ -5,7 +5,7 @@ #if defined(RENDERER_GL) #ifdef __EMSCRIPTEN__ - #define SOKOL_GLES2 + #define SOKOL_GLES3 #else #define SOKOL_GLCORE33 #endif @@ -14,9 +14,9 @@ #endif #define SOKOL_IMPL -#include "libs/sokol_audio.h" -#include "libs/sokol_time.h" -#include "libs/sokol_app.h" +#include +#include +#include #include "input.h" // FIXME: we should figure out the actual path where the executabe resides, @@ -161,15 +161,15 @@ static const uint8_t keyboard_map[] = { static void (*audio_callback)(float *buffer, uint32_t len) = NULL; -void platform_exit() { +void platform_exit(void) { sapp_quit(); } -vec2i_t platform_screen_size() { +vec2i_t platform_screen_size(void) { return vec2i(sapp_width(), sapp_height()); } -double platform_now() { +double platform_now(void) { return stm_sec(stm_now()); } diff --git a/src/render.h b/src/render.h index 335f9ad..74d96cc 100644 --- a/src/render.h +++ b/src/render.h @@ -28,18 +28,18 @@ typedef enum { extern uint16_t RENDER_NO_TEXTURE; void render_init(vec2i_t screen_size); -void render_cleanup(); +void render_cleanup(void); void render_set_screen_size(vec2i_t size); void render_set_resolution(render_resolution_t res); void render_set_post_effect(render_post_effect_t post); -vec2i_t render_size(); +vec2i_t render_size(void); -void render_frame_prepare(); -void render_frame_end(); +void render_frame_prepare(void); +void render_frame_end(void); void render_set_view(vec3_t pos, vec3_t angles); -void render_set_view_2d(); +void render_set_view_2d(void); void render_set_model_mat(mat4_t *m); void render_set_depth_write(bool enabled); void render_set_depth_test(bool enabled); @@ -57,7 +57,7 @@ void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_ uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels); vec2i_t render_texture_size(uint16_t texture_index); void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels); -uint16_t render_textures_len(); +uint16_t render_textures_len(void); void render_textures_reset(uint16_t len); void render_textures_dump(const char *path); diff --git a/src/render_gl.c b/src/render_gl.c index 435d43c..f09b49b 100644 --- a/src/render_gl.c +++ b/src/render_gl.c @@ -11,23 +11,19 @@ // Linux #elif defined(__unix__) #include -#elif defined(__MSYS__) - #include -// WINDOWS -#else + +// Windows +#elif defined(WIN32) #include #define GL3_PROTOTYPES 1 - #include - #pragma comment(lib, "glew32.lib") - - #include - #pragma comment(lib, "opengl32.lib") + #include + #include #endif -#include "libs/stb_image_write.h" +#include #include "system.h" #include "render.h" @@ -137,7 +133,7 @@ static const char * const SHADER_GAME_VS = SHADER_SOURCE( uniform vec2 fade; uniform float time; - void main() { + void main(void) { gl_Position = projection * view * model * vec4(pos, 1.0); gl_Position.xy += screen.xy * gl_Position.w; v_color = color; @@ -154,7 +150,7 @@ static const char * const SHADER_GAME_FS = SHADER_SOURCE( varying vec2 v_uv; uniform sampler2D texture; - void main() { + void main(void) { vec4 tex_color = texture2D(texture, v_uv); vec4 color = tex_color * v_color; if (color.a == 0.0) { @@ -184,7 +180,7 @@ typedef struct { } attribute; } prg_game_t; -prg_game_t *shader_game_init() { +prg_game_t *shader_game_init(void) { prg_game_t *s = mem_bump(sizeof(prg_game_t)); s->program = create_program(SHADER_GAME_VS, SHADER_GAME_FS); @@ -228,7 +224,7 @@ static const char * const SHADER_POST_VS = SHADER_SOURCE( uniform vec2 screen_size; uniform float time; - void main() { + void main(void) { gl_Position = projection * vec4(pos, 1.0); v_uv = uv; } @@ -240,7 +236,7 @@ static const char * const SHADER_POST_FS_DEFAULT = SHADER_SOURCE( uniform sampler2D texture; uniform vec2 screen_size; - void main() { + void main(void) { gl_FragColor = texture2D(texture, v_uv); } ); @@ -332,14 +328,14 @@ void shader_post_general_init(prg_post_t *s) { bind_va_f(s->attribute.uv, vertex_t, uv, 0); } -prg_post_t *shader_post_default_init() { +prg_post_t *shader_post_default_init(void) { prg_post_t *s = mem_bump(sizeof(prg_post_t)); s->program = create_program(SHADER_POST_VS, SHADER_POST_FS_DEFAULT); shader_post_general_init(s); return s; } -prg_post_t *shader_post_crt_init() { +prg_post_t *shader_post_crt_init(void) { prg_post_t *s = mem_bump(sizeof(prg_post_t)); s->program = create_program(SHADER_POST_VS, SHADER_POST_FS_CRT); shader_post_general_init(s); @@ -383,7 +379,7 @@ prg_post_t *prg_post; prg_post_t *prg_post_effects[NUM_RENDER_POST_EFFCTS] = {}; -static void render_flush(); +static void render_flush(void); // static void gl_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { @@ -464,7 +460,7 @@ void render_init(vec2i_t screen_size) { render_set_screen_size(screen_size); } -void render_cleanup() { +void render_cleanup(void) { // TODO } @@ -572,11 +568,11 @@ void render_set_post_effect(render_post_effect_t post) { prg_post = prg_post_effects[post]; } -vec2i_t render_size() { +vec2i_t render_size(void) { return backbuffer_size; } -void render_frame_prepare() { +void render_frame_prepare(void) { use_program(prg_game); glBindFramebuffer(GL_FRAMEBUFFER, backbuffer); glViewport(0, 0, backbuffer_size.x, backbuffer_size.y); @@ -591,7 +587,7 @@ void render_frame_prepare() { glEnable(GL_DEPTH_TEST); } -void render_frame_end() { +void render_frame_end(void) { render_flush(); use_program(prg_post); @@ -625,7 +621,7 @@ void render_frame_end() { render_flush(); } -void render_flush() { +void render_flush(void) { if (tris_len == 0) { return; } @@ -662,7 +658,7 @@ void render_set_view(vec3_t pos, vec3_t angles) { glUniform2f(prg_game->uniform.fade, RENDER_FADEOUT_NEAR, RENDER_FADEOUT_FAR); } -void render_set_view_2d() { +void render_set_view_2d(void) { render_flush(); render_set_depth_test(false); render_set_depth_write(false); @@ -959,7 +955,7 @@ void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels) { glTexSubImage2D(GL_TEXTURE_2D, 0, t->offset.x, t->offset.y, t->size.x, t->size.y, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } -uint16_t render_textures_len() { +uint16_t render_textures_len(void) { return textures_len; } diff --git a/src/render_software.c b/src/render_software.c index cca370e..1911a6b 100644 --- a/src/render_software.c +++ b/src/render_software.c @@ -43,7 +43,7 @@ void render_init(vec2i_t screen_size) { RENDER_NO_TEXTURE = render_texture_create(2, 2, white_pixels); } -void render_cleanup() {} +void render_cleanup(void) {} void render_set_screen_size(vec2i_t size) { screen_size = size; @@ -63,12 +63,12 @@ void render_set_screen_size(vec2i_t size) { void render_set_resolution(render_resolution_t res) {} void render_set_post_effect(render_post_effect_t post) {} -vec2i_t render_size() { +vec2i_t render_size(void) { return screen_size; } -void render_frame_prepare() { +void render_frame_prepare(void) { screen_buffer = platform_get_screenbuffer(&screen_pitch); screen_ppr = screen_pitch / sizeof(rgba_t); @@ -80,7 +80,7 @@ void render_frame_prepare() { } } -void render_frame_end() {} +void render_frame_end(void) {} void render_set_view(vec3_t pos, vec3_t angles) { view_mat = mat4_identity(); @@ -92,7 +92,7 @@ void render_set_view(vec3_t pos, vec3_t angles) { render_set_model_mat(&mat4_identity()); } -void render_set_view_2d() { +void render_set_view_2d(void) { float near = -1; float far = 1; float left = 0; @@ -227,7 +227,7 @@ void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels) { // memcpy(t->pixels, pixels, t->size.x * t->size.y * sizeof(rgba_t)); } -uint16_t render_textures_len() { +uint16_t render_textures_len(void) { return textures_len; } diff --git a/src/system.c b/src/system.c index 6f1a266..7440ba5 100644 --- a/src/system.c +++ b/src/system.c @@ -13,23 +13,23 @@ static double time_scale = 1.0; static double tick_last; static double cycle_time = 0; -void system_init() { +void system_init(void) { time_real = platform_now(); input_init(); render_init(platform_screen_size()); game_init(); } -void system_cleanup() { +void system_cleanup(void) { render_cleanup(); input_cleanup(); } -void system_exit() { +void system_exit(void) { platform_exit(); } -void system_update() { +void system_update(void) { double time_real_now = platform_now(); double real_delta = time_real_now - time_real; time_real = time_real_now; @@ -52,7 +52,7 @@ void system_update() { mem_temp_check(); } -void system_reset_cycle_time() { +void system_reset_cycle_time(void) { cycle_time = 0; } @@ -60,7 +60,7 @@ void system_resize(vec2i_t size) { render_set_screen_size(size); } -double system_time_scale_get() { +double system_time_scale_get(void) { return time_scale; } @@ -68,14 +68,14 @@ void system_time_scale_set(double scale) { time_scale = scale; } -double system_tick() { +double system_tick(void) { return tick_last; } -double system_time() { +double system_time(void) { return time_scaled; } -double system_cycle_time() { +double system_cycle_time(void) { return cycle_time; } diff --git a/src/system.h b/src/system.h index 873a54e..8925d28 100755 --- a/src/system.h +++ b/src/system.h @@ -7,17 +7,17 @@ #define SYSTEM_WINDOW_WIDTH 1280 #define SYSTEM_WINDOW_HEIGHT 720 -void system_init(); -void system_update(); -void system_cleanup(); -void system_exit(); +void system_init(void); +void system_update(void); +void system_cleanup(void); +void system_exit(void); void system_resize(vec2i_t size); -double system_time(); -double system_tick(); -double system_cycle_time(); -void system_reset_cycle_time(); -double system_time_scale_get(); +double system_time(void); +double system_tick(void); +double system_cycle_time(void); +void system_reset_cycle_time(void); +double system_time_scale_get(void); void system_time_scale_set(double ts); #endif diff --git a/src/utils.h b/src/utils.h index 861ef54..dcebb31 100644 --- a/src/utils.h +++ b/src/utils.h @@ -4,6 +4,12 @@ #include #include "types.h" +#ifdef WIN32 + #undef min + #undef max + #undef near + #undef far +#endif #if !defined(offsetof) #define offsetof(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) diff --git a/src/wipeout/droid.c b/src/wipeout/droid.c index c856361..694088e 100755 --- a/src/wipeout/droid.c +++ b/src/wipeout/droid.c @@ -17,7 +17,7 @@ static Object *droid_model; -void droid_load() { +void droid_load(void) { texture_list_t droid_textures = image_get_compressed_textures("wipeout/common/rescu.cmp"); droid_model = objects_load("wipeout/common/rescu.prm", droid_textures); } diff --git a/src/wipeout/droid.h b/src/wipeout/droid.h index 000a718..b5e21b9 100755 --- a/src/wipeout/droid.h +++ b/src/wipeout/droid.h @@ -29,7 +29,7 @@ typedef struct droid_t { void droid_draw(droid_t *droid); -void droid_load(); +void droid_load(void); void droid_init(droid_t *droid, ship_t *ship); void droid_update(droid_t *droid, ship_t *ship); void droid_update_intro(droid_t *droid, ship_t *ship); diff --git a/src/wipeout/game.c b/src/wipeout/game.c index 5fa8b72..6dc8bf6 100755 --- a/src/wipeout/game.c +++ b/src/wipeout/game.c @@ -487,8 +487,8 @@ game_t g = {0}; struct { - void (*init)(); - void (*update)(); + void (*init)(void); + void (*update)(void); } game_scenes[] = { [GAME_SCENE_INTRO] = {intro_init, intro_update}, [GAME_SCENE_TITLE] = {title_init, title_update}, @@ -501,8 +501,7 @@ static game_scene_t scene_next = GAME_SCENE_NONE; static int global_textures_len = 0; static void *global_mem_mark = 0; -void game_init() { - +void game_init(void) { uint32_t size; save_t *save_file = (save_t *)platform_load_userdata("save.dat", &size); if (save_file) { @@ -589,7 +588,7 @@ void game_set_scene(game_scene_t scene) { scene_next = scene; } -void game_reset_championship() { +void game_reset_championship(void) { for (int i = 0; i < len(g.championship_ranks); i++) { g.championship_ranks[i].points = 0; g.championship_ranks[i].pilot = i; @@ -597,7 +596,7 @@ void game_reset_championship() { g.lives = NUM_LIVES; } -void game_update() { +void game_update(void) { double frame_start_time = platform_now(); int sh = render_size().y; @@ -635,7 +634,7 @@ void game_update() { // FIXME: use a text based format? // FIXME: this should probably run async somewhere save.is_dirty = false; - platform_store_userdata("save.dat", &save, sizeof(save_t)); + platform_store_userdata("save.dat", &save, sizeof(save_t)); printf("wrote save.dat\n"); } diff --git a/src/wipeout/game.h b/src/wipeout/game.h index 2634e6c..4164385 100755 --- a/src/wipeout/game.h +++ b/src/wipeout/game.h @@ -262,9 +262,9 @@ extern const game_def_t def; extern game_t g; extern save_t save; -void game_init(); +void game_init(void); void game_set_scene(game_scene_t scene); -void game_reset_championship(); -void game_update(); +void game_reset_championship(void); +void game_update(void); #endif diff --git a/src/wipeout/hud.c b/src/wipeout/hud.c index c1315c9..e4480e1 100755 --- a/src/wipeout/hud.c +++ b/src/wipeout/hud.c @@ -50,7 +50,7 @@ const struct { static uint16_t speedo_facia_texture; -void hud_load() { +void hud_load(void) { speedo_facia_texture = image_get_texture("wipeout/textures/speedo.tim"); target_reticle = image_get_texture_semi_trans("wipeout/textures/target2.tim"); weapon_icon_textures = image_get_compressed_textures("wipeout/common/wicons.cmp"); diff --git a/src/wipeout/hud.h b/src/wipeout/hud.h index c166772..39cfa9f 100755 --- a/src/wipeout/hud.h +++ b/src/wipeout/hud.h @@ -3,7 +3,7 @@ #include "ship.h" -void hud_load(); +void hud_load(void); void hud_draw(ship_t *ship); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/image.c b/src/wipeout/image.c index dc141ac..9f5fc7f 100755 --- a/src/wipeout/image.c +++ b/src/wipeout/image.c @@ -17,7 +17,7 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION -#include "../libs/stb_image_write.h" +#include #define TIM_TYPE_PALETTED_4_BPP 0x08 diff --git a/src/wipeout/ingame_menus.c b/src/wipeout/ingame_menus.c index da820e1..1cb7f7a 100644 --- a/src/wipeout/ingame_menus.c +++ b/src/wipeout/ingame_menus.c @@ -19,7 +19,7 @@ static void page_hall_of_fame_init(menu_t * menu); static texture_list_t pilot_portraits; static menu_t *ingame_menu; -void ingame_menus_load() { +void ingame_menus_load(void) { pilot_portraits = image_get_compressed_textures(def.pilots[g.pilot].portrait); ingame_menu = mem_bump(sizeof(menu_t)); } @@ -86,7 +86,7 @@ static void button_music(menu_t *menu, int data) { menu_page_add_button(page, 0, "RANDOM", button_music_random); } -menu_t *pause_menu_init() { +menu_t *pause_menu_init(void) { sfx_play(SFX_MENU_SELECT); menu_reset(ingame_menu); @@ -103,7 +103,7 @@ menu_t *pause_menu_init() { // ----------------------------------------------------------------------------- // Game Over -menu_t *game_over_menu_init() { +menu_t *game_over_menu_init(void) { sfx_play(SFX_MENU_SELECT); menu_reset(ingame_menu); @@ -187,7 +187,7 @@ static void page_race_stats_draw(menu_t *menu, int data) { pos.y += 12; } -menu_t *race_stats_menu_init() { +menu_t *race_stats_menu_init(void) { sfx_play(SFX_MENU_SELECT); menu_reset(ingame_menu); diff --git a/src/wipeout/ingame_menus.h b/src/wipeout/ingame_menus.h index 2327527..2386b79 100644 --- a/src/wipeout/ingame_menus.h +++ b/src/wipeout/ingame_menus.h @@ -3,11 +3,11 @@ #include "menu.h" -void ingame_menus_load(); +void ingame_menus_load(void); -menu_t *pause_menu_init(); -menu_t *game_over_menu_init(); -menu_t *race_stats_menu_init(); +menu_t *pause_menu_init(void); +menu_t *game_over_menu_init(void); +menu_t *race_stats_menu_init(void); menu_t *text_scroll_menu_init(char * const *lines, int len); #endif diff --git a/src/wipeout/intro.c b/src/wipeout/intro.c index c6ed2db..c252b49 100644 --- a/src/wipeout/intro.c +++ b/src/wipeout/intro.c @@ -19,7 +19,7 @@ void *realloc_dummmy(void *p, size_t sz) { #define PLM_MALLOC mem_bump #define PLM_FREE free_dummmy #define PLM_REALLOC realloc_dummmy -#include "../libs/pl_mpeg.h" +#include #define INTRO_AUDIO_BUFFER_LEN (64 * 1024) @@ -33,9 +33,9 @@ static int audio_buffer_write_pos; static void video_cb(plm_t *plm, plm_frame_t *frame, void *user); static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user); static void audio_mix(float *samples, uint32_t len); -static void intro_end(); +static void intro_end(void); -void intro_init() { +void intro_init(void) { plm = plm_create_with_filename("wipeout/intro.mpeg"); if (!plm) { intro_end(); @@ -62,12 +62,12 @@ void intro_init() { audio_buffer_write_pos = 0; } -static void intro_end() { +static void intro_end(void) { sfx_set_external_mix_cb(NULL); game_set_scene(GAME_SCENE_TITLE); } -void intro_update() { +void intro_update(void) { if (!plm) { return; } diff --git a/src/wipeout/intro.h b/src/wipeout/intro.h index 21fea00..a2e00dc 100644 --- a/src/wipeout/intro.h +++ b/src/wipeout/intro.h @@ -1,8 +1,8 @@ #ifndef INTRO_H #define INTRO_H -void intro_init(); -void intro_update(); -void intro_cleanup(); +void intro_init(void); +void intro_update(void); +void intro_cleanup(void); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/main_menu.c b/src/wipeout/main_menu.c index f74c414..80d81dd 100755 --- a/src/wipeout/main_menu.c +++ b/src/wipeout/main_menu.c @@ -512,7 +512,7 @@ static void objects_unpack_imp(Object **dest_array, int len, Object *src) { } -void main_menu_init() { +void main_menu_init(void) { g.is_attract_mode = false; main_menu = mem_bump(sizeof(menu_t)); @@ -532,7 +532,7 @@ void main_menu_init() { page_main_init(main_menu); } -void main_menu_update() { +void main_menu_update(void) { render_set_view_2d(); render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), background); diff --git a/src/wipeout/main_menu.h b/src/wipeout/main_menu.h index 0e079f0..a939c32 100755 --- a/src/wipeout/main_menu.h +++ b/src/wipeout/main_menu.h @@ -1,7 +1,7 @@ #ifndef MAIN_MENU_H #define MAIN_MENU_H -void main_menu_init(); -void main_menu_update(); +void main_menu_init(void); +void main_menu_update(void); #endif diff --git a/src/wipeout/menu.c b/src/wipeout/menu.c index 5d3cfe5..4235fd6 100644 --- a/src/wipeout/menu.c +++ b/src/wipeout/menu.c @@ -7,7 +7,7 @@ #include "ui.h" #include "sfx.h" -bool blink() { +bool blink(void) { // blink 30 times per second return fmod(system_cycle_time(), 1.0/15.0) < 1.0/30.0; } diff --git a/src/wipeout/particle.c b/src/wipeout/particle.c index 0677f52..8c25cfc 100644 --- a/src/wipeout/particle.c +++ b/src/wipeout/particle.c @@ -10,17 +10,17 @@ static particle_t *particles; static int particles_active = 0; static texture_list_t particle_textures; -void particles_load() { +void particles_load(void) { particles = mem_bump(sizeof(particle_t) * PARTICLES_MAX); particle_textures = image_get_compressed_textures("wipeout/common/effects.cmp"); particles_init(); } -void particles_init() { +void particles_init(void) { particles_active = 0; } -void particles_update() { +void particles_update(void) { for (int i = 0; i < particles_active; i++) { particle_t *p = &particles[i]; @@ -33,7 +33,7 @@ void particles_update() { } } -void particles_draw() { +void particles_draw(void) { if (particles_active == 0) { return; } diff --git a/src/wipeout/particle.h b/src/wipeout/particle.h index 2851b80..1ae7367 100644 --- a/src/wipeout/particle.h +++ b/src/wipeout/particle.h @@ -23,10 +23,10 @@ typedef struct particle_t { uint16_t texture; } particle_t; -void particles_load(); -void particles_init(); +void particles_load(void); +void particles_init(void); void particles_spawn(vec3_t position, uint16_t type, vec3_t velocity, int size); -void particles_draw(); -void particles_update(); +void particles_draw(void); +void particles_update(void); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/race.c b/src/wipeout/race.c index 6d85f1b..187c2ec 100755 --- a/src/wipeout/race.c +++ b/src/wipeout/race.c @@ -29,7 +29,7 @@ static bool has_show_credits = false; static float attract_start_time; static menu_t *active_menu = NULL; -void race_init() { +void race_init(void) { ingame_menus_load(); menu_is_scroll_text = false; @@ -67,7 +67,7 @@ void race_init() { is_paused = false; } -void race_update() { +void race_update(void) { if (is_paused) { if (!active_menu) { active_menu = pause_menu_init(); @@ -131,7 +131,7 @@ void race_update() { } } -void race_start() { +void race_start(void) { active_menu = NULL; sfx_reset(); scene_init(); @@ -157,7 +157,7 @@ void race_start() { g.race_time = 0; } -void race_restart() { +void race_restart(void) { race_unpause(); if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { @@ -176,7 +176,7 @@ static bool sort_points_compare(pilot_points_t *pa, pilot_points_t *pb) { return (pa->points < pb->points); } -void race_end() { +void race_end(void) { race_release_control(); g.race_position = g.ships[g.pilot].position_rank; @@ -222,7 +222,7 @@ void race_end() { active_menu = race_stats_menu_init(); } -void race_next() { +void race_next(void) { int next_circut = g.circut + 1; // Championship complete @@ -259,7 +259,7 @@ void race_next() { } } -void race_release_control() { +void race_release_control(void) { flags_rm(g.ships[g.pilot].flags, SHIP_RACING); g.ships[g.pilot].remote_thrust_max = 3160; g.ships[g.pilot].remote_thrust_mag = 32; @@ -267,12 +267,12 @@ void race_release_control() { g.camera.update_func = camera_update_attract_random; } -void race_pause() { +void race_pause(void) { sfx_pause(); is_paused = true; } -void race_unpause() { +void race_unpause(void) { sfx_unpause(); is_paused = false; active_menu = NULL; diff --git a/src/wipeout/race.h b/src/wipeout/race.h index 86f0937..65192b8 100755 --- a/src/wipeout/race.h +++ b/src/wipeout/race.h @@ -1,14 +1,14 @@ #ifndef RACE_H #define RACE_H -void race_init(); -void race_update(); -void race_start(); -void race_restart(); -void race_pause(); -void race_unpause(); -void race_end(); -void race_next(); -void race_release_control(); +void race_init(void); +void race_update(void); +void race_start(void); +void race_restart(void); +void race_pause(void); +void race_unpause(void); +void race_end(void); +void race_next(void); +void race_release_control(void); #endif diff --git a/src/wipeout/scene.c b/src/wipeout/scene.c index 8ce5ced..3cce86c 100755 --- a/src/wipeout/scene.c +++ b/src/wipeout/scene.c @@ -47,7 +47,7 @@ static struct { void scene_pulsate_red_light(Object *obj); void scene_move_oil_pump(Object *obj); -void scene_update_aurora_borealis(); +void scene_update_aurora_borealis(void); void scene_load(const char *base_path, float sky_y_offset) { texture_list_t scene_textures = image_get_compressed_textures(get_path(base_path, "scene.cmp")); @@ -93,14 +93,14 @@ void scene_load(const char *base_path, float sky_y_offset) { aurora_borealis.enabled = false; } -void scene_init() { +void scene_init(void) { scene_set_start_booms(0); for (int i = 0; i < stands_len; i++) { stands[i].sfx = sfx_reserve_loop(SFX_CROWD); } } -void scene_update() { +void scene_update(void) { for (int i = 0; i < red_lights_len; i++) { scene_pulsate_red_light(red_lights[i]); } @@ -198,7 +198,7 @@ void scene_move_oil_pump(Object *pump) { mat4_set_yaw_pitch_roll(&pump->mat, vec3(sin(system_cycle_time() * 0.125 * M_PI * 2), 0, 0)); } -void scene_init_aurora_borealis() { +void scene_init_aurora_borealis(void) { aurora_borealis.enabled = true; clear(aurora_borealis.grey_coords); @@ -236,7 +236,7 @@ void scene_init_aurora_borealis() { } } -void scene_update_aurora_borealis() { +void scene_update_aurora_borealis(void) { float phase = system_time() / 30.0; for (int i = 0; i < 80; i++) { int16_t *coords = aurora_borealis.coords[i]; diff --git a/src/wipeout/scene.h b/src/wipeout/scene.h index 537fa28..a0c204a 100755 --- a/src/wipeout/scene.h +++ b/src/wipeout/scene.h @@ -6,10 +6,9 @@ void scene_load(const char *path, float sky_y_offset); void scene_draw(camera_t *camera); -void scene_init(); +void scene_init(void); void scene_set_start_booms(int num_lights); -void scene_init_aurora_borealis(); -void scene_update(); -void scene_draw(); +void scene_init_aurora_borealis(void); +void scene_update(void); #endif diff --git a/src/wipeout/sfx.c b/src/wipeout/sfx.c index 8c3deea..a7a33a1 100644 --- a/src/wipeout/sfx.c +++ b/src/wipeout/sfx.c @@ -7,7 +7,7 @@ #define QOA_IMPLEMENTATION #define QOA_NO_STDIO -#include "../libs/qoa.h" +#include typedef struct { int16_t *samples; @@ -50,7 +50,7 @@ static sfx_t *nodes; static music_decoder_t *music; static void (*external_mix_cb)(float *, uint32_t len) = NULL; -void sfx_load() { +void sfx_load(void) { // Init decode buffer for music uint32_t channels = 2; music = mem_bump(sizeof(music_decoder_t)); @@ -118,7 +118,7 @@ void sfx_load() { platform_set_audio_mix_cb(sfx_stero_mix); } -void sfx_reset() { +void sfx_reset(void) { for (int i = 0; i < SFX_MAX; i++) { if (flags_is(nodes[i].flags, SFX_LOOP)) { flags_set(nodes[i].flags, SFX_NONE); @@ -126,7 +126,7 @@ void sfx_reset() { } } -void sfx_unpause() { +void sfx_unpause(void) { for (int i = 0; i < SFX_MAX; i++) { if (flags_is(nodes[i].flags, SFX_LOOP_PAUSE)) { flags_rm(nodes[i].flags, SFX_LOOP_PAUSE); @@ -135,7 +135,7 @@ void sfx_unpause() { } } -void sfx_pause() { +void sfx_pause(void) { for (int i = 0; i < SFX_MAX; i++) { if (flags_is(nodes[i].flags, SFX_PLAY | SFX_LOOP)) { flags_rm(nodes[i].flags, SFX_PLAY); @@ -227,7 +227,7 @@ void sfx_set_position(sfx_t *sfx, vec3_t pos, vec3_t vel, float volume) { // Music -uint32_t sfx_music_decode_frame() { +uint32_t sfx_music_decode_frame(void) { if (!music->file) { return 0; } @@ -240,7 +240,7 @@ uint32_t sfx_music_decode_frame() { return frame_len; } -void sfx_music_rewind() { +void sfx_music_rewind(void) { fseek(music->file, music->first_frame_pos, SEEK_SET); music->sample_data_len = 0; music->sample_data_pos = 0; diff --git a/src/wipeout/sfx.h b/src/wipeout/sfx.h index e07f390..0ae09e2 100644 --- a/src/wipeout/sfx.h +++ b/src/wipeout/sfx.h @@ -58,12 +58,12 @@ typedef struct { #define SFX_MAX 64 #define SFX_MAX_ACTIVE 16 -void sfx_load(); +void sfx_load(void); void sfx_stero_mix(float *buffer, uint32_t len); void sfx_set_external_mix_cb(void (*cb)(float *, uint32_t len)); -void sfx_reset(); -void sfx_pause(); -void sfx_unpause(); +void sfx_reset(void); +void sfx_pause(void); +void sfx_unpause(void); sfx_t *sfx_play(sfx_source_t source_index); sfx_t *sfx_play_at(sfx_source_t source_index, vec3_t pos, vec3_t vel, float volume); @@ -77,9 +77,9 @@ typedef enum { SFX_MUSIC_LOOP } sfx_music_mode_t; -void sfx_music_next(); +void sfx_music_next(void); void sfx_music_play(uint32_t index); void sfx_music_mode(sfx_music_mode_t); -void sfx_music_pause(); +void sfx_music_pause(void); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/ship.c b/src/wipeout/ship.c index b06149c..ff807bf 100644 --- a/src/wipeout/ship.c +++ b/src/wipeout/ship.c @@ -16,7 +16,7 @@ #include "race.h" #include "sfx.h" -void ships_load() { +void ships_load(void) { texture_list_t ship_textures = image_get_compressed_textures("wipeout/common/allsh.cmp"); Object *ship_models = objects_load("wipeout/common/allsh.prm", ship_textures); @@ -120,7 +120,7 @@ static inline bool sort_rank_compare(pilot_points_t *pa, pilot_points_t *pb) { } } -void ships_update() { +void ships_update(void) { if (g.race_type == RACE_TYPE_TIME_TRIAL) { ship_update(&g.ships[g.pilot]); } @@ -145,7 +145,7 @@ void ships_update() { -void ships_draw() { +void ships_draw(void) { // Ship models for (int i = 0; i < len(g.ships); i++) { if ( diff --git a/src/wipeout/ship.h b/src/wipeout/ship.h index 4888ac8..47e4c91 100644 --- a/src/wipeout/ship.h +++ b/src/wipeout/ship.h @@ -146,10 +146,10 @@ typedef struct ship_t { sfx_t *sfx_shield; } ship_t; -void ships_load(); +void ships_load(void); void ships_init(section_t *section); -void ships_draw(); -void ships_update(); +void ships_draw(void); +void ships_update(void); void ship_init(ship_t *self, section_t *section, int pilot, int position); void ship_init_exhaust_plume(ship_t *self); @@ -165,4 +165,4 @@ vec3_t ship_wing_left(ship_t *self); vec3_t ship_wing_right(ship_t *self); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/title.c b/src/wipeout/title.c index 6098b4d..1ea0638 100644 --- a/src/wipeout/title.c +++ b/src/wipeout/title.c @@ -11,13 +11,13 @@ static uint16_t title_image; static float start_time; static bool has_shown_attract = false; -void title_init() { +void title_init(void) { title_image = image_get_texture("wipeout/textures/wiptitle.tim"); start_time = system_time(); sfx_music_mode(SFX_MUSIC_RANDOM); } -void title_update() { +void title_update(void) { render_set_view_2d(); render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), title_image); ui_draw_text_centered("PRESS ENTER", ui_scaled_pos(UI_POS_BOTTOM | UI_POS_CENTER, vec2i(0, -40)), UI_SIZE_8, UI_COLOR_DEFAULT); diff --git a/src/wipeout/title.h b/src/wipeout/title.h index 939d7c3..892ec8a 100644 --- a/src/wipeout/title.h +++ b/src/wipeout/title.h @@ -1,8 +1,8 @@ #ifndef TITLE_H #define TITLE_H -void title_init(); -void title_update(); -void title_cleanup(); +void title_init(void); +void title_update(void); +void title_cleanup(void); #endif diff --git a/src/wipeout/track.c b/src/wipeout/track.c index 21a235e..41cb6b4 100755 --- a/src/wipeout/track.c +++ b/src/wipeout/track.c @@ -282,7 +282,7 @@ void track_draw(camera_t *camera) { } } -void track_cycle_pickups() { +void track_cycle_pickups(void) { float pickup_cycle_time = 1.5 * system_cycle_time(); for (int i = 0; i < g.track.pickups_len; i++) { diff --git a/src/wipeout/track.h b/src/wipeout/track.h index 9389034..7b7e8a1 100755 --- a/src/wipeout/track.h +++ b/src/wipeout/track.h @@ -91,6 +91,6 @@ section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance struct camera_t; void track_draw(struct camera_t *camera); -void track_cycle_pickups(); +void track_cycle_pickups(void); -#endif \ No newline at end of file +#endif diff --git a/src/wipeout/ui.c b/src/wipeout/ui.c index 6a4a867..22ecadd 100644 --- a/src/wipeout/ui.c +++ b/src/wipeout/ui.c @@ -55,7 +55,7 @@ char_set_t char_set[UI_SIZE_MAX] = { uint16_t icon_textures[UI_ICON_MAX]; -void ui_load() { +void ui_load(void) { texture_list_t tl = image_get_compressed_textures("wipeout/textures/drfonts.cmp"); char_set[UI_SIZE_16].texture = texture_from_list(tl, 0); char_set[UI_SIZE_12].texture = texture_from_list(tl, 1); @@ -68,7 +68,7 @@ void ui_load() { icon_textures[UI_ICON_STAR] = texture_from_list(tl, 9); } -int ui_get_scale() { +int ui_get_scale(void) { return ui_scale; } @@ -81,7 +81,7 @@ vec2i_t ui_scaled(vec2i_t v) { return vec2i(v.x * ui_scale, v.y * ui_scale); } -vec2i_t ui_scaled_screen() { +vec2i_t ui_scaled_screen(void) { return vec2i_mulf(render_size(), ui_scale); } diff --git a/src/wipeout/ui.h b/src/wipeout/ui.h index 8d577ab..9774f85 100644 --- a/src/wipeout/ui.h +++ b/src/wipeout/ui.h @@ -32,13 +32,13 @@ typedef enum { UI_POS_BOTTOM = 1 << 5, } ui_pos_t; -void ui_load(); -void ui_cleanup(); +void ui_load(void); +void ui_cleanup(void); -int ui_get_scale(); +int ui_get_scale(void); void ui_set_scale(int scale); vec2i_t ui_scaled(vec2i_t v); -vec2i_t ui_scaled_screen(); +vec2i_t ui_scaled_screen(void); vec2i_t ui_scaled_pos(ui_pos_t anchor, vec2i_t offset); int ui_char_width(char c, ui_text_size_t size); diff --git a/src/wipeout/weapon.c b/src/wipeout/weapon.c index daf1546..5ec8779 100755 --- a/src/wipeout/weapon.c +++ b/src/wipeout/weapon.c @@ -74,7 +74,7 @@ void weapon_fire_turbo(ship_t *ship); void invert_shield_polys(Object *shield); -void weapons_load() { +void weapons_load(void) { weapons = mem_bump(sizeof(weapon_t) * WEAPONS_MAX); weapon_assets.reticle = image_get_texture("wipeout/textures/target2.tim"); @@ -106,7 +106,7 @@ void weapons_load() { weapons_init(); } -void weapons_init() { +void weapons_init(void) { weapons_active = 0; } @@ -160,7 +160,7 @@ void weapons_fire_delayed(ship_t *ship, int weapon_type) { bool weapon_collides_with_track(weapon_t *self); -void weapons_update() { +void weapons_update(void) { for (int i = 0; i < weapons_active; i++) { weapon_t *weapon = &weapons[i]; @@ -214,7 +214,7 @@ void weapons_update() { } } -void weapons_draw() { +void weapons_draw(void) { mat4_t mat = mat4_identity(); for (int i = 0; i < weapons_active; i++) { weapon_t *weapon = &weapons[i]; diff --git a/src/wipeout/weapon.h b/src/wipeout/weapon.h index e8a220a..9cfccdb 100755 --- a/src/wipeout/weapon.h +++ b/src/wipeout/weapon.h @@ -40,12 +40,12 @@ #define WEAPON_CLASS_PROJECTILE 2 -void weapons_load(); -void weapons_init(); +void weapons_load(void); +void weapons_init(void); void weapons_fire(ship_t *ship, int weapon_type); void weapons_fire_delayed(ship_t *ship, int weapon_type); -void weapons_update(); -void weapons_draw(); +void weapons_update(void); +void weapons_draw(void); int weapon_get_random_type(int type_class); #endif diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..d3e7408 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,15 @@ +{ + "name": "wipeout-rewrite", + "version-string": "1.0.0", + "builtin-baseline": "0fa8459cf3a7caca7adc58f992bc32ff13630684", + "dependencies": [ + { + "name": "sdl2", + "version>=": "2.26.5" + }, + { + "name": "glew", + "version>=": "2.2.0#3" + } + ] +}