diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..48ba6cc --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ + +IndentWidth: 4 +UseTab: Never + +BraceWrapping: + AfterFunction: false + AfterControlStatement: false + AfterEnum: true + AfterStruct: true + BeforeElse: true + IndentBraces: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3d96725..ee131c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,16 @@ -/wipeout.sublime-project -/wipeout.sublime-workspace +# ide stuff +.vscode + +# game data /wipeout/ +/save.dat +gamecontrollerdb.txt + +# build artifacts /build/ +coverage-report/ +*.info /wipegame /wipegame.exe diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fe07c99 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a8f047..f250f67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_minimum_required(VERSION 3.28 FATAL_ERROR) project(wipeout-rewrite) if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") @@ -7,7 +7,6 @@ endif() include(GNUInstallDirs) include(CMakeDependentOption) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") set(EMSCRIPTEN true) @@ -38,88 +37,100 @@ cmake_dependent_option(MINIMAL_BUNDLE "Do not include music/movies for web build option(PATH_ASSETS "Path to where the game assets should be located.") option(PATH_USERDATA "Path to where user data (e.g. game saves) should be located.") option(DEV_BUILD "Set asset/userdata paths to the source directory for testing" OFF) +option(USE_ASAN "Enable AddressSanitizer" OFF) + if (DEV_BUILD) set(PATH_ASSETS "${CMAKE_SOURCE_DIR}/") set(PATH_USERDATA "${CMAKE_SOURCE_DIR}/") endif() +# 3RD PARTY LIBRARIES +find_package(PkgConfig REQUIRED) find_package(OpenGL) -find_package(GLEW) -find_package(SDL2) +find_package(GLEW REQUIRED) +find_package(SDL2 CONFIG REQUIRED) +find_package(cmocka REQUIRED) +pkg_check_modules(libprotobuf-c REQUIRED IMPORTED_TARGET libprotobuf-c) + +include_directories(${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include) + +# COMMON COMPLIER FLAGS, DEFINES +set(CMAKE_C_STANDARD 11) +add_compile_options( + $<$:/W4> + $<$>:-Wall> + $<$>:-Wextra> +) + +if(DEV_BUILD AND NOT WIN32) + add_compile_options(-g -Og) + if(NOT WIN32) + add_compile_options(--coverage) + add_link_options(--coverage) + endif() +endif() + +if(USE_ASAN) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# INTERNAL LIBRARIES +# PROTOBUF MSGS +add_subdirectory(gen) +# NETWORK LIBRARY TARGET +add_subdirectory(network) + +# SERVER TARGET +add_subdirectory(server) + +# MAIN GAME TARGET set(common_src src/wipeout/camera.c - src/wipeout/camera.h + src/wipeout/server_com.c 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> -) + target_compile_definitions(wipeout PRIVATE $<$:-DPATH_ASSETS=${PATH_ASSETS}> $<$:-DPATH_USERDATA=${PATH_USERDATA}> ) +# COMMON LIBRARIES +target_link_libraries(wipeout PRIVATE network) + if(WIN32) target_compile_definitions(wipeout PRIVATE "NOMINMAX" @@ -220,9 +231,16 @@ endif() if("${PLATFORM}" STREQUAL SDL2) target_sources(wipeout PRIVATE src/platform_sdl.c) - target_link_libraries(wipeout PUBLIC SDL2::Main) + target_link_libraries(wipeout + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) elseif("${PLATFORM}" STREQUAL SOKOL) target_sources(wipeout PRIVATE src/platform_sokol.c) endif() +# UNIT TESTS TARGET +add_subdirectory(tests) + install(TARGETS wipeout) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..68415a4 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 19, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "vcpkg/scripts/buildsystems/vcpkg.cmake", + "DEV_BUILD": "ON" + } + }, + { + "name": "windows-clang-cl", + "inherits": "default", + "description": "Configuration for clang-cl on Windows using vcpkg", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "VCPKG_TARGET_TRIPLET": "x64-windows-static-md" + } + } + ], + "buildPresets": [ + { + "name": "build", + "configurePreset": "windows-clang-cl", + "configuration": "Debug" + } + ] +} \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index 4ec311f..0000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "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/Makefile b/Makefile deleted file mode 100644 index 221f4be..0000000 --- a/Makefile +++ /dev/null @@ -1,196 +0,0 @@ -CC ?= gcc -EMCC ?= emcc -UNAME_S := $(shell uname -s) -RENDERER ?= GL -USE_GLX ?= false -DEBUG ?= false -USER_CFLAGS ?= - -L_FLAGS ?= -lm -C_FLAGS ?= -Isrc/libs/ -std=gnu99 -Wall -Wno-unused-variable $(USER_CFLAGS) - -ifeq ($(DEBUG), true) - C_FLAGS := $(C_FLAGS) -g -else - C_FLAGS := $(C_FLAGS) -O3 -endif - - -# Rendeder --------------------------------------------------------------------- - -ifeq ($(RENDERER), GL) - RENDERER_SRC = src/render_gl.c - C_FLAGS := $(C_FLAGS) -DRENDERER_GL -else ifeq ($(RENDERER), SOFTWARE) - RENDERER_SRC = src/render_software.c - C_FLAGS := $(C_FLAGS) -DRENDERER_SOFTWARE -else -$(error Unknown RENDERER) -endif - -ifeq ($(GL_VERSION), GLES2) - C_FLAGS := $(C_FLAGS) -DUSE_GLES2 -endif - - - -# macOS ------------------------------------------------------------------------ - -ifeq ($(UNAME_S), Darwin) - BREW_HOME := $(shell brew --prefix) - C_FLAGS := $(C_FLAGS) -x objective-c -I/opt/homebrew/include -D_THREAD_SAFE -w - L_FLAGS := $(L_FLAGS) -L$(BREW_HOME)/lib -framework Foundation - - ifeq ($(RENDERER), GL) - L_FLAGS := $(L_FLAGS) -lGLEW -GLU -framework OpenGL - endif - - L_FLAGS_SDL = -lSDL2 - L_FLAGS_SOKOL = -framework Cocoa -framework QuartzCore -framework AudioToolbox - - -# Linux ------------------------------------------------------------------------ - -else ifeq ($(UNAME_S), Linux) - ifeq ($(RENDERER), GL) - L_FLAGS := $(L_FLAGS) -lGLEW - - # Prefer modern GLVND instead of legacy X11-only GLX - ifeq ($(USE_GLX), true) - L_FLAGS := $(L_FLAGS) -lGL - else - L_FLAGS := $(L_FLAGS) -lOpenGL - endif - endif - - L_FLAGS_SDL = -lSDL2 - L_FLAGS_SOKOL = -lX11 -lXcursor -pthread -lXi -ldl -lasound - - -# Windows MSYS ------------------------------------------------------------------ -else ifeq ($(shell uname -o), Msys) - ifeq ($(RENDERER), GL) - L_FLAGS := $(L_FLAGS) -lglew32 -lopengl32 - endif - - C_FLAGS := $(C_FLAGS) -DSDL_MAIN_HANDLED - L_FLAGS_SDL := $(shell sdl2-config --libs) - L_FLAGS_SOKOL = --pthread -lgdi32 -lole32 - - -# Windows NON-MSYS --------------------------------------------------------------- -else ifeq ($(OS), Windows_NT) - $(error TODO: FLAGS for windows have not been set up. Please modify this makefile and send a PR!) - -else -$(error Unknown environment) -endif - - - -# Source files ----------------------------------------------------------------- - -TARGET_NATIVE ?= wipegame -BUILD_DIR = build/obj/native -BUILD_DIR_WASM = build/obj/wasm - -WASM_RELEASE_DIR ?= build/wasm -TARGET_WASM ?= $(WASM_RELEASE_DIR)/wipeout.js -TARGET_WASM_MINIMAL ?= $(WASM_RELEASE_DIR)/wipeout-minimal.js - -COMMON_SRC = \ - src/wipeout/race.c \ - src/wipeout/camera.c \ - src/wipeout/object.c \ - src/wipeout/droid.c \ - src/wipeout/ui.c \ - src/wipeout/hud.c \ - src/wipeout/image.c \ - src/wipeout/game.c \ - src/wipeout/menu.c \ - src/wipeout/main_menu.c \ - src/wipeout/ingame_menus.c \ - src/wipeout/title.c \ - src/wipeout/intro.c \ - src/wipeout/scene.c \ - src/wipeout/ship.c \ - src/wipeout/ship_ai.c \ - src/wipeout/ship_player.c \ - src/wipeout/track.c \ - src/wipeout/weapon.c \ - src/wipeout/particle.c \ - src/wipeout/sfx.c \ - src/utils.c \ - src/types.c \ - src/system.c \ - src/mem.c \ - src/input.c \ - $(RENDERER_SRC) - - -# Targets native --------------------------------------------------------------- - -COMMON_OBJ = $(patsubst %.c, $(BUILD_DIR)/%.o, $(COMMON_SRC)) -COMMON_DEPS = $(patsubst %.c, $(BUILD_DIR)/%.d, $(COMMON_SRC)) - -sdl: C_FLAGS += $(shell sdl2-config --cflags) -sdl: $(BUILD_DIR)/src/platform_sdl.o -sdl: $(COMMON_OBJ) - $(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SDL) - -sokol: $(BUILD_DIR)/src/platform_sokol.o -sokol: $(COMMON_OBJ) - $(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SOKOL) - -$(BUILD_DIR): - mkdir -p $(BUILD_DIR) - -$(BUILD_DIR)/%.o: %.c - mkdir -p $(dir $@) - $(CC) $(C_FLAGS) -MMD -MP -c $< -o $@ - --include $(COMMON_DEPS) - - -# Targets wasm ----------------------------------------------------------------- - -COMMON_OBJ_WASM = $(patsubst %.c, $(BUILD_DIR_WASM)/%.o, $(COMMON_SRC)) -COMMON_DEPS_WASM = $(patsubst %.c, $(BUILD_DIR_WASM)/%.d, $(COMMON_SRC)) - -wasm: wasm_full wasm_minimal - cp src/wasm-index.html $(WASM_RELEASE_DIR)/game.html - - -wasm_full: $(BUILD_DIR_WASM)/src/platform_sokol.o -wasm_full: $(COMMON_OBJ_WASM) - mkdir -p $(WASM_RELEASE_DIR) - $(EMCC) $^ -o $(TARGET_WASM) -lGLEW -lGL \ - -s ALLOW_MEMORY_GROWTH=1 \ - -s ENVIRONMENT=web \ - --preload-file wipeout - -wasm_minimal: $(BUILD_DIR_WASM)/src/platform_sokol.o -wasm_minimal: $(COMMON_OBJ_WASM) - mkdir -p $(WASM_RELEASE_DIR) - $(EMCC) $^ -o $(TARGET_WASM_MINIMAL) -lGLEW -lGL \ - -s ALLOW_MEMORY_GROWTH=1 \ - -s ENVIRONMENT=web \ - --preload-file wipeout \ - --exclude-file wipeout/music \ - --exclude-file wipeout/intro.mpeg - -$(BUILD_DIR_WASM): - mkdir -p $(BUILD_DIR_WASM) - -$(BUILD_DIR_WASM)/%.o: %.c - mkdir -p $(dir $@) - $(EMCC) $(C_FLAGS) -MMD -MP -c $< -o $@ - --include $(COMMON_DEPS_WASM) - - - - -.PHONY: clean -clean: - $(RM) -rf $(BUILD_DIR) $(BUILD_DIR_WASM) $(WASM_RELEASE_DIR) diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake deleted file mode 100644 index 8649c92..0000000 --- a/cmake/FindSDL2.cmake +++ /dev/null @@ -1,388 +0,0 @@ -# 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/gen/CMakeLists.txt b/gen/CMakeLists.txt new file mode 100644 index 0000000..e41c6ca --- /dev/null +++ b/gen/CMakeLists.txt @@ -0,0 +1,48 @@ + +file(GLOB PROTO_PROTOBUFS ${CMAKE_SOURCE_DIR}/protos/*.proto) + +set(GENERATED_SRCS "") +set(GENERATED_HDRS "") + +if(WIN32) + set(PATHENV "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/protobuf-c;$ENV{PATH}") +else() + set(PATHENV "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/protobuf-c:$ENV{PATH}") +endif() + +foreach(PROTO_FILE ${PROTO_PROTOBUFS}) + + get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE) + set(GENERATED_SRC ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb-c.c) + set(GENERATED_HDR ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb-c.h) + + list(APPEND GENERATED_SRCS ${GENERATED_SRC}) + list(APPEND GENERATED_HDRS ${GENERATED_HDR}) + + add_custom_command( + OUTPUT ${GENERATED_SRC} ${GENERATED_HDR} + COMMAND ${CMAKE_COMMAND} -E env + "PATH=${PATHENV}" + ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/protobuf/protoc + ARGS --c_out=${CMAKE_CURRENT_BINARY_DIR} -I ${CMAKE_SOURCE_DIR}/protos ${PROTO_FILE} + DEPENDS ${PROTO_FILE} + COMMENT "Running protoc to generate ${GENERATED_SRC} and ${GENERATED_HDR}" + VERBATIM + ) +endforeach(PROTO_FILE ${PROTO_PROTOBUFS}) + + +# Custom target (optional: for IDEs or manual builds) +add_custom_target(generate_proto_files + DEPENDS ${GENERATED_SRCS} ${GENERATED_HDRS} +) + +add_library(protos STATIC + ${GENERATED_SRCS} +) + +add_dependencies(protos generate_proto_files) + +target_link_libraries(protos PUBLIC PkgConfig::libprotobuf-c) + +target_include_directories(protos PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/network/CMakeLists.txt b/network/CMakeLists.txt new file mode 100644 index 0000000..5b59fdb --- /dev/null +++ b/network/CMakeLists.txt @@ -0,0 +1,16 @@ + +add_library(network + addr_conversions.c + msg.c + network.c + network_wrapper.c +) + +set(OS_LIBS "") +if(WIN32) + set(OS_LIBS "Ws2_32.lib;Iphlpapi.lib;${OS_LIBS}") +endif() + +target_link_libraries(network PUBLIC ${OS_LIBS} protos) + +target_include_directories(network PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/network/addr_conversions.c b/network/addr_conversions.c new file mode 100644 index 0000000..52f536a --- /dev/null +++ b/network/addr_conversions.c @@ -0,0 +1,72 @@ + +#include "addr_conversions.h" + +#if defined(WIN32) +#include +#include +#else +#include +#include +#include +#include +#endif +#include +#include + +void string_to_socket_addr(const char *str_addr, struct sockaddr *sadr) +{ + struct hostent *h; + + memset(sadr, 0, sizeof(*sadr)); + ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + + ((struct sockaddr_in *)sadr)->sin_port = 0; + + if (str_addr[0] >= '0' && str_addr[0] <= '9') + { + // it's an ip4 address + inet_pton(AF_INET, str_addr, &((struct sockaddr_in *)sadr)->sin_addr); + } + else + { + // it's a string address + if (!(h = gethostbyname(str_addr))) + return; + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; + } + + return; +} + +void string_to_addr(const char* str, netadr_t* addr) { + struct sockaddr_in sadr; + + string_to_socket_addr(str, (struct sockaddr*)&sadr); + + sockadr_to_netadr(&sadr, addr); +} + +const char *addr_to_string(netadr_t addr) +{ + static char str[64]; + + snprintf(str, sizeof(str), "%i.%i.%i.%i:%hu", addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port); + + return str; +} + +void netadr_to_sockadr(netadr_t *addr, struct sockaddr_in *s) +{ + + memset(s, 0, sizeof(*s)); + + s->sin_family = AF_INET; + *(int *)&s->sin_addr = *(int *)&addr->ip; + s->sin_port = addr->port; +} + +void sockadr_to_netadr(struct sockaddr_in *s, netadr_t *addr) +{ + *(int *)&addr->ip = *(int *)&s->sin_addr; + addr->port = s->sin_port; +} \ No newline at end of file diff --git a/network/addr_conversions.h b/network/addr_conversions.h new file mode 100644 index 0000000..5f5f6cf --- /dev/null +++ b/network/addr_conversions.h @@ -0,0 +1,18 @@ + +#pragma once + +#include "network_types.h" + +struct sockaddr; +struct sockaddr_in; + +// translate a network address (like localhost) or ip to a soecket address +void string_to_socket_addr(const char *str_addr, struct sockaddr *sadr); + +void string_to_addr(const char* str, netadr_t* addr); + +const char *addr_to_string(netadr_t addr); + +void netadr_to_sockadr(netadr_t *addr, struct sockaddr_in *s); + +void sockadr_to_netadr(struct sockaddr_in *s, netadr_t *addr); diff --git a/network/msg.c b/network/msg.c new file mode 100644 index 0000000..6a846de --- /dev/null +++ b/network/msg.c @@ -0,0 +1,145 @@ + +#include "network_types.h" + +#include "msg.h" + +#include + +unsigned short msg_short_flip_endian(unsigned short value) +{ + return (value >> 8) | (value << 8); +} + +unsigned int msg_int_flip_endian(unsigned int value) +{ + return ((value >> 24) & 0xFF) | + ((value >> 8) & 0xFF00) | + ((value << 8) & 0xFF0000) | + ((value << 24) & 0xFF000000); +} + +int msg_read_bits(msg_t *msg, int bits) +{ + int value = 0; + int get = 0; + bool sgn = false; + int i; + int nbits = 0; + + if (bits < 0) + { + bits = -bits; + sgn = true; + } + else + { + sgn = false; + } + + if (msg->oob) + { + if (bits == 8) + { + value = msg->data[msg->readcount]; + msg->readcount += 1; + msg->bit += 8; + } + else if (bits == 16) + { + unsigned short *sp = (unsigned short *)&msg->data[msg->readcount]; + value = msg_short_flip_endian(*sp); + msg->readcount += 2; + msg->bit += 16; + } + else if (bits == 32) + { + unsigned int *ip = (unsigned int *)&msg->data[msg->readcount]; + value = msg_int_flip_endian(*ip); + msg->readcount += 4; + msg->bit += 32; + } + else + { + //Com_Error(ERR_DROP, "can't read %d bits\n", bits); + } + } + else + { + nbits = 0; + if (bits & 7) + { + nbits = bits & 7; + for (i = 0; i < nbits; i++) + { + //value |= (Huff_getBit(msg->data, &msg->bit) << i); + } + bits = bits - nbits; + } + if (bits) + { + for (i = 0; i < bits; i += 8) + { + //Huff_offsetReceive(msgHuff.decompressor.tree, &get, msg->data, &msg->bit); + value |= (get << (i + nbits)); + } + } + msg->readcount = (msg->bit >> 3) + 1; + } + if (sgn) + { + if (value & (1 << (bits - 1))) + { + value |= -1 ^ ((1 << bits) - 1); + } + } + + return value; +} + + +int msg_read_byte(msg_t *msg) +{ + int c = (unsigned char)msg_read_bits(msg, 8); + if (msg->readcount > msg->cursize) + { + c = -1; + } + return c; +} + +char *msg_read_string_line(msg_t *msg) +{ + static char string[MAX_STRING_CHARS]; + unsigned long l = 0; + int c = 0; + + do + { + c = msg_read_byte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0) + { + break; + } + // translate all fmt spec to avoid crash bugs + if (c == '%') + { + c = '.'; + } + // don't allow higher ascii values + if (c > 127) + { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +void msg_bitstream(msg_t* buf) { + buf->oob = true; +} diff --git a/network/msg.h b/network/msg.h new file mode 100644 index 0000000..20a703e --- /dev/null +++ b/network/msg.h @@ -0,0 +1,8 @@ + +#pragma once + +#include "network_types.h" + +int msg_read_byte(msg_t *msg); + +char *msg_read_string_line(msg_t *msg); diff --git a/network/network.c b/network/network.c new file mode 100644 index 0000000..c43423c --- /dev/null +++ b/network/network.c @@ -0,0 +1,431 @@ + +#include "network.h" +#include "network_wrapper.h" + +#include "addr_conversions.h" +#include "msg.h" + +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + +#include + +#if defined(WIN32) +static WSADATA winsockdata; +static bool winsock_initialized = false; +#endif + +static msg_queue_item_t msg_queue[10]; +static int msg_queue_size = 0; + +static int client_sockfd = INVALID_SOCKET; + +static const char *network_get_last_error(void) +{ +#if defined(WIN32) + int code = WSAGetLastError(); + + char *errorMsg = NULL; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&errorMsg, + 0, + NULL); + + return errorMsg; + +#else + return strerror(errno); +#endif +} + +static void system_send_packet(int sockfd, int length, const void *data, struct sockaddr_in dest_net) +{ + + if(sockfd == INVALID_SOCKET) + { + printf("unable to get socket for sending packet: %s\n", network_get_last_error()); + return; + } + + + int ret = wrap_sendto(sockfd, data, length, 0, (struct sockaddr *)&dest_net, sizeof(dest_net)); + + if (ret == -1) + { + // printf("unable to send packet to %s: %s\n", addr_to_string(dest_net), network_get_last_error()); + } +} + + +#if defined(WIN32) +bool system_init_winsock(void) { + if (!winsock_initialized) { + if ((WSAStartup(MAKEWORD(2, 2), &winsockdata) != 0)) { + printf("unable to init windows socket: %s\n", + network_get_last_error()); + return false; + } + winsock_initialized = true; + } + + return true; +} + +void system_cleanup_winsock(void) { + if (winsock_initialized) { + WSACleanup(); + winsock_initialized = false; + } +} +#endif + +int network_get_client_socket(void) { + +#if defined(WIN32) + if(!system_init_winsock()) { + return INVALID_SOCKET; + } +#endif + + +#if defined(WIN32) + SOCKET new_socket = INVALID_SOCKET; +#else + int new_socket = INVALID_SOCKET; +#endif + + struct addrinfo hints, *res; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; // fill in my IP for me + + getaddrinfo(NULL, "8000", &hints, &res); + + if ((new_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == + INVALID_SOCKET) { + printf("unable to open socket: %s\n", network_get_last_error()); + return INVALID_SOCKET; + } + +#if defined(WIN32) + unsigned long _true = 1; + if (ioctlsocket(new_socket, FIONBIO, &_true) == -1) { +#else + bool _true = true; + if (ioctl(new_socket, FIONBIO, &_true) == -1) { +#endif + printf("can't make socket non-blocking: %s\n", + network_get_last_error()); + + network_close_socket(&new_socket); + return INVALID_SOCKET; + } + + int i = 1; + if (setsockopt(new_socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, + sizeof(i)) == -1) { + printf("can't make socket broadcastable: %s\n", + network_get_last_error()); + network_close_socket(&new_socket); + return INVALID_SOCKET; + } + + // Set receive timeout + // TODO: can this fail? + struct timeval timeout; + timeout.tv_sec = CLIENT_SOCKET_TIMEOUT / 1000; // convert milliseconds to seconds + timeout.tv_usec = 0; + setsockopt(new_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + return new_socket; +} + +bool network_bind_socket(int sockfd, char* port) +{ + if(client_sockfd != INVALID_SOCKET) + { + printf("socket already bound, cannot bind again\n"); + return true; + } + + if(sockfd == INVALID_SOCKET) + { + printf("could not create socket: %s\n", network_get_last_error()); + return false; + } + + struct addrinfo hints; + struct addrinfo* res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + getaddrinfo(NULL, port, &hints, &res); + + if (bind(sockfd, res->ai_addr, res->ai_addrlen) == -1) + { + printf("couldn't bind address and port: %s\n", network_get_last_error()); + return false; + } + + char ipstr[INET_ADDRSTRLEN]; + struct sockaddr_in *addr = (struct sockaddr_in *)res->ai_addr; + inet_ntop(AF_INET, &(addr->sin_addr), ipstr, sizeof(ipstr)); + + printf("established connection at %s:%s\n", ipstr, port); + network_set_bound_ip_socket(sockfd); + + return true; +} + +bool network_has_bound_ip_socket(void) +{ + return client_sockfd != INVALID_SOCKET; +} + +void network_set_bound_ip_socket(int sockfd) +{ + client_sockfd = sockfd; +} + +int network_get_bound_ip_socket(void) { + return client_sockfd; +} + +void network_close_socket(int* sockfd) { + if(*sockfd == INVALID_SOCKET) { + return; + } +#if defined(WIN32) + closesocket(*sockfd); +#else + close(*sockfd); +#endif + + *sockfd = INVALID_SOCKET; +} + +static void network_add_msg_queue_item(const char *buf, const struct sockaddr_in* their_addr) { + msg_queue_item_t *item = (msg_queue_item_t *)malloc(sizeof(msg_queue_item_t)); + if (!item) { + perror("Failed to allocate memory for message queue item"); + return; + } + + item->command = strdup(buf); + if (!item->command) { + perror("Failed to allocate memory for command string"); + free(item); + return; + } + + memcpy(&item->dest_addr, their_addr, sizeof(struct sockaddr_in)); + msg_queue[msg_queue_size++] = *item; +} + +int network_get_msg_queue_size() { + return msg_queue_size; +} + +void network_clear_msg_queue() { + for (int i = 0; i < msg_queue_size; i++) { + free(msg_queue[i].command); + } + msg_queue_size = 0; +} + +void network_popleft_msg_queue() { + if (msg_queue_size > 0) { + free(msg_queue[0].command); + memmove(&msg_queue[0], &msg_queue[1], (msg_queue_size - 1) * sizeof(msg_queue_item_t)); + msg_queue_size--; + } +} + +bool network_get_msg_queue_item(msg_queue_item_t *item) { + if (msg_queue_size == 0) { + return false; + } + + *item = msg_queue[0]; + + return true; +} + + +bool network_get_packet() +{ + int MAXBUFLEN = 100; + char buf[MAXBUFLEN]; + struct sockaddr_storage their_addr; + socklen_t addr_len = sizeof their_addr; + int numbytes = 0; + + if ((numbytes = wrap_recvfrom(client_sockfd, buf, MAXBUFLEN-1 , 0, + (struct sockaddr *)&their_addr, &addr_len)) == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available right now, not a fatal error + return false; + } else { + perror("recvfrom"); + exit(1); + } + } + + buf[numbytes] = '\0'; + network_add_msg_queue_item(buf, (struct sockaddr_in*)&their_addr); + + return true; +} + +void network_send_packet(int sockfd, int length, const void *data, struct sockaddr_in dest_net) +{ + system_send_packet(sockfd, length, data, dest_net); +} + +int network_sleep(int msec) +{ + struct timeval timeout; + fd_set fdset; + + if(!network_has_bound_ip_socket()) { + printf("no socket bound, nothing to wait for\n"); + return 0; // no socket bound, nothing to wait for + } + + FD_ZERO(&fdset); + FD_SET(client_sockfd, &fdset); // network socket + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + return select(client_sockfd + 1, &fdset, NULL, NULL, &timeout); +} + +void network_get_my_ip(char *subnet, size_t len) { + + struct sockaddr_in dest = {0}; + dest.sin_family = AF_INET; + dest.sin_port = htons(53); // DNS + inet_pton(AF_INET, "8.8.8.8", &dest.sin_addr); + + int sockfd = network_get_client_socket(); + if(sockfd == INVALID_SOCKET) { + perror("could not get ip address, quitting\n"); + return; + } + + connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)); + + struct sockaddr_in local; + len = sizeof(local); + getsockname(sockfd, (struct sockaddr *)&local, &len); + + strncpy(subnet, inet_ntoa(local.sin_addr), len); + + network_close_socket(&sockfd); +} + +/** + * @brief Get broadcast addresses for LAN + * + * @return broadcast_list_t + */ +broadcast_list_t network_get_broadcast_addresses(void) { + broadcast_list_t result = {0}; + size_t capacity = 8; + +#if defined(WIN32) + DWORD size = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, NULL, &size); + IP_ADAPTER_ADDRESSES* adapters = (IP_ADAPTER_ADDRESSES*)malloc(size); + if (!adapters || GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, adapters, &size) != NO_ERROR) { + free(adapters); + return result; + } + + result.list = (broadcast_addr_t*)malloc(sizeof(broadcast_addr_t) * capacity); + + for (IP_ADAPTER_ADDRESSES* adapter = adapters; adapter; adapter = adapter->Next) { + if (adapter->IfType != IF_TYPE_ETHERNET_CSMACD && adapter->IfType != IF_TYPE_IEEE80211) + continue; + + for (IP_ADAPTER_UNICAST_ADDRESS* ua = adapter->FirstUnicastAddress; ua; ua = ua->Next) { + SOCKADDR_IN* sa = (SOCKADDR_IN*)ua->Address.lpSockaddr; + uint32_t ip = ntohl(sa->sin_addr.S_un.S_addr); + uint32_t mask = (0xFFFFFFFF << (32 - ua->OnLinkPrefixLength)) & 0xFFFFFFFF; + uint32_t bcast = (ip & mask) | (~mask); + + if (result.count >= capacity) { + capacity *= 2; + result.list = realloc(result.list, sizeof(broadcast_addr_t) * capacity); + } + + result.list[result.count++].broadcast.s_addr = htonl(bcast); + } + } + + free(adapters); +#else + struct ifaddrs* ifaddr = NULL; + if (getifaddrs(&ifaddr) == -1) { + return result; + } + + result.list = (broadcast_addr_t*)malloc(sizeof(broadcast_addr_t) * capacity); + + for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) + continue; + + if ((ifa->ifa_flags & IFF_LOOPBACK) || !(ifa->ifa_flags & IFF_BROADCAST)) + continue; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ - 1); + + if (ioctl(network_get_client_socket(), SIOCGIFBRDADDR, &ifr) < 0) + continue; + + struct sockaddr_in* baddr = (struct sockaddr_in*)&ifr.ifr_broadaddr; + if (result.count >= capacity) { + capacity *= 2; + result.list = realloc(result.list, sizeof(broadcast_addr_t) * capacity); + } + + result.list[result.count++].broadcast = baddr->sin_addr; + } + + freeifaddrs(ifaddr); +#endif + + return result; +} diff --git a/network/network.h b/network/network.h new file mode 100644 index 0000000..f40aef8 --- /dev/null +++ b/network/network.h @@ -0,0 +1,138 @@ + +#pragma once + +#include "network_types.h" + +#if defined(WIN32) +#include +#include +#else +#include +#define INVALID_SOCKET -1 +#endif + +#define WIPEOUT_PORT 8000 +#define WIPEOUT_CLIENT_PORT 8001 +#define CLIENT_SOCKET_TIMEOUT 3000 // 3 seconds + +typedef struct { + const char* command; + struct sockaddr_in dest_addr; +} msg_queue_item_t; + +#if defined(WIN32) +/** + * @brief Initialize the Winsock library + * + * @return true if initialization was successful + */ +bool system_init_winsock(void); + +/** + * @brief Cleanup the Winsock library + */ +void system_cleanup_winsock(void); +#endif + +/** + * @brief Check if the current socket is valid and bound + * + * @return true if the socket is valid and bound + */ +bool network_has_bound_ip_socket(void); + +/** + * @brief Set a bound ip socket; really + * should only be used for testing + * + * @param sockfd the socket fd + */ +void network_set_bound_ip_socket(int sockfd); + +/** + * @brief Get the currently bound ip socket; + * callers should check if the socket is valid first + * + * @return the socket fd + */ +int network_get_bound_ip_socket(void); + +/** + * @brief Get a client socket for sending and receiving packets + * + * Returns -1 (INVALID_SOCKET) if the socket could not be created + * + * @return int the socket fd + */ +int network_get_client_socket(void); + +/** + * @brief Close a socket. The socket fd will be set to INVALID_SOCKET + * on close. + * + * @param sockfd a pointer to the socket fd + */ +void network_close_socket(int* sockfd); + +/** + * @brief Bind a socket to a specific IP address and port to + * listen for incoming packets. + * + * @param sockfd the socket file descriptor + * @param port the port to bind to + */ +bool network_bind_socket(int sockfd, char* port); + +bool network_get_packet(void); + +/** + * @brief Send a packet of data to a specific destination + * + * @param sockfd the socket file descriptor + * @param length the length of the data to send + * @param data the data to send + * @param dest_net the destination netadr_t + */ +void network_send_packet(int sockfd, int length, const void* data, struct sockaddr_in dest_net); + +/** + * @brief clears the current message queue + */ +void network_clear_msg_queue(void); + +/** + * @brief gets the size of the message queue + */ +int network_get_msg_queue_size(void); + +/** + * @brief pop out the first message queue item + */ +void network_popleft_msg_queue(void); + +/** + * @brief get the first message queue item + * + * @param item pointer to the item + * + * @return true if there is an item, false otherwise + */ +bool network_get_msg_queue_item(msg_queue_item_t *item); + +/** + * @brief sleep for a duration, or until we have socket activity + * + * @param msec duration in milliseconds + * @return 0 if network is not available, or 1 if so + */ +int network_sleep(int msec); + +/** + * @brief get the local subnet for this network participant + * + * @param[in,out] subnet the subnet address + * @param len the length of the subnet address + */ +void network_get_my_ip(char* subnet, size_t len); + +broadcast_list_t network_get_broadcast_addresses(void); diff --git a/network/network_types.h b/network/network_types.h new file mode 100644 index 0000000..d4b9759 --- /dev/null +++ b/network/network_types.h @@ -0,0 +1,43 @@ + +#pragma once + +#include +#if defined(WIN32) +#include +#else +#include +#endif + +typedef unsigned char byte; + +#define MAX_STRING_CHARS 1024 +#define MAX_MSGLEN 16 + +typedef struct { + byte ip[4]; + // N.B. needs to be big-endian + unsigned short port; +} netadr_t; + +typedef struct { + int cursize; + int readcount; + int bit; + int maxsize; + bool oob; + byte* data; +} msg_t; + +typedef enum { + CLIENT, + SERVER, +} network_role_t; + +typedef struct { + struct in_addr broadcast; +} broadcast_addr_t; + +typedef struct { + broadcast_addr_t* list; + size_t count; +} broadcast_list_t; \ No newline at end of file diff --git a/network/network_wrapper.c b/network/network_wrapper.c new file mode 100644 index 0000000..968a787 --- /dev/null +++ b/network/network_wrapper.c @@ -0,0 +1,12 @@ + +#include "network_wrapper.h" + +ssize_t wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) { + return recvfrom(sockfd, buf, len, flags, src_addr, addrlen); +} + +ssize_t wrap_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { + return sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} \ No newline at end of file diff --git a/network/network_wrapper.h b/network/network_wrapper.h new file mode 100644 index 0000000..b188fa6 --- /dev/null +++ b/network/network_wrapper.h @@ -0,0 +1,14 @@ +#pragma once + +#if defined(_WIN32) +#include +#include "win_defines.h" +#else +#include +#endif + +ssize_t wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen); + +ssize_t wrap_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); diff --git a/network/win_defines.h b/network/win_defines.h new file mode 100644 index 0000000..b129d2c --- /dev/null +++ b/network/win_defines.h @@ -0,0 +1,18 @@ + +#pragma once + +#if defined(WIN32) + +#ifndef socklen_t +typedef int socklen_t; +#endif + +#ifndef ssize_t +#ifdef _WIN64 +typedef __int64 ssize_t; +#else +typedef long ssize_t; +#endif +#endif + +#endif diff --git a/protos/ServerInfo.proto b/protos/ServerInfo.proto new file mode 100644 index 0000000..66c2826 --- /dev/null +++ b/protos/ServerInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package wipeout; + +message ServerInfo { + string name = 1; + int32 port = 2; +} \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000..161238c --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,13 @@ + +add_subdirectory(serverlib) + +add_executable( + server + main.c +) + +target_link_libraries( + server PRIVATE + serverlib + network +) \ No newline at end of file diff --git a/server/main.c b/server/main.c new file mode 100644 index 0000000..491499c --- /dev/null +++ b/server/main.c @@ -0,0 +1,62 @@ + +#include + +#include +#include + +#include +#include +#include +#if defined(WIN32) +#else +#include +#endif + +typedef struct +{ + const char *name; + int num_clients; +} server_t; + +static server_t server; + +static void server_init() +{ + server.name = "master blaster"; + server.num_clients = 0; + + client_com_init(); +} + +int main(int argc, char** argv) +{ + (void)argc; // unused + (void)argv; // unused + + printf("welcome to the server!\n"); + + server_init(); + + int sockfd = network_get_client_socket(); + if(sockfd == INVALID_SOCKET) { + printf("could not create socket, quitting\n"); + return 1; + } + if(!network_bind_socket(sockfd, "8000")) { + printf("could not start server, quitting\n"); + return 1; + } + + while (true) + { + if(network_sleep(100) <= 0) { + // no network activity, continue + continue; + } + + network_get_packet(); + server_process_queue(); + } + + return 0; +} diff --git a/server/server_types.h b/server/server_types.h new file mode 100644 index 0000000..7e1ce9d --- /dev/null +++ b/server/server_types.h @@ -0,0 +1,7 @@ + +#ifndef SERVER_TYPES_H +#define SERVER_TYPES_H + +#include + +#endif \ No newline at end of file diff --git a/server/serverlib/CMakeLists.txt b/server/serverlib/CMakeLists.txt new file mode 100644 index 0000000..263ddcf --- /dev/null +++ b/server/serverlib/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_library(serverlib STATIC + client_com.c +) + +target_include_directories(serverlib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(serverlib PRIVATE + network +) diff --git a/server/serverlib/README.md b/server/serverlib/README.md new file mode 100644 index 0000000..feb444f --- /dev/null +++ b/server/serverlib/README.md @@ -0,0 +1,4 @@ + +# Network play + +A server hosts the game state - human players send controller inputs and get back positions of each racer. diff --git a/server/serverlib/client_com.c b/server/serverlib/client_com.c new file mode 100644 index 0000000..765edc6 --- /dev/null +++ b/server/serverlib/client_com.c @@ -0,0 +1,171 @@ + +#include "client_com.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +#include +#include +#else +#include +#endif + +#include + +typedef enum { DISCONNECTED, CONNECTED } connect_state_t; + +struct client_t { + const char *name; + struct sockaddr_in addr; // client address +}; + +unsigned int current_client_count = 0; // number of connected clients +static client_t* clients = NULL; // array of clients + + +void client_com_init(void) { + clients = malloc(sizeof(client_t) * MAX_CLIENTS); + if (!clients) { + fprintf(stderr, "Failed to allocate memory for clients\n"); + exit(EXIT_FAILURE); + } + current_client_count = 0; +} + + +static void server_connect_client(struct sockaddr_in net_addr) { + + if(!clients) { + fprintf(stderr, "Client array not initialized\n"); + + const char* response = "connect_failed"; + network_send_packet(network_get_bound_ip_socket(), strlen(response), response, net_addr); + return; + } + + if (current_client_count >= MAX_CLIENTS) { + fprintf(stderr, "Cannot connect client: max clients reached\n"); + + const char* response = "connect_failed"; + network_send_packet(network_get_bound_ip_socket(), strlen(response), response, net_addr); + + return; + } + + clients[current_client_count].name = "Client"; // TODO: get actual client name + clients[current_client_count].addr = net_addr; + current_client_count++; + + const char* response = "connected"; + network_send_packet(network_get_bound_ip_socket(), strlen(response), response, net_addr); + + // char addr[INET_ADDRSTRLEN]; + // netadr_to_sockadr(net_addr, (struct sockaddr_in *)&client->ip); + + // printf("Client %s connected\n", addr); +} + +static void server_disconnect_client(void) { + // handle client disconnection + client_t *client = malloc(sizeof(client_t)); + if (!client) { + fprintf(stderr, "Failed to allocate memory for client\n"); + return; + } + client->name = "Client"; + + // client will disconnect on their end, + // no need to tell them we are removing them +} + +void server_set_connected_clients_count(int count) { + current_client_count = count; + if (current_client_count > MAX_CLIENTS) { + fprintf(stderr, "Too many clients connected: %d\n", current_client_count); + current_client_count = MAX_CLIENTS; // cap at max + } +} + +unsigned int server_get_connected_clients_count(void) { return current_client_count; } + +client_t *server_get_client_by_index(unsigned int index) { + if(index >= current_client_count) { + fprintf(stderr, "Index out of bounds: %d\n", index); + return NULL; + } + + return &clients[index]; +} + +static void server_status(struct sockaddr_in net_addr) { + + Wipeout__ServerInfo msg = WIPEOUT__SERVER_INFO__INIT; + + // TODO: + // read from config or environment + msg.name = "MY SERVER"; + msg.port = 8000; + + size_t len = wipeout__server_info__get_packed_size(&msg); + uint8_t *buffer = malloc(len); + if (!buffer) { + fprintf(stderr, "Failed to allocate buffer for server info\n"); + return; + } + wipeout__server_info__pack(&msg, buffer); + network_send_packet(network_get_bound_ip_socket(), len, buffer, net_addr); + + return; +} + +/** + * Parse messages received from a client, + * and kick off any commands as appopriate + */ +static void server_parse_msg(msg_queue_item_t *item) { + const char *cmd = item->command; + char addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &((struct sockaddr_in *)&item->dest_addr)->sin_addr, + addr, sizeof(addr)); + char buf[100]; + + // netadr_t net_addr; + // sockadr_to_netadr((struct sockaddr_in *)&item->dest_addr, &net_addr); + + if (strcmp(cmd, "status") == 0) { + // handle status + server_status(item->dest_addr); + return; + } else if (strcmp(cmd, "connect") == 0) { + // handle connect + server_connect_client(item->dest_addr); + return; + } else if (strcmp(cmd, "disconnect") == 0) { + // handle quit + server_disconnect_client(); + sprintf(buf, "Client %s disconnected\n", addr); + } else if (strcmp(cmd, "hello") == 0) { + // handle hello (just echo back the client's address) + sprintf(buf, "Hello from %s\n", addr); + } else { + sprintf(buf, "Unknown command: %s\n", cmd); + } + network_send_packet(network_get_bound_ip_socket(), strlen(buf), buf, + item->dest_addr); +} + +void server_process_queue(void) { + msg_queue_item_t item; + while (network_get_msg_queue_item(&item)) { + server_parse_msg(&item); + network_popleft_msg_queue(); + } +} diff --git a/server/serverlib/client_com.h b/server/serverlib/client_com.h new file mode 100644 index 0000000..76b1855 --- /dev/null +++ b/server/serverlib/client_com.h @@ -0,0 +1,35 @@ + +#pragma once +// concerning syncing of clients to the game state + +static const int MAX_CLIENTS = 8; + +typedef struct client_t client_t; + +void client_com_init(void); + +/** + * @brief Processes queued messages from clients + */ +void server_process_queue(void); + +/** + * @brief Set the server connect clients count; + * should really only be used in tests + * + * @param count The number of connected clients to set + */ +void server_set_connected_clients_count(int count); + +/** + * @brief Get the number of connected clients + * @return The number of connected clients + */ +unsigned int server_get_connected_clients_count(void); + +/** + * @brief Get a client by index + * @param index The index of the client to get + * @return The client at the given index, or NULL if out of bounds + */ +client_t* server_get_client_by_index(unsigned int index); diff --git a/server/serverlib/orchestrate.c b/server/serverlib/orchestrate.c new file mode 100644 index 0000000..ae8d9c2 --- /dev/null +++ b/server/serverlib/orchestrate.c @@ -0,0 +1,23 @@ + +static int event_loop() { + + return 0; +} + +void orchestrate_frame() { + + static int last_time; + + int min_msec = 1; + int msec; + + do + { + // run a few frames until we + // hit our sync target of min_msec + int frame_time = event_loop(); + msec = frame_time - last_time; + } while (msec < min_msec); + + +} \ No newline at end of file diff --git a/server/serverlib/orchestrate.h b/server/serverlib/orchestrate.h new file mode 100644 index 0000000..9433fea --- /dev/null +++ b/server/serverlib/orchestrate.h @@ -0,0 +1,9 @@ + +#ifndef ORCHESTRATE__H +#define ORCHESTRATE__H + +// run a frame of the game +void orchestrate_frame(); + + +#endif \ No newline at end of file diff --git a/src/input.c b/src/input.c index 9e04a80..5bfe124 100644 --- a/src/input.c +++ b/src/input.c @@ -229,7 +229,7 @@ void input_textinput(int32_t ascii_char) { void input_bind(input_layer_t layer, button_t button, uint8_t action) { error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button); - error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); + error_if(action >= INPUT_ACTION_MAX, "Invalid input action %d", action); error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer); actions_state[action] = 0; @@ -259,19 +259,19 @@ void input_unbind_all(input_layer_t layer) { float input_state(uint8_t action) { - error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); + error_if(action >= INPUT_ACTION_MAX, "Invalid input action %d", action); return actions_state[action]; } bool input_pressed(uint8_t action) { - error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); + error_if(action >= INPUT_ACTION_MAX, "Invalid input action %d", action); return actions_pressed[action]; } bool input_released(uint8_t action) { - error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); + error_if(action >= INPUT_ACTION_MAX, "Invalid input action %d", action); return actions_released[action]; } diff --git a/src/libs/sokol_app.h b/src/libs/sokol_app.h deleted file mode 100644 index a1f45b6..0000000 --- a/src/libs/sokol_app.h +++ /dev/null @@ -1,11518 +0,0 @@ -#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) -#define SOKOL_APP_IMPL -#endif -#ifndef SOKOL_APP_INCLUDED -/* - sokol_app.h -- cross-platform application wrapper - - Project URL: https://github.com/floooh/sokol - - Do this: - #define SOKOL_IMPL or - #define SOKOL_APP_IMPL - before you include this file in *one* C or C++ file to create the - implementation. - - In the same place define one of the following to select the 3D-API - which should be initialized by sokol_app.h (this must also match - the backend selected for sokol_gfx.h if both are used in the same - project): - - #define SOKOL_GLCORE33 - #define SOKOL_GLES3 - #define SOKOL_D3D11 - #define SOKOL_METAL - #define SOKOL_WGPU - - Optionally provide the following defines with your own implementations: - - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) - SOKOL_WIN32_FORCE_MAIN - define this on Win32 to use a main() entry point instead of WinMain - SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function - SOKOL_APP_API_DECL - public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_APP_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - - Optionally define the following to force debug checks and validations - even in release mode: - - SOKOL_DEBUG - by default this is defined if _DEBUG is defined - - If sokol_app.h is compiled as a DLL, define the following before - including the declaration or implementation: - - SOKOL_DLL - - On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) - or __declspec(dllimport) as needed. - - On Linux, SOKOL_GLCORE33 can use either GLX or EGL. - GLX is default, set SOKOL_FORCE_EGL to override. - - For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp - - Portions of the Windows and Linux GL initialization, event-, icon- etc... code - have been taken from GLFW (http://www.glfw.org/) - - iOS onscreen keyboard support 'inspired' by libgdx. - - Link with the following system libraries: - - - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit - - on macOS with GL: Cocoa, QuartzCore, OpenGL - - on iOS with Metal: Foundation, UIKit, Metal, MetalKit - - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit - - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) - - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) - - on Android: GLESv3, EGL, log, android - - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib - - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined - - link with the following libs: -lkernel32 -luser32 -lshell32 - - additionally with the GL backend: -lgdi32 - - additionally with the D3D11 backend: -ld3d11 -ldxgi - - On Linux, you also need to use the -pthread compiler and linker option, otherwise weird - things will happen, see here for details: https://github.com/floooh/sokol/issues/376 - - On macOS and iOS, the implementation must be compiled as Objective-C. - - FEATURE OVERVIEW - ================ - sokol_app.h provides a minimalistic cross-platform API which - implements the 'application-wrapper' parts of a 3D application: - - - a common application entry function - - creates a window and 3D-API context/device with a 'default framebuffer' - - makes the rendered frame visible - - provides keyboard-, mouse- and low-level touch-events - - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android - - 3D-APIs: Metal, D3D11, GL3.2, GLES3, WebGL, WebGL2 - - FEATURE/PLATFORM MATRIX - ======================= - | 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 - ============ - --- Add a sokol_main() function to your code which returns a sapp_desc structure - with initialization parameters and callback function pointers. This - function is called very early, usually at the start of the - platform's entry function (e.g. main or WinMain). You should do as - little as possible here, since the rest of your code might be called - from another thread (this depends on the platform): - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - .width = 640, - .height = 480, - .init_cb = my_init_func, - .frame_cb = my_frame_func, - .cleanup_cb = my_cleanup_func, - .event_cb = my_event_func, - ... - }; - } - - To get any logging output in case of errors you need to provide a log - callback. The easiest way is via sokol_log.h: - - #include "sokol_log.h" - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger.func = slog_func, - }; - } - - There are many more setup parameters, but these are the most important. - For a complete list search for the sapp_desc structure declaration - below. - - DO NOT call any sokol-app function from inside sokol_main(), since - sokol-app will not be initialized at this point. - - The .width and .height parameters are the preferred size of the 3D - rendering canvas. The actual size may differ from this depending on - platform and other circumstances. Also the canvas size may change at - any time (for instance when the user resizes the application window, - or rotates the mobile device). You can just keep .width and .height - zero-initialized to open a default-sized window (what "default-size" - exactly means is platform-specific, but usually it's a size that covers - most of, but not all, of the display). - - All provided function callbacks will be called from the same thread, - but this may be different from the thread where sokol_main() was called. - - .init_cb (void (*)(void)) - This function is called once after the application window, - 3D rendering context and swap chain have been created. The - function takes no arguments and has no return value. - .frame_cb (void (*)(void)) - This is the per-frame callback, which is usually called 60 - times per second. This is where your application would update - most of its state and perform all rendering. - .cleanup_cb (void (*)(void)) - The cleanup callback is called once right before the application - quits. - .event_cb (void (*)(const sapp_event* event)) - The event callback is mainly for input handling, but is also - used to communicate other types of events to the application. Keep the - event_cb struct member zero-initialized if your application doesn't require - event handling. - - As you can see, those 'standard callbacks' don't have a user_data - argument, so any data that needs to be preserved between callbacks - must live in global variables. If keeping state in global variables - is not an option, there's an alternative set of callbacks with - an additional user_data pointer argument: - - .user_data (void*) - The user-data argument for the callbacks below - .init_userdata_cb (void (*)(void* user_data)) - .frame_userdata_cb (void (*)(void* user_data)) - .cleanup_userdata_cb (void (*)(void* user_data)) - .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) - - The function sapp_userdata() can be used to query the user_data - pointer provided in the sapp_desc struct. - - You can also call sapp_query_desc() to get a copy of the - original sapp_desc structure. - - NOTE that there's also an alternative compile mode where sokol_app.h - doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. - - --- Implement the initialization callback function (init_cb), this is called - once after the rendering surface, 3D API and swap chain have been - initialized by sokol_app. All sokol-app functions can be called - from inside the initialization callback, the most useful functions - at this point are: - - int sapp_width(void) - int sapp_height(void) - Returns the current width and height of the default framebuffer in pixels, - this may change from one frame to the next, and it may be different - from the initial size provided in the sapp_desc struct. - - float sapp_widthf(void) - float sapp_heightf(void) - These are alternatives to sapp_width() and sapp_height() which return - the default framebuffer size as float values instead of integer. This - may help to prevent casting back and forth between int and float - in more strongly typed languages than C and C++. - - double sapp_frame_duration(void) - Returns the frame duration in seconds averaged over a number of - frames to smooth out any jittering spikes. - - int sapp_color_format(void) - int sapp_depth_format(void) - The color and depth-stencil pixelformats of the default framebuffer, - as integer values which are compatible with sokol-gfx's - sg_pixel_format enum (so that they can be plugged directly in places - where sg_pixel_format is expected). Possible values are: - - 23 == SG_PIXELFORMAT_RGBA8 - 28 == SG_PIXELFORMAT_BGRA8 - 42 == SG_PIXELFORMAT_DEPTH - 43 == SG_PIXELFORMAT_DEPTH_STENCIL - - int sapp_sample_count(void) - Return the MSAA sample count of the default framebuffer. - - const void* sapp_metal_get_device(void) - const void* sapp_metal_get_renderpass_descriptor(void) - const void* sapp_metal_get_drawable(void) - If the Metal backend has been selected, these functions return pointers - to various Metal API objects required for rendering, otherwise - they return a null pointer. These void pointers are actually - Objective-C ids converted with a (ARC) __bridge cast so that - the ids can be tunnel through C code. Also note that the returned - pointers to the renderpass-descriptor and drawable may change from one - frame to the next, only the Metal device object is guaranteed to - stay the same. - - const void* sapp_macos_get_window(void) - On macOS, get the NSWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_ios_get_window(void) - On iOS, get the UIWindow object pointer, otherwise a null pointer. - Before being used as Objective-C object, the void* must be converted - back with a (ARC) __bridge cast. - - const void* sapp_win32_get_hwnd(void) - On Windows, get the window's HWND, otherwise a null pointer. The - HWND has been cast to a void pointer in order to be tunneled - through code which doesn't include Windows.h. - - const void* sapp_d3d11_get_device(void) - const void* sapp_d3d11_get_device_context(void) - const void* sapp_d3d11_get_render_target_view(void) - const void* sapp_d3d11_get_depth_stencil_view(void) - Similar to the sapp_metal_* functions, the sapp_d3d11_* functions - return pointers to D3D11 API objects required for rendering, - only if the D3D11 backend has been selected. Otherwise they - return a null pointer. Note that the returned pointers to the - render-target-view and depth-stencil-view may change from one - frame to the next! - - const void* sapp_wgpu_get_device(void) - const void* sapp_wgpu_get_render_view(void) - const void* sapp_wgpu_get_resolve_view(void) - const void* sapp_wgpu_get_depth_stencil_view(void) - These are the WebGPU-specific functions to get the WebGPU - objects and values required for rendering. If sokol_app.h - is not compiled with SOKOL_WGPU, these functions return null. - - const void* sapp_android_get_native_activity(void); - On Android, get the native activity ANativeActivity pointer, otherwise - a null pointer. - - --- Implement the frame-callback function, this function will be called - on the same thread as the init callback, but might be on a different - thread than the sokol_main() function. Note that the size of - the rendering framebuffer might have changed since the frame callback - was called last. Call the functions sapp_width() and sapp_height() - each frame to get the current size. - - --- Optionally implement the event-callback to handle input events. - sokol-app provides the following type of input events: - - a 'virtual key' was pressed down or released - - a single text character was entered (provided as UTF-32 code point) - - a mouse button was pressed down or released (left, right, middle) - - mouse-wheel or 2D scrolling events - - the mouse was moved - - the mouse has entered or left the application window boundaries - - low-level, portable multi-touch events (began, moved, ended, cancelled) - - the application window was resized, iconified or restored - - the application was suspended or restored (on mobile platforms) - - the user or application code has asked to quit the application - - a string was pasted to the system clipboard - - one or more files have been dropped onto the application window - - To explicitly 'consume' an event and prevent that the event is - forwarded for further handling to the operating system, call - sapp_consume_event() from inside the event handler (NOTE that - this behaviour is currently only implemented for some HTML5 - events, support for other platforms and event types will - be added as needed, please open a github ticket and/or provide - a PR if needed). - - NOTE: Do *not* call any 3D API rendering functions in the event - callback function, since the 3D API context may not be active when the - event callback is called (it may work on some platforms and 3D APIs, - but not others, and the exact behaviour may change between - sokol-app versions). - - --- Implement the cleanup-callback function, this is called once - after the user quits the application (see the section - "APPLICATION QUIT" for detailed information on quitting - behaviour, and how to intercept a pending quit - for instance to show a - "Really Quit?" dialog box). Note that the cleanup-callback isn't - guaranteed to be called on the web and mobile platforms. - - MOUSE CURSOR TYPE AND VISIBILITY - ================================ - You can show and hide the mouse cursor with - - void sapp_show_mouse(bool show) - - And to get the current shown status: - - bool sapp_mouse_shown(void) - - NOTE that hiding the mouse cursor is different and independent from - the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when - active (MOUSE LOCK is described below). - - To change the mouse cursor to one of several predefined types, call - the function: - - void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) - - Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore - the standard look. - - To get the currently active mouse cursor type, call: - - sapp_mouse_cursor sapp_get_mouse_cursor(void) - - MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) - ================================================ - In normal mouse mode, no mouse movement events are reported when the - mouse leaves the windows client area or hits the screen border (whether - it's one or the other depends on the platform), and the mouse move events - (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in - framebuffer pixels in the sapp_event items mouse_x and mouse_y, and - relative movement in framebuffer pixels in the sapp_event items mouse_dx - and mouse_dy. - - To get continuous mouse movement (also when the mouse leaves the window - client area or hits the screen border), activate mouse-lock mode - by calling: - - sapp_lock_mouse(true) - - When mouse lock is activated, the mouse pointer is hidden, the - reported absolute mouse position (sapp_event.mouse_x/y) appears - frozen, and the relative mouse movement in sapp_event.mouse_dx/dy - no longer has a direct relation to framebuffer pixels but instead - uses "raw mouse input" (what "raw mouse input" exactly means also - differs by platform). - - To deactivate mouse lock and return to normal mouse mode, call - - sapp_lock_mouse(false) - - And finally, to check if mouse lock is currently active, call - - if (sapp_mouse_locked()) { ... } - - On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() - functions work as expected (mouse lock is activated or deactivated - immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() - also immediately returns the new state after sapp_lock_mouse() - is called. - - On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave - differently, as dictated by the limitations of the HTML5 Pointer Lock API: - - - sapp_lock_mouse(true) can be called at any time, but it will - only take effect in a 'short-lived input event handler of a specific - type', meaning when one of the following events happens: - - SAPP_EVENTTYPE_MOUSE_DOWN - - SAPP_EVENTTYPE_MOUSE_UP - - SAPP_EVENTTYPE_MOUSE_SCROLL - - SAPP_EVENTTYPE_KEY_UP - - SAPP_EVENTTYPE_KEY_DOWN - - The mouse lock/unlock action on the web platform is asynchronous, - this means that sapp_mouse_locked() won't immediately return - the new status after calling sapp_lock_mouse(), instead the - reported status will only change when the pointer lock has actually - been activated or deactivated in the browser. - - On the web, mouse lock can be deactivated by the user at any time - by pressing the Esc key. When this happens, sokol_app.h behaves - the same as if sapp_lock_mouse(false) is called. - - For things like camera manipulation it's most straightforward to lock - and unlock the mouse right from the sokol_app.h event handler, for - instance the following code enters and leaves mouse lock when the - left mouse button is pressed and released, and then uses the relative - movement information to manipulate a camera (taken from the - cgltf-sapp.c sample in the sokol-samples repository - at https://github.com/floooh/sokol-samples): - - static void input(const sapp_event* ev) { - switch (ev->type) { - case SAPP_EVENTTYPE_MOUSE_DOWN: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(true); - } - break; - - case SAPP_EVENTTYPE_MOUSE_UP: - if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - sapp_lock_mouse(false); - } - break; - - case SAPP_EVENTTYPE_MOUSE_MOVE: - if (sapp_mouse_locked()) { - cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); - } - break; - - default: - break; - } - } - - CLIPBOARD SUPPORT - ================= - Applications can send and receive UTF-8 encoded text data from and to the - system clipboard. By default, clipboard support is disabled and - must be enabled at startup via the following sapp_desc struct - members: - - sapp_desc.enable_clipboard - set to true to enable clipboard support - sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes - - Enabling the clipboard will dynamically allocate a clipboard buffer - for UTF-8 encoded text data of the requested size in bytes, the default - size is 8 KBytes. Strings that don't fit into the clipboard buffer - (including the terminating zero) will be silently clipped, so it's - important that you provide a big enough clipboard size for your - use case. - - To send data to the clipboard, call sapp_set_clipboard_string() with - a pointer to an UTF-8 encoded, null-terminated C-string. - - NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be - called from inside a 'short-lived event handler', and there are a few - other HTML5-specific caveats to workaround. You'll basically have to - tinker until it works in all browsers :/ (maybe the situation will - improve when all browsers agree on and implement the new - HTML5 navigator.clipboard API). - - To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED - event in your event handler function, and then call sapp_get_clipboard_string() - to obtain the pasted UTF-8 encoded text. - - NOTE that behaviour of sapp_get_clipboard_string() is slightly different - depending on platform: - - - on the HTML5 platform, the internal clipboard buffer will only be updated - right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, - and sapp_get_clipboard_string() will simply return the current content - of the clipboard buffer - - on 'native' platforms, the call to sapp_get_clipboard_string() will - update the internal clipboard buffer with the most recent data - from the system clipboard - - Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, - and then call sapp_get_clipboard_string() right in the event handler. - - The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app - as follows: - - - on macOS: when the Cmd+V key is pressed down - - on HTML5: when the browser sends a 'paste' event to the global 'window' object - - on all other platforms: when the Ctrl+V key is pressed down - - DRAG AND DROP SUPPORT - ===================== - PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 - and on the native desktop platforms (Win32, Linux and macOS) because - of security-related restrictions in the HTML5 drag'n'drop API. The - WASM/HTML5 specifics are described at the end of this documentation - section: - - Like clipboard support, drag'n'drop support must be explicitly enabled - at startup in the sapp_desc struct. - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - ... - }; - } - - You can also adjust the maximum number of files that are accepted - in a drop operation, and the maximum path length in bytes if needed: - - sapp_desc sokol_main() { - return (sapp_desc) { - .enable_dragndrop = true, // default is false - .max_dropped_files = 8, // default is 1 - .max_dropped_file_path_length = 8192, // in bytes, default is 2048 - ... - }; - } - - When drag'n'drop is enabled, the event callback will be invoked with an - event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on - the application window. - - After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the - number of dropped files, and their absolute paths by calling separate - functions: - - void on_event(const sapp_event* ev) { - if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { - - // the mouse position where the drop happened - float x = ev->mouse_x; - float y = ev->mouse_y; - - // get the number of files and their paths like this: - const int num_dropped_files = sapp_get_num_dropped_files(); - for (int i = 0; i < num_dropped_files; i++) { - const char* path = sapp_get_dropped_file_path(i); - ... - } - } - } - - The returned file paths are UTF-8 encoded strings. - - You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() - anywhere, also outside the event handler callback, but be aware that the - file path strings will be overwritten with the next drop operation. - - In any case, sapp_get_dropped_file_path() will never return a null pointer, - instead an empty string "" will be returned if the drag'n'drop feature - hasn't been enabled, the last drop-operation failed, or the file path index - is out of range. - - Drag'n'drop caveats: - - - if more files are dropped in a single drop-action - than sapp_desc.max_dropped_files, the additional - files will be silently ignored - - if any of the file paths is longer than - sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 - encoding) the entire drop operation will be silently ignored (this - needs some sort of error feedback in the future) - - no mouse positions are reported while the drag is in - process, this may change in the future - - Drag'n'drop on HTML5/WASM: - - The HTML5 drag'n'drop API doesn't return file paths, but instead - black-box 'file objects' which must be used to load the content - of dropped files. This is the reason why sokol_app.h adds two - HTML5-specific functions to the drag'n'drop API: - - uint32_t sapp_html5_get_dropped_file_size(int index) - Returns the size in bytes of a dropped file. - - void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) - Asynchronously loads the content of a dropped file into a - provided memory buffer (which must be big enough to hold - the file content) - - To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED - event is received: - - sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ - .dropped_file_index = 0, - .callback = fetch_cb - .buffer = { - .ptr = buf, - .size = sizeof(buf) - }, - .user_data = ... - }); - - Make sure that the memory pointed to by 'buf' stays valid until the - callback function is called! - - As result of the asynchronous loading operation (no matter if succeeded or - failed) the 'fetch_cb' function will be called: - - void fetch_cb(const sapp_html5_fetch_response* response) { - // IMPORTANT: check if the loading operation actually succeeded: - if (response->succeeded) { - // the size of the loaded file: - const size_t num_bytes = response->data.size; - // and the pointer to the data (same as 'buf' in the fetch-call): - const void* ptr = response->data.ptr; - } - else { - // on error check the error code: - switch (response->error_code) { - case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: - ... - break; - case SAPP_HTML5_FETCH_ERROR_OTHER: - ... - break; - } - } - } - - Check the droptest-sapp example for a real-world example which works - both on native platforms and the web: - - https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c - - HIGH-DPI RENDERING - ================== - You can set the sapp_desc.high_dpi flag during initialization to request - a full-resolution framebuffer on HighDPI displays. The default behaviour - is sapp_desc.high_dpi=false, this means that the application will - render to a lower-resolution framebuffer on HighDPI displays and the - rendered content will be upscaled by the window system composer. - - In a HighDPI scenario, you still request the same window size during - sokol_main(), but the framebuffer sizes returned by sapp_width() - and sapp_height() will be scaled up according to the DPI scaling - ratio. - - Note that on some platforms the DPI scaling factor may change at any - time (for instance when a window is moved from a high-dpi display - to a low-dpi display). - - To query the current DPI scaling factor, call the function: - - float sapp_dpi_scale(void); - - For instance on a Retina Mac, returning the following sapp_desc - struct from sokol_main(): - - sapp_desc sokol_main() { - return (sapp_desc) { - .width = 640, - .height = 480, - .high_dpi = true, - ... - }; - } - - ...the functions the functions sapp_width(), sapp_height() - and sapp_dpi_scale() will return the following values: - - sapp_width: 1280 - sapp_height: 960 - sapp_dpi_scale: 2.0 - - If the high_dpi flag is false, or you're not running on a Retina display, - the values would be: - - sapp_width: 640 - sapp_height: 480 - sapp_dpi_scale: 1.0 - - If the window is moved from the Retina display to a low-dpi external display, - the values would change as follows: - - sapp_width: 1280 => 640 - sapp_height: 960 => 480 - sapp_dpi_scale: 2.0 => 1.0 - - Currently there is no event associated with a DPI change, but an - SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the - framebuffer size changing. - - Per-monitor DPI is currently supported on macOS and Windows. - - APPLICATION QUIT - ================ - Without special quit handling, a sokol_app.h application will quit - 'gracefully' when the user clicks the window close-button unless a - platform's application model prevents this (e.g. on web or mobile). - 'Graceful exit' means that the application-provided cleanup callback will - be called before the application quits. - - On native desktop platforms sokol_app.h provides more control over the - application-quit-process. It's possible to initiate a 'programmatic quit' - from the application code, and a quit initiated by the application user can - be intercepted (for instance to show a custom dialog box). - - This 'programmatic quit protocol' is implemented through 3 functions - and 1 event: - - - sapp_quit(): This function simply quits the application without - giving the user a chance to intervene. Usually this might - be called when the user clicks the 'Ok' button in a 'Really Quit?' - dialog box - - sapp_request_quit(): Calling sapp_request_quit() will send the - event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler - callback, giving the user code a chance to intervene and cancel the - pending quit process (for instance to show a 'Really Quit?' dialog - box). If the event handler callback does nothing, the application - will be quit as usual. To prevent this, call the function - sapp_cancel_quit() from inside the event handler. - - sapp_cancel_quit(): Cancels a pending quit request, either initiated - by the user clicking the window close button, or programmatically - by calling sapp_request_quit(). The only place where calling this - function makes sense is from inside the event handler callback when - the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. - - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user - clicks the window's close button or application code calls the - sapp_request_quit() function. The event handler callback code can handle - this event by calling sapp_cancel_quit() to cancel the quit. - If the event is ignored, the application will quit as usual. - - On the web platform, the quit behaviour differs from native platforms, - because of web-specific restrictions: - - A `programmatic quit` initiated by calling sapp_quit() or - sapp_request_quit() will work as described above: the cleanup callback is - called, platform-specific cleanup is performed (on the web - this means that JS event handlers are unregisters), and then - the request-animation-loop will be exited. However that's all. The - web page itself will continue to exist (e.g. it's not possible to - programmatically close the browser tab). - - On the web it's also not possible to run custom code when the user - closes a browser tab, so it's not possible to prevent this with a - fancy custom dialog box. - - Instead the standard "Leave Site?" dialog box can be activated (or - deactivated) with the following function: - - sapp_html5_ask_leave_site(bool ask); - - The initial state of the associated internal flag can be provided - at startup via sapp_desc.html5_ask_leave_site. - - This feature should only be used sparingly in critical situations - for - instance when the user would loose data - since popping up modal dialog - boxes is considered quite rude in the web world. Note that there's no way - to customize the content of this dialog box or run any code as a result - of the user's decision. Also note that the user must have interacted with - the site before the dialog box will appear. These are all security measures - to prevent fishing. - - The Dear ImGui HighDPI sample contains example code of how to - implement a 'Really Quit?' dialog box with Dear ImGui (native desktop - platforms only), and for showing the hardwired "Leave Site?" dialog box - when running on the web platform: - - https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html - - FULLSCREEN - ========== - If the sapp_desc.fullscreen flag is true, sokol-app will try to create - a fullscreen window on platforms with a 'proper' window system - (mobile devices will always use fullscreen). The implementation details - depend on the target platform, in general sokol-app will use a - 'soft approach' which doesn't interfere too much with the platform's - window system (for instance borderless fullscreen window instead of - a 'real' fullscreen mode). Such details might change over time - as sokol-app is adapted for different needs. - - The most important effect of fullscreen mode to keep in mind is that - the requested canvas width and height will be ignored for the initial - window size, calling sapp_width() and sapp_height() will instead return - the resolution of the fullscreen canvas (however the provided size - might still be used for the non-fullscreen window, in case the user can - switch back from fullscreen- to windowed-mode). - - To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). - - To check if the application window is currently in fullscreen mode, - call sapp_is_fullscreen(). - - WINDOW ICON SUPPORT - =================== - Some sokol_app.h backends allow to change the window icon programmatically: - - - on Win32: the small icon in the window's title bar, and the - bigger icon in the task bar - - on Linux: highly dependent on the used window manager, but usually - the window's title bar icon and/or the task bar icon - - on HTML5: the favicon shown in the page's browser tab - - NOTE that it is not possible to set the actual application icon which is - displayed by the operating system on the desktop or 'home screen'. Those - icons must be provided 'traditionally' through operating-system-specific - resources which are associated with the application (sokol_app.h might - later support setting the window icon from platform specific resource data - though). - - There are two ways to set the window icon: - - - at application start in the sokol_main() function by initializing - the sapp_desc.icon nested struct - - or later by calling the function sapp_set_icon() - - As a convenient shortcut, sokol_app.h comes with a builtin default-icon - (a rainbow-colored 'S', which at least looks a bit better than the Windows - default icon for applications), which can be activated like this: - - At startup in sokol_main(): - - sapp_desc sokol_main(...) { - return (sapp_desc){ - ... - icon.sokol_default = true - }; - } - - Or later by calling: - - sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true }); - - NOTE that a completely zero-initialized sapp_icon_desc struct will not - update the window icon in any way. This is an 'escape hatch' so that you - can handle the window icon update yourself (or if you do this already, - sokol_app.h won't get in your way, in this case just leave the - sapp_desc.icon struct zero-initialized). - - Providing your own icon images works exactly like in GLFW (down to the - data format): - - You provide one or more 'candidate images' in different sizes, and the - sokol_app.h platform backends pick the best match for the specific backend - and icon type. - - For each candidate image, you need to provide: - - - the width in pixels - - the height in pixels - - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844 - on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44) - - For instance, if you have 3 candidate images (small, medium, big) of - sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup - like this: - - // the actual pixel data (RGBA8, origin top-left) - const uint32_t small[16][16] = { ... }; - const uint32_t medium[32][32] = { ... }; - const uint32_t big[64][64] = { ... }; - - const sapp_icon_desc icon_desc = { - .images = { - { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) }, - { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) }, - // ...or without the SAPP_RANGE helper macro: - { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } } - } - }; - - An sapp_icon_desc struct initialized like this can then either be applied - at application start in sokol_main: - - sapp_desc sokol_main(...) { - return (sapp_desc){ - ... - icon = icon_desc - }; - } - - ...or later by calling sapp_set_icon(): - - sapp_set_icon(&icon_desc); - - Some window icon caveats: - - - once the window icon has been updated, there's no way to go back to - the platform's default icon, this is because some platforms (Linux - and HTML5) don't switch the icon visual back to the default even if - the custom icon is deleted or removed - - on HTML5, if the sokol_app.h icon doesn't show up in the browser - tab, check that there's no traditional favicon 'link' element - is defined in the page's index.html, sokol_app.h will only - append a new favicon link element, but not delete any manually - defined favicon in the page - - For an example and test of the window icon feature, check out the the - 'icon-sapp' sample on the sokol-samples git repository. - - ONSCREEN KEYBOARD - ================= - On some platforms which don't provide a physical keyboard, sokol-app - can display the platform's integrated onscreen keyboard for text - input. To request that the onscreen keyboard is shown, call - - sapp_show_keyboard(true); - - Likewise, to hide the keyboard call: - - sapp_show_keyboard(false); - - Note that on the web platform, the keyboard can only be shown from - inside an input handler. On such platforms, sapp_show_keyboard() - will only work as expected when it is called from inside the - sokol-app event callback function. When called from other places, - an internal flag will be set, and the onscreen keyboard will be - called at the next 'legal' opportunity (when the next input event - is handled). - - OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) - ====================================================== - In its default configuration, sokol_app.h "hijacks" the platform's - standard main() function. This was done because different platforms - have different main functions which are not compatible with - C's main() (for instance WinMain on Windows has completely different - arguments). However, this "main hijacking" posed a problem for - usage scenarios like integrating sokol_app.h with other languages than - C or C++, so an alternative SOKOL_NO_ENTRY mode has been added - in which the user code provides the platform's main function: - - - define SOKOL_NO_ENTRY before including the sokol_app.h implementation - - do *not* provide a sokol_main() function - - instead provide the standard main() function of the platform - - from the main function, call the function ```sapp_run()``` which - takes a pointer to an ```sapp_desc``` structure. - - ```sapp_run()``` takes over control and calls the provided init-, frame-, - shutdown- and event-callbacks just like in the default model, it - will only return when the application quits (or not at all on some - platforms, like emscripten) - - NOTE: SOKOL_NO_ENTRY is currently not supported on Android. - - WINDOWS CONSOLE OUTPUT - ====================== - On Windows, regular windowed applications don't show any stdout/stderr text - output, which can be a bit of a hassle for printf() debugging or generally - logging text to the console. Also, console output by default uses a local - codepage setting and thus international UTF-8 encoded text is printed - as garbage. - - To help with these issues, sokol_app.h can be configured at startup - via the following Windows-specific sapp_desc flags: - - sapp_desc.win32_console_utf8 (default: false) - When set to true, the output console codepage will be switched - to UTF-8 (and restored to the original codepage on exit) - - sapp_desc.win32_console_attach (default: false) - When set to true, stdout and stderr will be attached to the - console of the parent process (if the parent process actually - has a console). This means that if the application was started - in a command line window, stdout and stderr output will be printed - to the terminal, just like a regular command line program. But if - the application is started via double-click, it will behave like - a regular UI application, and stdout/stderr will not be visible. - - sapp_desc.win32_console_create (default: false) - When set to true, a new console window will be created and - stdout/stderr will be redirected to that console window. It - doesn't matter if the application is started from the command - line or via double-click. - - MEMORY ALLOCATION OVERRIDE - ========================== - You can override the memory allocation functions at initialization time - like this: - - void* my_alloc(size_t size, void* user_data) { - return malloc(size); - } - - void my_free(void* ptr, void* user_data) { - free(ptr); - } - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc){ - // ... - .allocator = { - .alloc = my_alloc, - .free = my_free, - .user_data = ..., - } - }; - } - - If no overrides are provided, malloc and free will be used. - - This only affects memory allocation calls done by sokol_app.h - itself though, not any allocations in OS libraries. - - - ERROR REPORTING AND LOGGING - =========================== - To get any logging information at all you need to provide a logging callback in the setup call - the easiest way is to use sokol_log.h: - - #include "sokol_log.h" - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger.func = slog_func, - }; - } - - To override logging with your own callback, first write a logging function like this: - - void my_log(const char* tag, // e.g. 'sapp' - uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info - uint32_t log_item_id, // SAPP_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_app.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data) - { - ... - } - - ...and then setup sokol-app like this: - - sapp_desc sokol_main(int argc, char* argv[]) { - return (sapp_desc) { - ... - .logger = { - .func = my_log, - .user_data = my_user_data, - } - }; - } - - The provided logging function must be reentrant (e.g. be callable from - different threads). - - If you don't want to provide your own custom logger it is highly recommended to use - the standard logger in sokol_log.h instead, otherwise you won't see any warnings or - errors. - - - TEMP NOTE DUMP - ============== - - onscreen keyboard support on Android requires Java :(, should we even bother? - - sapp_desc needs a bool whether to initialize depth-stencil surface - - GL context initialization needs more control (at least what GL version to initialize) - - application icon - - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy - at the latest but should do it earlier, in onStop, as an app is "killable" after onStop - on Android Honeycomb and later (it can't be done at the moment as the app may be started - again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) - - - LICENSE - ======= - zlib/libpng license - - Copyright (c) 2018 Andre Weissflog - - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ -#define SOKOL_APP_INCLUDED (1) -#include // size_t -#include -#include - -#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) -#define SOKOL_APP_API_DECL SOKOL_API_DECL -#endif -#ifndef SOKOL_APP_API_DECL -#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) -#define SOKOL_APP_API_DECL __declspec(dllexport) -#elif defined(_WIN32) && defined(SOKOL_DLL) -#define SOKOL_APP_API_DECL __declspec(dllimport) -#else -#define SOKOL_APP_API_DECL extern -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* misc constants */ -enum { - SAPP_MAX_TOUCHPOINTS = 8, - SAPP_MAX_MOUSEBUTTONS = 3, - SAPP_MAX_KEYCODES = 512, - SAPP_MAX_ICONIMAGES = 8, -}; - -/* - sapp_event_type - - The type of event that's passed to the event handler callback - in the sapp_event.type field. These are not just "traditional" - input events, but also notify the application about state changes - or other user-invoked actions. -*/ -typedef enum sapp_event_type { - SAPP_EVENTTYPE_INVALID, - SAPP_EVENTTYPE_KEY_DOWN, - SAPP_EVENTTYPE_KEY_UP, - SAPP_EVENTTYPE_CHAR, - SAPP_EVENTTYPE_MOUSE_DOWN, - SAPP_EVENTTYPE_MOUSE_UP, - SAPP_EVENTTYPE_MOUSE_SCROLL, - SAPP_EVENTTYPE_MOUSE_MOVE, - SAPP_EVENTTYPE_MOUSE_ENTER, - SAPP_EVENTTYPE_MOUSE_LEAVE, - SAPP_EVENTTYPE_TOUCHES_BEGAN, - SAPP_EVENTTYPE_TOUCHES_MOVED, - SAPP_EVENTTYPE_TOUCHES_ENDED, - SAPP_EVENTTYPE_TOUCHES_CANCELLED, - SAPP_EVENTTYPE_RESIZED, - SAPP_EVENTTYPE_ICONIFIED, - SAPP_EVENTTYPE_RESTORED, - SAPP_EVENTTYPE_FOCUSED, - SAPP_EVENTTYPE_UNFOCUSED, - SAPP_EVENTTYPE_SUSPENDED, - SAPP_EVENTTYPE_RESUMED, - SAPP_EVENTTYPE_QUIT_REQUESTED, - SAPP_EVENTTYPE_CLIPBOARD_PASTED, - SAPP_EVENTTYPE_FILES_DROPPED, - _SAPP_EVENTTYPE_NUM, - _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF -} sapp_event_type; - -/* - sapp_keycode - - The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the - struct field sapp_event.key_code. - - Note that the keycode values are identical with GLFW. -*/ -typedef enum sapp_keycode { - SAPP_KEYCODE_INVALID = 0, - SAPP_KEYCODE_SPACE = 32, - SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ - SAPP_KEYCODE_COMMA = 44, /* , */ - SAPP_KEYCODE_MINUS = 45, /* - */ - SAPP_KEYCODE_PERIOD = 46, /* . */ - SAPP_KEYCODE_SLASH = 47, /* / */ - SAPP_KEYCODE_0 = 48, - SAPP_KEYCODE_1 = 49, - SAPP_KEYCODE_2 = 50, - SAPP_KEYCODE_3 = 51, - SAPP_KEYCODE_4 = 52, - SAPP_KEYCODE_5 = 53, - SAPP_KEYCODE_6 = 54, - SAPP_KEYCODE_7 = 55, - SAPP_KEYCODE_8 = 56, - SAPP_KEYCODE_9 = 57, - SAPP_KEYCODE_SEMICOLON = 59, /* ; */ - SAPP_KEYCODE_EQUAL = 61, /* = */ - SAPP_KEYCODE_A = 65, - SAPP_KEYCODE_B = 66, - SAPP_KEYCODE_C = 67, - SAPP_KEYCODE_D = 68, - SAPP_KEYCODE_E = 69, - SAPP_KEYCODE_F = 70, - SAPP_KEYCODE_G = 71, - SAPP_KEYCODE_H = 72, - SAPP_KEYCODE_I = 73, - SAPP_KEYCODE_J = 74, - SAPP_KEYCODE_K = 75, - SAPP_KEYCODE_L = 76, - SAPP_KEYCODE_M = 77, - SAPP_KEYCODE_N = 78, - SAPP_KEYCODE_O = 79, - SAPP_KEYCODE_P = 80, - SAPP_KEYCODE_Q = 81, - SAPP_KEYCODE_R = 82, - SAPP_KEYCODE_S = 83, - SAPP_KEYCODE_T = 84, - SAPP_KEYCODE_U = 85, - SAPP_KEYCODE_V = 86, - SAPP_KEYCODE_W = 87, - SAPP_KEYCODE_X = 88, - SAPP_KEYCODE_Y = 89, - SAPP_KEYCODE_Z = 90, - SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ - SAPP_KEYCODE_BACKSLASH = 92, /* \ */ - SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ - SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ - SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ - SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ - SAPP_KEYCODE_ESCAPE = 256, - SAPP_KEYCODE_ENTER = 257, - SAPP_KEYCODE_TAB = 258, - SAPP_KEYCODE_BACKSPACE = 259, - SAPP_KEYCODE_INSERT = 260, - SAPP_KEYCODE_DELETE = 261, - SAPP_KEYCODE_RIGHT = 262, - SAPP_KEYCODE_LEFT = 263, - SAPP_KEYCODE_DOWN = 264, - SAPP_KEYCODE_UP = 265, - SAPP_KEYCODE_PAGE_UP = 266, - SAPP_KEYCODE_PAGE_DOWN = 267, - SAPP_KEYCODE_HOME = 268, - SAPP_KEYCODE_END = 269, - SAPP_KEYCODE_CAPS_LOCK = 280, - SAPP_KEYCODE_SCROLL_LOCK = 281, - SAPP_KEYCODE_NUM_LOCK = 282, - SAPP_KEYCODE_PRINT_SCREEN = 283, - SAPP_KEYCODE_PAUSE = 284, - SAPP_KEYCODE_F1 = 290, - SAPP_KEYCODE_F2 = 291, - SAPP_KEYCODE_F3 = 292, - SAPP_KEYCODE_F4 = 293, - SAPP_KEYCODE_F5 = 294, - SAPP_KEYCODE_F6 = 295, - SAPP_KEYCODE_F7 = 296, - SAPP_KEYCODE_F8 = 297, - SAPP_KEYCODE_F9 = 298, - SAPP_KEYCODE_F10 = 299, - SAPP_KEYCODE_F11 = 300, - SAPP_KEYCODE_F12 = 301, - SAPP_KEYCODE_F13 = 302, - SAPP_KEYCODE_F14 = 303, - SAPP_KEYCODE_F15 = 304, - SAPP_KEYCODE_F16 = 305, - SAPP_KEYCODE_F17 = 306, - SAPP_KEYCODE_F18 = 307, - SAPP_KEYCODE_F19 = 308, - SAPP_KEYCODE_F20 = 309, - SAPP_KEYCODE_F21 = 310, - SAPP_KEYCODE_F22 = 311, - SAPP_KEYCODE_F23 = 312, - SAPP_KEYCODE_F24 = 313, - SAPP_KEYCODE_F25 = 314, - SAPP_KEYCODE_KP_0 = 320, - SAPP_KEYCODE_KP_1 = 321, - SAPP_KEYCODE_KP_2 = 322, - SAPP_KEYCODE_KP_3 = 323, - SAPP_KEYCODE_KP_4 = 324, - SAPP_KEYCODE_KP_5 = 325, - SAPP_KEYCODE_KP_6 = 326, - SAPP_KEYCODE_KP_7 = 327, - SAPP_KEYCODE_KP_8 = 328, - SAPP_KEYCODE_KP_9 = 329, - SAPP_KEYCODE_KP_DECIMAL = 330, - SAPP_KEYCODE_KP_DIVIDE = 331, - SAPP_KEYCODE_KP_MULTIPLY = 332, - SAPP_KEYCODE_KP_SUBTRACT = 333, - SAPP_KEYCODE_KP_ADD = 334, - SAPP_KEYCODE_KP_ENTER = 335, - SAPP_KEYCODE_KP_EQUAL = 336, - SAPP_KEYCODE_LEFT_SHIFT = 340, - SAPP_KEYCODE_LEFT_CONTROL = 341, - SAPP_KEYCODE_LEFT_ALT = 342, - SAPP_KEYCODE_LEFT_SUPER = 343, - SAPP_KEYCODE_RIGHT_SHIFT = 344, - SAPP_KEYCODE_RIGHT_CONTROL = 345, - SAPP_KEYCODE_RIGHT_ALT = 346, - SAPP_KEYCODE_RIGHT_SUPER = 347, - SAPP_KEYCODE_MENU = 348, -} sapp_keycode; - -/* - Android specific 'tool type' enum for touch events. This lets the - application check what type of input device was used for - touch events. - - NOTE: the values must remain in sync with the corresponding - Android SDK type, so don't change those. - - See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN -*/ -typedef enum sapp_android_tooltype { - SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN - SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER - SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS - SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE -} sapp_android_tooltype; - -/* - sapp_touchpoint - - Describes a single touchpoint in a multitouch event (TOUCHES_BEGAN, - TOUCHES_MOVED, TOUCHES_ENDED). - - Touch points are stored in the nested array sapp_event.touches[], - and the number of touches is stored in sapp_event.num_touches. -*/ -typedef struct sapp_touchpoint { - uintptr_t identifier; - float pos_x; - float pos_y; - sapp_android_tooltype android_tooltype; // only valid on Android - bool changed; -} sapp_touchpoint; - -/* - sapp_mousebutton - - The currently pressed mouse button in the events MOUSE_DOWN - and MOUSE_UP, stored in the struct field sapp_event.mouse_button. -*/ -typedef enum sapp_mousebutton { - SAPP_MOUSEBUTTON_LEFT = 0x0, - SAPP_MOUSEBUTTON_RIGHT = 0x1, - SAPP_MOUSEBUTTON_MIDDLE = 0x2, - SAPP_MOUSEBUTTON_INVALID = 0x100, -} sapp_mousebutton; - -/* - These are currently pressed modifier keys (and mouse buttons) which are - passed in the event struct field sapp_event.modifiers. -*/ -enum { - SAPP_MODIFIER_SHIFT = 0x1, // left or right shift key - SAPP_MODIFIER_CTRL = 0x2, // left or right control key - SAPP_MODIFIER_ALT = 0x4, // left or right alt key - SAPP_MODIFIER_SUPER = 0x8, // left or right 'super' key - SAPP_MODIFIER_LMB = 0x100, // left mouse button - SAPP_MODIFIER_RMB = 0x200, // right mouse button - SAPP_MODIFIER_MMB = 0x400, // middle mouse button -}; - -/* - sapp_event - - This is an all-in-one event struct passed to the event handler - user callback function. Note that it depends on the event - type what struct fields actually contain useful values, so you - should first check the event type before reading other struct - fields. -*/ -typedef struct sapp_event { - uint64_t frame_count; // current frame counter, always valid, useful for checking if two events were issued in the same frame - sapp_event_type type; // the event type, always valid - sapp_keycode key_code; // the virtual key code, only valid in KEY_UP, KEY_DOWN - uint32_t char_code; // the UTF-32 character code, only valid in CHAR events - bool key_repeat; // true if this is a key-repeat event, valid in KEY_UP, KEY_DOWN and CHAR - uint32_t modifiers; // current modifier keys, valid in all key-, char- and mouse-events - sapp_mousebutton mouse_button; // mouse button that was pressed or released, valid in MOUSE_DOWN, MOUSE_UP - float mouse_x; // current horizontal mouse position in pixels, always valid except during mouse lock - float mouse_y; // current vertical mouse position in pixels, always valid except during mouse lock - float mouse_dx; // relative horizontal mouse movement since last frame, always valid - float mouse_dy; // relative vertical mouse movement since last frame, always valid - float scroll_x; // horizontal mouse wheel scroll distance, valid in MOUSE_SCROLL events - float scroll_y; // vertical mouse wheel scroll distance, valid in MOUSE_SCROLL events - int num_touches; // number of valid items in the touches[] array - sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; // current touch points, valid in TOUCHES_BEGIN, TOUCHES_MOVED, TOUCHES_ENDED - int window_width; // current window- and framebuffer sizes in pixels, always valid - int window_height; - int framebuffer_width; // = window_width * dpi_scale - int framebuffer_height; // = window_height * dpi_scale -} sapp_event; - -/* - sg_range - - A general pointer/size-pair struct and constructor macros for passing binary blobs - into sokol_app.h. -*/ -typedef struct sapp_range { - const void* ptr; - size_t size; -} sapp_range; -// disabling this for every includer isn't great, but the warnings are also quite pointless -#if defined(_MSC_VER) -#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ -#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ -#endif -#if defined(__cplusplus) -#define SAPP_RANGE(x) sapp_range{ &x, sizeof(x) } -#else -#define SAPP_RANGE(x) (sapp_range){ &x, sizeof(x) } -#endif - -/* - sapp_image_desc - - This is used to describe image data to sokol_app.h (at first, window - icons, later maybe cursor images). - - Note that the actual image pixel format depends on the use case: - - - window icon pixels are RGBA8 - - cursor images are ??? (FIXME) -*/ -typedef struct sapp_image_desc { - int width; - int height; - sapp_range pixels; -} sapp_image_desc; - -/* - sapp_icon_desc - - An icon description structure for use in sapp_desc.icon and - sapp_set_icon(). - - When setting a custom image, the application can provide a number of - candidates differing in size, and sokol_app.h will pick the image(s) - closest to the size expected by the platform's window system. - - To set sokol-app's default icon, set .sokol_default to true. - - Otherwise provide candidate images of different sizes in the - images[] array. - - If both the sokol_default flag is set to true, any image candidates - will be ignored and the sokol_app.h default icon will be set. -*/ -typedef struct sapp_icon_desc { - bool sokol_default; - sapp_image_desc images[SAPP_MAX_ICONIMAGES]; -} sapp_icon_desc; - -/* - sapp_allocator - - Used in sapp_desc to provide custom memory-alloc and -free functions - to sokol_app.h. If memory management should be overridden, both the - alloc and free function must be provided (e.g. it's not valid to - override one function but not the other). -*/ -typedef struct sapp_allocator { - void* (*alloc)(size_t size, void* user_data); - void (*free)(void* ptr, void* user_data); - void* user_data; -} sapp_allocator; - -/* - sapp_log_item - - Log items are defined via X-Macros and expanded to an enum - 'sapp_log_item', and in debug mode to corresponding - human readable error messages. -*/ -#define _SAPP_LOG_ITEMS \ - _SAPP_LOGITEM_XMACRO(OK, "Ok") \ - _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ - _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0, 3.2 and 4.1)") \ - _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ - _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ - _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ - _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ - _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ - _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ - _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED, "OpenGL 3.2 not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ - _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ - _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ - _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ - _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ - _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ - _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ - _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ - _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ - _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ - _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ - _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ - _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ - _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity sucessfully created") \ - _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ - _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ - _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ - -#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, -typedef enum sapp_log_item { - _SAPP_LOG_ITEMS -} sapp_log_item; -#undef _SAPP_LOGITEM_XMACRO - -/* - sapp_logger - - Used in sapp_desc to provide a logging function. Please be aware that - without logging function, sokol-app will be completely silent, e.g. it will - not report errors or warnings. For maximum error verbosity, compile in - debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance - the standard logging function from sokol_log.h). -*/ -typedef struct sapp_logger { - void (*func)( - const char* tag, // always "sapp" - uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info - uint32_t log_item_id, // SAPP_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_app.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data); - void* user_data; -} sapp_logger; - -typedef struct sapp_desc { - void (*init_cb)(void); // these are the user-provided callbacks without user data - void (*frame_cb)(void); - void (*cleanup_cb)(void); - void (*event_cb)(const sapp_event*); - - void* user_data; // these are the user-provided callbacks with user data - void (*init_userdata_cb)(void*); - void (*frame_userdata_cb)(void*); - void (*cleanup_userdata_cb)(void*); - void (*event_userdata_cb)(const sapp_event*, void*); - - int width; // the preferred width of the window / canvas - int height; // the preferred height of the window / canvas - int sample_count; // MSAA sample count - int swap_interval; // the preferred swap interval (ignored on some platforms) - bool high_dpi; // whether the rendering canvas is full-resolution on HighDPI displays - bool fullscreen; // whether the window should be created in fullscreen mode - bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) - const char* window_title; // the window title as UTF-8 encoded string - bool enable_clipboard; // enable clipboard access, default is false - int clipboard_size; // max size of clipboard content in bytes - bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false - int max_dropped_files; // max number of dropped files to process (default: 1) - int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048) - sapp_icon_desc icon; // the initial window icon to set - sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) - sapp_logger logger; // logging callback override (default: NO LOGGING!) - - /* backend-specific options */ - int gl_major_version; // override GL major and minor version (the default GL version is 3.2) - int gl_minor_version; - bool win32_console_utf8; // if true, set the output console codepage to UTF-8 - bool win32_console_create; // if true, attach stdout/stderr to a new console window - bool win32_console_attach; // if true, attach stdout/stderr to parent process - const char* html5_canvas_name; // the name (id) of the HTML5 canvas element, default is "canvas" - bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked - bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames - bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention - bool html5_ask_leave_site; // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) - bool ios_keyboard_resizes_canvas; // if true, showing the iOS keyboard shrinks the canvas -} sapp_desc; - -/* HTML5 specific: request and response structs for - asynchronously loading dropped-file content. -*/ -typedef enum sapp_html5_fetch_error { - SAPP_HTML5_FETCH_ERROR_NO_ERROR, - SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, - SAPP_HTML5_FETCH_ERROR_OTHER, -} sapp_html5_fetch_error; - -typedef struct sapp_html5_fetch_response { - bool succeeded; // true if the loading operation has succeeded - sapp_html5_fetch_error error_code; - int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) - sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) - sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) - void* user_data; // user-provided user data pointer -} sapp_html5_fetch_response; - -typedef struct sapp_html5_fetch_request { - int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 - void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) - sapp_range buffer; // ptr/size of a memory buffer to load the data into - void* user_data; // optional userdata pointer -} sapp_html5_fetch_request; - -/* - sapp_mouse_cursor - - Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) -*/ -typedef enum sapp_mouse_cursor { - SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor - SAPP_MOUSECURSOR_ARROW, - SAPP_MOUSECURSOR_IBEAM, - SAPP_MOUSECURSOR_CROSSHAIR, - SAPP_MOUSECURSOR_POINTING_HAND, - SAPP_MOUSECURSOR_RESIZE_EW, - SAPP_MOUSECURSOR_RESIZE_NS, - SAPP_MOUSECURSOR_RESIZE_NWSE, - SAPP_MOUSECURSOR_RESIZE_NESW, - SAPP_MOUSECURSOR_RESIZE_ALL, - SAPP_MOUSECURSOR_NOT_ALLOWED, - _SAPP_MOUSECURSOR_NUM, -} sapp_mouse_cursor; - -/* user-provided functions */ -extern sapp_desc sokol_main(int argc, char* argv[]); - -/* returns true after sokol-app has been initialized */ -SOKOL_APP_API_DECL bool sapp_isvalid(void); -/* returns the current framebuffer width in pixels */ -SOKOL_APP_API_DECL int sapp_width(void); -/* same as sapp_width(), but returns float */ -SOKOL_APP_API_DECL float sapp_widthf(void); -/* returns the current framebuffer height in pixels */ -SOKOL_APP_API_DECL int sapp_height(void); -/* same as sapp_height(), but returns float */ -SOKOL_APP_API_DECL float sapp_heightf(void); -/* get default framebuffer color pixel format */ -SOKOL_APP_API_DECL int sapp_color_format(void); -/* get default framebuffer depth pixel format */ -SOKOL_APP_API_DECL int sapp_depth_format(void); -/* get default framebuffer sample count */ -SOKOL_APP_API_DECL int sapp_sample_count(void); -/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ -SOKOL_APP_API_DECL bool sapp_high_dpi(void); -/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ -SOKOL_APP_API_DECL float sapp_dpi_scale(void); -/* show or hide the mobile device onscreen keyboard */ -SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); -/* return true if the mobile device onscreen keyboard is currently shown */ -SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); -/* query fullscreen mode */ -SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); -/* toggle fullscreen mode */ -SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL void sapp_show_mouse(bool show); -/* show or hide the mouse cursor */ -SOKOL_APP_API_DECL bool sapp_mouse_shown(void); -/* enable/disable mouse-pointer-lock mode */ -SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); -/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ -SOKOL_APP_API_DECL bool sapp_mouse_locked(void); -/* set mouse cursor type */ -SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); -/* get current mouse cursor type */ -SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); -/* return the userdata pointer optionally provided in sapp_desc */ -SOKOL_APP_API_DECL void* sapp_userdata(void); -/* return a copy of the sapp_desc structure */ -SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); -/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ -SOKOL_APP_API_DECL void sapp_request_quit(void); -/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ -SOKOL_APP_API_DECL void sapp_cancel_quit(void); -/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ -SOKOL_APP_API_DECL void sapp_quit(void); -/* call from inside event callback to consume the current event (don't forward to platform) */ -SOKOL_APP_API_DECL void sapp_consume_event(void); -/* get the current frame counter (for comparison with sapp_event.frame_count) */ -SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); -/* get an averaged/smoothed frame duration in seconds */ -SOKOL_APP_API_DECL double sapp_frame_duration(void); -/* write string into clipboard */ -SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); -/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ -SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void); -/* set the window title (only on desktop platforms) */ -SOKOL_APP_API_DECL void sapp_set_window_title(const char* str); -/* set the window icon (only on Windows and Linux) */ -SOKOL_APP_API_DECL void sapp_set_icon(const sapp_icon_desc* icon_desc); -/* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */ -SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void); -/* gets the dropped file paths */ -SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); - -/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ -SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); - -/* EGL: get EGLDisplay object */ -SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); -/* EGL: get EGLContext object */ -SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); - -/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ -SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); -/* HTML5: get byte size of a dropped file */ -SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); -/* HTML5: asynchronously load the content of a dropped file */ -SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); - -/* Metal: get bridged pointer to Metal device object */ -SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); -/* Metal: get bridged pointer to this frame's renderpass descriptor */ -SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); -/* Metal: get bridged pointer to current drawable */ -SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); -/* macOS: get bridged pointer to macOS NSWindow */ -SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); -/* iOS: get bridged pointer to iOS UIWindow */ -SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); - -/* D3D11: get pointer to ID3D11Device object */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); -/* D3D11: get pointer to ID3D11DeviceContext object */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void); -/* D3D11: get pointer to IDXGISwapChain object */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_swap_chain(void); -/* D3D11: get pointer to ID3D11RenderTargetView object */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void); -/* D3D11: get pointer to ID3D11DepthStencilView */ -SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); -/* Win32: get the HWND window handle */ -SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); - -/* WebGPU: get WGPUDevice handle */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); -/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); -/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); -/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ -SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); - -/* Android: get native activity handle */ -SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); - -#ifdef __cplusplus -} /* extern "C" */ - -/* reference-based equivalents for C++ */ -inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } - -#endif - -// this WinRT specific hack is required when wWinMain is in a static library -#if defined(_MSC_VER) && defined(UNICODE) -#include -#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -#pragma comment(linker, "/include:wWinMain") -#endif -#endif - -#endif // SOKOL_APP_INCLUDED - -// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ -// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ -// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ -// -// >>implementation -#ifdef SOKOL_APP_IMPL -#define SOKOL_APP_IMPL_INCLUDED (1) - -#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) -#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" -#endif - -#include // malloc, free -#include // memset -#include // size_t -#include // roundf - -/* check if the config defines are alright */ -#if defined(__APPLE__) - // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting - #if !defined(__cplusplus) - #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) - #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" - #endif - #endif - #define _SAPP_APPLE (1) - #include - #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE - /* MacOS */ - #define _SAPP_MACOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) - #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") - #endif - #else - /* iOS or iOS Simulator */ - #define _SAPP_IOS (1) - #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) - #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") - #endif - #endif -#elif defined(__EMSCRIPTEN__) - /* emscripten (asm.js or wasm) */ - #define _SAPP_EMSCRIPTEN (1) - #if !defined(SOKOL_GLES3) && !defined(SOKOL_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) */ - #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) - #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") - #endif -#elif defined(__linux__) || defined(__unix__) - /* Linux */ - #define _SAPP_LINUX (1) - #if defined(SOKOL_GLCORE33) - #if !defined(SOKOL_FORCE_EGL) - #define _SAPP_GLX (1) - #endif - #elif !defined(SOKOL_GLES3) - #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3") - #endif -#else -#error "sokol_app.h: Unknown platform" -#endif - -#ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL -#endif -#ifndef SOKOL_DEBUG - #ifndef NDEBUG - #define SOKOL_DEBUG - #endif -#endif -#ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) -#endif -#ifndef SOKOL_UNREACHABLE - #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) -#endif - -#ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) || defined(__clang__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif -#endif -#ifndef _SOKOL_UNUSED - #define _SOKOL_UNUSED(x) (void)(x) -#endif - -#if defined(_SAPP_APPLE) - #if defined(SOKOL_METAL) - #import - #import - #endif - #if defined(_SAPP_MACOS) - #if !defined(SOKOL_METAL) - #ifndef GL_SILENCE_DEPRECATION - #define GL_SILENCE_DEPRECATION - #endif - #include - #endif - #elif defined(_SAPP_IOS) - #import - #if !defined(SOKOL_METAL) - #import - #endif - #endif - #include - #include -#elif defined(_SAPP_EMSCRIPTEN) - #if defined(SOKOL_WGPU) - #include - #endif - #include - #include -#elif defined(_SAPP_WIN32) - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ - #pragma warning(disable:4054) /* 'type cast': from function pointer */ - #pragma warning(disable:4055) /* 'type cast': from data pointer */ - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ - #endif - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #ifndef NOMINMAX - #define NOMINMAX - #endif - #include - #include - #include - #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem - #if defined(SOKOL_WIN32_FORCE_MAIN) - #pragma comment (linker, "/subsystem:console") - #else - #pragma comment (linker, "/subsystem:windows") - #endif - #endif - #include /* freopen_s() */ - #include /* wcslen() */ - - #pragma comment (lib, "kernel32") - #pragma comment (lib, "user32") - #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ - #pragma comment (lib, "gdi32") - #if defined(SOKOL_D3D11) - #pragma comment (lib, "dxgi") - #pragma comment (lib, "d3d11") - #endif - - #if defined(SOKOL_D3D11) - #ifndef D3D11_NO_HELPERS - #define D3D11_NO_HELPERS - #endif - #include - #include - // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it - #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) - #endif - #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ - #define WM_MOUSEHWHEEL (0x020E) - #endif - #ifndef WM_DPICHANGED - #define WM_DPICHANGED (0x02E0) - #endif -#elif defined(_SAPP_ANDROID) - #include - #include - #include - #include - #include - #include -#elif defined(_SAPP_LINUX) - #define GL_GLEXT_PROTOTYPES - #include - #include - #include - #include - #include - #include - #include - #include - #include /* XC_* font cursors */ - #include /* CARD32 */ - #if !defined(_SAPP_GLX) - #include - #endif - #include /* dlopen, dlsym, dlclose */ - #include /* LONG_MAX */ - #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ - #include -#endif - -// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ -// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ -// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ -// -// >>frame timing -#define _SAPP_RING_NUM_SLOTS (256) -typedef struct { - int head; - int tail; - double buf[_SAPP_RING_NUM_SLOTS]; -} _sapp_ring_t; - -_SOKOL_PRIVATE int _sapp_ring_idx(int i) { - return i % _SAPP_RING_NUM_SLOTS; -} - -_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { - ring->head = 0; - ring->tail = 0; -} - -_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { - return _sapp_ring_idx(ring->head + 1) == ring->tail; -} - -_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { - return ring->head == ring->tail; -} - -_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { - int count; - if (ring->head >= ring->tail) { - count = ring->head - ring->tail; - } - else { - count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; - } - SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); - return count; -} - -_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { - SOKOL_ASSERT(!_sapp_ring_full(ring)); - ring->buf[ring->head] = val; - ring->head = _sapp_ring_idx(ring->head + 1); -} - -_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { - SOKOL_ASSERT(!_sapp_ring_empty(ring)); - double val = ring->buf[ring->tail]; - ring->tail = _sapp_ring_idx(ring->tail + 1); - return val; -} - -/* - NOTE: - - Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? - A: The value appears to be highly unstable during the first few - seconds, sometimes several frames are dropped in sequence, or - switch between 120 and 60 Hz for a few frames. Simply measuring - and averaging the frame time yielded a more stable frame duration. - Maybe switching to CVDisplayLink would yield better results. - Until then just measure the time. -*/ -typedef struct { - #if defined(_SAPP_APPLE) - struct { - mach_timebase_info_data_t timebase; - uint64_t start; - } mach; - #elif defined(_SAPP_EMSCRIPTEN) - // empty - #elif defined(_SAPP_WIN32) - struct { - LARGE_INTEGER freq; - LARGE_INTEGER start; - } win; - #else // Linux, Android, ... - #ifdef CLOCK_MONOTONIC - #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC - #else - // on some embedded platforms, CLOCK_MONOTONIC isn't defined - #define _SAPP_CLOCK_MONOTONIC (1) - #endif - struct { - uint64_t start; - } posix; - #endif -} _sapp_timestamp_t; - -_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { - int64_t q = value / denom; - int64_t r = value % denom; - return q * numer + r * numer / denom; -} - -_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { - #if defined(_SAPP_APPLE) - mach_timebase_info(&ts->mach.timebase); - ts->mach.start = mach_absolute_time(); - #elif defined(_SAPP_EMSCRIPTEN) - (void)ts; - #elif defined(_SAPP_WIN32) - QueryPerformanceFrequency(&ts->win.freq); - QueryPerformanceCounter(&ts->win.start); - #else - struct timespec tspec; - clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); - ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; - #endif -} - -_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { - #if defined(_SAPP_APPLE) - const uint64_t traw = mach_absolute_time() - ts->mach.start; - const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); - return (double)now / 1000000000.0; - #elif defined(_SAPP_EMSCRIPTEN) - (void)ts; - SOKOL_ASSERT(false); - return 0.0; - #elif defined(_SAPP_WIN32) - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); - return (double)now / 1000000000.0; - #else - struct timespec tspec; - clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); - const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; - return (double)now / 1000000000.0; - #endif -} - -typedef struct { - double last; - double accum; - double avg; - int spike_count; - int num; - _sapp_timestamp_t timestamp; - _sapp_ring_t ring; -} _sapp_timing_t; - -_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { - t->last = 0.0; - t->accum = 0.0; - t->spike_count = 0; - t->num = 0; - _sapp_ring_init(&t->ring); -} - -_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { - t->avg = 1.0 / 60.0; // dummy value until first actual value is available - _sapp_timing_reset(t); - _sapp_timestamp_init(&t->timestamp); -} - -_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { - // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) - double min_dur = 0.0; - double max_dur = 0.1; - // if we have enough samples for a useful average, use a much tighter 'valid window' - if (_sapp_ring_full(&t->ring)) { - min_dur = t->avg * 0.8; - max_dur = t->avg * 1.2; - } - if ((dur < min_dur) || (dur > max_dur)) { - t->spike_count++; - // if there have been many spikes in a row, the display refresh rate - // might have changed, so a timing reset is needed - if (t->spike_count > 20) { - _sapp_timing_reset(t); - } - return; - } - if (_sapp_ring_full(&t->ring)) { - double old_val = _sapp_ring_dequeue(&t->ring); - t->accum -= old_val; - t->num -= 1; - } - _sapp_ring_enqueue(&t->ring, dur); - t->accum += dur; - t->num += 1; - SOKOL_ASSERT(t->num > 0); - t->avg = t->accum / t->num; - t->spike_count = 0; -} - -_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { - t->last = 0.0; -} - -_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { - const double now = _sapp_timestamp_now(&t->timestamp); - if (t->last > 0.0) { - double dur = now - t->last; - _sapp_timing_put(t, dur); - } - t->last = now; -} - -_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { - if (t->last > 0.0) { - double dur = now - t->last; - _sapp_timing_put(t, dur); - } - t->last = now; -} - -_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { - return t->avg; -} - -// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██████ ██ ██ ██ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ██ ██████ ██████ ██ ███████ -// -// >> structs -#if defined(_SAPP_MACOS) -@interface _sapp_macos_app_delegate : NSObject -@end -@interface _sapp_macos_window : NSWindow -@end -@interface _sapp_macos_window_delegate : NSObject -@end -#if defined(SOKOL_METAL) - @interface _sapp_macos_view : MTKView - @end -#elif defined(SOKOL_GLCORE33) - @interface _sapp_macos_view : NSOpenGLView - - (void)timerFired:(id)sender; - @end -#endif // SOKOL_GLCORE33 - -typedef struct { - uint32_t flags_changed_store; - uint8_t mouse_buttons; - NSWindow* window; - NSTrackingArea* tracking_area; - id keyup_monitor; - _sapp_macos_app_delegate* app_dlg; - _sapp_macos_window_delegate* win_dlg; - _sapp_macos_view* view; - NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; - #if defined(SOKOL_METAL) - id mtl_device; - #endif -} _sapp_macos_t; - -#endif // _SAPP_MACOS - -#if defined(_SAPP_IOS) - -@interface _sapp_app_delegate : NSObject -@end -@interface _sapp_textfield_dlg : NSObject -- (void)keyboardWasShown:(NSNotification*)notif; -- (void)keyboardWillBeHidden:(NSNotification*)notif; -- (void)keyboardDidChangeFrame:(NSNotification*)notif; -@end -#if defined(SOKOL_METAL) - @interface _sapp_ios_view : MTKView; - @end -#else - @interface _sapp_ios_view : GLKView - @end -#endif - -typedef struct { - UIWindow* window; - _sapp_ios_view* view; - UITextField* textfield; - _sapp_textfield_dlg* textfield_dlg; - #if defined(SOKOL_METAL) - UIViewController* view_ctrl; - id mtl_device; - #else - GLKViewController* view_ctrl; - EAGLContext* eagl_ctx; - #endif - bool suspended; -} _sapp_ios_t; - -#endif // _SAPP_IOS - -#if defined(_SAPP_EMSCRIPTEN) - -#if defined(SOKOL_WGPU) -typedef struct { - int state; - WGPUDevice device; - WGPUSwapChain swapchain; - WGPUTextureFormat render_format; - WGPUTexture msaa_tex; - WGPUTexture depth_stencil_tex; - WGPUTextureView swapchain_view; - WGPUTextureView msaa_view; - WGPUTextureView depth_stencil_view; -} _sapp_wgpu_t; -#endif - -typedef struct { - bool textfield_created; - bool wants_show_keyboard; - bool wants_hide_keyboard; - bool mouse_lock_requested; - uint16_t mouse_buttons; - #if defined(SOKOL_WGPU) - _sapp_wgpu_t wgpu; - #endif -} _sapp_emsc_t; -#endif // _SAPP_EMSCRIPTEN - -#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) -typedef struct { - ID3D11Device* device; - ID3D11DeviceContext* device_context; - ID3D11Texture2D* rt; - ID3D11RenderTargetView* rtv; - ID3D11Texture2D* msaa_rt; - ID3D11RenderTargetView* msaa_rtv; - ID3D11Texture2D* ds; - ID3D11DepthStencilView* dsv; - DXGI_SWAP_CHAIN_DESC swap_chain_desc; - IDXGISwapChain* swap_chain; - IDXGIDevice1* dxgi_device; - bool use_dxgi_frame_stats; - UINT sync_refresh_count; -} _sapp_d3d11_t; -#endif - -#if defined(_SAPP_WIN32) - -#ifndef DPI_ENUMS_DECLARED -typedef enum PROCESS_DPI_AWARENESS -{ - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 -} PROCESS_DPI_AWARENESS; -typedef enum MONITOR_DPI_TYPE { - MDT_EFFECTIVE_DPI = 0, - MDT_ANGULAR_DPI = 1, - MDT_RAW_DPI = 2, - MDT_DEFAULT = MDT_EFFECTIVE_DPI -} MONITOR_DPI_TYPE; -#endif /*DPI_ENUMS_DECLARED*/ - -typedef struct { - bool aware; - float content_scale; - float window_scale; - float mouse_scale; -} _sapp_win32_dpi_t; - -typedef struct { - HWND hwnd; - HMONITOR hmonitor; - HDC dc; - HICON big_icon; - HICON small_icon; - HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; - UINT orig_codepage; - LONG mouse_locked_x, mouse_locked_y; - RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed - bool is_win10_or_greater; - bool in_create_window; - bool iconified; - bool mouse_tracked; - uint8_t mouse_capture_mask; - _sapp_win32_dpi_t dpi; - bool raw_input_mousepos_valid; - LONG raw_input_mousepos_x; - LONG raw_input_mousepos_y; - uint8_t raw_input_data[256]; -} _sapp_win32_t; - -#if defined(SOKOL_GLCORE33) -#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_NO_ACCELERATION_ARB 0x2025 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_SAMPLES_ARB 0x2042 -#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define ERROR_INVALID_VERSION_ARB 0x2095 -#define ERROR_INVALID_PROFILE_ARB 0x2096 -#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 -typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); -typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); -typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); -typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); -typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); -typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); -typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); -typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); -typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); - -typedef struct { - HINSTANCE opengl32; - HGLRC gl_ctx; - PFN_wglCreateContext CreateContext; - PFN_wglDeleteContext DeleteContext; - PFN_wglGetProcAddress GetProcAddress; - PFN_wglGetCurrentDC GetCurrentDC; - PFN_wglMakeCurrent MakeCurrent; - PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; - PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; - PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; - PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - bool ext_swap_control; - bool arb_multisample; - bool arb_pixel_format; - bool arb_create_context; - bool arb_create_context_profile; - HWND msg_hwnd; - HDC msg_dc; -} _sapp_wgl_t; -#endif // SOKOL_GLCORE33 - -#endif // _SAPP_WIN32 - -#if defined(_SAPP_ANDROID) -typedef enum { - _SOKOL_ANDROID_MSG_CREATE, - _SOKOL_ANDROID_MSG_RESUME, - _SOKOL_ANDROID_MSG_PAUSE, - _SOKOL_ANDROID_MSG_FOCUS, - _SOKOL_ANDROID_MSG_NO_FOCUS, - _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, - _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, - _SOKOL_ANDROID_MSG_DESTROY, -} _sapp_android_msg_t; - -typedef struct { - pthread_t thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - int read_from_main_fd; - int write_from_main_fd; -} _sapp_android_pt_t; - -typedef struct { - ANativeWindow* window; - AInputQueue* input; -} _sapp_android_resources_t; - -typedef struct { - ANativeActivity* activity; - _sapp_android_pt_t pt; - _sapp_android_resources_t pending; - _sapp_android_resources_t current; - ALooper* looper; - bool is_thread_started; - bool is_thread_stopping; - bool is_thread_stopped; - bool has_created; - bool has_resumed; - bool has_focus; - EGLConfig config; - EGLDisplay display; - EGLContext context; - EGLSurface surface; -} _sapp_android_t; - -#endif // _SAPP_ANDROID - -#if defined(_SAPP_LINUX) - -#define _SAPP_X11_XDND_VERSION (5) - -#define GLX_VENDOR 1 -#define GLX_RGBA_BIT 0x00000001 -#define GLX_WINDOW_BIT 0x00000001 -#define GLX_DRAWABLE_TYPE 0x8010 -#define GLX_RENDER_TYPE 0x8011 -#define GLX_DOUBLEBUFFER 5 -#define GLX_RED_SIZE 8 -#define GLX_GREEN_SIZE 9 -#define GLX_BLUE_SIZE 10 -#define GLX_ALPHA_SIZE 11 -#define GLX_DEPTH_SIZE 12 -#define GLX_STENCIL_SIZE 13 -#define GLX_SAMPLES 0x186a1 -#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define GLX_CONTEXT_FLAGS_ARB 0x2094 - -typedef XID GLXWindow; -typedef XID GLXDrawable; -typedef struct __GLXFBConfig* GLXFBConfig; -typedef struct __GLXcontext* GLXContext; -typedef void (*__GLXextproc)(void); - -typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); -typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); -typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); -typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); -typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); -typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); -typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); -typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); -typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); -typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); -typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); -typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); -typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); -typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); - -typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); -typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); - -typedef struct { - bool available; - int major_opcode; - int event_base; - int error_base; - int major; - int minor; -} _sapp_xi_t; - -typedef struct { - int version; - Window source; - Atom format; - Atom XdndAware; - Atom XdndEnter; - Atom XdndPosition; - Atom XdndStatus; - Atom XdndActionCopy; - Atom XdndDrop; - Atom XdndFinished; - Atom XdndSelection; - Atom XdndTypeList; - Atom text_uri_list; -} _sapp_xdnd_t; - -typedef struct { - uint8_t mouse_buttons; - Display* display; - int screen; - Window root; - Colormap colormap; - Window window; - Cursor hidden_cursor; - Cursor cursors[_SAPP_MOUSECURSOR_NUM]; - int window_state; - float dpi; - unsigned char error_code; - Atom UTF8_STRING; - Atom WM_PROTOCOLS; - Atom WM_DELETE_WINDOW; - Atom WM_STATE; - Atom NET_WM_NAME; - Atom NET_WM_ICON_NAME; - Atom NET_WM_ICON; - Atom NET_WM_STATE; - Atom NET_WM_STATE_FULLSCREEN; - _sapp_xi_t xi; - _sapp_xdnd_t xdnd; -} _sapp_x11_t; - -#if defined(_SAPP_GLX) - -typedef struct { - void* libgl; - int major; - int minor; - int event_base; - int error_base; - GLXContext ctx; - GLXWindow window; - - // GLX 1.3 functions - PFNGLXGETFBCONFIGSPROC GetFBConfigs; - PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; - PFNGLXGETCLIENTSTRINGPROC GetClientString; - PFNGLXQUERYEXTENSIONPROC QueryExtension; - PFNGLXQUERYVERSIONPROC QueryVersion; - PFNGLXDESTROYCONTEXTPROC DestroyContext; - PFNGLXMAKECURRENTPROC MakeCurrent; - PFNGLXSWAPBUFFERSPROC SwapBuffers; - PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; - PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; - PFNGLXCREATEWINDOWPROC CreateWindow; - PFNGLXDESTROYWINDOWPROC DestroyWindow; - - // GLX 1.4 and extension functions - PFNGLXGETPROCADDRESSPROC GetProcAddress; - PFNGLXGETPROCADDRESSPROC GetProcAddressARB; - PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; - PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; - PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - - // extension availability - bool EXT_swap_control; - bool MESA_swap_control; - bool ARB_multisample; - bool ARB_create_context; - bool ARB_create_context_profile; -} _sapp_glx_t; - -#else - -typedef struct { - EGLDisplay display; - EGLContext context; - EGLSurface surface; -} _sapp_egl_t; - -#endif // _SAPP_GLX - -#endif // _SAPP_LINUX - -/* helper macros */ -#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) -#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) - -#define _SAPP_MAX_TITLE_LENGTH (128) -#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) -#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) -/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ -#define _SAPP_PIXELFORMAT_RGBA8 (23) -#define _SAPP_PIXELFORMAT_BGRA8 (28) -#define _SAPP_PIXELFORMAT_DEPTH (42) -#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (43) - -#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) - // this is ARC compatible - #if defined(__cplusplus) - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } - #else - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } - #endif -#else - #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } -#endif - -typedef struct { - bool enabled; - int buf_size; - char* buffer; -} _sapp_clipboard_t; - -typedef struct { - bool enabled; - int max_files; - int max_path_length; - int num_files; - int buf_size; - char* buffer; -} _sapp_drop_t; - -typedef struct { - float x, y; - float dx, dy; - bool shown; - bool locked; - bool pos_valid; - sapp_mouse_cursor current_cursor; -} _sapp_mouse_t; - -typedef struct { - sapp_desc desc; - bool valid; - bool fullscreen; - bool first_frame; - bool init_called; - bool cleanup_called; - bool quit_requested; - bool quit_ordered; - bool event_consumed; - bool html5_ask_leave_site; - bool onscreen_keyboard_shown; - int window_width; - int window_height; - int framebuffer_width; - int framebuffer_height; - int sample_count; - int swap_interval; - float dpi_scale; - uint64_t frame_count; - _sapp_timing_t timing; - sapp_event event; - _sapp_mouse_t mouse; - _sapp_clipboard_t clipboard; - _sapp_drop_t drop; - sapp_icon_desc default_icon_desc; - uint32_t* default_icon_pixels; - #if defined(_SAPP_MACOS) - _sapp_macos_t macos; - #elif defined(_SAPP_IOS) - _sapp_ios_t ios; - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_t emsc; - #elif defined(_SAPP_WIN32) - _sapp_win32_t win32; - #if defined(SOKOL_D3D11) - _sapp_d3d11_t d3d11; - #elif defined(SOKOL_GLCORE33) - _sapp_wgl_t wgl; - #endif - #elif defined(_SAPP_ANDROID) - _sapp_android_t android; - #elif defined(_SAPP_LINUX) - _sapp_x11_t x11; - #if defined(_SAPP_GLX) - _sapp_glx_t glx; - #else - _sapp_egl_t egl; - #endif - #endif - char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; - char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */ - wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; /* UTF-32 or UCS-2 */ - sapp_keycode keycodes[SAPP_MAX_KEYCODES]; -} _sapp_t; -static _sapp_t _sapp; - -// ██ ██████ ██████ ██████ ██ ███ ██ ██████ -// ██ ██ ██ ██ ██ ██ ████ ██ ██ -// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ -// -// >>logging -#if defined(SOKOL_DEBUG) -#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, -static const char* _sapp_log_messages[] = { - _SAPP_LOG_ITEMS -}; -#undef _SAPP_LOGITEM_XMACRO -#endif // SOKOL_DEBUG - -#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) -#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) -#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) -#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) - -static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { - if (_sapp.desc.logger.func) { - const char* filename = 0; - #if defined(SOKOL_DEBUG) - filename = __FILE__; - if (0 == msg) { - msg = _sapp_log_messages[log_item]; - } - #endif - _sapp.desc.logger.func("sapp", log_level, log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); - } - else { - // for log level PANIC it would be 'undefined behaviour' to continue - if (log_level == 0) { - abort(); - } - } -} - -// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ -// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ -// -// >>memory -_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { - SOKOL_ASSERT(ptr && (size > 0)); - memset(ptr, 0, size); -} - -_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { - SOKOL_ASSERT(size > 0); - void* ptr; - if (_sapp.desc.allocator.alloc) { - ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data); - } - else { - ptr = malloc(size); - } - if (0 == ptr) { - _SAPP_PANIC(MALLOC_FAILED); - } - return ptr; -} - -_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { - void* ptr = _sapp_malloc(size); - _sapp_clear(ptr, size); - return ptr; -} - -_SOKOL_PRIVATE void _sapp_free(void* ptr) { - if (_sapp.desc.allocator.free) { - _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data); - } - else { - free(ptr); - } -} - -// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ █████ ██ ██████ █████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ -// -// >>helpers -_SOKOL_PRIVATE void _sapp_call_init(void) { - if (_sapp.desc.init_cb) { - _sapp.desc.init_cb(); - } - else if (_sapp.desc.init_userdata_cb) { - _sapp.desc.init_userdata_cb(_sapp.desc.user_data); - } - _sapp.init_called = true; -} - -_SOKOL_PRIVATE void _sapp_call_frame(void) { - if (_sapp.init_called && !_sapp.cleanup_called) { - if (_sapp.desc.frame_cb) { - _sapp.desc.frame_cb(); - } - else if (_sapp.desc.frame_userdata_cb) { - _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); - } - } -} - -_SOKOL_PRIVATE void _sapp_call_cleanup(void) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.cleanup_cb) { - _sapp.desc.cleanup_cb(); - } - else if (_sapp.desc.cleanup_userdata_cb) { - _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); - } - _sapp.cleanup_called = true; - } -} - -_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { - if (!_sapp.cleanup_called) { - if (_sapp.desc.event_cb) { - _sapp.desc.event_cb(e); - } - else if (_sapp.desc.event_userdata_cb) { - _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); - } - } - if (_sapp.event_consumed) { - _sapp.event_consumed = false; - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { - SOKOL_ASSERT(_sapp.drop.buffer); - SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); - int offset = index * _sapp.drop.max_path_length; - SOKOL_ASSERT(offset < _sapp.drop.buf_size); - return &_sapp.drop.buffer[offset]; -} - -/* Copy a string into a fixed size buffer with guaranteed zero- - termination. - - Return false if the string didn't fit into the buffer and had to be clamped. - - FIXME: Currently UTF-8 strings might become invalid if the string - is clamped, because the last zero-byte might be written into - the middle of a multi-byte sequence. -*/ -_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { - SOKOL_ASSERT(src && dst && (max_len > 0)); - char* const end = &(dst[max_len-1]); - char c = 0; - for (int i = 0; i < max_len; i++) { - c = *src; - if (c != 0) { - src++; - } - *dst++ = c; - } - /* truncated? */ - if (c != 0) { - *end = 0; - return false; - } - else { - return true; - } -} - -_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { - SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); - sapp_desc res = *desc; - res.sample_count = _sapp_def(res.sample_count, 1); - res.swap_interval = _sapp_def(res.swap_interval, 1); - // NOTE: can't patch the default for gl_major_version and gl_minor_version - // independently, because a desired version 4.0 would be patched to 4.2 - // (or expressed differently: zero is a valid value for gl_minor_version - // and can't be used to indicate 'default') - if (0 == res.gl_major_version) { - res.gl_major_version = 3; - res.gl_minor_version = 2; - } - res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas"); - res.clipboard_size = _sapp_def(res.clipboard_size, 8192); - res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); - res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); - res.window_title = _sapp_def(res.window_title, "sokol_app"); - return res; -} - -_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { - SOKOL_ASSERT(desc); - SOKOL_ASSERT(desc->width >= 0); - SOKOL_ASSERT(desc->height >= 0); - SOKOL_ASSERT(desc->sample_count >= 0); - SOKOL_ASSERT(desc->swap_interval >= 0); - SOKOL_ASSERT(desc->clipboard_size >= 0); - SOKOL_ASSERT(desc->max_dropped_files >= 0); - SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); - _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); - _sapp.desc = _sapp_desc_defaults(desc); - _sapp.first_frame = true; - // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this - _sapp.window_width = _sapp.desc.width; - _sapp.window_height = _sapp.desc.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp.sample_count = _sapp.desc.sample_count; - _sapp.swap_interval = _sapp.desc.swap_interval; - _sapp.html5_canvas_selector[0] = '#'; - _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); - _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; - _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; - _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; - if (_sapp.clipboard.enabled) { - _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; - _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); - } - _sapp.drop.enabled = _sapp.desc.enable_dragndrop; - if (_sapp.drop.enabled) { - _sapp.drop.max_files = _sapp.desc.max_dropped_files; - _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; - _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; - _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); - } - _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); - _sapp.desc.window_title = _sapp.window_title; - _sapp.dpi_scale = 1.0f; - _sapp.fullscreen = _sapp.desc.fullscreen; - _sapp.mouse.shown = true; - _sapp_timing_init(&_sapp.timing); -} - -_SOKOL_PRIVATE void _sapp_discard_state(void) { - if (_sapp.clipboard.enabled) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - _sapp_free((void*)_sapp.clipboard.buffer); - } - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - _sapp_free((void*)_sapp.drop.buffer); - } - if (_sapp.default_icon_pixels) { - _sapp_free((void*)_sapp.default_icon_pixels); - } - _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); -} - -_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { - _sapp_clear(&_sapp.event, sizeof(_sapp.event)); - _sapp.event.type = type; - _sapp.event.frame_count = _sapp.frame_count; - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - _sapp.event.window_width = _sapp.window_width; - _sapp.event.window_height = _sapp.window_height; - _sapp.event.framebuffer_width = _sapp.framebuffer_width; - _sapp.event.framebuffer_height = _sapp.framebuffer_height; - _sapp.event.mouse_x = _sapp.mouse.x; - _sapp.event.mouse_y = _sapp.mouse.y; - _sapp.event.mouse_dx = _sapp.mouse.dx; - _sapp.event.mouse_dy = _sapp.mouse.dy; -} - -_SOKOL_PRIVATE bool _sapp_events_enabled(void) { - /* only send events when an event callback is set, and the init function was called */ - return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; -} - -_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { - if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { - return _sapp.keycodes[scan_code]; - } - else { - return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { - if (_sapp.drop.enabled) { - SOKOL_ASSERT(_sapp.drop.buffer); - _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); - } -} - -_SOKOL_PRIVATE void _sapp_frame(void) { - if (_sapp.first_frame) { - _sapp.first_frame = false; - _sapp_call_init(); - } - _sapp_call_frame(); - _sapp.frame_count++; -} - -_SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) { - SOKOL_ASSERT(desc->width > 0); - SOKOL_ASSERT(desc->height > 0); - SOKOL_ASSERT(desc->pixels.ptr != 0); - SOKOL_ASSERT(desc->pixels.size > 0); - const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t); - if (wh_size != desc->pixels.size) { - _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); - return false; - } - return true; -} - -_SOKOL_PRIVATE int _sapp_image_bestmatch(const sapp_image_desc image_descs[], int num_images, int width, int height) { - int least_diff = 0x7FFFFFFF; - int least_index = 0; - for (int i = 0; i < num_images; i++) { - int diff = (image_descs[i].width * image_descs[i].height) - (width * height); - if (diff < 0) { - diff = -diff; - } - if (diff < least_diff) { - least_diff = diff; - least_index = i; - } - } - return least_index; -} - -_SOKOL_PRIVATE int _sapp_icon_num_images(const sapp_icon_desc* desc) { - int index = 0; - for (; index < SAPP_MAX_ICONIMAGES; index++) { - if (0 == desc->images[index].pixels.ptr) { - break; - } - } - return index; -} - -_SOKOL_PRIVATE bool _sapp_validate_icon_desc(const sapp_icon_desc* desc, int num_images) { - SOKOL_ASSERT(num_images <= SAPP_MAX_ICONIMAGES); - for (int i = 0; i < num_images; i++) { - const sapp_image_desc* img_desc = &desc->images[i]; - if (!_sapp_image_validate(img_desc)) { - return false; - } - } - return true; -} - -_SOKOL_PRIVATE void _sapp_setup_default_icon(void) { - SOKOL_ASSERT(0 == _sapp.default_icon_pixels); - - const int num_icons = 3; - const int icon_sizes[3] = { 16, 32, 64 }; // must be multiple of 8! - - // allocate a pixel buffer for all icon pixels - int all_num_pixels = 0; - for (int i = 0; i < num_icons; i++) { - all_num_pixels += icon_sizes[i] * icon_sizes[i]; - } - _sapp.default_icon_pixels = (uint32_t*) _sapp_malloc_clear((size_t)all_num_pixels * sizeof(uint32_t)); - - // initialize default_icon_desc struct - uint32_t* dst = _sapp.default_icon_pixels; - const uint32_t* dst_end = dst + all_num_pixels; - (void)dst_end; // silence unused warning in release mode - for (int i = 0; i < num_icons; i++) { - const int dim = (int) icon_sizes[i]; - const int num_pixels = dim * dim; - sapp_image_desc* img_desc = &_sapp.default_icon_desc.images[i]; - img_desc->width = dim; - img_desc->height = dim; - img_desc->pixels.ptr = dst; - img_desc->pixels.size = (size_t)num_pixels * sizeof(uint32_t); - dst += num_pixels; - } - SOKOL_ASSERT(dst == dst_end); - - // Amstrad CPC font 'S' - const uint8_t tile[8] = { - 0x3C, - 0x66, - 0x60, - 0x3C, - 0x06, - 0x66, - 0x3C, - 0x00, - }; - // rainbow colors - const uint32_t colors[8] = { - 0xFF4370FF, - 0xFF26A7FF, - 0xFF58EEFF, - 0xFF57E1D4, - 0xFF65CC9C, - 0xFF6ABB66, - 0xFFF5A542, - 0xFFC2577E, - }; - dst = _sapp.default_icon_pixels; - const uint32_t blank = 0x00FFFFFF; - const uint32_t shadow = 0xFF000000; - for (int i = 0; i < num_icons; i++) { - const int dim = icon_sizes[i]; - SOKOL_ASSERT((dim % 8) == 0); - const int scale = dim / 8; - for (int ty = 0, y = 0; ty < 8; ty++) { - const uint32_t color = colors[ty]; - for (int sy = 0; sy < scale; sy++, y++) { - uint8_t bits = tile[ty]; - for (int tx = 0, x = 0; tx < 8; tx++, bits<<=1) { - uint32_t pixel = (0 == (bits & 0x80)) ? blank : color; - for (int sx = 0; sx < scale; sx++, x++) { - SOKOL_ASSERT(dst < dst_end); - *dst++ = pixel; - } - } - } - } - } - SOKOL_ASSERT(dst == dst_end); - - // right shadow - dst = _sapp.default_icon_pixels; - for (int i = 0; i < num_icons; i++) { - const int dim = icon_sizes[i]; - for (int y = 0; y < dim; y++) { - uint32_t prev_color = blank; - for (int x = 0; x < dim; x++) { - const int dst_index = y * dim + x; - const uint32_t cur_color = dst[dst_index]; - if ((cur_color == blank) && (prev_color != blank)) { - dst[dst_index] = shadow; - } - prev_color = cur_color; - } - } - dst += dim * dim; - } - SOKOL_ASSERT(dst == dst_end); - - // bottom shadow - dst = _sapp.default_icon_pixels; - for (int i = 0; i < num_icons; i++) { - const int dim = icon_sizes[i]; - for (int x = 0; x < dim; x++) { - uint32_t prev_color = blank; - for (int y = 0; y < dim; y++) { - const int dst_index = y * dim + x; - const uint32_t cur_color = dst[dst_index]; - if ((cur_color == blank) && (prev_color != blank)) { - dst[dst_index] = shadow; - } - prev_color = cur_color; - } - } - dst += dim * dim; - } - SOKOL_ASSERT(dst == dst_end); -} - -// █████ ██████ ██████ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██████ ██████ ██ █████ -// ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ███████ ███████ -// -// >>apple -#if defined(_SAPP_APPLE) - -#if __has_feature(objc_arc) -#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } -#else -#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } -#endif - -// ███ ███ █████ ██████ ██████ ███████ -// ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ ███████ ██ ██ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██████ ██████ ███████ -// -// >>macos -#if defined(_SAPP_MACOS) - -_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { - _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; - _sapp.keycodes[0x12] = SAPP_KEYCODE_1; - _sapp.keycodes[0x13] = SAPP_KEYCODE_2; - _sapp.keycodes[0x14] = SAPP_KEYCODE_3; - _sapp.keycodes[0x15] = SAPP_KEYCODE_4; - _sapp.keycodes[0x17] = SAPP_KEYCODE_5; - _sapp.keycodes[0x16] = SAPP_KEYCODE_6; - _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; - _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; - _sapp.keycodes[0x19] = SAPP_KEYCODE_9; - _sapp.keycodes[0x00] = SAPP_KEYCODE_A; - _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; - _sapp.keycodes[0x08] = SAPP_KEYCODE_C; - _sapp.keycodes[0x02] = SAPP_KEYCODE_D; - _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; - _sapp.keycodes[0x03] = SAPP_KEYCODE_F; - _sapp.keycodes[0x05] = SAPP_KEYCODE_G; - _sapp.keycodes[0x04] = SAPP_KEYCODE_H; - _sapp.keycodes[0x22] = SAPP_KEYCODE_I; - _sapp.keycodes[0x26] = SAPP_KEYCODE_J; - _sapp.keycodes[0x28] = SAPP_KEYCODE_K; - _sapp.keycodes[0x25] = SAPP_KEYCODE_L; - _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; - _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; - _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; - _sapp.keycodes[0x23] = SAPP_KEYCODE_P; - _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01] = SAPP_KEYCODE_S; - _sapp.keycodes[0x11] = SAPP_KEYCODE_T; - _sapp.keycodes[0x20] = SAPP_KEYCODE_U; - _sapp.keycodes[0x09] = SAPP_KEYCODE_V; - _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; - _sapp.keycodes[0x07] = SAPP_KEYCODE_X; - _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; - _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x77] = SAPP_KEYCODE_END; - _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; - _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; -} - -_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - if (_sapp.macos.keyup_monitor != nil) { - [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; - // NOTE: removeMonitor also releases the object - _sapp.macos.keyup_monitor = nil; - } - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); - _SAPP_OBJC_RELEASE(_sapp.macos.view); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); - #endif - _SAPP_OBJC_RELEASE(_sapp.macos.window); -} - -// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) -@interface NSCursor() -+ (id)_windowResizeNorthWestSouthEastCursor; -+ (id)_windowResizeNorthEastSouthWestCursor; -+ (id)_windowResizeNorthSouthCursor; -+ (id)_windowResizeEastWestCursor; -@end - -_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { - _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug - _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; - _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; -} - -_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_macos_init_keytable(); - [NSApplication sharedApplication]; - - // set the application dock icon as early as possible, otherwise - // the dummy icon will be visible for a short time - sapp_set_icon(&_sapp.desc.icon); - _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init]; - NSApp.delegate = _sapp.macos.app_dlg; - - // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: - NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { - if ([event modifierFlags] & NSEventModifierFlagCommand) { - [[NSApp keyWindow] sendEvent:event]; - } - return event; - }; - _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; - - [NSApp run]; - // NOTE: [NSApp run] never returns, instead cleanup code - // must be put into applicationWillTerminate -} - -/* MacOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_macos_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) { - const NSEventModifierFlags f = (ev == nil) ? NSEvent.modifierFlags : ev.modifierFlags; - const NSUInteger b = NSEvent.pressedMouseButtons; - uint32_t m = 0; - if (f & NSEventModifierFlagShift) { - m |= SAPP_MODIFIER_SHIFT; - } - if (f & NSEventModifierFlagControl) { - m |= SAPP_MODIFIER_CTRL; - } - if (f & NSEventModifierFlagOption) { - m |= SAPP_MODIFIER_ALT; - } - if (f & NSEventModifierFlagCommand) { - m |= SAPP_MODIFIER_SUPER; - } - if (0 != (b & (1<<0))) { - m |= SAPP_MODIFIER_LMB; - } - if (0 != (b & (1<<1))) { - m |= SAPP_MODIFIER_RMB; - } - if (0 != (b & (1<<2))) { - m |= SAPP_MODIFIER_MMB; - } - return m; -} - -_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mod; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -/* NOTE: unlike the iOS version of this function, the macOS version - can dynamically update the DPI scaling factor when a window is moved - between HighDPI / LowDPI screens. -*/ -_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; - } - else { - _sapp.dpi_scale = 1.0f; - } - const NSRect bounds = [_sapp.macos.view bounds]; - _sapp.window_width = (int)roundf(bounds.size.width); - _sapp.window_height = (int)roundf(bounds.size.height); - #if defined(SOKOL_METAL) - _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); - const CGSize fb_size = _sapp.macos.view.drawableSize; - const int cur_fb_width = (int)roundf(fb_size.width); - const int cur_fb_height = (int)roundf(fb_size.height); - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - #elif defined(SOKOL_GLCORE33) - const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); - const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - _sapp.framebuffer_width = cur_fb_width; - _sapp.framebuffer_height = cur_fb_height; - #endif - if (_sapp.framebuffer_width == 0) { - _sapp.framebuffer_width = 1; - } - if (_sapp.framebuffer_height == 0) { - _sapp.framebuffer_height = 1; - } - if (_sapp.window_width == 0) { - _sapp.window_width = 1; - } - if (_sapp.window_height == 0) { - _sapp.window_height = 1; - } - if (dim_changed) { - #if defined(SOKOL_METAL) - CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.macos.view.drawableSize = drawable_size; - #else - // nothing to do for GL? - #endif - if (!_sapp.first_frame) { - _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { - /* NOTE: the _sapp.fullscreen flag is also notified by the - windowDidEnterFullscreen / windowDidExitFullscreen - event handlers - */ - _sapp.fullscreen = !_sapp.fullscreen; - [_sapp.macos.window toggleFullScreen:nil]; -} - -_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { - @autoreleasepool { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; - [pasteboard setString:@(str) forType:NSPasteboardTypeString]; - } -} - -_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.buffer); - @autoreleasepool { - _sapp.clipboard.buffer[0] = 0; - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { - return _sapp.clipboard.buffer; - } - NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; - if (!str) { - return _sapp.clipboard.buffer; - } - _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - } - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { - [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; -} - -_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nspoint(NSPoint mouse_pos, bool clear_dxdy) { - if (!_sapp.mouse.locked) { - float new_x = mouse_pos.x * _sapp.dpi_scale; - float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; - 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; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_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) { - CGDisplayShowCursor(kCGDirectMainDisplay); - } - else { - CGDisplayHideCursor(kCGDirectMainDisplay); - } -} - -_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - /* - NOTE that this code doesn't warp the mouse cursor to the window - center as everybody else does it. This lead to a spike in the - *second* mouse-moved event after the warp happened. The - mouse centering doesn't seem to be required (mouse-moved events - are reported correctly even when the cursor is at an edge of the screen). - - NOTE also that the hide/show of the mouse cursor should properly - stack with calls to sapp_show_mouse() - */ - if (_sapp.mouse.locked) { - CGAssociateMouseAndMouseCursorPosition(NO); - [NSCursor hide]; - } - else { - [NSCursor unhide]; - CGAssociateMouseAndMouseCursorPosition(YES); - } -} - -_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { - // show/hide cursor only if visibility status has changed (required because show/hide stacks) - if (shown != _sapp.mouse.shown) { - if (shown) { - [NSCursor unhide]; - } - else { - [NSCursor hide]; - } - } - // update cursor type - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (_sapp.macos.cursors[cursor]) { - [_sapp.macos.cursors[cursor] set]; - } - else { - [[NSCursor arrowCursor] set]; - } -} - -_SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) { - NSDockTile* dock_tile = NSApp.dockTile; - const int wanted_width = (int) dock_tile.size.width; - const int wanted_height = (int) dock_tile.size.height; - const int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, wanted_width, wanted_height); - const sapp_image_desc* img_desc = &icon_desc->images[img_index]; - - CGColorSpaceRef cg_color_space = CGColorSpaceCreateDeviceRGB(); - CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)img_desc->pixels.ptr, (CFIndex)img_desc->pixels.size); - CGDataProviderRef cg_data_provider = CGDataProviderCreateWithCFData(cf_data); - CGImageRef cg_img = CGImageCreate( - (size_t)img_desc->width, // width - (size_t)img_desc->height, // height - 8, // bitsPerComponent - 32, // bitsPerPixel - (size_t)img_desc->width * 4,// bytesPerRow - cg_color_space, // space - kCGImageAlphaLast | kCGImageByteOrderDefault, // bitmapInfo - cg_data_provider, // provider - NULL, // decode - false, // shouldInterpolate - kCGRenderingIntentDefault); - CFRelease(cf_data); - CGDataProviderRelease(cg_data_provider); - CGColorSpaceRelease(cg_color_space); - - NSImage* ns_image = [[NSImage alloc] initWithCGImage:cg_img size:dock_tile.size]; - dock_tile.contentView = [NSImageView imageViewWithImage:ns_image]; - [dock_tile display]; - _SAPP_OBJC_RELEASE(ns_image); - CGImageRelease(cg_img); -} - -_SOKOL_PRIVATE void _sapp_macos_frame(void) { - _sapp_frame(); - if (_sapp.quit_requested || _sapp.quit_ordered) { - [_sapp.macos.window performClose:nil]; - } -} - -@implementation _sapp_macos_app_delegate -- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { - _SOKOL_UNUSED(aNotification); - _sapp_macos_init_cursors(); - if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { - // use 4/5 of screen size as default size - NSRect screen_rect = NSScreen.mainScreen.frame; - if (_sapp.window_width == 0) { - _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); - } - if (_sapp.window_height == 0) { - _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f); - } - } - const NSUInteger style = - NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | - NSWindowStyleMaskResizable; - NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); - _sapp.macos.window = [[_sapp_macos_window alloc] - initWithContentRect:window_rect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO]; - _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate - _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; - _sapp.macos.window.acceptsMouseMovedEvents = YES; - _sapp.macos.window.restorable = YES; - - _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; - _sapp.macos.window.delegate = _sapp.macos.win_dlg; - #if defined(SOKOL_METAL) - NSInteger max_fps = 60; - #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) - if (@available(macOS 12.0, *)) { - max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; - } - #endif - _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.macos.view = [[_sapp_macos_view alloc] init]; - [_sapp.macos.view updateTrackingAreas]; - _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.macos.view.device = _sapp.macos.mtl_device; - _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; - _sapp.macos.view.autoResizeDrawable = false; - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; - #elif defined(SOKOL_GLCORE33) - NSOpenGLPixelFormatAttribute attrs[32]; - int i = 0; - attrs[i++] = NSOpenGLPFAAccelerated; - attrs[i++] = NSOpenGLPFADoubleBuffer; - attrs[i++] = NSOpenGLPFAOpenGLProfile; - const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; - switch(glVersion) { - case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; - case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; - case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; - default: - _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); - } - attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; - attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; - if (_sapp.sample_count > 1) { - attrs[i++] = NSOpenGLPFAMultisample; - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; - attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; - } - else { - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; - } - attrs[i++] = 0; - NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - SOKOL_ASSERT(glpixelformat_obj != nil); - - _sapp.macos.view = [[_sapp_macos_view alloc] - initWithFrame:window_rect - pixelFormat:glpixelformat_obj]; - _SAPP_OBJC_RELEASE(glpixelformat_obj); - [_sapp.macos.view updateTrackingAreas]; - if (_sapp.desc.high_dpi) { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; - } - else { - [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; - } - - _sapp.macos.window.contentView = _sapp.macos.view; - [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; - - NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 - target:_sapp.macos.view - selector:@selector(timerFired:) - userInfo:nil - repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; - timer_obj = nil; - #endif - [_sapp.macos.window center]; - _sapp.valid = true; - if (_sapp.fullscreen) { - /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ - [_sapp.macos.window toggleFullScreen:self]; - } - NSApp.activationPolicy = NSApplicationActivationPolicyRegular; - [NSApp activateIgnoringOtherApps:YES]; - [_sapp.macos.window makeKeyAndOrderFront:nil]; - _sapp_macos_update_dimensions(); - [NSEvent setMouseCoalescingEnabled:NO]; -} - -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { - _SOKOL_UNUSED(sender); - return YES; -} - -- (void)applicationWillTerminate:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_call_cleanup(); - _sapp_macos_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_macos_window_delegate -- (BOOL)windowShouldClose:(id)sender { - _SOKOL_UNUSED(sender); - /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a chance to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - return YES; - } - else { - return NO; - } -} - -- (void)windowDidResize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_update_dimensions(); -} - -- (void)windowDidChangeScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_timing_reset(&_sapp.timing); - _sapp_macos_update_dimensions(); -} - -- (void)windowDidMiniaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); -} - -- (void)windowDidDeminiaturize:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); -} - -- (void)windowDidBecomeKey:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_FOCUSED); -} - -- (void)windowDidResignKey:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp_macos_app_event(SAPP_EVENTTYPE_UNFOCUSED); -} - -- (void)windowDidEnterFullScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp.fullscreen = true; -} - -- (void)windowDidExitFullScreen:(NSNotification*)notification { - _SOKOL_UNUSED(notification); - _sapp.fullscreen = false; -} -@end - -@implementation _sapp_macos_window -- (instancetype)initWithContentRect:(NSRect)contentRect - styleMask:(NSWindowStyleMask)style - backing:(NSBackingStoreType)backingStoreType - defer:(BOOL)flag { - if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; - #endif - } - return self; -} - -- (NSDragOperation)draggingEntered:(id)sender { - return NSDragOperationCopy; -} - -- (NSDragOperation)draggingUpdated:(id)sender { - return NSDragOperationCopy; -} - -- (BOOL)performDragOperation:(id)sender { - #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - NSPasteboard *pboard = [sender draggingPasteboard]; - if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; - bool drop_failed = false; - for (int i = 0; i < _sapp.drop.num_files; i++) { - NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; - if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - drop_failed = true; - break; - } - } - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_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); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } - return YES; - } - #endif - return NO; -} -@end - -@implementation _sapp_macos_view -#if defined(SOKOL_GLCORE33) -- (void)timerFired:(id)sender { - _SOKOL_UNUSED(sender); - [self setNeedsDisplay:YES]; -} -- (void)prepareOpenGL { - [super prepareOpenGL]; - GLint swapInt = 1; - NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; - [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; - [ctx makeCurrentContext]; -} -#endif - -_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { - /* - - NOTE: late event polling temporarily out-commented to check if this - causes infrequent and almost impossible to reproduce problems with the - window close events, see: - https://github.com/floooh/sokol/pull/483#issuecomment-805148815 - - - const NSEventMask mask = NSEventMaskLeftMouseDown | - NSEventMaskLeftMouseUp| - NSEventMaskRightMouseDown | - NSEventMaskRightMouseUp | - NSEventMaskMouseMoved | - NSEventMaskLeftMouseDragged | - NSEventMaskRightMouseDragged | - NSEventMaskMouseEntered | - NSEventMaskMouseExited | - NSEventMaskKeyDown | - NSEventMaskKeyUp | - NSEventMaskCursorUpdate | - NSEventMaskScrollWheel | - NSEventMaskTabletPoint | - NSEventMaskTabletProximity | - NSEventMaskOtherMouseDown | - NSEventMaskOtherMouseUp | - NSEventMaskOtherMouseDragged | - NSEventMaskPressure | - NSEventMaskDirectTouch; - @autoreleasepool { - for (;;) { - // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, - // see: https://github.com/floooh/sokol/issues/486 - NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; - if (event == nil) { - break; - } - [NSApp sendEvent:event]; - } - } - */ -} - -- (void)drawRect:(NSRect)rect { - _SOKOL_UNUSED(rect); - _sapp_timing_measure(&_sapp.timing); - /* Catch any last-moment input events */ - _sapp_macos_poll_input_events(); - @autoreleasepool { - _sapp_macos_frame(); - } - #if !defined(SOKOL_METAL) - [[_sapp.macos.view openGLContext] flushBuffer]; - #endif -} - -- (BOOL)isOpaque { - return YES; -} -- (BOOL)canBecomeKeyView { - return YES; -} -- (BOOL)acceptsFirstResponder { - return YES; -} -- (void)updateTrackingAreas { - if (_sapp.macos.tracking_area != nil) { - [self removeTrackingArea:_sapp.macos.tracking_area]; - _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); - } - const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingEnabledDuringMouseDrag | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect | - NSTrackingAssumeInside; - _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - [self addTrackingArea:_sapp.macos.tracking_area]; - [super updateTrackingAreas]; -} - -// 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_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 - */ - if (0 == _sapp.macos.mouse_buttons) { - _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); - } -} -- (void)mouseExited:(NSEvent*)event { - _sapp_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_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.0f) || (_sapp_absf(dy) > 0.0f)) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_macos_mods(event); - _sapp.event.scroll_x = dx; - _sapp.event.scroll_y = dy; - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyDown:(NSEvent*)event { - if (_sapp_events_enabled()) { - _sapp_gl_make_current(); - const uint32_t mods = _sapp_macos_mods(event); - const sapp_keycode key_code = _sapp_translate_key(event.keyCode); - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); - const NSString* chars = event.characters; - const NSUInteger len = chars.length; - if (len > 0) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = mods; - for (NSUInteger i = 0; i < len; i++) { - const unichar codepoint = [chars characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) { - continue; - } - _sapp.event.char_code = codepoint; - _sapp.event.key_repeat = event.isARepeat; - _sapp_call_event(&_sapp.event); - } - } - /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ - if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} -- (void)keyUp:(NSEvent*)event { - _sapp_gl_make_current(); - _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, - _sapp_translate_key(event.keyCode), - event.isARepeat, - _sapp_macos_mods(event)); -} -- (void)flagsChanged:(NSEvent*)event { - const uint32_t old_f = _sapp.macos.flags_changed_store; - const uint32_t new_f = (uint32_t)event.modifierFlags; - _sapp.macos.flags_changed_store = new_f; - sapp_keycode key_code = SAPP_KEYCODE_INVALID; - bool down = false; - if ((new_f ^ old_f) & NSEventModifierFlagShift) { - key_code = SAPP_KEYCODE_LEFT_SHIFT; - down = 0 != (new_f & NSEventModifierFlagShift); - } - if ((new_f ^ old_f) & NSEventModifierFlagControl) { - key_code = SAPP_KEYCODE_LEFT_CONTROL; - down = 0 != (new_f & NSEventModifierFlagControl); - } - if ((new_f ^ old_f) & NSEventModifierFlagOption) { - key_code = SAPP_KEYCODE_LEFT_ALT; - down = 0 != (new_f & NSEventModifierFlagOption); - } - if ((new_f ^ old_f) & NSEventModifierFlagCommand) { - key_code = SAPP_KEYCODE_LEFT_SUPER; - down = 0 != (new_f & NSEventModifierFlagCommand); - } - if (key_code != SAPP_KEYCODE_INVALID) { - _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, - key_code, - false, - _sapp_macos_mods(event)); - } -} -@end - -#endif // macOS - -// ██ ██████ ███████ -// ██ ██ ██ ██ -// ██ ██ ██ ███████ -// ██ ██ ██ ██ -// ██ ██████ ███████ -// -// >>ios -#if defined(_SAPP_IOS) - -_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { - // NOTE: it's safe to call [release] on a nil object - _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); - _SAPP_OBJC_RELEASE(_sapp.ios.textfield); - #if defined(SOKOL_METAL) - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); - #else - _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); - _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); - #endif - _SAPP_OBJC_RELEASE(_sapp.ios.view); - _SAPP_OBJC_RELEASE(_sapp.ios.window); -} - -_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { - _sapp_init_state(desc); - static int argc = 1; - static char* argv[] = { (char*)"sokol_app" }; - UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); -} - -/* iOS entry function */ -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_ios_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ - -_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - NSEnumerator* enumerator = event.allTouches.objectEnumerator; - UITouch* ios_touch; - while ((ios_touch = [enumerator nextObject])) { - if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { - CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; - sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; - cur_point->identifier = (uintptr_t) ios_touch; - cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; - cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; - cur_point->changed = [touches containsObject:ios_touch]; - } - } - if (_sapp.event.num_touches > 0) { - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); - _sapp.window_width = (int)roundf(screen_rect.size.width); - _sapp.window_height = (int)roundf(screen_rect.size.height); - int cur_fb_width, cur_fb_height; - #if defined(SOKOL_METAL) - const CGSize fb_size = _sapp.ios.view.drawableSize; - cur_fb_width = (int)roundf(fb_size.width); - cur_fb_height = (int)roundf(fb_size.height); - #else - cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); - cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); - #endif - const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || - (_sapp.framebuffer_height != cur_fb_height); - if (dim_changed) { - #if defined(SOKOL_METAL) - const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; - _sapp.ios.view.drawableSize = drawable_size; - #else - // nothing to do here, GLKView correctly respects the view's contentScaleFactor - #endif - if (!_sapp.first_frame) { - _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_ios_frame(void) { - _sapp_ios_update_dimensions(); - _sapp_frame(); -} - -_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { - /* if not happened yet, create an invisible text field */ - if (nil == _sapp.ios.textfield) { - _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; - _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; - _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; - _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; - _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; - _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; - _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; - _sapp.ios.textfield.hidden = YES; - _sapp.ios.textfield.text = @"x"; - _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; - [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; - - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWasShown:) - name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardWillBeHidden:) - name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg - selector:@selector(keyboardDidChangeFrame:) - name:UIKeyboardDidChangeFrameNotification object:nil]; - } - if (shown) { - /* setting the text field as first responder brings up the onscreen keyboard */ - [_sapp.ios.textfield becomeFirstResponder]; - } - else { - [_sapp.ios.textfield resignFirstResponder]; - } -} - -@implementation _sapp_app_delegate -- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - CGRect screen_rect = UIScreen.mainScreen.bounds; - _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; - _sapp.window_width = (int)roundf(screen_rect.size.width); - _sapp.window_height = (int)roundf(screen_rect.size.height); - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; - } - else { - _sapp.dpi_scale = 1.0f; - } - _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); - NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; - #if defined(SOKOL_METAL) - _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); - _sapp.ios.view = [[_sapp_ios_view alloc] init]; - _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.ios.view.device = _sapp.ios.mtl_device; - _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; - /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor - and automatically renders at Retina resolution. We'll disable - autoResize and instead do the resizing in _sapp_ios_update_dimensions() - */ - _sapp.ios.view.autoResizeDrawable = false; - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - _sapp.ios.view_ctrl = [[UIViewController alloc] init]; - _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #else - _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; - _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; - GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; - _sapp.ios.view.drawableMultisample = msaa; - _sapp.ios.view.context = _sapp.ios.eagl_ctx; - _sapp.ios.view.enableSetNeedsDisplay = NO; - _sapp.ios.view.userInteractionEnabled = YES; - _sapp.ios.view.multipleTouchEnabled = YES; - // on GLKView, contentScaleFactor appears to work just fine! - if (_sapp.desc.high_dpi) { - _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; - } - else { - _sapp.ios.view.contentScaleFactor = 1.0; - } - _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; - _sapp.ios.view_ctrl.view = _sapp.ios.view; - _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; - _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; - #endif - [_sapp.ios.window makeKeyAndVisible]; - - _sapp.valid = true; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - if (!_sapp.ios.suspended) { - _sapp.ios.suspended = true; - _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); - } -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - if (_sapp.ios.suspended) { - _sapp.ios.suspended = false; - _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); - } -} - -/* NOTE: this method will rarely ever be called, iOS application - which are terminated by the user are usually killed via signal 9 - by the operating system. -*/ -- (void)applicationWillTerminate:(UIApplication *)application { - _SOKOL_UNUSED(application); - _sapp_call_cleanup(); - _sapp_ios_discard_state(); - _sapp_discard_state(); -} -@end - -@implementation _sapp_textfield_dlg -- (void)keyboardWasShown:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = true; - /* query the keyboard's size, and modify the content view's size */ - if (_sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (void)keyboardWillBeHidden:(NSNotification*)notif { - _sapp.onscreen_keyboard_shown = false; - if (_sapp.desc.ios_keyboard_resizes_canvas) { - _sapp.ios.view.frame = UIScreen.mainScreen.bounds; - } -} -- (void)keyboardDidChangeFrame:(NSNotification*)notif { - /* this is for the case when the screen rotation changes while the keyboard is open */ - if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { - NSDictionary* info = notif.userInfo; - CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - CGRect view_frame = UIScreen.mainScreen.bounds; - view_frame.size.height -= kbd_h; - _sapp.ios.view.frame = view_frame; - } -} -- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { - if (_sapp_events_enabled()) { - const NSUInteger len = string.length; - if (len > 0) { - for (NSUInteger i = 0; i < len; i++) { - unichar c = [string characterAtIndex:i]; - if (c >= 32) { - /* ignore surrogates for now */ - if ((c < 0xD800) || (c > 0xDFFF)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = c; - _sapp_call_event(&_sapp.event); - } - } - if (c <= 32) { - sapp_keycode k = SAPP_KEYCODE_INVALID; - switch (c) { - case 10: k = SAPP_KEYCODE_ENTER; break; - case 32: k = SAPP_KEYCODE_SPACE; break; - default: break; - } - if (k != SAPP_KEYCODE_INVALID) { - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = k; - _sapp_call_event(&_sapp.event); - } - } - } - } - else { - /* this was a backspace */ - _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); - _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; - _sapp_call_event(&_sapp.event); - } - } - return NO; -} -@end - -@implementation _sapp_ios_view -- (void)drawRect:(CGRect)rect { - _SOKOL_UNUSED(rect); - _sapp_timing_measure(&_sapp.timing); - @autoreleasepool { - _sapp_ios_frame(); - } -} -- (BOOL)isOpaque { - return YES; -} -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); -} -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); -} -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); -} -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { - _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); -} -@end -#endif /* TARGET_OS_IPHONE */ - -#endif /* _SAPP_APPLE */ - -// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ -// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ -// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ -// -// >>emscripten -#if defined(_SAPP_EMSCRIPTEN) - -#if defined(EM_JS_DEPS) -EM_JS_DEPS(sokol_app, "$withStackSave,$allocateUTF8OnStack"); -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); - -/* this function is called from a JS event handler when the user hides - the onscreen keyboard pressing the 'dismiss keyboard key' -*/ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { - _sapp.onscreen_keyboard_shown = false; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { - if (_sapp.clipboard.enabled) { - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ -EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { - return _sapp.html5_ask_leave_site ? 1 : 0; -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { - if (!_sapp.drop.enabled) { - return; - } - if (num < 0) { - num = 0; - } - if (num > _sapp.drop.max_files) { - num = _sapp.drop.max_files; - } - _sapp.drop.num_files = num; - _sapp_clear_drop_buffer(); -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { - /* NOTE: name is only the filename part, not a path */ - if (!_sapp.drop.enabled) { - return; - } - if (0 == name) { - return; - } - SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); - if ((i < 0) || (i >= _sapp.drop.num_files)) { - return; - } - if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - _sapp.drop.num_files = 0; - } -} - -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y, int mods) { - if (!_sapp.drop.enabled) { - return; - } - if (0 == _sapp.drop.num_files) { - /* there was an error copying the filenames */ - _sapp_clear_drop_buffer(); - return; - - } - if (_sapp_events_enabled()) { - _sapp.mouse.x = (float)x * _sapp.dpi_scale; - _sapp.mouse.y = (float)y * _sapp.dpi_scale; - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - // 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; - _sapp_clear(&response, sizeof(response)); - response.succeeded = (0 != success); - response.error_code = (sapp_html5_fetch_error) error_code; - response.file_index = index; - response.data.ptr = buf_ptr; - response.data.size = fetched_size; - response.buffer.ptr = buf_ptr; - response.buffer.size = buf_size; - response.user_data = user_data; - callback(&response); -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* Javascript helper functions for mobile virtual keyboard input */ -EM_JS(void, sapp_js_create_textfield, (void), { - const _sapp_inp = document.createElement("input"); - _sapp_inp.type = "text"; - _sapp_inp.id = "_sokol_app_input_element"; - _sapp_inp.autocapitalize = "none"; - _sapp_inp.addEventListener("focusout", function(_sapp_event) { - __sapp_emsc_notify_keyboard_hidden() - - }); - document.body.append(_sapp_inp); -}); - -EM_JS(void, sapp_js_focus_textfield, (void), { - document.getElementById("_sokol_app_input_element").focus(); -}); - -EM_JS(void, sapp_js_unfocus_textfield, (void), { - document.getElementById("_sokol_app_input_element").blur(); -}); - -EM_JS(void, sapp_js_add_beforeunload_listener, (void), { - Module.sokol_beforeunload = (event) => { - if (__sapp_html5_get_ask_leave_site() != 0) { - event.preventDefault(); - event.returnValue = ' '; - } - }; - window.addEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { - window.removeEventListener('beforeunload', Module.sokol_beforeunload); -}); - -EM_JS(void, sapp_js_add_clipboard_listener, (void), { - Module.sokol_paste = (event) => { - const pasted_str = event.clipboardData.getData('text'); - withStackSave(() => { - const cstr = allocateUTF8OnStack(pasted_str); - __sapp_emsc_onpaste(cstr); - }); - }; - window.addEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_remove_clipboard_listener, (void), { - window.removeEventListener('paste', Module.sokol_paste); -}); - -EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { - const str = UTF8ToString(c_str); - const ta = document.createElement('textarea'); - ta.setAttribute('autocomplete', 'off'); - ta.setAttribute('autocorrect', 'off'); - ta.setAttribute('autocapitalize', 'off'); - ta.setAttribute('spellcheck', 'false'); - ta.style.left = -100 + 'px'; - ta.style.top = -100 + 'px'; - ta.style.height = 1; - ta.style.width = 1; - ta.value = str; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); -}); - -_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { - sapp_js_write_clipboard(str); -} - -EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { - Module.sokol_drop_files = []; - const canvas_name = UTF8ToString(canvas_name_cstr); - const canvas = document.getElementById(canvas_name); - Module.sokol_dragenter = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragleave = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_dragover = (event) => { - event.stopPropagation(); - event.preventDefault(); - }; - Module.sokol_drop = (event) => { - event.stopPropagation(); - event.preventDefault(); - const files = event.dataTransfer.files; - Module.sokol_dropped_files = files; - __sapp_emsc_begin_drop(files.length); - for (let i = 0; i < files.length; i++) { - withStackSave(() => { - const cstr = allocateUTF8OnStack(files[i].name); - __sapp_emsc_drop(i, cstr); - }); - } - 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, mods); - }; - canvas.addEventListener('dragenter', Module.sokol_dragenter, false); - canvas.addEventListener('dragleave', Module.sokol_dragleave, false); - canvas.addEventListener('dragover', Module.sokol_dragover, false); - canvas.addEventListener('drop', Module.sokol_drop, false); -}); - -EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { - \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F - const files = Module.sokol_dropped_files; - if ((index < 0) || (index >= files.length)) { - return 0; - } - else { - return files[index].size; - } -}); - -EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { - const reader = new FileReader(); - reader.onload = (loadEvent) => { - const content = loadEvent.target.result; - if (content.byteLength > buf_size) { - // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL - __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); - } - else { - HEAPU8.set(new Uint8Array(content), buf_ptr); - __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); - } - }; - reader.onerror = () => { - // SAPP_HTML5_FETCH_ERROR_OTHER - __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); - }; - \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F - const files = Module.sokol_dropped_files; - reader.readAsArrayBuffer(files[index]); -}); - -EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { - const canvas_name = UTF8ToString(canvas_name_cstr); - const canvas = document.getElementById(canvas_name); - canvas.removeEventListener('dragenter', Module.sokol_dragenter); - canvas.removeEventListener('dragleave', Module.sokol_dragleave); - canvas.removeEventListener('dragover', Module.sokol_dragover); - canvas.removeEventListener('drop', Module.sokol_drop); -}); - -/* called from the emscripten event handler to update the keyboard visibility - state, this must happen from an JS input event handler, otherwise - the request will be ignored by the browser -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { - if (_sapp.emsc.wants_show_keyboard) { - /* create input text field on demand */ - if (!_sapp.emsc.textfield_created) { - _sapp.emsc.textfield_created = true; - sapp_js_create_textfield(); - } - /* focus the text input field, this will bring up the keyboard */ - _sapp.onscreen_keyboard_shown = true; - _sapp.emsc.wants_show_keyboard = false; - sapp_js_focus_textfield(); - } - if (_sapp.emsc.wants_hide_keyboard) { - /* unfocus the text input field */ - if (_sapp.emsc.textfield_created) { - _sapp.onscreen_keyboard_shown = false; - _sapp.emsc.wants_hide_keyboard = false; - sapp_js_unfocus_textfield(); - } - } -} - -/* actually showing the onscreen keyboard must be initiated from a JS - input event handler, so we'll just keep track of the desired - state, and the actual state change will happen with the next input event -*/ -_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { - if (show) { - _sapp.emsc.wants_show_keyboard = true; - } - else { - _sapp.emsc.wants_hide_keyboard = true; - } -} - -EM_JS(void, sapp_js_init, (const char* c_str_target), { - // lookup and store canvas object by name - const target_str = UTF8ToString(c_str_target); - Module.sapp_emsc_target = document.getElementById(target_str); - if (!Module.sapp_emsc_target) { - console.log("sokol_app.h: invalid target:" + target_str); - } - if (!Module.sapp_emsc_target.requestPointerLock) { - console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); - } -}); - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = emsc_event->isActive; - return EM_TRUE; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - _sapp.mouse.locked = false; - _sapp.emsc.mouse_lock_requested = false; - return true; -} - -EM_JS(void, sapp_js_request_pointerlock, (void), { - if (Module.sapp_emsc_target) { - if (Module.sapp_emsc_target.requestPointerLock) { - Module.sapp_emsc_target.requestPointerLock(); - } - } -}); - -EM_JS(void, sapp_js_exit_pointerlock, (void), { - if (document.exitPointerLock) { - document.exitPointerLock(); - } -}); - -_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { - if (lock) { - /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ - _sapp.emsc.mouse_lock_requested = true; - } - else { - /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ - _sapp.emsc.mouse_lock_requested = false; - sapp_js_exit_pointerlock(); - } -} - -/* called from inside event handlers to check if mouse lock had been requested, - and if yes, actually enter mouse lock. -*/ -_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { - if (_sapp.emsc.mouse_lock_requested) { - _sapp.emsc.mouse_lock_requested = false; - sapp_js_request_pointerlock(); - } -} - -// set mouse cursor type -EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { - if (Module.sapp_emsc_target) { - let cursor; - if (shown === 0) { - cursor = "none"; - } - else switch (cursor_type) { - case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT - case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW - case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM - case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR - case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND - case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW - case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS - case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE - case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW - case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL - case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED - default: cursor = "auto"; break; - } - Module.sapp_emsc_target.style.cursor = cursor; - } -}); - -_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - sapp_js_set_cursor((int)cursor, shown ? 1 : 0); -} - -/* JS helper functions to update browser tab favicon */ -EM_JS(void, sapp_js_clear_favicon, (void), { - const link = document.getElementById('sokol-app-favicon'); - if (link) { - document.head.removeChild(link); - } -}); - -EM_JS(void, sapp_js_set_favicon, (int w, int h, const uint8_t* pixels), { - const canvas = document.createElement('canvas'); - canvas.width = w; - canvas.height = h; - const ctx = canvas.getContext('2d'); - const img_data = ctx.createImageData(w, h); - img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); - ctx.putImageData(img_data, 0, 0); - const new_link = document.createElement('link'); - new_link.id = 'sokol-app-favicon'; - new_link.rel = 'shortcut icon'; - new_link.href = canvas.toDataURL(); - document.head.appendChild(new_link); -}); - -_SOKOL_PRIVATE void _sapp_emsc_set_icon(const sapp_icon_desc* icon_desc, int num_images) { - SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); - sapp_js_clear_favicon(); - // find the best matching image candidate for 16x16 pixels - int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, 16, 16); - const sapp_image_desc* img_desc = &icon_desc->images[img_index]; - sapp_js_set_favicon(img_desc->width, img_desc->height, (const uint8_t*) img_desc->pixels.ptr); -} - -#if defined(SOKOL_WGPU) -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void); -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void); -#endif - -_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_button_mods(uint16_t buttons) { - uint32_t m = 0; - if (0 != (buttons & (1<<0))) { m |= SAPP_MODIFIER_LMB; } - if (0 != (buttons & (1<<1))) { m |= SAPP_MODIFIER_RMB; } // not a bug - if (0 != (buttons & (1<<2))) { m |= SAPP_MODIFIER_MMB; } // not a bug - return m; -} - -_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_event_mods(const EmscriptenMouseEvent* ev) { - uint32_t m = 0; - if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } - if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } - if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } - if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } - m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); - return m; -} - -_SOKOL_PRIVATE uint32_t _sapp_emsc_key_event_mods(const EmscriptenKeyboardEvent* ev) { - uint32_t m = 0; - if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } - if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } - if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } - if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } - m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); - return m; -} - -_SOKOL_PRIVATE uint32_t _sapp_emsc_touch_event_mods(const EmscriptenTouchEvent* ev) { - uint32_t m = 0; - if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } - if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } - if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } - if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } - m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); - return m; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { - _SOKOL_UNUSED(event_type); - _SOKOL_UNUSED(user_data); - double w, h; - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - /* The above method might report zero when toggling HTML5 fullscreen, - in that case use the window's inner width reported by the - emscripten event. This works ok when toggling *into* fullscreen - but doesn't properly restore the previous canvas size when switching - back from fullscreen. - - In general, due to the HTML5's fullscreen API's flaky nature it is - recommended to use 'soft fullscreen' (stretching the WebGL canvas - over the browser windows client rect) with a CSS definition like this: - - position: absolute; - top: 0px; - left: 0px; - margin: 0px; - border: 0; - width: 100%; - height: 100%; - overflow: hidden; - display: block; - */ - if (w < 1.0) { - w = ui_event->windowInnerWidth; - } - else { - _sapp.window_width = (int)roundf(w); - } - if (h < 1.0) { - h = ui_event->windowInnerHeight; - } - else { - _sapp.window_height = (int)roundf(h); - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); - SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_WGPU) - /* on WebGPU: recreate size-dependent rendering surfaces */ - _sapp_emsc_wgpu_surfaces_discard(); - _sapp_emsc_wgpu_surfaces_create(); - #endif - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_RESIZED); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - _sapp.emsc.mouse_buttons = emsc_event->buttons; - if (_sapp.mouse.locked) { - _sapp.mouse.dx = (float) emsc_event->movementX; - _sapp.mouse.dy = (float) emsc_event->movementY; - } else { - float new_x = emsc_event->targetX * _sapp.dpi_scale; - float new_y = emsc_event->targetY * _sapp.dpi_scale; - if (_sapp.mouse.pos_valid) { - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } - if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { - sapp_event_type type; - bool is_button_event = false; - bool clear_dxdy = false; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_MOUSEDOWN: - type = SAPP_EVENTTYPE_MOUSE_DOWN; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEUP: - type = SAPP_EVENTTYPE_MOUSE_UP; - is_button_event = true; - break; - case EMSCRIPTEN_EVENT_MOUSEMOVE: - type = SAPP_EVENTTYPE_MOUSE_MOVE; - break; - case EMSCRIPTEN_EVENT_MOUSEENTER: - type = SAPP_EVENTTYPE_MOUSE_ENTER; - 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); - if (is_button_event) { - switch (emsc_event->button) { - case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; - case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; - case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; - default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; - } - } else { - _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; - } - _sapp_call_event(&_sapp.event); - } - // mouse lock can only be activated in mouse button events (not in move, enter or leave) - if (is_button_event) { - _sapp_emsc_update_mouse_lock_state(); - } - } - _sapp_emsc_update_keyboard_state(); - return true; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(user_data); - _sapp.emsc.mouse_buttons = emsc_event->mouse.buttons; - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(&emsc_event->mouse); - /* see https://github.com/floooh/sokol/issues/339 */ - float scale; - switch (emsc_event->deltaMode) { - case DOM_DELTA_PIXEL: scale = -0.04f; break; - case DOM_DELTA_LINE: scale = -1.33f; break; - case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess - default: scale = -0.1f; break; // shouldn't happen - } - _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; - _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; - _sapp_call_event(&_sapp.event); - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - return true; -} - -static struct { - const char* str; - sapp_keycode code; -} _sapp_emsc_keymap[] = { - { "Backspace", SAPP_KEYCODE_BACKSPACE }, - { "Tab", SAPP_KEYCODE_TAB }, - { "Enter", SAPP_KEYCODE_ENTER }, - { "ShiftLeft", SAPP_KEYCODE_LEFT_SHIFT }, - { "ShiftRight", SAPP_KEYCODE_RIGHT_SHIFT }, - { "ControlLeft", SAPP_KEYCODE_LEFT_CONTROL }, - { "ControlRight", SAPP_KEYCODE_RIGHT_CONTROL }, - { "AltLeft", SAPP_KEYCODE_LEFT_ALT }, - { "AltRight", SAPP_KEYCODE_RIGHT_ALT }, - { "Pause", SAPP_KEYCODE_PAUSE }, - { "CapsLock", SAPP_KEYCODE_CAPS_LOCK }, - { "Escape", SAPP_KEYCODE_ESCAPE }, - { "Space", SAPP_KEYCODE_SPACE }, - { "PageUp", SAPP_KEYCODE_PAGE_UP }, - { "PageDown", SAPP_KEYCODE_PAGE_DOWN }, - { "End", SAPP_KEYCODE_END }, - { "Home", SAPP_KEYCODE_HOME }, - { "ArrowLeft", SAPP_KEYCODE_LEFT }, - { "ArrowUp", SAPP_KEYCODE_UP }, - { "ArrowRight", SAPP_KEYCODE_RIGHT }, - { "ArrowDown", SAPP_KEYCODE_DOWN }, - { "PrintScreen", SAPP_KEYCODE_PRINT_SCREEN }, - { "Insert", SAPP_KEYCODE_INSERT }, - { "Delete", SAPP_KEYCODE_DELETE }, - { "Digit0", SAPP_KEYCODE_0 }, - { "Digit1", SAPP_KEYCODE_1 }, - { "Digit2", SAPP_KEYCODE_2 }, - { "Digit3", SAPP_KEYCODE_3 }, - { "Digit4", SAPP_KEYCODE_4 }, - { "Digit5", SAPP_KEYCODE_5 }, - { "Digit6", SAPP_KEYCODE_6 }, - { "Digit7", SAPP_KEYCODE_7 }, - { "Digit8", SAPP_KEYCODE_8 }, - { "Digit9", SAPP_KEYCODE_9 }, - { "KeyA", SAPP_KEYCODE_A }, - { "KeyB", SAPP_KEYCODE_B }, - { "KeyC", SAPP_KEYCODE_C }, - { "KeyD", SAPP_KEYCODE_D }, - { "KeyE", SAPP_KEYCODE_E }, - { "KeyF", SAPP_KEYCODE_F }, - { "KeyG", SAPP_KEYCODE_G }, - { "KeyH", SAPP_KEYCODE_H }, - { "KeyI", SAPP_KEYCODE_I }, - { "KeyJ", SAPP_KEYCODE_J }, - { "KeyK", SAPP_KEYCODE_K }, - { "KeyL", SAPP_KEYCODE_L }, - { "KeyM", SAPP_KEYCODE_M }, - { "KeyN", SAPP_KEYCODE_N }, - { "KeyO", SAPP_KEYCODE_O }, - { "KeyP", SAPP_KEYCODE_P }, - { "KeyQ", SAPP_KEYCODE_Q }, - { "KeyR", SAPP_KEYCODE_R }, - { "KeyS", SAPP_KEYCODE_S }, - { "KeyT", SAPP_KEYCODE_T }, - { "KeyU", SAPP_KEYCODE_U }, - { "KeyV", SAPP_KEYCODE_V }, - { "KeyW", SAPP_KEYCODE_W }, - { "KeyX", SAPP_KEYCODE_X }, - { "KeyY", SAPP_KEYCODE_Y }, - { "KeyZ", SAPP_KEYCODE_Z }, - { "MetaLeft", SAPP_KEYCODE_LEFT_SUPER }, - { "MetaRight", SAPP_KEYCODE_RIGHT_SUPER }, - { "Numpad0", SAPP_KEYCODE_KP_0 }, - { "Numpad1", SAPP_KEYCODE_KP_1 }, - { "Numpad2", SAPP_KEYCODE_KP_2 }, - { "Numpad3", SAPP_KEYCODE_KP_3 }, - { "Numpad4", SAPP_KEYCODE_KP_4 }, - { "Numpad5", SAPP_KEYCODE_KP_5 }, - { "Numpad6", SAPP_KEYCODE_KP_6 }, - { "Numpad7", SAPP_KEYCODE_KP_7 }, - { "Numpad8", SAPP_KEYCODE_KP_8 }, - { "Numpad9", SAPP_KEYCODE_KP_9 }, - { "NumpadMultiply", SAPP_KEYCODE_KP_MULTIPLY }, - { "NumpadAdd", SAPP_KEYCODE_KP_ADD }, - { "NumpadSubtract", SAPP_KEYCODE_KP_SUBTRACT }, - { "NumpadDecimal", SAPP_KEYCODE_KP_DECIMAL }, - { "NumpadDivide", SAPP_KEYCODE_KP_DIVIDE }, - { "F1", SAPP_KEYCODE_F1 }, - { "F2", SAPP_KEYCODE_F2 }, - { "F3", SAPP_KEYCODE_F3 }, - { "F4", SAPP_KEYCODE_F4 }, - { "F5", SAPP_KEYCODE_F5 }, - { "F6", SAPP_KEYCODE_F6 }, - { "F7", SAPP_KEYCODE_F7 }, - { "F8", SAPP_KEYCODE_F8 }, - { "F9", SAPP_KEYCODE_F9 }, - { "F10", SAPP_KEYCODE_F10 }, - { "F11", SAPP_KEYCODE_F11 }, - { "F12", SAPP_KEYCODE_F12 }, - { "NumLock", SAPP_KEYCODE_NUM_LOCK }, - { "ScrollLock", SAPP_KEYCODE_SCROLL_LOCK }, - { "Semicolon", SAPP_KEYCODE_SEMICOLON }, - { "Equal", SAPP_KEYCODE_EQUAL }, - { "Comma", SAPP_KEYCODE_COMMA }, - { "Minus", SAPP_KEYCODE_MINUS }, - { "Period", SAPP_KEYCODE_PERIOD }, - { "Slash", SAPP_KEYCODE_SLASH }, - { "Backquote", SAPP_KEYCODE_GRAVE_ACCENT }, - { "BracketLeft", SAPP_KEYCODE_LEFT_BRACKET }, - { "Backslash", SAPP_KEYCODE_BACKSLASH }, - { "BracketRight", SAPP_KEYCODE_RIGHT_BRACKET }, - { "Quote", SAPP_KEYCODE_GRAVE_ACCENT }, // FIXME: ??? - { 0, SAPP_KEYCODE_INVALID }, -}; - -_SOKOL_PRIVATE sapp_keycode _sapp_emsc_translate_key(const char* str) { - int i = 0; - const char* keystr; - while (( keystr = _sapp_emsc_keymap[i].str )) { - if (0 == strcmp(str, keystr)) { - return _sapp_emsc_keymap[i].code; - } - i += 1; - } - return SAPP_KEYCODE_INVALID; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - bool retval = true; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_KEYDOWN: - type = SAPP_EVENTTYPE_KEY_DOWN; - break; - case EMSCRIPTEN_EVENT_KEYUP: - type = SAPP_EVENTTYPE_KEY_UP; - break; - case EMSCRIPTEN_EVENT_KEYPRESS: - type = SAPP_EVENTTYPE_CHAR; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - bool send_keyup_followup = false; - _sapp_init_event(type); - _sapp.event.key_repeat = emsc_event->repeat; - _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); - if (type == SAPP_EVENTTYPE_CHAR) { - // FIXME: this doesn't appear to work on Android Chrome - _sapp.event.char_code = emsc_event->charCode; - /* workaround to make Cmd+V work on Safari */ - if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { - retval = false; - } - } - else { - if (0 != emsc_event->code[0]) { - // This code path is for desktop browsers which send untranslated 'physical' key code strings - // (which is what we actually want for key events) - _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); - } else { - // This code path is for mobile browsers which only send localized key code - // strings. Note that the translation will only work for a small subset - // of localization-agnostic keys (like Enter, arrow keys, etc...), but - // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) - _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); - } - - /* Special hack for macOS: if the Super key is pressed, macOS doesn't - send keyUp events. As a workaround, to prevent keys from - "sticking", we'll send a keyup event following a keydown - when the SUPER key is pressed - */ - if ((type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && - (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && - (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) - { - send_keyup_followup = true; - } - // only forward keys to the browser (can further be suppressed by sapp_consume_event()) - switch (_sapp.event.key_code) { - case SAPP_KEYCODE_WORLD_1: - case SAPP_KEYCODE_WORLD_2: - case SAPP_KEYCODE_ESCAPE: - case SAPP_KEYCODE_ENTER: - case SAPP_KEYCODE_TAB: - case SAPP_KEYCODE_BACKSPACE: - case SAPP_KEYCODE_INSERT: - case SAPP_KEYCODE_DELETE: - case SAPP_KEYCODE_RIGHT: - case SAPP_KEYCODE_LEFT: - case SAPP_KEYCODE_DOWN: - case SAPP_KEYCODE_UP: - case SAPP_KEYCODE_PAGE_UP: - case SAPP_KEYCODE_PAGE_DOWN: - case SAPP_KEYCODE_HOME: - case SAPP_KEYCODE_END: - case SAPP_KEYCODE_CAPS_LOCK: - case SAPP_KEYCODE_SCROLL_LOCK: - case SAPP_KEYCODE_NUM_LOCK: - case SAPP_KEYCODE_PRINT_SCREEN: - case SAPP_KEYCODE_PAUSE: - case SAPP_KEYCODE_F1: - case SAPP_KEYCODE_F2: - case SAPP_KEYCODE_F3: - case SAPP_KEYCODE_F4: - case SAPP_KEYCODE_F5: - case SAPP_KEYCODE_F6: - case SAPP_KEYCODE_F7: - case SAPP_KEYCODE_F8: - case SAPP_KEYCODE_F9: - case SAPP_KEYCODE_F10: - case SAPP_KEYCODE_F11: - case SAPP_KEYCODE_F12: - case SAPP_KEYCODE_F13: - case SAPP_KEYCODE_F14: - case SAPP_KEYCODE_F15: - case SAPP_KEYCODE_F16: - case SAPP_KEYCODE_F17: - case SAPP_KEYCODE_F18: - case SAPP_KEYCODE_F19: - case SAPP_KEYCODE_F20: - case SAPP_KEYCODE_F21: - case SAPP_KEYCODE_F22: - case SAPP_KEYCODE_F23: - case SAPP_KEYCODE_F24: - case SAPP_KEYCODE_F25: - case SAPP_KEYCODE_LEFT_SHIFT: - case SAPP_KEYCODE_LEFT_CONTROL: - case SAPP_KEYCODE_LEFT_ALT: - case SAPP_KEYCODE_LEFT_SUPER: - case SAPP_KEYCODE_RIGHT_SHIFT: - case SAPP_KEYCODE_RIGHT_CONTROL: - case SAPP_KEYCODE_RIGHT_ALT: - case SAPP_KEYCODE_RIGHT_SUPER: - case SAPP_KEYCODE_MENU: - /* consume the event */ - break; - default: - /* forward key to browser */ - retval = false; - break; - } - } - if (_sapp_call_event(&_sapp.event)) { - // event was consumed via sapp_consume_event() - retval = true; - } - if (send_keyup_followup) { - _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; - if (_sapp_call_event(&_sapp.event)) { - retval = true; - } - } - } - } - _sapp_emsc_update_keyboard_state(); - _sapp_emsc_update_mouse_lock_state(); - return retval; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(user_data); - bool retval = true; - if (_sapp_events_enabled()) { - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_TOUCHSTART: - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case EMSCRIPTEN_EVENT_TOUCHMOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case EMSCRIPTEN_EVENT_TOUCHEND: - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case EMSCRIPTEN_EVENT_TOUCHCANCEL: - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - type = SAPP_EVENTTYPE_INVALID; - retval = false; - break; - } - if (type != SAPP_EVENTTYPE_INVALID) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_emsc_touch_event_mods(emsc_event); - _sapp.event.num_touches = emsc_event->numTouches; - if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { - _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; - } - for (int i = 0; i < _sapp.event.num_touches; i++) { - const EmscriptenTouchPoint* src = &emsc_event->touches[i]; - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)src->identifier; - dst->pos_x = src->targetX * _sapp.dpi_scale; - dst->pos_y = src->targetY * _sapp.dpi_scale; - dst->changed = src->isChanged; - } - _sapp_call_event(&_sapp.event); - } - } - _sapp_emsc_update_keyboard_state(); - return retval; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_focus_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(emsc_event); - _SOKOL_UNUSED(user_data); - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FOCUSED); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_blur_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { - _SOKOL_UNUSED(emsc_type); - _SOKOL_UNUSED(emsc_event); - _SOKOL_UNUSED(user_data); - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_UNFOCUSED); - _sapp_call_event(&_sapp.event); - } - return true; -} - -#if defined(SOKOL_GLES3) -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { - _SOKOL_UNUSED(reserved); - _SOKOL_UNUSED(user_data); - sapp_event_type type; - switch (emsc_type) { - case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; - case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; - default: type = SAPP_EVENTTYPE_INVALID; break; - } - if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } - return true; -} - -_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { - EmscriptenWebGLContextAttributes attrs; - emscripten_webgl_init_context_attributes(&attrs); - attrs.alpha = _sapp.desc.alpha; - attrs.depth = true; - attrs.stencil = true; - attrs.antialias = _sapp.sample_count > 1; - attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; - attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; - attrs.enableExtensionsByDefault = true; - attrs.majorVersion = 2; - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); - // FIXME: error message? - emscripten_webgl_make_context_current(ctx); - - /* some WebGL extension are not enabled automatically by emscripten */ - emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); -} -#endif - -#if defined(SOKOL_WGPU) -#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) -#define _SAPP_EMSC_WGPU_STATE_READY (1) -#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) - -#if defined(__cplusplus) -extern "C" { -#endif -/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ -EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); - _sapp.emsc.wgpu.device = (WGPUDevice) device_id; - _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; - _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; -} -#if defined(__cplusplus) -} // extern "C" -#endif - -/* embedded JS function to handle all the asynchronous WebGPU setup */ -EM_JS(void, sapp_js_wgpu_init, (), { - WebGPU.initManagers(); - // FIXME: the extension activation must be more clever here - navigator.gpu.requestAdapter().then((adapter) => { - console.log("wgpu adapter extensions: " + adapter.extensions); - adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then((device) => { - var gpuContext = document.getElementById("canvas").getContext("gpupresent"); - console.log("wgpu device extensions: " + adapter.extensions); - gpuContext.getSwapChainPreferredFormat(device).then((fmt) => { - const swapChainDescriptor = { device: device, format: fmt }; - const swapChain = gpuContext.configureSwapChain(swapChainDescriptor); - const deviceId = WebGPU.mgrDevice.create(device); - const swapChainId = WebGPU.mgrSwapChain.create(swapChain); - const fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); - console.log("wgpu device: " + device); - console.log("wgpu swap chain: " + swapChain); - console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); - __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); - }); - }); - }); -}); - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { - SOKOL_ASSERT(_sapp.emsc.wgpu.device); - SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); - SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); - - WGPUTextureDescriptor ds_desc; - _sapp_clear(&ds_desc, sizeof(ds_desc)); - ds_desc.usage = WGPUTextureUsage_OutputAttachment; - ds_desc.dimension = WGPUTextureDimension_2D; - ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; - ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; - ds_desc.size.depth = 1; - ds_desc.arrayLayerCount = 1; - ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; - ds_desc.mipLevelCount = 1; - ds_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); - _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); - - if (_sapp.sample_count > 1) { - WGPUTextureDescriptor msaa_desc; - _sapp_clear(&msaa_desc, sizeof(msaa_desc)); - msaa_desc.usage = WGPUTextureUsage_OutputAttachment; - msaa_desc.dimension = WGPUTextureDimension_2D; - msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; - msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; - msaa_desc.size.depth = 1; - msaa_desc.arrayLayerCount = 1; - msaa_desc.format = _sapp.emsc.wgpu.render_format; - msaa_desc.mipLevelCount = 1; - msaa_desc.sampleCount = _sapp.sample_count; - _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); - _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { - if (_sapp.emsc.wgpu.msaa_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); - _sapp.emsc.wgpu.msaa_tex = 0; - } - if (_sapp.emsc.wgpu.msaa_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); - _sapp.emsc.wgpu.msaa_view = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_tex) { - wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); - _sapp.emsc.wgpu.depth_stencil_tex = 0; - } - if (_sapp.emsc.wgpu.depth_stencil_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); - _sapp.emsc.wgpu.depth_stencil_view = 0; - } -} - -_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { - if (_sapp.emsc.wgpu.swapchain_view) { - wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); - } - _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); -} -#endif - -_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); - emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb); - emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb); - emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_focus_cb); - emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_blur_cb); - sapp_js_add_beforeunload_listener(); - if (_sapp.clipboard.enabled) { - sapp_js_add_clipboard_listener(); - } - if (_sapp.drop.enabled) { - sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); - #endif -} - -_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { - emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); - emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); - emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); - sapp_js_remove_beforeunload_listener(); - if (_sapp.clipboard.enabled) { - sapp_js_remove_clipboard_listener(); - } - if (_sapp.drop.enabled) { - sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); - } - #if defined(SOKOL_GLES3) - emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); - emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); - #endif -} - -_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { - _SOKOL_UNUSED(userData); - _sapp_timing_external(&_sapp.timing, time / 1000.0); - - #if defined(SOKOL_WGPU) - /* - on WebGPU, the emscripten frame callback will already be called while - the asynchronous WebGPU device and swapchain initialization is still - in progress - */ - switch (_sapp.emsc.wgpu.state) { - case _SAPP_EMSC_WGPU_STATE_INITIAL: - /* async JS init hasn't finished yet */ - break; - case _SAPP_EMSC_WGPU_STATE_READY: - /* perform post-async init stuff */ - _sapp_emsc_wgpu_surfaces_create(); - _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; - break; - case _SAPP_EMSC_WGPU_STATE_RUNNING: - /* a regular frame */ - _sapp_emsc_wgpu_next_frame(); - _sapp_frame(); - break; - } - #else - /* WebGL code path */ - _sapp_frame(); - #endif - - /* quit-handling */ - if (_sapp.quit_requested) { - _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - _sapp_call_event(&_sapp.event); - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - _sapp_emsc_unregister_eventhandlers(); - _sapp_call_cleanup(); - _sapp_discard_state(); - return EM_FALSE; - } - return EM_TRUE; -} - -_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { - _sapp_init_state(desc); - sapp_js_init(&_sapp.html5_canvas_selector[1]); - double w, h; - if (_sapp.desc.html5_canvas_resize) { - w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); - h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); - } - else { - emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); - } - if (_sapp.desc.high_dpi) { - _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); - } - _sapp.window_width = (int)roundf(w); - _sapp.window_height = (int)roundf(h); - _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); - _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); - emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); - #if defined(SOKOL_GLES3) - _sapp_emsc_webgl_init(); - #elif defined(SOKOL_WGPU) - sapp_js_wgpu_init(); - #endif - _sapp.valid = true; - _sapp_emsc_register_eventhandlers(); - sapp_set_icon(&desc->icon); - - /* start the frame loop */ - emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0); - - /* NOT A BUG: do not call _sapp_discard_state() here, instead this is - called in _sapp_emsc_frame() when the application is ordered to quit - */ -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_emsc_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_EMSCRIPTEN */ - -// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ -// -// >>gl helpers -#if defined(SOKOL_GLCORE33) -typedef struct { - int red_bits; - int green_bits; - int blue_bits; - int alpha_bits; - int depth_bits; - int stencil_bits; - int samples; - bool doublebuffer; - uintptr_t handle; -} _sapp_gl_fbconfig; - -_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { - _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); - /* -1 means "don't care" */ - fbconfig->red_bits = -1; - fbconfig->green_bits = -1; - fbconfig->blue_bits = -1; - fbconfig->alpha_bits = -1; - fbconfig->depth_bits = -1; - fbconfig->stencil_bits = -1; - fbconfig->samples = -1; -} - -_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { - int missing, least_missing = 1000000; - int color_diff, least_color_diff = 10000000; - int extra_diff, least_extra_diff = 10000000; - const _sapp_gl_fbconfig* current; - const _sapp_gl_fbconfig* closest = 0; - for (int i = 0; i < count; i++) { - current = alternatives + i; - if (desired->doublebuffer != current->doublebuffer) { - continue; - } - missing = 0; - if (desired->alpha_bits > 0 && current->alpha_bits == 0) { - missing++; - } - if (desired->depth_bits > 0 && current->depth_bits == 0) { - missing++; - } - if (desired->stencil_bits > 0 && current->stencil_bits == 0) { - missing++; - } - if (desired->samples > 0 && current->samples == 0) { - /* Technically, several multisampling buffers could be - involved, but that's a lower level implementation detail and - not important to us here, so we count them as one - */ - missing++; - } - - /* These polynomials make many small channel size differences matter - less than one large channel size difference - Calculate color channel size difference value - */ - color_diff = 0; - if (desired->red_bits != -1) { - color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); - } - if (desired->green_bits != -1) { - color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); - } - if (desired->blue_bits != -1) { - color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); - } - - /* Calculate non-color channel size difference value */ - extra_diff = 0; - if (desired->alpha_bits != -1) { - extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); - } - if (desired->depth_bits != -1) { - extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); - } - if (desired->stencil_bits != -1) { - extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); - } - if (desired->samples != -1) { - extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); - } - - /* Figure out if the current one is better than the best one found so far - Least number of missing buffers is the most important heuristic, - then color buffer size match and lastly size match for other buffers - */ - if (missing < least_missing) { - closest = current; - } - else if (missing == least_missing) { - if ((color_diff < least_color_diff) || - (color_diff == least_color_diff && extra_diff < least_extra_diff)) - { - closest = current; - } - } - if (current == closest) { - least_missing = missing; - least_color_diff = color_diff; - least_extra_diff = extra_diff; - } - } - return closest; -} -#endif - -// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ -// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ -// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ -// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ -// -// >>windows -#if defined(_SAPP_WIN32) -_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - _sapp_clear(dst, (size_t)dst_num_bytes); - const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); - const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); - if ((dst_needed > 0) && (dst_needed < dst_chars)) { - MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); - return true; - } - else { - /* input string doesn't fit into destination buffer */ - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { - /* same as GLFW */ - _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; - _sapp.keycodes[0x002] = SAPP_KEYCODE_1; - _sapp.keycodes[0x003] = SAPP_KEYCODE_2; - _sapp.keycodes[0x004] = SAPP_KEYCODE_3; - _sapp.keycodes[0x005] = SAPP_KEYCODE_4; - _sapp.keycodes[0x006] = SAPP_KEYCODE_5; - _sapp.keycodes[0x007] = SAPP_KEYCODE_6; - _sapp.keycodes[0x008] = SAPP_KEYCODE_7; - _sapp.keycodes[0x009] = SAPP_KEYCODE_8; - _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; - _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; - _sapp.keycodes[0x030] = SAPP_KEYCODE_B; - _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; - _sapp.keycodes[0x020] = SAPP_KEYCODE_D; - _sapp.keycodes[0x012] = SAPP_KEYCODE_E; - _sapp.keycodes[0x021] = SAPP_KEYCODE_F; - _sapp.keycodes[0x022] = SAPP_KEYCODE_G; - _sapp.keycodes[0x023] = SAPP_KEYCODE_H; - _sapp.keycodes[0x017] = SAPP_KEYCODE_I; - _sapp.keycodes[0x024] = SAPP_KEYCODE_J; - _sapp.keycodes[0x025] = SAPP_KEYCODE_K; - _sapp.keycodes[0x026] = SAPP_KEYCODE_L; - _sapp.keycodes[0x032] = SAPP_KEYCODE_M; - _sapp.keycodes[0x031] = SAPP_KEYCODE_N; - _sapp.keycodes[0x018] = SAPP_KEYCODE_O; - _sapp.keycodes[0x019] = SAPP_KEYCODE_P; - _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; - _sapp.keycodes[0x013] = SAPP_KEYCODE_R; - _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; - _sapp.keycodes[0x014] = SAPP_KEYCODE_T; - _sapp.keycodes[0x016] = SAPP_KEYCODE_U; - _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; - _sapp.keycodes[0x011] = SAPP_KEYCODE_W; - _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; - _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; - _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; - _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; - _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; - _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; - _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; - _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; - _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; - _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; - _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; - _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; - _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; - _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; - _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; - _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; - _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; - _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; - _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; - _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; - _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; - _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; - _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; - _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; - _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; - _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; - _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; - _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; - _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; - _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; - _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; - _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; - _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; - _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; - _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; - _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; - _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; - _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; - _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; - _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; - _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; - _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; - _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; - _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; - _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; - _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; - _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; - _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; - _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; - _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; - _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; - _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; - _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; - _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; - _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; - _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; - _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; - _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; - _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; - _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; - _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; - _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; - _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; - _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; - _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; - _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; - _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; - _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; - _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; - _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; - _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; - _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; - _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; - _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; - _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; - _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; - _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; - _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; - _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; - _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; - _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; - _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; - _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; - _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; -} -#endif // _SAPP_WIN32 - -#if defined(_SAPP_WIN32) - -#if defined(SOKOL_D3D11) - -#if defined(__cplusplus) -#define _sapp_d3d11_Release(self) (self)->Release() -#define _sapp_win32_refiid(iid) iid -#else -#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) -#define _sapp_win32_refiid(iid) &iid -#endif - -#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } - - -static const IID _sapp_IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89, {0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c} }; -static const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; -static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; - -static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { - #if defined(__cplusplus) - return self->GetBuffer(Buffer, riid, ppSurface); - #else - return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); - #endif -} - -static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { - #if defined(__cplusplus) - return self->QueryInterface(riid, ppvObject); - #else - return self->lpVtbl->QueryInterface(self, riid, ppvObject); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { - #if defined(__cplusplus) - return self->CreateRenderTargetView(pResource, pDesc, ppRTView); - #else - return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { - #if defined(__cplusplus) - return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); - #else - return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); - #endif -} - -static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { - #if defined(__cplusplus) - return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); - #else - return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); - #endif -} - -static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { - #if defined(__cplusplus) - self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #else - self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); - #endif -} - -static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { - #if defined(__cplusplus) - return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); - #else - return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); - #endif -} - -static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { - #if defined(__cplusplus) - return self->Present(SyncInterval, Flags); - #else - return self->lpVtbl->Present(self, SyncInterval, Flags); - #endif -} - -static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { - #if defined(__cplusplus) - return self->GetFrameStatistics(pStats); - #else - return self->lpVtbl->GetFrameStatistics(self, pStats); - #endif -} - -static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { - #if defined(__cplusplus) - return self->SetMaximumFrameLatency(MaxLatency); - #else - return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); - #endif -} - -static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { - #if defined(__cplusplus) - return self->GetAdapter(pAdapter); - #else - return self->lpVtbl->GetAdapter(self, pAdapter); - #endif -} - -static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { - #if defined(__cplusplus) - return self->GetParent(riid, ppParent); - #else - return self->lpVtbl->GetParent(self, riid, ppParent); - #endif -} - -static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { - #if defined(__cplusplus) - return self->MakeWindowAssociation(WindowHandle, Flags); - #else - return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); - #endif -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { - DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; - sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; - sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; - sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - sc_desc->BufferDesc.RefreshRate.Numerator = 60; - sc_desc->BufferDesc.RefreshRate.Denominator = 1; - sc_desc->OutputWindow = _sapp.win32.hwnd; - sc_desc->Windowed = true; - if (_sapp.win32.is_win10_or_greater) { - sc_desc->BufferCount = 2; - sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; - _sapp.d3d11.use_dxgi_frame_stats = true; - } - else { - sc_desc->BufferCount = 1; - sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - _sapp.d3d11.use_dxgi_frame_stats = false; - } - sc_desc->SampleDesc.Count = 1; - sc_desc->SampleDesc.Quality = 0; - sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; - #if defined(SOKOL_DEBUG) - create_flags |= D3D11_CREATE_DEVICE_DEBUG; - #endif - D3D_FEATURE_LEVEL feature_level; - HRESULT hr = D3D11CreateDeviceAndSwapChain( - NULL, /* pAdapter (use default) */ - D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ - NULL, /* Software */ - create_flags, /* Flags */ - NULL, /* pFeatureLevels */ - 0, /* FeatureLevels */ - D3D11_SDK_VERSION, /* SDKVersion */ - sc_desc, /* pSwapChainDesc */ - &_sapp.d3d11.swap_chain, /* ppSwapChain */ - &_sapp.d3d11.device, /* ppDevice */ - &feature_level, /* pFeatureLevel */ - &_sapp.d3d11.device_context); /* ppImmediateContext */ - _SOKOL_UNUSED(hr); - #if defined(SOKOL_DEBUG) - if (!SUCCEEDED(hr)) { - // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the - // 'D3D11 debug layer' stopped working, indicated by the error message: - // === - // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. - // These flags must be removed, or the Windows 10 SDK must be installed. - // Flags include: D3D11_CREATE_DEVICE_DEBUG - // === - // - // ...just retry with the DEBUG flag switched off - _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); - create_flags &= ~D3D11_CREATE_DEVICE_DEBUG; - hr = D3D11CreateDeviceAndSwapChain( - NULL, /* pAdapter (use default) */ - D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ - NULL, /* Software */ - create_flags, /* Flags */ - NULL, /* pFeatureLevels */ - 0, /* FeatureLevels */ - D3D11_SDK_VERSION, /* SDKVersion */ - sc_desc, /* pSwapChainDesc */ - &_sapp.d3d11.swap_chain, /* ppSwapChain */ - &_sapp.d3d11.device, /* ppDevice */ - &feature_level, /* pFeatureLevel */ - &_sapp.d3d11.device_context); /* ppImmediateContext */ - } - #endif - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); - - // minimize frame latency, disable Alt-Enter - hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); - if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { - _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); - IDXGIAdapter* dxgi_adapter = 0; - hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); - if (SUCCEEDED(hr) && dxgi_adapter) { - IDXGIFactory* dxgi_factory = 0; - hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); - if (SUCCEEDED(hr)) { - _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); - _SAPP_SAFE_RELEASE(dxgi_factory); - } - else { - _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); - } - _SAPP_SAFE_RELEASE(dxgi_adapter); - } - else { - _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); - } - } - else { - _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); - _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); - _SAPP_SAFE_RELEASE(_sapp.d3d11.device); -} - -_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { - SOKOL_ASSERT(0 == _sapp.d3d11.rt); - SOKOL_ASSERT(0 == _sapp.d3d11.rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); - SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(0 == _sapp.d3d11.ds); - SOKOL_ASSERT(0 == _sapp.d3d11.dsv); - - HRESULT hr; - - /* view for the swapchain-created framebuffer */ - hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); - hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); - - /* common desc for MSAA and depth-stencil texture */ - D3D11_TEXTURE2D_DESC tex_desc; - _sapp_clear(&tex_desc, sizeof(tex_desc)); - tex_desc.Width = (UINT)_sapp.framebuffer_width; - tex_desc.Height = (UINT)_sapp.framebuffer_height; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Usage = D3D11_USAGE_DEFAULT; - tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; - tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; - tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); - - /* create MSAA texture and view if antialiasing requested */ - if (_sapp.sample_count > 1) { - tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); - hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); - } - - /* texture and view for the depth-stencil-surface */ - tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; - hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); - hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); - SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { - _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); - _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); - _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); - _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); -} - -_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { - if (_sapp.d3d11.swap_chain) { - _sapp_d3d11_destroy_default_render_target(); - _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); - _sapp_d3d11_create_default_render_target(); - } -} - -_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { - /* do MSAA resolve if needed */ - if (_sapp.sample_count > 1) { - SOKOL_ASSERT(_sapp.d3d11.rt); - SOKOL_ASSERT(_sapp.d3d11.msaa_rt); - _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); - } - UINT flags = 0; - if (_sapp.win32.is_win10_or_greater && do_not_wait) { - /* this hack/workaround somewhat improves window-movement and -sizing - responsiveness when rendering is controlled via WM_TIMER during window - move and resize on NVIDIA cards on Win10 with recent drivers. - */ - flags = DXGI_PRESENT_DO_NOT_WAIT; - } - _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); -} - -#endif /* SOKOL_D3D11 */ - -#if defined(SOKOL_GLCORE33) -_SOKOL_PRIVATE void _sapp_wgl_init(void) { - _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); - if (!_sapp.wgl.opengl32) { - _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); - } - SOKOL_ASSERT(_sapp.wgl.opengl32); - _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); - SOKOL_ASSERT(_sapp.wgl.CreateContext); - _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); - SOKOL_ASSERT(_sapp.wgl.DeleteContext); - _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); - SOKOL_ASSERT(_sapp.wgl.GetProcAddress); - _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); - SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); - _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); - SOKOL_ASSERT(_sapp.wgl.MakeCurrent); - - _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, - L"SOKOLAPP", - L"sokol-app message window", - WS_CLIPSIBLINGS|WS_CLIPCHILDREN, - 0, 0, 1, 1, - NULL, NULL, - GetModuleHandleW(NULL), - NULL); - if (!_sapp.wgl.msg_hwnd) { - _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); - } - SOKOL_ASSERT(_sapp.wgl.msg_hwnd); - ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); - MSG msg; - while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); - if (!_sapp.wgl.msg_dc) { - _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { - SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); - DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; - FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; -} - -_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext && extensions); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { - SOKOL_ASSERT(ext); - if (_sapp.wgl.GetExtensionsStringEXT) { - const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - if (_sapp.wgl.GetExtensionsStringARB) { - const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); - if (extensions) { - if (_sapp_wgl_has_ext(ext, extensions)) { - return true; - } - } - } - return false; -} - -_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { - SOKOL_ASSERT(_sapp.wgl.msg_dc); - PIXELFORMATDESCRIPTOR pfd; - _sapp_clear(&pfd, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 24; - if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { - _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); - } - HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); - if (!rc) { - _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); - } - if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { - _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); - } - _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); - _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); - _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); - _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); - _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); - _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); - _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); - _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); - _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); - _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); - _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); - _sapp.wgl.DeleteContext(rc); -} - -_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - int value = 0; - if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { - _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); - } - return value; -} - -_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { - SOKOL_ASSERT(_sapp.win32.dc); - SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); - const _sapp_gl_fbconfig* closest; - - int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); - _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); - SOKOL_ASSERT(usable_configs); - int usable_count = 0; - for (int i = 0; i < native_count; i++) { - const int n = i + 1; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { - continue; - } - if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { - continue; - } - u->red_bits = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); - u->green_bits = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); - u->blue_bits = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); - u->alpha_bits = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); - u->depth_bits = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); - u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); - if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { - u->doublebuffer = true; - } - if (_sapp.wgl.arb_multisample) { - u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); - } - u->handle = (uintptr_t)n; - usable_count++; - } - SOKOL_ASSERT(usable_count > 0); - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - int pixel_format = 0; - if (closest) { - pixel_format = (int) closest->handle; - } - _sapp_free(usable_configs); - return pixel_format; -} - -_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { - int pixel_format = _sapp_wgl_find_pixel_format(); - if (0 == pixel_format) { - _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); - } - PIXELFORMATDESCRIPTOR pfd; - if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { - _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); - } - if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { - _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); - } - if (!_sapp.wgl.arb_create_context) { - _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); - } - if (!_sapp.wgl.arb_create_context_profile) { - _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); - } - const int attrs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, - WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, 0 - }; - _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); - if (!_sapp.wgl.gl_ctx) { - const DWORD err = GetLastError(); - if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { - _SAPP_PANIC(WIN32_WGL_OPENGL_3_2_NOT_SUPPORTED); - } - else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { - _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); - } - else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { - _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); - } - else { - _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); - } - } - _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); - if (_sapp.wgl.ext_swap_control) { - /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ - _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); - } -} - -_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { - SOKOL_ASSERT(_sapp.wgl.gl_ctx); - _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); - _sapp.wgl.gl_ctx = 0; -} - -_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { - SOKOL_ASSERT(_sapp.win32.dc); - /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ - SwapBuffers(_sapp.win32.dc); -} -#endif /* SOKOL_GLCORE33 */ - -_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { - SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); - _sapp_clear(dst, (size_t)dst_num_bytes); - const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); - if (bytes_needed <= dst_num_bytes) { - WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); - return true; - } - else { - return false; - } -} - -/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ -_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { - RECT rect; - if (GetClientRect(_sapp.win32.hwnd, &rect)) { - float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; - float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; - _sapp.window_width = (int)roundf(window_width); - _sapp.window_height = (int)roundf(window_height); - int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); - int fb_height = (int)roundf(window_height * _sapp.win32.dpi.content_scale); - /* prevent a framebuffer size of 0 when window is minimized */ - if (0 == fb_width) { - fb_width = 1; - } - if (0 == fb_height) { - fb_height = 1; - } - if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { - _sapp.framebuffer_width = fb_width; - _sapp.framebuffer_height = fb_height; - return true; - } - } - else { - _sapp.window_width = _sapp.window_height = 1; - _sapp.framebuffer_width = _sapp.framebuffer_height = 1; - } - return false; -} - -_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { - HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); - MONITORINFO minfo; - _sapp_clear(&minfo, sizeof(minfo)); - minfo.cbSize = sizeof(MONITORINFO); - GetMonitorInfo(monitor, &minfo); - const RECT mr = minfo.rcMonitor; - const int monitor_w = mr.right - mr.left; - const int monitor_h = mr.bottom - mr.top; - - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD win_style; - RECT rect = { 0, 0, 0, 0 }; - - _sapp.fullscreen = fullscreen; - if (!_sapp.fullscreen) { - win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect = _sapp.win32.stored_window_rect; - } - else { - GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); - win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; - rect.left = mr.left; - rect.top = mr.top; - rect.right = rect.left + monitor_w; - rect.bottom = rect.top + monitor_h; - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - } - const int win_w = rect.right - rect.left; - const int win_h = rect.bottom - rect.top; - const int win_x = rect.left; - const int win_y = rect.top; - SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); - SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); -} - -_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { - _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); -} - -_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined - // before windows.h is included, but we can't guarantee that because - // the sokol_app.h implementation may be included with other implementations - // in the same compilation unit - int id = 0; - switch (cursor) { - case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL - case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM - case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS - case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND - case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE - case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS - case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE - case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW - case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL - case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO - default: break; - } - if (id != 0) { - _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); - } - // fallback: default cursor - if (0 == _sapp.win32.cursors[cursor]) { - // 32512 => IDC_ARROW - _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); - } - SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); -} - -_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { - for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { - _sapp_win32_init_cursor((sapp_mouse_cursor)i); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { - POINT pos; - if (!GetCursorPos(&pos)) { - return false; - } - if (WindowFromPoint(pos) != _sapp.win32.hwnd) { - return false; - } - RECT area; - GetClientRect(_sapp.win32.hwnd, &area); - ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); - ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); - return PtInRect(&area, pos) == TRUE; -} - -_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { - // NOTE: when called from WM_SETCURSOR, the area test would be redundant - if (!skip_area_test) { - if (!_sapp_win32_cursor_in_content_area()) { - return; - } - } - if (!shown) { - SetCursor(NULL); - } - else { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); - SetCursor(_sapp.win32.cursors[cursor]); - } -} - -_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { - if (0 == _sapp.win32.mouse_capture_mask) { - SetCapture(_sapp.win32.hwnd); - } - _sapp.win32.mouse_capture_mask |= btn_mask; -} - -_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { - if (0 != _sapp.win32.mouse_capture_mask) { - _sapp.win32.mouse_capture_mask &= ~btn_mask; - if (0 == _sapp.win32.mouse_capture_mask) { - ReleaseCapture(); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - _sapp_win32_release_mouse(0xFF); - if (_sapp.mouse.locked) { - /* store the current mouse position, so it can be restored when unlocked */ - POINT pos; - BOOL res = GetCursorPos(&pos); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - _sapp.win32.mouse_locked_x = pos.x; - _sapp.win32.mouse_locked_y = pos.y; - - /* while the mouse is locked, make the mouse cursor invisible and - confine the mouse movement to a small rectangle inside our window - (so that we don't miss any mouse up events) - */ - RECT client_rect = { - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y, - _sapp.win32.mouse_locked_x, - _sapp.win32.mouse_locked_y - }; - ClipCursor(&client_rect); - - /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ - ShowCursor(FALSE); - - /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ - const RAWINPUTDEVICE rid = { - 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC - 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE - 0, // dwFlags - _sapp.win32.hwnd // hwndTarget - }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); - } - /* in case the raw mouse device only supports absolute position reporting, - we need to skip the dx/dy compution for the first WM_INPUT event - */ - _sapp.win32.raw_input_mousepos_valid = false; - } - else { - /* disable raw input for mouse */ - const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); - } - - /* let the mouse roam freely again */ - ClipCursor(NULL); - ShowCursor(TRUE); - - /* restore the 'pre-locked' mouse position */ - BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); - SOKOL_ASSERT(res); _SOKOL_UNUSED(res); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { - const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); - if (cur_monitor != _sapp.win32.hmonitor) { - _sapp.win32.hmonitor = cur_monitor; - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { - uint32_t mods = 0; - if (GetKeyState(VK_SHIFT) & (1<<15)) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (GetKeyState(VK_CONTROL) & (1<<15)) { - mods |= SAPP_MODIFIER_CTRL; - } - if (GetKeyState(VK_MENU) & (1<<15)) { - mods |= SAPP_MODIFIER_ALT; - } - if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { - mods |= SAPP_MODIFIER_SUPER; - } - const bool swapped = (TRUE == GetSystemMetrics(SM_SWAPBUTTON)); - if (GetAsyncKeyState(VK_LBUTTON)) { - mods |= swapped ? SAPP_MODIFIER_RMB : SAPP_MODIFIER_LMB; - } - if (GetAsyncKeyState(VK_RBUTTON)) { - mods |= swapped ? SAPP_MODIFIER_LMB : SAPP_MODIFIER_RMB; - } - if (GetAsyncKeyState(VK_MBUTTON)) { - mods |= SAPP_MODIFIER_MMB; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { - if (!_sapp.mouse.locked) { - const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; - const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; - if (_sapp.mouse.pos_valid) { - // don't update dx/dy in the very first event - _sapp.mouse.dx = new_x - _sapp.mouse.x; - _sapp.mouse.dy = new_y - _sapp.mouse.y; - } - _sapp.mouse.x = new_x; - _sapp.mouse.y = new_y; - _sapp.mouse.pos_valid = true; - } -} - -_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.mouse_button = btn; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.scroll_x = -x / 30.0f; - _sapp.event.scroll_y = y / 30.0f; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { - if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { - _sapp_init_event(type); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.key_code = _sapp.keycodes[vk]; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { - if (_sapp_events_enabled() && (c >= 32)) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp.event.char_code = c; - _sapp.event.key_repeat = repeat; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { - /* called on WM_DPICHANGED, which will only be sent to the application - if sapp_desc.high_dpi is true and the Windows version is recent enough - to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 - */ - SOKOL_ASSERT(_sapp.desc.high_dpi); - HINSTANCE user32 = LoadLibraryA("user32.dll"); - if (!user32) { - return; - } - typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); - GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); - if (fn_getdpiforwindow) { - UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); - // NOTE: for high-dpi apps, mouse_scale remains one - _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; - _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; - _sapp.dpi_scale = _sapp.win32.dpi.window_scale; - SetWindowPos(hWnd, 0, - proposed_win_rect->left, - proposed_win_rect->top, - proposed_win_rect->right - proposed_win_rect->left, - proposed_win_rect->bottom - proposed_win_rect->top, - SWP_NOZORDER | SWP_NOACTIVATE); - } - FreeLibrary(user32); -} - -_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { - if (!_sapp.drop.enabled) { - return; - } - _sapp_clear_drop_buffer(); - bool drop_failed = false; - const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); - _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; - for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { - const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; - WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); - DragQueryFileW(hdrop, i, buffer, num_chars); - if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - drop_failed = true; - } - _sapp_free(buffer); - } - DragFinish(hdrop); - if (!drop_failed) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); - _sapp.event.modifiers = _sapp_win32_mods(); - _sapp_call_event(&_sapp.event); - } - } - else { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - } -} - -_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { - #if defined(SOKOL_D3D11) - // on D3D11, use the more precise DXGI timestamp - if (_sapp.d3d11.use_dxgi_frame_stats) { - DXGI_FRAME_STATISTICS dxgi_stats; - _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); - HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); - if (SUCCEEDED(hr)) { - if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { - if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { - _sapp_timing_discontinuity(&_sapp.timing); - } - _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; - LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; - const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); - _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); - } - return; - } - } - // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason - _sapp_timing_measure(&_sapp.timing); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_timing_measure(&_sapp.timing); - #endif -} - -_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (!_sapp.win32.in_create_window) { - switch (uMsg) { - case WM_CLOSE: - /* only give user a chance to intervene when sapp_quit() wasn't already called */ - if (!_sapp.quit_ordered) { - /* if window should be closed and event handling is enabled, give user code - a change to intervene via sapp_cancel_quit() - */ - _sapp.quit_requested = true; - _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - if (_sapp.quit_ordered) { - PostQuitMessage(0); - } - return 0; - case WM_SYSCOMMAND: - switch (wParam & 0xFFF0) { - case SC_SCREENSAVE: - case SC_MONITORPOWER: - if (_sapp.fullscreen) { - /* disable screen saver and blanking in fullscreen mode */ - return 0; - } - break; - case SC_KEYMENU: - /* user trying to access menu via ALT */ - return 0; - } - break; - case WM_ERASEBKGND: - return 1; - case WM_SIZE: - { - const bool iconified = wParam == SIZE_MINIMIZED; - if (iconified != _sapp.win32.iconified) { - _sapp.win32.iconified = iconified; - if (iconified) { - _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else { - _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - break; - case WM_SETFOCUS: - _sapp_win32_app_event(SAPP_EVENTTYPE_FOCUSED); - break; - case WM_KILLFOCUS: - /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ - if (_sapp.mouse.locked) { - _sapp_win32_lock_mouse(false); - } - _sapp_win32_app_event(SAPP_EVENTTYPE_UNFOCUSED); - break; - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT) { - _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); - return TRUE; - } - break; - case WM_DPICHANGED: - { - /* Update window's DPI and size if its moved to another monitor with a different DPI - Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. - */ - _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); - break; - } - case WM_LBUTTONDOWN: - _sapp_win32_mouse_update(lParam); - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); - _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - /* mouse only reports absolute position - NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. - (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). - See: https://github.com/floooh/sokol/issues/806 and - https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) - */ - LONG new_x = raw_mouse_data->data.mouse.lLastX; - LONG new_y = raw_mouse_data->data.mouse.lLastY; - if (_sapp.win32.raw_input_mousepos_valid) { - _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); - _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); - } - _sapp.win32.raw_input_mousepos_x = new_x; - _sapp.win32.raw_input_mousepos_y = new_y; - _sapp.win32.raw_input_mousepos_valid = true; - } - else { - /* mouse reports movement delta (this seems to be the common case) */ - _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; - _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; - } - _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); - } - break; - - case WM_MOUSELEAVE: - if (!_sapp.mouse.locked) { - _sapp.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); - } - break; - case WM_MOUSEWHEEL: - _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); - break; - case WM_MOUSEHWHEEL: - _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); - break; - case WM_CHAR: - _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); - break; - case WM_KEYUP: - case WM_SYSKEYUP: - _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); - break; - case WM_ENTERSIZEMOVE: - SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); - break; - case WM_EXITSIZEMOVE: - KillTimer(_sapp.win32.hwnd, 1); - break; - case WM_TIMER: - _sapp_win32_timing_measure(); - _sapp_frame(); - #if defined(SOKOL_D3D11) - // present with DXGI_PRESENT_DO_NOT_WAIT - _sapp_d3d11_present(true); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* NOTE: resizing the swap-chain during resize leads to a substantial - memory spike (hundreds of megabytes for a few seconds). - - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); - } - */ - break; - case WM_NCLBUTTONDOWN: - /* workaround for half-second pause when starting to move window - see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ - */ - if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { - POINT point; - GetCursorPos(&point); - ScreenToClient(_sapp.win32.hwnd, &point); - PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); - } - break; - case WM_DROPFILES: - _sapp_win32_files_dropped((HDROP)wParam); - break; - case WM_DISPLAYCHANGE: - // refresh rate might have changed - _sapp_timing_reset(&_sapp.timing); - break; - - default: - break; - } - } - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -_SOKOL_PRIVATE void _sapp_win32_create_window(void) { - WNDCLASSW wndclassw; - _sapp_clear(&wndclassw, sizeof(wndclassw)); - wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; - wndclassw.hInstance = GetModuleHandleW(NULL); - wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); - wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); - wndclassw.lpszClassName = L"SOKOLAPP"; - RegisterClassW(&wndclassw); - - /* NOTE: regardless whether fullscreen is requested or not, a regular - windowed-mode window will always be created first (however in hidden - mode, so that no windowed-mode window pops up before the fullscreen window) - */ - const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - RECT rect = { 0, 0, 0, 0 }; - DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; - rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); - rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); - const bool use_default_width = 0 == _sapp.window_width; - const bool use_default_height = 0 == _sapp.window_height; - AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); - const int win_width = rect.right - rect.left; - const int win_height = rect.bottom - rect.top; - _sapp.win32.in_create_window = true; - _sapp.win32.hwnd = CreateWindowExW( - win_ex_style, // dwExStyle - L"SOKOLAPP", // lpClassName - _sapp.window_title_wide, // lpWindowName - win_style, // dwStyle - CW_USEDEFAULT, // X - SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! - use_default_width ? CW_USEDEFAULT : win_width, // nWidth - use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) - NULL, // hWndParent - NULL, // hMenu - GetModuleHandle(NULL), // hInstance - NULL); // lParam - _sapp.win32.in_create_window = false; - _sapp.win32.dc = GetDC(_sapp.win32.hwnd); - _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); - SOKOL_ASSERT(_sapp.win32.dc); - - /* this will get the actual windowed-mode window size, if fullscreen - is requested, the set_fullscreen function will then capture the - current window rectangle, which then might be used later to - restore the window position when switching back to windowed - */ - _sapp_win32_update_dimensions(); - if (_sapp.fullscreen) { - _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); - _sapp_win32_update_dimensions(); - } - ShowWindow(_sapp.win32.hwnd, SW_SHOW); - DragAcceptFiles(_sapp.win32.hwnd, 1); -} - -_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { - DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; - UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); -} - -_SOKOL_PRIVATE void _sapp_win32_destroy_icons(void) { - if (_sapp.win32.big_icon) { - DestroyIcon(_sapp.win32.big_icon); - _sapp.win32.big_icon = 0; - } - if (_sapp.win32.small_icon) { - DestroyIcon(_sapp.win32.small_icon); - _sapp.win32.small_icon = 0; - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_console(void) { - if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { - BOOL con_valid = FALSE; - if (_sapp.desc.win32_console_create) { - con_valid = AllocConsole(); - } - else if (_sapp.desc.win32_console_attach) { - con_valid = AttachConsole(ATTACH_PARENT_PROCESS); - } - if (con_valid) { - FILE* res_fp = 0; - errno_t err; - err = freopen_s(&res_fp, "CON", "w", stdout); - (void)err; - err = freopen_s(&res_fp, "CON", "w", stderr); - (void)err; - } - } - if (_sapp.desc.win32_console_utf8) { - _sapp.win32.orig_codepage = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - } -} - -_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { - if (_sapp.desc.win32_console_utf8) { - SetConsoleOutputCP(_sapp.win32.orig_codepage); - } -} - -_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { - - DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); - typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); - typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 - typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); - typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; - SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; - GETDPIFORMONITOR_T fn_getdpiformonitor = 0; - SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; - - HINSTANCE user32 = LoadLibraryA("user32.dll"); - if (user32) { - fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); - fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); - } - HINSTANCE shcore = LoadLibraryA("shcore.dll"); - if (shcore) { - fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); - fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); - } - /* - NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): - - These are different attempts to get DPI handling on Windows right, from oldest - to newest. SetProcessDpiAwarenessContext() is required for the new - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. - */ - if (fn_setprocessdpiawareness) { - if (_sapp.desc.high_dpi) { - /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, - if that fails, fall back to system-dpi-awareness - */ - _sapp.win32.dpi.aware = true; - DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; - if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { - // fallback to system-dpi-aware - fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); - } - } - else { - /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ - _sapp.win32.dpi.aware = false; - fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); - } - } - else if (fn_setprocessdpiaware) { - // fallback for Windows 7 - _sapp.win32.dpi.aware = true; - fn_setprocessdpiaware(); - } - /* get dpi scale factor for main monitor */ - if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { - POINT pt = { 1, 1 }; - HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); - UINT dpix, dpiy; - HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); - _SOKOL_UNUSED(hr); - SOKOL_ASSERT(SUCCEEDED(hr)); - /* clamp window scale to an integer factor */ - _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; - } - else { - _sapp.win32.dpi.window_scale = 1.0f; - } - if (_sapp.desc.high_dpi) { - _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; - _sapp.win32.dpi.mouse_scale = 1.0f; - } - else { - _sapp.win32.dpi.content_scale = 1.0f; - _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; - } - _sapp.dpi_scale = _sapp.win32.dpi.content_scale; - if (user32) { - FreeLibrary(user32); - } - if (shcore) { - FreeLibrary(shcore); - } -} - -_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { - SOKOL_ASSERT(str); - SOKOL_ASSERT(_sapp.win32.hwnd); - SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); - - if (!OpenClipboard(_sapp.win32.hwnd)) { - return false; - } - - HANDLE object = 0; - wchar_t* wchar_buf = 0; - - const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); - object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); - if (NULL == object) { - goto error; - } - wchar_buf = (wchar_t*) GlobalLock(object); - if (NULL == wchar_buf) { - goto error; - } - if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { - goto error; - } - GlobalUnlock(object); - wchar_buf = 0; - EmptyClipboard(); - // NOTE: when successful, SetClipboardData() takes ownership of memory object! - if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { - goto error; - } - CloseClipboard(); - return true; - -error: - if (wchar_buf) { - GlobalUnlock(object); - } - if (object) { - GlobalFree(object); - } - CloseClipboard(); - return false; -} - -_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { - SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); - SOKOL_ASSERT(_sapp.win32.hwnd); - if (!OpenClipboard(_sapp.win32.hwnd)) { - /* silently ignore any errors and just return the current - content of the local clipboard buffer - */ - return _sapp.clipboard.buffer; - } - HANDLE object = GetClipboardData(CF_UNICODETEXT); - if (!object) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); - if (!wchar_buf) { - CloseClipboard(); - return _sapp.clipboard.buffer; - } - if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { - _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); - } - GlobalUnlock(object); - CloseClipboard(); - return _sapp.clipboard.buffer; -} - -_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { - _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); -} - -_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { - BITMAPV5HEADER bi; - _sapp_clear(&bi, sizeof(bi)); - bi.bV5Size = sizeof(bi); - bi.bV5Width = desc->width; - bi.bV5Height = -desc->height; // NOTE the '-' here to indicate that origin is top-left - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_BITFIELDS; - bi.bV5RedMask = 0x00FF0000; - bi.bV5GreenMask = 0x0000FF00; - bi.bV5BlueMask = 0x000000FF; - bi.bV5AlphaMask = 0xFF000000; - - uint8_t* target = 0; - const uint8_t* source = (const uint8_t*)desc->pixels.ptr; - - HDC dc = GetDC(NULL); - HBITMAP color = CreateDIBSection(dc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&target, NULL, (DWORD)0); - ReleaseDC(NULL, dc); - if (0 == color) { - return NULL; - } - SOKOL_ASSERT(target); - - HBITMAP mask = CreateBitmap(desc->width, desc->height, 1, 1, NULL); - if (0 == mask) { - DeleteObject(color); - return NULL; - } - - for (int i = 0; i < (desc->width*desc->height); i++) { - target[0] = source[2]; - target[1] = source[1]; - target[2] = source[0]; - target[3] = source[3]; - target += 4; - source += 4; - } - - ICONINFO icon_info; - _sapp_clear(&icon_info, sizeof(icon_info)); - icon_info.fIcon = true; - icon_info.xHotspot = 0; - icon_info.yHotspot = 0; - icon_info.hbmMask = mask; - icon_info.hbmColor = color; - HICON icon_handle = CreateIconIndirect(&icon_info); - DeleteObject(color); - DeleteObject(mask); - - return icon_handle; -} - -_SOKOL_PRIVATE void _sapp_win32_set_icon(const sapp_icon_desc* icon_desc, int num_images) { - SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); - - int big_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); - int sml_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); - HICON big_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[big_img_index]); - HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index]); - - // if icon creation or lookup has failed for some reason, leave the currently set icon untouched - if (0 != big_icon) { - SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_BIG, (LPARAM) big_icon); - if (0 != _sapp.win32.big_icon) { - DestroyIcon(_sapp.win32.big_icon); - } - _sapp.win32.big_icon = big_icon; - } - if (0 != sml_icon) { - SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_SMALL, (LPARAM) sml_icon); - if (0 != _sapp.win32.small_icon) { - DestroyIcon(_sapp.win32.small_icon); - } - _sapp.win32.small_icon = sml_icon; - } -} - -/* don't laugh, but this seems to be the easiest and most robust - way to check if we're running on Win10 - - From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 -*/ -_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { - HMODULE h = GetModuleHandleW(L"kernel32.dll"); - if (NULL != h) { - return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { - _sapp_init_state(desc); - _sapp_win32_init_console(); - _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); - _sapp_win32_init_keytable(); - _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); - _sapp_win32_init_dpi(); - _sapp_win32_init_cursors(); - _sapp_win32_create_window(); - sapp_set_icon(&desc->icon); - #if defined(SOKOL_D3D11) - _sapp_d3d11_create_device_and_swapchain(); - _sapp_d3d11_create_default_render_target(); - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_init(); - _sapp_wgl_load_extensions(); - _sapp_wgl_create_context(); - #endif - _sapp.valid = true; - - bool done = false; - while (!(done || _sapp.quit_ordered)) { - _sapp_win32_timing_measure(); - MSG msg; - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { - if (WM_QUIT == msg.message) { - done = true; - continue; - } - else { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - _sapp_frame(); - #if defined(SOKOL_D3D11) - _sapp_d3d11_present(false); - if (IsIconic(_sapp.win32.hwnd)) { - Sleep((DWORD)(16 * _sapp.swap_interval)); - } - #endif - #if defined(SOKOL_GLCORE33) - _sapp_wgl_swap_buffers(); - #endif - /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ - if (_sapp_win32_update_dimensions()) { - #if defined(SOKOL_D3D11) - _sapp_d3d11_resize_default_render_target(); - #endif - _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); - } - /* check if the window monitor has changed, need to reset timing because - the new monitor might have a different refresh rate - */ - if (_sapp_win32_update_monitor()) { - _sapp_timing_reset(&_sapp.timing); - } - if (_sapp.quit_requested) { - PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); - } - } - _sapp_call_cleanup(); - - #if defined(SOKOL_D3D11) - _sapp_d3d11_destroy_default_render_target(); - _sapp_d3d11_destroy_device_and_swapchain(); - #else - _sapp_wgl_destroy_context(); - _sapp_wgl_shutdown(); - #endif - _sapp_win32_destroy_window(); - _sapp_win32_destroy_icons(); - _sapp_win32_restore_console(); - _sapp_discard_state(); -} - -_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { - int argc = 0; - char** argv = 0; - char* args; - - LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); - if (w_argv == NULL) { - // FIXME: chicken egg problem, can't report errors before sokol_main() is called! - } else { - size_t size = wcslen(w_command_line) * 4; - argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); - SOKOL_ASSERT(argv); - args = (char*) &argv[argc + 1]; - int n; - for (int i = 0; i < argc; ++i) { - n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); - if (n == 0) { - // FIXME: chicken egg problem, can't report errors before sokol_main() is called! - break; - } - argv[i] = args; - size -= (size_t)n; - args += n; - } - LocalFree(w_argv); - } - *o_argc = argc; - return argv; -} - -#if !defined(SOKOL_NO_ENTRY) -#if defined(SOKOL_WIN32_FORCE_MAIN) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_win32_run(&desc); - return 0; -} -#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { - _SOKOL_UNUSED(hInstance); - _SOKOL_UNUSED(hPrevInstance); - _SOKOL_UNUSED(lpCmdLine); - _SOKOL_UNUSED(nCmdShow); - int argc_utf8 = 0; - char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); - sapp_desc desc = sokol_main(argc_utf8, argv_utf8); - _sapp_win32_run(&desc); - _sapp_free(argv_utf8); - return 0; -} -#endif /* SOKOL_WIN32_FORCE_MAIN */ -#endif /* SOKOL_NO_ENTRY */ - -#ifdef _MSC_VER - #pragma warning(pop) -#endif - -#endif /* _SAPP_WIN32 */ - -// █████ ███ ██ ██████ ██████ ██████ ██ ██████ -// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ -// -// >>android -#if defined(_SAPP_ANDROID) - -/* android loop thread */ -_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { - SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); - - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - return false; - } - if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { - return false; - } - 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, - EGL_ALPHA_SIZE, alpha_size, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, 0, - EGL_NONE, - }; - EGLConfig available_cfgs[32]; - EGLint cfg_count; - eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); - SOKOL_ASSERT(cfg_count > 0); - SOKOL_ASSERT(cfg_count <= 32); - - /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ - EGLConfig config; - bool exact_cfg_found = false; - for (int i = 0; i < cfg_count; ++i) { - EGLConfig c = available_cfgs[i]; - EGLint r, g, b, a, d; - if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && - eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && - r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { - exact_cfg_found = true; - config = c; - break; - } - } - if (!exact_cfg_found) { - config = available_cfgs[0]; - } - - EGLint ctx_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 3, - EGL_NONE, - }; - EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); - if (context == EGL_NO_CONTEXT) { - return false; - } - - _sapp.android.config = config; - _sapp.android.display = display; - _sapp.android.context = context; - return true; -} - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { - if (_sapp.android.display != EGL_NO_DISPLAY) { - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } - if (_sapp.android.context != EGL_NO_CONTEXT) { - eglDestroyContext(_sapp.android.display, _sapp.android.context); - _sapp.android.context = EGL_NO_CONTEXT; - } - eglTerminate(_sapp.android.display); - _sapp.android.display = EGL_NO_DISPLAY; - } -} - -_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - /* TODO: set window flags */ - /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ - - /* create egl surface and make it current */ - EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); - if (surface == EGL_NO_SURFACE) { - return false; - } - if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { - return false; - } - _sapp.android.surface = surface; - return true; -} - -_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { - if (_sapp.android.display == EGL_NO_DISPLAY) { - return; - } - eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (_sapp.android.surface != EGL_NO_SURFACE) { - eglDestroySurface(_sapp.android.display, _sapp.android.surface); - _sapp.android.surface = EGL_NO_SURFACE; - } -} - -_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - SOKOL_ASSERT(window); - - const int32_t win_w = ANativeWindow_getWidth(window); - const int32_t win_h = ANativeWindow_getHeight(window); - SOKOL_ASSERT(win_w >= 0 && win_h >= 0); - const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); - _sapp.window_width = win_w; - _sapp.window_height = win_h; - if (win_changed || force_update) { - if (!_sapp.desc.high_dpi) { - const int32_t buf_w = win_w / 2; - const int32_t buf_h = win_h / 2; - EGLint format; - EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); - SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); - /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions - as the ANativeWindow size results in weird display artefacts, that's - why it's only called when the buffer geometry is different from - the window size - */ - int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); - SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); - } - } - - /* query surface size */ - EGLint fb_w, fb_h; - EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); - EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); - SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); - SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); - const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); - _sapp.framebuffer_width = fb_w; - _sapp.framebuffer_height = fb_h; - _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; - if (win_changed || fb_changed || force_update) { - if (!_sapp.first_frame) { - _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); - } - } -} - -_SOKOL_PRIVATE void _sapp_android_cleanup(void) { - if (_sapp.android.surface != EGL_NO_SURFACE) { - /* egl context is bound, cleanup gracefully */ - if (_sapp.init_called && !_sapp.cleanup_called) { - _sapp_call_cleanup(); - } - } - /* always try to cleanup by destroying egl context */ - _sapp_android_cleanup_egl(); -} - -_SOKOL_PRIVATE void _sapp_android_shutdown(void) { - /* try to cleanup while we still have a surface and can call cleanup_cb() */ - _sapp_android_cleanup(); - /* request exit */ - ANativeActivity_finish(_sapp.android.activity); -} - -_SOKOL_PRIVATE void _sapp_android_frame(void) { - SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); - SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); - SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); - _sapp_timing_measure(&_sapp.timing); - _sapp_android_update_dimensions(_sapp.android.current.window, false); - _sapp_frame(); - eglSwapBuffers(_sapp.android.display, _sapp.android.surface); -} - -_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { - return false; - } - if (!_sapp_events_enabled()) { - return false; - } - int32_t action_idx = AMotionEvent_getAction(e); - int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; - sapp_event_type type = SAPP_EVENTTYPE_INVALID; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - type = SAPP_EVENTTYPE_TOUCHES_BEGAN; - break; - case AMOTION_EVENT_ACTION_MOVE: - type = SAPP_EVENTTYPE_TOUCHES_MOVED; - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - type = SAPP_EVENTTYPE_TOUCHES_ENDED; - break; - case AMOTION_EVENT_ACTION_CANCEL: - type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; - break; - default: - break; - } - if (type == SAPP_EVENTTYPE_INVALID) { - return false; - } - int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - _sapp_init_event(type); - _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); - if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { - _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; - } - for (int32_t i = 0; i < _sapp.event.num_touches; i++) { - sapp_touchpoint* dst = &_sapp.event.touches[i]; - dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); - dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; - dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; - dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); - if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || - action == AMOTION_EVENT_ACTION_POINTER_UP) { - dst->changed = (i == idx); - } else { - dst->changed = true; - } - } - _sapp_call_event(&_sapp.event); - return true; -} - -_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { - if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { - return false; - } - if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { - /* FIXME: this should be hooked into a "really quit?" mechanism - so the app can ask the user for confirmation, this is currently - generally missing in sokol_app.h - */ - _sapp_android_shutdown(); - return true; - } - return false; -} - -_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { - _SOKOL_UNUSED(fd); - _SOKOL_UNUSED(data); - if ((events & ALOOPER_EVENT_INPUT) == 0) { - _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); - return 1; - } - SOKOL_ASSERT(_sapp.android.current.input); - AInputEvent* event = NULL; - while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { - if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { - continue; - } - int32_t handled = 0; - if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { - handled = 1; - } - AInputQueue_finishEvent(_sapp.android.current.input, event, handled); - } - return 1; -} - -_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { - _SOKOL_UNUSED(data); - if ((events & ALOOPER_EVENT_INPUT) == 0) { - _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); - return 1; - } - - _sapp_android_msg_t msg; - if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { - _SAPP_ERROR(ANDROID_READ_MSG_FAILED); - return 1; - } - - pthread_mutex_lock(&_sapp.android.pt.mutex); - switch (msg) { - case _SOKOL_ANDROID_MSG_CREATE: - { - _SAPP_INFO(ANDROID_MSG_CREATE); - SOKOL_ASSERT(!_sapp.valid); - bool result = _sapp_android_init_egl(); - SOKOL_ASSERT(result); _SOKOL_UNUSED(result); - _sapp.valid = true; - _sapp.android.has_created = true; - } - break; - case _SOKOL_ANDROID_MSG_RESUME: - _SAPP_INFO(ANDROID_MSG_RESUME); - _sapp.android.has_resumed = true; - _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); - break; - case _SOKOL_ANDROID_MSG_PAUSE: - _SAPP_INFO(ANDROID_MSG_PAUSE); - _sapp.android.has_resumed = false; - _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); - break; - case _SOKOL_ANDROID_MSG_FOCUS: - _SAPP_INFO(ANDROID_MSG_FOCUS); - _sapp.android.has_focus = true; - break; - case _SOKOL_ANDROID_MSG_NO_FOCUS: - _SAPP_INFO(ANDROID_MSG_NO_FOCUS); - _sapp.android.has_focus = false; - break; - case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: - _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); - if (_sapp.android.current.window != _sapp.android.pending.window) { - if (_sapp.android.current.window != NULL) { - _sapp_android_cleanup_egl_surface(); - } - if (_sapp.android.pending.window != NULL) { - if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { - _sapp_android_update_dimensions(_sapp.android.pending.window, true); - } else { - _sapp_android_shutdown(); - } - } - } - _sapp.android.current.window = _sapp.android.pending.window; - break; - case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: - _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); - if (_sapp.android.current.input != _sapp.android.pending.input) { - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - if (_sapp.android.pending.input != NULL) { - AInputQueue_attachLooper( - _sapp.android.pending.input, - _sapp.android.looper, - ALOOPER_POLL_CALLBACK, - _sapp_android_input_cb, - NULL); /* data */ - } - } - _sapp.android.current.input = _sapp.android.pending.input; - break; - case _SOKOL_ANDROID_MSG_DESTROY: - _SAPP_INFO(ANDROID_MSG_DESTROY); - _sapp_android_cleanup(); - _sapp.valid = false; - _sapp.android.is_thread_stopping = true; - break; - default: - _SAPP_WARN(ANDROID_UNKNOWN_MSG); - break; - } - pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ - pthread_mutex_unlock(&_sapp.android.pt.mutex); - return 1; -} - -_SOKOL_PRIVATE bool _sapp_android_should_update(void) { - bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; - bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; - return is_in_front && has_surface; -} - -_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { - SOKOL_ASSERT(_sapp.valid); - /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ - if (shown) { - ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); - } else { - ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); - } -} - -_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { - _SOKOL_UNUSED(arg); - _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); - - _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); - ALooper_addFd(_sapp.android.looper, - _sapp.android.pt.read_from_main_fd, - ALOOPER_POLL_CALLBACK, - ALOOPER_EVENT_INPUT, - _sapp_android_main_cb, - NULL); /* data */ - - /* signal start to main thread */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_started = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* main loop */ - while (!_sapp.android.is_thread_stopping) { - /* sokol frame */ - if (_sapp_android_should_update()) { - _sapp_android_frame(); - } - - /* process all events (or stop early if app is requested to quit) */ - bool process_events = true; - while (process_events && !_sapp.android.is_thread_stopping) { - bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); - process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; - } - } - - /* cleanup thread */ - if (_sapp.android.current.input != NULL) { - AInputQueue_detachLooper(_sapp.android.current.input); - } - - /* the following causes heap corruption on exit, why?? - ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); - ALooper_release(_sapp.android.looper);*/ - - /* signal "destroyed" */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.is_thread_stopped = true; - pthread_cond_broadcast(&_sapp.android.pt.cond); - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); - return NULL; -} - -/* android main/ui thread */ -_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { - if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { - _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); -} - -_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); - _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); -} - -_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); - *out_size = 0; - return NULL; -} - -_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); - if (has_focus) { - _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); - } else { - _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); - } -} - -_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); - _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); -} - -_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.window = window; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); - while (_sapp.android.current.window != window) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); - _sapp_android_msg_set_native_window(window); -} - -_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { - _SOKOL_UNUSED(activity); - _SOKOL_UNUSED(window); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); - _sapp_android_msg_set_native_window(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp.android.pending.input = input; - _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); - while (_sapp.android.current.input != input) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); - _sapp_android_msg_set_input_queue(queue); -} - -_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { - _SOKOL_UNUSED(activity); - _SOKOL_UNUSED(queue); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); - _sapp_android_msg_set_input_queue(NULL); -} - -_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); - /* see android:configChanges in manifest */ -} - -_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); -} - -_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { - /* - * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) - * on my device (Moto X 2nd gen) when the app is removed from the task view - * (TaskStackView: onTaskViewDismissed). - * - * However, if ANativeActivity_finish() is explicitly called from for example - * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? - */ - _SOKOL_UNUSED(activity); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); - - /* send destroy msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); - while (!_sapp.android.is_thread_stopped) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* clean up main thread */ - pthread_cond_destroy(&_sapp.android.pt.cond); - pthread_mutex_destroy(&_sapp.android.pt.mutex); - - close(_sapp.android.pt.read_from_main_fd); - close(_sapp.android.pt.write_from_main_fd); - - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); - - /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ - exit(0); -} - -JNIEXPORT -void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { - _SOKOL_UNUSED(saved_state); - _SOKOL_UNUSED(saved_state_size); - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); - - // the NativeActity pointer needs to be available inside sokol_main() - // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() - // will clear the global _sapp_t struct, so we need to initialize the native - // activity pointer twice, once before sokol_main() and once after _sapp_init_state() - _sapp_clear(&_sapp, sizeof(_sapp)); - _sapp.android.activity = activity; - sapp_desc desc = sokol_main(0, NULL); - _sapp_init_state(&desc); - _sapp.android.activity = activity; - - int pipe_fd[2]; - if (pipe(pipe_fd) != 0) { - _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); - return; - } - _sapp.android.pt.read_from_main_fd = pipe_fd[0]; - _sapp.android.pt.write_from_main_fd = pipe_fd[1]; - - pthread_mutex_init(&_sapp.android.pt.mutex, NULL); - pthread_cond_init(&_sapp.android.pt.cond, NULL); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); - pthread_attr_destroy(&attr); - - /* wait until main loop has started */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - while (!_sapp.android.is_thread_started) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* send create msg */ - pthread_mutex_lock(&_sapp.android.pt.mutex); - _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); - while (!_sapp.android.has_created) { - pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); - } - pthread_mutex_unlock(&_sapp.android.pt.mutex); - - /* register for callbacks */ - activity->callbacks->onStart = _sapp_android_on_start; - activity->callbacks->onResume = _sapp_android_on_resume; - activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; - activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; - activity->callbacks->onPause = _sapp_android_on_pause; - activity->callbacks->onStop = _sapp_android_on_stop; - activity->callbacks->onDestroy = _sapp_android_on_destroy; - activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; - /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ - /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ - activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; - activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; - activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; - /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ - activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; - activity->callbacks->onLowMemory = _sapp_android_on_low_memory; - - _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); - - /* NOT A BUG: do NOT call sapp_discard_state() */ -} - -#endif /* _SAPP_ANDROID */ - -// ██ ██ ███ ██ ██ ██ ██ ██ -// ██ ██ ████ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ████ ██████ ██ ██ -// -// >>linux -#if defined(_SAPP_LINUX) - -/* see GLFW's xkb_unicode.c */ -static const struct _sapp_x11_codepair { - uint16_t keysym; - uint16_t ucs; -} _sapp_x11_keysymtab[] = { - { 0x01a1, 0x0104 }, - { 0x01a2, 0x02d8 }, - { 0x01a3, 0x0141 }, - { 0x01a5, 0x013d }, - { 0x01a6, 0x015a }, - { 0x01a9, 0x0160 }, - { 0x01aa, 0x015e }, - { 0x01ab, 0x0164 }, - { 0x01ac, 0x0179 }, - { 0x01ae, 0x017d }, - { 0x01af, 0x017b }, - { 0x01b1, 0x0105 }, - { 0x01b2, 0x02db }, - { 0x01b3, 0x0142 }, - { 0x01b5, 0x013e }, - { 0x01b6, 0x015b }, - { 0x01b7, 0x02c7 }, - { 0x01b9, 0x0161 }, - { 0x01ba, 0x015f }, - { 0x01bb, 0x0165 }, - { 0x01bc, 0x017a }, - { 0x01bd, 0x02dd }, - { 0x01be, 0x017e }, - { 0x01bf, 0x017c }, - { 0x01c0, 0x0154 }, - { 0x01c3, 0x0102 }, - { 0x01c5, 0x0139 }, - { 0x01c6, 0x0106 }, - { 0x01c8, 0x010c }, - { 0x01ca, 0x0118 }, - { 0x01cc, 0x011a }, - { 0x01cf, 0x010e }, - { 0x01d0, 0x0110 }, - { 0x01d1, 0x0143 }, - { 0x01d2, 0x0147 }, - { 0x01d5, 0x0150 }, - { 0x01d8, 0x0158 }, - { 0x01d9, 0x016e }, - { 0x01db, 0x0170 }, - { 0x01de, 0x0162 }, - { 0x01e0, 0x0155 }, - { 0x01e3, 0x0103 }, - { 0x01e5, 0x013a }, - { 0x01e6, 0x0107 }, - { 0x01e8, 0x010d }, - { 0x01ea, 0x0119 }, - { 0x01ec, 0x011b }, - { 0x01ef, 0x010f }, - { 0x01f0, 0x0111 }, - { 0x01f1, 0x0144 }, - { 0x01f2, 0x0148 }, - { 0x01f5, 0x0151 }, - { 0x01f8, 0x0159 }, - { 0x01f9, 0x016f }, - { 0x01fb, 0x0171 }, - { 0x01fe, 0x0163 }, - { 0x01ff, 0x02d9 }, - { 0x02a1, 0x0126 }, - { 0x02a6, 0x0124 }, - { 0x02a9, 0x0130 }, - { 0x02ab, 0x011e }, - { 0x02ac, 0x0134 }, - { 0x02b1, 0x0127 }, - { 0x02b6, 0x0125 }, - { 0x02b9, 0x0131 }, - { 0x02bb, 0x011f }, - { 0x02bc, 0x0135 }, - { 0x02c5, 0x010a }, - { 0x02c6, 0x0108 }, - { 0x02d5, 0x0120 }, - { 0x02d8, 0x011c }, - { 0x02dd, 0x016c }, - { 0x02de, 0x015c }, - { 0x02e5, 0x010b }, - { 0x02e6, 0x0109 }, - { 0x02f5, 0x0121 }, - { 0x02f8, 0x011d }, - { 0x02fd, 0x016d }, - { 0x02fe, 0x015d }, - { 0x03a2, 0x0138 }, - { 0x03a3, 0x0156 }, - { 0x03a5, 0x0128 }, - { 0x03a6, 0x013b }, - { 0x03aa, 0x0112 }, - { 0x03ab, 0x0122 }, - { 0x03ac, 0x0166 }, - { 0x03b3, 0x0157 }, - { 0x03b5, 0x0129 }, - { 0x03b6, 0x013c }, - { 0x03ba, 0x0113 }, - { 0x03bb, 0x0123 }, - { 0x03bc, 0x0167 }, - { 0x03bd, 0x014a }, - { 0x03bf, 0x014b }, - { 0x03c0, 0x0100 }, - { 0x03c7, 0x012e }, - { 0x03cc, 0x0116 }, - { 0x03cf, 0x012a }, - { 0x03d1, 0x0145 }, - { 0x03d2, 0x014c }, - { 0x03d3, 0x0136 }, - { 0x03d9, 0x0172 }, - { 0x03dd, 0x0168 }, - { 0x03de, 0x016a }, - { 0x03e0, 0x0101 }, - { 0x03e7, 0x012f }, - { 0x03ec, 0x0117 }, - { 0x03ef, 0x012b }, - { 0x03f1, 0x0146 }, - { 0x03f2, 0x014d }, - { 0x03f3, 0x0137 }, - { 0x03f9, 0x0173 }, - { 0x03fd, 0x0169 }, - { 0x03fe, 0x016b }, - { 0x047e, 0x203e }, - { 0x04a1, 0x3002 }, - { 0x04a2, 0x300c }, - { 0x04a3, 0x300d }, - { 0x04a4, 0x3001 }, - { 0x04a5, 0x30fb }, - { 0x04a6, 0x30f2 }, - { 0x04a7, 0x30a1 }, - { 0x04a8, 0x30a3 }, - { 0x04a9, 0x30a5 }, - { 0x04aa, 0x30a7 }, - { 0x04ab, 0x30a9 }, - { 0x04ac, 0x30e3 }, - { 0x04ad, 0x30e5 }, - { 0x04ae, 0x30e7 }, - { 0x04af, 0x30c3 }, - { 0x04b0, 0x30fc }, - { 0x04b1, 0x30a2 }, - { 0x04b2, 0x30a4 }, - { 0x04b3, 0x30a6 }, - { 0x04b4, 0x30a8 }, - { 0x04b5, 0x30aa }, - { 0x04b6, 0x30ab }, - { 0x04b7, 0x30ad }, - { 0x04b8, 0x30af }, - { 0x04b9, 0x30b1 }, - { 0x04ba, 0x30b3 }, - { 0x04bb, 0x30b5 }, - { 0x04bc, 0x30b7 }, - { 0x04bd, 0x30b9 }, - { 0x04be, 0x30bb }, - { 0x04bf, 0x30bd }, - { 0x04c0, 0x30bf }, - { 0x04c1, 0x30c1 }, - { 0x04c2, 0x30c4 }, - { 0x04c3, 0x30c6 }, - { 0x04c4, 0x30c8 }, - { 0x04c5, 0x30ca }, - { 0x04c6, 0x30cb }, - { 0x04c7, 0x30cc }, - { 0x04c8, 0x30cd }, - { 0x04c9, 0x30ce }, - { 0x04ca, 0x30cf }, - { 0x04cb, 0x30d2 }, - { 0x04cc, 0x30d5 }, - { 0x04cd, 0x30d8 }, - { 0x04ce, 0x30db }, - { 0x04cf, 0x30de }, - { 0x04d0, 0x30df }, - { 0x04d1, 0x30e0 }, - { 0x04d2, 0x30e1 }, - { 0x04d3, 0x30e2 }, - { 0x04d4, 0x30e4 }, - { 0x04d5, 0x30e6 }, - { 0x04d6, 0x30e8 }, - { 0x04d7, 0x30e9 }, - { 0x04d8, 0x30ea }, - { 0x04d9, 0x30eb }, - { 0x04da, 0x30ec }, - { 0x04db, 0x30ed }, - { 0x04dc, 0x30ef }, - { 0x04dd, 0x30f3 }, - { 0x04de, 0x309b }, - { 0x04df, 0x309c }, - { 0x05ac, 0x060c }, - { 0x05bb, 0x061b }, - { 0x05bf, 0x061f }, - { 0x05c1, 0x0621 }, - { 0x05c2, 0x0622 }, - { 0x05c3, 0x0623 }, - { 0x05c4, 0x0624 }, - { 0x05c5, 0x0625 }, - { 0x05c6, 0x0626 }, - { 0x05c7, 0x0627 }, - { 0x05c8, 0x0628 }, - { 0x05c9, 0x0629 }, - { 0x05ca, 0x062a }, - { 0x05cb, 0x062b }, - { 0x05cc, 0x062c }, - { 0x05cd, 0x062d }, - { 0x05ce, 0x062e }, - { 0x05cf, 0x062f }, - { 0x05d0, 0x0630 }, - { 0x05d1, 0x0631 }, - { 0x05d2, 0x0632 }, - { 0x05d3, 0x0633 }, - { 0x05d4, 0x0634 }, - { 0x05d5, 0x0635 }, - { 0x05d6, 0x0636 }, - { 0x05d7, 0x0637 }, - { 0x05d8, 0x0638 }, - { 0x05d9, 0x0639 }, - { 0x05da, 0x063a }, - { 0x05e0, 0x0640 }, - { 0x05e1, 0x0641 }, - { 0x05e2, 0x0642 }, - { 0x05e3, 0x0643 }, - { 0x05e4, 0x0644 }, - { 0x05e5, 0x0645 }, - { 0x05e6, 0x0646 }, - { 0x05e7, 0x0647 }, - { 0x05e8, 0x0648 }, - { 0x05e9, 0x0649 }, - { 0x05ea, 0x064a }, - { 0x05eb, 0x064b }, - { 0x05ec, 0x064c }, - { 0x05ed, 0x064d }, - { 0x05ee, 0x064e }, - { 0x05ef, 0x064f }, - { 0x05f0, 0x0650 }, - { 0x05f1, 0x0651 }, - { 0x05f2, 0x0652 }, - { 0x06a1, 0x0452 }, - { 0x06a2, 0x0453 }, - { 0x06a3, 0x0451 }, - { 0x06a4, 0x0454 }, - { 0x06a5, 0x0455 }, - { 0x06a6, 0x0456 }, - { 0x06a7, 0x0457 }, - { 0x06a8, 0x0458 }, - { 0x06a9, 0x0459 }, - { 0x06aa, 0x045a }, - { 0x06ab, 0x045b }, - { 0x06ac, 0x045c }, - { 0x06ae, 0x045e }, - { 0x06af, 0x045f }, - { 0x06b0, 0x2116 }, - { 0x06b1, 0x0402 }, - { 0x06b2, 0x0403 }, - { 0x06b3, 0x0401 }, - { 0x06b4, 0x0404 }, - { 0x06b5, 0x0405 }, - { 0x06b6, 0x0406 }, - { 0x06b7, 0x0407 }, - { 0x06b8, 0x0408 }, - { 0x06b9, 0x0409 }, - { 0x06ba, 0x040a }, - { 0x06bb, 0x040b }, - { 0x06bc, 0x040c }, - { 0x06be, 0x040e }, - { 0x06bf, 0x040f }, - { 0x06c0, 0x044e }, - { 0x06c1, 0x0430 }, - { 0x06c2, 0x0431 }, - { 0x06c3, 0x0446 }, - { 0x06c4, 0x0434 }, - { 0x06c5, 0x0435 }, - { 0x06c6, 0x0444 }, - { 0x06c7, 0x0433 }, - { 0x06c8, 0x0445 }, - { 0x06c9, 0x0438 }, - { 0x06ca, 0x0439 }, - { 0x06cb, 0x043a }, - { 0x06cc, 0x043b }, - { 0x06cd, 0x043c }, - { 0x06ce, 0x043d }, - { 0x06cf, 0x043e }, - { 0x06d0, 0x043f }, - { 0x06d1, 0x044f }, - { 0x06d2, 0x0440 }, - { 0x06d3, 0x0441 }, - { 0x06d4, 0x0442 }, - { 0x06d5, 0x0443 }, - { 0x06d6, 0x0436 }, - { 0x06d7, 0x0432 }, - { 0x06d8, 0x044c }, - { 0x06d9, 0x044b }, - { 0x06da, 0x0437 }, - { 0x06db, 0x0448 }, - { 0x06dc, 0x044d }, - { 0x06dd, 0x0449 }, - { 0x06de, 0x0447 }, - { 0x06df, 0x044a }, - { 0x06e0, 0x042e }, - { 0x06e1, 0x0410 }, - { 0x06e2, 0x0411 }, - { 0x06e3, 0x0426 }, - { 0x06e4, 0x0414 }, - { 0x06e5, 0x0415 }, - { 0x06e6, 0x0424 }, - { 0x06e7, 0x0413 }, - { 0x06e8, 0x0425 }, - { 0x06e9, 0x0418 }, - { 0x06ea, 0x0419 }, - { 0x06eb, 0x041a }, - { 0x06ec, 0x041b }, - { 0x06ed, 0x041c }, - { 0x06ee, 0x041d }, - { 0x06ef, 0x041e }, - { 0x06f0, 0x041f }, - { 0x06f1, 0x042f }, - { 0x06f2, 0x0420 }, - { 0x06f3, 0x0421 }, - { 0x06f4, 0x0422 }, - { 0x06f5, 0x0423 }, - { 0x06f6, 0x0416 }, - { 0x06f7, 0x0412 }, - { 0x06f8, 0x042c }, - { 0x06f9, 0x042b }, - { 0x06fa, 0x0417 }, - { 0x06fb, 0x0428 }, - { 0x06fc, 0x042d }, - { 0x06fd, 0x0429 }, - { 0x06fe, 0x0427 }, - { 0x06ff, 0x042a }, - { 0x07a1, 0x0386 }, - { 0x07a2, 0x0388 }, - { 0x07a3, 0x0389 }, - { 0x07a4, 0x038a }, - { 0x07a5, 0x03aa }, - { 0x07a7, 0x038c }, - { 0x07a8, 0x038e }, - { 0x07a9, 0x03ab }, - { 0x07ab, 0x038f }, - { 0x07ae, 0x0385 }, - { 0x07af, 0x2015 }, - { 0x07b1, 0x03ac }, - { 0x07b2, 0x03ad }, - { 0x07b3, 0x03ae }, - { 0x07b4, 0x03af }, - { 0x07b5, 0x03ca }, - { 0x07b6, 0x0390 }, - { 0x07b7, 0x03cc }, - { 0x07b8, 0x03cd }, - { 0x07b9, 0x03cb }, - { 0x07ba, 0x03b0 }, - { 0x07bb, 0x03ce }, - { 0x07c1, 0x0391 }, - { 0x07c2, 0x0392 }, - { 0x07c3, 0x0393 }, - { 0x07c4, 0x0394 }, - { 0x07c5, 0x0395 }, - { 0x07c6, 0x0396 }, - { 0x07c7, 0x0397 }, - { 0x07c8, 0x0398 }, - { 0x07c9, 0x0399 }, - { 0x07ca, 0x039a }, - { 0x07cb, 0x039b }, - { 0x07cc, 0x039c }, - { 0x07cd, 0x039d }, - { 0x07ce, 0x039e }, - { 0x07cf, 0x039f }, - { 0x07d0, 0x03a0 }, - { 0x07d1, 0x03a1 }, - { 0x07d2, 0x03a3 }, - { 0x07d4, 0x03a4 }, - { 0x07d5, 0x03a5 }, - { 0x07d6, 0x03a6 }, - { 0x07d7, 0x03a7 }, - { 0x07d8, 0x03a8 }, - { 0x07d9, 0x03a9 }, - { 0x07e1, 0x03b1 }, - { 0x07e2, 0x03b2 }, - { 0x07e3, 0x03b3 }, - { 0x07e4, 0x03b4 }, - { 0x07e5, 0x03b5 }, - { 0x07e6, 0x03b6 }, - { 0x07e7, 0x03b7 }, - { 0x07e8, 0x03b8 }, - { 0x07e9, 0x03b9 }, - { 0x07ea, 0x03ba }, - { 0x07eb, 0x03bb }, - { 0x07ec, 0x03bc }, - { 0x07ed, 0x03bd }, - { 0x07ee, 0x03be }, - { 0x07ef, 0x03bf }, - { 0x07f0, 0x03c0 }, - { 0x07f1, 0x03c1 }, - { 0x07f2, 0x03c3 }, - { 0x07f3, 0x03c2 }, - { 0x07f4, 0x03c4 }, - { 0x07f5, 0x03c5 }, - { 0x07f6, 0x03c6 }, - { 0x07f7, 0x03c7 }, - { 0x07f8, 0x03c8 }, - { 0x07f9, 0x03c9 }, - { 0x08a1, 0x23b7 }, - { 0x08a2, 0x250c }, - { 0x08a3, 0x2500 }, - { 0x08a4, 0x2320 }, - { 0x08a5, 0x2321 }, - { 0x08a6, 0x2502 }, - { 0x08a7, 0x23a1 }, - { 0x08a8, 0x23a3 }, - { 0x08a9, 0x23a4 }, - { 0x08aa, 0x23a6 }, - { 0x08ab, 0x239b }, - { 0x08ac, 0x239d }, - { 0x08ad, 0x239e }, - { 0x08ae, 0x23a0 }, - { 0x08af, 0x23a8 }, - { 0x08b0, 0x23ac }, - { 0x08bc, 0x2264 }, - { 0x08bd, 0x2260 }, - { 0x08be, 0x2265 }, - { 0x08bf, 0x222b }, - { 0x08c0, 0x2234 }, - { 0x08c1, 0x221d }, - { 0x08c2, 0x221e }, - { 0x08c5, 0x2207 }, - { 0x08c8, 0x223c }, - { 0x08c9, 0x2243 }, - { 0x08cd, 0x21d4 }, - { 0x08ce, 0x21d2 }, - { 0x08cf, 0x2261 }, - { 0x08d6, 0x221a }, - { 0x08da, 0x2282 }, - { 0x08db, 0x2283 }, - { 0x08dc, 0x2229 }, - { 0x08dd, 0x222a }, - { 0x08de, 0x2227 }, - { 0x08df, 0x2228 }, - { 0x08ef, 0x2202 }, - { 0x08f6, 0x0192 }, - { 0x08fb, 0x2190 }, - { 0x08fc, 0x2191 }, - { 0x08fd, 0x2192 }, - { 0x08fe, 0x2193 }, - { 0x09e0, 0x25c6 }, - { 0x09e1, 0x2592 }, - { 0x09e2, 0x2409 }, - { 0x09e3, 0x240c }, - { 0x09e4, 0x240d }, - { 0x09e5, 0x240a }, - { 0x09e8, 0x2424 }, - { 0x09e9, 0x240b }, - { 0x09ea, 0x2518 }, - { 0x09eb, 0x2510 }, - { 0x09ec, 0x250c }, - { 0x09ed, 0x2514 }, - { 0x09ee, 0x253c }, - { 0x09ef, 0x23ba }, - { 0x09f0, 0x23bb }, - { 0x09f1, 0x2500 }, - { 0x09f2, 0x23bc }, - { 0x09f3, 0x23bd }, - { 0x09f4, 0x251c }, - { 0x09f5, 0x2524 }, - { 0x09f6, 0x2534 }, - { 0x09f7, 0x252c }, - { 0x09f8, 0x2502 }, - { 0x0aa1, 0x2003 }, - { 0x0aa2, 0x2002 }, - { 0x0aa3, 0x2004 }, - { 0x0aa4, 0x2005 }, - { 0x0aa5, 0x2007 }, - { 0x0aa6, 0x2008 }, - { 0x0aa7, 0x2009 }, - { 0x0aa8, 0x200a }, - { 0x0aa9, 0x2014 }, - { 0x0aaa, 0x2013 }, - { 0x0aae, 0x2026 }, - { 0x0aaf, 0x2025 }, - { 0x0ab0, 0x2153 }, - { 0x0ab1, 0x2154 }, - { 0x0ab2, 0x2155 }, - { 0x0ab3, 0x2156 }, - { 0x0ab4, 0x2157 }, - { 0x0ab5, 0x2158 }, - { 0x0ab6, 0x2159 }, - { 0x0ab7, 0x215a }, - { 0x0ab8, 0x2105 }, - { 0x0abb, 0x2012 }, - { 0x0abc, 0x2329 }, - { 0x0abe, 0x232a }, - { 0x0ac3, 0x215b }, - { 0x0ac4, 0x215c }, - { 0x0ac5, 0x215d }, - { 0x0ac6, 0x215e }, - { 0x0ac9, 0x2122 }, - { 0x0aca, 0x2613 }, - { 0x0acc, 0x25c1 }, - { 0x0acd, 0x25b7 }, - { 0x0ace, 0x25cb }, - { 0x0acf, 0x25af }, - { 0x0ad0, 0x2018 }, - { 0x0ad1, 0x2019 }, - { 0x0ad2, 0x201c }, - { 0x0ad3, 0x201d }, - { 0x0ad4, 0x211e }, - { 0x0ad6, 0x2032 }, - { 0x0ad7, 0x2033 }, - { 0x0ad9, 0x271d }, - { 0x0adb, 0x25ac }, - { 0x0adc, 0x25c0 }, - { 0x0add, 0x25b6 }, - { 0x0ade, 0x25cf }, - { 0x0adf, 0x25ae }, - { 0x0ae0, 0x25e6 }, - { 0x0ae1, 0x25ab }, - { 0x0ae2, 0x25ad }, - { 0x0ae3, 0x25b3 }, - { 0x0ae4, 0x25bd }, - { 0x0ae5, 0x2606 }, - { 0x0ae6, 0x2022 }, - { 0x0ae7, 0x25aa }, - { 0x0ae8, 0x25b2 }, - { 0x0ae9, 0x25bc }, - { 0x0aea, 0x261c }, - { 0x0aeb, 0x261e }, - { 0x0aec, 0x2663 }, - { 0x0aed, 0x2666 }, - { 0x0aee, 0x2665 }, - { 0x0af0, 0x2720 }, - { 0x0af1, 0x2020 }, - { 0x0af2, 0x2021 }, - { 0x0af3, 0x2713 }, - { 0x0af4, 0x2717 }, - { 0x0af5, 0x266f }, - { 0x0af6, 0x266d }, - { 0x0af7, 0x2642 }, - { 0x0af8, 0x2640 }, - { 0x0af9, 0x260e }, - { 0x0afa, 0x2315 }, - { 0x0afb, 0x2117 }, - { 0x0afc, 0x2038 }, - { 0x0afd, 0x201a }, - { 0x0afe, 0x201e }, - { 0x0ba3, 0x003c }, - { 0x0ba6, 0x003e }, - { 0x0ba8, 0x2228 }, - { 0x0ba9, 0x2227 }, - { 0x0bc0, 0x00af }, - { 0x0bc2, 0x22a5 }, - { 0x0bc3, 0x2229 }, - { 0x0bc4, 0x230a }, - { 0x0bc6, 0x005f }, - { 0x0bca, 0x2218 }, - { 0x0bcc, 0x2395 }, - { 0x0bce, 0x22a4 }, - { 0x0bcf, 0x25cb }, - { 0x0bd3, 0x2308 }, - { 0x0bd6, 0x222a }, - { 0x0bd8, 0x2283 }, - { 0x0bda, 0x2282 }, - { 0x0bdc, 0x22a2 }, - { 0x0bfc, 0x22a3 }, - { 0x0cdf, 0x2017 }, - { 0x0ce0, 0x05d0 }, - { 0x0ce1, 0x05d1 }, - { 0x0ce2, 0x05d2 }, - { 0x0ce3, 0x05d3 }, - { 0x0ce4, 0x05d4 }, - { 0x0ce5, 0x05d5 }, - { 0x0ce6, 0x05d6 }, - { 0x0ce7, 0x05d7 }, - { 0x0ce8, 0x05d8 }, - { 0x0ce9, 0x05d9 }, - { 0x0cea, 0x05da }, - { 0x0ceb, 0x05db }, - { 0x0cec, 0x05dc }, - { 0x0ced, 0x05dd }, - { 0x0cee, 0x05de }, - { 0x0cef, 0x05df }, - { 0x0cf0, 0x05e0 }, - { 0x0cf1, 0x05e1 }, - { 0x0cf2, 0x05e2 }, - { 0x0cf3, 0x05e3 }, - { 0x0cf4, 0x05e4 }, - { 0x0cf5, 0x05e5 }, - { 0x0cf6, 0x05e6 }, - { 0x0cf7, 0x05e7 }, - { 0x0cf8, 0x05e8 }, - { 0x0cf9, 0x05e9 }, - { 0x0cfa, 0x05ea }, - { 0x0da1, 0x0e01 }, - { 0x0da2, 0x0e02 }, - { 0x0da3, 0x0e03 }, - { 0x0da4, 0x0e04 }, - { 0x0da5, 0x0e05 }, - { 0x0da6, 0x0e06 }, - { 0x0da7, 0x0e07 }, - { 0x0da8, 0x0e08 }, - { 0x0da9, 0x0e09 }, - { 0x0daa, 0x0e0a }, - { 0x0dab, 0x0e0b }, - { 0x0dac, 0x0e0c }, - { 0x0dad, 0x0e0d }, - { 0x0dae, 0x0e0e }, - { 0x0daf, 0x0e0f }, - { 0x0db0, 0x0e10 }, - { 0x0db1, 0x0e11 }, - { 0x0db2, 0x0e12 }, - { 0x0db3, 0x0e13 }, - { 0x0db4, 0x0e14 }, - { 0x0db5, 0x0e15 }, - { 0x0db6, 0x0e16 }, - { 0x0db7, 0x0e17 }, - { 0x0db8, 0x0e18 }, - { 0x0db9, 0x0e19 }, - { 0x0dba, 0x0e1a }, - { 0x0dbb, 0x0e1b }, - { 0x0dbc, 0x0e1c }, - { 0x0dbd, 0x0e1d }, - { 0x0dbe, 0x0e1e }, - { 0x0dbf, 0x0e1f }, - { 0x0dc0, 0x0e20 }, - { 0x0dc1, 0x0e21 }, - { 0x0dc2, 0x0e22 }, - { 0x0dc3, 0x0e23 }, - { 0x0dc4, 0x0e24 }, - { 0x0dc5, 0x0e25 }, - { 0x0dc6, 0x0e26 }, - { 0x0dc7, 0x0e27 }, - { 0x0dc8, 0x0e28 }, - { 0x0dc9, 0x0e29 }, - { 0x0dca, 0x0e2a }, - { 0x0dcb, 0x0e2b }, - { 0x0dcc, 0x0e2c }, - { 0x0dcd, 0x0e2d }, - { 0x0dce, 0x0e2e }, - { 0x0dcf, 0x0e2f }, - { 0x0dd0, 0x0e30 }, - { 0x0dd1, 0x0e31 }, - { 0x0dd2, 0x0e32 }, - { 0x0dd3, 0x0e33 }, - { 0x0dd4, 0x0e34 }, - { 0x0dd5, 0x0e35 }, - { 0x0dd6, 0x0e36 }, - { 0x0dd7, 0x0e37 }, - { 0x0dd8, 0x0e38 }, - { 0x0dd9, 0x0e39 }, - { 0x0dda, 0x0e3a }, - { 0x0ddf, 0x0e3f }, - { 0x0de0, 0x0e40 }, - { 0x0de1, 0x0e41 }, - { 0x0de2, 0x0e42 }, - { 0x0de3, 0x0e43 }, - { 0x0de4, 0x0e44 }, - { 0x0de5, 0x0e45 }, - { 0x0de6, 0x0e46 }, - { 0x0de7, 0x0e47 }, - { 0x0de8, 0x0e48 }, - { 0x0de9, 0x0e49 }, - { 0x0dea, 0x0e4a }, - { 0x0deb, 0x0e4b }, - { 0x0dec, 0x0e4c }, - { 0x0ded, 0x0e4d }, - { 0x0df0, 0x0e50 }, - { 0x0df1, 0x0e51 }, - { 0x0df2, 0x0e52 }, - { 0x0df3, 0x0e53 }, - { 0x0df4, 0x0e54 }, - { 0x0df5, 0x0e55 }, - { 0x0df6, 0x0e56 }, - { 0x0df7, 0x0e57 }, - { 0x0df8, 0x0e58 }, - { 0x0df9, 0x0e59 }, - { 0x0ea1, 0x3131 }, - { 0x0ea2, 0x3132 }, - { 0x0ea3, 0x3133 }, - { 0x0ea4, 0x3134 }, - { 0x0ea5, 0x3135 }, - { 0x0ea6, 0x3136 }, - { 0x0ea7, 0x3137 }, - { 0x0ea8, 0x3138 }, - { 0x0ea9, 0x3139 }, - { 0x0eaa, 0x313a }, - { 0x0eab, 0x313b }, - { 0x0eac, 0x313c }, - { 0x0ead, 0x313d }, - { 0x0eae, 0x313e }, - { 0x0eaf, 0x313f }, - { 0x0eb0, 0x3140 }, - { 0x0eb1, 0x3141 }, - { 0x0eb2, 0x3142 }, - { 0x0eb3, 0x3143 }, - { 0x0eb4, 0x3144 }, - { 0x0eb5, 0x3145 }, - { 0x0eb6, 0x3146 }, - { 0x0eb7, 0x3147 }, - { 0x0eb8, 0x3148 }, - { 0x0eb9, 0x3149 }, - { 0x0eba, 0x314a }, - { 0x0ebb, 0x314b }, - { 0x0ebc, 0x314c }, - { 0x0ebd, 0x314d }, - { 0x0ebe, 0x314e }, - { 0x0ebf, 0x314f }, - { 0x0ec0, 0x3150 }, - { 0x0ec1, 0x3151 }, - { 0x0ec2, 0x3152 }, - { 0x0ec3, 0x3153 }, - { 0x0ec4, 0x3154 }, - { 0x0ec5, 0x3155 }, - { 0x0ec6, 0x3156 }, - { 0x0ec7, 0x3157 }, - { 0x0ec8, 0x3158 }, - { 0x0ec9, 0x3159 }, - { 0x0eca, 0x315a }, - { 0x0ecb, 0x315b }, - { 0x0ecc, 0x315c }, - { 0x0ecd, 0x315d }, - { 0x0ece, 0x315e }, - { 0x0ecf, 0x315f }, - { 0x0ed0, 0x3160 }, - { 0x0ed1, 0x3161 }, - { 0x0ed2, 0x3162 }, - { 0x0ed3, 0x3163 }, - { 0x0ed4, 0x11a8 }, - { 0x0ed5, 0x11a9 }, - { 0x0ed6, 0x11aa }, - { 0x0ed7, 0x11ab }, - { 0x0ed8, 0x11ac }, - { 0x0ed9, 0x11ad }, - { 0x0eda, 0x11ae }, - { 0x0edb, 0x11af }, - { 0x0edc, 0x11b0 }, - { 0x0edd, 0x11b1 }, - { 0x0ede, 0x11b2 }, - { 0x0edf, 0x11b3 }, - { 0x0ee0, 0x11b4 }, - { 0x0ee1, 0x11b5 }, - { 0x0ee2, 0x11b6 }, - { 0x0ee3, 0x11b7 }, - { 0x0ee4, 0x11b8 }, - { 0x0ee5, 0x11b9 }, - { 0x0ee6, 0x11ba }, - { 0x0ee7, 0x11bb }, - { 0x0ee8, 0x11bc }, - { 0x0ee9, 0x11bd }, - { 0x0eea, 0x11be }, - { 0x0eeb, 0x11bf }, - { 0x0eec, 0x11c0 }, - { 0x0eed, 0x11c1 }, - { 0x0eee, 0x11c2 }, - { 0x0eef, 0x316d }, - { 0x0ef0, 0x3171 }, - { 0x0ef1, 0x3178 }, - { 0x0ef2, 0x317f }, - { 0x0ef3, 0x3181 }, - { 0x0ef4, 0x3184 }, - { 0x0ef5, 0x3186 }, - { 0x0ef6, 0x318d }, - { 0x0ef7, 0x318e }, - { 0x0ef8, 0x11eb }, - { 0x0ef9, 0x11f0 }, - { 0x0efa, 0x11f9 }, - { 0x0eff, 0x20a9 }, - { 0x13a4, 0x20ac }, - { 0x13bc, 0x0152 }, - { 0x13bd, 0x0153 }, - { 0x13be, 0x0178 }, - { 0x20ac, 0x20ac }, - { 0xfe50, '`' }, - { 0xfe51, 0x00b4 }, - { 0xfe52, '^' }, - { 0xfe53, '~' }, - { 0xfe54, 0x00af }, - { 0xfe55, 0x02d8 }, - { 0xfe56, 0x02d9 }, - { 0xfe57, 0x00a8 }, - { 0xfe58, 0x02da }, - { 0xfe59, 0x02dd }, - { 0xfe5a, 0x02c7 }, - { 0xfe5b, 0x00b8 }, - { 0xfe5c, 0x02db }, - { 0xfe5d, 0x037a }, - { 0xfe5e, 0x309b }, - { 0xfe5f, 0x309c }, - { 0xfe63, '/' }, - { 0xfe64, 0x02bc }, - { 0xfe65, 0x02bd }, - { 0xfe66, 0x02f5 }, - { 0xfe67, 0x02f3 }, - { 0xfe68, 0x02cd }, - { 0xfe69, 0xa788 }, - { 0xfe6a, 0x02f7 }, - { 0xfe6e, ',' }, - { 0xfe6f, 0x00a4 }, - { 0xfe80, 'a' }, /* XK_dead_a */ - { 0xfe81, 'A' }, /* XK_dead_A */ - { 0xfe82, 'e' }, /* XK_dead_e */ - { 0xfe83, 'E' }, /* XK_dead_E */ - { 0xfe84, 'i' }, /* XK_dead_i */ - { 0xfe85, 'I' }, /* XK_dead_I */ - { 0xfe86, 'o' }, /* XK_dead_o */ - { 0xfe87, 'O' }, /* XK_dead_O */ - { 0xfe88, 'u' }, /* XK_dead_u */ - { 0xfe89, 'U' }, /* XK_dead_U */ - { 0xfe8a, 0x0259 }, - { 0xfe8b, 0x018f }, - { 0xfe8c, 0x00b5 }, - { 0xfe90, '_' }, - { 0xfe91, 0x02c8 }, - { 0xfe92, 0x02cc }, - { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, - { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, - { 0xffab /*XKB_KEY_KP_Add*/, '+' }, - { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, - { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, - { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, - { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, - { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, - { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, - { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, - { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, - { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, - { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, - { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, - { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, - { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, - { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, - { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } -}; - -_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { - _SOKOL_UNUSED(display); - _sapp.x11.error_code = event->error_code; - return 0; -} - -_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { - _sapp.x11.error_code = Success; - XSetErrorHandler(_sapp_x11_error_handler); -} - -_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { - XSync(_sapp.x11.display, False); - XSetErrorHandler(NULL); -} - -_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { - _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); - _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); - _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); - _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); - _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); - _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); - _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); - _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); - _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); - if (_sapp.drop.enabled) { - _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); - _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); - _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); - _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); - _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); - _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); - _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); - _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); - _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); - _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); - } - - /* check Xi extension for raw mouse input */ - if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { - _sapp.x11.xi.major = 2; - _sapp.x11.xi.minor = 0; - if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { - _sapp.x11.xi.available = true; - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { - /* from GLFW: - - NOTE: Default to the display-wide DPI as we don't currently have a policy - for which monitor a window is considered to be on - - _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * - 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); - - NOTE: Basing the scale on Xft.dpi where available should provide the most - consistent user experience (matches Qt, Gtk, etc), although not - always the most accurate one - */ - bool dpi_ok = false; - char* rms = XResourceManagerString(_sapp.x11.display); - if (rms) { - XrmDatabase db = XrmGetStringDatabase(rms); - if (db) { - XrmValue value; - char* type = NULL; - if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { - if (type && strcmp(type, "String") == 0) { - _sapp.x11.dpi = atof(value.addr); - dpi_ok = true; - } - } - XrmDestroyDatabase(db); - } - } - // fallback if querying DPI had failed: assume the standard DPI 96.0f - if (!dpi_ok) { - _sapp.x11.dpi = 96.0f; - _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); - } -} - -#if defined(_SAPP_GLX) - -_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { - SOKOL_ASSERT(ext); - const char* start = extensions; - while (true) { - const char* where = strstr(start, ext); - if (!where) { - return false; - } - const char* terminator = where + strlen(ext); - if ((where == start) || (*(where - 1) == ' ')) { - if (*terminator == ' ' || *terminator == '\0') { - break; - } - } - start = terminator; - } - return true; -} - -_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { - if (extensions) { - return _sapp_glx_has_ext(ext, extensions); - } - else { - return false; - } -} - -_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) -{ - if (_sapp.glx.GetProcAddress) { - return (void*) _sapp.glx.GetProcAddress(procname); - } - else if (_sapp.glx.GetProcAddressARB) { - return (void*) _sapp.glx.GetProcAddressARB(procname); - } - else { - return dlsym(_sapp.glx.libgl, procname); - } -} - -_SOKOL_PRIVATE void _sapp_glx_init() { - const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; - for (int i = 0; sonames[i]; i++) { - _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); - if (_sapp.glx.libgl) { - break; - } - } - if (!_sapp.glx.libgl) { - _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); - } - _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); - _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); - _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); - _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); - _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); - _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); - _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); - _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); - _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); - _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); - _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); - _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); - _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); - _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); - if (!_sapp.glx.GetFBConfigs || - !_sapp.glx.GetFBConfigAttrib || - !_sapp.glx.GetClientString || - !_sapp.glx.QueryExtension || - !_sapp.glx.QueryVersion || - !_sapp.glx.DestroyContext || - !_sapp.glx.MakeCurrent || - !_sapp.glx.SwapBuffers || - !_sapp.glx.QueryExtensionsString || - !_sapp.glx.CreateWindow || - !_sapp.glx.DestroyWindow || - !_sapp.glx.GetProcAddress || - !_sapp.glx.GetProcAddressARB || - !_sapp.glx.GetVisualFromFBConfig) - { - _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); - } - - if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { - _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); - } - if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { - _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); - } - if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { - _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); - } - const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); - if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { - _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); - _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; - } - if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { - _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); - _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; - } - _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); - if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { - _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); - _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; - } - _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); -} - -_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { - int value; - _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); - return value; -} - -_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { - GLXFBConfig* native_configs; - _sapp_gl_fbconfig* usable_configs; - const _sapp_gl_fbconfig* closest; - int i, native_count, usable_count; - const char* vendor; - bool trust_window_bit = true; - - /* HACK: This is a (hopefully temporary) workaround for Chromium - (VirtualBox GL) not setting the window bit on any GLXFBConfigs - */ - vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); - if (vendor && strcmp(vendor, "Chromium") == 0) { - trust_window_bit = false; - } - - native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); - if (!native_configs || !native_count) { - _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); - } - - usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); - usable_count = 0; - for (i = 0; i < native_count; i++) { - const GLXFBConfig n = native_configs[i]; - _sapp_gl_fbconfig* u = usable_configs + usable_count; - _sapp_gl_init_fbconfig(u); - - /* Only consider RGBA GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { - continue; - } - /* Only consider window GLXFBConfigs */ - if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { - if (trust_window_bit) { - continue; - } - } - u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); - u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); - u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); - u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); - u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); - u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); - if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { - u->doublebuffer = true; - } - if (_sapp.glx.ARB_multisample) { - u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); - } - u->handle = (uintptr_t) n; - usable_count++; - } - _sapp_gl_fbconfig desired; - _sapp_gl_init_fbconfig(&desired); - desired.red_bits = 8; - desired.green_bits = 8; - desired.blue_bits = 8; - desired.alpha_bits = 8; - desired.depth_bits = 24; - desired.stencil_bits = 8; - desired.doublebuffer = true; - desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; - closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); - GLXFBConfig result = 0; - if (closest) { - result = (GLXFBConfig) closest->handle; - } - XFree(native_configs); - _sapp_free(usable_configs); - return result; -} - -_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native) { - _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); - } - XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); - if (!result) { - _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); - } - *visual = result->visual; - *depth = result->depth; - XFree(result); -} - -_SOKOL_PRIVATE void _sapp_glx_create_context(void) { - GLXFBConfig native = _sapp_glx_choosefbconfig(); - if (0 == native){ - _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); - } - if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { - _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); - } - _sapp_x11_grab_error_handler(); - const int attribs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, - GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - 0, 0 - }; - _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); - if (!_sapp.glx.ctx) { - _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); - } - _sapp_x11_release_error_handler(); - _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); - if (!_sapp.glx.window) { - _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); - } -} - -_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { - if (_sapp.glx.window) { - _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); - _sapp.glx.window = 0; - } - if (_sapp.glx.ctx) { - _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); - _sapp.glx.ctx = 0; - } -} - -_SOKOL_PRIVATE void _sapp_glx_make_current(void) { - _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); -} - -_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { - _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); -} - -_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { - _sapp_glx_make_current(); - if (_sapp.glx.EXT_swap_control) { - _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); - } - else if (_sapp.glx.MESA_swap_control) { - _sapp.glx.SwapIntervalMESA(interval); - } -} - -#endif /* _SAPP_GLX */ - -_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { - XEvent event; - _sapp_clear(&event, sizeof(event)); - - event.type = ClientMessage; - event.xclient.window = _sapp.x11.window; - event.xclient.format = 32; - event.xclient.message_type = type; - event.xclient.data.l[0] = a; - event.xclient.data.l[1] = b; - event.xclient.data.l[2] = c; - event.xclient.data.l[3] = d; - event.xclient.data.l[4] = e; - - XSendEvent(_sapp.x11.display, _sapp.x11.root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &event); -} - -_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { - XWindowAttributes attribs; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); - _sapp.window_width = attribs.width; - _sapp.window_height = attribs.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; -} - -_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { - /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ - if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { - if (enable) { - const int _NET_WM_STATE_ADD = 1; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_ADD, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - else { - const int _NET_WM_STATE_REMOVE = 0; - _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, - _NET_WM_STATE_REMOVE, - _sapp.x11.NET_WM_STATE_FULLSCREEN, - 0, 1, 0); - } - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { - SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); - const int w = 16; - const int h = 16; - XcursorImage* img = XcursorImageCreate(w, h); - SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); - img->xhot = 0; - img->yhot = 0; - const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); - _sapp_clear(img->pixels, num_bytes); - _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); - XcursorImageDestroy(img); -} - - _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - SOKOL_ASSERT(_sapp.x11.display); - if (theme) { - XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); - if (img) { - _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); - XcursorImageDestroy(img); - } - } - if (0 == _sapp.x11.cursors[cursor]) { - _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); - } -} - -_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { - SOKOL_ASSERT(_sapp.x11.display); - const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); - const int size = XcursorGetDefaultSize(_sapp.x11.display); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); - _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); - _sapp_x11_create_hidden_cursor(); -} - -_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { - SOKOL_ASSERT(_sapp.x11.display); - if (_sapp.x11.hidden_cursor) { - XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); - _sapp.x11.hidden_cursor = 0; - } - for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { - if (_sapp.x11.cursors[i]) { - XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); - _sapp.x11.cursors[i] = 0; - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { - _sapp.fullscreen = !_sapp.fullscreen; - _sapp_x11_set_fullscreen(_sapp.fullscreen); - _sapp_x11_query_window_size(); -} - -_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (shown) { - if (_sapp.x11.cursors[cursor]) { - XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); - } - else { - XUndefineCursor(_sapp.x11.display, _sapp.x11.window); - } - } - else { - XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { - if (lock == _sapp.mouse.locked) { - return; - } - _sapp.mouse.dx = 0.0f; - _sapp.mouse.dy = 0.0f; - _sapp.mouse.locked = lock; - if (_sapp.mouse.locked) { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISetMask(mask, XI_RawMotion); - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XGrabPointer(_sapp.x11.display, // display - _sapp.x11.window, // grab_window - True, // owner_events - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask - GrabModeAsync, // pointer_mode - GrabModeAsync, // keyboard_mode - _sapp.x11.window, // confine_to - _sapp.x11.hidden_cursor, // cursor - CurrentTime); // time - } - else { - if (_sapp.x11.xi.available) { - XIEventMask em; - unsigned char mask[] = { 0 }; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); - } - XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); - XUngrabPointer(_sapp.x11.display, CurrentTime); - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { - Xutf8SetWMProperties(_sapp.x11.display, - _sapp.x11.window, - _sapp.window_title, _sapp.window_title, - NULL, 0, NULL, NULL, NULL); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, - PropModeReplace, - (unsigned char*)_sapp.window_title, - strlen(_sapp.window_title)); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { - SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); - int long_count = 0; - for (int i = 0; i < num_images; i++) { - const sapp_image_desc* img_desc = &icon_desc->images[i]; - long_count += 2 + (img_desc->width * img_desc->height); - } - long* icon_data = (long*) _sapp_malloc_clear((size_t)long_count * sizeof(long)); - SOKOL_ASSERT(icon_data); - long* dst = icon_data; - for (int img_index = 0; img_index < num_images; img_index++) { - const sapp_image_desc* img_desc = &icon_desc->images[img_index]; - const uint8_t* src = (const uint8_t*) img_desc->pixels.ptr; - *dst++ = img_desc->width; - *dst++ = img_desc->height; - const int num_pixels = img_desc->width * img_desc->height; - for (int pixel_index = 0; pixel_index < num_pixels; pixel_index++) { - *dst++ = ((long)(src[pixel_index * 4 + 0]) << 16) | - ((long)(src[pixel_index * 4 + 1]) << 8) | - ((long)(src[pixel_index * 4 + 2]) << 0) | - ((long)(src[pixel_index * 4 + 3]) << 24); - } - } - XChangeProperty(_sapp.x11.display, _sapp.x11.window, - _sapp.x11.NET_WM_ICON, - XA_CARDINAL, 32, - PropModeReplace, - (unsigned char*)icon_data, - long_count); - _sapp_free(icon_data); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { - _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone); - XSetWindowAttributes wa; - _sapp_clear(&wa, sizeof(wa)); - const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; - wa.colormap = _sapp.x11.colormap; - wa.border_pixel = 0; - wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | - PointerMotionMask | ButtonPressMask | ButtonReleaseMask | - ExposureMask | FocusChangeMask | VisibilityChangeMask | - EnterWindowMask | LeaveWindowMask | PropertyChangeMask; - - int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); - int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); - int window_width = _sapp.window_width; - int window_height = _sapp.window_height; - if (0 == window_width) { - window_width = (display_width * 4) / 5; - } - if (0 == window_height) { - window_height = (display_height * 4) / 5; - } - int window_xpos = (display_width - window_width) / 2; - int window_ypos = (display_height - window_height) / 2; - if (window_xpos < 0) { - window_xpos = 0; - } - if (window_ypos < 0) { - window_ypos = 0; - } - _sapp_x11_grab_error_handler(); - _sapp.x11.window = XCreateWindow(_sapp.x11.display, - _sapp.x11.root, - window_xpos, - window_ypos, - (uint32_t)window_width, - (uint32_t)window_height, - 0, /* border width */ - depth, /* color depth */ - InputOutput, - visual, - wamask, - &wa); - _sapp_x11_release_error_handler(); - if (!_sapp.x11.window) { - _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); - } - Atom protocols[] = { - _sapp.x11.WM_DELETE_WINDOW - }; - XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); - - XSizeHints* hints = XAllocSizeHints(); - hints->flags = (PWinGravity | PPosition | PSize); - hints->win_gravity = StaticGravity; - hints->x = window_xpos; - hints->y = window_ypos; - hints->width = window_width; - hints->height = window_height; - XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); - XFree(hints); - - /* announce support for drag'n'drop */ - if (_sapp.drop.enabled) { - const Atom version = _SAPP_X11_XDND_VERSION; - XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); - } - _sapp_x11_update_window_title(); - _sapp_x11_query_window_size(); -} - -_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { - if (_sapp.x11.window) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XDestroyWindow(_sapp.x11.display, _sapp.x11.window); - _sapp.x11.window = 0; - } - if (_sapp.x11.colormap) { - XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); - _sapp.x11.colormap = 0; - } - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { - XWindowAttributes wa; - XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); - return wa.map_state == IsViewable; -} - -_SOKOL_PRIVATE void _sapp_x11_show_window(void) { - if (!_sapp_x11_window_visible()) { - XMapWindow(_sapp.x11.display, _sapp.x11.window); - XRaiseWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); - } -} - -_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { - XUnmapWindow(_sapp.x11.display, _sapp.x11.window); - XFlush(_sapp.x11.display); -} - -_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { - Atom actualType; - int actualFormat; - unsigned long itemCount, bytesAfter; - XGetWindowProperty(_sapp.x11.display, - window, - property, - 0, - LONG_MAX, - False, - type, - &actualType, - &actualFormat, - &itemCount, - &bytesAfter, - value); - return itemCount; -} - -_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { - int result = WithdrawnState; - struct { - CARD32 state; - Window icon; - } *state = NULL; - - if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { - result = (int)state->state; - } - if (state) { - XFree(state); - } - return result; -} - -_SOKOL_PRIVATE uint32_t _sapp_x11_key_modifier_bit(sapp_keycode key) { - switch (key) { - case SAPP_KEYCODE_LEFT_SHIFT: - case SAPP_KEYCODE_RIGHT_SHIFT: - return SAPP_MODIFIER_SHIFT; - case SAPP_KEYCODE_LEFT_CONTROL: - case SAPP_KEYCODE_RIGHT_CONTROL: - return SAPP_MODIFIER_CTRL; - case SAPP_KEYCODE_LEFT_ALT: - case SAPP_KEYCODE_RIGHT_ALT: - return SAPP_MODIFIER_ALT; - case SAPP_KEYCODE_LEFT_SUPER: - case SAPP_KEYCODE_RIGHT_SUPER: - return SAPP_MODIFIER_SUPER; - default: - return 0; - } -} - -_SOKOL_PRIVATE uint32_t _sapp_x11_button_modifier_bit(sapp_mousebutton btn) { - switch (btn) { - case SAPP_MOUSEBUTTON_LEFT: return SAPP_MODIFIER_LMB; - case SAPP_MOUSEBUTTON_RIGHT: return SAPP_MODIFIER_RMB; - case SAPP_MOUSEBUTTON_MIDDLE: return SAPP_MODIFIER_MMB; - default: return 0; - } -} - -_SOKOL_PRIVATE uint32_t _sapp_x11_mods(uint32_t x11_mods) { - uint32_t mods = 0; - if (x11_mods & ShiftMask) { - mods |= SAPP_MODIFIER_SHIFT; - } - if (x11_mods & ControlMask) { - mods |= SAPP_MODIFIER_CTRL; - } - if (x11_mods & Mod1Mask) { - mods |= SAPP_MODIFIER_ALT; - } - if (x11_mods & Mod4Mask) { - mods |= SAPP_MODIFIER_SUPER; - } - if (x11_mods & Button1Mask) { - mods |= SAPP_MODIFIER_LMB; - } - if (x11_mods & Button2Mask) { - mods |= SAPP_MODIFIER_MMB; - } - if (x11_mods & Button3Mask) { - mods |= SAPP_MODIFIER_RMB; - } - return mods; -} - -_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { - switch (event->xbutton.button) { - case Button1: return SAPP_MOUSEBUTTON_LEFT; - case Button2: return SAPP_MOUSEBUTTON_MIDDLE; - case Button3: return SAPP_MOUSEBUTTON_RIGHT; - default: return SAPP_MOUSEBUTTON_INVALID; - } -} - -_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y, 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); - _sapp.event.mouse_button = btn; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); - _sapp.event.modifiers = mods; - _sapp.event.scroll_x = x; - _sapp.event.scroll_y = y; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(type); - _sapp.event.key_code = key; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - /* check if a CLIPBOARD_PASTED event must be sent too */ - if (_sapp.clipboard.enabled && - (type == SAPP_EVENTTYPE_KEY_DOWN) && - (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && - (_sapp.event.key_code == SAPP_KEYCODE_V)) - { - _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); - _sapp_call_event(&_sapp.event); - } - } -} - -_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { - if (_sapp_events_enabled()) { - _sapp_init_event(SAPP_EVENTTYPE_CHAR); - _sapp.event.char_code = chr; - _sapp.event.key_repeat = repeat; - _sapp.event.modifiers = mods; - _sapp_call_event(&_sapp.event); - } -} - -_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { - int dummy; - KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); - SOKOL_ASSERT(keysyms); - KeySym keysym = keysyms[0]; - XFree(keysyms); - switch (keysym) { - case XK_Escape: return SAPP_KEYCODE_ESCAPE; - case XK_Tab: return SAPP_KEYCODE_TAB; - case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; - case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; - case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; - case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; - case XK_Meta_L: - case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; - case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ - case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ - case XK_Meta_R: - case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; - case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; - case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; - case XK_Menu: return SAPP_KEYCODE_MENU; - case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; - case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; - case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; - case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; - case XK_Pause: return SAPP_KEYCODE_PAUSE; - case XK_Delete: return SAPP_KEYCODE_DELETE; - case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; - case XK_Return: return SAPP_KEYCODE_ENTER; - case XK_Home: return SAPP_KEYCODE_HOME; - case XK_End: return SAPP_KEYCODE_END; - case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; - case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; - case XK_Insert: return SAPP_KEYCODE_INSERT; - case XK_Left: return SAPP_KEYCODE_LEFT; - case XK_Right: return SAPP_KEYCODE_RIGHT; - case XK_Down: return SAPP_KEYCODE_DOWN; - case XK_Up: return SAPP_KEYCODE_UP; - case XK_F1: return SAPP_KEYCODE_F1; - case XK_F2: return SAPP_KEYCODE_F2; - case XK_F3: return SAPP_KEYCODE_F3; - case XK_F4: return SAPP_KEYCODE_F4; - case XK_F5: return SAPP_KEYCODE_F5; - case XK_F6: return SAPP_KEYCODE_F6; - case XK_F7: return SAPP_KEYCODE_F7; - case XK_F8: return SAPP_KEYCODE_F8; - case XK_F9: return SAPP_KEYCODE_F9; - case XK_F10: return SAPP_KEYCODE_F10; - case XK_F11: return SAPP_KEYCODE_F11; - case XK_F12: return SAPP_KEYCODE_F12; - case XK_F13: return SAPP_KEYCODE_F13; - case XK_F14: return SAPP_KEYCODE_F14; - case XK_F15: return SAPP_KEYCODE_F15; - case XK_F16: return SAPP_KEYCODE_F16; - case XK_F17: return SAPP_KEYCODE_F17; - case XK_F18: return SAPP_KEYCODE_F18; - case XK_F19: return SAPP_KEYCODE_F19; - case XK_F20: return SAPP_KEYCODE_F20; - case XK_F21: return SAPP_KEYCODE_F21; - case XK_F22: return SAPP_KEYCODE_F22; - case XK_F23: return SAPP_KEYCODE_F23; - case XK_F24: return SAPP_KEYCODE_F24; - case XK_F25: return SAPP_KEYCODE_F25; - - case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; - case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; - case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; - case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; - - case XK_KP_Insert: return SAPP_KEYCODE_KP_0; - case XK_KP_End: return SAPP_KEYCODE_KP_1; - case XK_KP_Down: return SAPP_KEYCODE_KP_2; - case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; - case XK_KP_Left: return SAPP_KEYCODE_KP_4; - case XK_KP_Begin: return SAPP_KEYCODE_KP_5; - case XK_KP_Right: return SAPP_KEYCODE_KP_6; - case XK_KP_Home: return SAPP_KEYCODE_KP_7; - case XK_KP_Up: return SAPP_KEYCODE_KP_8; - case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; - case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; - case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; - case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; - - case XK_a: return SAPP_KEYCODE_A; - case XK_b: return SAPP_KEYCODE_B; - case XK_c: return SAPP_KEYCODE_C; - case XK_d: return SAPP_KEYCODE_D; - case XK_e: return SAPP_KEYCODE_E; - case XK_f: return SAPP_KEYCODE_F; - case XK_g: return SAPP_KEYCODE_G; - case XK_h: return SAPP_KEYCODE_H; - case XK_i: return SAPP_KEYCODE_I; - case XK_j: return SAPP_KEYCODE_J; - case XK_k: return SAPP_KEYCODE_K; - case XK_l: return SAPP_KEYCODE_L; - case XK_m: return SAPP_KEYCODE_M; - case XK_n: return SAPP_KEYCODE_N; - case XK_o: return SAPP_KEYCODE_O; - case XK_p: return SAPP_KEYCODE_P; - case XK_q: return SAPP_KEYCODE_Q; - case XK_r: return SAPP_KEYCODE_R; - case XK_s: return SAPP_KEYCODE_S; - case XK_t: return SAPP_KEYCODE_T; - case XK_u: return SAPP_KEYCODE_U; - case XK_v: return SAPP_KEYCODE_V; - case XK_w: return SAPP_KEYCODE_W; - case XK_x: return SAPP_KEYCODE_X; - case XK_y: return SAPP_KEYCODE_Y; - case XK_z: return SAPP_KEYCODE_Z; - case XK_1: return SAPP_KEYCODE_1; - case XK_2: return SAPP_KEYCODE_2; - case XK_3: return SAPP_KEYCODE_3; - case XK_4: return SAPP_KEYCODE_4; - case XK_5: return SAPP_KEYCODE_5; - case XK_6: return SAPP_KEYCODE_6; - case XK_7: return SAPP_KEYCODE_7; - case XK_8: return SAPP_KEYCODE_8; - case XK_9: return SAPP_KEYCODE_9; - case XK_0: return SAPP_KEYCODE_0; - case XK_space: return SAPP_KEYCODE_SPACE; - case XK_minus: return SAPP_KEYCODE_MINUS; - case XK_equal: return SAPP_KEYCODE_EQUAL; - case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; - case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; - case XK_backslash: return SAPP_KEYCODE_BACKSLASH; - case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; - case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; - case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; - case XK_comma: return SAPP_KEYCODE_COMMA; - case XK_period: return SAPP_KEYCODE_PERIOD; - case XK_slash: return SAPP_KEYCODE_SLASH; - case XK_less: return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ - default: return SAPP_KEYCODE_INVALID; - } -} - -_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { - int min = 0; - int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; - int mid; - - /* First check for Latin-1 characters (1:1 mapping) */ - if ((keysym >= 0x0020 && keysym <= 0x007e) || - (keysym >= 0x00a0 && keysym <= 0x00ff)) - { - return keysym; - } - - /* Also check for directly encoded 24-bit UCS characters */ - if ((keysym & 0xff000000) == 0x01000000) { - return keysym & 0x00ffffff; - } - - /* Binary search in table */ - while (max >= min) { - mid = (min + max) / 2; - if (_sapp_x11_keysymtab[mid].keysym < keysym) { - min = mid + 1; - } - else if (_sapp_x11_keysymtab[mid].keysym > keysym) { - max = mid - 1; - } - else { - return _sapp_x11_keysymtab[mid].ucs; - } - } - - /* No matching Unicode value found */ - return -1; -} - -_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { - SOKOL_ASSERT(src); - SOKOL_ASSERT(_sapp.drop.buffer); - - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - - /* - src is (potentially percent-encoded) string made of one or multiple paths - separated by \r\n, each path starting with 'file://' - */ - bool err = false; - int src_count = 0; - char src_chr = 0; - char* dst_ptr = _sapp.drop.buffer; - const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 - while (0 != (src_chr = *src++)) { - src_count++; - char dst_chr = 0; - /* check leading 'file://' */ - if (src_count <= 7) { - if (((src_count == 1) && (src_chr != 'f')) || - ((src_count == 2) && (src_chr != 'i')) || - ((src_count == 3) && (src_chr != 'l')) || - ((src_count == 4) && (src_chr != 'e')) || - ((src_count == 5) && (src_chr != ':')) || - ((src_count == 6) && (src_chr != '/')) || - ((src_count == 7) && (src_chr != '/'))) - { - _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); - err = true; - break; - } - } - else if (src_chr == '\r') { - // skip - } - else if (src_chr == '\n') { - src_count = 0; - _sapp.drop.num_files++; - // too many files is not an error - if (_sapp.drop.num_files >= _sapp.drop.max_files) { - break; - } - dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; - dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); - } - else if ((src_chr == '%') && src[0] && src[1]) { - // a percent-encoded byte (most likely UTF-8 multibyte sequence) - const char digits[3] = { src[0], src[1], 0 }; - src += 2; - dst_chr = (char) strtol(digits, 0, 16); - } - else { - dst_chr = src_chr; - } - if (dst_chr) { - // dst_end_ptr already has adjustment for terminating zero - if (dst_ptr < dst_end_ptr) { - *dst_ptr++ = dst_chr; - } - else { - _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); - err = true; - break; - } - } - } - if (err) { - _sapp_clear_drop_buffer(); - _sapp.drop.num_files = 0; - return false; - } - else { - return true; - } -} - -// XLib manual says keycodes are in the range [8, 255] inclusive. -// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html -static bool _sapp_x11_keycodes[256]; - -_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { - Bool filtered = XFilterEvent(event, None); - switch (event->type) { - case GenericEvent: - if (_sapp.mouse.locked && _sapp.x11.xi.available) { - if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { - if (XGetEventData(_sapp.x11.display, &event->xcookie)) { - if (event->xcookie.evtype == XI_RawMotion) { - XIRawEvent* re = (XIRawEvent*) event->xcookie.data; - if (re->valuators.mask_len) { - const double* values = re->raw_values; - if (XIMaskIsSet(re->valuators.mask, 0)) { - _sapp.mouse.dx = (float) *values; - values++; - } - if (XIMaskIsSet(re->valuators.mask, 1)) { - _sapp.mouse.dy = (float) *values; - } - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); - } - } - XFreeEventData(_sapp.x11.display, &event->xcookie); - } - } - } - break; - case FocusIn: - // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW - if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { - _sapp_x11_app_event(SAPP_EVENTTYPE_FOCUSED); - } - break; - case FocusOut: - /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ - if (_sapp.mouse.locked) { - _sapp_x11_lock_mouse(false); - } - // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW - if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { - _sapp_x11_app_event(SAPP_EVENTTYPE_UNFOCUSED); - } - break; - case KeyPress: - { - int keycode = (int)event->xkey.keycode; - const sapp_keycode key = _sapp_x11_translate_key(keycode); - bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; - _sapp_x11_keycodes[keycode & 0xFF] = true; - uint32_t mods = _sapp_x11_mods(event->xkey.state); - // X11 doesn't set modifier bit on key down, so emulate that - mods |= _sapp_x11_key_modifier_bit(key); - if (key != SAPP_KEYCODE_INVALID) { - _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); - } - KeySym keysym; - XLookupString(&event->xkey, NULL, 0, &keysym, NULL); - int32_t chr = _sapp_x11_keysym_to_unicode(keysym); - if (chr > 0) { - _sapp_x11_char_event((uint32_t)chr, repeat, mods); - } - } - break; - case KeyRelease: - { - int keycode = (int)event->xkey.keycode; - const sapp_keycode key = _sapp_x11_translate_key(keycode); - _sapp_x11_keycodes[keycode & 0xFF] = false; - if (key != SAPP_KEYCODE_INVALID) { - uint32_t mods = _sapp_x11_mods(event->xkey.state); - // X11 doesn't clear modifier bit on key up, so emulate that - mods &= ~_sapp_x11_key_modifier_bit(key); - _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); - } - } - break; - case ButtonPress: - { - _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, 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 - mods |= _sapp_x11_button_modifier_bit(btn); - if (btn != SAPP_MOUSEBUTTON_INVALID) { - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); - _sapp.x11.mouse_buttons |= (1 << btn); - } - else { - /* might be a scroll event */ - switch (event->xbutton.button) { - case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; - case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; - case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; - case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; - } - } - } - break; - case ButtonRelease: - { - _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); - const sapp_mousebutton btn = _sapp_x11_translate_button(event); - if (btn != SAPP_MOUSEBUTTON_INVALID) { - uint32_t mods = _sapp_x11_mods(event->xbutton.state); - // X11 doesn't clear modifier bit on button up, so emulate that - mods &= ~_sapp_x11_button_modifier_bit(btn); - _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, mods); - _sapp.x11.mouse_buttons &= ~(1 << btn); - } - } - break; - case EnterNotify: - /* don't send enter/leave events while mouse button held down */ - if (0 == _sapp.x11.mouse_buttons) { - _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y, 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) { - _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; - case ConfigureNotify: - if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { - _sapp.window_width = event->xconfigure.width; - _sapp.window_height = event->xconfigure.height; - _sapp.framebuffer_width = _sapp.window_width; - _sapp.framebuffer_height = _sapp.window_height; - _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); - } - break; - case PropertyNotify: - if (event->xproperty.state == PropertyNewValue) { - if (event->xproperty.atom == _sapp.x11.WM_STATE) { - const int state = _sapp_x11_get_window_state(); - if (state != _sapp.x11.window_state) { - _sapp.x11.window_state = state; - if (state == IconicState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); - } - else if (state == NormalState) { - _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); - } - } - } - } - break; - case ClientMessage: - if (filtered) { - return; - } - if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { - const Atom protocol = (Atom)event->xclient.data.l[0]; - if (protocol == _sapp.x11.WM_DELETE_WINDOW) { - _sapp.quit_requested = true; - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { - const bool is_list = 0 != (event->xclient.data.l[1] & 1); - _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; - _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _sapp.x11.xdnd.format = None; - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - uint32_t count = 0; - Atom* formats = 0; - if (is_list) { - count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); - } - else { - count = 3; - formats = (Atom*) event->xclient.data.l + 2; - } - for (uint32_t i = 0; i < count; i++) { - if (formats[i] == _sapp.x11.xdnd.text_uri_list) { - _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; - break; - } - } - if (is_list && formats) { - XFree(formats); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - Time time = CurrentTime; - if (_sapp.x11.xdnd.format) { - if (_sapp.x11.xdnd.version >= 1) { - time = (Time)event->xclient.data.l[2]; - } - XConvertSelection(_sapp.x11.display, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.xdnd.format, - _sapp.x11.xdnd.XdndSelection, - _sapp.x11.window, - time); - } - else if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = 0; // drag was rejected - reply.xclient.data.l[2] = None; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { - /* drag operation has moved over the window - FIXME: we could track the mouse position here, but - this isn't implemented on other platforms either so far - */ - if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { - return; - } - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - if (_sapp.x11.xdnd.format) { - /* reply that we are ready to copy the dragged data */ - reply.xclient.data.l[1] = 1; // accept with no rectangle - if (_sapp.x11.xdnd.version >= 2) { - reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; - } - } - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - break; - case SelectionNotify: - if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { - char* data = 0; - uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, - event->xselection.property, - event->xselection.target, - (unsigned char**) &data); - if (_sapp.drop.enabled && result) { - if (_sapp_x11_parse_dropped_files_list(data)) { - _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); - } - } - } - if (_sapp.x11.xdnd.version >= 2) { - XEvent reply; - _sapp_clear(&reply, sizeof(reply)); - reply.type = ClientMessage; - reply.xclient.window = _sapp.x11.xdnd.source; - reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)_sapp.x11.window; - reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; - XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); - XFlush(_sapp.x11.display); - } - } - break; - case DestroyNotify: - break; - } -} - -#if !defined(_SAPP_GLX) - -_SOKOL_PRIVATE void _sapp_egl_init(void) { -#if defined(SOKOL_GLCORE33) - if (!eglBindAPI(EGL_OPENGL_API)) { - _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); - } -#else - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); - } -#endif - - _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); - if (EGL_NO_DISPLAY == _sapp.egl.display) { - _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); - } - - EGLint major, minor; - if (!eglInitialize(_sapp.egl.display, &major, &minor)) { - _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); - } - - EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; - EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; - const EGLint config_attrs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - #if defined(SOKOL_GLCORE33) - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - #elif defined(SOKOL_GLES3) - EGL_RENDERABLE_TYPE, 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 - the compiler or linker options. They have no other purpose. - */ - pthread_attr_t pthread_attr; - pthread_attr_init(&pthread_attr); - pthread_attr_destroy(&pthread_attr); - - _sapp_init_state(desc); - _sapp.x11.window_state = NormalState; - - XInitThreads(); - XrmInitialize(); - _sapp.x11.display = XOpenDisplay(NULL); - if (!_sapp.x11.display) { - _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); - } - _sapp.x11.screen = DefaultScreen(_sapp.x11.display); - _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); - XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); - _sapp_x11_query_system_dpi(); - _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; - _sapp_x11_init_extensions(); - _sapp_x11_create_cursors(); -#if defined(_SAPP_GLX) - _sapp_glx_init(); - Visual* visual = 0; - int depth = 0; - _sapp_glx_choose_visual(&visual, &depth); - _sapp_x11_create_window(visual, depth); - _sapp_glx_create_context(); - _sapp_glx_swapinterval(_sapp.swap_interval); -#else - _sapp_egl_init(); -#endif - sapp_set_icon(&desc->icon); - _sapp.valid = true; - _sapp_x11_show_window(); - if (_sapp.fullscreen) { - _sapp_x11_set_fullscreen(true); - } - - XFlush(_sapp.x11.display); - while (!_sapp.quit_ordered) { - _sapp_timing_measure(&_sapp.timing); - int count = XPending(_sapp.x11.display); - while (count--) { - XEvent event; - XNextEvent(_sapp.x11.display, &event); - _sapp_x11_process_event(&event); - } - _sapp_frame(); -#if defined(_SAPP_GLX) - _sapp_glx_swap_buffers(); -#else - eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); -#endif - XFlush(_sapp.x11.display); - /* handle quit-requested, either from window or from sapp_request_quit() */ - if (_sapp.quit_requested && !_sapp.quit_ordered) { - /* give user code a chance to intervene */ - _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); - /* if user code hasn't intervened, quit the app */ - if (_sapp.quit_requested) { - _sapp.quit_ordered = true; - } - } - } - _sapp_call_cleanup(); -#if defined(_SAPP_GLX) - _sapp_glx_destroy_context(); -#else - _sapp_egl_destroy(); -#endif - _sapp_x11_destroy_window(); - _sapp_x11_destroy_cursors(); - XCloseDisplay(_sapp.x11.display); - _sapp_discard_state(); -} - -#if !defined(SOKOL_NO_ENTRY) -int main(int argc, char* argv[]) { - sapp_desc desc = sokol_main(argc, argv); - _sapp_linux_run(&desc); - return 0; -} -#endif /* SOKOL_NO_ENTRY */ -#endif /* _SAPP_LINUX */ - -// ██████ ██ ██ ██████ ██ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██ ██ ██████ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██████ ██████ ███████ ██ ██████ -// -// >>public -#if defined(SOKOL_NO_ENTRY) -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - SOKOL_ASSERT(desc); - #if defined(_SAPP_MACOS) - _sapp_macos_run(desc); - #elif defined(_SAPP_IOS) - _sapp_ios_run(desc); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_run(desc); - #elif defined(_SAPP_WIN32) - _sapp_win32_run(desc); - #elif defined(_SAPP_LINUX) - _sapp_linux_run(desc); - #else - #error "sapp_run() not supported on this platform" - #endif -} - -/* this is just a stub so the linker doesn't complain */ -sapp_desc sokol_main(int argc, char* argv[]) { - _SOKOL_UNUSED(argc); - _SOKOL_UNUSED(argv); - sapp_desc desc; - _sapp_clear(&desc, sizeof(desc)); - return desc; -} -#else -/* likewise, in normal mode, sapp_run() is just an empty stub */ -SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { - _SOKOL_UNUSED(desc); -} -#endif - -SOKOL_API_IMPL bool sapp_isvalid(void) { - return _sapp.valid; -} - -SOKOL_API_IMPL void* sapp_userdata(void) { - return _sapp.desc.user_data; -} - -SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { - return _sapp.desc; -} - -SOKOL_API_IMPL uint64_t sapp_frame_count(void) { - return _sapp.frame_count; -} - -SOKOL_API_IMPL double sapp_frame_duration(void) { - return _sapp_timing_get_avg(&_sapp.timing); -} - -SOKOL_API_IMPL int sapp_width(void) { - return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; -} - -SOKOL_API_IMPL float sapp_widthf(void) { - return (float)sapp_width(); -} - -SOKOL_API_IMPL int sapp_height(void) { - return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; -} - -SOKOL_API_IMPL float sapp_heightf(void) { - return (float)sapp_height(); -} - -SOKOL_API_IMPL int sapp_color_format(void) { - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - switch (_sapp.emsc.wgpu.render_format) { - case WGPUTextureFormat_RGBA8Unorm: - return _SAPP_PIXELFORMAT_RGBA8; - case WGPUTextureFormat_BGRA8Unorm: - return _SAPP_PIXELFORMAT_BGRA8; - default: - SOKOL_UNREACHABLE; - return 0; - } - #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) - return _SAPP_PIXELFORMAT_BGRA8; - #else - return _SAPP_PIXELFORMAT_RGBA8; - #endif -} - -SOKOL_API_IMPL int sapp_depth_format(void) { - return _SAPP_PIXELFORMAT_DEPTH_STENCIL; -} - -SOKOL_API_IMPL int sapp_sample_count(void) { - return _sapp.sample_count; -} - -SOKOL_API_IMPL bool sapp_high_dpi(void) { - return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); -} - -SOKOL_API_IMPL float sapp_dpi_scale(void) { - return _sapp.dpi_scale; -} - -SOKOL_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) { - #if defined(_SAPP_IOS) - _sapp_ios_show_keyboard(show); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_show_keyboard(show); - #elif defined(_SAPP_ANDROID) - _sapp_android_show_keyboard(show); - #else - _SOKOL_UNUSED(show); - #endif -} - -SOKOL_API_IMPL bool sapp_keyboard_shown(void) { - return _sapp.onscreen_keyboard_shown; -} - -SOKOL_API_IMPL bool sapp_is_fullscreen(void) { - return _sapp.fullscreen; -} - -SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { - #if defined(_SAPP_MACOS) - _sapp_macos_toggle_fullscreen(); - #elif defined(_SAPP_WIN32) - _sapp_win32_toggle_fullscreen(); - #elif defined(_SAPP_LINUX) - _sapp_x11_toggle_fullscreen(); - #endif -} - -/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ -SOKOL_API_IMPL void sapp_show_mouse(bool show) { - if (_sapp.mouse.shown != show) { - #if defined(_SAPP_MACOS) - _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); - #endif - _sapp.mouse.shown = show; - } -} - -SOKOL_API_IMPL bool sapp_mouse_shown(void) { - return _sapp.mouse.shown; -} - -SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { - #if defined(_SAPP_MACOS) - _sapp_macos_lock_mouse(lock); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_lock_mouse(lock); - #elif defined(_SAPP_WIN32) - _sapp_win32_lock_mouse(lock); - #elif defined(_SAPP_LINUX) - _sapp_x11_lock_mouse(lock); - #else - _sapp.mouse.locked = lock; - #endif -} - -SOKOL_API_IMPL bool sapp_mouse_locked(void) { - return _sapp.mouse.locked; -} - -SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { - SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); - if (_sapp.mouse.current_cursor != cursor) { - #if defined(_SAPP_MACOS) - _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); - #endif - _sapp.mouse.current_cursor = cursor; - } -} - -SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { - return _sapp.mouse.current_cursor; -} - -SOKOL_API_IMPL void sapp_request_quit(void) { - _sapp.quit_requested = true; -} - -SOKOL_API_IMPL void sapp_cancel_quit(void) { - _sapp.quit_requested = false; -} - -SOKOL_API_IMPL void sapp_quit(void) { - _sapp.quit_ordered = true; -} - -SOKOL_API_IMPL void sapp_consume_event(void) { - _sapp.event_consumed = true; -} - -/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ -SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { - if (!_sapp.clipboard.enabled) { - return; - } - SOKOL_ASSERT(str); - #if defined(_SAPP_MACOS) - _sapp_macos_set_clipboard_string(str); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_set_clipboard_string(str); - #elif defined(_SAPP_WIN32) - _sapp_win32_set_clipboard_string(str); - #else - /* not implemented */ - #endif - _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); -} - -SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { - if (!_sapp.clipboard.enabled) { - return ""; - } - #if defined(_SAPP_MACOS) - return _sapp_macos_get_clipboard_string(); - #elif defined(_SAPP_EMSCRIPTEN) - return _sapp.clipboard.buffer; - #elif defined(_SAPP_WIN32) - return _sapp_win32_get_clipboard_string(); - #else - /* not implemented */ - return _sapp.clipboard.buffer; - #endif -} - -SOKOL_API_IMPL void sapp_set_window_title(const char* title) { - SOKOL_ASSERT(title); - _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); - #if defined(_SAPP_MACOS) - _sapp_macos_update_window_title(); - #elif defined(_SAPP_WIN32) - _sapp_win32_update_window_title(); - #elif defined(_SAPP_LINUX) - _sapp_x11_update_window_title(); - #endif -} - -SOKOL_API_IMPL void sapp_set_icon(const sapp_icon_desc* desc) { - SOKOL_ASSERT(desc); - if (desc->sokol_default) { - if (0 == _sapp.default_icon_pixels) { - _sapp_setup_default_icon(); - } - SOKOL_ASSERT(0 != _sapp.default_icon_pixels); - desc = &_sapp.default_icon_desc; - } - const int num_images = _sapp_icon_num_images(desc); - if (num_images == 0) { - return; - } - SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); - if (!_sapp_validate_icon_desc(desc, num_images)) { - return; - } - #if defined(_SAPP_MACOS) - _sapp_macos_set_icon(desc, num_images); - #elif defined(_SAPP_WIN32) - _sapp_win32_set_icon(desc, num_images); - #elif defined(_SAPP_LINUX) - _sapp_x11_set_icon(desc, num_images); - #elif defined(_SAPP_EMSCRIPTEN) - _sapp_emsc_set_icon(desc, num_images); - #endif -} - -SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { - SOKOL_ASSERT(_sapp.drop.enabled); - return _sapp.drop.num_files; -} - -SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - SOKOL_ASSERT(_sapp.drop.buffer); - if (!_sapp.drop.enabled) { - return ""; - } - if ((index < 0) || (index >= _sapp.drop.max_files)) { - return ""; - } - return (const char*) _sapp_dropped_file_path_ptr(index); -} - -SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); - #if defined(_SAPP_EMSCRIPTEN) - if (!_sapp.drop.enabled) { - return 0; - } - return sapp_js_dropped_file_size(index); - #else - (void)index; - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { - SOKOL_ASSERT(_sapp.drop.enabled); - SOKOL_ASSERT(request); - SOKOL_ASSERT(request->callback); - SOKOL_ASSERT(request->buffer.ptr); - SOKOL_ASSERT(request->buffer.size > 0); - #if defined(_SAPP_EMSCRIPTEN) - const int index = request->dropped_file_index; - sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; - if ((index < 0) || (index >= _sapp.drop.num_files)) { - error_code = SAPP_HTML5_FETCH_ERROR_OTHER; - } - if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { - error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; - } - if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { - _sapp_emsc_invoke_fetch_cb(index, - false, // success - (int)error_code, - request->callback, - 0, // fetched_size - (void*)request->buffer.ptr, - request->buffer.size, - request->user_data); - } - else { - sapp_js_fetch_dropped_file(index, - request->callback, - (void*)request->buffer.ptr, - request->buffer.size, - request->user_data); - } - #else - (void)request; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.mtl_device; - #else - const void* obj = (__bridge const void*) _sapp.ios.mtl_device; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_METAL) - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; - #else - const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; - #endif - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_macos_get_window(void) { - #if defined(_SAPP_MACOS) - const void* obj = (__bridge const void*) _sapp.macos.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_ios_get_window(void) { - #if defined(_SAPP_IOS) - const void* obj = (__bridge const void*) _sapp.ios.window; - SOKOL_ASSERT(obj); - return obj; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.device_context; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_swap_chain(void) { - SOKOL_ASSERT(_sapp.valid); -#if defined(SOKOL_D3D11) - return _sapp.d3d11.swap_chain; -#else - return 0; -#endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - if (_sapp.d3d11.msaa_rtv) { - return _sapp.d3d11.msaa_rtv; - } - else { - return _sapp.d3d11.rtv; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(SOKOL_D3D11) - return _sapp.d3d11.dsv; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_WIN32) - return _sapp.win32.hwnd; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.device; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.msaa_view; - } - else { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - if (_sapp.sample_count > 1) { - return (const void*) _sapp.emsc.wgpu.swapchain_view; - } - else { - return 0; - } - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { - SOKOL_ASSERT(_sapp.valid); - #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) - return (const void*) _sapp.emsc.wgpu.depth_stencil_view; - #else - return 0; - #endif -} - -SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { - // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() - // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) - #if defined(_SAPP_ANDROID) - return (void*)_sapp.android.activity; - #else - return 0; - #endif -} - -SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { - _sapp.html5_ask_leave_site = ask; -} - -#endif /* SOKOL_APP_IMPL */ diff --git a/src/libs/sokol_audio.h b/src/libs/sokol_audio.h deleted file mode 100644 index bdd6953..0000000 --- a/src/libs/sokol_audio.h +++ /dev/null @@ -1,2596 +0,0 @@ -#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL) -#define SOKOL_AUDIO_IMPL -#endif -#ifndef SOKOL_AUDIO_INCLUDED -/* - sokol_audio.h -- cross-platform audio-streaming API - - Project URL: https://github.com/floooh/sokol - - Do this: - #define SOKOL_IMPL or - #define SOKOL_AUDIO_IMPL - before you include this file in *one* C or C++ file to create the - implementation. - - Optionally provide the following defines with your own implementations: - - SOKOL_DUMMY_BACKEND - use a dummy backend - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - - SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024) - SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on - macOS instead of using embedded CoreAudio declarations - SAUDIO_ANDROID_AAUDIO - on Android, select the AAudio backend (default) - SAUDIO_ANDROID_SLES - on Android, select the OpenSLES backend - - If sokol_audio.h is compiled as a DLL, define the following before - including the declaration or implementation: - - SOKOL_DLL - - On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport) - or __declspec(dllimport) as needed. - - Link with the following libraries: - - - on macOS: AudioToolbox - - on iOS: AudioToolbox, AVFoundation - - on Linux: asound - - on Android: link with OpenSLES or aaudio - - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib - - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32 - - FEATURE OVERVIEW - ================ - You provide a mono- or stereo-stream of 32-bit float samples, which - Sokol Audio feeds into platform-specific audio backends: - - - Windows: WASAPI - - Linux: ALSA - - macOS: CoreAudio - - iOS: CoreAudio+AVAudioSession - - emscripten: WebAudio with ScriptProcessorNode - - Android: AAudio (default) or OpenSLES, select at build time - - Sokol Audio will not do any buffer mixing or volume control, if you have - multiple independent input streams of sample data you need to perform the - mixing yourself before forwarding the data to Sokol Audio. - - There are two mutually exclusive ways to provide the sample data: - - 1. Callback model: You provide a callback function, which will be called - when Sokol Audio needs new samples. On all platforms except emscripten, - this function is called from a separate thread. - 2. Push model: Your code pushes small blocks of sample data from your - main loop or a thread you created. The pushed data is stored in - a ring buffer where it is pulled by the backend code when - needed. - - The callback model is preferred because it is the most direct way to - feed sample data into the audio backends and also has less moving parts - (there is no ring buffer between your code and the audio backend). - - Sometimes it is not possible to generate the audio stream directly in a - callback function running in a separate thread, for such cases Sokol Audio - provides the push-model as a convenience. - - SOKOL AUDIO, SOLOUD AND MINIAUDIO - ================================= - The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the - SoLoud library (with some modifications, so any bugs in there are most - likely my fault). If you need a more fully-featured audio solution, check - out SoLoud, it's excellent: - - https://github.com/jarikomppa/soloud - - Another alternative which feature-wise is somewhere inbetween SoLoud and - sokol-audio might be MiniAudio: - - https://github.com/mackron/miniaudio - - GLOSSARY - ======== - - stream buffer: - The internal audio data buffer, usually provided by the backend API. The - size of the stream buffer defines the base latency, smaller buffers have - lower latency but may cause audio glitches. Bigger buffers reduce or - eliminate glitches, but have a higher base latency. - - - stream callback: - Optional callback function which is called by Sokol Audio when it - needs new samples. On Windows, macOS/iOS and Linux, this is called in - a separate thread, on WebAudio, this is called per-frame in the - browser thread. - - - channel: - A discrete track of audio data, currently 1-channel (mono) and - 2-channel (stereo) is supported and tested. - - - sample: - The magnitude of an audio signal on one channel at a given time. In - Sokol Audio, samples are 32-bit float numbers in the range -1.0 to - +1.0. - - - frame: - The tightly packed set of samples for all channels at a given time. - For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples. - - - packet: - In Sokol Audio, a small chunk of audio data that is moved from the - main thread to the audio streaming thread in order to decouple the - rate at which the main thread provides new audio data, and the - streaming thread consuming audio data. - - WORKING WITH SOKOL AUDIO - ======================== - First call saudio_setup() with your preferred audio playback options. - In most cases you can stick with the default values, these provide - a good balance between low-latency and glitch-free playback - on all audio backends. - - You should always provide a logging callback to be aware of any - warnings and errors. The easiest way is to use sokol_log.h for this: - - #include "sokol_log.h" - // ... - saudio_setup(&(saudio_desc){ - .logger = { - .func = slog_func, - } - }); - - If you want to use the callback-model, you need to provide a stream - callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb, - otherwise keep both function pointers zero-initialized. - - Use push model and default playback parameters: - - saudio_setup(&(saudio_desc){ .logger.func = slog_func }); - - Use stream callback model and default playback parameters: - - saudio_setup(&(saudio_desc){ - .stream_cb = my_stream_callback - .logger.func = slog_func, - }); - - The standard stream callback doesn't have a user data argument, if you want - that, use the alternative stream_userdata_cb and also set the user_data pointer: - - saudio_setup(&(saudio_desc){ - .stream_userdata_cb = my_stream_callback, - .user_data = &my_data - .logger.func = slog_func, - }); - - The following playback parameters can be provided through the - saudio_desc struct: - - General parameters (both for stream-callback and push-model): - - int sample_rate -- the sample rate in Hz, default: 44100 - int num_channels -- number of channels, default: 1 (mono) - int buffer_frames -- number of frames in streaming buffer, default: 2048 - - The stream callback prototype (either with or without userdata): - - void (*stream_cb)(float* buffer, int num_frames, int num_channels) - void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data) - Function pointer to the user-provide stream callback. - - Push-model parameters: - - int packet_frames -- number of frames in a packet, default: 128 - int num_packets -- number of packets in ring buffer, default: 64 - - The sample_rate and num_channels parameters are only hints for the audio - backend, it isn't guaranteed that those are the values used for actual - playback. - - To get the actual parameters, call the following functions after - saudio_setup(): - - int saudio_sample_rate(void) - int saudio_channels(void); - - It's unlikely that the number of channels will be different than requested, - but a different sample rate isn't uncommon. - - (NOTE: there's an yet unsolved issue when an audio backend might switch - to a different sample rate when switching output devices, for instance - plugging in a bluetooth headset, this case is currently not handled in - Sokol Audio). - - You can check if audio initialization was successful with - saudio_isvalid(). If backend initialization failed for some reason - (for instance when there's no audio device in the machine), this - will return false. Not checking for success won't do any harm, all - Sokol Audio function will silently fail when called after initialization - has failed, so apart from missing audio output, nothing bad will happen. - - Before your application exits, you should call - - saudio_shutdown(); - - This stops the audio thread (on Linux, Windows and macOS/iOS) and - properly shuts down the audio backend. - - THE STREAM CALLBACK MODEL - ========================= - To use Sokol Audio in stream-callback-mode, provide a callback function - like this in the saudio_desc struct when calling saudio_setup(): - - void stream_cb(float* buffer, int num_frames, int num_channels) { - ... - } - - Or the alternative version with a user-data argument: - - void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) { - my_data_t* my_data = (my_data_t*) user_data; - ... - } - - The job of the callback function is to fill the *buffer* with 32-bit - float sample values. - - To output silence, fill the buffer with zeros: - - void stream_cb(float* buffer, int num_frames, int num_channels) { - const int num_samples = num_frames * num_channels; - for (int i = 0; i < num_samples; i++) { - buffer[i] = 0.0f; - } - } - - For stereo output (num_channels == 2), the samples for the left - and right channel are interleaved: - - void stream_cb(float* buffer, int num_frames, int num_channels) { - assert(2 == num_channels); - for (int i = 0; i < num_frames; i++) { - buffer[2*i + 0] = ...; // left channel - buffer[2*i + 1] = ...; // right channel - } - } - - Please keep in mind that the stream callback function is running in a - separate thread, if you need to share data with the main thread you need - to take care yourself to make the access to the shared data thread-safe! - - THE PUSH MODEL - ============== - To use the push-model for providing audio data, simply don't set (keep - zero-initialized) the stream_cb field in the saudio_desc struct when - calling saudio_setup(). - - To provide sample data with the push model, call the saudio_push() - function at regular intervals (for instance once per frame). You can - call the saudio_expect() function to ask Sokol Audio how much room is - in the ring buffer, but if you provide a continuous stream of data - at the right sample rate, saudio_expect() isn't required (it's a simple - way to sync/throttle your sample generation code with the playback - rate though). - - With saudio_push() you may need to maintain your own intermediate sample - buffer, since pushing individual sample values isn't very efficient. - The following example is from the MOD player sample in - sokol-samples (https://github.com/floooh/sokol-samples): - - const int num_frames = saudio_expect(); - if (num_frames > 0) { - const int num_samples = num_frames * saudio_channels(); - read_samples(flt_buf, num_samples); - saudio_push(flt_buf, num_frames); - } - - Another option is to ignore saudio_expect(), and just push samples as they - are generated in small batches. In this case you *need* to generate the - samples at the right sample rate: - - The following example is taken from the Tiny Emulators project - (https://github.com/floooh/chips-test), this is for mono playback, - so (num_samples == num_frames): - - // tick the sound generator - if (ay38910_tick(&sys->psg)) { - // new sample is ready - sys->sample_buffer[sys->sample_pos++] = sys->psg.sample; - if (sys->sample_pos == sys->num_samples) { - // new sample packet is ready - saudio_push(sys->sample_buffer, sys->num_samples); - sys->sample_pos = 0; - } - } - - THE WEBAUDIO BACKEND - ==================== - The WebAudio backend is currently using a ScriptProcessorNode callback to - feed the sample data into WebAudio. ScriptProcessorNode has been - deprecated for a while because it is running from the main thread, with - the default initialization parameters it works 'pretty well' though. - Ultimately Sokol Audio will use Audio Worklets, but this requires a few - more things to fall into place (Audio Worklets implemented everywhere, - SharedArrayBuffers enabled again, and I need to figure out a 'low-cost' - solution in terms of implementation effort, since Audio Worklets are - a lot more complex than ScriptProcessorNode if the audio data needs to come - from the main thread). - - The WebAudio backend is automatically selected when compiling for - emscripten (__EMSCRIPTEN__ define exists). - - https://developers.google.com/web/updates/2017/12/audio-worklet - https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern - - "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/ - - Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/ - - THE COREAUDIO BACKEND - ===================== - The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined). - Since the CoreAudio API is implemented in C (not Objective-C) on macOS the - implementation part of Sokol Audio can be included into a C source file. - - However on iOS, Sokol Audio must be compiled as Objective-C due to it's - reliance on the AVAudioSession object. The iOS code path support both - being compiled with or without ARC (Automatic Reference Counting). - - For thread synchronisation, the CoreAudio backend will use the - pthread_mutex_* functions. - - The incoming floating point samples will be directly forwarded to - CoreAudio without further conversion. - - macOS and iOS applications that use Sokol Audio need to link with - the AudioToolbox framework. - - THE WASAPI BACKEND - ================== - The WASAPI backend is automatically selected when compiling on Windows - (_WIN32 is defined). - - For thread synchronisation a Win32 critical section is used. - - WASAPI may use a different size for its own streaming buffer then requested, - so the base latency may be slightly bigger. The current backend implementation - converts the incoming floating point sample values to signed 16-bit - integers. - - The required Windows system DLLs are linked with #pragma comment(lib, ...), - so you shouldn't need to add additional linker libs in the build process - (otherwise this is a bug which should be fixed in sokol_audio.h). - - THE ALSA BACKEND - ================ - The ALSA backend is automatically selected when compiling on Linux - ('linux' is defined). - - For thread synchronisation, the pthread_mutex_* functions are used. - - Samples are directly forwarded to ALSA in 32-bit float format, no - further conversion is taking place. - - You need to link with the 'asound' library, and the - header must be present (usually both are installed with some sort - of ALSA development package). - - - 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); - } - - ... - saudio_setup(&(saudio_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_audio.h - itself though, not any allocations in OS libraries. - - Memory allocation will only happen on the same thread where saudio_setup() - was called, so you don't need to worry about thread-safety. - - - 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" - - saudio_setup(&(saudio_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. 'saudio' - uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info - uint32_t log_item_id, // SAUDIO_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_audio.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data) - { - ... - } - - ...and then setup sokol-audio like this: - - saudio_setup(&(saudio_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. - - LICENSE - ======= - - zlib/libpng license - - Copyright (c) 2018 Andre Weissflog - - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ -#define SOKOL_AUDIO_INCLUDED (1) -#include // size_t -#include -#include - -#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL) -#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL -#endif -#ifndef SOKOL_AUDIO_API_DECL -#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL) -#define SOKOL_AUDIO_API_DECL __declspec(dllexport) -#elif defined(_WIN32) && defined(SOKOL_DLL) -#define SOKOL_AUDIO_API_DECL __declspec(dllimport) -#else -#define SOKOL_AUDIO_API_DECL extern -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* - saudio_log_item - - Log items are defined via X-Macros, and expanded to an - enum 'saudio_log_item', and in debug mode only, - corresponding strings. - - Used as parameter in the logging callback. -*/ -#define _SAUDIO_LOG_ITEMS \ - _SAUDIO_LOGITEM_XMACRO(OK, "Ok") \ - _SAUDIO_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_OPEN_FAILED, "snd_pcm_open() failed") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED, "floating point sample format not supported") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED, "requested buffer size not supported") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED, "requested channel count not supported") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED, "snd_pcm_hw_params_set_rate_near() failed") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_FAILED, "snd_pcm_hw_params() failed") \ - _SAUDIO_LOGITEM_XMACRO(ALSA_PTHREAD_CREATE_FAILED, "pthread_create() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_EVENT_FAILED, "CreateEvent() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED, "CoCreateInstance() for IMMDeviceEnumerator failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED, "IMMDeviceEnumerator.GetDefaultAudioEndpoint() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_DEVICE_ACTIVATE_FAILED, "IMMDevice.Activate() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED, "IAudioClient.Initialize() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED, "IAudioClient.GetBufferSize() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED, "IAudioClient.GetService() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED, "IAudioClient.SetEventHandle() failed") \ - _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_THREAD_FAILED, "CreateThread() failed") \ - _SAUDIO_LOGITEM_XMACRO(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED, "AAudioStreamBuilder_openStream() failed") \ - _SAUDIO_LOGITEM_XMACRO(AAUDIO_PTHREAD_CREATE_FAILED, "pthread_create() failed after AAUDIO_ERROR_DISCONNECTED") \ - _SAUDIO_LOGITEM_XMACRO(AAUDIO_RESTARTING_STREAM_AFTER_ERROR, "restarting AAudio stream after error") \ - _SAUDIO_LOGITEM_XMACRO(USING_AAUDIO_BACKEND, "using AAudio backend") \ - _SAUDIO_LOGITEM_XMACRO(AAUDIO_CREATE_STREAMBUILDER_FAILED, "AAudio_createStreamBuilder() failed") \ - _SAUDIO_LOGITEM_XMACRO(USING_SLES_BACKEND, "using OpenSLES backend") \ - _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_ENGINE_FAILED, "slCreateEngine() failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED, "GetInterface() for SL_IID_ENGINE failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_OUTPUT_MIX_FAILED, "CreateOutputMix() failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED, "CreateAudioPlayer() failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED, "GetInterface() for SL_IID_PLAY failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \ - _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED, "GetInterface() for SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed") \ - _SAUDIO_LOGITEM_XMACRO(COREAUDIO_NEW_OUTPUT_FAILED, "AudioQueueNewOutput() failed") \ - _SAUDIO_LOGITEM_XMACRO(COREAUDIO_ALLOCATE_BUFFER_FAILED, "AudioQueueAllocateBuffer() failed") \ - _SAUDIO_LOGITEM_XMACRO(COREAUDIO_START_FAILED, "AudioQueueStart() failed") \ - _SAUDIO_LOGITEM_XMACRO(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE, "backend buffer size isn't multiple of packet size") \ - -#define _SAUDIO_LOGITEM_XMACRO(item,msg) SAUDIO_LOGITEM_##item, -typedef enum saudio_log_item { - _SAUDIO_LOG_ITEMS -} saudio_log_item; -#undef _SAUDIO_LOGITEM_XMACRO - -/* - saudio_logger - - Used in saudio_desc to provide a custom logging and error reporting - callback to sokol-audio. -*/ -typedef struct saudio_logger { - void (*func)( - const char* tag, // always "saudio" - uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info - uint32_t log_item_id, // SAUDIO_LOGITEM_* - const char* message_or_null, // a message string, may be nullptr in release mode - uint32_t line_nr, // line number in sokol_audio.h - const char* filename_or_null, // source filename, may be nullptr in release mode - void* user_data); - void* user_data; -} saudio_logger; - -/* - saudio_allocator - - Used in saudio_desc to provide custom memory-alloc and -free functions - to sokol_audio.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 saudio_allocator { - void* (*alloc)(size_t size, void* user_data); - void (*free)(void* ptr, void* user_data); - void* user_data; -} saudio_allocator; - -typedef struct saudio_desc { - int sample_rate; // requested sample rate - int num_channels; // number of channels, default: 1 (mono) - int buffer_frames; // number of frames in streaming buffer - int packet_frames; // number of frames in a packet - int num_packets; // number of packets in packet queue - void (*stream_cb)(float* buffer, int num_frames, int num_channels); // optional streaming callback (no user data) - void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data - void* user_data; // optional user data argument for stream_userdata_cb - saudio_allocator allocator; // optional allocation override functions - saudio_logger logger; // optional logging function (default: NO LOGGING!) -} saudio_desc; - -/* setup sokol-audio */ -SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc); -/* shutdown sokol-audio */ -SOKOL_AUDIO_API_DECL void saudio_shutdown(void); -/* true after setup if audio backend was successfully initialized */ -SOKOL_AUDIO_API_DECL bool saudio_isvalid(void); -/* return the saudio_desc.user_data pointer */ -SOKOL_AUDIO_API_DECL void* saudio_userdata(void); -/* return a copy of the original saudio_desc struct */ -SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void); -/* actual sample rate */ -SOKOL_AUDIO_API_DECL int saudio_sample_rate(void); -/* return actual backend buffer size in number of frames */ -SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void); -/* actual number of channels */ -SOKOL_AUDIO_API_DECL int saudio_channels(void); -/* return true if audio context is currently suspended (only in WebAudio backend, all other backends return false) */ -SOKOL_AUDIO_API_DECL bool saudio_suspended(void); -/* get current number of frames to fill packet queue */ -SOKOL_AUDIO_API_DECL int saudio_expect(void); -/* push sample frames from main thread, returns number of frames actually pushed */ -SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames); - -#ifdef __cplusplus -} /* extern "C" */ - -/* reference-based equivalents for c++ */ -inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); } - -#endif -#endif // SOKOL_AUDIO_INCLUDED - -// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ -// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ -// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ -// -// >>implementation -#ifdef SOKOL_AUDIO_IMPL -#define SOKOL_AUDIO_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 saudio_desc.allocator to override memory allocation functions" -#endif - -#include // alloc, free -#include // memset, memcpy -#include // size_t - -#ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL -#endif -#ifndef SOKOL_DEBUG - #ifndef NDEBUG - #define SOKOL_DEBUG - #endif -#endif -#ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) -#endif - -#ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) || defined(__clang__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif -#endif - -#ifndef _SOKOL_UNUSED - #define _SOKOL_UNUSED(x) (void)(x) -#endif - -// platform detection defines -#if defined(SOKOL_DUMMY_BACKEND) - // nothing -#elif defined(__APPLE__) - #define _SAUDIO_APPLE (1) - #include - #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE - #define _SAUDIO_IOS (1) - #else - #define _SAUDIO_MACOS (1) - #endif -#elif defined(__EMSCRIPTEN__) - #define _SAUDIO_EMSCRIPTEN (1) -#elif defined(_WIN32) - #define _SAUDIO_WINDOWS (1) - #include - #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) - #error "sokol_audio.h no longer supports UWP" - #endif -#elif defined(__ANDROID__) - #define _SAUDIO_ANDROID (1) - #if !defined(SAUDIO_ANDROID_SLES) && !defined(SAUDIO_ANDROID_AAUDIO) - #define SAUDIO_ANDROID_AAUDIO (1) - #endif -#elif defined(__linux__) || defined(__unix__) - #define _SAUDIO_LINUX (1) -#else -#error "sokol_audio.h: Unknown platform" -#endif - -// platform-specific headers and definitions -#if defined(SOKOL_DUMMY_BACKEND) - #define _SAUDIO_NOTHREADS (1) -#elif defined(_SAUDIO_WINDOWS) - #define _SAUDIO_WINTHREADS (1) - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #ifndef NOMINMAX - #define NOMINMAX - #endif - #include - #include - #pragma comment (lib, "kernel32") - #pragma comment (lib, "ole32") - #ifndef CINTERFACE - #define CINTERFACE - #endif - #ifndef COBJMACROS - #define COBJMACROS - #endif - #ifndef CONST_VTABLE - #define CONST_VTABLE - #endif - #include - #include - static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} }; - static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} }; - static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} }; - static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} }; - static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2} }; - static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} }; - static const GUID _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} }; - #if defined(__cplusplus) - #define _SOKOL_AUDIO_WIN32COM_ID(x) (x) - #else - #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x) - #endif - /* fix for Visual Studio 2015 SDKs */ - #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM - #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 - #endif - #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY - #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 - #endif - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable:4505) /* unreferenced local function has been removed */ - #endif -#elif defined(_SAUDIO_APPLE) - #define _SAUDIO_PTHREADS (1) - #include - #if defined(_SAUDIO_IOS) - // always use system headers on iOS (for now at least) - #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) - #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1) - #endif - #if !defined(__cplusplus) - #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) - #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" - #endif - #endif - #include - #include - #else - #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) - #include - #endif - #endif -#elif defined(_SAUDIO_ANDROID) - #define _SAUDIO_PTHREADS (1) - #include - #if defined(SAUDIO_ANDROID_SLES) - #include "SLES/OpenSLES_Android.h" - #elif defined(SAUDIO_ANDROID_AAUDIO) - #include "aaudio/AAudio.h" - #endif -#elif defined(_SAUDIO_LINUX) - #include - #define _SAUDIO_PTHREADS (1) - #include - #define ALSA_PCM_NEW_HW_PARAMS_API - #include -#elif defined(__EMSCRIPTEN__) - #define _SAUDIO_NOTHREADS (1) - #include -#endif - -#define _saudio_def(val, def) (((val) == 0) ? (def) : (val)) -#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) - -#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100) -#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048) -#define _SAUDIO_DEFAULT_PACKET_FRAMES (128) -#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4) - -#ifndef SAUDIO_RING_MAX_SLOTS -#define SAUDIO_RING_MAX_SLOTS (1024) -#endif - -// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██████ ██ ██ ██ ██ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██ ██ ██ ██████ ██████ ██ ███████ -// -// >>structs -#if defined(_SAUDIO_PTHREADS) - -typedef struct { - pthread_mutex_t mutex; -} _saudio_mutex_t; - -#elif defined(_SAUDIO_WINTHREADS) - -typedef struct { - CRITICAL_SECTION critsec; -} _saudio_mutex_t; - -#elif defined(_SAUDIO_NOTHREADS) - -typedef struct { - int dummy_mutex; -} _saudio_mutex_t; - -#endif - -#if defined(SOKOL_DUMMY_BACKEND) - -typedef struct { - int dummy; -} _saudio_dummy_backend_t; - -#elif defined(_SAUDIO_APPLE) - -#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) - -typedef AudioQueueRef _saudio_AudioQueueRef; -typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef; -typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription; -typedef OSStatus _saudio_OSStatus; - -#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM) -#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat) -#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked) - -#else -#ifdef __cplusplus -extern "C" { -#endif - -// embedded AudioToolbox declarations -typedef uint32_t _saudio_AudioFormatID; -typedef uint32_t _saudio_AudioFormatFlags; -typedef int32_t _saudio_OSStatus; -typedef uint32_t _saudio_SMPTETimeType; -typedef uint32_t _saudio_SMPTETimeFlags; -typedef uint32_t _saudio_AudioTimeStampFlags; -typedef void* _saudio_CFRunLoopRef; -typedef void* _saudio_CFStringRef; -typedef void* _saudio_AudioQueueRef; - -#define _saudio_kAudioFormatLinearPCM ('lpcm') -#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0) -#define _saudio_kAudioFormatFlagIsPacked (1U << 3) - -typedef struct _saudio_AudioStreamBasicDescription { - double mSampleRate; - _saudio_AudioFormatID mFormatID; - _saudio_AudioFormatFlags mFormatFlags; - uint32_t mBytesPerPacket; - uint32_t mFramesPerPacket; - uint32_t mBytesPerFrame; - uint32_t mChannelsPerFrame; - uint32_t mBitsPerChannel; - uint32_t mReserved; -} _saudio_AudioStreamBasicDescription; - -typedef struct _saudio_AudioStreamPacketDescription { - int64_t mStartOffset; - uint32_t mVariableFramesInPacket; - uint32_t mDataByteSize; -} _saudio_AudioStreamPacketDescription; - -typedef struct _saudio_SMPTETime { - int16_t mSubframes; - int16_t mSubframeDivisor; - uint32_t mCounter; - _saudio_SMPTETimeType mType; - _saudio_SMPTETimeFlags mFlags; - int16_t mHours; - int16_t mMinutes; - int16_t mSeconds; - int16_t mFrames; -} _saudio_SMPTETime; - -typedef struct _saudio_AudioTimeStamp { - double mSampleTime; - uint64_t mHostTime; - double mRateScalar; - uint64_t mWordClockTime; - _saudio_SMPTETime mSMPTETime; - _saudio_AudioTimeStampFlags mFlags; - uint32_t mReserved; -} _saudio_AudioTimeStamp; - -typedef struct _saudio_AudioQueueBuffer { - const uint32_t mAudioDataBytesCapacity; - void* const mAudioData; - uint32_t mAudioDataByteSize; - void * mUserData; - const uint32_t mPacketDescriptionCapacity; - _saudio_AudioStreamPacketDescription* const mPacketDescriptions; - uint32_t mPacketDescriptionCount; -} _saudio_AudioQueueBuffer; -typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef; - -typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer); - -extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ); -extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate); -extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer); -extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs); -extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime); -extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS - -typedef struct { - _saudio_AudioQueueRef ca_audio_queue; - #if defined(_SAUDIO_IOS) - id ca_interruption_handler; - #endif -} _saudio_apple_backend_t; - -#elif defined(_SAUDIO_LINUX) - -typedef struct { - snd_pcm_t* device; - float* buffer; - int buffer_byte_size; - int buffer_frames; - pthread_t thread; - bool thread_stop; -} _saudio_alsa_backend_t; - -#elif defined(SAUDIO_ANDROID_SLES) - -#define SAUDIO_SLES_NUM_BUFFERS (2) - -typedef struct { - pthread_mutex_t mutex; - pthread_cond_t cond; - int count; -} _saudio_sles_semaphore_t; - -typedef struct { - SLObjectItf engine_obj; - SLEngineItf engine; - SLObjectItf output_mix_obj; - SLVolumeItf output_mix_vol; - SLDataLocator_OutputMix out_locator; - SLDataSink dst_data_sink; - SLObjectItf player_obj; - SLPlayItf player; - SLVolumeItf player_vol; - SLAndroidSimpleBufferQueueItf player_buffer_queue; - - int16_t* output_buffers[SAUDIO_SLES_NUM_BUFFERS]; - float* src_buffer; - int active_buffer; - _saudio_sles_semaphore_t buffer_sem; - pthread_t thread; - volatile int thread_stop; - SLDataLocator_AndroidSimpleBufferQueue in_locator; -} _saudio_sles_backend_t; - -#elif defined(SAUDIO_ANDROID_AAUDIO) - -typedef struct { - AAudioStreamBuilder* builder; - AAudioStream* stream; - pthread_t thread; - pthread_mutex_t mutex; -} _saudio_aaudio_backend_t; - -#elif defined(_SAUDIO_WINDOWS) - -typedef struct { - HANDLE thread_handle; - HANDLE buffer_end_event; - bool stop; - UINT32 dst_buffer_frames; - int src_buffer_frames; - int src_buffer_byte_size; - int src_buffer_pos; - float* src_buffer; -} _saudio_wasapi_thread_data_t; - -typedef struct { - IMMDeviceEnumerator* device_enumerator; - IMMDevice* device; - IAudioClient* audio_client; - IAudioRenderClient* render_client; - _saudio_wasapi_thread_data_t thread; -} _saudio_wasapi_backend_t; - -#elif defined(_SAUDIO_EMSCRIPTEN) - -typedef struct { - uint8_t* buffer; -} _saudio_web_backend_t; - -#else -#error "unknown platform" -#endif - -#if defined(SOKOL_DUMMY_BACKEND) -typedef _saudio_dummy_backend_t _saudio_backend_t; -#elif defined(_SAUDIO_APPLE) -typedef _saudio_apple_backend_t _saudio_backend_t; -#elif defined(_SAUDIO_EMSCRIPTEN) -typedef _saudio_web_backend_t _saudio_backend_t; -#elif defined(_SAUDIO_WINDOWS) -typedef _saudio_wasapi_backend_t _saudio_backend_t; -#elif defined(SAUDIO_ANDROID_SLES) -typedef _saudio_sles_backend_t _saudio_backend_t; -#elif defined(SAUDIO_ANDROID_AAUDIO) -typedef _saudio_aaudio_backend_t _saudio_backend_t; -#elif defined(_SAUDIO_LINUX) -typedef _saudio_alsa_backend_t _saudio_backend_t; -#endif - -/* a ringbuffer structure */ -typedef struct { - int head; // next slot to write to - int tail; // next slot to read from - int num; // number of slots in queue - int queue[SAUDIO_RING_MAX_SLOTS]; -} _saudio_ring_t; - -/* a packet FIFO structure */ -typedef struct { - bool valid; - int packet_size; /* size of a single packets in bytes(!) */ - int num_packets; /* number of packet in fifo */ - uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */ - int cur_packet; /* current write-packet */ - int cur_offset; /* current byte-offset into current write packet */ - _saudio_mutex_t mutex; /* mutex for thread-safe access */ - _saudio_ring_t read_queue; /* buffers with data, ready to be streamed */ - _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */ -} _saudio_fifo_t; - -/* sokol-audio state */ -typedef struct { - bool valid; - void (*stream_cb)(float* buffer, int num_frames, int num_channels); - void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); - void* user_data; - int sample_rate; /* sample rate */ - int buffer_frames; /* number of frames in streaming buffer */ - int bytes_per_frame; /* filled by backend */ - int packet_frames; /* number of frames in a packet */ - int num_packets; /* number of packets in packet queue */ - int num_channels; /* actual number of channels */ - saudio_desc desc; - _saudio_fifo_t fifo; - _saudio_backend_t backend; -} _saudio_state_t; - -_SOKOL_PRIVATE _saudio_state_t _saudio; - -_SOKOL_PRIVATE bool _saudio_has_callback(void) { - return (_saudio.stream_cb || _saudio.stream_userdata_cb); -} - -_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) { - if (_saudio.stream_cb) { - _saudio.stream_cb(buffer, num_frames, num_channels); - } - else if (_saudio.stream_userdata_cb) { - _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data); - } -} - -// ██ ██████ ██████ ██████ ██ ███ ██ ██████ -// ██ ██ ██ ██ ██ ██ ████ ██ ██ -// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ -// -// >>logging -#if defined(SOKOL_DEBUG) -#define _SAUDIO_LOGITEM_XMACRO(item,msg) #item ": " msg, -static const char* _saudio_log_messages[] = { - _SAUDIO_LOG_ITEMS -}; -#undef _SAUDIO_LOGITEM_XMACRO -#endif // SOKOL_DEBUG - -#define _SAUDIO_PANIC(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 0, __LINE__) -#define _SAUDIO_ERROR(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 1, __LINE__) -#define _SAUDIO_WARN(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 2, __LINE__) -#define _SAUDIO_INFO(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 3, __LINE__) - -static void _saudio_log(saudio_log_item log_item, uint32_t log_level, uint32_t line_nr) { - if (_saudio.desc.logger.func) { - #if defined(SOKOL_DEBUG) - const char* filename = __FILE__; - const char* message = _saudio_log_messages[log_item]; - #else - const char* filename = 0; - const char* message = 0; - #endif - _saudio.desc.logger.func("saudio", log_level, log_item, message, line_nr, filename, _saudio.desc.logger.user_data); - } - else { - // for log level PANIC it would be 'undefined behaviour' to continue - if (log_level == 0) { - abort(); - } - } -} - -// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ -// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ -// -// >>memory -_SOKOL_PRIVATE void _saudio_clear(void* ptr, size_t size) { - SOKOL_ASSERT(ptr && (size > 0)); - memset(ptr, 0, size); -} - -_SOKOL_PRIVATE void* _saudio_malloc(size_t size) { - SOKOL_ASSERT(size > 0); - void* ptr; - if (_saudio.desc.allocator.alloc) { - ptr = _saudio.desc.allocator.alloc(size, _saudio.desc.allocator.user_data); - } - else { - ptr = malloc(size); - } - if (0 == ptr) { - _SAUDIO_PANIC(MALLOC_FAILED); - } - return ptr; -} - -_SOKOL_PRIVATE void* _saudio_malloc_clear(size_t size) { - void* ptr = _saudio_malloc(size); - _saudio_clear(ptr, size); - return ptr; -} - -_SOKOL_PRIVATE void _saudio_free(void* ptr) { - if (_saudio.desc.allocator.free) { - _saudio.desc.allocator.free(ptr, _saudio.desc.allocator.user_data); - } - else { - free(ptr); - } -} - -// ███ ███ ██ ██ ████████ ███████ ██ ██ -// ████ ████ ██ ██ ██ ██ ██ ██ -// ██ ████ ██ ██ ██ ██ █████ ███ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██████ ██ ███████ ██ ██ -// -// >>mutex -#if defined(_SAUDIO_NOTHREADS) - -_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; } -_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; } -_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; } -_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; } - -#elif defined(_SAUDIO_PTHREADS) - -_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutex_init(&m->mutex, &attr); -} - -_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { - pthread_mutex_destroy(&m->mutex); -} - -_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { - pthread_mutex_lock(&m->mutex); -} - -_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { - pthread_mutex_unlock(&m->mutex); -} - -#elif defined(_SAUDIO_WINTHREADS) - -_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { - InitializeCriticalSection(&m->critsec); -} - -_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { - DeleteCriticalSection(&m->critsec); -} - -_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { - EnterCriticalSection(&m->critsec); -} - -_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { - LeaveCriticalSection(&m->critsec); -} -#else -#error "sokol_audio.h: unknown platform!" -#endif - -// ██████ ██ ███ ██ ██████ ██████ ██ ██ ███████ ███████ ███████ ██████ -// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██ ██ ██ ██ ██ ███ ██████ ██ ██ █████ █████ █████ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ████ ██████ ██████ ██████ ██ ██ ███████ ██ ██ -// -// >>ringbuffer -_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) { - return (i % ring->num); -} - -_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) { - SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS); - ring->head = 0; - ring->tail = 0; - /* one slot reserved to detect 'full' vs 'empty' */ - ring->num = num_slots + 1; -} - -_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) { - return _saudio_ring_idx(ring, ring->head + 1) == ring->tail; -} - -_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) { - return ring->head == ring->tail; -} - -_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) { - int count; - if (ring->head >= ring->tail) { - count = ring->head - ring->tail; - } - else { - count = (ring->head + ring->num) - ring->tail; - } - SOKOL_ASSERT(count < ring->num); - return count; -} - -_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) { - SOKOL_ASSERT(!_saudio_ring_full(ring)); - ring->queue[ring->head] = val; - ring->head = _saudio_ring_idx(ring, ring->head + 1); -} - -_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) { - SOKOL_ASSERT(!_saudio_ring_empty(ring)); - int val = ring->queue[ring->tail]; - ring->tail = _saudio_ring_idx(ring, ring->tail + 1); - return val; -} - -// ███████ ██ ███████ ██████ -// ██ ██ ██ ██ ██ -// █████ ██ █████ ██ ██ -// ██ ██ ██ ██ ██ -// ██ ██ ██ ██████ -// -// >>fifo -_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) { - /* this must be called before initializing both the backend and the fifo itself! */ - _saudio_mutex_init(&fifo->mutex); -} - -_SOKOL_PRIVATE void _saudio_fifo_destroy_mutex(_saudio_fifo_t* fifo) { - _saudio_mutex_destroy(&fifo->mutex); -} - -_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) { - /* NOTE: there's a chicken-egg situation during the init phase where the - streaming thread must be started before the fifo is actually initialized, - thus the fifo init must already be protected from access by the fifo_read() func. - */ - _saudio_mutex_lock(&fifo->mutex); - SOKOL_ASSERT((packet_size > 0) && (num_packets > 0)); - fifo->packet_size = packet_size; - fifo->num_packets = num_packets; - fifo->base_ptr = (uint8_t*) _saudio_malloc((size_t)(packet_size * num_packets)); - fifo->cur_packet = -1; - fifo->cur_offset = 0; - _saudio_ring_init(&fifo->read_queue, num_packets); - _saudio_ring_init(&fifo->write_queue, num_packets); - for (int i = 0; i < num_packets; i++) { - _saudio_ring_enqueue(&fifo->write_queue, i); - } - SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue)); - SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets); - SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue)); - SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0); - fifo->valid = true; - _saudio_mutex_unlock(&fifo->mutex); -} - -_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) { - SOKOL_ASSERT(fifo->base_ptr); - _saudio_free(fifo->base_ptr); - fifo->base_ptr = 0; - fifo->valid = false; -} - -_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) { - _saudio_mutex_lock(&fifo->mutex); - int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size); - if (fifo->cur_packet != -1) { - num_bytes += fifo->packet_size - fifo->cur_offset; - } - _saudio_mutex_unlock(&fifo->mutex); - SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size))); - return num_bytes; -} - -/* write new data to the write queue, this is called from main thread */ -_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) { - /* returns the number of bytes written, this will be smaller then requested - if the write queue runs full - */ - int all_to_copy = num_bytes; - while (all_to_copy > 0) { - /* need to grab a new packet? */ - if (fifo->cur_packet == -1) { - _saudio_mutex_lock(&fifo->mutex); - if (!_saudio_ring_empty(&fifo->write_queue)) { - fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue); - } - _saudio_mutex_unlock(&fifo->mutex); - SOKOL_ASSERT(fifo->cur_offset == 0); - } - /* append data to current write packet */ - if (fifo->cur_packet != -1) { - int to_copy = all_to_copy; - const int max_copy = fifo->packet_size - fifo->cur_offset; - if (to_copy > max_copy) { - to_copy = max_copy; - } - uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset; - memcpy(dst, ptr, (size_t)to_copy); - ptr += to_copy; - fifo->cur_offset += to_copy; - all_to_copy -= to_copy; - SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size); - SOKOL_ASSERT(all_to_copy >= 0); - } - else { - /* early out if we're starving */ - int bytes_copied = num_bytes - all_to_copy; - SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes)); - return bytes_copied; - } - /* if write packet is full, push to read queue */ - if (fifo->cur_offset == fifo->packet_size) { - _saudio_mutex_lock(&fifo->mutex); - _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet); - _saudio_mutex_unlock(&fifo->mutex); - fifo->cur_packet = -1; - fifo->cur_offset = 0; - } - } - SOKOL_ASSERT(all_to_copy == 0); - return num_bytes; -} - -/* read queued data, this is called form the stream callback (maybe separate thread) */ -_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) { - /* NOTE: fifo_read might be called before the fifo is properly initialized */ - _saudio_mutex_lock(&fifo->mutex); - int num_bytes_copied = 0; - if (fifo->valid) { - SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size)); - SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets)); - const int num_packets_needed = num_bytes / fifo->packet_size; - uint8_t* dst = ptr; - /* either pull a full buffer worth of data, or nothing */ - if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) { - for (int i = 0; i < num_packets_needed; i++) { - int packet_index = _saudio_ring_dequeue(&fifo->read_queue); - _saudio_ring_enqueue(&fifo->write_queue, packet_index); - const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size; - memcpy(dst, src, (size_t)fifo->packet_size); - dst += fifo->packet_size; - num_bytes_copied += fifo->packet_size; - } - SOKOL_ASSERT(num_bytes == num_bytes_copied); - } - } - _saudio_mutex_unlock(&fifo->mutex); - return num_bytes_copied; -} - -// ██████ ██ ██ ███ ███ ███ ███ ██ ██ -// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ -// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██████ ██ ██ ██ ██ ██ -// -// >>dummy -#if defined(SOKOL_DUMMY_BACKEND) -_SOKOL_PRIVATE bool _saudio_dummy_backend_init(void) { - _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); - return true; -}; -_SOKOL_PRIVATE void _saudio_dummy_backend_shutdown(void) { }; - -// █████ ██ ███████ █████ -// ██ ██ ██ ██ ██ ██ -// ███████ ██ ███████ ███████ -// ██ ██ ██ ██ ██ ██ -// ██ ██ ███████ ███████ ██ ██ -// -// >>alsa -#elif defined(_SAUDIO_LINUX) - -/* the streaming callback runs in a separate thread */ -_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) { - _SOKOL_UNUSED(param); - while (!_saudio.backend.thread_stop) { - /* snd_pcm_writei() will be blocking until it needs data */ - int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames); - if (write_res < 0) { - /* underrun occurred */ - snd_pcm_prepare(_saudio.backend.device); - } - else { - /* fill the streaming buffer with new data */ - if (_saudio_has_callback()) { - _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels); - } - else { - if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) { - /* not enough read data available, fill the entire buffer with silence */ - _saudio_clear(_saudio.backend.buffer, (size_t)_saudio.backend.buffer_byte_size); - } - } - } - } - return 0; -} - -_SOKOL_PRIVATE bool _saudio_alsa_backend_init(void) { - int dir; uint32_t rate; - int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0); - if (rc < 0) { - _SAUDIO_ERROR(ALSA_SND_PCM_OPEN_FAILED); - return false; - } - - /* configuration works by restricting the 'configuration space' step - by step, we require all parameters except the sample rate to - match perfectly - */ - snd_pcm_hw_params_t* params = 0; - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_hw_params_any(_saudio.backend.device, params); - snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) { - _SAUDIO_ERROR(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED); - goto error; - } - if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) { - _SAUDIO_ERROR(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED); - goto error; - } - if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) { - _SAUDIO_ERROR(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED); - goto error; - } - /* let ALSA pick a nearby sampling rate */ - rate = (uint32_t) _saudio.sample_rate; - dir = 0; - if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) { - _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED); - goto error; - } - if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) { - _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_FAILED); - goto error; - } - - /* read back actual sample rate and channels */ - _saudio.sample_rate = (int)rate; - _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); - - /* allocate the streaming buffer */ - _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame; - _saudio.backend.buffer_frames = _saudio.buffer_frames; - _saudio.backend.buffer = (float*) _saudio_malloc_clear((size_t)_saudio.backend.buffer_byte_size); - - /* create the buffer-streaming start thread */ - if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) { - _SAUDIO_ERROR(ALSA_PTHREAD_CREATE_FAILED); - goto error; - } - - return true; -error: - if (_saudio.backend.device) { - snd_pcm_close(_saudio.backend.device); - _saudio.backend.device = 0; - } - return false; -}; - -_SOKOL_PRIVATE void _saudio_alsa_backend_shutdown(void) { - SOKOL_ASSERT(_saudio.backend.device); - _saudio.backend.thread_stop = true; - pthread_join(_saudio.backend.thread, 0); - snd_pcm_drain(_saudio.backend.device); - snd_pcm_close(_saudio.backend.device); - _saudio_free(_saudio.backend.buffer); -}; - -// ██ ██ █████ ███████ █████ ██████ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ █ ██ ███████ ███████ ███████ ██████ ██ -// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ -// ███ ███ ██ ██ ███████ ██ ██ ██ ██ -// -// >>wasapi -#elif defined(_SAUDIO_WINDOWS) - -/* fill intermediate buffer with new data and reset buffer_pos */ -_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) { - if (_saudio_has_callback()) { - _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels); - } - else { - if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) { - /* not enough read data available, fill the entire buffer with silence */ - _saudio_clear(_saudio.backend.thread.src_buffer, (size_t)_saudio.backend.thread.src_buffer_byte_size); - } - } -} - -_SOKOL_PRIVATE int _saudio_wasapi_min(int a, int b) { - return (a < b) ? a : b; -} - -_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) { - BYTE* wasapi_buffer = 0; - if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) { - return; - } - SOKOL_ASSERT(wasapi_buffer); - - /* copy samples to WASAPI buffer, refill source buffer if needed */ - int num_remaining_samples = num_frames * _saudio.num_channels; - int buffer_pos = _saudio.backend.thread.src_buffer_pos; - const int buffer_size_in_samples = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float); - float* dst = (float*)wasapi_buffer; - const float* dst_end = dst + num_remaining_samples; - _SOKOL_UNUSED(dst_end); // suppress unused warning in release mode - const float* src = _saudio.backend.thread.src_buffer; - - while (num_remaining_samples > 0) { - if (0 == buffer_pos) { - _saudio_wasapi_fill_buffer(); - } - const int samples_to_copy = _saudio_wasapi_min(num_remaining_samples, buffer_size_in_samples - buffer_pos); - SOKOL_ASSERT((buffer_pos + samples_to_copy) <= buffer_size_in_samples); - SOKOL_ASSERT((dst + samples_to_copy) <= dst_end); - memcpy(dst, &src[buffer_pos], (size_t)samples_to_copy * sizeof(float)); - num_remaining_samples -= samples_to_copy; - SOKOL_ASSERT(num_remaining_samples >= 0); - buffer_pos += samples_to_copy; - dst += samples_to_copy; - - SOKOL_ASSERT(buffer_pos <= buffer_size_in_samples); - if (buffer_pos == buffer_size_in_samples) { - buffer_pos = 0; - } - } - _saudio.backend.thread.src_buffer_pos = buffer_pos; - IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0); -} - -_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) { - (void)param; - _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames); - IAudioClient_Start(_saudio.backend.audio_client); - while (!_saudio.backend.thread.stop) { - WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE); - UINT32 padding = 0; - if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) { - continue; - } - SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding); - int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding; - if (num_frames > 0) { - _saudio_wasapi_submit_buffer(num_frames); - } - } - return 0; -} - -_SOKOL_PRIVATE void _saudio_wasapi_release(void) { - if (_saudio.backend.thread.src_buffer) { - _saudio_free(_saudio.backend.thread.src_buffer); - _saudio.backend.thread.src_buffer = 0; - } - if (_saudio.backend.render_client) { - IAudioRenderClient_Release(_saudio.backend.render_client); - _saudio.backend.render_client = 0; - } - if (_saudio.backend.audio_client) { - IAudioClient_Release(_saudio.backend.audio_client); - _saudio.backend.audio_client = 0; - } - if (_saudio.backend.device) { - IMMDevice_Release(_saudio.backend.device); - _saudio.backend.device = 0; - } - if (_saudio.backend.device_enumerator) { - IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator); - _saudio.backend.device_enumerator = 0; - } - if (0 != _saudio.backend.thread.buffer_end_event) { - CloseHandle(_saudio.backend.thread.buffer_end_event); - _saudio.backend.thread.buffer_end_event = 0; - } -} - -_SOKOL_PRIVATE bool _saudio_wasapi_backend_init(void) { - REFERENCE_TIME dur; - /* CoInitializeEx could have been called elsewhere already, in which - case the function returns with S_FALSE (thus it does not make much - sense to check the result) - */ - HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); - _SOKOL_UNUSED(hr); - _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0); - if (0 == _saudio.backend.thread.buffer_end_event) { - _SAUDIO_ERROR(WASAPI_CREATE_EVENT_FAILED); - goto error; - } - if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator), - 0, CLSCTX_ALL, - _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator), - (void**)&_saudio.backend.device_enumerator))) - { - _SAUDIO_ERROR(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED); - goto error; - } - if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator, - eRender, eConsole, - &_saudio.backend.device))) - { - _SAUDIO_ERROR(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED); - goto error; - } - if (FAILED(IMMDevice_Activate(_saudio.backend.device, - _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), - CLSCTX_ALL, 0, - (void**)&_saudio.backend.audio_client))) - { - _SAUDIO_ERROR(WASAPI_DEVICE_ACTIVATE_FAILED); - goto error; - } - - WAVEFORMATEXTENSIBLE fmtex; - _saudio_clear(&fmtex, sizeof(fmtex)); - fmtex.Format.nChannels = (WORD)_saudio.num_channels; - fmtex.Format.nSamplesPerSec = (DWORD)_saudio.sample_rate; - fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - fmtex.Format.wBitsPerSample = 32; - fmtex.Format.nBlockAlign = (fmtex.Format.nChannels * fmtex.Format.wBitsPerSample) / 8; - fmtex.Format.nAvgBytesPerSec = fmtex.Format.nSamplesPerSec * fmtex.Format.nBlockAlign; - fmtex.Format.cbSize = 22; /* WORD + DWORD + GUID */ - fmtex.Samples.wValidBitsPerSample = 32; - if (_saudio.num_channels == 1) { - fmtex.dwChannelMask = SPEAKER_FRONT_CENTER; - } - else { - fmtex.dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT; - } - fmtex.SubFormat = _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - dur = (REFERENCE_TIME) - (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0))); - if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client, - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, - dur, 0, (WAVEFORMATEX*)&fmtex, 0))) - { - _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED); - goto error; - } - if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) { - _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED); - goto error; - } - if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client, - _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient), - (void**)&_saudio.backend.render_client))) - { - _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED); - goto error; - } - if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) { - _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED); - goto error; - } - _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); - _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames; - _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame; - - /* allocate an intermediate buffer for sample format conversion */ - _saudio.backend.thread.src_buffer = (float*) _saudio_malloc((size_t)_saudio.backend.thread.src_buffer_byte_size); - - /* create streaming thread */ - _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0); - if (0 == _saudio.backend.thread.thread_handle) { - _SAUDIO_ERROR(WASAPI_CREATE_THREAD_FAILED); - goto error; - } - return true; -error: - _saudio_wasapi_release(); - return false; -} - -_SOKOL_PRIVATE void _saudio_wasapi_backend_shutdown(void) { - if (_saudio.backend.thread.thread_handle) { - _saudio.backend.thread.stop = true; - SetEvent(_saudio.backend.thread.buffer_end_event); - WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE); - CloseHandle(_saudio.backend.thread.thread_handle); - _saudio.backend.thread.thread_handle = 0; - } - if (_saudio.backend.audio_client) { - IAudioClient_Stop(_saudio.backend.audio_client); - } - _saudio_wasapi_release(); - CoUninitialize(); -} - -// ██ ██ ███████ ██████ █████ ██ ██ ██████ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ █ ██ █████ ██████ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███ ███ ███████ ██████ ██ ██ ██████ ██████ ██ ██████ -// -// >>webaudio -#elif defined(_SAUDIO_EMSCRIPTEN) - -#ifdef __cplusplus -extern "C" { -#endif - -EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) { - SOKOL_ASSERT(_saudio.backend.buffer); - if (num_frames == _saudio.buffer_frames) { - if (_saudio_has_callback()) { - _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels); - } - else { - const int num_bytes = num_frames * _saudio.bytes_per_frame; - if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) { - /* not enough read data available, fill the entire buffer with silence */ - _saudio_clear(_saudio.backend.buffer, (size_t)num_bytes); - } - } - int res = (int) _saudio.backend.buffer; - return res; - } - else { - return 0; - } -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* setup the WebAudio context and attach a ScriptProcessorNode */ -EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), { - Module._saudio_context = null; - Module._saudio_node = null; - if (typeof AudioContext !== 'undefined') { - Module._saudio_context = new AudioContext({ - sampleRate: sample_rate, - latencyHint: 'interactive', - }); - } - else { - Module._saudio_context = null; - console.log('sokol_audio.h: no WebAudio support'); - } - if (Module._saudio_context) { - console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate); - Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels); - Module._saudio_node.onaudioprocess = (event) => { - const num_frames = event.outputBuffer.length; - const ptr = __saudio_emsc_pull(num_frames); - if (ptr) { - const num_channels = event.outputBuffer.numberOfChannels; - for (let chn = 0; chn < num_channels; chn++) { - const chan = event.outputBuffer.getChannelData(chn); - for (let i = 0; i < num_frames; i++) { - chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)] - } - } - } - }; - Module._saudio_node.connect(Module._saudio_context.destination); - - // in some browsers, WebAudio needs to be activated on a user action - const resume_webaudio = () => { - if (Module._saudio_context) { - if (Module._saudio_context.state === 'suspended') { - Module._saudio_context.resume(); - } - } - }; - document.addEventListener('click', resume_webaudio, {once:true}); - document.addEventListener('touchend', resume_webaudio, {once:true}); - document.addEventListener('keydown', resume_webaudio, {once:true}); - return 1; - } - else { - return 0; - } -}); - -/* shutdown the WebAudioContext and ScriptProcessorNode */ -EM_JS(void, saudio_js_shutdown, (void), { - \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F - const ctx = Module._saudio_context; - if (ctx !== null) { - if (Module._saudio_node) { - Module._saudio_node.disconnect(); - } - ctx.close(); - Module._saudio_context = null; - Module._saudio_node = null; - } -}); - -/* get the actual sample rate back from the WebAudio context */ -EM_JS(int, saudio_js_sample_rate, (void), { - if (Module._saudio_context) { - return Module._saudio_context.sampleRate; - } - else { - return 0; - } -}); - -/* get the actual buffer size in number of frames */ -EM_JS(int, saudio_js_buffer_frames, (void), { - if (Module._saudio_node) { - return Module._saudio_node.bufferSize; - } - else { - return 0; - } -}); - -/* return 1 if the WebAudio context is currently suspended, else 0 */ -EM_JS(int, saudio_js_suspended, (void), { - if (Module._saudio_context) { - if (Module._saudio_context.state === 'suspended') { - return 1; - } - else { - return 0; - } - } -}); - -_SOKOL_PRIVATE bool _saudio_webaudio_backend_init(void) { - if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) { - _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; - _saudio.sample_rate = saudio_js_sample_rate(); - _saudio.buffer_frames = saudio_js_buffer_frames(); - const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame); - _saudio.backend.buffer = (uint8_t*) _saudio_malloc(buf_size); - return true; - } - else { - return false; - } -} - -_SOKOL_PRIVATE void _saudio_webaudio_backend_shutdown(void) { - saudio_js_shutdown(); - if (_saudio.backend.buffer) { - _saudio_free(_saudio.backend.buffer); - _saudio.backend.buffer = 0; - } -} - -// █████ █████ ██ ██ ██████ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ███████ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██████ ██████ ██ ██████ -// -// >>aaudio -#elif defined(SAUDIO_ANDROID_AAUDIO) - -_SOKOL_PRIVATE aaudio_data_callback_result_t _saudio_aaudio_data_callback(AAudioStream* stream, void* user_data, void* audio_data, int32_t num_frames) { - _SOKOL_UNUSED(user_data); - _SOKOL_UNUSED(stream); - if (_saudio_has_callback()) { - _saudio_stream_callback((float*)audio_data, (int)num_frames, _saudio.num_channels); - } - else { - uint8_t* ptr = (uint8_t*)audio_data; - int num_bytes = _saudio.bytes_per_frame * num_frames; - if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) { - // not enough read data available, fill the entire buffer with silence - memset(ptr, 0, (size_t)num_bytes); - } - } - return AAUDIO_CALLBACK_RESULT_CONTINUE; -} - -_SOKOL_PRIVATE bool _saudio_aaudio_start_stream(void) { - if (AAudioStreamBuilder_openStream(_saudio.backend.builder, &_saudio.backend.stream) != AAUDIO_OK) { - _SAUDIO_ERROR(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED); - return false; - } - AAudioStream_requestStart(_saudio.backend.stream); - return true; -} - -_SOKOL_PRIVATE void _saudio_aaudio_stop_stream(void) { - if (_saudio.backend.stream) { - AAudioStream_requestStop(_saudio.backend.stream); - AAudioStream_close(_saudio.backend.stream); - _saudio.backend.stream = 0; - } -} - -_SOKOL_PRIVATE void* _saudio_aaudio_restart_stream_thread_fn(void* param) { - _SOKOL_UNUSED(param); - _SAUDIO_WARN(AAUDIO_RESTARTING_STREAM_AFTER_ERROR); - pthread_mutex_lock(&_saudio.backend.mutex); - _saudio_aaudio_stop_stream(); - _saudio_aaudio_start_stream(); - pthread_mutex_unlock(&_saudio.backend.mutex); - return 0; -} - -_SOKOL_PRIVATE void _saudio_aaudio_error_callback(AAudioStream* stream, void* user_data, aaudio_result_t error) { - _SOKOL_UNUSED(stream); - _SOKOL_UNUSED(user_data); - if (error == AAUDIO_ERROR_DISCONNECTED) { - if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_aaudio_restart_stream_thread_fn, 0)) { - _SAUDIO_ERROR(AAUDIO_PTHREAD_CREATE_FAILED); - } - } -} - -_SOKOL_PRIVATE void _saudio_aaudio_backend_shutdown(void) { - pthread_mutex_lock(&_saudio.backend.mutex); - _saudio_aaudio_stop_stream(); - pthread_mutex_unlock(&_saudio.backend.mutex); - if (_saudio.backend.builder) { - AAudioStreamBuilder_delete(_saudio.backend.builder); - _saudio.backend.builder = 0; - } - pthread_mutex_destroy(&_saudio.backend.mutex); -} - -_SOKOL_PRIVATE bool _saudio_aaudio_backend_init(void) { - _SAUDIO_INFO(USING_AAUDIO_BACKEND); - - _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); - - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutex_init(&_saudio.backend.mutex, &attr); - - if (AAudio_createStreamBuilder(&_saudio.backend.builder) != AAUDIO_OK) { - _SAUDIO_ERROR(AAUDIO_CREATE_STREAMBUILDER_FAILED); - _saudio_aaudio_backend_shutdown(); - return false; - } - - AAudioStreamBuilder_setFormat(_saudio.backend.builder, AAUDIO_FORMAT_PCM_FLOAT); - AAudioStreamBuilder_setSampleRate(_saudio.backend.builder, _saudio.sample_rate); - AAudioStreamBuilder_setChannelCount(_saudio.backend.builder, _saudio.num_channels); - AAudioStreamBuilder_setBufferCapacityInFrames(_saudio.backend.builder, _saudio.buffer_frames * 2); - AAudioStreamBuilder_setFramesPerDataCallback(_saudio.backend.builder, _saudio.buffer_frames); - AAudioStreamBuilder_setDataCallback(_saudio.backend.builder, _saudio_aaudio_data_callback, 0); - AAudioStreamBuilder_setErrorCallback(_saudio.backend.builder, _saudio_aaudio_error_callback, 0); - - if (!_saudio_aaudio_start_stream()) { - _saudio_aaudio_backend_shutdown(); - return false; - } - - return true; -} - -// ██████ ██████ ███████ ███ ██ ███████ ██ ███████ ███████ -// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ -// ██ ██ ██████ █████ ██ ██ ██ ███████ ██ █████ ███████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██ ███████ ██ ████ ███████ ███████ ███████ ███████ -// -// >>opensles -// >>sles -#elif defined(SAUDIO_ANDROID_SLES) - -_SOKOL_PRIVATE void _saudio_sles_semaphore_init(_saudio_sles_semaphore_t* sem) { - sem->count = 0; - int r = pthread_mutex_init(&sem->mutex, NULL); - SOKOL_ASSERT(r == 0); - r = pthread_cond_init(&sem->cond, NULL); - SOKOL_ASSERT(r == 0); - (void)(r); -} - -_SOKOL_PRIVATE void _saudio_sles_semaphore_destroy(_saudio_sles_semaphore_t* sem) { - pthread_cond_destroy(&sem->cond); - pthread_mutex_destroy(&sem->mutex); -} - -_SOKOL_PRIVATE void _saudio_sles_semaphore_post(_saudio_sles_semaphore_t* sem, int count) { - int r = pthread_mutex_lock(&sem->mutex); - SOKOL_ASSERT(r == 0); - for (int ii = 0; ii < count; ii++) { - r = pthread_cond_signal(&sem->cond); - SOKOL_ASSERT(r == 0); - } - sem->count += count; - r = pthread_mutex_unlock(&sem->mutex); - SOKOL_ASSERT(r == 0); - (void)(r); -} - -_SOKOL_PRIVATE bool _saudio_sles_semaphore_wait(_saudio_sles_semaphore_t* sem) { - int r = pthread_mutex_lock(&sem->mutex); - SOKOL_ASSERT(r == 0); - while (r == 0 && sem->count <= 0) { - r = pthread_cond_wait(&sem->cond, &sem->mutex); - } - bool ok = (r == 0); - if (ok) { - --sem->count; - } - r = pthread_mutex_unlock(&sem->mutex); - (void)(r); - return ok; -} - -/* fill intermediate buffer with new data and reset buffer_pos */ -_SOKOL_PRIVATE void _saudio_sles_fill_buffer(void) { - int src_buffer_frames = _saudio.buffer_frames; - if (_saudio_has_callback()) { - _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels); - } - else { - const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float); - if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) { - /* not enough read data available, fill the entire buffer with silence */ - _saudio_clear(_saudio.backend.src_buffer, (size_t)src_buffer_byte_size); - } - } -} - -_SOKOL_PRIVATE void SLAPIENTRY _saudio_sles_play_cb(SLPlayItf player, void *context, SLuint32 event) { - _SOKOL_UNUSED(context); - _SOKOL_UNUSED(player); - if (event & SL_PLAYEVENT_HEADATEND) { - _saudio_sles_semaphore_post(&_saudio.backend.buffer_sem, 1); - } -} - -_SOKOL_PRIVATE void* _saudio_sles_thread_fn(void* param) { - _SOKOL_UNUSED(param); - while (!_saudio.backend.thread_stop) { - /* get next output buffer, advance, next buffer. */ - int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; - _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS; - int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; - - /* queue this buffer */ - const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short); - (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes); - - /* fill the next buffer */ - _saudio_sles_fill_buffer(); - const int num_samples = _saudio.num_channels * _saudio.buffer_frames; - for (int i = 0; i < num_samples; ++i) { - next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF); - } - - _saudio_sles_semaphore_wait(&_saudio.backend.buffer_sem); - } - - return 0; -} - -_SOKOL_PRIVATE void _saudio_sles_backend_shutdown(void) { - _saudio.backend.thread_stop = 1; - pthread_join(_saudio.backend.thread, 0); - - if (_saudio.backend.player_obj) { - (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj); - } - - if (_saudio.backend.output_mix_obj) { - (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj); - } - - if (_saudio.backend.engine_obj) { - (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj); - } - - for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; i++) { - _saudio_free(_saudio.backend.output_buffers[i]); - } - _saudio_free(_saudio.backend.src_buffer); -} - -_SOKOL_PRIVATE bool _saudio_sles_backend_init(void) { - _SAUDIO_INFO(USING_SLES_BACKEND); - - _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; - - for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; ++i) { - const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; - _saudio.backend.output_buffers[i] = (int16_t*) _saudio_malloc_clear((size_t)buffer_size_bytes); - } - - { - const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames; - _saudio.backend.src_buffer = (float*) _saudio_malloc_clear((size_t)buffer_size_bytes); - } - - /* Create engine */ - const SLEngineOption opts[] = { { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE } }; - if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_CREATE_ENGINE_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - - (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE); - if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - - /* Create output mix. */ - { - const SLInterfaceID ids[] = { SL_IID_VOLUME }; - const SLboolean req[] = { SL_BOOLEAN_FALSE }; - - if ((*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_CREATE_OUTPUT_MIX_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE); - - if ((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) { - _SAUDIO_WARN(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED); - } - } - - /* android buffer queue */ - _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - _saudio.backend.in_locator.numBuffers = SAUDIO_SLES_NUM_BUFFERS; - - /* data format */ - SLDataFormat_PCM format; - format.formatType = SL_DATAFORMAT_PCM; - format.numChannels = (SLuint32)_saudio.num_channels; - format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000); - format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format.containerSize = 16; - format.endianness = SL_BYTEORDER_LITTLEENDIAN; - - if (_saudio.num_channels == 2) { - format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - } else { - format.channelMask = SL_SPEAKER_FRONT_CENTER; - } - - SLDataSource src; - src.pLocator = &_saudio.backend.in_locator; - src.pFormat = &format; - - /* Output mix. */ - _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX; - _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj; - - _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator; - _saudio.backend.dst_data_sink.pFormat = NULL; - - /* setup player */ - { - const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; - const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE }; - - if ((*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req) != SL_RESULT_SUCCESS) - { - _SAUDIO_ERROR(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE); - - if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED); - } - if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue) != SL_RESULT_SUCCESS) { - _SAUDIO_ERROR(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED); - _saudio_sles_backend_shutdown(); - return false; - } - } - - /* begin */ - { - const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; - (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes); - _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS; - - (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_sles_play_cb, NULL); - (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND); - (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING); - } - - /* create the buffer-streaming start thread */ - if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_sles_thread_fn, 0)) { - _saudio_sles_backend_shutdown(); - return false; - } - - return true; -} - -// ██████ ██████ ██████ ███████ █████ ██ ██ ██████ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██████ ██ ██ ███████ ██ ██ ██████ ██████ ██ ██████ -// -// >>coreaudio -#elif defined(_SAUDIO_APPLE) - -#if defined(_SAUDIO_IOS) -#if __has_feature(objc_arc) -#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; } -#else -#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; } -#endif - -@interface _saudio_interruption_handler : NSObject { } -@end - -@implementation _saudio_interruption_handler --(id)init { - self = [super init]; - AVAudioSession* session = [AVAudioSession sharedInstance]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session]; - return self; -} - --(void)dealloc { - [self remove_handler]; - #if !__has_feature(objc_arc) - [super dealloc]; - #endif -} - --(void)remove_handler { - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil]; -} - --(void)handle_interruption:(NSNotification*)notification { - AVAudioSession* session = [AVAudioSession sharedInstance]; - SOKOL_ASSERT(session); - NSDictionary* dict = notification.userInfo; - SOKOL_ASSERT(dict); - NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue]; - switch (type) { - case AVAudioSessionInterruptionTypeBegan: - if (_saudio.backend.ca_audio_queue) { - AudioQueuePause(_saudio.backend.ca_audio_queue); - } - [session setActive:false error:nil]; - break; - case AVAudioSessionInterruptionTypeEnded: - [session setActive:true error:nil]; - if (_saudio.backend.ca_audio_queue) { - AudioQueueStart(_saudio.backend.ca_audio_queue, NULL); - } - break; - default: - break; - } -} -@end -#endif // _SAUDIO_IOS - -/* NOTE: the buffer data callback is called on a separate thread! */ -_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) { - _SOKOL_UNUSED(user_data); - if (_saudio_has_callback()) { - const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame; - const int num_channels = _saudio.num_channels; - _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels); - } - else { - uint8_t* ptr = (uint8_t*)buffer->mAudioData; - int num_bytes = (int) buffer->mAudioDataByteSize; - if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) { - /* not enough read data available, fill the entire buffer with silence */ - _saudio_clear(ptr, (size_t)num_bytes); - } - } - AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); -} - -_SOKOL_PRIVATE void _saudio_coreaudio_backend_shutdown(void) { - if (_saudio.backend.ca_audio_queue) { - AudioQueueStop(_saudio.backend.ca_audio_queue, true); - AudioQueueDispose(_saudio.backend.ca_audio_queue, false); - _saudio.backend.ca_audio_queue = 0; - } - #if defined(_SAUDIO_IOS) - /* remove interruption handler */ - if (_saudio.backend.ca_interruption_handler != nil) { - [_saudio.backend.ca_interruption_handler remove_handler]; - _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler); - } - /* deactivate audio session */ - AVAudioSession* session = [AVAudioSession sharedInstance]; - SOKOL_ASSERT(session); - [session setActive:false error:nil];; - #endif // _SAUDIO_IOS -} - -_SOKOL_PRIVATE bool _saudio_coreaudio_backend_init(void) { - SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue); - - #if defined(_SAUDIO_IOS) - /* activate audio session */ - AVAudioSession* session = [AVAudioSession sharedInstance]; - SOKOL_ASSERT(session != nil); - [session setCategory: AVAudioSessionCategoryPlayback error:nil]; - [session setActive:true error:nil]; - - /* create interruption handler */ - _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init]; - #endif - - /* create an audio queue with fp32 samples */ - _saudio_AudioStreamBasicDescription fmt; - _saudio_clear(&fmt, sizeof(fmt)); - fmt.mSampleRate = (double) _saudio.sample_rate; - fmt.mFormatID = _saudio_kAudioFormatLinearPCM; - fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked; - fmt.mFramesPerPacket = 1; - fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels; - fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels; - fmt.mBytesPerPacket = fmt.mBytesPerFrame; - fmt.mBitsPerChannel = 32; - _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue); - if (0 != res) { - _SAUDIO_ERROR(COREAUDIO_NEW_OUTPUT_FAILED); - return false; - } - SOKOL_ASSERT(_saudio.backend.ca_audio_queue); - - /* create 2 audio buffers */ - for (int i = 0; i < 2; i++) { - _saudio_AudioQueueBufferRef buf = NULL; - const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame; - res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf); - if (0 != res) { - _SAUDIO_ERROR(COREAUDIO_ALLOCATE_BUFFER_FAILED); - _saudio_coreaudio_backend_shutdown(); - return false; - } - buf->mAudioDataByteSize = buf_byte_size; - _saudio_clear(buf->mAudioData, buf->mAudioDataByteSize); - AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL); - } - - /* init or modify actual playback parameters */ - _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame; - - /* ...and start playback */ - res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL); - if (0 != res) { - _SAUDIO_ERROR(COREAUDIO_START_FAILED); - _saudio_coreaudio_backend_shutdown(); - return false; - } - return true; -} - -#else -#error "unsupported platform" -#endif - -bool _saudio_backend_init(void) { - #if defined(SOKOL_DUMMY_BACKEND) - return _saudio_dummy_backend_init(); - #elif defined(_SAUDIO_LINUX) - return _saudio_alsa_backend_init(); - #elif defined(_SAUDIO_WINDOWS) - return _saudio_wasapi_backend_init(); - #elif defined(_SAUDIO_EMSCRIPTEN) - return _saudio_webaudio_backend_init(); - #elif defined(SAUDIO_ANDROID_AAUDIO) - return _saudio_aaudio_backend_init(); - #elif defined(SAUDIO_ANDROID_SLES) - return _saudio_sles_backend_init(); - #elif defined(_SAUDIO_APPLE) - return _saudio_coreaudio_backend_init(); - #else - #error "unknown platform" - #endif -} - -void _saudio_backend_shutdown(void) { - #if defined(SOKOL_DUMMY_BACKEND) - _saudio_dummy_backend_shutdown(); - #elif defined(_SAUDIO_LINUX) - _saudio_alsa_backend_shutdown(); - #elif defined(_SAUDIO_WINDOWS) - _saudio_wasapi_backend_shutdown(); - #elif defined(_SAUDIO_EMSCRIPTEN) - _saudio_webaudio_backend_shutdown(); - #elif defined(SAUDIO_ANDROID_AAUDIO) - _saudio_aaudio_backend_shutdown(); - #elif defined(SAUDIO_ANDROID_SLES) - _saudio_sles_backend_shutdown(); - #elif defined(_SAUDIO_APPLE) - return _saudio_coreaudio_backend_shutdown(); - #else - #error "unknown platform" - #endif -} - -// ██████ ██ ██ ██████ ██ ██ ██████ -// ██ ██ ██ ██ ██ ██ ██ ██ ██ -// ██████ ██ ██ ██████ ██ ██ ██ -// ██ ██ ██ ██ ██ ██ ██ ██ -// ██ ██████ ██████ ███████ ██ ██████ -// -// >>public -SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) { - SOKOL_ASSERT(!_saudio.valid); - SOKOL_ASSERT(desc); - SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); - _saudio_clear(&_saudio, sizeof(_saudio)); - _saudio.desc = *desc; - _saudio.stream_cb = desc->stream_cb; - _saudio.stream_userdata_cb = desc->stream_userdata_cb; - _saudio.user_data = desc->user_data; - _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE); - _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES); - _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES); - _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS); - _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1); - _saudio_fifo_init_mutex(&_saudio.fifo); - if (_saudio_backend_init()) { - /* the backend might not support the requested exact buffer size, - make sure the actual buffer size is still a multiple of - the requested packet size - */ - if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) { - _SAUDIO_ERROR(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE); - _saudio_backend_shutdown(); - return; - } - SOKOL_ASSERT(_saudio.bytes_per_frame > 0); - _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets); - _saudio.valid = true; - } - else { - _saudio_fifo_destroy_mutex(&_saudio.fifo); - } -} - -SOKOL_API_IMPL void saudio_shutdown(void) { - if (_saudio.valid) { - _saudio_backend_shutdown(); - _saudio_fifo_shutdown(&_saudio.fifo); - _saudio_fifo_destroy_mutex(&_saudio.fifo); - _saudio.valid = false; - } -} - -SOKOL_API_IMPL bool saudio_isvalid(void) { - return _saudio.valid; -} - -SOKOL_API_IMPL void* saudio_userdata(void) { - return _saudio.desc.user_data; -} - -SOKOL_API_IMPL saudio_desc saudio_query_desc(void) { - return _saudio.desc; -} - -SOKOL_API_IMPL int saudio_sample_rate(void) { - return _saudio.sample_rate; -} - -SOKOL_API_IMPL int saudio_buffer_frames(void) { - return _saudio.buffer_frames; -} - -SOKOL_API_IMPL int saudio_channels(void) { - return _saudio.num_channels; -} - -SOKOL_API_IMPL bool saudio_suspended(void) { - #if defined(_SAUDIO_EMSCRIPTEN) - if (_saudio.valid) { - return 1 == saudio_js_suspended(); - } - else { - return false; - } - #else - return false; - #endif -} - -SOKOL_API_IMPL int saudio_expect(void) { - if (_saudio.valid) { - const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame; - return num_frames; - } - else { - return 0; - } -} - -SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) { - SOKOL_ASSERT(frames && (num_frames > 0)); - if (_saudio.valid) { - const int num_bytes = num_frames * _saudio.bytes_per_frame; - const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes); - return num_written / _saudio.bytes_per_frame; - } - else { - return 0; - } -} - -#undef _saudio_def -#undef _saudio_def_flt - -#if defined(_SAUDIO_WINDOWS) -#ifdef _MSC_VER -#pragma warning(pop) -#endif -#endif - -#endif /* SOKOL_AUDIO_IMPL */ diff --git a/src/libs/sokol_time.h b/src/libs/sokol_time.h deleted file mode 100644 index 2d4d456..0000000 --- a/src/libs/sokol_time.h +++ /dev/null @@ -1,319 +0,0 @@ -#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) -#define SOKOL_TIME_IMPL -#endif -#ifndef SOKOL_TIME_INCLUDED -/* - sokol_time.h -- simple cross-platform time measurement - - Project URL: https://github.com/floooh/sokol - - Do this: - #define SOKOL_IMPL or - #define SOKOL_TIME_IMPL - before you include this file in *one* C or C++ file to create the - implementation. - - Optionally provide the following defines with your own implementations: - SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) - SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) - SOKOL_API_DECL - same as SOKOL_TIME_API_DECL - SOKOL_API_IMPL - public function implementation prefix (default: -) - - If sokol_time.h is compiled as a DLL, define the following before - including the declaration or implementation: - - SOKOL_DLL - - On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) - or __declspec(dllimport) as needed. - - void stm_setup(); - Call once before any other functions to initialize sokol_time - (this calls for instance QueryPerformanceFrequency on Windows) - - uint64_t stm_now(); - Get current point in time in unspecified 'ticks'. The value that - is returned has no relation to the 'wall-clock' time and is - not in a specific time unit, it is only useful to compute - time differences. - - uint64_t stm_diff(uint64_t new, uint64_t old); - Computes the time difference between new and old. This will always - return a positive, non-zero value. - - uint64_t stm_since(uint64_t start); - Takes the current time, and returns the elapsed time since start - (this is a shortcut for "stm_diff(stm_now(), start)") - - uint64_t stm_laptime(uint64_t* last_time); - This is useful for measuring frame time and other recurring - events. It takes the current time, returns the time difference - to the value in last_time, and stores the current time in - last_time for the next call. If the value in last_time is 0, - the return value will be zero (this usually happens on the - very first call). - - uint64_t stm_round_to_common_refresh_rate(uint64_t duration) - This oddly named function takes a measured frame time and - returns the closest "nearby" common display refresh rate frame duration - in ticks. If the input duration isn't close to any common display - refresh rate, the input duration will be returned unchanged as a fallback. - 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: - - double stm_sec(uint64_t ticks); - double stm_ms(uint64_t ticks); - double stm_us(uint64_t ticks); - double stm_ns(uint64_t ticks); - Converts a tick value into seconds, milliseconds, microseconds - or nanoseconds. Note that not all platforms will have nanosecond - or even microsecond precision. - - Uses the following time measurement functions under the hood: - - Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() - MacOS/iOS: mach_absolute_time() - emscripten: emscripten_get_now() - Linux+others: clock_gettime(CLOCK_MONOTONIC) - - zlib/libpng license - - Copyright (c) 2018 Andre Weissflog - - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ -#define SOKOL_TIME_INCLUDED (1) -#include - -#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) -#define SOKOL_TIME_API_DECL SOKOL_API_DECL -#endif -#ifndef SOKOL_TIME_API_DECL -#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) -#define SOKOL_TIME_API_DECL __declspec(dllexport) -#elif defined(_WIN32) && defined(SOKOL_DLL) -#define SOKOL_TIME_API_DECL __declspec(dllimport) -#else -#define SOKOL_TIME_API_DECL extern -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -SOKOL_TIME_API_DECL void stm_setup(void); -SOKOL_TIME_API_DECL uint64_t stm_now(void); -SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); -SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); -SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); -SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); -SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); -SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); -SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); -SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif // SOKOL_TIME_INCLUDED - -/*-- IMPLEMENTATION ----------------------------------------------------------*/ -#ifdef SOKOL_TIME_IMPL -#define SOKOL_TIME_IMPL_INCLUDED (1) -#include /* memset */ - -#ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL -#endif -#ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) -#endif -#ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) || defined(__clang__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif -#endif - -#if defined(_WIN32) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -typedef struct { - uint32_t initialized; - LARGE_INTEGER freq; - LARGE_INTEGER start; -} _stm_state_t; -#elif defined(__APPLE__) && defined(__MACH__) -#include -typedef struct { - uint32_t initialized; - mach_timebase_info_data_t timebase; - uint64_t start; -} _stm_state_t; -#elif defined(__EMSCRIPTEN__) -#include -typedef struct { - uint32_t initialized; - double start; -} _stm_state_t; -#else /* anything else, this will need more care for non-Linux platforms */ -#ifdef ESP8266 -// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined -#define CLOCK_MONOTONIC 0 -#endif -#include -typedef struct { - uint32_t initialized; - uint64_t start; -} _stm_state_t; -#endif -static _stm_state_t _stm; - -/* prevent 64-bit overflow when computing relative timestamp - see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 -*/ -#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) -_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 - -SOKOL_API_IMPL void stm_setup(void) { - memset(&_stm, 0, sizeof(_stm)); - _stm.initialized = 0xABCDABCD; - #if defined(_WIN32) - QueryPerformanceFrequency(&_stm.freq); - QueryPerformanceCounter(&_stm.start); - #elif defined(__APPLE__) && defined(__MACH__) - mach_timebase_info(&_stm.timebase); - _stm.start = mach_absolute_time(); - #elif defined(__EMSCRIPTEN__) - _stm.start = emscripten_get_now(); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; - #endif -} - -SOKOL_API_IMPL uint64_t stm_now(void) { - SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); - uint64_t now; - #if defined(_WIN32) - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - 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) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); - #elif defined(__EMSCRIPTEN__) - double js_now = emscripten_get_now() - _stm.start; - now = (uint64_t) (js_now * 1000000.0); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; - #endif - return now; -} - -SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { - if (new_ticks > old_ticks) { - return new_ticks - old_ticks; - } - else { - return 1; - } -} - -SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { - return stm_diff(stm_now(), start_ticks); -} - -SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { - SOKOL_ASSERT(last_time); - uint64_t dt = 0; - uint64_t now = stm_now(); - if (0 != *last_time) { - dt = stm_diff(now, *last_time); - } - *last_time = now; - return dt; -} - -// first number is frame duration in ns, second number is tolerance in ns, -// the resulting min/max values must not overlap! -static const uint64_t _stm_refresh_rates[][2] = { - { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms - { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms - { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms - { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 - { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms - { 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms - { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms - { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms - { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms - { 0, 0 }, // keep the last element always at zero -}; - -SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { - uint64_t ns; - int i = 0; - while (0 != (ns = _stm_refresh_rates[i][0])) { - uint64_t tol = _stm_refresh_rates[i][1]; - if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { - return ns; - } - i++; - } - // fallthough: didn't fit into any buckets - return ticks; -} - -SOKOL_API_IMPL double stm_sec(uint64_t ticks) { - return (double)ticks / 1000000000.0; -} - -SOKOL_API_IMPL double stm_ms(uint64_t ticks) { - return (double)ticks / 1000000.0; -} - -SOKOL_API_IMPL double stm_us(uint64_t ticks) { - return (double)ticks / 1000.0; -} - -SOKOL_API_IMPL double stm_ns(uint64_t ticks) { - return (double)ticks; -} -#endif /* SOKOL_TIME_IMPL */ - diff --git a/src/libs/stb_image_write.h b/src/libs/stb_image_write.h deleted file mode 100644 index e4b32ed..0000000 --- a/src/libs/stb_image_write.h +++ /dev/null @@ -1,1724 +0,0 @@ -/* stb_image_write - v1.16 - public domain - http://nothings.org/stb - writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 - no warranty implied; use at your own risk - - Before #including, - - #define STB_IMAGE_WRITE_IMPLEMENTATION - - in the file that you want to have the implementation. - - Will probably not work correctly with strict-aliasing optimizations. - -ABOUT: - - This header file is a library for writing images to C stdio or a callback. - - The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation; though providing a custom - zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. - This library is designed for source code compactness and simplicity, - not optimal image file size or run-time performance. - -BUILDING: - - You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. - You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace - malloc,realloc,free. - You can #define STBIW_MEMMOVE() to replace memmove() - You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function - for PNG compression (instead of the builtin one), it must have the following signature: - unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); - The returned data will be freed with STBIW_FREE() (free() by default), - so it must be heap allocated with STBIW_MALLOC() (malloc() by default), - -UNICODE: - - If compiling for Windows and you wish to use Unicode filenames, compile - with - #define STBIW_WINDOWS_UTF8 - and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert - Windows wchar_t filenames to utf8. - -USAGE: - - There are five functions, one for each image file format: - - int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); - int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - - void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically - - There are also five equivalent functions that use an arbitrary write function. You are - expected to open/close your file-equivalent before and after calling these: - - int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - - where the callback is: - void stbi_write_func(void *context, void *data, int size); - - You can configure it with these global variables: - int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE - int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression - int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode - - - You can define STBI_WRITE_NO_STDIO to disable the file variant of these - functions, so the library will not use stdio.h at all. However, this will - also disable HDR writing, because it requires stdio for formatted output. - - Each function returns 0 on failure and non-0 on success. - - The functions create an image file defined by the parameters. The image - is a rectangle of pixels stored from left-to-right, top-to-bottom. - Each pixel contains 'comp' channels of data stored interleaved with 8-bits - per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is - monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. - The *data pointer points to the first byte of the top-left-most pixel. - For PNG, "stride_in_bytes" is the distance in bytes from the first byte of - a row of pixels to the first byte of the next row of pixels. - - PNG creates output files with the same number of components as the input. - The BMP format expands Y to RGB in the file format and does not - output alpha. - - PNG supports writing rectangles of data even when the bytes storing rows of - data are not consecutive in memory (e.g. sub-rectangles of a larger image), - by supplying the stride between the beginning of adjacent rows. The other - formats do not. (Thus you cannot write a native-format BMP through the BMP - writer, both because it is in BGR order and because it may have padding - at the end of the line.) - - PNG allows you to set the deflate compression level by setting the global - variable 'stbi_write_png_compression_level' (it defaults to 8). - - HDR expects linear float data. Since the format is always 32-bit rgb(e) - data, alpha (if provided) is discarded, and for monochrome data it is - replicated across all three channels. - - TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed - data, set the global variable 'stbi_write_tga_with_rle' to 0. - - JPEG does ignore alpha channels in input data; quality is between 1 and 100. - Higher quality looks better but results in a bigger image. - JPEG baseline (no JPEG progressive). - -CREDITS: - - - Sean Barrett - PNG/BMP/TGA - Baldur Karlsson - HDR - Jean-Sebastien Guay - TGA monochrome - Tim Kelsey - misc enhancements - Alan Hickman - TGA RLE - Emmanuel Julien - initial file IO callback implementation - Jon Olick - original jo_jpeg.cpp code - Daniel Gibson - integrate JPEG, allow external zlib - Aarni Koskela - allow choosing PNG filter - - bugfixes: - github:Chribba - Guillaume Chereau - github:jry2 - github:romigrou - Sergio Gonzalez - Jonas Karlsson - Filip Wasil - Thatcher Ulrich - github:poppolopoppo - Patrick Boettcher - github:xeekworx - Cap Petschulat - Simon Rodriguez - Ivan Tikhonov - github:ignotion - Adam Schackart - Andrew Kensler - -LICENSE - - See end of file for license information. - -*/ - -#ifndef INCLUDE_STB_IMAGE_WRITE_H -#define INCLUDE_STB_IMAGE_WRITE_H - -#include - -// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' -#ifndef STBIWDEF -#ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static -#else -#ifdef __cplusplus -#define STBIWDEF extern "C" -#else -#define STBIWDEF extern -#endif -#endif -#endif - -#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations -STBIWDEF int stbi_write_tga_with_rle; -STBIWDEF int stbi_write_png_compression_level; -STBIWDEF int stbi_write_force_png_filter; -#endif - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); - -#ifdef STBIW_WINDOWS_UTF8 -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif -#endif - -typedef void stbi_write_func(void *context, void *data, int size); - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - -STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); - -#endif//INCLUDE_STB_IMAGE_WRITE_H - -#ifdef STB_IMAGE_WRITE_IMPLEMENTATION - -#ifdef _WIN32 - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#ifndef STBI_WRITE_NO_STDIO -#include -#endif // STBI_WRITE_NO_STDIO - -#include -#include -#include -#include - -#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) -// ok -#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." -#endif - -#ifndef STBIW_MALLOC -#define STBIW_MALLOC(sz) malloc(sz) -#define STBIW_REALLOC(p,newsz) realloc(p,newsz) -#define STBIW_FREE(p) free(p) -#endif - -#ifndef STBIW_REALLOC_SIZED -#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) -#endif - - -#ifndef STBIW_MEMMOVE -#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) -#endif - - -#ifndef STBIW_ASSERT -#include -#define STBIW_ASSERT(x) assert(x) -#endif - -#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) - -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi_write_png_compression_level = 8; -static int stbi_write_tga_with_rle = 1; -static int stbi_write_force_png_filter = -1; -#else -int stbi_write_png_compression_level = 8; -int stbi_write_tga_with_rle = 1; -int stbi_write_force_png_filter = -1; -#endif - -static int stbi__flip_vertically_on_write = 0; - -STBIWDEF void stbi_flip_vertically_on_write(int flag) -{ - stbi__flip_vertically_on_write = flag; -} - -typedef struct -{ - stbi_write_func *func; - void *context; - unsigned char buffer[64]; - int buf_used; -} stbi__write_context; - -// initialize a callback-based context -static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) -{ - s->func = c; - s->context = context; -} - -#ifndef STBI_WRITE_NO_STDIO - -static void stbi__stdio_write(void *context, void *data, int size) -{ - fwrite(data,1,size,(FILE*) context); -} - -#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) -#ifdef __cplusplus -#define STBIW_EXTERN extern "C" -#else -#define STBIW_EXTERN extern -#endif -STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); - -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbiw__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) - return 0; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - -static int stbi__start_write_file(stbi__write_context *s, const char *filename) -{ - FILE *f = stbiw__fopen(filename, "wb"); - stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); - return f != NULL; -} - -static void stbi__end_write_file(stbi__write_context *s) -{ - fclose((FILE *)s->context); -} - -#endif // !STBI_WRITE_NO_STDIO - -typedef unsigned int stbiw_uint32; -typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; - -static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); - s->func(s->context,&x,1); - break; } - case '2': { int x = va_arg(v,int); - unsigned char b[2]; - b[0] = STBIW_UCHAR(x); - b[1] = STBIW_UCHAR(x>>8); - s->func(s->context,b,2); - break; } - case '4': { stbiw_uint32 x = va_arg(v,int); - unsigned char b[4]; - b[0]=STBIW_UCHAR(x); - b[1]=STBIW_UCHAR(x>>8); - b[2]=STBIW_UCHAR(x>>16); - b[3]=STBIW_UCHAR(x>>24); - s->func(s->context,b,4); - break; } - default: - STBIW_ASSERT(0); - return; - } - } -} - -static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); -} - -static void stbiw__write_flush(stbi__write_context *s) -{ - if (s->buf_used) { - s->func(s->context, &s->buffer, s->buf_used); - s->buf_used = 0; - } -} - -static void stbiw__putc(stbi__write_context *s, unsigned char c) -{ - s->func(s->context, &c, 1); -} - -static void stbiw__write1(stbi__write_context *s, unsigned char a) -{ - if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) - stbiw__write_flush(s); - s->buffer[s->buf_used++] = a; -} - -static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) -{ - int n; - if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) - stbiw__write_flush(s); - n = s->buf_used; - s->buf_used = n+3; - s->buffer[n+0] = a; - s->buffer[n+1] = b; - s->buffer[n+2] = c; -} - -static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) -{ - unsigned char bg[3] = { 255, 0, 255}, px[3]; - int k; - - if (write_alpha < 0) - stbiw__write1(s, d[comp - 1]); - - switch (comp) { - case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case - case 1: - if (expand_mono) - stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp - else - stbiw__write1(s, d[0]); // monochrome TGA - break; - case 4: - if (!write_alpha) { - // composite against pink background - for (k = 0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; - stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); - break; - } - if (write_alpha > 0) - stbiw__write1(s, d[comp - 1]); -} - -static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) -{ - stbiw_uint32 zero = 0; - int i,j, j_end; - - if (y <= 0) - return; - - if (stbi__flip_vertically_on_write) - vdir *= -1; - - if (vdir < 0) { - j_end = -1; j = y-1; - } else { - j_end = y; j = 0; - } - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - unsigned char *d = (unsigned char *) data + (j*x+i)*comp; - stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); - } - stbiw__write_flush(s); - s->func(s->context, &zero, scanline_pad); - } -} - -static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) -{ - if (y < 0 || x < 0) { - return 0; - } else { - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); - stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); - return 1; - } -} - -static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) -{ - if (comp != 4) { - // write RGB bitmap - int pad = (-x*3) & 3; - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header - } else { - // RGBA bitmaps need a v4 header - // use BI_BITFIELDS mode with 32bpp and alpha mask - // (straight BI_RGB with alpha mask doesn't work in most readers) - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, - "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", - 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header - 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header - } -} - -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_bmp_core(&s, x, y, comp, data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_bmp_core(&s, x, y, comp, data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif //!STBI_WRITE_NO_STDIO - -static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) -{ - int has_alpha = (comp == 2 || comp == 4); - int colorbytes = has_alpha ? comp-1 : comp; - int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - - if (y < 0 || x < 0) - return 0; - - if (!stbi_write_tga_with_rle) { - return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, - "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); - } else { - int i,j,k; - int jend, jdir; - - stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - - if (stbi__flip_vertically_on_write) { - j = 0; - jend = y; - jdir = 1; - } else { - j = y-1; - jend = -1; - jdir = -1; - } - for (; j != jend; j += jdir) { - unsigned char *row = (unsigned char *) data + j * x * comp; - int len; - - for (i = 0; i < x; i += len) { - unsigned char *begin = row + i * comp; - int diff = 1; - len = 1; - - if (i < x - 1) { - ++len; - diff = memcmp(begin, row + (i + 1) * comp, comp); - if (diff) { - const unsigned char *prev = begin; - for (k = i + 2; k < x && len < 128; ++k) { - if (memcmp(prev, row + k * comp, comp)) { - prev += comp; - ++len; - } else { - --len; - break; - } - } - } else { - for (k = i + 2; k < x && len < 128; ++k) { - if (!memcmp(begin, row + k * comp, comp)) { - ++len; - } else { - break; - } - } - } - } - - if (diff) { - unsigned char header = STBIW_UCHAR(len - 1); - stbiw__write1(s, header); - for (k = 0; k < len; ++k) { - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); - } - } else { - unsigned char header = STBIW_UCHAR(len - 129); - stbiw__write1(s, header); - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); - } - } - } - stbiw__write_flush(s); - } - return 1; -} - -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_tga_core(&s, x, y, comp, (void *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR writer -// by Baldur Karlsson - -#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) - -#ifndef STBI_WRITE_NO_STDIO - -static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) -{ - int exponent; - float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); - - if (maxcomp < 1e-32f) { - rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; - } else { - float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; - - rgbe[0] = (unsigned char)(linear[0] * normalize); - rgbe[1] = (unsigned char)(linear[1] * normalize); - rgbe[2] = (unsigned char)(linear[2] * normalize); - rgbe[3] = (unsigned char)(exponent + 128); - } -} - -static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) -{ - unsigned char lengthbyte = STBIW_UCHAR(length+128); - STBIW_ASSERT(length+128 <= 255); - s->func(s->context, &lengthbyte, 1); - s->func(s->context, &databyte, 1); -} - -static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) -{ - unsigned char lengthbyte = STBIW_UCHAR(length); - STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code - s->func(s->context, &lengthbyte, 1); - s->func(s->context, data, length); -} - -static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) -{ - unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; - unsigned char rgbe[4]; - float linear[3]; - int x; - - scanlineheader[2] = (width&0xff00)>>8; - scanlineheader[3] = (width&0x00ff); - - /* skip RLE for images too small or large */ - if (width < 8 || width >= 32768) { - for (x=0; x < width; x++) { - switch (ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - s->func(s->context, rgbe, 4); - } - } else { - int c,r; - /* encode into scratch buffer */ - for (x=0; x < width; x++) { - switch(ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - scratch[x + width*0] = rgbe[0]; - scratch[x + width*1] = rgbe[1]; - scratch[x + width*2] = rgbe[2]; - scratch[x + width*3] = rgbe[3]; - } - - s->func(s->context, scanlineheader, 4); - - /* RLE each component separately */ - for (c=0; c < 4; c++) { - unsigned char *comp = &scratch[width*c]; - - x = 0; - while (x < width) { - // find first run - r = x; - while (r+2 < width) { - if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) - break; - ++r; - } - if (r+2 >= width) - r = width; - // dump up to first run - while (x < r) { - int len = r-x; - if (len > 128) len = 128; - stbiw__write_dump_data(s, len, &comp[x]); - x += len; - } - // if there's a run, output it - if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd - // find next byte after run - while (r < width && comp[r] == comp[x]) - ++r; - // output run up to r - while (x < r) { - int len = r-x; - if (len > 127) len = 127; - stbiw__write_run_data(s, len, comp[x]); - x += len; - } - } - } - } - } -} - -static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) -{ - if (y <= 0 || x <= 0 || data == NULL) - return 0; - else { - // Each component is stored separately. Allocate scratch space for full output scanline. - unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); - int i, len; - char buffer[128]; - char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; - s->func(s->context, header, sizeof(header)-1); - -#ifdef __STDC_LIB_EXT1__ - len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#else - len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#endif - s->func(s->context, buffer, len); - - for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); - STBIW_FREE(scratch); - return 1; - } -} - -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_hdr_core(&s, x, y, comp, (float *) data); -} - -STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif // STBI_WRITE_NO_STDIO - - -////////////////////////////////////////////////////////////////////////////// -// -// PNG writer -// - -#ifndef STBIW_ZLIB_COMPRESS -// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() -#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) -#define stbiw__sbm(a) stbiw__sbraw(a)[0] -#define stbiw__sbn(a) stbiw__sbraw(a)[1] - -#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) -#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) -#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) - -#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) -#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) - -static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) -{ - int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); - STBIW_ASSERT(p); - if (p) { - if (!*arr) ((int *) p)[1] = 0; - *arr = (void *) ((int *) p + 2); - stbiw__sbm(*arr) = m; - } - return *arr; -} - -static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) -{ - while (*bitcount >= 8) { - stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); - *bitbuffer >>= 8; - *bitcount -= 8; - } - return data; -} - -static int stbiw__zlib_bitrev(int code, int codebits) -{ - int res=0; - while (codebits--) { - res = (res << 1) | (code & 1); - code >>= 1; - } - return res; -} - -static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) -{ - int i; - for (i=0; i < limit && i < 258; ++i) - if (a[i] != b[i]) break; - return i; -} - -static unsigned int stbiw__zhash(unsigned char *data) -{ - stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - return hash; -} - -#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) -#define stbiw__zlib_add(code,codebits) \ - (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) -#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) -// default huffman tables -#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) -#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) -#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) -#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) -#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) -#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) - -#define stbiw__ZHASH 16384 - -#endif // STBIW_ZLIB_COMPRESS - -STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) -{ -#ifdef STBIW_ZLIB_COMPRESS - // user provided a zlib compress implementation, use that - return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); -#else // use builtin - static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; - static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; - static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; - unsigned int bitbuf=0; - int i,j, bitcount=0; - unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); - if (hash_table == NULL) - return NULL; - if (quality < 5) quality = 5; - - stbiw__sbpush(out, 0x78); // DEFLATE 32K window - stbiw__sbpush(out, 0x5e); // FLEVEL = 1 - stbiw__zlib_add(1,1); // BFINAL = 1 - stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman - - for (i=0; i < stbiw__ZHASH; ++i) - hash_table[i] = NULL; - - i=0; - while (i < data_len-3) { - // hash next 3 bytes of data to be compressed - int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; - unsigned char *bestloc = 0; - unsigned char **hlist = hash_table[h]; - int n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32768) { // if entry lies within window - int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) { best=d; bestloc=hlist[j]; } - } - } - // when hash table entry is too long, delete half the entries - if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); - stbiw__sbn(hash_table[h]) = quality; - } - stbiw__sbpush(hash_table[h],data+i); - - if (bestloc) { - // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal - h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); - hlist = hash_table[h]; - n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32767) { - int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); - if (e > best) { // if next match is better, bail on current match - bestloc = NULL; - break; - } - } - } - } - - if (bestloc) { - int d = (int) (data+i - bestloc); // distance back - STBIW_ASSERT(d <= 32767 && best <= 258); - for (j=0; best > lengthc[j+1]-1; ++j); - stbiw__zlib_huff(j+257); - if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); - for (j=0; d > distc[j+1]-1; ++j); - stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); - if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); - i += best; - } else { - stbiw__zlib_huffb(data[i]); - ++i; - } - } - // write out final bytes - for (;i < data_len; ++i) - stbiw__zlib_huffb(data[i]); - stbiw__zlib_huff(256); // end of block - // pad with 0 bits to byte boundary - while (bitcount) - stbiw__zlib_add(0,1); - - for (i=0; i < stbiw__ZHASH; ++i) - (void) stbiw__sbfree(hash_table[i]); - STBIW_FREE(hash_table); - - // store uncompressed instead if compression was worse - if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { - stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 - for (j = 0; j < data_len;) { - int blocklen = data_len - j; - if (blocklen > 32767) blocklen = 32767; - stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression - stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN - stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN - stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); - memcpy(out+stbiw__sbn(out), data+j, blocklen); - stbiw__sbn(out) += blocklen; - j += blocklen; - } - } - - { - // compute adler32 on input - unsigned int s1=1, s2=0; - int blocklen = (int) (data_len % 5552); - j=0; - while (j < data_len) { - for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } - s1 %= 65521; s2 %= 65521; - j += blocklen; - blocklen = 5552; - } - stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s2)); - stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s1)); - } - *out_len = stbiw__sbn(out); - // make returned pointer freeable - STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); - return (unsigned char *) stbiw__sbraw(out); -#endif // STBIW_ZLIB_COMPRESS -} - -static unsigned int stbiw__crc32(unsigned char *buffer, int len) -{ -#ifdef STBIW_CRC32 - return STBIW_CRC32(buffer, len); -#else - static unsigned int crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - unsigned int crc = ~0u; - int i; - for (i=0; i < len; ++i) - crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; - return ~crc; -#endif -} - -#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) -#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); -#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) - -static void stbiw__wpcrc(unsigned char **data, int len) -{ - unsigned int crc = stbiw__crc32(*data - len - 4, len+4); - stbiw__wp32(*data, crc); -} - -static unsigned char stbiw__paeth(int a, int b, int c) -{ - int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); - if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); - if (pb <= pc) return STBIW_UCHAR(b); - return STBIW_UCHAR(c); -} - -// @OPTIMIZE: provide an option that always forces left-predict or paeth predict -static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) -{ - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (y != 0) ? mapping : firstmap; - int i; - int type = mymap[filter_type]; - unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); - int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; - - if (type==0) { - memcpy(line_buffer, z, width*n); - return; - } - - // first loop isn't optimized since it's just one pixel - for (i = 0; i < n; ++i) { - switch (type) { - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - } - switch (type) { - case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; - case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; - case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; - case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } -} - -STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) -{ - int force_filter = stbi_write_force_png_filter; - int ctype[5] = { -1, 0, 4, 2, 6 }; - unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; - unsigned char *out,*o, *filt, *zlib; - signed char *line_buffer; - int j,zlen; - - if (stride_bytes == 0) - stride_bytes = x * n; - - if (force_filter >= 5) { - force_filter = -1; - } - - filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } - for (j=0; j < y; ++j) { - int filter_type; - if (force_filter > -1) { - filter_type = force_filter; - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); - } else { // Estimate the best filter by running through all of them: - int best_filter = 0, best_filter_val = 0x7fffffff, est, i; - for (filter_type = 0; filter_type < 5; filter_type++) { - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); - - // Estimate the entropy of the line using this filter; the less, the better. - est = 0; - for (i = 0; i < x*n; ++i) { - est += abs((signed char) line_buffer[i]); - } - if (est < best_filter_val) { - best_filter_val = est; - best_filter = filter_type; - } - } - if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); - filter_type = best_filter; - } - } - // when we get here, filter_type contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) filter_type; - STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); - } - STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); - STBIW_FREE(filt); - if (!zlib) return 0; - - // each tag requires 12 bytes of overhead - out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); - if (!out) return 0; - *out_len = 8 + 12+13 + 12+zlen + 12; - - o=out; - STBIW_MEMMOVE(o,sig,8); o+= 8; - stbiw__wp32(o, 13); // header length - stbiw__wptag(o, "IHDR"); - stbiw__wp32(o, x); - stbiw__wp32(o, y); - *o++ = 8; - *o++ = STBIW_UCHAR(ctype[n]); - *o++ = 0; - *o++ = 0; - *o++ = 0; - stbiw__wpcrc(&o,13); - - stbiw__wp32(o, zlen); - stbiw__wptag(o, "IDAT"); - STBIW_MEMMOVE(o, zlib, zlen); - o += zlen; - STBIW_FREE(zlib); - stbiw__wpcrc(&o, zlen); - - stbiw__wp32(o,0); - stbiw__wptag(o, "IEND"); - stbiw__wpcrc(&o,0); - - STBIW_ASSERT(o == out + *out_len); - - return out; -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) -{ - FILE *f; - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - - f = stbiw__fopen(filename, "wb"); - if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); - fclose(f); - STBIW_FREE(png); - return 1; -} -#endif - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) -{ - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - func(context, png, len); - STBIW_FREE(png); - return 1; -} - - -/* *************************************************************************** - * - * JPEG writer - * - * This is based on Jon Olick's jo_jpeg.cpp: - * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html - */ - -static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, - 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; - -static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { - int bitBuf = *bitBufP, bitCnt = *bitCntP; - bitCnt += bs[1]; - bitBuf |= bs[0] << (24 - bitCnt); - while(bitCnt >= 8) { - unsigned char c = (bitBuf >> 16) & 255; - stbiw__putc(s, c); - if(c == 255) { - stbiw__putc(s, 0); - } - bitBuf <<= 8; - bitCnt -= 8; - } - *bitBufP = bitBuf; - *bitCntP = bitCnt; -} - -static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { - float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; - float z1, z2, z3, z4, z5, z11, z13; - - float tmp0 = d0 + d7; - float tmp7 = d0 - d7; - float tmp1 = d1 + d6; - float tmp6 = d1 - d6; - float tmp2 = d2 + d5; - float tmp5 = d2 - d5; - float tmp3 = d3 + d4; - float tmp4 = d3 - d4; - - // Even part - float tmp10 = tmp0 + tmp3; // phase 2 - float tmp13 = tmp0 - tmp3; - float tmp11 = tmp1 + tmp2; - float tmp12 = tmp1 - tmp2; - - d0 = tmp10 + tmp11; // phase 3 - d4 = tmp10 - tmp11; - - z1 = (tmp12 + tmp13) * 0.707106781f; // c4 - d2 = tmp13 + z1; // phase 5 - d6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; // phase 2 - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - // The rotator is modified from fig 4-8 to avoid extra negations. - z5 = (tmp10 - tmp12) * 0.382683433f; // c6 - z2 = tmp10 * 0.541196100f + z5; // c2-c6 - z4 = tmp12 * 1.306562965f + z5; // c2+c6 - z3 = tmp11 * 0.707106781f; // c4 - - z11 = tmp7 + z3; // phase 5 - z13 = tmp7 - z3; - - *d5p = z13 + z2; // phase 6 - *d3p = z13 - z2; - *d1p = z11 + z4; - *d7p = z11 - z4; - - *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; -} - -static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { - int tmp1 = val < 0 ? -val : val; - val = val < 0 ? val-1 : val; - bits[1] = 1; - while(tmp1 >>= 1) { - ++bits[1]; - } - bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { - } - // end0pos = first element in reverse order !=0 - if(end0pos == 0) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - return DU[0]; - } - for(i = 1; i <= end0pos; ++i) { - int startpos = i; - int nrzeroes; - unsigned short bits[2]; - for (; DU[i]==0 && i<=end0pos; ++i) { - } - nrzeroes = i-startpos; - if ( nrzeroes >= 16 ) { - int lng = nrzeroes>>4; - int nrmarker; - for (nrmarker=1; nrmarker <= lng; ++nrmarker) - stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); - nrzeroes &= 15; - } - stbiw__jpg_calcBits(DU[i], bits); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); - } - if(end0pos != 63) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - } - return DU[0]; -} - -static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { - // Constants that don't pollute global namespace - static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; - static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; - static const unsigned char std_ac_luminance_values[] = { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; - static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; - static const unsigned char std_ac_chrominance_values[] = { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - // Huffman tables - static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; - static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; - static const unsigned short YAC_HT[256][2] = { - {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const unsigned short UVAC_HT[256][2] = { - {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, - 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; - static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, - 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; - static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, - 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; - - int row, col, i, k, subsample; - float fdtbl_Y[64], fdtbl_UV[64]; - unsigned char YTable[64], UVTable[64]; - - if(!data || !width || !height || comp > 4 || comp < 1) { - return 0; - } - - quality = quality ? quality : 90; - subsample = quality <= 90 ? 1 : 0; - quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; - quality = quality < 50 ? 5000 / quality : 200 - quality * 2; - - for(i = 0; i < 64; ++i) { - int uvti, yti = (YQT[i]*quality+50)/100; - YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); - uvti = (UVQT[i]*quality+50)/100; - UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); - } - - for(row = 0, k = 0; row < 8; ++row) { - for(col = 0; col < 8; ++col, ++k) { - fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - } - } - - // Write Headers - { - static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; - static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; - const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), - 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; - s->func(s->context, (void*)head0, sizeof(head0)); - s->func(s->context, (void*)YTable, sizeof(YTable)); - stbiw__putc(s, 1); - s->func(s->context, UVTable, sizeof(UVTable)); - s->func(s->context, (void*)head1, sizeof(head1)); - s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); - s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); - stbiw__putc(s, 0x10); // HTYACinfo - s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); - s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); - stbiw__putc(s, 1); // HTUDCinfo - s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); - stbiw__putc(s, 0x11); // HTUACinfo - s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); - s->func(s->context, (void*)head2, sizeof(head2)); - } - - // Encode 8x8 macroblocks - { - static const unsigned short fillBits[] = {0x7F, 7}; - int DCY=0, DCU=0, DCV=0; - int bitBuf=0, bitCnt=0; - // comp == 2 is grey+alpha (alpha is ignored) - int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; - const unsigned char *dataR = (const unsigned char *)data; - const unsigned char *dataG = dataR + ofsG; - const unsigned char *dataB = dataR + ofsB; - int x, y, pos; - if(subsample) { - for(y = 0; y < height; y += 16) { - for(x = 0; x < width; x += 16) { - float Y[256], U[256], V[256]; - for(row = y, pos = 0; row < y+16; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+16; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - - // subsample U,V - { - float subU[64], subV[64]; - int yy, xx; - for(yy = 0, pos = 0; yy < 8; ++yy) { - for(xx = 0; xx < 8; ++xx, ++pos) { - int j = yy*32+xx*2; - subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; - subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; - } - } - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - } else { - for(y = 0; y < height; y += 8) { - for(x = 0; x < width; x += 8) { - float Y[64], U[64], V[64]; - for(row = y, pos = 0; row < y+8; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+8; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - - // Do the bit alignment of the EOI marker - stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); - } - - // EOI - stbiw__putc(s, 0xFF); - stbiw__putc(s, 0xD9); - - return 1; -} - -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); -} - - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -#endif // STB_IMAGE_WRITE_IMPLEMENTATION - -/* Revision history - 1.16 (2021-07-11) - make Deflate code emit uncompressed blocks when it would otherwise expand - support writing BMPs with alpha channel - 1.15 (2020-07-13) unknown - 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels - 1.13 - 1.12 - 1.11 (2019-08-11) - - 1.10 (2019-02-07) - support utf8 filenames in Windows; fix warnings and platform ifdefs - 1.09 (2018-02-11) - fix typo in zlib quality API, improve STB_I_W_STATIC in C++ - 1.08 (2018-01-29) - add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter - 1.07 (2017-07-24) - doc fix - 1.06 (2017-07-23) - writing JPEG (using Jon Olick's code) - 1.05 ??? - 1.04 (2017-03-03) - monochrome BMP expansion - 1.03 ??? - 1.02 (2016-04-02) - avoid allocating large structures on the stack - 1.01 (2016-01-16) - STBIW_REALLOC_SIZED: support allocators with no realloc support - avoid race-condition in crc initialization - minor compile issues - 1.00 (2015-09-14) - installable file IO function - 0.99 (2015-09-13) - warning fixes; TGA rle support - 0.98 (2015-04-08) - added STBIW_MALLOC, STBIW_ASSERT etc - 0.97 (2015-01-18) - fixed HDR asserts, rewrote HDR rle logic - 0.96 (2015-01-17) - add HDR output - fix monochrome BMP - 0.95 (2014-08-17) - add monochrome TGA output - 0.94 (2014-05-31) - rename private functions to avoid conflicts with stb_image.h - 0.93 (2014-05-27) - warning fixes - 0.92 (2010-08-01) - casts to unsigned char to fix warnings - 0.91 (2010-07-17) - first public release - 0.90 first internal release -*/ - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/src/mem.c b/src/mem.c index d209982..4b0911e 100644 --- a/src/mem.c +++ b/src/mem.c @@ -63,7 +63,7 @@ void mem_temp_free(void *p) { bool found = false; uint32_t remaining_max = 0; - for (int i = 0; i < temp_objects_len; i++) { + for (unsigned int i = 0; i < temp_objects_len; i++) { if (temp_objects[i] == offset) { temp_objects[i--] = temp_objects[--temp_objects_len]; found = true; diff --git a/src/platform.h b/src/platform.h index 3219269..4fe5957 100644 --- a/src/platform.h +++ b/src/platform.h @@ -15,6 +15,10 @@ uint8_t *platform_load_asset(const char *name, uint32_t *bytes_read); uint8_t *platform_load_userdata(const char *name, uint32_t *bytes_read); uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len); +void platform_force_feedback(double strength, uint32_t duration); + +void platform_set_force_feedback(bool enabled); + #if defined(RENDERER_SOFTWARE) rgba_t *platform_get_screenbuffer(int32_t *pitch); #endif diff --git a/src/platform_sdl.c b/src/platform_sdl.c index c881618..3ce2f7f 100755 --- a/src/platform_sdl.c +++ b/src/platform_sdl.c @@ -11,6 +11,8 @@ static bool wants_to_exit = false; static SDL_Window *window; static SDL_AudioDeviceID audio_device; static SDL_GameController *gamepad; +static bool force_feedback_supported = false; +static bool force_feedback_enabled = false; static void (*audio_callback)(float *buffer, uint32_t len) = NULL; static char *path_assets = ""; static char *path_userdata = ""; @@ -62,6 +64,21 @@ SDL_GameController *platform_find_gamepad(void) { return NULL; } +void platform_force_feedback(double strength, uint32_t duration) { + if(!gamepad) { + return; + } + else if(!(force_feedback_enabled && force_feedback_supported)) { + return; + } + + SDL_GameControllerRumble( gamepad, (uint16_t) (0xFFFF * strength), (uint16_t) (0xFFFF * strength), duration ); +} + +void platform_set_force_feedback(bool enabled) { + force_feedback_enabled = enabled; +} + void platform_pump_events(void) { SDL_Event ev; @@ -95,6 +112,9 @@ void platform_pump_events(void) { // Gamepads connect/disconnect else if (ev.type == SDL_CONTROLLERDEVICEADDED) { gamepad = SDL_GameControllerOpen(ev.cdevice.which); + if(SDL_GameControllerHasRumble(gamepad)) { + force_feedback_supported = true; + } } else if (ev.type == SDL_CONTROLLERDEVICEREMOVED) { if (gamepad && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad))) { @@ -213,7 +233,7 @@ void platform_set_fullscreen(bool fullscreen) { } } -void platform_audio_callback(void* userdata, uint8_t* stream, int len) { +void platform_audio_callback(void*, uint8_t* stream, int len) { if (audio_callback) { audio_callback((float *)stream, len/sizeof(float)); } @@ -344,7 +364,7 @@ uint32_t platform_store_userdata(const char *name, void *bytes, int32_t len) { #error "Unsupported renderer for platform SDL" #endif -int main(int argc, char *argv[]) { +int main(int, char**) { 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 diff --git a/src/platform_sokol.c b/src/platform_sokol.c index 710c875..7c17a4b 100644 --- a/src/platform_sokol.c +++ b/src/platform_sokol.c @@ -264,6 +264,17 @@ void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) { audio_callback = cb; } +void platform_force_feedback(double strength, uint32_t duration) { + // Sokol does not support haptic feedback + (void)strength; // avoid unused variable warning + (void)duration; // avoid unused variable warning +} + +void platform_set_force_feedback(bool enable) { + // Sokol does not support haptic feedback + (void)enable; // avoid unused variable warning +} + FILE *platform_open_asset(const char *name, const char *mode) { char *path = strcat(strcpy(temp_path, path_assets), name); return fopen(path, mode); diff --git a/src/render.h b/src/render.h index 74d96cc..28b75e4 100644 --- a/src/render.h +++ b/src/render.h @@ -56,7 +56,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); +void render_texture_replace_pixels(uint16_t texture_index, rgba_t *pixels); 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 f09b49b..e75fe2e 100644 --- a/src/render_gl.c +++ b/src/render_gl.c @@ -907,21 +907,21 @@ uint16_t render_texture_create(uint32_t tw, uint32_t th, rgba_t *pixels) { } // Left border - for (int32_t y = 0; y < bh; y++) { - for (int32_t x = 0; x < ATLAS_BORDER; x++) { + for (uint32_t y = 0; y < bh; y++) { + for (uint32_t x = 0; x < ATLAS_BORDER; x++) { pb[y * bw + x] = pixels[clamp(y-ATLAS_BORDER, 0, th-1) * tw]; } } // Right border - for (int32_t y = 0; y < bh; y++) { - for (int32_t x = 0; x < ATLAS_BORDER; x++) { + for (uint32_t y = 0; y < bh; y++) { + for (uint32_t x = 0; x < ATLAS_BORDER; x++) { pb[y * bw + x + bw - ATLAS_BORDER] = pixels[tw - 1 + clamp(y-ATLAS_BORDER, 0, th-1) * tw]; } } // Texture - for (int32_t y = 0; y < th; y++) { + for (uint32_t y = 0; y < th; y++) { memcpy(pb + bw * (y + ATLAS_BORDER) + ATLAS_BORDER, pixels + tw * y, tw * sizeof(rgba_t)); } } @@ -938,7 +938,6 @@ uint16_t render_texture_create(uint32_t tw, uint32_t th, rgba_t *pixels) { textures_len++; textures[texture_index] = (render_texture_t){ {x + ATLAS_BORDER, y + ATLAS_BORDER}, {tw, th} }; - printf("inserted atlas texture (%3dx%3d) at (%3d,%3d)\n", tw, th, grid_x, grid_y); return texture_index; } @@ -947,7 +946,7 @@ vec2i_t render_texture_size(uint16_t texture_index) { return textures[texture_index].size; } -void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels) { +void render_texture_replace_pixels(uint16_t texture_index, rgba_t *pixels) { error_if(texture_index >= textures_len, "Invalid texture %d", texture_index); render_texture_t *t = &textures[texture_index]; @@ -977,7 +976,7 @@ void render_textures_reset(uint16_t len) { } // Replay all texture grid insertions up to the reset len - for (int i = 0; i < textures_len; i++) { + for (unsigned int i = 0; i < textures_len; i++) { uint32_t grid_x = (textures[i].offset.x - ATLAS_BORDER) / ATLAS_GRID; uint32_t grid_y = (textures[i].offset.y - ATLAS_BORDER) / ATLAS_GRID; uint32_t grid_width = (textures[i].size.x + ATLAS_BORDER * 2 + ATLAS_GRID - 1) / ATLAS_GRID; diff --git a/src/system.c b/src/system.c index 7440ba5..f6c83ed 100644 --- a/src/system.c +++ b/src/system.c @@ -1,4 +1,5 @@ #include "system.h" + #include "input.h" #include "render.h" #include "platform.h" diff --git a/src/types.c b/src/types.c index f1f5f55..8adcb47 100644 --- a/src/types.c +++ b/src/types.c @@ -20,7 +20,7 @@ float vec3_angle(vec3_t a, vec3_t b) { (a.x * a.x + a.y * a.y + a.z * a.z) * (b.x * b.x + b.y * b.y + b.z * b.z) ); - float cosine = (magnitude == 0) + float cosine = (magnitude == 0.0F) ? 1 : vec3_dot(a, b) / magnitude; return acos(clamp(cosine, -1, 1)); diff --git a/src/utils.c b/src/utils.c index 596e757..01b030a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -39,11 +39,11 @@ uint8_t *file_load(const char *path, uint32_t *bytes_read) { *bytes_read = fread(bytes, 1, size, f); fclose(f); - error_if(*bytes_read != size, "Could not read file: %s", path); + error_if(*bytes_read != (unsigned int)size, "Could not read file: %s", path); return bytes; } -uint32_t file_store(const char *path, void *bytes, int32_t len) { +uint32_t file_store(const char *path, void *bytes, uint32_t len) { FILE *f = fopen(path, "wb"); error_if(!f, "Could not open file for writing: %s", path); diff --git a/src/utils.h b/src/utils.h index dcebb31..f5d6bbb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -79,7 +79,7 @@ int32_t rand_int(int32_t min, int32_t max); bool file_exists(const char *path); uint8_t *file_load(const char *path, uint32_t *bytes_read); -uint32_t file_store(const char *path, void *bytes, int32_t len); +uint32_t file_store(const char *path, void *bytes, uint32_t len); #define sort(LIST, LEN, COMPARE_FUNC) \ diff --git a/src/wipeout/camera.c b/src/wipeout/camera.c index 04ce0b7..b79d37c 100755 --- a/src/wipeout/camera.c +++ b/src/wipeout/camera.c @@ -40,7 +40,7 @@ void camera_update(camera_t *camera, ship_t *ship, droid_t *droid) { camera_update_shake(camera); } -void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t*) { vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 1024)); pos.y -= 200; camera->section = track_nearest_section(pos, vec3(1,1,1), camera->section, NULL); @@ -60,13 +60,13 @@ void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t *droid) camera->angle = vec3(ship->angle.x, ship->angle.y, 0); } -void camera_update_race_internal(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_race_internal(camera_t *camera, ship_t *ship, droid_t*) { camera->section = ship->section; camera->position = ship_cockpit(ship); camera->angle = vec3(ship->angle.x, ship->angle.y, ship->angle.z * save.internal_roll); } -void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t*) { // Set to final position vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 0.25 * 4096)); @@ -95,7 +95,7 @@ void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t *droid) { } } -void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t*) { camera->update_timer -= system_tick(); if (camera->update_timer <= 0) { camera->update_func = camera_update_attract_random; @@ -119,7 +119,7 @@ void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t *droid camera->angle.y = -atan2(target.x, target.z); } -void camera_update_rescue(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_rescue(camera_t *camera, ship_t*, droid_t *droid) { camera->position = vec3_add(camera->section->center, vec3(300, -1500, 300)); vec3_t target = vec3_sub(droid->position, camera->position); @@ -129,7 +129,7 @@ void camera_update_rescue(camera_t *camera, ship_t *ship, droid_t *droid) { } -void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t*) { camera->update_timer -= system_tick(); if (camera->update_timer <= 0) { camera->update_func = camera_update_attract_random; @@ -140,7 +140,7 @@ void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t *dro camera->angle = vec3(ship->angle.x, ship->angle.y, 0); // No roll } -void camera_update_static_follow(camera_t *camera, ship_t *ship, droid_t *droid) { +void camera_update_static_follow(camera_t *camera, ship_t *ship, droid_t*) { camera->update_timer -= system_tick(); if (camera->update_timer <= 0) { camera->update_func = camera_update_attract_random; diff --git a/src/wipeout/droid.c b/src/wipeout/droid.c index 340c378..a5baf86 100755 --- a/src/wipeout/droid.c +++ b/src/wipeout/droid.c @@ -127,7 +127,7 @@ void droid_update(droid_t *droid, ship_t *ship) { } } -void droid_update_intro(droid_t *droid, ship_t *ship) { +void droid_update_intro(droid_t *droid, ship_t*) { droid->update_timer -= system_tick(); if (droid->update_timer < DROID_UPDATE_TIME_INTRO_3) { diff --git a/src/wipeout/game.c b/src/wipeout/game.c index f2c16bc..223b7d4 100755 --- a/src/wipeout/game.c +++ b/src/wipeout/game.c @@ -33,6 +33,7 @@ const game_def_t def = { .race_types = { [RACE_TYPE_CHAMPIONSHIP] = {.name = "CHAMPIONSHIP RACE"}, [RACE_TYPE_SINGLE] = {.name = "SINGLE RACE"}, + [RACE_TYPE_NETWORK] = {.name = "NETWORK"}, [RACE_TYPE_TIME_TRIAL] = {.name = "TIME TRIAL"}, }, @@ -403,6 +404,8 @@ save_t save = { .fullscreen = false, .screen_res = 0, .post_effect = 0, + .enable_force_feedback = false, + .network_interface = 0, .has_rapier_class = true, // for testing; should be false in prod .has_bonus_circuts = true, // for testing; should be false in prod @@ -538,7 +541,6 @@ void game_init(void) { sfx_music_mode(SFX_MUSIC_PAUSED); sfx_music_play(rand_int(0, len(def.music))); - // System binds; always fixed // Keyboard input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_UP, A_MENU_UP); @@ -583,7 +585,7 @@ void game_init(void) { // User defined, loaded from the save struct - for (int action = 0; action < len(save.buttons); action++) { + for (unsigned int action = 0; action < len(save.buttons); action++) { if (save.buttons[action][0] != INPUT_INVALID) { input_bind(INPUT_LAYER_USER, save.buttons[action][0], action); } @@ -602,7 +604,7 @@ void game_set_scene(game_scene_t scene) { } void game_reset_championship(void) { - for (int i = 0; i < len(g.championship_ranks); i++) { + for (unsigned int i = 0; i < len(g.championship_ranks); i++) { g.championship_ranks[i].points = 0; g.championship_ranks[i].pilot = i; } diff --git a/src/wipeout/game.h b/src/wipeout/game.h index 98e789c..19b7cea 100755 --- a/src/wipeout/game.h +++ b/src/wipeout/game.h @@ -61,6 +61,7 @@ enum race_class { enum race_type { RACE_TYPE_CHAMPIONSHIP, RACE_TYPE_SINGLE, + RACE_TYPE_NETWORK, RACE_TYPE_TIME_TRIAL, NUM_RACE_TYPES, }; @@ -198,7 +199,7 @@ typedef struct { int race_type; int highscore_tab; int team; - int pilot; + unsigned short pilot; int circut; bool is_attract_mode; bool show_credits; @@ -247,6 +248,8 @@ typedef struct { int screen_res; int post_effect; float screen_shake; + bool enable_force_feedback; + int network_interface; uint32_t has_rapier_class; uint32_t has_bonus_circuts; diff --git a/src/wipeout/image.c b/src/wipeout/image.c index 99712d6..b248a85 100755 --- a/src/wipeout/image.c +++ b/src/wipeout/image.c @@ -47,7 +47,7 @@ image_t *image_alloc(uint32_t width, uint32_t height) { image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) { uint32_t p = 0; - uint32_t magic = get_i32_le(bytes, &p); + get_i32_le(bytes, &p); // magic uint32_t type = get_i32_le(bytes, &p); rgba_t palette[256]; @@ -55,17 +55,17 @@ image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) { type == TIM_TYPE_PALETTED_4_BPP || type == TIM_TYPE_PALETTED_8_BPP ) { - uint32_t header_length = get_i32_le(bytes, &p); - uint16_t palette_x = get_i16_le(bytes, &p); - uint16_t palette_y = get_i16_le(bytes, &p); + get_i32_le(bytes, &p); // header length + get_i16_le(bytes, &p); // palette x + get_i16_le(bytes, &p); // palette y uint16_t palette_colors = get_i16_le(bytes, &p); - uint16_t palettes = get_i16_le(bytes, &p); + get_i16_le(bytes, &p); // palettes for (int i = 0; i < palette_colors; i++) { palette[i] = tim_16bit_to_rgba(get_u16_le(bytes, &p), transparent); } } - uint32_t data_size = get_i32_le(bytes, &p); + get_i32_le(bytes, &p); // data size int32_t pixels_per_16bit = 1; if (type == TIM_TYPE_PALETTED_8_BPP) { @@ -75,8 +75,8 @@ image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) { pixels_per_16bit = 4; } - uint16_t skip_x = get_i16_le(bytes, &p); - uint16_t skip_y = get_i16_le(bytes, &p); + get_i16_le(bytes, &p); // ship x + get_i16_le(bytes, &p); // ship y uint16_t entries_per_row = get_i16_le(bytes, &p); uint16_t rows = get_i16_le(bytes, &p); @@ -287,8 +287,7 @@ texture_list_t image_get_compressed_textures(char *name) { cmp_t *cmp = image_load_compressed(name); texture_list_t list = {.start = render_textures_len(), .len = cmp->len}; - for (int i = 0; i < cmp->len; i++) { - int32_t width, height; + for (unsigned int i = 0; i < cmp->len; i++) { image_t *image = image_load_from_bytes(cmp->entries[i], false); // char png_name[1024] = {0}; diff --git a/src/wipeout/ingame_menus.c b/src/wipeout/ingame_menus.c index d9723be..4bd6916 100644 --- a/src/wipeout/ingame_menus.c +++ b/src/wipeout/ingame_menus.c @@ -27,7 +27,7 @@ void ingame_menus_load(void) { // ----------------------------------------------------------------------------- // Pause Menu -static void button_continue(menu_t *menu, int data) { +static void button_continue(menu_t*, int) { race_unpause(); } @@ -40,7 +40,7 @@ static void button_restart_confirm(menu_t *menu, int data) { } } -static void button_restart_or_quit(menu_t *menu, int data) { +static void button_restart_or_quit(menu_t*, int data) { if (data) { race_restart(); } @@ -49,7 +49,7 @@ static void button_restart_or_quit(menu_t *menu, int data) { } } -static void button_restart(menu_t *menu, int data) { +static void button_restart(menu_t *menu, int) { menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO RESTART", "YES", "NO", button_restart_confirm); } @@ -62,25 +62,25 @@ static void button_quit_confirm(menu_t *menu, int data) { } } -static void button_quit(menu_t *menu, int data) { +static void button_quit(menu_t *menu, int) { menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm); } -static void button_music_track(menu_t *menu, int data) { +static void button_music_track(menu_t*, int data) { sfx_music_play(data); sfx_music_mode(SFX_MUSIC_LOOP); } -static void button_music_random(menu_t *menu, int data) { +static void button_music_random(menu_t*, int) { sfx_music_play(rand_int(0, len(def.music))); sfx_music_mode(SFX_MUSIC_RANDOM); } -static void button_music(menu_t *menu, int data) { - menu_page_t *page = menu_push(menu, "MUSIC", NULL); +static void button_music(menu_t *menu, int) { + menu_page_t *page = menu_push(menu, "MUSIC", NULL, NULL, NULL); - for (int i = 0; i < len(def.music); i++) { + for (unsigned int i = 0; i < len(def.music); i++) { menu_page_add_button(page, i, def.music[i].name, button_music_track); } menu_page_add_button(page, 0, "RANDOM", button_music_random); @@ -90,7 +90,7 @@ menu_t *pause_menu_init(void) { sfx_play(SFX_MENU_SELECT); menu_reset(ingame_menu); - menu_page_t *page = menu_push(ingame_menu, "PAUSED", NULL); + menu_page_t *page = menu_push(ingame_menu, "PAUSED", NULL, NULL, NULL); menu_page_add_button(page, 0, "CONTINUE", button_continue); menu_page_add_button(page, 0, "RESTART", button_restart); menu_page_add_button(page, 0, "QUIT", button_quit); @@ -107,7 +107,7 @@ menu_t *game_over_menu_init(void) { sfx_play(SFX_MENU_SELECT); menu_reset(ingame_menu); - menu_page_t *page = menu_push(ingame_menu, "GAME OVER", NULL); + menu_page_t *page = menu_push(ingame_menu, "GAME OVER", NULL, NULL, NULL); menu_page_add_button(page, 1, "", button_quit_confirm); return ingame_menu; } @@ -116,7 +116,7 @@ menu_t *game_over_menu_init(void) { // ----------------------------------------------------------------------------- // Race Stats -static void button_qualify_confirm(menu_t *menu, int data) { +static void button_qualify_confirm(menu_t*, int data) { if (data) { race_restart(); } @@ -125,7 +125,7 @@ static void button_qualify_confirm(menu_t *menu, int data) { } } -static void button_race_stats_continue(menu_t *menu, int data) { +static void button_race_stats_continue(menu_t *menu, int) { if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { if (g.race_position <= QUALIFYING_RANK) { page_race_points_init(menu); @@ -145,7 +145,7 @@ static void button_race_stats_continue(menu_t *menu, int data) { } } -static void page_race_stats_draw(menu_t *menu, int data) { +static void page_race_stats_draw(menu_t *menu, int) { menu_page_t *page = &menu->pages[menu->index]; vec2i_t pos = page->title_pos; pos.x -= 140; @@ -201,7 +201,7 @@ menu_t *race_stats_menu_init(void) { else { title = "FAILED TO QUALIFY"; } - menu_page_t *page = menu_push(ingame_menu, title, page_race_stats_draw); + menu_page_t *page = menu_push(ingame_menu, title, page_race_stats_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; page->title_pos = vec2i(0, -100); @@ -213,7 +213,7 @@ menu_t *race_stats_menu_init(void) { // ----------------------------------------------------------------------------- // Race Table -static void button_race_points_continue(menu_t *menu, int data) { +static void button_race_points_continue(menu_t *menu, int) { if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { page_championship_points_init(menu); } @@ -225,7 +225,7 @@ static void button_race_points_continue(menu_t *menu, int data) { } } -static void page_race_points_draw(menu_t *menu, int data) { +static void page_race_points_draw(menu_t *menu, int) { menu_page_t *page = &menu->pages[menu->index]; vec2i_t pos = page->title_pos; pos.x -= 140; @@ -237,7 +237,7 @@ static void page_race_points_draw(menu_t *menu, int data) { pos.y += 24; - for (int i = 0; i < len(g.race_ranks); i++) { + for (unsigned int i = 0; i < len(g.race_ranks); i++) { rgba_t color = g.race_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT; ui_draw_text(def.pilots[g.race_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color); int w = ui_number_width(g.race_ranks[i].points, UI_SIZE_8); @@ -247,7 +247,7 @@ static void page_race_points_draw(menu_t *menu, int data) { } static void page_race_points_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "RACE POINTS", page_race_points_draw); + menu_page_t *page = menu_push(menu, "RACE POINTS", page_race_points_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; page->title_pos = vec2i(0, -100); @@ -258,7 +258,7 @@ static void page_race_points_init(menu_t *menu) { // ----------------------------------------------------------------------------- // Championship Table -static void button_championship_points_continue(menu_t *menu, int data) { +static void button_championship_points_continue(menu_t *menu, int) { if (g.is_new_race_record) { page_hall_of_fame_init(menu); } @@ -270,7 +270,7 @@ static void button_championship_points_continue(menu_t *menu, int data) { } } -static void page_championship_points_draw(menu_t *menu, int data) { +static void page_championship_points_draw(menu_t *menu, int) { menu_page_t *page = &menu->pages[menu->index]; vec2i_t pos = page->title_pos; pos.x -= 140; @@ -282,7 +282,7 @@ static void page_championship_points_draw(menu_t *menu, int data) { pos.y += 24; - for (int i = 0; i < len(g.championship_ranks); i++) { + for (unsigned int i = 0; i < len(g.championship_ranks); i++) { rgba_t color = g.championship_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT; ui_draw_text(def.pilots[g.championship_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color); int w = ui_number_width(g.championship_ranks[i].points, UI_SIZE_8); @@ -292,7 +292,7 @@ static void page_championship_points_draw(menu_t *menu, int data) { } static void page_championship_points_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "CHAMPIONSHIP TABLE", page_championship_points_draw); + menu_page_t *page = menu_push(menu, "CHAMPIONSHIP TABLE", page_championship_points_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; page->title_pos = vec2i(0, -100); @@ -311,7 +311,7 @@ static const char *hs_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static int hs_char_index = 0; static bool hs_entry_complete = false; -static void hall_of_fame_draw_name_entry(menu_t *menu, ui_pos_t anchor, vec2i_t pos) { +static void hall_of_fame_draw_name_entry(menu_t*, ui_pos_t anchor, vec2i_t pos) { int entry_len = strlen(hs_new_entry.name); int entry_width = ui_text_width(hs_new_entry.name, UI_SIZE_16); @@ -373,7 +373,7 @@ static void hall_of_fame_draw_name_entry(menu_t *menu, ui_pos_t anchor, vec2i_t ui_draw_text(hs_new_entry.name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_ACCENT); } -static void page_hall_of_fame_draw(menu_t *menu, int data) { +static void page_hall_of_fame_draw(menu_t *menu, int) { // FIXME: doing this all in the draw() function leads to all kinds of // complications @@ -385,7 +385,6 @@ static void page_hall_of_fame_draw(menu_t *menu, int data) { save.is_dirty = true; // Insert new highscore entry into the save struct - highscores_entry_t temp_entry = hs->entries[0]; for (int i = 0; i < NUM_HIGHSCORES; i++) { if (hs_new_entry.time < hs->entries[i].time) { for (int j = NUM_HIGHSCORES - 2; j >= i; j--) { @@ -431,7 +430,7 @@ static void page_hall_of_fame_draw(menu_t *menu, int data) { static void page_hall_of_fame_init(menu_t *menu) { menu_reset(menu); // Can't go back! - menu_page_t *page = menu_push(menu, "HALL OF FAME", page_hall_of_fame_draw); + menu_page_t *page = menu_push(menu, "HALL OF FAME", page_hall_of_fame_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; page->title_pos = vec2i(0, -100); @@ -451,7 +450,7 @@ static char * const *text_scroll_lines; static int text_scroll_lines_len; static double text_scroll_start_time; -static void text_scroll_menu_draw(menu_t *menu, int data) { +static void text_scroll_menu_draw(menu_t*, int) { double time = system_time() - text_scroll_start_time; int scale = ui_get_scale(); int speed = 32; @@ -480,7 +479,7 @@ menu_t *text_scroll_menu_init(char * const *lines, int len) { menu_reset(ingame_menu); - menu_page_t *page = menu_push(ingame_menu, "", text_scroll_menu_draw); + menu_page_t *page = menu_push(ingame_menu, "", text_scroll_menu_draw, NULL, NULL); menu_page_add_button(page, 1, "", button_quit_confirm); return ingame_menu; } diff --git a/src/wipeout/intro.c b/src/wipeout/intro.c index 0de5c98..dc5f435 100644 --- a/src/wipeout/intro.c +++ b/src/wipeout/intro.c @@ -10,8 +10,8 @@ #include "image.h" #include "game.h" -void free_dummmy(void *p) {} -void *realloc_dummmy(void *p, size_t sz) { +void free_dummmy(void*) {} +void *realloc_dummmy(void*, size_t) { die("pl_mpeg needed to realloc. Not implemented. Maybe increase PLM_BUFFER_DEFAULT_SIZE"); return NULL; } @@ -86,7 +86,7 @@ void intro_update(void) { } } -static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user) { +static void audio_cb(plm_t*, plm_samples_t *samples, void*) { int len = samples->count * 2; for (int i = 0; i < len; i++) { audio_buffer[audio_buffer_write_pos % INTRO_AUDIO_BUFFER_LEN] = samples->interleaved[i]; @@ -95,7 +95,7 @@ static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user) { } static void audio_mix(float *samples, uint32_t len) { - int i; + unsigned int i; for (i = 0; i < len && audio_buffer_read_pos < audio_buffer_write_pos; i++) { samples[i] = audio_buffer[audio_buffer_read_pos % INTRO_AUDIO_BUFFER_LEN]; audio_buffer_read_pos++; @@ -105,7 +105,7 @@ static void audio_mix(float *samples, uint32_t len) { } } -static void video_cb(plm_t *plm, plm_frame_t *frame, void *user) { +static void video_cb(plm_t *plm, plm_frame_t *frame, void*) { plm_frame_to_rgba(frame, (uint8_t *)frame_buffer, plm_get_width(plm) * sizeof(rgba_t)); render_texture_replace_pixels(texture, frame_buffer); } diff --git a/src/wipeout/main_menu.c b/src/wipeout/main_menu.c index 7dd0b3f..e47a42f 100755 --- a/src/wipeout/main_menu.c +++ b/src/wipeout/main_menu.c @@ -8,6 +8,7 @@ #include "main_menu.h" #include "game.h" #include "image.h" +#include "server_com.h" #include "ui.h" static void page_main_init(menu_t *menu); @@ -17,6 +18,7 @@ static void page_race_type_init(menu_t *menu); static void page_team_init(menu_t *menu); static void page_pilot_init(menu_t *menu); static void page_circut_init(menu_t *menu); +static void page_network_init(menu_t *menu); static void page_options_controls_init(menu_t *menu); static void page_options_video_init(menu_t *menu); static void page_options_audio_init(menu_t *menu); @@ -46,14 +48,18 @@ static void draw_model(Object *model, vec2_t offset, vec3_t pos, float rotation) render_set_screen_position(vec2(0, 0)); } +// ----------------------------------------------------------------------------- +// Commonly used settings choices +static const char *opts_off_on[] = {"OFF", "ON"}; + // ----------------------------------------------------------------------------- // Main Menu -static void button_start_game(menu_t *menu, int data) { - page_race_class_init(menu); +static void button_start_game(menu_t *menu, int) { + page_race_type_init(menu); } -static void button_options(menu_t *menu, int data) { +static void button_options(menu_t *menu, int) { page_options_init(menu); } @@ -66,11 +72,11 @@ static void button_quit_confirm(menu_t *menu, int data) { } } -static void button_quit(menu_t *menu, int data) { +static void button_quit(menu_t *menu, int) { menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm); } -static void page_main_draw(menu_t *menu, int data) { +static void page_main_draw(menu_t*, int data) { switch (data) { case 0: draw_model(g.ships[0].model, vec2(0, -0.1), vec3(0, 0, -700), system_cycle_time()); break; case 1: draw_model(models.misc.options, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; @@ -79,7 +85,7 @@ static void page_main_draw(menu_t *menu, int data) { } static void page_main_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "OPTIONS", page_main_draw); + menu_page_t *page = menu_push(menu, "OPTIONS", page_main_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; @@ -94,28 +100,90 @@ static void page_main_init(menu_t *menu) { #endif } +// ----------------------------------------------------------------------------- +// Network + +static void page_network_draw(menu_t *menu, int) { + menu_page_t *page = &menu->pages[menu->index]; + + int name_col = page->items_pos.x + page->block_width - 200; + int server_ip_col = page->items_pos.x + page->block_width - 100; + int server_ping_col = page->items_pos.x + page->block_width; + + int line_y = page->items_pos.y - 20; + + vec2i_t col0_pos = vec2i(name_col - ui_text_width("NAME", UI_SIZE_8), line_y); + ui_draw_text("NAME", ui_scaled_pos(page->items_anchor, col0_pos), UI_SIZE_8, UI_COLOR_DEFAULT); + + vec2i_t col1_pos = vec2i(server_ip_col - ui_text_width("IP", UI_SIZE_8), line_y); + ui_draw_text("IP", ui_scaled_pos(page->items_anchor, col1_pos), UI_SIZE_8, UI_COLOR_DEFAULT); + + vec2i_t col2_pos = vec2i(server_ping_col - ui_text_width("PING", UI_SIZE_8), line_y); + ui_draw_text("PING", ui_scaled_pos(page->items_anchor, col2_pos), UI_SIZE_8, UI_COLOR_DEFAULT); + line_y += 40; + + for (unsigned int i = 0; i < server_com_get_n_servers(); i++) { + //server_info_t *server = &server_com_get_servers()[i]; + + // TODO: + //pos.x = server_ip_col - ui_text_width(server->ip, UI_SIZE_8); + //ui_draw_text(server->ip, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); + + //pos.x = server_ping_col - ui_text_width(server->ping, UI_SIZE_8); + //char ping_str[16]; + //snprintf(ping_str, sizeof(ping_str), "%d ms", server->ping); + ///ui_draw_text(ping_str, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); + + line_y += 12; + } +} + +static void toggle_network_interface(menu_t*, int data) { + save.network_interface = data; + save.is_dirty = true; + + // TODO + // additionally, should restart the network discovery thread + server_com_init_network_discovery(); +} + +static void page_network_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "NETWORK", page_network_draw, server_com_init_network_discovery, server_com_halt_network_discovery); + server_com_set_menu_page(page); + + flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); + page->title_pos = vec2i(-160, -100); + page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + page->items_pos = vec2i(-160, -50); + page->block_width = 320; + page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + + static const char *opts_network_interfaces[] = { "LAN", "INTERNET" }; + + menu_page_add_toggle(page, save.network_interface, "NETWORK INTERFACE", opts_network_interfaces, len(opts_network_interfaces), toggle_network_interface); +} // ----------------------------------------------------------------------------- // Options -static void button_controls(menu_t *menu, int data) { +static void button_controls(menu_t *menu, int) { page_options_controls_init(menu); } -static void button_video(menu_t *menu, int data) { +static void button_video(menu_t *menu, int) { page_options_video_init(menu); } -static void button_audio(menu_t *menu, int data) { +static void button_audio(menu_t *menu, int) { page_options_audio_init(menu); } -static void button_highscores(menu_t *menu, int data) { +static void button_highscores(menu_t *menu, int) { page_options_highscores_init(menu); } -static void page_options_draw(menu_t *menu, int data) { +static void page_options_draw(menu_t*, int data) { switch (data) { case 0: draw_model(models.controller, vec2(0, -0.1), vec3(0, 0, -6000), system_cycle_time()); break; case 1: draw_model(models.rescue, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; // TODO: needs better model @@ -125,7 +193,7 @@ static void page_options_draw(menu_t *menu, int data) { } static void page_options_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "OPTIONS", page_options_draw); + menu_page_t *page = menu_push(menu, "OPTIONS", page_options_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; @@ -141,11 +209,10 @@ static void page_options_init(menu_t *menu) { // ----------------------------------------------------------------------------- // Options Controls -static const char *button_names[NUM_GAME_ACTIONS][2] = {}; static int control_current_action; static float await_input_deadline; -void button_capture(void *user, button_t button, int32_t ascii_char) { +void button_capture(void *user, button_t button, int32_t) { if (button == INPUT_INVALID) { return; } @@ -160,7 +227,7 @@ void button_capture(void *user, button_t button, int32_t ascii_char) { int index = button < INPUT_KEY_MAX ? 0 : 1; // joypad or keyboard // unbind this button if it's bound anywhere - for (int i = 0; i < len(save.buttons); i++) { + for (unsigned int i = 0; i < len(save.buttons); i++) { if (save.buttons[i][index] == button) { save.buttons[i][index] = INPUT_INVALID; } @@ -172,7 +239,7 @@ void button_capture(void *user, button_t button, int32_t ascii_char) { menu_pop(menu); } -static void page_options_control_set_draw(menu_t *menu, int data) { +static void page_options_control_set_draw(menu_t *menu, int) { float remaining = await_input_deadline - platform_now(); menu_page_t *page = &menu->pages[menu->index]; @@ -191,12 +258,12 @@ static void page_options_controls_set_init(menu_t *menu, int data) { control_current_action = data; await_input_deadline = platform_now() + 3; - menu_page_t *page = menu_push(menu, "AWAITING INPUT", page_options_control_set_draw); + menu_push(menu, "AWAITING INPUT", page_options_control_set_draw, NULL, NULL); input_capture(button_capture, menu); } -static void page_options_control_draw(menu_t *menu, int data) { +static void page_options_control_draw(menu_t *menu, int) { menu_page_t *page = &menu->pages[menu->index]; int left = page->items_pos.x + page->block_width - 100; @@ -236,15 +303,21 @@ static void page_options_control_draw(menu_t *menu, int data) { } } -static void toggle_analog_response(menu_t *menu, int data) { +static void toggle_analog_response(menu_t*, int data) { save.analog_response = (float)data + 1; save.is_dirty = true; } static const char *analog_response[] = {"LINEAR", "MODERATE", "HEAVY"}; +static void toggle_enable_force_feedback(menu_t *menu, int data) { + save.enable_force_feedback = data; + save.is_dirty = true; + platform_set_force_feedback(save.enable_force_feedback); +} + static void page_options_controls_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw); + menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw, NULL, NULL); flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); page->title_pos = vec2i(-160, -100); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; @@ -265,45 +338,46 @@ static void page_options_controls_init(menu_t *menu) { menu_page_add_button(page, A_CHANGE_VIEW, "VIEW", page_options_controls_set_init); menu_page_add_toggle(page, save.analog_response - 1, "ANALOG RESPONSE", analog_response, len(analog_response), toggle_analog_response); + menu_page_add_toggle(page, save.enable_force_feedback, "ENABLE FORCE FEEDBACK", opts_off_on, len(opts_off_on), toggle_enable_force_feedback); } // ----------------------------------------------------------------------------- // Options Video -static void toggle_fullscreen(menu_t *menu, int data) { +static void toggle_fullscreen(menu_t*, int data) { save.fullscreen = data; save.is_dirty = true; platform_set_fullscreen(save.fullscreen); } -static void toggle_internal_roll(menu_t *menu, int data) { +static void toggle_internal_roll(menu_t*, int data) { save.internal_roll = (float)data * 0.1; save.is_dirty = true; } -static void toggle_show_fps(menu_t *menu, int data) { +static void toggle_show_fps(menu_t*, int data) { save.show_fps = data; save.is_dirty = true; } -static void toggle_ui_scale(menu_t *menu, int data) { +static void toggle_ui_scale(menu_t*, int data) { save.ui_scale = data; save.is_dirty = true; } -static void toggle_res(menu_t *menu, int data) { +static void toggle_res(menu_t*, int data) { render_set_resolution(data); save.screen_res = data; save.is_dirty = true; } -static void toggle_post(menu_t *menu, int data) { +static void toggle_post(menu_t*, int data) { render_set_post_effect(data); save.post_effect = data; save.is_dirty = true; } -static void toggle_screen_shake(menu_t *menu, int data) { +static void toggle_screen_shake(menu_t*, int data) { save.screen_shake = (float)data * 0.5; save.is_dirty = true; } @@ -316,7 +390,7 @@ static const char *opts_post[] = {"NONE", "CRT EFFECT"}; static const char *opts_screen_shake[] = {"DISABLED", "REDUCED", "FULL"}; static void page_options_video_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL); + menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL, NULL, NULL); flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); page->title_pos = vec2i(-160, -100); page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; @@ -338,12 +412,12 @@ static void page_options_video_init(menu_t *menu) { // ----------------------------------------------------------------------------- // Options Audio -static void toggle_music_volume(menu_t *menu, int data) { +static void toggle_music_volume(menu_t*, int data) { save.music_volume = (float)data * 0.1; save.is_dirty = true; } -static void toggle_sfx_volume(menu_t *menu, int data) { +static void toggle_sfx_volume(menu_t*, int data) { save.sfx_volume = (float)data * 0.1; save.is_dirty = true; } @@ -351,7 +425,7 @@ static void toggle_sfx_volume(menu_t *menu, int data) { static const char *opts_volume[] = {"0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; static void page_options_audio_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "AUDIO OPTIONS", NULL); + menu_page_t *page = menu_push(menu, "AUDIO OPTIONS", NULL, NULL, NULL); flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); page->title_pos = vec2i(-160, -100); @@ -409,7 +483,7 @@ static void page_options_highscores_viewer_input_handler() { } } -static void page_options_highscores_viewer_draw(menu_t *menu, int data) { +static void page_options_highscores_viewer_draw(menu_t*, int) { ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER; vec2i_t pos = vec2i(0, -70); @@ -435,10 +509,10 @@ static void page_options_highscores_viewer_draw(menu_t *menu, int data) { static void page_options_highscores_viewer_init(menu_t *menu) { menu_page_t *page; if (options_highscores_tab == HIGHSCORE_TAB_TIME_TRIAL) { - page = menu_push(menu, "BEST TIME TRIAL TIMES", page_options_highscores_viewer_draw); + page = menu_push(menu, "BEST TIME TRIAL TIMES", page_options_highscores_viewer_draw, NULL, NULL); } else /*options_highscores_tab == HIGHSCORE_TAB_RACE)*/ { - page = menu_push(menu, "BEST RACE TIMES", page_options_highscores_viewer_draw); + page = menu_push(menu, "BEST RACE TIMES", page_options_highscores_viewer_draw, NULL, NULL); } flags_add(page->layout_flags, MENU_FIXED); @@ -451,12 +525,12 @@ static void button_highscores_viewer(menu_t *menu, int data) { page_options_highscores_viewer_init(menu); } -static void page_options_highscores_draw(menu_t *menu, int data) { +static void page_options_highscores_draw(menu_t*, int) { draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); } static void page_options_highscores_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "VIEW BEST TIMES", page_options_highscores_draw); + menu_page_t *page = menu_push(menu, "VIEW BEST TIMES", page_options_highscores_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); @@ -481,7 +555,7 @@ static void button_race_class_select(menu_t *menu, int data) { return; } g.race_class = data; - page_race_type_init(menu); + page_team_init(menu); } static void page_race_class_draw(menu_t *menu, int data) { @@ -501,8 +575,8 @@ static void page_race_class_draw(menu_t *menu, int data) { } static void page_race_class_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACING CLASS", page_race_class_draw); - for (int i = 0; i < len(def.race_classes); i++) { + menu_page_t *page = menu_push(menu, "SELECT RACING CLASS", page_race_class_draw, NULL, NULL); + for (unsigned int i = 0; i < len(def.race_classes); i++) { menu_page_add_button(page, i, def.race_classes[i].name, button_race_class_select); } } @@ -515,25 +589,30 @@ static void page_race_class_init(menu_t *menu) { static void button_race_type_select(menu_t *menu, int data) { g.race_type = data; g.highscore_tab = g.race_type == RACE_TYPE_TIME_TRIAL ? HIGHSCORE_TAB_TIME_TRIAL : HIGHSCORE_TAB_RACE; - page_team_init(menu); + + if(g.race_type != RACE_TYPE_NETWORK) { + page_race_class_init(menu); + } else { + page_network_init(menu); + } } -static void page_race_type_draw(menu_t *menu, int data) { +static void page_race_type_draw(menu_t*, int data) { switch (data) { - case 0: draw_model(models.misc.championship, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; - case 1: draw_model(models.misc.single_race, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; - case 2: draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + case RACE_TYPE_CHAMPIONSHIP: draw_model(models.misc.championship, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + case RACE_TYPE_SINGLE: draw_model(models.misc.single_race, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + case RACE_TYPE_TIME_TRIAL: draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; } } static void page_race_type_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACE TYPE", page_race_type_draw); + menu_page_t *page = menu_push(menu, "SELECT RACE TYPE", page_race_type_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; page->items_pos = vec2i(0, -110); page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.race_types); i++) { + for (unsigned int i = 0; i < len(def.race_types); i++) { menu_page_add_button(page, i, def.race_types[i].name, button_race_type_select); } } @@ -548,7 +627,7 @@ static void button_team_select(menu_t *menu, int data) { page_pilot_init(menu); } -static void page_team_draw(menu_t *menu, int data) { +static void page_team_draw(menu_t*, int data) { int team_model_index = (data + 3) % 4; // models in the prm are shifted by -1 draw_model(models.teams[team_model_index], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); draw_model(g.ships[def.teams[data].pilots[0]].model, vec2(0, -0.3), vec3(-700, -800, -1300), system_cycle_time()*1.1); @@ -556,13 +635,13 @@ static void page_team_draw(menu_t *menu, int data) { } static void page_team_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT YOUR TEAM", page_team_draw); + menu_page_t *page = menu_push(menu, "SELECT YOUR TEAM", page_team_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; page->items_pos = vec2i(0, -110); page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.teams); i++) { + for (unsigned int i = 0; i < len(def.teams); i++) { menu_page_add_button(page, i, def.teams[i].name, button_team_select); } } @@ -584,18 +663,18 @@ static void button_pilot_select(menu_t *menu, int data) { } } -static void page_pilot_draw(menu_t *menu, int data) { +static void page_pilot_draw(menu_t*, int data) { draw_model(models.pilots[def.pilots[data].logo_model], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); } static void page_pilot_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "CHOOSE YOUR PILOT", page_pilot_draw); + menu_page_t *page = menu_push(menu, "CHOOSE YOUR PILOT", page_pilot_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; page->items_pos = vec2i(0, -110); page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.teams[g.team].pilots); i++) { + for (unsigned int i = 0; i < len(def.teams[g.team].pilots); i++) { menu_page_add_button(page, def.teams[g.team].pilots[i], def.pilots[def.teams[g.team].pilots[i]].name, button_pilot_select); } } @@ -604,12 +683,12 @@ static void page_pilot_init(menu_t *menu) { // ----------------------------------------------------------------------------- // Circut -static void button_circut_select(menu_t *menu, int data) { +static void button_circut_select(menu_t*, int data) { g.circut = data; game_set_scene(GAME_SCENE_RACE); } -static void page_circut_draw(menu_t *menu, int data) { +static void page_circut_draw(menu_t*, int data) { vec2i_t pos = vec2i(0, -25); vec2i_t size = vec2i(128, 74); vec2i_t scaled_size = ui_scaled(size); @@ -618,13 +697,13 @@ static void page_circut_draw(menu_t *menu, int data) { } static void page_circut_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACING CIRCUT", page_circut_draw); + menu_page_t *page = menu_push(menu, "SELECT RACING CIRCUT", page_circut_draw, NULL, NULL); flags_add(page->layout_flags, MENU_FIXED); page->title_pos = vec2i(0, 30); page->title_anchor = UI_POS_TOP | UI_POS_CENTER; page->items_pos = vec2i(0, -100); page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.circuts); i++) { + for (unsigned int i = 0; i < len(def.circuts); i++) { if (!def.circuts[i].is_bonus_circut || save.has_bonus_circuts) { menu_page_add_button(page, i, def.circuts[i].name, button_circut_select); } diff --git a/src/wipeout/menu.c b/src/wipeout/menu.c index 4cd005a..47804b2 100644 --- a/src/wipeout/menu.c +++ b/src/wipeout/menu.c @@ -16,7 +16,7 @@ void menu_reset(menu_t *menu) { menu->index = -1; } -menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)) { +menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int), void(*init_func)(), void(*exit_func)()) { error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded"); menu_page_t *page = &menu->pages[++menu->index]; page->layout_flags = MENU_VERTICAL | MENU_ALIGN_CENTER; @@ -24,10 +24,17 @@ menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int page->title = title; page->subtitle = NULL; page->draw_func = draw_func; + page->init_func = init_func; + page->exit_func = exit_func; page->entries_len = 0; page->index = 0; page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + + if(page->init_func) { + page->init_func(); + } + return page; } @@ -51,23 +58,28 @@ void menu_pop(menu_t *menu) { if (menu->index == 0) { return; } + + if(menu->pages[menu->index].exit_func) { + menu->pages[menu->index].exit_func(); + } + menu->index--; } -void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)) { +void menu_page_add_button(menu_page_t *page, int data, const char *text, void(*select_func)(menu_t *, int)) { error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded"); menu_entry_t *entry = &page->entries[page->entries_len++]; entry->data = data; - entry->text = text; + entry->text = strdup(text); entry->select_func = select_func; entry->type = MENU_ENTRY_BUTTON; } -void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)) { +void menu_page_add_toggle(menu_page_t *page, int data, const char *text, const char **options, int len, void(*select_func)(menu_t *, int)) { error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded"); menu_entry_t *entry = &page->entries[page->entries_len++]; entry->data = data; - entry->text = text; + entry->text = strdup(text); entry->select_func = select_func; entry->type = MENU_ENTRY_TOGGLE; entry->options = options; diff --git a/src/wipeout/menu.h b/src/wipeout/menu.h index b559339..75dd9a6 100644 --- a/src/wipeout/menu.h +++ b/src/wipeout/menu.h @@ -38,6 +38,8 @@ struct menu_page_t { char *title, *subtitle; menu_page_layout_t layout_flags; void (*draw_func)(menu_t *, int); + void (*init_func)(); + void (*exit_func)(); menu_entry_t entries[MENU_ENTRIES_MAX]; int entries_len; int index; @@ -55,11 +57,11 @@ struct menu_t { void menu_reset(menu_t *menu); -menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)); +menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int), void(*init_func)(), void(*exit_func)()); menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int)); void menu_pop(menu_t *menu); -void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)); -void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)); +void menu_page_add_button(menu_page_t *page, int data, const char *text, void(*select_func)(menu_t *, int)); +void menu_page_add_toggle(menu_page_t *page, int data, const char *text, const char **options, int len, void(*select_func)(menu_t *, int)); void menu_update(menu_t *menu); #endif diff --git a/src/wipeout/race.c b/src/wipeout/race.c index 85ed783..af4c11f 100755 --- a/src/wipeout/race.c +++ b/src/wipeout/race.c @@ -51,7 +51,7 @@ void race_init(void) { if (g.is_attract_mode) { attract_start_time = system_time(); - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { flags_rm(g.ships[i].flags, SHIP_VIEW_INTERNAL); flags_rm(g.ships[i].flags, SHIP_RACING); } @@ -148,12 +148,12 @@ void race_start(void) { particles_init(); weapons_init(); - for (int i = 0; i < len(g.race_ranks); i++) { + for (unsigned int i = 0; i < len(g.race_ranks); i++) { g.race_ranks[i].points = 0; g.race_ranks[i].pilot = i; } - for (int i = 0; i < len(g.lap_times); i++) { - for (int j = 0; j < len(g.lap_times[i]); j++) { + for (unsigned int i = 0; i < len(g.lap_times); i++) { + for (unsigned int j = 0; j < len(g.lap_times[i]); j++) { g.lap_times[i][j] = 0; } } @@ -211,11 +211,11 @@ void race_end(void) { } if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { - for (int i = 0; i < len(def.race_points_for_rank); i++) { + for (unsigned int i = 0; i < len(def.race_points_for_rank); i++) { g.race_ranks[i].points = def.race_points_for_rank[i]; // Find the pilot for this race rank in the championship table - for (int j = 0; j < len(g.championship_ranks); j++) { + for (unsigned int j = 0; j < len(g.championship_ranks); j++) { if (g.race_ranks[i].pilot == g.championship_ranks[j].pilot) { g.championship_ranks[j].points += def.race_points_for_rank[i]; break; diff --git a/src/wipeout/server_com.c b/src/wipeout/server_com.c new file mode 100644 index 0000000..110810a --- /dev/null +++ b/src/wipeout/server_com.c @@ -0,0 +1,248 @@ + +#include "server_com.h" + +#include "menu.h" +#include "network_wrapper.h" + +#include +#include +#include + +#if defined(WIN32) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +atomic_bool network_discovery_on = false; + +thrd_t network_discovery_thread; +thrd_t network_discovery_response_thread; + +static int DISCOVERY_TIMEOUT = 3; // seconds + +static int sockfd = INVALID_SOCKET; + +struct server_info_t{ + const char* name; + struct sockaddr_in addr; // server address +}; + +static server_info_t* servers = NULL; // dynamically allocated array of server_info_t +static unsigned int n_servers = 0; + +static menu_page_t* server_menu_page = NULL; // menu for server discovery + +void server_com_client_init(void) { + // TODO +} + +static void server_com_client_connect(menu_t*, int index) { + + // TODO: causes a problem if we try to connect + // before server discovery has terminated; + // should we just halt discovery when we connect? + + if(!servers || n_servers == 0) { + printf("No servers available to connect to.\n"); + return; + } + + struct server_info_t server = servers[index]; + printf("Connecting to server: %s\n", server.name); + + socklen_t fromlen = sizeof(server.addr); + + const char* message = "connect"; + network_send_packet(sockfd, strlen(message), message, server.addr); + + char buffer[1024]; + ssize_t len = wrap_recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, + (struct sockaddr*)(&server.addr), &fromlen); + + if (len < 0) { + perror("recvfrom"); + return; + } + + buffer[len] = '\0'; // null-terminate the received data + printf("Received response from server: %s\n", buffer); + if (strcmp(buffer, "connected") == 0) { + printf("Successfully connected to server %s\n", server.name); + } + else { + printf("Failed to connect to server %s: %s\n", server.name, buffer); + } +} + +static void server_com_update_servers() { + // TODO: + // is this function necessary? + // we can't update or add a button after + // startup, only change the text + + + if(!server_menu_page) { + printf("No server menu page set, cannot update servers.\n"); + return; + } + + server_info_t server = servers[n_servers]; + char name[32]; + snprintf(name, sizeof(name), "%s", server.name); + + menu_page_add_button(server_menu_page, n_servers, name, server_com_client_connect); +} + +/** + * @brief since we're connecting over UDP, need to have a thread running + * separately to listen for discovery responses so we don't miss any + */ +static int server_com_discovery_response(void* arg) { + (void)arg; // unused + + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + char buffer[1024]; + + servers = realloc(servers, 0); // start with an empty list + n_servers = 0; + + time_t start_time = time(NULL); + + while (true) { + + while(!network_discovery_on) { + thrd_yield(); // wait until discovery is enabled + start_time = time(NULL); // reset start time when we start listening + } + + if(time(NULL) - start_time > DISCOVERY_TIMEOUT) { + printf("No responses received in %d seconds, stopping discovery.\n", DISCOVERY_TIMEOUT); + network_discovery_on = false; // stop listening + continue; + } + + ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, + (struct sockaddr*)&from, &fromlen); + + // TODO: how do we need to handle recvfrom timeouts and errors? + // if (len < 0) { + // if (errno == EWOULDBLOCK || errno == EAGAIN) { + // printf("Timeout reached.\n"); + // break; + // } else { + // perror("recvfrom"); + // break; + // } + // } + + if (len > 0) { + Wipeout__ServerInfo* msg = wipeout__server_info__unpack(NULL, len, (const uint8_t*)buffer); + if(msg == NULL) { + fprintf(stderr, "Failed to unpack server info message\n"); + continue; + } + + buffer[len] = '\0'; + printf("Found server %s @ %s:%d\n", msg->name, inet_ntoa(from.sin_addr), msg->port); + + servers = realloc(servers, sizeof(server_info_t) * (n_servers + 1)); + servers[n_servers] = (server_info_t) { + .name = msg->name, + .addr = { + .sin_family = AF_INET, + .sin_port = htons(msg->port), + .sin_addr = from.sin_addr + } + }; + server_com_update_servers(); // update the menu with the new server + n_servers++; + } + } + + return 1; +} + +/** + * @brief Run network discovery to find servers + */ +static int server_com_network_discovery(void* arg) { + (void)arg; + +#ifdef _WIN32 + if(!system_init_winsock()) { + printf("Failed to initialize Winsock for network discovery.\n"); + return 0; + } +#endif + + const char* message = "status"; + broadcast_list_t broadcasts = network_get_broadcast_addresses(); + + for (size_t i = 0; i < broadcasts.count; ++i) { + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(WIPEOUT_PORT), + .sin_addr = broadcasts.list[i].broadcast, + }; + + wrap_sendto(sockfd, message, strlen(message), 0, + (struct sockaddr*)&addr, sizeof(addr)); + + printf("[*] Broadcast packet sent. Waiting for replies...\n"); + // tell receiver thread to start listening + network_discovery_on = true; + } + + free(broadcasts.list); + + return 1; +} + +void server_com_init_network_discovery(void) { + + sockfd = network_get_client_socket(); + if(sockfd == INVALID_SOCKET) { + printf("unable to create socket to run network discovery\n"); + return; + } + + if(!network_bind_socket(sockfd, "8001")) { + printf("unable to bind socket for network discovery\n"); + network_close_socket(&sockfd); + return; + } + + // immediately make us ready to get responses before we start broadcasting + thrd_create(&network_discovery_response_thread, (thrd_start_t)server_com_discovery_response, NULL); + thrd_detach(network_discovery_response_thread); + + thrd_create(&network_discovery_thread, (thrd_start_t)server_com_network_discovery, NULL); + thrd_detach(network_discovery_thread); +} + +void server_com_halt_network_discovery(void) { + network_discovery_on = false; + thrd_join(network_discovery_thread, NULL); + thrd_join(network_discovery_response_thread, NULL); +} + +server_info_t *server_com_get_servers(void) { + return servers; +} + +unsigned int server_com_get_n_servers(void) { + return n_servers; +} + +void server_com_set_menu_page(menu_page_t *page) { + server_menu_page = page; +} diff --git a/src/wipeout/server_com.h b/src/wipeout/server_com.h new file mode 100644 index 0000000..365b14a --- /dev/null +++ b/src/wipeout/server_com.h @@ -0,0 +1,23 @@ + +#pragma once + +#include "menu.h" + +/* +client communication to server(s), +including server discovery +*/ + +typedef struct server_info_t server_info_t; + +void server_com_set_menu_page(menu_page_t *page); + +void server_com_client_init(void); + +void server_com_init_network_discovery(void); + +void server_com_halt_network_discovery(void); + +server_info_t* server_com_get_servers(void); + +unsigned int server_com_get_n_servers(void); \ No newline at end of file diff --git a/src/wipeout/sfx.c b/src/wipeout/sfx.c index 2d4d319..422e401 100644 --- a/src/wipeout/sfx.c +++ b/src/wipeout/sfx.c @@ -76,7 +76,7 @@ void sfx_load(void) { uint32_t sample_index = 0; int32_t history[2] = {0, 0}; - for (int p = 0; p < vb_size;) { + for (unsigned int p = 0; p < vb_size;) { uint8_t header = vb[p++]; uint8_t flags = vb[p++]; uint8_t shift = header & 0x0f; @@ -332,7 +332,7 @@ void sfx_stero_mix(float *buffer, uint32_t len) { uint32_t music_src_index = music->sample_data_pos * music->qoa.channels; - for (int i = 0; i < len; i += 2) { + for (uint32_t i = 0; i < len; i += 2) { float left = 0; float right = 0; diff --git a/src/wipeout/ship.c b/src/wipeout/ship.c index 57a2929..cba077c 100644 --- a/src/wipeout/ship.c +++ b/src/wipeout/ship.c @@ -1,6 +1,7 @@ #include "../mem.h" #include "../utils.h" #include "../system.h" +#include "../platform.h" #include "object.h" #include "scene.h" @@ -23,7 +24,7 @@ void ships_load(void) { texture_list_t collision_textures = image_get_compressed_textures("wipeout/common/alcol.cmp"); Object *collision_models = objects_load("wipeout/common/alcol.prm", collision_textures); - int object_index; + unsigned int object_index; Object *ship_model = ship_models; Object *collision_model = collision_models; @@ -46,7 +47,7 @@ void ships_load(void) { image_get_texture_semi_trans("wipeout/textures/shad3.tim"); image_get_texture_semi_trans("wipeout/textures/shad4.tim"); - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { g.ships[i].shadow_texture = shadow_textures_start + (i >> 1); } } @@ -58,7 +59,7 @@ void ships_init(section_t *section) { int ranks_to_pilots[NUM_PILOTS]; // Initialize ranks with all pilots in order - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { ranks_to_pilots[i] = i; } @@ -70,7 +71,7 @@ void ships_init(section_t *section) { // Randomize some tiers in an ongoing championship else if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { // Initialize with current championship order - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { ranks_to_pilots[i] = g.championship_ranks[i].pilot; } shuffle(ranks_to_pilots, 2); // shuffle 0..1 @@ -78,7 +79,7 @@ void ships_init(section_t *section) { } // player is always last - for (int i = 0; i < len(ranks_to_pilots)-1; i++) { + for (unsigned int i = 0; i < len(ranks_to_pilots)-1; i++) { if (ranks_to_pilots[i] == g.pilot) { swap(ranks_to_pilots[i], ranks_to_pilots[i+1]); } @@ -89,7 +90,7 @@ void ships_init(section_t *section) { for (int i = 0; i < start_line_pos - 15; i++) { section = section->next; } - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { start_sections[i] = section; section = section->next; if ((i % 2) == 0) { @@ -97,7 +98,7 @@ void ships_init(section_t *section) { } } - for (int i = 0; i < len(ranks_to_pilots); i++) { + for (unsigned int i = 0; i < len(ranks_to_pilots); i++) { int rank_inv = (len(g.ships)-1) - i; int pilot = ranks_to_pilots[i]; ship_init(&g.ships[pilot], start_sections[rank_inv], pilot, rank_inv); @@ -125,18 +126,18 @@ void ships_update(void) { ship_update(&g.ships[g.pilot]); } else { - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { ship_update(&g.ships[i]); } - for (int j = 0; j < (len(g.ships) - 1); j++) { - for (int i = j + 1; i < len(g.ships); i++) { + for (unsigned int j = 0; j < (len(g.ships) - 1); j++) { + for (unsigned int i = j + 1; i < len(g.ships); i++) { ship_collide_with_ship(&g.ships[i], &g.ships[j]); } } if (flags_is(g.ships[g.pilot].flags, SHIP_RACING)) { sort(g.race_ranks, len(g.race_ranks), sort_rank_compare); - for (int32_t i = 0; i < len(g.ships); i++) { + for (uint32_t i = 0; i < len(g.ships); i++) { g.ships[g.race_ranks[i].pilot].position_rank = i + 1; } } @@ -144,7 +145,7 @@ void ships_update(void) { } void ships_reset_exhaust_plumes(void) { - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { ship_reset_exhaust_plume(&g.ships[i]); } } @@ -152,7 +153,7 @@ void ships_reset_exhaust_plumes(void) { void ships_draw(void) { // Ship models - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { if ( (flags_is(g.ships[i].flags, SHIP_VIEW_INTERNAL) && flags_not(g.ships[i].flags, SHIP_IN_RESCUE)) || (g.race_type == RACE_TYPE_TIME_TRIAL && i != g.pilot) @@ -170,7 +171,7 @@ void ships_draw(void) { render_set_depth_write(false); render_set_depth_offset(-32.0); - for (int i = 0; i < len(g.ships); i++) { + for (unsigned int i = 0; i < len(g.ships); i++) { if ( (g.race_type == RACE_TYPE_TIME_TRIAL && i != g.pilot) || flags_not(g.ships[i].flags, SHIP_VISIBLE) || @@ -643,6 +644,7 @@ void ship_resolve_wing_collision(ship_t *self, track_face_t *face, float directi self->velocity = vec3_add(self->velocity, vec3_mulf(face->normal, 4096.0)); // div by 4096? float magnitude = (fabsf(angle) * self->speed) * 2 * M_PI / 4096.0; // (6 velocity shift, 12 angle shift?) + platform_force_feedback(magnitude, 500); vec3_t wing_pos; if (direction > 0) { @@ -662,14 +664,14 @@ void ship_resolve_wing_collision(ship_t *self, track_face_t *face, float directi void ship_resolve_nose_collision(ship_t *self, track_face_t *face, float direction) { - vec3_t collision_vector = vec3_sub(self->section->center, face->tris[0].vertices[2].pos); - float angle = vec3_angle(collision_vector, self->dir_forward); self->velocity = vec3_reflect(self->velocity, face->normal, 2); self->position = vec3_sub(self->position, vec3_mulf(self->velocity, 0.015625)); // system_tick? self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.5)); self->velocity = vec3_add(self->velocity, vec3_mulf(face->normal, 4096)); // div by 4096? float magnitude = ((self->speed * 0.0625) + 400) * 2 * M_PI / 4096.0; + platform_force_feedback(magnitude, 500); + if (direction > 0) { self->angular_velocity.y += magnitude; } @@ -945,7 +947,7 @@ bool ship_intersects_ship(ship_t *self, ship_t *other) { float dp1 = vec3_dot(vec3_sub(p1, other_points[vi]), plane1); float dp2 = vec3_dot(other_lines[vi], plane1); - if (dp2 != 0) { + if (dp2 != 0.0F) { float norm = dp1 / dp2; if ((norm >= 0) && (norm <= 1)) { @@ -997,6 +999,8 @@ void ship_collide_with_ship(ship_t *self, ship_t *other) { self->mass + other->mass ); + platform_force_feedback(1.0, 500); + vec3_t ship_react = vec3_mulf(vec3_sub(vc, self->velocity), 0.5); // >> 1 vec3_t other_react = vec3_mulf(vec3_sub(vc, other->velocity), 0.5); // >> 1 self->position = vec3_sub(self->position, vec3_mulf(self->velocity, 0.015625)); // >> 6 diff --git a/src/wipeout/ship_ai.c b/src/wipeout/ship_ai.c index 171926e..324a709 100644 --- a/src/wipeout/ship_ai.c +++ b/src/wipeout/ship_ai.c @@ -38,14 +38,14 @@ void ship_ai_update_intro_await_go(ship_t *self) { } } -vec3_t ship_ai_strat_hold_left(ship_t *self, track_face_t *face) { +vec3_t ship_ai_strat_hold_left(ship_t*, track_face_t *face) { vec3_t fv1 = face->tris[0].vertices[1].pos; vec3_t fv2 = face->tris[0].vertices[0].pos; return vec3_mulf(vec3_sub(fv1, fv2), 0.5); } -vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face) { +vec3_t ship_ai_strat_hold_right(ship_t*, track_face_t *face) { vec3_t fv1 = face->tris[0].vertices[0].pos; vec3_t fv2 = face->tris[0].vertices[1].pos; @@ -53,7 +53,7 @@ vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face) { } -vec3_t ship_ai_strat_hold_center(ship_t *self, track_face_t *face) { +vec3_t ship_ai_strat_hold_center(ship_t*, track_face_t*) { return vec3(0, 0, 0); } diff --git a/src/wipeout/ship_player.c b/src/wipeout/ship_player.c index fe086e9..f728121 100644 --- a/src/wipeout/ship_player.c +++ b/src/wipeout/ship_player.c @@ -58,7 +58,7 @@ void ship_player_update_intro_await_three(ship_t *self) { ship_player_update_intro_general(self); if (self->update_timer <= UPDATE_TIME_THREE) { - sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_3); + sfx_play(SFX_VOICE_COUNT_3); self->update_func = ship_player_update_intro_await_two; } } @@ -68,7 +68,7 @@ void ship_player_update_intro_await_two(ship_t *self) { if (self->update_timer <= UPDATE_TIME_TWO) { scene_set_start_booms(1); - sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_2); + sfx_play(SFX_VOICE_COUNT_2); self->update_func = ship_player_update_intro_await_one; } } @@ -78,7 +78,7 @@ void ship_player_update_intro_await_one(ship_t *self) { if (self->update_timer <= UPDATE_TIME_ONE) { scene_set_start_booms(2); - sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_1); + sfx_play(SFX_VOICE_COUNT_1); self->update_func = ship_player_update_intro_await_go; } } @@ -88,7 +88,7 @@ void ship_player_update_intro_await_go(ship_t *self) { if (self->update_timer <= UPDATE_TIME_GO) { scene_set_start_booms(3); - sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_GO); + sfx_play(SFX_VOICE_COUNT_GO); if (flags_is(self->flags, SHIP_RACING)) { // Check for stall @@ -520,7 +520,7 @@ ship_t *ship_player_find_target(ship_t *self) { int shortest_distance = 256; ship_t *nearest_ship = NULL; - for (int i = 0; i < len(g.ships); i++) { + for (long unsigned int i = 0; i < len(g.ships); i++) { ship_t *other = &g.ships[i]; if (self == other) { continue; diff --git a/src/wipeout/track.c b/src/wipeout/track.c index 4065721..defe2c9 100755 --- a/src/wipeout/track.c +++ b/src/wipeout/track.c @@ -20,7 +20,7 @@ void track_load(const char *base_path) { cmp_t *cmp = image_load_compressed(get_path(base_path, "library.cmp")); image_t *temp_tile = image_alloc(128, 128); - for (int i = 0; i < ttf->len; i++) { + for (unsigned int i = 0; i < ttf->len; i++) { for (int tx = 0; tx < 4; tx++) { for (int ty = 0; ty < 4; ty++) { uint32_t sub_tile_index = ttf->tiles[i].near[ty * 4 + tx]; @@ -94,7 +94,7 @@ ttf_t *track_load_tile_format(char *ttf_name) { ttf_t *ttf = mem_temp_alloc(sizeof(ttf_t) + sizeof(ttf_tile_t) * num_tiles); ttf->len = num_tiles; - for (int t = 0; t < num_tiles; t++) { + for (unsigned int t = 0; t < num_tiles; t++) { for (int i = 0; i < 16; i++) { ttf->tiles[t].near[i] = get_i16(ttf_bytes, &p); } @@ -248,7 +248,7 @@ void track_load_sections(char *file_name) { void track_draw_section(section_t *section) { track_face_t *face = g.track.faces + section->face_start; - int16_t face_count = section->face_count; + uint16_t face_count = section->face_count; for (uint32_t j = 0; j < face_count; j++) { uint16_t tex_index = texture_from_list(g.track.textures, face->texture); @@ -266,8 +266,6 @@ void track_draw(camera_t *camera) { vec3_t cam_pos = camera->position; vec3_t cam_dir = camera_forward(camera); - int drawn = 0; - int skipped = 0; for(int32_t i = 0; i < g.track.section_count; i++) { section_t *s = &g.track.sections[i]; vec3_t diff = vec3_sub(cam_pos, s->center); diff --git a/src/wipeout/track.h b/src/wipeout/track.h index 69f2a12..bcfea6c 100755 --- a/src/wipeout/track.h +++ b/src/wipeout/track.h @@ -48,7 +48,7 @@ typedef struct section_t { vec3_t center; int16_t face_start; - int16_t face_count; + uint16_t face_count; int16_t flags; int16_t num; diff --git a/src/wipeout/weapon.c b/src/wipeout/weapon.c index 498b0fb..3eecdc9 100755 --- a/src/wipeout/weapon.c +++ b/src/wipeout/weapon.c @@ -1,6 +1,7 @@ #include "../mem.h" #include "../utils.h" #include "../system.h" +#include "../platform.h" #include "track.h" #include "ship.h" @@ -386,6 +387,7 @@ void weapon_update_mine(weapon_t *self) { if (ship->pilot == g.pilot) { ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.125)); camera_set_shake(&g.camera, CAMERA_SHAKE_LONG); + platform_force_feedback(0.75, 500); } else { ship->speed = ship->speed * 0.125; @@ -436,6 +438,7 @@ void weapon_update_missile(weapon_t *self) { ship->angular_velocity.z += rand_float(-0.1, 0.1); ship->turn_rate_from_hit = rand_float(-0.1, 0.1); camera_set_shake(&g.camera, CAMERA_SHAKE_LONG); + platform_force_feedback(1.0, 500); } else { ship->speed = ship->speed * 0.03125; @@ -484,6 +487,7 @@ void weapon_update_rocket(weapon_t *self) { ship->angular_velocity.z += rand_float(-0.1, 0.1);; ship->turn_rate_from_hit = rand_float(-0.1, 0.1);; camera_set_shake(&g.camera, CAMERA_SHAKE_LONG); + platform_force_feedback(1.0, 750); } else { ship->speed = ship->speed * 0.03125; @@ -533,6 +537,9 @@ void weapon_update_ebolt(weapon_t *self) { if (flags_not(ship->flags, SHIP_SHIELDED)) { flags_add(ship->flags, SHIP_ELECTROED); ship->ebolt_timer = WEAPON_EBOLT_DURATION; + if (ship->pilot == g.pilot) { + platform_force_feedback(0.8, WEAPON_EBOLT_DURATION * 1000); + } } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..d742d8a --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,17 @@ + + +add_executable(tests + main.c + test_network.c + test_server.c + +# mocks and test utilities + network_wrapper.c + utils.c +) + +target_link_libraries(tests PRIVATE + cmocka::cmocka + network + serverlib +) \ No newline at end of file diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..d1fa0f7 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,42 @@ + +#include "utils.h" + +#include +#include +#include +#include + +extern void test_network_get_packet(void **state); +extern void test_network_get_packet_no_data(void **state); +extern void test_network_get_local_subnet(void **state); +extern void network_close_socket_sets_socket_invalid(void **state); + + +extern void empties_queue_after_process(void **state); +extern void unknown_message_echo(void **state); +extern void server_status_query(void **state); +extern void server_connect_client_ok(void **state); +extern void server_connect_fails_too_many_clients(void **state); + +int main(void) { + const struct CMUnitTest network_tests[] = { + // network library tests + cmocka_unit_test(test_network_get_packet), + cmocka_unit_test(test_network_get_packet_no_data), + cmocka_unit_test(test_network_get_local_subnet), + cmocka_unit_test(network_close_socket_sets_socket_invalid), + + + }; + + const struct CMUnitTest server_tests[] = { + cmocka_unit_test_prestate_setup_teardown(empties_queue_after_process, server_test_setup, network_test_cleanup, NULL), + cmocka_unit_test_prestate_setup_teardown(unknown_message_echo, server_test_setup, network_test_cleanup, NULL), + cmocka_unit_test_prestate_setup_teardown(server_status_query, server_test_setup, network_test_cleanup, NULL), + cmocka_unit_test_prestate_setup_teardown(server_connect_client_ok, server_test_setup, network_test_cleanup, NULL), + cmocka_unit_test_prestate_setup_teardown(server_connect_fails_too_many_clients, server_test_setup, network_test_cleanup, NULL), + }; + + return cmocka_run_group_tests_name("network_tests", network_tests, NULL, NULL) || + cmocka_run_group_tests_name("server_tests", server_tests, NULL, NULL); +} \ No newline at end of file diff --git a/tests/network_wrapper.c b/tests/network_wrapper.c new file mode 100644 index 0000000..1256e21 --- /dev/null +++ b/tests/network_wrapper.c @@ -0,0 +1,75 @@ + +#include + +#include +#include +#include +#include +#include + +#if defined(WIN32) +#include +#include +#else +#include +#include +#endif + +// protobufs +#include + +ssize_t wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) { + check_expected(sockfd); + check_expected(len); + check_expected(flags); + + // Optionally check or set addr and addrlen if relevant + if (src_addr != NULL && addrlen != NULL) { + memcpy(src_addr, mock_ptr_type(struct sockaddr *), sizeof(struct sockaddr)); + *addrlen = mock_type(socklen_t); + } + + // Optionally simulate data in the buffer + const char *data = mock_ptr_type(char *); + size_t data_len = strlen(data); // or however long you need + memcpy(buf, data, data_len < len ? data_len : len); + + return (ssize_t)mock_type(int); // number of bytes to return +} + +ssize_t wrap_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { + check_expected(sockfd); + check_expected(len); + check_expected(flags); + check_expected(buf); + + // Get message type for this test + const char *msg_type = mock_type(const char *); + + if (strcmp(msg_type, "STATUS") == 0) { + Wipeout__ServerInfo *msg = wipeout__server_info__unpack(NULL, len, buf); + assert_non_null(msg); + const char *expected_name = mock_type(const char *); + int expected_port = mock_type(int); + + assert_string_equal(msg->name, expected_name); + assert_int_equal(msg->port, expected_port); + wipeout__server_info__free_unpacked(msg, NULL); + } else if (strcmp(msg_type, "STRING") == 0) { + // const char *expected = mock_type(const char *); + // assert_memory_equal(buf, expected, len); + } else { + fail_msg("Unknown message type in wrap_sendto"); + } + + // TODO: is this necessary? + // Optionally check or set dest_addr and addrlen if relevant + // if (dest_addr != NULL && addrlen > 0) { + // check_expected(dest_addr->sa_data); + // check_expected(dest_addr->sa_family); + // } + + return (ssize_t)mock_type(int); // number of bytes sent +} \ No newline at end of file diff --git a/tests/test_network.c b/tests/test_network.c new file mode 100644 index 0000000..fbbd0d0 --- /dev/null +++ b/tests/test_network.c @@ -0,0 +1,113 @@ + + +#include +#include + +#include "utils.h" + +#include +#if defined(WIN32) +#include +#else +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +void network_close_socket_sets_socket_invalid(void** state) { + (void)state; // unused + + int sockfd = 3; // Example socket descriptor + network_set_bound_ip_socket(sockfd); + + // Ensure the socket is set + assert_int_equal(network_get_bound_ip_socket(), sockfd); + + // Close the socket + network_close_socket(&sockfd); + + // Check that the socket is now INVALID_SOCKET + assert_int_equal(sockfd, INVALID_SOCKET); + + // cleanup + network_test_cleanup(state); +} + +void test_network_get_packet(void** state) { + + const char *mock_data = "Hello, World!"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); // for src_addr + will_return(wrap_recvfrom, mock_addr_len); // for addrlen + will_return(wrap_recvfrom, mock_data); // for buf content + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + network_set_bound_ip_socket(3); // Set the socket descriptor + + bool result = network_get_packet(); + + // Check the result + assert_true(result); + + // check message queue, should have one item + int queue_size = network_get_msg_queue_size(); + assert_int_equal(queue_size, 1); + + // cleanup + network_test_cleanup(state); +} + +void test_network_get_packet_no_data(void** state) { + network_clear_msg_queue(); + + const char *mock_data = "Hello, World!"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); // for src_addr + will_return(wrap_recvfrom, mock_addr_len); // for addrlen + will_return(wrap_recvfrom, mock_data); // for buf content + will_return(wrap_recvfrom, -1); // return value + + network_set_bound_ip_socket(3); // Set the socket descriptor + errno = EAGAIN; // Simulate no data available + bool result = network_get_packet(); + assert_false(result); + + // check message queue, should still be empty + int queue_size = network_get_msg_queue_size(); + assert_int_equal(queue_size, 0); + + // cleanup + network_test_cleanup(state); +} + +void test_network_get_local_subnet(void** state) { + + char my_ip[INET_ADDRSTRLEN]; + network_get_my_ip(my_ip, INET_ADDRSTRLEN); + + assert_non_null(my_ip); + assert_string_not_equal(my_ip, "127.0.0.1"); + + // this utility shouldn't change or set the bound socket + // for this client + int sockfd = network_get_bound_ip_socket(); + assert_int_equal(sockfd, INVALID_SOCKET); + + // cleanup + network_test_cleanup(state); +} diff --git a/tests/test_server.c b/tests/test_server.c new file mode 100644 index 0000000..8009e03 --- /dev/null +++ b/tests/test_server.c @@ -0,0 +1,203 @@ +#include +#include + +#include "utils.h" + +#include +#include +#include +#include +#include + +#include + +#if defined(WIN32) +#include +#else +#include +#include +#include +#endif + +#include + + +void empties_queue_after_process(void**) { + const char *mock_data = "hello"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); + will_return(wrap_recvfrom, mock_addr_len); + will_return(wrap_recvfrom, mock_data); + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + + const char* returned_data = "Hello from 0.0.0.0\n"; + + expect_value(wrap_sendto, sockfd, 3); + expect_string(wrap_sendto, buf, returned_data); + expect_value(wrap_sendto, len, strlen(returned_data)); + expect_value(wrap_sendto, flags, 0); + + will_return(wrap_sendto, "STRING"); // tag for wrap_sendto to know how to interpret buf + will_return(wrap_sendto, strlen(returned_data)); // return value + + network_set_bound_ip_socket(3); + network_get_packet(); + + int queue_size = network_get_msg_queue_size(); + assert_int_equal(queue_size, 1); + + // should run a step to check if we have work to do + server_process_queue(); + + queue_size = network_get_msg_queue_size(); + assert_int_equal(queue_size, 0); +} + +void unknown_message_echo(void**) { + const char *mock_data = "unknown"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); + will_return(wrap_recvfrom, mock_addr_len); + will_return(wrap_recvfrom, mock_data); + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + const char* returned_data = "Unknown command: unknown\n"; + + expect_value(wrap_sendto, sockfd, 3); + expect_string(wrap_sendto, buf, returned_data); + expect_value(wrap_sendto, len, strlen(returned_data)); + expect_value(wrap_sendto, flags, 0); + + will_return(wrap_sendto, "STRING"); // tag for wrap_sendto to know how to interpret buf + will_return(wrap_sendto, strlen(returned_data)); // return value + + network_set_bound_ip_socket(3); + network_get_packet(); + + int queue_size = network_get_msg_queue_size(); + assert_int_equal(queue_size, 1); + + server_process_queue(); +} + +void server_status_query(void**) { + const char *mock_data = "status"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); + will_return(wrap_recvfrom, mock_addr_len); + will_return(wrap_recvfrom, mock_data); + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + // response to status query + expect_value(wrap_sendto, sockfd, 3); + expect_not_value(wrap_sendto, buf, NULL); + expect_any(wrap_sendto, len); + expect_value(wrap_sendto, flags, 0); + + will_return(wrap_sendto, "STATUS"); // tag for wrap_sendto to know how to interpret buf + will_return(wrap_sendto, "MY SERVER"); + will_return(wrap_sendto, 8000); + will_return(wrap_sendto, 0); // return value + + network_set_bound_ip_socket(3); + network_get_packet(); + + server_process_queue(); +} + +void server_connect_client_ok(void**) { + + // client sends us a connect message + const char *mock_data = "connect"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); + will_return(wrap_recvfrom, mock_addr_len); + will_return(wrap_recvfrom, mock_data); + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + network_set_bound_ip_socket(3); + network_get_packet(); + + + // we tell the client they are connected + const char* returned_data = "connected"; + + expect_value(wrap_sendto, sockfd, 3); + expect_string(wrap_sendto, buf, returned_data); + expect_value(wrap_sendto, len, strlen(returned_data)); + expect_value(wrap_sendto, flags, 0); + + will_return(wrap_sendto, "STRING"); // tag for wrap_sendto to know how to interpret buf + will_return(wrap_sendto, strlen(returned_data)); // return value + + int connected_client_count = server_get_connected_clients_count(); + assert_int_equal(connected_client_count, 0); + server_process_queue(); + + connected_client_count = server_get_connected_clients_count(); + assert_int_equal(connected_client_count, 1); + client_t *client = server_get_client_by_index(0); + assert_non_null(client); + // TODO: check client IP +} + +void server_connect_fails_too_many_clients(void**) { + + server_set_connected_clients_count(8); // simulate 8 clients already connected + + // client sends us a connect message + const char *mock_data = "connect"; + struct sockaddr_in mock_addr = {0}; + socklen_t mock_addr_len = sizeof(mock_addr); + + expect_value(wrap_recvfrom, sockfd, 3); + expect_value(wrap_recvfrom, len, 99); + expect_value(wrap_recvfrom, flags, 0); + will_return(wrap_recvfrom, &mock_addr); + will_return(wrap_recvfrom, mock_addr_len); + will_return(wrap_recvfrom, mock_data); + will_return(wrap_recvfrom, strlen(mock_data)); // return value + + network_set_bound_ip_socket(3); + network_get_packet(); + + + // we tell the client they are connected + const char* returned_data = "connect_failed"; + + expect_value(wrap_sendto, sockfd, 3); + expect_string(wrap_sendto, buf, returned_data); + expect_value(wrap_sendto, len, strlen(returned_data)); + expect_value(wrap_sendto, flags, 0); + + will_return(wrap_sendto, "STRING"); // tag for wrap_sendto to know how to interpret buf + will_return(wrap_sendto, strlen(returned_data)); // return value + + int connected_client_count = server_get_connected_clients_count(); + assert_int_equal(connected_client_count, 8); + server_process_queue(); + + connected_client_count = server_get_connected_clients_count(); + assert_int_equal(connected_client_count, 8); +} \ No newline at end of file diff --git a/tests/utils.c b/tests/utils.c new file mode 100644 index 0000000..a14bf79 --- /dev/null +++ b/tests/utils.c @@ -0,0 +1,28 @@ + +#include "utils.h" + +#include +#include + +int network_test_cleanup(void** state) { + (void)state; // unused + // Reset the network state after each test + network_clear_msg_queue(); + network_set_bound_ip_socket(INVALID_SOCKET); + + return 0; +} + +int server_test_setup(void** state) { + (void)state; // unused + // Initialize the server state before each test + client_com_init(); + return 0; +} + +int server_test_cleanup(void** state) { + (void)state; // unused + // Reset the server state after each test + server_set_connected_clients_count(0); + return 0; +} \ No newline at end of file diff --git a/tests/utils.h b/tests/utils.h new file mode 100644 index 0000000..9a88b43 --- /dev/null +++ b/tests/utils.h @@ -0,0 +1,8 @@ + +#pragma once + +int network_test_cleanup(void** state); + +int server_test_setup(void** state); + +int server_test_cleanup(void** state); \ No newline at end of file diff --git a/vcpkg b/vcpkg new file mode 160000 index 0000000..ce613c4 --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit ce613c41372b23b1f51333815feb3edd87ef8a8b diff --git a/vcpkg.json b/vcpkg.json index d3e7408..ff665a4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "version-string": "1.0.0", "builtin-baseline": "0fa8459cf3a7caca7adc58f992bc32ff13630684", "dependencies": [ + "cmocka", { "name": "sdl2", "version>=": "2.26.5" @@ -10,6 +11,13 @@ { "name": "glew", "version>=": "2.2.0#3" - } + }, + "sokol", + "stb", + { + "name": "protobuf-c", + "features": ["tools"] + }, + "pkgconf" ] }